diff --git a/Server/CommandHandler.cs b/Server/CommandHandler.cs new file mode 100644 index 0000000..90a3ff7 --- /dev/null +++ b/Server/CommandHandler.cs @@ -0,0 +1,26 @@ +namespace Server; + +public static class CommandHandler { + public delegate string Handler(string[] args); + + public static Dictionary Handlers = new Dictionary(); + + static CommandHandler() { + RegisterCommand("help", _ => $"Valid commands: {string.Join(", ", Handlers.Keys)}"); + } + + public static void RegisterCommand(string name, Handler handler) { + Handlers[name] = handler; + } + + public static string GetResult(string input) { + try { + string[] args = input.Split(' '); + if (args.Length == 0) return "No command entered, see help command for valid commands"; + string commandName = args[0]; + return Handlers.TryGetValue(commandName, out Handler? handler) ? handler(args[1..]) : $"Invalid command {args[0]}, see help command for valid commands"; + } catch (Exception e) { + return $"An error occured while trying to process your command: {e}"; + } + } +} \ No newline at end of file diff --git a/Server/FlipOptions.cs b/Server/FlipOptions.cs new file mode 100644 index 0000000..bf0107d --- /dev/null +++ b/Server/FlipOptions.cs @@ -0,0 +1,7 @@ +namespace Server; + +public enum FlipOptions { + Both, + Self, + Others +} \ No newline at end of file diff --git a/Server/Program.cs b/Server/Program.cs index 792112c..47291f1 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -1,7 +1,9 @@ using System.Collections.Concurrent; using System.Numerics; using Server; +using Shared; using Shared.Packet.Packets; +using Tomlyn; using Timer = System.Timers.Timer; Server.Server server = new Server.Server(); @@ -21,23 +23,17 @@ async Task ClientSyncShineBag(Client client) { } async void SyncShineBag() { - await Parallel.ForEachAsync(server.Clients, async (client, _) => { - await ClientSyncShineBag(client); - }); + await Parallel.ForEachAsync(server.Clients, async (client, _) => { await ClientSyncShineBag(client); }); } Timer timer = new Timer(120000); timer.AutoReset = true; timer.Enabled = true; -timer.Elapsed += (_, _) => { - SyncShineBag(); -}; +timer.Elapsed += (_, _) => { SyncShineBag(); }; timer.Start(); -bool piss = false; +bool flipEnabled = Settings.Instance.Flip.EnabledOnStart; -Guid lycel = Guid.Parse("d5feae62-2e71-1000-88fd-597ea147ae88"); -// Guid lycel = Guid.Parse("5e1f9db4-1c27-1000-a421-4701972e443e"); -Guid test = Guid.Parse("00000001-0000-0000-0000-000000000000"); +float MarioSize(bool is2d) => is2d ? 180 : 160; server.PacketHandler = (c, p) => { switch (p) { @@ -55,18 +51,19 @@ server.PacketHandler = (c, p) => { SyncShineBag(); break; } - case PlayerPacket playerPacket when c.Id == lycel && c.Id != test && piss: { - playerPacket.Position += Vector3.UnitY * 160; + case PlayerPacket playerPacket when flipEnabled && Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Others && Settings.Instance.Flip.Players.Contains(c.Id): { + playerPacket.Position += Vector3.UnitY * MarioSize(playerPacket.Is2d); playerPacket.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI)) * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI)); server.Broadcast(playerPacket, c); return false; } - case PlayerPacket playerPacket when c.Id != lycel && piss: { + case PlayerPacket playerPacket when flipEnabled && Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Self && !Settings.Instance.Flip.Players.Contains(c.Id): { server.BroadcastReplace(playerPacket, c, (from, to, sp) => { - if (to.Id == lycel) { - sp.Position += Vector3.UnitY * 160; + if (Settings.Instance.Flip.Players.Contains(to.Id)) { + sp.Position += Vector3.UnitY * MarioSize(playerPacket.Is2d); sp.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI)) * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI)); } + to.Send(sp, from); }); return false; @@ -76,12 +73,71 @@ server.PacketHandler = (c, p) => { return true; }; -Task.Run(() => { - while (true) { - Console.ReadLine(); - piss = !piss; - server.Logger.Warn($"Lycel flipped to {piss}"); +CommandHandler.RegisterCommand("flip", args => { + const string optionUsage = "Valid options: list, add , remove , set , pov "; + if (args.Length < 1) + return optionUsage; + switch (args[0]) { + case "list" when args.Length == 1: + return "User ids: " + string.Join(", ", Settings.Instance.Flip.Players.ToList()); + case "add" when args.Length == 2: { + if (Guid.TryParse(args[1], out Guid result)) { + Settings.Instance.Flip.Players.Add(result); + Settings.SaveSettings(); + return $"Added {result} to flipped players"; + } else + return $"Invalid user id {args[1]}"; + } + case "remove" when args.Length == 2: { + if (Guid.TryParse(args[1], out Guid result)) { + string output = Settings.Instance.Flip.Players.Remove(result) ? $"Removed {result} to flipped players" : $"User {result} wasn't in the flipped players list"; + Settings.SaveSettings(); + return output; + } + + return $"Invalid user id {args[1]}"; + } + case "set" when args.Length == 2: { + if (bool.TryParse(args[1], out bool result)) { + flipEnabled = result; + return result ? "Enabled player flipping for session" : "Disabled player flipping for session"; + } + + return optionUsage; + } + case "pov" when args.Length == 2: { + if (Enum.TryParse(args[1], true, out FlipOptions result)) { + Settings.Instance.Flip.Pov = result; + Settings.SaveSettings(); + return $"Point of view set to {result}"; + } + + return optionUsage; + } + default: + return optionUsage; } }); -await server.Listen(1027); \ No newline at end of file +CommandHandler.RegisterCommand("shine", (args) => { + const string optionUsage = "Valid options: list"; + if (args.Length < 1) + return optionUsage; + switch (args[0]) { + case "list" when args.Length == 1: + return $"Shines: {string.Join(", ", shineBag)}"; + default: + return optionUsage; + } +}); + +Task.Run(() => { + Logger logger = new Logger("Console"); + logger.Info("Run help command for valid commands."); + while (true) { + string? text = Console.ReadLine(); + if (text != null) logger.Info(CommandHandler.GetResult(text)); + } +}); + +await server.Listen(); \ No newline at end of file diff --git a/Server/Server.cs b/Server/Server.cs index 0ebc965..f6c49d8 100644 --- a/Server/Server.cs +++ b/Server/Server.cs @@ -15,13 +15,13 @@ public class Server { public Func? PacketHandler = null!; public event Action ClientJoined = null!; - public async Task Listen(ushort port) { + public async Task Listen() { Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - serverSocket.Bind(new IPEndPoint(IPAddress.Any, port)); + serverSocket.Bind(new IPEndPoint(IPAddress.Parse(Settings.Instance.Server.Address), Settings.Instance.Server.Port)); serverSocket.Listen(); - Logger.Info($"Listening on port {port}"); + Logger.Info($"Listening on {serverSocket.LocalEndPoint}"); while (true) { Socket socket = await serverSocket.AcceptAsync(); diff --git a/Server/Settings.cs b/Server/Settings.cs new file mode 100644 index 0000000..3b51dc8 --- /dev/null +++ b/Server/Settings.cs @@ -0,0 +1,65 @@ +using System.Net; +using Shared; +using Tomlyn; +using Tomlyn.Model; +using Tomlyn.Syntax; + +namespace Server; + +public class Settings { + public static Settings Instance = new Settings(); + private static readonly Logger Logger = new Logger("Settings"); + static Settings() { + LoadSettings(); + } + + public static void LoadSettings() { + if (File.Exists("settings.toml")) { + string text = File.ReadAllText("settings.toml"); + if (Toml.TryToModel(text, out Settings? settings, out DiagnosticsBag? bag, options: new TomlModelOptions() { + ConvertTo = (value, _) => { + if (value is string str && Guid.TryParse(str, out Guid result)) + return result; + + return null; + } + })) + Logger.Info("Loaded settings from settings.toml"); + else + Logger.Warn($"Failed to load settings.toml: {bag}"); + if (settings != null) Instance = settings; + } else { + SaveSettings(); + } + } + + public static void SaveSettings(Settings? settings = null) { + try { + File.WriteAllText("settings.toml", Toml.FromModel(settings ?? Instance!, new TomlModelOptions { + ConvertTo = (x, _) => { + if (x is Guid guid) + return guid.ToString(); + + return null!; + } + })); + Logger.Info("Saved settings to settings.toml"); + } catch (Exception e) { + Logger.Error($"Failed to save settings.toml {e}"); + } + } + + public ServerTable Server { get; set; } = new ServerTable(); + public FlipTable Flip { get; set; } = new FlipTable(); + + public class ServerTable { + public string Address { get; set; } = IPAddress.Any.ToString(); + public ushort Port { get; set; } = 1027; + } + + public class FlipTable { + public List Players { get; set; } = new List(); + public bool EnabledOnStart { get; set; } = true; + public FlipOptions Pov { get; set; } = FlipOptions.Both; + } +} \ No newline at end of file diff --git a/Shared/Packet/Packets/LogPacket.cs b/Shared/Packet/Packets/LogPacket.cs deleted file mode 100644 index ca8c30d..0000000 --- a/Shared/Packet/Packets/LogPacket.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text; - -namespace Shared.Packet.Packets; - -public struct LogPacket : IPacket { - public string Text; - public void Serialize(Span data) { - - } - - public void Deserialize(Span data) { - Text = Encoding.UTF8.GetString(data[..]).TrimNullTerm(); - } -} \ No newline at end of file diff --git a/Shared/Packet/Packets/PlayerPacket.cs b/Shared/Packet/Packets/PlayerPacket.cs index f344a43..38114c4 100644 --- a/Shared/Packet/Packets/PlayerPacket.cs +++ b/Shared/Packet/Packets/PlayerPacket.cs @@ -6,7 +6,7 @@ namespace Shared.Packet.Packets; [Packet(PacketType.Player)] public struct PlayerPacket : IPacket { - public const int NameSize = 0x30; + public const int NameSize = 0x20; public Vector3 Position; public Quaternion Rotation; @@ -20,14 +20,17 @@ public struct PlayerPacket : IPacket { public bool IsIt; public int ScenarioNum; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)] - public string Stage; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x40)] + public string Stage = ""; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)] - public string Act; + public string Act = ""; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)] - public string SubAct; + public string SubAct = ""; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)] + public string Hack = ""; public void Serialize(Span data) { int offset = 0; @@ -43,11 +46,13 @@ public struct PlayerPacket : IPacket { MemoryMarshal.Write(data[offset++..], ref IsIt); MemoryMarshal.Write(data[offset..], ref ScenarioNum); offset += 5; - Encoding.UTF8.GetBytes(Stage).CopyTo(data[offset..(offset + NameSize)]); - offset += NameSize; + Encoding.UTF8.GetBytes(Stage).CopyTo(data[offset..(offset + 0x40)]); + offset += 0x40; Encoding.UTF8.GetBytes(Act).CopyTo(data[offset..(offset + NameSize)]); offset += NameSize; - Encoding.UTF8.GetBytes(SubAct).CopyTo(data[offset..]); + Encoding.UTF8.GetBytes(SubAct).CopyTo(data[offset..(offset + NameSize)]); + offset += NameSize; + Encoding.UTF8.GetBytes(Hack).CopyTo(data[offset..]); } public void Deserialize(Span data) { @@ -64,10 +69,12 @@ public struct PlayerPacket : IPacket { // offset++; // padding ScenarioNum = MemoryMarshal.Read(data[offset..]); offset += 5; - Stage = Encoding.UTF8.GetString(data[offset..(offset + NameSize)]).TrimEnd('\0'); - offset += NameSize; + Stage = Encoding.UTF8.GetString(data[offset..(offset + 0x40)]).TrimEnd('\0'); + offset += 0x40; Act = Encoding.UTF8.GetString(data[offset..(offset + NameSize)]).TrimEnd('\0'); offset += NameSize; SubAct = Encoding.UTF8.GetString(data[offset..(offset + NameSize)]).TrimEnd('\0'); + offset += NameSize; + SubAct = Encoding.UTF8.GetString(data[offset..(offset + NameSize)]).TrimEnd('\0'); } } \ No newline at end of file diff --git a/TestClient/Program.cs b/TestClient/Program.cs index 4dccda3..8474be8 100644 --- a/TestClient/Program.cs +++ b/TestClient/Program.cs @@ -17,13 +17,14 @@ Vector3 basePoint = Vector3.Zero; PacketType[] reboundPackets = { PacketType.Player, - // PacketType.Cap, + PacketType.Cap, PacketType.Capture, PacketType.Costume, PacketType.Tag, PacketType.Shine }; +string lastCapture = ""; async Task S() { IMemoryOwner owner = MemoryPool.Shared.Rent(Constants.MaxPacketSize); while (true) { @@ -31,25 +32,29 @@ async Task S() { PacketHeader header = MemoryMarshal.Read(owner.Memory.Span); PacketType type = header.Type; if (header.Id != otherId) continue; - // if (type is PacketType.Player) { - // CapPacket cap = new CapPacket(); - // PlayerPacket playerPacket = new PlayerPacket(); - // playerPacket.Deserialize(owner.Memory.Span[Constants.HeaderSize..]); - // cap.Position = playerPacket.Position + Vector3.UnitY * 500f; - // // cap.Rotation = Quaternion.CreateFromYawPitchRoll(0,0,0); - // cap.CapAnim = "StayR"; - // playerPacket.Position = new Vector3(1000000f); - // playerPacket.ThrowingCap = true; - // header.Id = ownId; - // MemoryMarshal.Write(owner.Memory.Span, ref header); - // playerPacket.Serialize(owner.Memory.Span[Constants.HeaderSize..]); - // await stream.WriteAsync(owner.Memory[..Constants.MaxPacketSize]); - // header.Type = PacketType.Cap; - // MemoryMarshal.Write(owner.Memory.Span, ref header); - // cap.Serialize(owner.Memory.Span[Constants.HeaderSize..]); - // await stream.WriteAsync(owner.Memory[..Constants.MaxPacketSize]); - // continue; - // } + if (type is PacketType.Player) { + // CapPacket cap = new CapPacket(); + PlayerPacket playerPacket = new PlayerPacket(); + playerPacket.Deserialize(owner.Memory.Span[Constants.HeaderSize..]); + logger.Info(playerPacket.Hack); + if (playerPacket.Hack != lastCapture) { + logger.Info($"Changed to hack: {lastCapture = playerPacket.Hack}"); + } + // cap.Position = playerPacket.Position + Vector3.UnitY * 500f; + // cap.Rotation = Quaternion.CreateFromYawPitchRoll(0,0,0); + // cap.CapAnim = "StayR"; + // playerPacket.Position = new Vector3(1000000f); + // playerPacket.ThrowingCap = true; + // header.Id = ownId; + // MemoryMarshal.Write(owner.Memory.Span, ref header); + // playerPacket.Serialize(owner.Memory.Span[Constants.HeaderSize..]); + // await stream.WriteAsync(owner.Memory[..Constants.MaxPacketSize]); + // header.Type = PacketType.Cap; + // MemoryMarshal.Write(owner.Memory.Span, ref header); + // cap.Serialize(owner.Memory.Span[Constants.HeaderSize..]); + // await stream.WriteAsync(owner.Memory[..Constants.MaxPacketSize]); + // continue; + } if (reboundPackets.All(x => x != type)) continue; header.Id = ownId; MemoryMarshal.Write(owner.Memory.Span, ref header);