From af5007e3b75eba3c000e43790c0ad1366245b117 Mon Sep 17 00:00:00 2001 From: Spirtix Date: Sat, 2 Sep 2023 22:52:48 +0200 Subject: [PATCH] initial code release --- .gitignore | 7 + sodoff-mmo.sln | 22 ++ src/Attributes/CommandHandlerAttribute.cs | 10 + .../ExtensionCommandHandlerAttribute.cs | 10 + src/CommandHandlers/ChatMessageHandler.cs | 33 +++ src/CommandHandlers/DateTimeHandler.cs | 17 ++ src/CommandHandlers/ExitHandler.cs | 12 ++ src/CommandHandlers/HandshakeHandler.cs | 41 ++++ src/CommandHandlers/JoinRoomHandler.cs | 106 ++++++++++ src/CommandHandlers/LoginHandler.cs | 63 ++++++ src/CommandHandlers/PingHandler.cs | 17 ++ .../SetPositionVariablesHandler.cs | 62 ++++++ .../SetUserVariablesHandler.cs | 170 +++++++++++++++ src/Core/Client.cs | 84 ++++++++ src/Core/ICommandHandler.cs | 6 + src/Core/ModuleManager.cs | 50 +++++ src/Core/Room.cs | 47 +++++ src/Core/Runtime.cs | 20 ++ src/Data/DataDecoder.cs | 55 +++++ src/Data/DataEncoder.cs | 132 ++++++++++++ src/Data/DataWrapper.cs | 11 + src/Data/NetworkArray.cs | 128 +++++++++++ src/Data/NetworkData.cs | 125 +++++++++++ src/Data/NetworkDataType.cs | 23 ++ src/Data/NetworkObject.cs | 198 ++++++++++++++++++ src/Data/NetworkPacket.cs | 74 +++++++ src/Data/PlayerData.cs | 64 ++++++ src/Program.cs | 5 + src/Server.cs | 77 +++++++ src/sodoffmmo.csproj | 14 ++ 30 files changed, 1683 insertions(+) create mode 100644 .gitignore create mode 100644 sodoff-mmo.sln create mode 100644 src/Attributes/CommandHandlerAttribute.cs create mode 100644 src/Attributes/ExtensionCommandHandlerAttribute.cs create mode 100644 src/CommandHandlers/ChatMessageHandler.cs create mode 100644 src/CommandHandlers/DateTimeHandler.cs create mode 100644 src/CommandHandlers/ExitHandler.cs create mode 100644 src/CommandHandlers/HandshakeHandler.cs create mode 100644 src/CommandHandlers/JoinRoomHandler.cs create mode 100644 src/CommandHandlers/LoginHandler.cs create mode 100644 src/CommandHandlers/PingHandler.cs create mode 100644 src/CommandHandlers/SetPositionVariablesHandler.cs create mode 100644 src/CommandHandlers/SetUserVariablesHandler.cs create mode 100644 src/Core/Client.cs create mode 100644 src/Core/ICommandHandler.cs create mode 100644 src/Core/ModuleManager.cs create mode 100644 src/Core/Room.cs create mode 100644 src/Core/Runtime.cs create mode 100644 src/Data/DataDecoder.cs create mode 100644 src/Data/DataEncoder.cs create mode 100644 src/Data/DataWrapper.cs create mode 100644 src/Data/NetworkArray.cs create mode 100644 src/Data/NetworkData.cs create mode 100644 src/Data/NetworkDataType.cs create mode 100644 src/Data/NetworkObject.cs create mode 100644 src/Data/NetworkPacket.cs create mode 100644 src/Data/PlayerData.cs create mode 100644 src/Program.cs create mode 100644 src/Server.cs create mode 100644 src/sodoffmmo.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4df4dab --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.vs +.vscode +.DS_Store +src/bin +src/obj +src/Properties +src/sodoffmmo.csproj.user \ No newline at end of file diff --git a/sodoff-mmo.sln b/sodoff-mmo.sln new file mode 100644 index 0000000..f56f5a4 --- /dev/null +++ b/sodoff-mmo.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sodoffmmo", "src\sodoffmmo.csproj", "{049E36E9-29EC-4A15-96A5-99A049BD2F83}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {049E36E9-29EC-4A15-96A5-99A049BD2F83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {049E36E9-29EC-4A15-96A5-99A049BD2F83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {049E36E9-29EC-4A15-96A5-99A049BD2F83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {049E36E9-29EC-4A15-96A5-99A049BD2F83}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Attributes/CommandHandlerAttribute.cs b/src/Attributes/CommandHandlerAttribute.cs new file mode 100644 index 0000000..c7adc7d --- /dev/null +++ b/src/Attributes/CommandHandlerAttribute.cs @@ -0,0 +1,10 @@ +namespace sodoffmmo.Attributes; + +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +class CommandHandlerAttribute : Attribute { + public int ID { get; } + + public CommandHandlerAttribute(int id) { + ID = id; + } +} \ No newline at end of file diff --git a/src/Attributes/ExtensionCommandHandlerAttribute.cs b/src/Attributes/ExtensionCommandHandlerAttribute.cs new file mode 100644 index 0000000..b50eef3 --- /dev/null +++ b/src/Attributes/ExtensionCommandHandlerAttribute.cs @@ -0,0 +1,10 @@ +namespace sodoffmmo.Attributes; + +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +class ExtensionCommandHandlerAttribute : Attribute { + public string Name { get; } + + public ExtensionCommandHandlerAttribute(string name) { + Name = name; + } +} \ No newline at end of file diff --git a/src/CommandHandlers/ChatMessageHandler.cs b/src/CommandHandlers/ChatMessageHandler.cs new file mode 100644 index 0000000..15c721e --- /dev/null +++ b/src/CommandHandlers/ChatMessageHandler.cs @@ -0,0 +1,33 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; + +namespace sodoffmmo.CommandHandlers; + +[ExtensionCommandHandler("SCM")] +class ChatMessageHandler : ICommandHandler { + public void Handle(Client client, NetworkObject receivedObject) { + string message = receivedObject.Get("p").Get("chm"); + + NetworkObject cmd = new(); + NetworkObject data = new(); + data.Add("arr", new string[] { "CMR", "-1", client.PlayerData.Uid, "1", message, "", "1", "placeholder" }); + cmd.Add("c", "CMR"); + cmd.Add("p", data); + + NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize(); + foreach (var roomClient in client.Room.Clients) { + if (roomClient != client) { + roomClient.Send(packet); + } + } + + cmd = new(); + data = new(); + data.Add("arr", new string[] { "SCA", "-1", "1", message, "", "1" }); + cmd.Add("c", "SCA"); + cmd.Add("p", data); + packet = NetworkObject.WrapObject(1, 13, cmd).Serialize(); + client.Send(packet); + } +} diff --git a/src/CommandHandlers/DateTimeHandler.cs b/src/CommandHandlers/DateTimeHandler.cs new file mode 100644 index 0000000..e2f4bc5 --- /dev/null +++ b/src/CommandHandlers/DateTimeHandler.cs @@ -0,0 +1,17 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; + +namespace sodoffmmo.CommandHandlers; + +[ExtensionCommandHandler("DT")] +class DateTimeHandler : ICommandHandler { + public void Handle(Client client, NetworkObject receivedObject) { + NetworkObject cmd = new(); + NetworkObject obj = new(); + obj.Add("arr", new string[] { "DT", DateTime.UtcNow.ToString("MM/dd/yyyy HH:mm") }); + cmd.Add("c", "DT"); + cmd.Add("p", obj); + client.Send(NetworkObject.WrapObject(1, 13, cmd).Serialize()); + } +} diff --git a/src/CommandHandlers/ExitHandler.cs b/src/CommandHandlers/ExitHandler.cs new file mode 100644 index 0000000..c3be2c6 --- /dev/null +++ b/src/CommandHandlers/ExitHandler.cs @@ -0,0 +1,12 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; + +namespace sodoffmmo.CommandHandlers; + +[CommandHandler(26)] +class ExitHandler : ICommandHandler { + public void Handle(Client client, NetworkObject receivedObject) { + client.LeaveRoom(); + } +} diff --git a/src/CommandHandlers/HandshakeHandler.cs b/src/CommandHandlers/HandshakeHandler.cs new file mode 100644 index 0000000..2eea93f --- /dev/null +++ b/src/CommandHandlers/HandshakeHandler.cs @@ -0,0 +1,41 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; +using System.Text; +using System; + +namespace sodoffmmo.CommandHandlers; + +[CommandHandler(0)] +class HandshakeHandler : ICommandHandler +{ + public void Handle(Client client, NetworkObject receivedObject) + { + string? token = receivedObject.Get("rt"); + if (token != null) { + client.Send(NetworkObject.WrapObject(0, 1006, new NetworkObject()).Serialize()); + return; + } + + NetworkObject obj = new(); + + obj.Add("tk", RandomString(32)); + obj.Add("ct", 1024); + obj.Add("ms", 1000000); + + client.Send(NetworkObject.WrapObject(0, 0, obj).Serialize()); + } + + private string RandomString(int length) { + Random random = new Random(); + const string pool = "abcdefghijklmnopqrstuvwxyz0123456789"; + var builder = new StringBuilder(); + + for (var i = 0; i < length; i++) { + var c = pool[random.Next(0, pool.Length)]; + builder.Append(c); + } + + return builder.ToString(); + } +} \ No newline at end of file diff --git a/src/CommandHandlers/JoinRoomHandler.cs b/src/CommandHandlers/JoinRoomHandler.cs new file mode 100644 index 0000000..6a9bdcd --- /dev/null +++ b/src/CommandHandlers/JoinRoomHandler.cs @@ -0,0 +1,106 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; + +namespace sodoffmmo.CommandHandlers; + +[ExtensionCommandHandler("JA")] +class JoinRoomHandler : ICommandHandler +{ + Room room; + Client client; + public void Handle(Client client, NetworkObject receivedObject) + { + string roomName = receivedObject.Get("p").Get("rn"); + client.LeaveRoom(); + client.InvalidatePlayerData(); + if (!Room.Exists(roomName)) + Room.Add(roomName); + room = Room.Get(roomName); + Console.WriteLine($"Join Room: {roomName} RoomID: {room.Id} IID: {client.internalId}"); + this.client = client; + + RespondJoinRoom(); + SubscribeRoom(); + UpdatePlayerUserVariables(); + client.Room = room; + } + + private void RespondJoinRoom() { + NetworkObject obj = new(); + NetworkArray roomInfo = new(); + roomInfo.Add(room.Id); + roomInfo.Add(room.Name); // Room Name + roomInfo.Add(room.Name); // Group Name + roomInfo.Add(true); + roomInfo.Add(false); + roomInfo.Add(false); + roomInfo.Add((short)24); + roomInfo.Add((short)27); + roomInfo.Add(new NetworkArray()); + roomInfo.Add((short)0); + roomInfo.Add((short)0); + + NetworkArray userList = new(); + foreach (Client player in room.Clients) { + userList.Add(player.PlayerData.GetNetworkData()); + } + + obj.Add("r", roomInfo); + obj.Add("ul", userList); + + NetworkPacket packet = NetworkObject.WrapObject(0, 4, obj).Serialize(); + packet.Compress(); + client.Send(packet); + } + + private void SubscribeRoom() { + NetworkObject obj = new(); + NetworkArray list = new(); + + NetworkArray r1 = new(); + r1.Add(room.Id); + r1.Add(room.Name); // Room Name + r1.Add(room.Name); // Group Name + r1.Add(true); + r1.Add(false); + r1.Add(false); + r1.Add((short)24); + r1.Add((short)27); + r1.Add(new NetworkArray()); + r1.Add((short)0); + r1.Add((short)0); + + NetworkArray r2 = new(); + r2.Add(room.Id); + r2.Add(room.Name); // Room Name + r2.Add(room.Name); // Group Name + r2.Add(true); + r2.Add(false); + r2.Add(false); + r2.Add((short)7); + r2.Add((short)27); + r2.Add(new NetworkArray()); + r2.Add((short)0); + r2.Add((short)0); + + list.Add(r1); + list.Add(r2); + + obj.Add("rl", list); + obj.Add("g", room.Name); + client.Send(NetworkObject.WrapObject(0, 15, obj).Serialize()); + } + + private void UpdatePlayerUserVariables() { + foreach (Client c in room.Clients) { + NetworkObject cmd = new(); + NetworkObject obj = new(); + cmd.Add("c", "SUV"); + obj.Add("MID", c.PlayerData.Id); + cmd.Add("p", obj); + client.Send(NetworkObject.WrapObject(1, 13, cmd).Serialize()); + } + + } +} \ No newline at end of file diff --git a/src/CommandHandlers/LoginHandler.cs b/src/CommandHandlers/LoginHandler.cs new file mode 100644 index 0000000..3927fe2 --- /dev/null +++ b/src/CommandHandlers/LoginHandler.cs @@ -0,0 +1,63 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; + +namespace sodoffmmo.CommandHandlers; + +[CommandHandler(1)] +class LoginHandler : ICommandHandler +{ + public void Handle(Client client, NetworkObject receivedObject) + { + NetworkArray rl = new(); + + NetworkArray r1 = new(); + r1.Add(0); + r1.Add("MP_SYS"); + r1.Add("default"); + r1.Add(true); + r1.Add(false); + r1.Add(false); + r1.Add((short)0); + r1.Add((short)10); + r1.Add(new NetworkArray()); + r1.Add((short)0); + r1.Add((short)0); + rl.Add(r1); + + NetworkArray r2 = new(); + + r2.Add(1); + r2.Add("ADMIN"); + r2.Add("default"); + r2.Add(false); + r2.Add(false); + r2.Add(true); + r2.Add((short)0); + r2.Add((short)1); + r2.Add(new NetworkArray()); + rl.Add(r2); + + NetworkArray r3 = new(); + r3.Add(2); + r3.Add("LIMBO"); + r3.Add("default"); + r3.Add(false); + r3.Add(false); + r3.Add(false); + r3.Add((short)31); + r3.Add((short)10000); + r3.Add(new NetworkArray()); + rl.Add(r3); + + NetworkObject content = new(); + content.Add("rl", rl); + content.Add("zn", "JumpStart"); + content.Add("rs", (short)5); + content.Add("un", "005fd387-c264-410f-acf3-dbe3a06aaffa"); + content.Add("id", 1143760); + content.Add("pi", (short)1); + + client.Send(NetworkObject.WrapObject(0, 1, content).Serialize()); + } +} \ No newline at end of file diff --git a/src/CommandHandlers/PingHandler.cs b/src/CommandHandlers/PingHandler.cs new file mode 100644 index 0000000..b5d3478 --- /dev/null +++ b/src/CommandHandlers/PingHandler.cs @@ -0,0 +1,17 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; + +namespace sodoffmmo.CommandHandlers; + +[ExtensionCommandHandler("PNG")] +class PingHandler : ICommandHandler { + public void Handle(Client client, NetworkObject receivedObject) { + NetworkObject cmd = new(); + NetworkObject obj = new(); + obj.Add("arr", new string[] { "PNG", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() }); + cmd.Add("c", "PNG"); + cmd.Add("p", obj); + client.Send(NetworkObject.WrapObject(1, 13, cmd).Serialize()); + } +} diff --git a/src/CommandHandlers/SetPositionVariablesHandler.cs b/src/CommandHandlers/SetPositionVariablesHandler.cs new file mode 100644 index 0000000..17b3547 --- /dev/null +++ b/src/CommandHandlers/SetPositionVariablesHandler.cs @@ -0,0 +1,62 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; + +namespace sodoffmmo.CommandHandlers; + +[ExtensionCommandHandler("SPV")] +class SetPositionVariablesHandler : ICommandHandler { + Client client; + NetworkObject spvData; + public void Handle(Client client, NetworkObject receivedObject) { + this.client = client; + spvData = receivedObject; + UpdatePositionVariables(); + SendSPVCommand(); + } + + private void UpdatePositionVariables() { + NetworkObject obj = spvData.Get("p"); + float[] pos = obj.Get("U"); + client.PlayerData.R = obj.Get("R"); + client.PlayerData.P1 = pos[0]; + client.PlayerData.P2 = pos[1]; + client.PlayerData.P3 = pos[2]; + client.PlayerData.R1 = pos[3]; + client.PlayerData.R2 = pos[4]; + client.PlayerData.R3 = pos[5]; + client.PlayerData.Mx = obj.Get("MX"); + client.PlayerData.F = obj.Get("F"); + client.PlayerData.Mbf = obj.Get("MBF"); + } + + private void SendSPVCommand() { + NetworkObject cmd = new(); + NetworkObject obj = new(); + NetworkArray container = new(); + NetworkObject vars = new(); + vars.Add("MX", (float)client.PlayerData.Mx); + vars.Add("ST", Runtime.CurrentRuntime); + vars.Add("NT", Runtime.CurrentRuntime.ToString()); + vars.Add("t", (int)(Runtime.CurrentRuntime / 1000)); + vars.Add("F", client.PlayerData.F); + vars.Add("MBF", client.PlayerData.Mbf); + vars.Add("R", client.PlayerData.R); + vars.Add("U", new float[] { (float)client.PlayerData.P1, (float)client.PlayerData.P2, (float)client.PlayerData.P3, (float)client.PlayerData.R1, (float)client.PlayerData.R2, (float)client.PlayerData.R3 }); + vars.Add("MID", client.PlayerData.Id); + + container.Add(vars); + obj.Add("arr", container); + cmd.Add("c", "SPV"); + cmd.Add("p", obj); + + + NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize(); + lock (client.Room.roomLock) { + foreach (var roomClient in client.Room.Clients) { + if (roomClient != client) + roomClient.Send(packet); + } + } + } +} diff --git a/src/CommandHandlers/SetUserVariablesHandler.cs b/src/CommandHandlers/SetUserVariablesHandler.cs new file mode 100644 index 0000000..39d1d9d --- /dev/null +++ b/src/CommandHandlers/SetUserVariablesHandler.cs @@ -0,0 +1,170 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; +using sodoffmmo.Data; +using System.Globalization; + +namespace sodoffmmo.CommandHandlers; + +[ExtensionCommandHandler("SUV")] +class SetUserVariablesHandler : ICommandHandler { + NetworkObject suvData; + Client client; + string? uid; + public void Handle(Client client, NetworkObject receivedObject) { + this.client = client; + suvData = receivedObject.Get("p"); + uid = suvData.Get("UID"); + + // TODO + if (uid != null && client.PlayerData.Uid != uid) + ProcessPlayerData(); + else + UpdateVars(); + + } + + private void ProcessPlayerData() { + client.PlayerData.Uid = uid; + client.PlayerData.A = suvData.Get("A"); + client.PlayerData.Cu = suvData.Get("CU"); + client.PlayerData.M = suvData.Get("M"); + client.PlayerData.L = suvData.Get("L"); + client.PlayerData.Ra = suvData.Get("RA"); + string udt = suvData.Get("UDT"); + if (udt != null) + client.PlayerData.Udt = udt; + client.PlayerData.P1 = double.Parse(suvData.Get("P1"), CultureInfo.InvariantCulture); + client.PlayerData.P2 = double.Parse(suvData.Get("P2"), CultureInfo.InvariantCulture); + client.PlayerData.P3 = double.Parse(suvData.Get("P3"), CultureInfo.InvariantCulture); + client.PlayerData.R1 = double.Parse(suvData.Get("R1"), CultureInfo.InvariantCulture); + client.PlayerData.R2 = double.Parse(suvData.Get("R2"), CultureInfo.InvariantCulture); + client.PlayerData.R3 = double.Parse(suvData.Get("R3"), CultureInfo.InvariantCulture); + client.PlayerData.R = double.Parse(suvData.Get("R"), CultureInfo.InvariantCulture); + client.PlayerData.Mbf = int.Parse(suvData.Get("MBF")); + client.PlayerData.F = int.Parse(suvData.Get("F")); + client.PlayerData.J = suvData.Get("J"); + client.PlayerData.Bu = suvData.Get("BU"); + client.PlayerData.Fp = suvData.Get("FP"); + client.PlayerData.Id = client.Room.NextPlayerId(); + Console.WriteLine($"SUV {client.Room.Name} IID: {client.internalId}"); + client.Room.AddClient(client); + UpdatePlayersInRoom(); + SendSUVToPlayerInRoom(); + } + + private void UpdateVars() { + string? FP = suvData.Get("FP"); + string? PU = suvData.Get("PU"); + string? L = suvData.Get("L"); + if (FP is null && PU is null && L is null) { + return; // TODO + } + NetworkObject data = new(); + NetworkObject data2 = new(); + data.Add("u", client.PlayerData.Id); + + NetworkArray vl = new(); + if (FP != null) { + client.PlayerData.Fp = FP; + data2.Add("FP", client.PlayerData.Fp); + vl.Add(NetworkArray.StringParam("FP", client.PlayerData.Fp)); + } + if (PU != null) { + client.PlayerData.Pu = PU; + data2.Add("PU", client.PlayerData.Pu); + vl.Add(NetworkArray.StringParam("PU", client.PlayerData.Pu)); + } + if (L != null) { + client.PlayerData.L = L; + data2.Add("L", client.PlayerData.L); + vl.Add(NetworkArray.StringParam("L", client.PlayerData.L)); + } + data.Add("vl", vl); + + NetworkPacket packet = NetworkObject.WrapObject(0, 12, data).Serialize(); + lock (client.Room.roomLock) { + foreach (var roomClient in client.Room.Clients) { + if (roomClient != client) + roomClient.Send(packet); + } + } + + NetworkObject cmd = new(); + cmd.Add("c", "SUV"); + + NetworkObject container = new(); + + NetworkArray arr = new(); + data2.Add("RID", "1"); + data2.Add("MID", client.PlayerData.Id); + arr.Add(data2); + container.Add("arr", arr); + cmd.Add("p", container); + packet = NetworkObject.WrapObject(1, 13, cmd).Serialize(); + foreach (var roomClient in client.Room.Clients) { + if (roomClient != client) + roomClient.Send(packet); + } + } + + private void UpdatePlayersInRoom() { + NetworkObject data = new(); + data.Add("r", client.Room.Id); + + NetworkArray user = new(); + user.Add(client.PlayerData.Id); + user.Add(client.PlayerData.Uid); + user.Add((short)1); + user.Add((short)client.PlayerData.Id); + + NetworkArray playerData = new(); + playerData.Add(NetworkArray.DoubleParam("R1", client.PlayerData.R1)); + playerData.Add(NetworkArray.StringParam("FP", client.PlayerData.Fp)); + playerData.Add(NetworkArray.DoubleParam("MX", client.PlayerData.Mx)); + playerData.Add(NetworkArray.StringParam("UDT", client.PlayerData.Udt)); + playerData.Add(NetworkArray.DoubleParam("P2", client.PlayerData.P2)); + playerData.Add(NetworkArray.DoubleParam("NT", Runtime.CurrentRuntime)); + playerData.Add(NetworkArray.IntParam("t", (int)(Runtime.CurrentRuntime / 1000))); + playerData.Add(NetworkArray.StringParam("J", client.PlayerData.J)); + playerData.Add(NetworkArray.IntParam("F", client.PlayerData.F)); + playerData.Add(NetworkArray.IntParam("MBF", client.PlayerData.Mbf)); + playerData.Add(NetworkArray.DoubleParam("R2", client.PlayerData.R2)); + playerData.Add(NetworkArray.DoubleParam("R", client.PlayerData.R)); + playerData.Add(NetworkArray.StringParam("BU", client.PlayerData.Bu)); + playerData.Add(NetworkArray.DoubleParam("P1", client.PlayerData.P1)); + playerData.Add(NetworkArray.StringParam("UID", client.PlayerData.Uid)); + playerData.Add(NetworkArray.DoubleParam("R3", client.PlayerData.R3)); + playerData.Add(NetworkArray.StringParam("PU", client.PlayerData.Pu)); + playerData.Add(NetworkArray.StringParam("A", client.PlayerData.A)); + playerData.Add(NetworkArray.StringParam("RA", client.PlayerData.Ra)); + playerData.Add(NetworkArray.DoubleParam("P3", client.PlayerData.P3)); + playerData.Add(NetworkArray.StringParam("CU", client.PlayerData.Cu)); + playerData.Add(NetworkArray.StringParam("M", client.PlayerData.M)); + playerData.Add(NetworkArray.StringParam("L", client.PlayerData.L)); + + user.Add(playerData); + data.Add("u", user); + NetworkPacket packet = NetworkObject.WrapObject(0, 1000, data).Serialize(); + packet.Compress(); + + foreach (var roomClient in client.Room.Clients) { + if (roomClient != client) + roomClient.Send(packet); + } + } + + private void SendSUVToPlayerInRoom() { + NetworkObject cmd = new(); + NetworkObject obj = new(); + + cmd.Add("c", "SUV"); + obj.Add("MID", client.PlayerData.Id); + cmd.Add("p", obj); + + NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize(); + foreach (var roomClient in client.Room.Clients) { + if (roomClient != client.Room.Clients) + roomClient.Send(packet); + } + } +} diff --git a/src/Core/Client.cs b/src/Core/Client.cs new file mode 100644 index 0000000..9ebddb5 --- /dev/null +++ b/src/Core/Client.cs @@ -0,0 +1,84 @@ +using sodoffmmo.Data; +using System; +using System.Net.Sockets; + +namespace sodoffmmo.Core; +public class Client { + static int id; + static object lck = new(); // For debugging purposes + public int internalId; // --- + public PlayerData PlayerData { get; set; } = new(); + public Room Room { get; set; } + + private readonly Socket socket; + private NetworkData? lastData; + private NetworkPacket? incompleteData; + private bool hasIncompletePakcet = false; + + public Client(Socket clientSocket) { + socket = clientSocket; + lock (lck) { + internalId = ++id; + } + } + + public async Task Receive() { + byte[] buffer = new byte[2048]; + int len = await socket.ReceiveAsync(buffer, SocketFlags.None); + if (len == 0) + throw new SocketException(); + lastData = new NetworkData(buffer); + } + + public bool TryGetNextPacket(out NetworkPacket packet) { + packet = new(); + if (hasIncompletePakcet) { + // TODO + } + byte header = lastData!.ReadByte(); + if (header != 0x80 && header != 0xa0) + return false; + short length = lastData.ReadShort(); + byte[] data = lastData.ReadChunk(length); + packet = new NetworkPacket(header, data); + return true; + } + + public void Send(NetworkPacket packet) { + socket.Send(packet.SendData); + } + + public void LeaveRoom() { + if (Room != null) { + Console.WriteLine($"Leave room {Room.Name} IID: {internalId}"); + Room.RemoveClient(this); + NetworkObject data = new(); + data.Add("r", Room.Id); + data.Add("u", PlayerData.Id); + + NetworkPacket packet = NetworkObject.WrapObject(0, 1004, data).Serialize(); + foreach (var roomClient in Room.Clients) { + roomClient.Send(packet); + } + Room = null; + } + } + + public void InvalidatePlayerData() { + PlayerData = new(); + } + + public void Disconnect() { + try { + socket.Shutdown(SocketShutdown.Both); + } finally { + socket.Close(); + } + } + + public bool Connected { + get { + return socket.Connected; + } + } +} diff --git a/src/Core/ICommandHandler.cs b/src/Core/ICommandHandler.cs new file mode 100644 index 0000000..e4c0b34 --- /dev/null +++ b/src/Core/ICommandHandler.cs @@ -0,0 +1,6 @@ +using sodoffmmo.Data; + +namespace sodoffmmo.Core; +public interface ICommandHandler { + public void Handle(Client client, NetworkObject receivedObject); +} diff --git a/src/Core/ModuleManager.cs b/src/Core/ModuleManager.cs new file mode 100644 index 0000000..d83ec98 --- /dev/null +++ b/src/Core/ModuleManager.cs @@ -0,0 +1,50 @@ +using System.Reflection; +using sodoffmmo.Attributes; + +namespace sodoffmmo.Core; + +class ModuleManager { + Dictionary handlers = new(); + Dictionary extHandlers = new(); + + public void RegisterModules() { + RegisterHandlers(); + RegisterExtensionHandlers(); + } + + public ICommandHandler GetCommandHandler(int id) { + if (handlers.TryGetValue(id, out Type? handler)) + return (ICommandHandler)Activator.CreateInstance(handler)!; + throw new Exception($"Command handler with ID {id} not found!"); + } + + public ICommandHandler GetCommandHandler(string name) { + if (extHandlers.TryGetValue(name, out Type? handler)) + return (ICommandHandler)Activator.CreateInstance(handler)!; + throw new Exception($"Command handler with name \"{name}\" not found!"); + } + + private void RegisterHandlers() { + handlers.Clear(); + var handlerTypes = Assembly.GetExecutingAssembly().GetTypes() + .Where(type => typeof(ICommandHandler).IsAssignableFrom(type)) + .Where(type => type.GetCustomAttribute() != null); + + foreach (var handlerType in handlerTypes) { + CommandHandlerAttribute attrib = (handlerType.GetCustomAttribute(typeof(CommandHandlerAttribute)) as CommandHandlerAttribute)!; + handlers[attrib.ID] = handlerType; + } + } + + private void RegisterExtensionHandlers() { + extHandlers.Clear(); + var extHandlerTypes = Assembly.GetExecutingAssembly().GetTypes() + .Where(type => typeof(ICommandHandler).IsAssignableFrom(type)) + .Where(type => type.GetCustomAttribute() != null); + + foreach (var extHandlerType in extHandlerTypes) { + ExtensionCommandHandlerAttribute attrib = (extHandlerType.GetCustomAttribute(typeof(ExtensionCommandHandlerAttribute)) as ExtensionCommandHandlerAttribute)!; + extHandlers[attrib.Name] = extHandlerType; + } + } +} \ No newline at end of file diff --git a/src/Core/Room.cs b/src/Core/Room.cs new file mode 100644 index 0000000..d0fcfa4 --- /dev/null +++ b/src/Core/Room.cs @@ -0,0 +1,47 @@ +using System; + +namespace sodoffmmo.Core; +public class Room { + static int playerId = 0; + static int roomId = 0; + static Dictionary rooms = new(); + public object roomLock = new object(); + + List clients = new(); + + public string Name { get; private set; } + public int Id { get; private set; } + + public Room(int id, string name) { + Name = name; + Id = id; + } + + public IEnumerable Clients { + get { + return new List(clients); + } + } + + public void AddClient(Client client) { + lock (roomLock) { + clients.Add(client); + } + } + + public void RemoveClient(Client client) { + lock (roomLock) { + clients.Remove(client); + } + } + + public int NextPlayerId() => ++playerId; + + public static Room Get(string name) => rooms[name]; + + public static bool Exists(string name) => rooms.ContainsKey(name); + + public static void Add(string name) { + rooms[name] = new Room(rooms.Count + 1, name); + } +} diff --git a/src/Core/Runtime.cs b/src/Core/Runtime.cs new file mode 100644 index 0000000..0482a84 --- /dev/null +++ b/src/Core/Runtime.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; + +namespace sodoffmmo.Core; +public static class Runtime { + static Runtime() { + var currentProcess = Process.GetCurrentProcess(); + lastSystemTime = (long)(DateTime.Now - currentProcess.StartTime).TotalMilliseconds; + currentProcess.Dispose(); + stopwatch = new Stopwatch(); stopwatch.Start(); + } + + private static long lastSystemTime; + private static Stopwatch stopwatch; + + public static long CurrentRuntime { + get { + return stopwatch.ElapsedMilliseconds + lastSystemTime; + } + } +} diff --git a/src/Data/DataDecoder.cs b/src/Data/DataDecoder.cs new file mode 100644 index 0000000..64ee027 --- /dev/null +++ b/src/Data/DataDecoder.cs @@ -0,0 +1,55 @@ +namespace sodoffmmo.Data; + +internal static class DataDecoder { + internal static DataWrapper DecodeNull(NetworkData data) => new DataWrapper(NetworkDataType.Null, null!); + + internal static DataWrapper DecodeBool(NetworkData data) => new DataWrapper(NetworkDataType.Bool, data.ReadBool()); + + internal static DataWrapper DecodeByte(NetworkData data) => new DataWrapper(NetworkDataType.Byte, data.ReadByte()); + + internal static DataWrapper DecodeShort(NetworkData data) => new DataWrapper(NetworkDataType.Short, data.ReadShort()); + + internal static DataWrapper DecodeInt(NetworkData data) => new DataWrapper(NetworkDataType.Int, data.ReadInt()); + + internal static DataWrapper DecodeLong(NetworkData data) => new DataWrapper(NetworkDataType.Long, data.ReadLong()); + + internal static DataWrapper DecodeFloat(NetworkData data) => new DataWrapper(NetworkDataType.Float, data.ReadFloat()); + + internal static DataWrapper DecodeDouble(NetworkData data) => new DataWrapper(NetworkDataType.Double, data.ReadDouble()); + + internal static DataWrapper DecodeString(NetworkData data) => new DataWrapper(NetworkDataType.String, data.ReadString()); + + internal static DataWrapper DecodeByteArray(NetworkData data) { + int count = data.ReadInt(); + return new DataWrapper(NetworkDataType.ByteArray, data.ReadChunk(count)); + } + + internal static DataWrapper DecodeBoolArray(NetworkData data) + => new DataWrapper(NetworkDataType.BoolArray, DecodeTypedArray(data, d => d.ReadBool())); + + internal static DataWrapper DecodeShortArray(NetworkData data) + => new DataWrapper(NetworkDataType.ShortArray, DecodeTypedArray(data, d => d.ReadShort())); + + internal static DataWrapper DecodeIntArray(NetworkData data) + => new DataWrapper(NetworkDataType.IntArray, DecodeTypedArray(data, d => d.ReadInt())); + + internal static DataWrapper DecodeLongArray(NetworkData data) + => new DataWrapper(NetworkDataType.LongArray, DecodeTypedArray(data, d => d.ReadLong())); + + internal static DataWrapper DecodeFloatArray(NetworkData data) + => new DataWrapper(NetworkDataType.FloatArray, DecodeTypedArray(data, d => d.ReadFloat())); + + internal static DataWrapper DecodeDoubleArray(NetworkData data) + => new DataWrapper(NetworkDataType.DoubleArray, DecodeTypedArray(data, d => d.ReadDouble())); + + internal static DataWrapper DecodeStringArray(NetworkData data) + => new DataWrapper(NetworkDataType.IntArray, DecodeTypedArray(data, d => d.ReadString())); + + private static T[] DecodeTypedArray(NetworkData data, Func readFunction) { + short length = data.ReadShort(); + T[] arr = new T[length]; + for (short i = 0; i < length; i++) + arr[i] = readFunction(data); + return arr; + } +} diff --git a/src/Data/DataEncoder.cs b/src/Data/DataEncoder.cs new file mode 100644 index 0000000..9d005f5 --- /dev/null +++ b/src/Data/DataEncoder.cs @@ -0,0 +1,132 @@ +namespace sodoffmmo.Data; +internal static class DataEncoder { + internal static byte[] EncodeNull() => new byte[] { 0 }; + + internal static byte[] EncodeByte(byte value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.Byte); + data.WriteValue(value); + return data.Data; + } + + internal static byte[] EncodeBool(bool value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.Bool); + data.WriteValue(value); + return data.Data; + } + + internal static byte[] EncodeShort(short value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.Short); + data.WriteValue(value); + return data.Data; + } + + internal static byte[] EncodeInt(int value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.Int); + data.WriteValue(value); + return data.Data; + } + + internal static byte[] EncodeLong(long value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.Long); + data.WriteValue(value); + return data.Data; + } + + internal static byte[] EncodeFloat(float value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.Float); + data.WriteValue(value); + return data.Data; + } + + internal static byte[] EncodeDouble(double value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.Double); + data.WriteValue(value); + return data.Data; + } + + internal static byte[] EncodeString(string value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.String); + data.WriteValue(value); + return data.Data; + } + + internal static byte[] EncodeBoolArray(bool[] value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.BoolArray); + data.WriteValue(Convert.ToInt16(value.Length)); + for (int i = 0; i < value.Length; i++) + data.WriteValue(value[i]); + return data.Data; + } + + internal static byte[] EncodeByteArray(byte[] value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.ByteArray); + data.WriteValue(Convert.ToInt16(value.Length)); + for (int i = 0; i < value.Length; i++) + data.WriteValue(value[i]); + return data.Data; + } + + internal static byte[] EncodeShortArray(short[] value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.ShortArray); + data.WriteValue(Convert.ToInt16(value.Length)); + for (int i = 0; i < value.Length; i++) + data.WriteValue(value[i]); + return data.Data; + } + + internal static byte[] EncodeIntArray(int[] value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.IntArray); + data.WriteValue(Convert.ToInt16(value.Length)); + for (int i = 0; i < value.Length; i++) + data.WriteValue(value[i]); + return data.Data; + } + + internal static byte[] EncodeLongArray(long[] value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.LongArray); + data.WriteValue(Convert.ToInt16(value.Length)); + for (int i = 0; i < value.Length; i++) + data.WriteValue(value[i]); + return data.Data; + } + + internal static byte[] EncodeFloatArray(float[] value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.FloatArray); + data.WriteValue(Convert.ToInt16(value.Length)); + for (int i = 0; i < value.Length; i++) + data.WriteValue(value[i]); + return data.Data; + } + + internal static byte[] EncodeDoubleArray(double[] value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.DoubleArray); + data.WriteValue(Convert.ToInt16(value.Length)); + for (int i = 0; i < value.Length; i++) + data.WriteValue(value[i]); + return data.Data; + } + + internal static byte[] EncodeStringArray(string[] value) { + NetworkData data = new(); + data.WriteValue((byte)NetworkDataType.StringArray); + data.WriteValue(Convert.ToInt16(value.Length)); + for (int i = 0; i < value.Length; i++) + data.WriteValue(value[i]); + return data.Data; + } +} diff --git a/src/Data/DataWrapper.cs b/src/Data/DataWrapper.cs new file mode 100644 index 0000000..a013c49 --- /dev/null +++ b/src/Data/DataWrapper.cs @@ -0,0 +1,11 @@ +namespace sodoffmmo.Data; +public class DataWrapper { + + public int Type { get; private set; } + public object Data { get; private set; } + + public DataWrapper(NetworkDataType type, object data) { + this.Type = (int)type; + this.Data = data; + } +} diff --git a/src/Data/NetworkArray.cs b/src/Data/NetworkArray.cs new file mode 100644 index 0000000..31756a6 --- /dev/null +++ b/src/Data/NetworkArray.cs @@ -0,0 +1,128 @@ +namespace sodoffmmo.Data; +public class NetworkArray { + List arrData = new(); + + public DataWrapper this[int i] { + get { return arrData[i]; } + } + + public int Length { + get { return arrData.Count; } + } + + public void Add(bool value) => AddObject(NetworkDataType.Bool, value); + + public void Add(byte value) => AddObject(NetworkDataType.Byte, value); + + public void Add(short value) => AddObject(NetworkDataType.Short, value); + + public void Add(int value) => AddObject(NetworkDataType.Int, value); + + public void Add(long value) => AddObject(NetworkDataType.Long, value); + + public void Add(float value) => AddObject(NetworkDataType.Float, value); + + public void Add(double value) => AddObject(NetworkDataType.Double, value); + + public void Add(string value) => AddObject(NetworkDataType.String, value); + + public void Add(bool[] value) => AddObject(NetworkDataType.BoolArray, value); + + public void Add(NetworkData value) => AddObject(NetworkDataType.ByteArray, value.Data); + + public void Add(short[] value) => AddObject(NetworkDataType.ShortArray, value); + + public void Add(int[] value) => AddObject(NetworkDataType.IntArray, value); + + public void Add(long[] value) => AddObject(NetworkDataType.LongArray, value); + + public void Add(float[] value) => AddObject(NetworkDataType.FloatArray, value); + + public void Add(double[] value) => AddObject(NetworkDataType.DoubleArray, value); + + public void Add(string[] value) => AddObject(NetworkDataType.StringArray, value); + + public void Add(NetworkArray value) => AddObject(NetworkDataType.NetworkArray, value); + + public void Add(NetworkObject value) => AddObject(NetworkDataType.NetworkObject, value); + + public void Add(DataWrapper dataWrapper) => arrData.Add(dataWrapper); + + private void AddObject(NetworkDataType dataType, object obj) => Add(new DataWrapper(dataType, obj)); + + public T GetValue(int index) { + if (index >= arrData.Count) + throw new IndexOutOfRangeException(); + return (T)arrData[index].Data; + } + + public bool GetBool(int index) => GetValue(index); + + public byte GetByte(int index) => GetValue(index); + + public short GetShort(int index) => GetValue(index); + + public int GetInt(int index) => GetValue(index); + + public long GetLong(int index) => GetValue(index); + + public float GetFloat(int index) => GetValue(index); + + public double GetDouble(int index) => GetValue(index); + + public string GetUtfString(int index) => GetValue(index); + + public bool[] GetBoolArray(int index) => GetValue(index); + + public NetworkData GetNetworkData(int index) => GetValue(index); + + public short[] GetShortArray(int index) => GetValue(index); + + public int[] GetIntArray(int index) => GetValue(index); + + public long[] GetLongArray(int index) => GetValue(index); + + public float[] GetFloatArray(int index) => GetValue(index); + + public double[] GetDoubleArray(int index) => GetValue(index); + + public string[] GetStringArray(int index) => GetValue(index); + + public NetworkArray GetNetworkArray(int index) => GetValue(index); + + public NetworkObject GetNetworkObject(int index) => GetValue(index); + + public bool Contains(object obj) { + if (obj is NetworkObject || obj is NetworkArray) + throw new Exception("Unsupported object type"); + for (int i = 0; i < arrData.Count; i++) { + if (object.Equals(arrData[i], obj)) + return true; + } + return false; + } + + public static NetworkArray DoubleParam(string name, double value) { + NetworkArray arr = new(); + arr.Add(name); + arr.Add((byte)3); + arr.Add(value); + return arr; + } + + public static NetworkArray StringParam(string name, string value) { + NetworkArray arr = new(); + arr.Add(name); + arr.Add((byte)4); + arr.Add(value); + return arr; + } + + public static NetworkArray IntParam(string name, int value) { + NetworkArray arr = new(); + arr.Add(name); + arr.Add((byte)2); + arr.Add(value); + return arr; + } +} diff --git a/src/Data/NetworkData.cs b/src/Data/NetworkData.cs new file mode 100644 index 0000000..e5e8602 --- /dev/null +++ b/src/Data/NetworkData.cs @@ -0,0 +1,125 @@ +using System.Text; + +namespace sodoffmmo.Data; +public class NetworkData { + byte[] data; + int offset = 0; + + public byte[] Data { + get { return data; } + } + + public NetworkData() { + data = new byte[0]; + } + + public NetworkData(byte[] data) { + this.data = data; + } + + public void Seek(int offset) { + int newOffset = this.offset + offset; + if (newOffset >= 0 && newOffset < data.Length) + this.offset = newOffset; + } + + public byte[] ReverseOrder(byte[] data) { + if (!BitConverter.IsLittleEndian) return data; + Array.Reverse(data); + return data; + } + + public byte ReadByte() { + return data[offset++]; + } + public byte[] ReadChunk(int count) { + byte[] chunk = new byte[count]; + Buffer.BlockCopy(data, offset, chunk, 0, count); + offset += count; + return chunk; + } + + public short ReadShort() { + byte[] arr = ReverseOrder(ReadChunk(2)); + return BitConverter.ToInt16(arr); + } + + public ushort ReadUShort() { + byte[] arr = ReverseOrder(ReadChunk(2)); + return BitConverter.ToUInt16(arr); + } + + public bool ReadBool() => data[offset++] == 1; + + public int ReadInt() { + byte[] arr = ReverseOrder(ReadChunk(4)); + return BitConverter.ToInt32(arr); + } + + public long ReadLong() { + byte[] arr = ReverseOrder(ReadChunk(8)); + return BitConverter.ToInt64(arr); + } + + public float ReadFloat() { + byte[] arr = ReverseOrder(ReadChunk(4)); + return BitConverter.ToSingle(arr); + } + + public double ReadDouble() { + byte[] arr = ReverseOrder(ReadChunk(8)); + return BitConverter.ToDouble(arr); + } + + public string ReadString() { + ushort count = ReadUShort(); + string str = Encoding.UTF8.GetString(data, offset, count); + offset += count; + return str; + } + + public void WriteChunk(byte[] chunk, int offset, int count) { + byte[] newData = new byte[data.Length + count]; + Buffer.BlockCopy(data, 0, newData, 0, data.Length); + Buffer.BlockCopy(chunk, offset, newData, data.Length, count); + data = newData; + } + + public void WriteChunk(byte[] chunk) => WriteChunk(chunk, 0, chunk.Length); + + public void WriteValue(byte b) => WriteChunk(new byte[] { b }); + + public void WriteValue(bool b) => WriteChunk(new byte[] { (byte)((!b) ? 0 : 1) }); + + public void WriteValue(int i) => WriteChunk(ReverseOrder(BitConverter.GetBytes(i))); + + public void WriteValue(short s) => WriteChunk(ReverseOrder(BitConverter.GetBytes(s))); + + public void WriteValue(ushort us) => WriteChunk(ReverseOrder(BitConverter.GetBytes(us))); + + public void WriteValue(long l) => WriteChunk(ReverseOrder(BitConverter.GetBytes(l))); + + public void WriteValue(float f) => WriteChunk(ReverseOrder(BitConverter.GetBytes(f))); + + public void WriteValue(double d) => WriteChunk(ReverseOrder(BitConverter.GetBytes(d))); + + public void WriteValue(string str) { + WriteValue(GetUTFStringLength(str)); + WriteChunk(Encoding.UTF8.GetBytes(str)); + } + + private ushort GetUTFStringLength(string str) { + ushort length = 0; + foreach (int c in str) { + if (c > 0 && c < 128) + ++length; + else if (c > 2047) + length += 3; + else + length += 2; + } + if (length > 32768) + throw new Exception("String is too long"); + return length; + } +} diff --git a/src/Data/NetworkDataType.cs b/src/Data/NetworkDataType.cs new file mode 100644 index 0000000..b13d4ae --- /dev/null +++ b/src/Data/NetworkDataType.cs @@ -0,0 +1,23 @@ +namespace sodoffmmo.Data; + +public enum NetworkDataType { + Null, + Bool, + Byte, + Short, + Int, + Long, + Float, + Double, + String, + BoolArray, + ByteArray, + ShortArray, + IntArray, + LongArray, + FloatArray, + DoubleArray, + StringArray, + NetworkArray, + NetworkObject, +} diff --git a/src/Data/NetworkObject.cs b/src/Data/NetworkObject.cs new file mode 100644 index 0000000..38c4435 --- /dev/null +++ b/src/Data/NetworkObject.cs @@ -0,0 +1,198 @@ +namespace sodoffmmo.Data; +public class NetworkObject { + Dictionary fields = new(); + + public NetworkObject() {} + + public NetworkObject(NetworkData data) { + Deserialize(data); + } + + public NetworkObject(byte[] data) : this(new NetworkData(data)) { } + + public void Add(string label, DataWrapper wrapper) => fields[label] = wrapper; + + public void Add(string label, byte value) => fields[label] = new DataWrapper(NetworkDataType.Byte, value); + + public void Add(string label, bool value) => fields[label] = new DataWrapper(NetworkDataType.Bool, value); + + public void Add(string label, short value) => fields[label] = new DataWrapper(NetworkDataType.Short, value); + + public void Add(string label, int value) => fields[label] = new DataWrapper(NetworkDataType.Int, value); + + public void Add(string label, long value) => fields[label] = new DataWrapper(NetworkDataType.Long, value); + + public void Add(string label, float value) => fields[label] = new DataWrapper(NetworkDataType.Float, value); + + public void Add(string label, double value) => fields[label] = new DataWrapper(NetworkDataType.Double, value); + + public void Add(string label, string value) => fields[label] = new DataWrapper(NetworkDataType.String, value); + + public void Add(string label, byte[] value) => fields[label] = new DataWrapper(NetworkDataType.ByteArray, value); + + public void Add(string label, bool[] value) => fields[label] = new DataWrapper(NetworkDataType.BoolArray, value); + + public void Add(string label, short[] value) => fields[label] = new DataWrapper(NetworkDataType.ShortArray, value); + + public void Add(string label, int[] value) => fields[label] = new DataWrapper(NetworkDataType.IntArray, value); + + public void Add(string label, long[] value) => fields[label] = new DataWrapper(NetworkDataType.LongArray, value); + + public void Add(string label, float[] value) => fields[label] = new DataWrapper(NetworkDataType.FloatArray, value); + + public void Add(string label, double[] value) => fields[label] = new DataWrapper(NetworkDataType.DoubleArray, value); + + public void Add(string label, string[] value) => fields[label] = new DataWrapper(NetworkDataType.StringArray, value); + + public void Add(string label, NetworkArray value) => fields[label] = new DataWrapper(NetworkDataType.NetworkArray, value); + + public void Add(string label, NetworkObject value) => fields[label] = new DataWrapper(NetworkDataType.NetworkObject, value); + + public T Get(string key) { + if (!fields.ContainsKey(key)) + return default; + return (T)fields[key].Data; + } + + + public NetworkPacket Serialize() => new NetworkPacket(0x80, SerializeObject(this)); + + private byte[] SerializeObject(NetworkObject obj) { + NetworkData data = new(); + data.WriteValue((byte)18); + data.WriteValue(Convert.ToInt16(obj.fields.Count)); + foreach (string key in obj.fields.Keys) { + data.WriteValue(key); + data.WriteChunk(EncodeObject(obj.fields[key])); + } + return data.Data; + } + + private void Deserialize(NetworkData data) { + if (data.ReadByte() != 0x12) + throw new Exception("Invalid object type"); + + short count = data.ReadShort(); + for (short i = 0; i < count; i++) { + string label = data.ReadString(); + DataWrapper obj = DecodeObject(data); + Add(label, obj); + } + } + + private byte[] EncodeObject(DataWrapper obj) { + switch ((NetworkDataType)obj.Type) { + case NetworkDataType.Null: + return DataEncoder.EncodeNull(); + case NetworkDataType.Bool: + return DataEncoder.EncodeBool((bool)obj.Data); + case NetworkDataType.Byte: + return DataEncoder.EncodeByte((byte)obj.Data); + case NetworkDataType.Short: + return DataEncoder.EncodeShort((short)obj.Data); + case NetworkDataType.Int: + return DataEncoder.EncodeInt((int)obj.Data); + case NetworkDataType.Long: + return DataEncoder.EncodeLong((long)obj.Data); + case NetworkDataType.Float: + return DataEncoder.EncodeFloat((float)obj.Data); + case NetworkDataType.Double: + return DataEncoder.EncodeDouble((double)obj.Data); + case NetworkDataType.String: + return DataEncoder.EncodeString((string)obj.Data); + case NetworkDataType.BoolArray: + return DataEncoder.EncodeBoolArray((bool[])obj.Data); + case NetworkDataType.ByteArray: + return DataEncoder.EncodeByteArray((byte[])obj.Data); + case NetworkDataType.ShortArray: + return DataEncoder.EncodeShortArray((short[])obj.Data); + case NetworkDataType.IntArray: + return DataEncoder.EncodeIntArray((int[])obj.Data); + case NetworkDataType.LongArray: + return DataEncoder.EncodeLongArray((long[])obj.Data); + case NetworkDataType.FloatArray: + return DataEncoder.EncodeFloatArray((float[])obj.Data); + case NetworkDataType.DoubleArray: + return DataEncoder.EncodeDoubleArray((double[])obj.Data); + case NetworkDataType.StringArray: + return DataEncoder.EncodeStringArray((string[])obj.Data); + case NetworkDataType.NetworkArray: + return EncodeNetworkArray((NetworkArray)obj.Data); + case NetworkDataType.NetworkObject: + return SerializeObject((NetworkObject)obj.Data); + default: throw new Exception("Invalid object"); + } + } + + private byte[] EncodeNetworkArray(NetworkArray arr) { + NetworkData data = new(); + data.WriteValue((byte)17); + data.WriteValue(Convert.ToInt16(arr.Length)); + for (int i = 0; i < arr.Length; i++) + data.WriteChunk(EncodeObject(arr[i])); + return data.Data; + } + + private DataWrapper DecodeObject(NetworkData data) { + switch ((NetworkDataType)data.ReadByte()) { + case NetworkDataType.Null: + return DataDecoder.DecodeNull(data); + case NetworkDataType.Bool: + return DataDecoder.DecodeBool(data); + case NetworkDataType.Byte: + return DataDecoder.DecodeByte(data); + case NetworkDataType.Short: + return DataDecoder.DecodeShort(data); + case NetworkDataType.Int: + return DataDecoder.DecodeInt(data); + case NetworkDataType.Long: + return DataDecoder.DecodeLong(data); + case NetworkDataType.Float: + return DataDecoder.DecodeFloat(data); + case NetworkDataType.Double: + return DataDecoder.DecodeDouble(data); + case NetworkDataType.String: + return DataDecoder.DecodeString(data); + case NetworkDataType.BoolArray: + return DataDecoder.DecodeBoolArray(data); + case NetworkDataType.ByteArray: + return DataDecoder.DecodeByteArray(data); + case NetworkDataType.ShortArray: + return DataDecoder.DecodeShortArray(data); + case NetworkDataType.IntArray: + return DataDecoder.DecodeIntArray(data); + case NetworkDataType.LongArray: + return DataDecoder.DecodeLongArray(data); + case NetworkDataType.FloatArray: + return DataDecoder.DecodeFloatArray(data); + case NetworkDataType.DoubleArray: + return DataDecoder.DecodeDoubleArray(data); + case NetworkDataType.StringArray: + return DataDecoder.DecodeStringArray(data); + case NetworkDataType.NetworkArray: + return DecodeNetworkArray(data); + case NetworkDataType.NetworkObject: + data.Seek(-1); + return new DataWrapper(NetworkDataType.NetworkObject, new NetworkObject(data)); + default: throw new Exception("Invalid object"); + } + } + + private DataWrapper DecodeNetworkArray(NetworkData data) { + NetworkArray array = new NetworkArray(); + short count = data.ReadShort(); + for (short i = 0; i < count; i++) { + DataWrapper wrapper = DecodeObject(data); + array.Add(wrapper); + } + return new DataWrapper(NetworkDataType.NetworkArray, array); + } + + public static NetworkObject WrapObject(byte c, short a, NetworkObject obj) { + NetworkObject wrapper = new(); + wrapper.Add("c", c); + wrapper.Add("a", a); + wrapper.Add("p", obj); + return wrapper; + } +} diff --git a/src/Data/NetworkPacket.cs b/src/Data/NetworkPacket.cs new file mode 100644 index 0000000..2f177ac --- /dev/null +++ b/src/Data/NetworkPacket.cs @@ -0,0 +1,74 @@ +using ComponentAce.Compression.Libs.zlib; + +namespace sodoffmmo.Data; +public class NetworkPacket { + + byte header; + byte[] data; + bool compressed = false; + + public int Length { + get { + return data.Length; + } + } + + public byte[] SendData { + get { + NetworkData sendData = new(); + sendData.WriteValue(header); + sendData.WriteValue((short)data.Length); + sendData.WriteChunk(data); + return sendData.Data; + } + } + + public NetworkPacket() { + data = new byte[0]; + } + + public NetworkPacket(NetworkData data, bool compressed = false) { + header = 0x80; + if (compressed) { + header = 0xa0; + this.compressed = true; + } + this.data = data.Data; + } + + public NetworkPacket(byte header, byte[] data) { + this.header = header; + this.data = data; + if (header == 0xa0) + compressed = true; + } + + public NetworkObject GetObject() { + if (compressed) + Decompress(); + + NetworkObject obj = new NetworkObject(data); + return obj; + } + + public void Compress() { + if (compressed) + return; + MemoryStream outStream = new(); + using (ZOutputStream zstream = new(outStream, 9)) { + zstream.Write(data); + zstream.Flush(); + } + data = outStream.ToArray(); + header = 0xa0; + } + + public void Decompress() { + MemoryStream outStream = new(); + using (ZOutputStream zstream = new(outStream)) { + zstream.Write(data); + zstream.Flush(); + } + data = outStream.ToArray(); + } +} diff --git a/src/Data/PlayerData.cs b/src/Data/PlayerData.cs new file mode 100644 index 0000000..a7a0dda --- /dev/null +++ b/src/Data/PlayerData.cs @@ -0,0 +1,64 @@ +namespace sodoffmmo.Data; +public class PlayerData { + public int Id { get; set; } = -1; + public double R { get; set; } + public double R1 { get; set; } + public double R2 { get; set; } + public double R3 { get; set; } + public string Fp { get; set; } = ""; + public double Mx { get; set; } = 6; + public string Udt { get; set; } = ""; + public double P1 { get; set; } + public double P2 { get; set; } + public double P3 { get; set; } + + public string Nt { get; set; } = "1.0"; + public int T { get; set; } = 0; + public string J { get; set; } = "2"; + public int F { get; set; } + public int Mbf { get; set; } + public string Bu { get; set; } = "False"; + public string Uid { get; set; } = ""; + public string Pu { get; set; } = ""; + public string A { get; set; } = ""; + public string Ra { get; set; } = ""; + public string Cu { get; set; } = "-1"; + public string M { get; set; } = "False"; + public string L { get; set; } = ""; + + public NetworkArray GetNetworkData() { + NetworkArray arr = new(); + arr.Add(Id); + arr.Add(Uid); + arr.Add((short)1); + arr.Add((short)Id); + + NetworkArray paramArr = new(); + paramArr.Add(NetworkArray.DoubleParam("R1", R1)); + paramArr.Add(NetworkArray.StringParam("FP", Fp)); + paramArr.Add(NetworkArray.DoubleParam("MX", Mx)); + paramArr.Add(NetworkArray.StringParam("UDT", Udt)); + paramArr.Add(NetworkArray.DoubleParam("P2", P2)); + paramArr.Add(NetworkArray.StringParam("NT", Nt)); + paramArr.Add(NetworkArray.IntParam("t", T)); + paramArr.Add(NetworkArray.StringParam("J", J)); + paramArr.Add(NetworkArray.IntParam("F", F)); + paramArr.Add(NetworkArray.IntParam("MBF", Mbf)); + paramArr.Add(NetworkArray.DoubleParam("R2", R2)); + paramArr.Add(NetworkArray.DoubleParam("R", R)); + paramArr.Add(NetworkArray.StringParam("BU", Bu)); + paramArr.Add(NetworkArray.DoubleParam("P1", P1)); + paramArr.Add(NetworkArray.StringParam("UID", Uid)); + paramArr.Add(NetworkArray.DoubleParam("R3", R3)); + paramArr.Add(NetworkArray.StringParam("PU", Pu)); + paramArr.Add(NetworkArray.StringParam("A", A)); + paramArr.Add(NetworkArray.StringParam("RA", Ra)); + paramArr.Add(NetworkArray.DoubleParam("P3", P3)); + paramArr.Add(NetworkArray.StringParam("CU", Cu)); + paramArr.Add(NetworkArray.StringParam("M", M)); + paramArr.Add(NetworkArray.StringParam("L", L)); + + arr.Add(paramArr); + return arr; + } +} diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 0000000..a4e661a --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,5 @@ +using sodoffmmo; +using System.Net; + +Server server = new(IPAddress.Any, 9933); +await server.Run(); diff --git a/src/Server.cs b/src/Server.cs new file mode 100644 index 0000000..eefc6c9 --- /dev/null +++ b/src/Server.cs @@ -0,0 +1,77 @@ +using sodoffmmo.Core; +using sodoffmmo.Data; +using System; +using System.Net; +using System.Net.Sockets; + +namespace sodoffmmo; +public class Server { + + readonly int port; + readonly IPAddress ipAddress; + ModuleManager moduleManager = new(); + + public Server(IPAddress ipAdress, int port) { + this.ipAddress = ipAdress; + this.port = port; + } + + public async Task Run() { + moduleManager.RegisterModules(); + using Socket listener = new(ipAddress.AddressFamily, + SocketType.Stream, + ProtocolType.Tcp); + + listener.Bind(new IPEndPoint(ipAddress, port)); + await Listen(listener); + } + + private async Task Listen(Socket listener) { + Console.WriteLine($"MMO Server listening on port {port}"); + listener.Listen(100); + while (true) { + Socket handler = await listener.AcceptAsync(); + Console.WriteLine($"New connection from {((IPEndPoint)handler.RemoteEndPoint!).Address}"); + _ = HandleClient(handler); + } + } + + private async Task HandleClient(Socket handler) { + Client client = new(handler); + try { + while (client.Connected) { + await client.Receive(); + List networkObjects = new(); + while (client.TryGetNextPacket(out NetworkPacket packet)) + networkObjects.Add(packet.GetObject()); + + _ = Task.Run(() => HandleObjects(networkObjects, client)); + } + } catch (SocketException) { + client.Disconnect(); + } finally { + try { + client.LeaveRoom(); + } catch (Exception) { } + Console.WriteLine("Socket disconnected IID: " + client.internalId); + } + } + + private void HandleObjects(List networkObjects, Client client) { + foreach (var obj in networkObjects) { + try { + short commandId = obj.Get("a"); + ICommandHandler handler; + if (commandId != 13) { + Console.WriteLine($"System command: {commandId} IID: {client.internalId}"); + handler = moduleManager.GetCommandHandler(commandId); + } else + handler = moduleManager.GetCommandHandler(obj.Get("p").Get("c")); + handler.Handle(client, obj.Get("p")); + } catch (Exception ex) { + if (!ex.Message.Contains("ID 7")) // Missing command 7 flooding the log + Console.WriteLine($"Exception IID: {client.internalId} - {ex}"); + } + } + } +} diff --git a/src/sodoffmmo.csproj b/src/sodoffmmo.csproj new file mode 100644 index 0000000..99a5f0a --- /dev/null +++ b/src/sodoffmmo.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + +