diff --git a/Server/Server.cs b/Server/Server.cs index 2691259..6724ad3 100644 --- a/Server/Server.cs +++ b/Server/Server.cs @@ -1,164 +1,170 @@ -using System.Buffers; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using Shared; -using Shared.Packet; -using Shared.Packet.Packets; - -namespace Server; - -public class Server { - private readonly MemoryPool memoryPool = MemoryPool.Shared; - public readonly List Clients = new List(); - public readonly Logger Logger = new Logger("Server"); - public async Task Listen(ushort port) { - TcpListener listener = TcpListener.Create(port); - - listener.Start(); - - while (true) { - Socket socket = await listener.AcceptSocketAsync(); - - if (Clients.Count > Constants.MaxClients) { - Logger.Warn("Turned away client due to max clients"); - await socket.DisconnectAsync(false); - continue; - } - - HandleSocket(socket); - } - } - - public static void FillPacket(PacketHeader header, T packet, Memory memory) where T : unmanaged, IPacket { - Span data = memory.Span; - - MemoryMarshal.Write(data, ref header); - MemoryMarshal.Write(data[Constants.HeaderSize..], ref packet); - } - - // broadcast packets to all clients - public async void Broadcast(T packet, Client? sender = null) where T : unmanaged, IPacket { - IMemoryOwner memory = memoryPool.Rent(Marshal.SizeOf() + Constants.HeaderSize); - - PacketHeader header = new PacketHeader { - Id = sender?.Id ?? Guid.Empty, - Type = Constants.Packets[typeof(T)].Type, - Sender = PacketSender.Server // todo maybe use client? - }; - FillPacket(header, packet, memory.Memory); - await Broadcast(memory, sender); - } - - /// - /// Takes ownership of data and disposes once done. - /// - /// Memory owner to dispose once done - /// Optional sender to not broadcast data to - public async Task Broadcast(IMemoryOwner data, Client? sender = null) { - await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data.Memory))); - data.Dispose(); - } - - /// - /// Broadcasts memory whose memory shouldn't be disposed, should only be fired by server code. - /// - /// Memory to send to the clients - /// Optional sender to not broadcast data to - public async void Broadcast(Memory data, Client? sender = null) { - await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data))); - } - - public Client? FindExistingClient(Guid id) { - return Clients.Find(client => client.Id == id); - } - - - private async void HandleSocket(Socket socket) { - Client client = new Client {Socket = socket}; - IMemoryOwner memory = null!; - bool first = true; - try { - while (true) { - memory = memoryPool.Rent(Constants.MaxPacketSize); - int size = await socket.ReceiveAsync(memory.Memory, SocketFlags.None); - if (size == 0) { // treat it as a disconnect and exit - Logger.Info($"Socket {socket.RemoteEndPoint} disconnected."); - await socket.DisconnectAsync(false); - break; - } - - PacketHeader header = GetHeader(memory.Memory.Span[..size]); - //Logger.Info($"first = {first}, type = {header.Type}, data = " + memory.Memory.Span[..size].Hex()); - // connection initialization - if (first) { - first = false; - if (header.Type != PacketType.Connect) { - throw new Exception($"First packet was not init, instead it was {header.Type}"); - } - - ConnectPacket connect = MemoryMarshal.Read(memory.Memory.Span[Constants.HeaderSize..size]); - lock (Clients) { - switch (connect.ConnectionType) { - case ConnectionTypes.FirstConnection: { - // do any cleanup required when it comes to new clients - List toDisconnect = Clients.FindAll(c => c.Id == header.Id && c.Connected && c.Socket != null); - Clients.RemoveAll(c => c.Id == header.Id); - - client.Id = header.Id; - Clients.Add(client); - - foreach (Client c in toDisconnect) c.Socket!.DisconnectAsync(false); - // done disconnecting and removing stale clients with the same id - break; - } - case ConnectionTypes.Reconnecting: { - if (FindExistingClient(header.Id) is { } newClient) { - if (newClient.Connected) throw new Exception($"Tried to join as already connected user {header.Id}"); - newClient.Socket = client.Socket; - client = newClient; - } else { - // TODO MAJOR: IF CLIENT COULD NOT BE FOUND, SERVER SHOULD GRACEFULLY TELL CLIENT THAT ON DISCONNECT - // can be done via disconnect packet when that gets more data? - throw new Exception("Could not find a suitable client to reconnect as"); - } - break; - } - default: - throw new Exception($"Invalid connection type {connect.ConnectionType}"); - } - } - - Logger.Info($"Client {socket.RemoteEndPoint} connected."); - - continue; - } - - - // todo support variable length packets when they show up - if (header.Sender == PacketSender.Client) { - Logger.Warn($"broadcasting {header.Type}"); - await Broadcast(memory, client); - } - else { - //todo handle server packets :) - } - } - } - catch (Exception e) { - if (e is SocketException {SocketErrorCode: SocketError.ConnectionReset}) { - Logger.Info($"Client {socket.RemoteEndPoint} disconnected from the server"); - } else { - Logger.Error($"Exception on socket {socket.RemoteEndPoint}, disconnecting for: {e}"); - await socket.DisconnectAsync(false); - } - - memory?.Dispose(); - } - client.Dispose(); - } - - private static PacketHeader GetHeader(Span data) { - //no need to error check, the client will disconnect when the packet is invalid :) - return MemoryMarshal.Read(data); - } +using System.Buffers; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using Shared; +using Shared.Packet; +using Shared.Packet.Packets; + +namespace Server; + +public class Server { + private readonly MemoryPool memoryPool = MemoryPool.Shared; + public readonly List Clients = new List(); + public readonly Logger Logger = new Logger("Server"); + public async Task Listen(ushort port) { + Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, port)); + serverSocket.Listen(); + + Logger.Info($"Listening on port {port}"); + + while (true) { + Socket socket = await serverSocket.AcceptAsync(); + + Logger.Warn("ok"); + + if (Clients.Count > Constants.MaxClients) { + Logger.Warn("Turned away client due to max clients"); + await socket.DisconnectAsync(false); + continue; + } + + HandleSocket(socket); + } + } + + public static void FillPacket(PacketHeader header, T packet, Memory memory) where T : unmanaged, IPacket { + Span data = memory.Span; + + MemoryMarshal.Write(data, ref header); + MemoryMarshal.Write(data[Constants.HeaderSize..], ref packet); + } + + // broadcast packets to all clients + public async Task Broadcast(T packet, Client? sender = null) where T : unmanaged, IPacket { + IMemoryOwner memory = memoryPool.Rent(Marshal.SizeOf() + Constants.HeaderSize); + + PacketHeader header = new PacketHeader { + Id = sender?.Id ?? Guid.Empty, + Type = Constants.Packets[typeof(T)].Type, + }; + FillPacket(header, packet, memory.Memory); + await Broadcast(memory, sender); + } + + /// + /// Takes ownership of data and disposes once done. + /// + /// Memory owner to dispose once done + /// Optional sender to not broadcast data to + public async Task Broadcast(IMemoryOwner data, Client? sender = null) { + await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data.Memory))); + data.Dispose(); + } + + /// + /// Broadcasts memory whose memory shouldn't be disposed, should only be fired by server code. + /// + /// Memory to send to the clients + /// Optional sender to not broadcast data to + public async void Broadcast(Memory data, Client? sender = null) { + await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data))); + } + + public Client? FindExistingClient(Guid id) { + return Clients.Find(client => client.Id == id); + } + + + private async void HandleSocket(Socket socket) { + Client client = new Client {Socket = socket}; + IMemoryOwner memory = null!; + bool first = true; + try { + while (true) { + memory = memoryPool.Rent(Constants.MaxPacketSize); + int size = await socket.ReceiveAsync(memory.Memory, SocketFlags.None); + if (size == 0) { // treat it as a disconnect and exit + Logger.Info($"Socket {socket.RemoteEndPoint} disconnected."); + await socket.DisconnectAsync(false); + break; + } + + PacketHeader header = GetHeader(memory.Memory.Span[..size]); + //Logger.Info($"first = {first}, type = {header.Type}, data = " + memory.Memory.Span[..size].Hex()); + // connection initialization + if (first) { + first = false; + if (header.Type != PacketType.Connect) { + throw new Exception($"First packet was not init, instead it was {header.Type}"); + } + + ConnectPacket connect = MemoryMarshal.Read(memory.Memory.Span[Constants.HeaderSize..size]); + lock (Clients) { + bool firstConn = false; + switch (connect.ConnectionType) { + case ConnectionTypes.FirstConnection: { + firstConn = true; + break; + } + case ConnectionTypes.Reconnecting: { + if (FindExistingClient(header.Id) is { } newClient) { + if (newClient.Connected) throw new Exception($"Tried to join as already connected user {header.Id}"); + newClient.Socket = client.Socket; + client = newClient; + } else { + firstConn = true; + } + break; + } + default: + throw new Exception($"Invalid connection type {connect.ConnectionType}"); + } + if (firstConn) { + // do any cleanup required when it comes to new clients + List toDisconnect = Clients.FindAll(c => c.Id == header.Id && c.Connected && c.Socket != null); + Clients.RemoveAll(c => c.Id == header.Id); + + client.Id = header.Id; + Clients.Add(client); + + Parallel.ForEachAsync(toDisconnect, (c, token) => c.Socket!.DisconnectAsync(false, token)); + // Broadcast(connect, client); + // done disconnecting and removing stale clients with the same id + } + } + + Logger.Info($"Client {socket.RemoteEndPoint} connected."); + + // continue; + } + + + // todo support variable length packets if they show up + // Logger.Warn($"broadcasting {header.Type}"); + await Broadcast(memory, client); + } + } + catch (Exception e) { + if (e is SocketException {SocketErrorCode: SocketError.ConnectionReset}) { + Logger.Info($"Client {socket.RemoteEndPoint} disconnected from the server"); + } else { + Logger.Error($"Exception on socket {socket.RemoteEndPoint}, disconnecting for: {e}"); + await socket.DisconnectAsync(false); + } + + memory?.Dispose(); + } + + Clients.Remove(client); + Broadcast(new DisconnectPacket(), client).ContinueWith(_ => { + client.Dispose(); + }); + } + + private static PacketHeader GetHeader(Span data) { + //no need to error check, the client will disconnect when the packet is invalid :) + return MemoryMarshal.Read(data); + } } \ No newline at end of file diff --git a/Shared/Constants.cs b/Shared/Constants.cs index b1a8545..5cfbdd1 100644 --- a/Shared/Constants.cs +++ b/Shared/Constants.cs @@ -1,19 +1,19 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using Shared.Packet; -using Shared.Packet.Packets; - -namespace Shared; - -public static class Constants { - public const int MaxPacketSize = 256; - public const int MaxClients = 4; - public static int HeaderSize => Marshal.SizeOf(); - - // dictionary of packet types to packet - public static readonly Dictionary Packets = Assembly - .GetExecutingAssembly() - .GetTypes() - .Where(type => type.IsAssignableTo(typeof(IPacket))) - .ToDictionary(type => type, type => type.GetCustomAttribute()!); +using System.Reflection; +using System.Runtime.InteropServices; +using Shared.Packet; +using Shared.Packet.Packets; + +namespace Shared; + +public static class Constants { + public const int MaxPacketSize = 256; + public const int MaxClients = 4; + public static int HeaderSize { get; } = Marshal.SizeOf(); + + // dictionary of packet types to packet + public static readonly Dictionary Packets = Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(type => type.IsAssignableTo(typeof(IPacket))) + .ToDictionary(type => type, type => type.GetCustomAttribute()!); } \ No newline at end of file diff --git a/Shared/Packet/PacketHeader.cs b/Shared/Packet/PacketHeader.cs index 1783588..1e620e8 100644 --- a/Shared/Packet/PacketHeader.cs +++ b/Shared/Packet/PacketHeader.cs @@ -1,10 +1,21 @@ -using System.Runtime.InteropServices; - -namespace Shared.Packet; - -[StructLayout(LayoutKind.Sequential)] -public struct PacketHeader { - public Guid Id; - public PacketType Type; - public PacketSender Sender; +using System.Runtime.InteropServices; +using Shared.Packet.Packets; + +namespace Shared.Packet; + +[StructLayout(LayoutKind.Sequential)] +public struct PacketHeader : IPacket { + // public int Length; + public Guid Id; + public PacketType Type; + public void Serialize(Span data) { + // MemoryMarshal.Write(data, ref Length); + MemoryMarshal.Write(data, ref Id); + MemoryMarshal.Write(data[16..], ref Type); + } + + public void Deserialize(Span data) { + Id = MemoryMarshal.Read(data); + Type = MemoryMarshal.Read(data[16..]); + } } \ No newline at end of file diff --git a/Shared/Packet/PacketSender.cs b/Shared/Packet/PacketSender.cs deleted file mode 100644 index 8bea420..0000000 --- a/Shared/Packet/PacketSender.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Shared.Packet; - -public enum PacketSender { - Server, - Client -} \ No newline at end of file diff --git a/Shared/Packet/PacketType.cs b/Shared/Packet/PacketType.cs index 535f8b1..a354f24 100644 --- a/Shared/Packet/PacketType.cs +++ b/Shared/Packet/PacketType.cs @@ -1,12 +1,14 @@ -namespace Shared.Packet; - -public enum PacketType { - Unknown, - Player, - Game, - Connect, - Disconnect, - Costume, - Shine, - Command +namespace Shared.Packet; + +public enum PacketType { + Unknown, + Player, + Cap, + Game, + Tag, + Connect, + Disconnect, + Costume, + Shine, + Command } \ No newline at end of file diff --git a/Shared/Packet/PacketUtils.cs b/Shared/Packet/PacketUtils.cs new file mode 100644 index 0000000..5ec94c8 --- /dev/null +++ b/Shared/Packet/PacketUtils.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; +using Shared.Packet.Packets; + +namespace Shared.Packet; + +public static class PacketUtils { + public static void SerializeHeaded(Span data, PacketHeader header, T t) where T : struct, IPacket { + header.Serialize(data); + t.Serialize(data[Constants.HeaderSize..]); + } + public static T Deserialize(Span data) where T : IPacket, new() { + T packet = new T(); + packet.Deserialize(data); + return packet; + } + + public static int SizeOf() where T : struct, IPacket { + return Constants.HeaderSize + Marshal.SizeOf(); + } +} \ No newline at end of file diff --git a/Shared/Packet/Packets/CommandPacket.cs b/Shared/Packet/Packets/CommandPacket.cs index 00f6116..8d30f5c 100644 --- a/Shared/Packet/Packets/CommandPacket.cs +++ b/Shared/Packet/Packets/CommandPacket.cs @@ -1,12 +1,13 @@ -namespace Shared.Packet.Packets; - -[Packet(PacketType.Command)] -public struct CommandPacket : IPacket { - public void Serialize(Span data) { - - } - - public void Deserialize(Span data) { - - } +namespace Shared.Packet.Packets; + +[Packet(PacketType.Command)] +public struct CommandPacket : IPacket { + //todo: implement something for this + public void Serialize(Span data) { + + } + + public void Deserialize(Span data) { + + } } \ No newline at end of file diff --git a/Shared/Packet/Packets/ConnectPacket.cs b/Shared/Packet/Packets/ConnectPacket.cs index 3d154af..d36eacc 100644 --- a/Shared/Packet/Packets/ConnectPacket.cs +++ b/Shared/Packet/Packets/ConnectPacket.cs @@ -1,15 +1,15 @@ -using System.Runtime.InteropServices; - -namespace Shared.Packet.Packets; - -[Packet(PacketType.Connect)] -public struct ConnectPacket : IPacket { - public ConnectionTypes ConnectionType; - public void Serialize(Span data) { - MemoryMarshal.Write(data, ref ConnectionType); - } - - public void Deserialize(Span data) { - ConnectionType = MemoryMarshal.Read(data); - } +using System.Runtime.InteropServices; + +namespace Shared.Packet.Packets; + +[Packet(PacketType.Connect)] +public struct ConnectPacket : IPacket { + public ConnectionTypes ConnectionType; + public void Serialize(Span data) { + MemoryMarshal.Write(data, ref ConnectionType); + } + + public void Deserialize(Span data) { + ConnectionType = MemoryMarshal.Read(data); + } } \ No newline at end of file diff --git a/Shared/Packet/Packets/CostumePacket.cs b/Shared/Packet/Packets/CostumePacket.cs index 8a63f49..0d39727 100644 --- a/Shared/Packet/Packets/CostumePacket.cs +++ b/Shared/Packet/Packets/CostumePacket.cs @@ -1,22 +1,24 @@ -using System.Runtime.InteropServices; - -namespace Shared.Packet.Packets; - -[Packet(PacketType.Costume)] -public struct CostumePacket : IPacket { - public const int CostumeNameSize = 0x20; - - public string BodyName; - public string CapName; - public void Serialize(Span data) { - Span strData = MemoryMarshal.Cast(data); - BodyName.CopyTo(strData[..CostumeNameSize]); - CapName.CopyTo(strData[CostumeNameSize..]); - } - - public void Deserialize(Span data) { - Span strData = MemoryMarshal.Cast(data); - BodyName = new string(strData[..CostumeNameSize].TrimEnd('\0')); - CapName = new string(strData[CostumeNameSize..].TrimEnd('\0')); - } +using System.Runtime.InteropServices; + +namespace Shared.Packet.Packets; + +[Packet(PacketType.Costume)] +public struct CostumePacket : IPacket { + public const int CostumeNameSize = 0x20; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CostumeNameSize)] + public string BodyName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CostumeNameSize)] + public string CapName; + public void Serialize(Span data) { + Span strData = MemoryMarshal.Cast(data); + BodyName.CopyTo(strData[..CostumeNameSize]); + CapName.CopyTo(strData[CostumeNameSize..]); + } + + public void Deserialize(Span data) { + Span strData = MemoryMarshal.Cast(data); + BodyName = new string(strData[..CostumeNameSize].TrimEnd('\0')); + CapName = new string(strData[CostumeNameSize..].TrimEnd('\0')); + } } \ No newline at end of file diff --git a/Shared/Packet/Packets/DisconnectPacket.cs b/Shared/Packet/Packets/DisconnectPacket.cs index abbeeb7..2ba6b06 100644 --- a/Shared/Packet/Packets/DisconnectPacket.cs +++ b/Shared/Packet/Packets/DisconnectPacket.cs @@ -1,12 +1,13 @@ -namespace Shared.Packet.Packets; - -[Packet(PacketType.Disconnect)] -public struct DisconnectPacket : IPacket { - public void Serialize(Span data) { - - } - - public void Deserialize(Span data) { - - } +namespace Shared.Packet.Packets; + +[Packet(PacketType.Disconnect)] +public struct DisconnectPacket : IPacket { + //empty packet + public void Serialize(Span data) { + + } + + public void Deserialize(Span data) { + + } } \ No newline at end of file diff --git a/Shared/Packet/Packets/GamePacket.cs b/Shared/Packet/Packets/GamePacket.cs new file mode 100644 index 0000000..d65eac4 --- /dev/null +++ b/Shared/Packet/Packets/GamePacket.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Shared.Packet.Packets; + +public class GamePacket : IPacket { + public int ScenarioNum; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = PlayerPacket.NameSize)] + public string Stage; + + public void Serialize(Span data) { + MemoryMarshal.Write(data, ref ScenarioNum); + Stage.CopyTo(MemoryMarshal.Cast(data[4..])); + } + + public void Deserialize(Span data) { + ScenarioNum = MemoryMarshal.Read(data); + Stage = new string(MemoryMarshal.Cast(data[4..])); + } +} \ No newline at end of file diff --git a/Shared/Packet/Packets/IPacket.cs b/Shared/Packet/Packets/IPacket.cs index 2badf31..556f8ca 100644 --- a/Shared/Packet/Packets/IPacket.cs +++ b/Shared/Packet/Packets/IPacket.cs @@ -1,7 +1,9 @@ -namespace Shared.Packet.Packets; - -// Packet interface for type safety -public interface IPacket { - void Serialize(Span data); - void Deserialize(Span data); +using System.Runtime.InteropServices; + +namespace Shared.Packet.Packets; + +// Packet interface for type safety +public interface IPacket { + void Serialize(Span data); + void Deserialize(Span data); } \ No newline at end of file diff --git a/Shared/Packet/Packets/MovementFlags.cs b/Shared/Packet/Packets/MovementFlags.cs new file mode 100644 index 0000000..30c0898 --- /dev/null +++ b/Shared/Packet/Packets/MovementFlags.cs @@ -0,0 +1,8 @@ +namespace Shared.Packet.Packets; + +[Flags] +public enum MovementFlags : byte { + IsFlat, + IsCapThrown, + IsSeeker +} \ No newline at end of file diff --git a/Shared/Packet/Packets/PlayerPacket.cs b/Shared/Packet/Packets/PlayerPacket.cs index a9662d6..73a2ece 100644 --- a/Shared/Packet/Packets/PlayerPacket.cs +++ b/Shared/Packet/Packets/PlayerPacket.cs @@ -1,60 +1,69 @@ -using System.Numerics; -using System.Runtime.InteropServices; - -namespace Shared.Packet.Packets; - -[Packet(PacketType.Player)] -public struct PlayerPacket : IPacket { - public const int NameSize = 0x30; - - public Vector3 Position; - public Quaternion Rotation; - public float[] AnimationBlendWeights; - public float AnimationRate; - public bool Flat; - public bool ThrowingCap; - public bool Seeker; - public int ScenarioNum; - public string Stage; - public string Act; - public string SubAct; - - public void Serialize(Span data) { - int offset = 0; - MemoryMarshal.Write(data, ref Position); - offset += Marshal.SizeOf(); - MemoryMarshal.Write(data[offset..], ref Rotation); - offset += Marshal.SizeOf(); - AnimationBlendWeights.CopyTo(MemoryMarshal.Cast(data[offset..(offset += 4 * 6)])); - MemoryMarshal.Write(data[(offset += 4)..], ref AnimationRate); - offset += 4; - MemoryMarshal.Write(data[offset++..], ref Flat); - MemoryMarshal.Write(data[offset++..], ref ThrowingCap); - MemoryMarshal.Write(data[offset++..], ref Seeker); - MemoryMarshal.Write(data[(offset += 4)..], ref ScenarioNum); - Span strData = MemoryMarshal.Cast(data[offset..]); - Stage.CopyTo(strData[..NameSize]); - Act.CopyTo(strData[NameSize..(2 * NameSize)]); - SubAct.CopyTo(strData[(2 * NameSize)..(3 * NameSize)]); - } - - public void Deserialize(Span data) { - int offset = 0; - Position = MemoryMarshal.Read(data); - offset += Marshal.SizeOf(); - Rotation = MemoryMarshal.Read(data[offset..]); - offset += Marshal.SizeOf(); - AnimationBlendWeights = MemoryMarshal.Cast(data[offset..(offset + 4 * 6)]).ToArray(); - offset += 4 * 6; - AnimationRate = MemoryMarshal.Read(data[(offset += 4)..]); - offset += 4; - Flat = MemoryMarshal.Read(data[offset++..]); - ThrowingCap = MemoryMarshal.Read(data[offset++..]); - Seeker = MemoryMarshal.Read(data[offset++..]); - ScenarioNum = MemoryMarshal.Read(data[(offset += 4)..]); - Span strData = MemoryMarshal.Cast(data[offset..]); - Stage = new string(strData[..NameSize].TrimEnd('\0')); - Act = new string(strData[NameSize..(2 * NameSize)].TrimEnd('\0')); - SubAct = new string(strData[(2 * NameSize)..(3 * NameSize)].TrimEnd('\0')); - } +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; + +namespace Shared.Packet.Packets; + +[Packet(PacketType.Player)] +public struct PlayerPacket : IPacket { + public const int NameSize = 0x30; + + public Vector3 Position; + public Quaternion Rotation; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + public float[] AnimationBlendWeights; + public float AnimationRate; + public bool Is2d; + public bool ThrowingCap; + public bool IsIt; + public int ScenarioNum; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)] + public string Stage; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)] + public string Act; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)] + public string SubAct; + + public void Serialize(Span data) { + int offset = 0; + MemoryMarshal.Write(data, ref Position); + offset += Marshal.SizeOf(); + MemoryMarshal.Write(data[offset..], ref Rotation); + offset += Marshal.SizeOf(); + AnimationBlendWeights.CopyTo(MemoryMarshal.Cast(data[offset..(offset += 4 * 6)])); + MemoryMarshal.Write(data[offset..], ref AnimationRate); + offset += 4; + MemoryMarshal.Write(data[offset++..], ref Is2d); + MemoryMarshal.Write(data[offset++..], ref ThrowingCap); + MemoryMarshal.Write(data[offset++..], ref IsIt); + MemoryMarshal.Write(data[offset..], ref ScenarioNum); + offset += 5; + // Span strData = MemoryMarshal.Cast(data[offset..]); + Encoding.UTF8.GetBytes(Stage).CopyTo(data[offset..(offset + NameSize)]); + offset += NameSize; + Encoding.UTF8.GetBytes(Act).CopyTo(data[offset..(offset + NameSize)]); + offset += NameSize; + Encoding.UTF8.GetBytes(SubAct).CopyTo(data[offset..]); + } + + public void Deserialize(Span data) { + int offset = 0; + Position = MemoryMarshal.Read(data); + offset += Marshal.SizeOf(); + Rotation = MemoryMarshal.Read(data[offset..]); + offset += Marshal.SizeOf(); + AnimationBlendWeights = MemoryMarshal.Cast(data[offset..(offset += 4 * 6)]).ToArray(); + AnimationRate = MemoryMarshal.Read(data[offset..(offset += 4)]); + Is2d = MemoryMarshal.Read(data[offset++..]); + ThrowingCap = MemoryMarshal.Read(data[offset++..]); + IsIt = MemoryMarshal.Read(data[offset++..]); + // offset++; // padding + ScenarioNum = MemoryMarshal.Read(data[offset..]); + offset += 5; + Stage = new string(Encoding.UTF8.GetString(data[offset..(offset + NameSize)]).TrimEnd('\0')); + offset += NameSize; + Act = new string(Encoding.UTF8.GetString(data[offset..(offset + NameSize)]).TrimEnd('\0')); + offset += NameSize; + SubAct = new string(Encoding.UTF8.GetString(data[offset..(offset + NameSize)]).TrimEnd('\0')); + } } \ No newline at end of file diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index b718b4d..b0d75bf 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -1,13 +1,13 @@ - - - - net6.0 - enable - enable - - - - - - - + + + + net6.0 + enable + enable + + + + + + + diff --git a/TestClient/Program.cs b/TestClient/Program.cs index 1dce625..a9ec173 100644 --- a/TestClient/Program.cs +++ b/TestClient/Program.cs @@ -8,18 +8,48 @@ using Shared.Packet; using Shared.Packet.Packets; TcpClient client = new TcpClient("127.0.0.1", 1027); -Guid ownId = new Guid(); +Guid ownId = new Guid(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); Guid otherId = Guid.Empty; Logger logger = new Logger("Client"); NetworkStream stream = client.GetStream(); -PacketHeader coolHeader = new PacketHeader { - Type = PacketType.Connect, - Sender = PacketSender.Client, - Id = ownId -}; int e = 0; double d = 0; +Vector3 basePoint = Vector3.Zero; +PlayerPacket? playerPacket = null; + +async void Funny() { + Memory memory = new Memory(new byte[256]); + + { + PacketHeader header = new PacketHeader { + Id = ownId, + Type = PacketType.Player + }; + MemoryMarshal.Write(memory.Span, ref header); + } + + while (true) { + d += Math.PI / 32; + if (playerPacket == null) { + // logger.Warn($"Waiting..."); + await Task.Delay(300); + continue; + } + + PlayerPacket packet = playerPacket.Value; + Vector3 pos = basePoint; + pos.X += 100f * (float) Math.Cos(d); + pos.Y += 300f; + pos.Z += 100f * (float) Math.Sin(d); + packet.Position = pos; + packet.Serialize(memory.Span[Constants.HeaderSize..]); + logger.Warn($"Current strs:{packet.Stage}-{packet.Act}-{packet.SubAct} {packet.Is2d} {packet.ThrowingCap} {packet.IsIt}"); + + await stream.WriteAsync(memory); + await Task.Delay(50); + } +} async Task S() { IMemoryOwner owner = MemoryPool.Shared.Rent(256); @@ -34,23 +64,26 @@ async Task S() { continue; } - d += Math.PI / 180; + header.Id = ownId; + MemoryMarshal.Write(owner.Memory.Span, ref header); unsafe { - MemoryMarshal.Write(owner.Memory.Span, ref coolHeader); - // unbelievably shitty way to marshal playerpacket - fixed (byte* basePtr = owner.Memory.Span) { - byte* dataPtr = basePtr + Constants.HeaderSize; - Vector3 pos = Unsafe.Read(dataPtr); - pos.X += 1000f * (float)Math.Cos(d); - pos.Z += 1000f * (float)Math.Sin(d); - Unsafe.Write(dataPtr, pos); + fixed (byte* data = owner.Memory.Span[Constants.HeaderSize..]) { + logger.Error($"{Marshal.OffsetOf(nameof(PlayerPacket.AnimationBlendWeights))} {Marshal.OffsetOf(nameof(PlayerPacket.AnimationRate))}"); + PlayerPacket packet = Marshal.PtrToStructure((IntPtr) data); + playerPacket = packet; + basePoint = packet.Position; } } - Console.WriteLine($"aargh {coolHeader.Id} {owner.Memory.Span[..256].Hex()}"); - await stream.WriteAsync(owner.Memory); + + // packet.SubAct = ""; } } } + +PacketHeader coolHeader = new PacketHeader { + Type = PacketType.Connect, + Id = ownId +}; IMemoryOwner owner = MemoryPool.Shared.Rent(256); MemoryMarshal.Write(owner.Memory.Span[..], ref coolHeader); ConnectPacket connect = new ConnectPacket { @@ -60,4 +93,6 @@ MemoryMarshal.Write(owner.Memory.Span[Constants.HeaderSize..256], ref connect); await stream.WriteAsync(owner.Memory); coolHeader.Type = PacketType.Player; MemoryMarshal.Write(owner.Memory.Span[..], ref coolHeader); +logger.Info("Connected"); +Task.Run(Funny); await S(); \ No newline at end of file