/* * MIT License * * Copyright (c) 2017 Michael VanOverbeek and ShiftOS devs * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using ShiftOS.Objects; using NetSockets; using System.Windows.Forms; using System.Threading; using ShiftOS; using static ShiftOS.Engine.SaveSystem; using Newtonsoft.Json; using System.Net.Sockets; using System.Diagnostics; using System.IO; using System.Reflection; namespace ShiftOS.Engine { /// /// Digital Society connection management class. /// public static class ServerManager { /// /// Print connection diagnostic information. /// public static void PrintDiagnostics() { Console.WriteLine($@"{{CLIENT_DIAGNOSTICS}} {{GUID}}: {thisGuid} Ping: {ServerManager.DigitalSocietyPing} ms {{CLIENT_DATA}}: {JsonConvert.SerializeObject(client, Formatting.Indented)}"); } /// /// Gets the unique identifier for this Digital Society connection. This can be used for peer-to-peer communication between two clients. /// public static Guid thisGuid { get; private set; } /// /// Gets the underlying NetSockets client for this connection. /// public static NetObjectClient client { get; private set; } private static bool UserDisconnect = false; /// /// Gets or sets the server response time for the last request made by this client. /// public static long DigitalSocietyPing { get; private set; } /// /// Disconnect from the digital society intentionally. /// public static void Disconnect() { UserDisconnect = true; if (client != null) { client.Disconnect(); } Disconnected?.Invoke(); } /// /// Occurs when you are disconnected from the Digital Society. /// public static event EmptyEventHandler Disconnected; /// /// Occurs when the unique ID for this client is sent by the server. /// public static event Action GUIDReceived; private static void delegateToServer(ServerMessage msg) { string[] split = msg.GUID.Split('|'); bool finished = false; foreach (var exec in Directory.GetFiles(Environment.CurrentDirectory)) { if(exec.ToLower().EndsWith(".exe") || exec.ToLower().EndsWith(".dll")) { try { var asm = Assembly.LoadFile(exec); foreach(var type in asm.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(Server)))) { var attrib = type.GetCustomAttributes().FirstOrDefault(x => x is ServerAttribute) as ServerAttribute; if(attrib != null) { if(split[0] == SaveSystem.CurrentSave.SystemName && split[1] == attrib.Port.ToString()) { if (Shiftorium.UpgradeAttributesUnlocked(type)) { type.GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(x => x.Name == "MessageReceived")?.Invoke(Activator.CreateInstance(type), null); finished = true; } } } } } catch { } } } if (finished == false) { Forward(split[2], "Error", $"{split[0]}:{split[1]}: connection refused"); } } private static void ServerManager_MessageReceived(ServerMessage msg) { switch(msg.Name) { case "getguid_fromserver": if(SaveSystem.CurrentSave.Username == msg.Contents) { client.Send(new NetObject("yes_i_am", new ServerMessage { Name = "getguid_reply", GUID = msg.GUID, Contents = thisGuid.ToString(), })); } break; case "msgtosys": try { var m = JsonConvert.DeserializeObject(msg.Contents); if(m.GUID.Split('|')[2] != thisGuid.ToString()) { delegateToServer(m); } } catch { } break; case "getguid_reply": GUIDReceived?.Invoke(msg.Contents); break; } } public static void SendMessageToIngameServer(string sysname, int port, string title, string contents) { var smsg = new ServerMessage { Name = title, GUID = $"{sysname}|{port}|{thisGuid.ToString()}", Contents = contents }; Forward("all", "msgtosys", JsonConvert.SerializeObject(smsg)); } public static void Detach_ServerManager_MessageReceived() { MessageReceived -= new ServerMessageReceived(ServerManager_MessageReceived); } /// /// Initiate a new Digital Society connection. /// /// The IP address or hostname of the target server /// The target port. public static void Initiate(string mud_address, int port) { client = new NetObjectClient(); client.OnDisconnected += (o, a) => { if (!UserDisconnect) { Desktop.PushNotification("digital_society_connection", "Disconnected from Digital Society.", "The ShiftOS kernel has been disconnected from the Digital Society. We are attempting to re-connect you."); TerminalBackend.PrefixEnabled = true; ConsoleEx.ForegroundColor = ConsoleColor.Red; ConsoleEx.Bold = true; Console.Write($@"Disconnected from MUD: "); ConsoleEx.Bold = false; ConsoleEx.Italic = true; ConsoleEx.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine("You have been disconnected from the multi-user domain for an unknown reason. Your save data is preserved within the kernel and you will be reconnected shortly."); TerminalBackend.PrefixEnabled = true; TerminalBackend.PrintPrompt(); Initiate(mud_address, port); } }; client.OnReceived += (o, a) => { if (PingTimer.IsRunning) { DigitalSocietyPing = PingTimer.ElapsedMilliseconds; PingTimer.Reset(); } var msg = a.Data.Object as ServerMessage; if (msg.Name == "Welcome") { thisGuid = new Guid(msg.Contents); GUIDReceived?.Invoke(msg.Contents); TerminalBackend.PrefixEnabled = true; TerminalBackend.PrintPrompt(); } else if(msg.Name == "allusers") { foreach(var acc in JsonConvert.DeserializeObject(msg.Contents)) { Console.WriteLine(acc); } TerminalBackend.PrintPrompt(); } else if(msg.Name == "update_your_cp") { var args = JsonConvert.DeserializeObject>(msg.Contents); if(args["username"] as string == SaveSystem.CurrentSave.Username) { SaveSystem.CurrentSave.Codepoints += (long)args["amount"]; Desktop.InvokeOnWorkerThread(new Action(() => { Infobox.Show($"MUD Control Centre", $"Someone bought an item in your shop, and they have paid {args["amount"]}, and as such, you have been granted these Codepoints."); })); SaveSystem.SaveGame(); } } else if(msg.Name =="broadcast") { Console.WriteLine(msg.Contents); } else if(msg.Name == "forward") { MessageReceived?.Invoke(JsonConvert.DeserializeObject(msg.Contents)); } else if (msg.Name == "Error") { var ex = JsonConvert.DeserializeObject(msg.Contents); TerminalBackend.PrefixEnabled = true; ConsoleEx.ForegroundColor = ConsoleColor.Red; ConsoleEx.Bold = true; Console.Write($@"{{MUD_ERROR}}: "); ConsoleEx.Bold = false; ConsoleEx.Italic = true; ConsoleEx.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(ex.Message); TerminalBackend.PrefixEnabled = true; TerminalBackend.PrintPrompt(); } else { MessageReceived?.Invoke(msg); } }; try { client.Connect(mud_address, port); } catch(SocketException ex) { System.Diagnostics.Debug.Print(ex.ToString()); Initiate(mud_address, port); } } private static Stopwatch PingTimer = new Stopwatch(); /// /// Send a message to the server. /// /// The message name /// The message body public static void SendMessage(string name, string contents) { var sMsg = new ServerMessage { Name = name, Contents = contents, GUID = thisGuid.ToString(), }; PingTimer.Start(); client.Send(new NetObject("msg", sMsg)); } private static bool singleplayer = false; public static bool IsSingleplayer { get { return singleplayer; } } public static void StartLANServer() { singleplayer = true; ShiftOS.Server.Program.ServerStarted += (address) => { Console.WriteLine($"Connecting to {address}..."); Initiate(address, 13370); }; Disconnected += () => { ShiftOS.Server.Program.Stop(); }; ShiftOS.Server.Program.Main(new[] { "" }); } /// /// Occurs when the server sends a message to this client. /// public static event ServerMessageReceived MessageReceived; /// /// Send a message to another client. /// /// The target client GUID. /// The message title /// The message contents public static void Forward(string targetGUID, string title, string message) { var smsg = new ServerMessage { GUID = targetGUID, Name = title, Contents = message }; ServerManager.SendMessage("mud_forward", JsonConvert.SerializeObject(smsg)); } } /// /// Delegate for handling server messages /// /// A server message containing the protocol message name, GUID of the sender, and the contents of the message. public delegate void ServerMessageReceived(ServerMessage msg); public class MultiplayerOnlyAttribute : Attribute { /// /// Marks this application as a multiplayer-only application. /// public MultiplayerOnlyAttribute() { } } }