mirror of
https://github.com/Sanae6/SmoOnlineServer.git
synced 2024-11-25 04:35:18 +00:00
Server overhaul, might rewrite though
This commit is contained in:
parent
cdad20ddd2
commit
de16e4a787
16 changed files with 469 additions and 359 deletions
332
Server/Server.cs
332
Server/Server.cs
|
@ -1,164 +1,170 @@
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Net.Sockets;
|
using System.Net;
|
||||||
using System.Runtime.InteropServices;
|
using System.Net.Sockets;
|
||||||
using Shared;
|
using System.Runtime.InteropServices;
|
||||||
using Shared.Packet;
|
using Shared;
|
||||||
using Shared.Packet.Packets;
|
using Shared.Packet;
|
||||||
|
using Shared.Packet.Packets;
|
||||||
namespace Server;
|
|
||||||
|
namespace Server;
|
||||||
public class Server {
|
|
||||||
private readonly MemoryPool<byte> memoryPool = MemoryPool<byte>.Shared;
|
public class Server {
|
||||||
public readonly List<Client> Clients = new List<Client>();
|
private readonly MemoryPool<byte> memoryPool = MemoryPool<byte>.Shared;
|
||||||
public readonly Logger Logger = new Logger("Server");
|
public readonly List<Client> Clients = new List<Client>();
|
||||||
public async Task Listen(ushort port) {
|
public readonly Logger Logger = new Logger("Server");
|
||||||
TcpListener listener = TcpListener.Create(port);
|
public async Task Listen(ushort port) {
|
||||||
|
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
listener.Start();
|
serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, port));
|
||||||
|
serverSocket.Listen();
|
||||||
while (true) {
|
|
||||||
Socket socket = await listener.AcceptSocketAsync();
|
Logger.Info($"Listening on port {port}");
|
||||||
|
|
||||||
if (Clients.Count > Constants.MaxClients) {
|
while (true) {
|
||||||
Logger.Warn("Turned away client due to max clients");
|
Socket socket = await serverSocket.AcceptAsync();
|
||||||
await socket.DisconnectAsync(false);
|
|
||||||
continue;
|
Logger.Warn("ok");
|
||||||
}
|
|
||||||
|
if (Clients.Count > Constants.MaxClients) {
|
||||||
HandleSocket(socket);
|
Logger.Warn("Turned away client due to max clients");
|
||||||
}
|
await socket.DisconnectAsync(false);
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
public static void FillPacket<T>(PacketHeader header, T packet, Memory<byte> memory) where T : unmanaged, IPacket {
|
|
||||||
Span<byte> data = memory.Span;
|
HandleSocket(socket);
|
||||||
|
}
|
||||||
MemoryMarshal.Write(data, ref header);
|
}
|
||||||
MemoryMarshal.Write(data[Constants.HeaderSize..], ref packet);
|
|
||||||
}
|
public static void FillPacket<T>(PacketHeader header, T packet, Memory<byte> memory) where T : unmanaged, IPacket {
|
||||||
|
Span<byte> data = memory.Span;
|
||||||
// broadcast packets to all clients
|
|
||||||
public async void Broadcast<T>(T packet, Client? sender = null) where T : unmanaged, IPacket {
|
MemoryMarshal.Write(data, ref header);
|
||||||
IMemoryOwner<byte> memory = memoryPool.Rent(Marshal.SizeOf<T>() + Constants.HeaderSize);
|
MemoryMarshal.Write(data[Constants.HeaderSize..], ref packet);
|
||||||
|
}
|
||||||
PacketHeader header = new PacketHeader {
|
|
||||||
Id = sender?.Id ?? Guid.Empty,
|
// broadcast packets to all clients
|
||||||
Type = Constants.Packets[typeof(T)].Type,
|
public async Task Broadcast<T>(T packet, Client? sender = null) where T : unmanaged, IPacket {
|
||||||
Sender = PacketSender.Server // todo maybe use client?
|
IMemoryOwner<byte> memory = memoryPool.Rent(Marshal.SizeOf<T>() + Constants.HeaderSize);
|
||||||
};
|
|
||||||
FillPacket(header, packet, memory.Memory);
|
PacketHeader header = new PacketHeader {
|
||||||
await Broadcast(memory, sender);
|
Id = sender?.Id ?? Guid.Empty,
|
||||||
}
|
Type = Constants.Packets[typeof(T)].Type,
|
||||||
|
};
|
||||||
/// <summary>
|
FillPacket(header, packet, memory.Memory);
|
||||||
/// Takes ownership of data and disposes once done.
|
await Broadcast(memory, sender);
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="data">Memory owner to dispose once done</param>
|
|
||||||
/// <param name="sender">Optional sender to not broadcast data to</param>
|
/// <summary>
|
||||||
public async Task Broadcast(IMemoryOwner<byte> data, Client? sender = null) {
|
/// Takes ownership of data and disposes once done.
|
||||||
await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data.Memory)));
|
/// </summary>
|
||||||
data.Dispose();
|
/// <param name="data">Memory owner to dispose once done</param>
|
||||||
}
|
/// <param name="sender">Optional sender to not broadcast data to</param>
|
||||||
|
public async Task Broadcast(IMemoryOwner<byte> data, Client? sender = null) {
|
||||||
/// <summary>
|
await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data.Memory)));
|
||||||
/// Broadcasts memory whose memory shouldn't be disposed, should only be fired by server code.
|
data.Dispose();
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="data">Memory to send to the clients</param>
|
|
||||||
/// <param name="sender">Optional sender to not broadcast data to</param>
|
/// <summary>
|
||||||
public async void Broadcast(Memory<byte> data, Client? sender = null) {
|
/// Broadcasts memory whose memory shouldn't be disposed, should only be fired by server code.
|
||||||
await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data)));
|
/// </summary>
|
||||||
}
|
/// <param name="data">Memory to send to the clients</param>
|
||||||
|
/// <param name="sender">Optional sender to not broadcast data to</param>
|
||||||
public Client? FindExistingClient(Guid id) {
|
public async void Broadcast(Memory<byte> data, Client? sender = null) {
|
||||||
return Clients.Find(client => client.Id == id);
|
await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Client? FindExistingClient(Guid id) {
|
||||||
private async void HandleSocket(Socket socket) {
|
return Clients.Find(client => client.Id == id);
|
||||||
Client client = new Client {Socket = socket};
|
}
|
||||||
IMemoryOwner<byte> memory = null!;
|
|
||||||
bool first = true;
|
|
||||||
try {
|
private async void HandleSocket(Socket socket) {
|
||||||
while (true) {
|
Client client = new Client {Socket = socket};
|
||||||
memory = memoryPool.Rent(Constants.MaxPacketSize);
|
IMemoryOwner<byte> memory = null!;
|
||||||
int size = await socket.ReceiveAsync(memory.Memory, SocketFlags.None);
|
bool first = true;
|
||||||
if (size == 0) { // treat it as a disconnect and exit
|
try {
|
||||||
Logger.Info($"Socket {socket.RemoteEndPoint} disconnected.");
|
while (true) {
|
||||||
await socket.DisconnectAsync(false);
|
memory = memoryPool.Rent(Constants.MaxPacketSize);
|
||||||
break;
|
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.");
|
||||||
PacketHeader header = GetHeader(memory.Memory.Span[..size]);
|
await socket.DisconnectAsync(false);
|
||||||
//Logger.Info($"first = {first}, type = {header.Type}, data = " + memory.Memory.Span[..size].Hex());
|
break;
|
||||||
// connection initialization
|
}
|
||||||
if (first) {
|
|
||||||
first = false;
|
PacketHeader header = GetHeader(memory.Memory.Span[..size]);
|
||||||
if (header.Type != PacketType.Connect) {
|
//Logger.Info($"first = {first}, type = {header.Type}, data = " + memory.Memory.Span[..size].Hex());
|
||||||
throw new Exception($"First packet was not init, instead it was {header.Type}");
|
// connection initialization
|
||||||
}
|
if (first) {
|
||||||
|
first = false;
|
||||||
ConnectPacket connect = MemoryMarshal.Read<ConnectPacket>(memory.Memory.Span[Constants.HeaderSize..size]);
|
if (header.Type != PacketType.Connect) {
|
||||||
lock (Clients) {
|
throw new Exception($"First packet was not init, instead it was {header.Type}");
|
||||||
switch (connect.ConnectionType) {
|
}
|
||||||
case ConnectionTypes.FirstConnection: {
|
|
||||||
// do any cleanup required when it comes to new clients
|
ConnectPacket connect = MemoryMarshal.Read<ConnectPacket>(memory.Memory.Span[Constants.HeaderSize..size]);
|
||||||
List<Client> toDisconnect = Clients.FindAll(c => c.Id == header.Id && c.Connected && c.Socket != null);
|
lock (Clients) {
|
||||||
Clients.RemoveAll(c => c.Id == header.Id);
|
bool firstConn = false;
|
||||||
|
switch (connect.ConnectionType) {
|
||||||
client.Id = header.Id;
|
case ConnectionTypes.FirstConnection: {
|
||||||
Clients.Add(client);
|
firstConn = true;
|
||||||
|
break;
|
||||||
foreach (Client c in toDisconnect) c.Socket!.DisconnectAsync(false);
|
}
|
||||||
// done disconnecting and removing stale clients with the same id
|
case ConnectionTypes.Reconnecting: {
|
||||||
break;
|
if (FindExistingClient(header.Id) is { } newClient) {
|
||||||
}
|
if (newClient.Connected) throw new Exception($"Tried to join as already connected user {header.Id}");
|
||||||
case ConnectionTypes.Reconnecting: {
|
newClient.Socket = client.Socket;
|
||||||
if (FindExistingClient(header.Id) is { } newClient) {
|
client = newClient;
|
||||||
if (newClient.Connected) throw new Exception($"Tried to join as already connected user {header.Id}");
|
} else {
|
||||||
newClient.Socket = client.Socket;
|
firstConn = true;
|
||||||
client = newClient;
|
}
|
||||||
} else {
|
break;
|
||||||
// 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?
|
default:
|
||||||
throw new Exception("Could not find a suitable client to reconnect as");
|
throw new Exception($"Invalid connection type {connect.ConnectionType}");
|
||||||
}
|
}
|
||||||
break;
|
if (firstConn) {
|
||||||
}
|
// do any cleanup required when it comes to new clients
|
||||||
default:
|
List<Client> toDisconnect = Clients.FindAll(c => c.Id == header.Id && c.Connected && c.Socket != null);
|
||||||
throw new Exception($"Invalid connection type {connect.ConnectionType}");
|
Clients.RemoveAll(c => c.Id == header.Id);
|
||||||
}
|
|
||||||
}
|
client.Id = header.Id;
|
||||||
|
Clients.Add(client);
|
||||||
Logger.Info($"Client {socket.RemoteEndPoint} connected.");
|
|
||||||
|
Parallel.ForEachAsync(toDisconnect, (c, token) => c.Socket!.DisconnectAsync(false, token));
|
||||||
continue;
|
// Broadcast(connect, client);
|
||||||
}
|
// done disconnecting and removing stale clients with the same id
|
||||||
|
}
|
||||||
|
}
|
||||||
// todo support variable length packets when they show up
|
|
||||||
if (header.Sender == PacketSender.Client) {
|
Logger.Info($"Client {socket.RemoteEndPoint} connected.");
|
||||||
Logger.Warn($"broadcasting {header.Type}");
|
|
||||||
await Broadcast(memory, client);
|
// continue;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
//todo handle server packets :)
|
|
||||||
}
|
// 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");
|
catch (Exception e) {
|
||||||
} else {
|
if (e is SocketException {SocketErrorCode: SocketError.ConnectionReset}) {
|
||||||
Logger.Error($"Exception on socket {socket.RemoteEndPoint}, disconnecting for: {e}");
|
Logger.Info($"Client {socket.RemoteEndPoint} disconnected from the server");
|
||||||
await socket.DisconnectAsync(false);
|
} else {
|
||||||
}
|
Logger.Error($"Exception on socket {socket.RemoteEndPoint}, disconnecting for: {e}");
|
||||||
|
await socket.DisconnectAsync(false);
|
||||||
memory?.Dispose();
|
}
|
||||||
}
|
|
||||||
client.Dispose();
|
memory?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PacketHeader GetHeader(Span<byte> data) {
|
Clients.Remove(client);
|
||||||
//no need to error check, the client will disconnect when the packet is invalid :)
|
Broadcast(new DisconnectPacket(), client).ContinueWith(_ => {
|
||||||
return MemoryMarshal.Read<PacketHeader>(data);
|
client.Dispose();
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PacketHeader GetHeader(Span<byte> data) {
|
||||||
|
//no need to error check, the client will disconnect when the packet is invalid :)
|
||||||
|
return MemoryMarshal.Read<PacketHeader>(data);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Shared.Packet;
|
using Shared.Packet;
|
||||||
using Shared.Packet.Packets;
|
using Shared.Packet.Packets;
|
||||||
|
|
||||||
namespace Shared;
|
namespace Shared;
|
||||||
|
|
||||||
public static class Constants {
|
public static class Constants {
|
||||||
public const int MaxPacketSize = 256;
|
public const int MaxPacketSize = 256;
|
||||||
public const int MaxClients = 4;
|
public const int MaxClients = 4;
|
||||||
public static int HeaderSize => Marshal.SizeOf<PacketHeader>();
|
public static int HeaderSize { get; } = Marshal.SizeOf<PacketHeader>();
|
||||||
|
|
||||||
// dictionary of packet types to packet
|
// dictionary of packet types to packet
|
||||||
public static readonly Dictionary<Type, PacketAttribute> Packets = Assembly
|
public static readonly Dictionary<Type, PacketAttribute> Packets = Assembly
|
||||||
.GetExecutingAssembly()
|
.GetExecutingAssembly()
|
||||||
.GetTypes()
|
.GetTypes()
|
||||||
.Where(type => type.IsAssignableTo(typeof(IPacket)))
|
.Where(type => type.IsAssignableTo(typeof(IPacket)))
|
||||||
.ToDictionary(type => type, type => type.GetCustomAttribute<PacketAttribute>()!);
|
.ToDictionary(type => type, type => type.GetCustomAttribute<PacketAttribute>()!);
|
||||||
}
|
}
|
|
@ -1,10 +1,21 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Shared.Packet.Packets;
|
||||||
namespace Shared.Packet;
|
|
||||||
|
namespace Shared.Packet;
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct PacketHeader {
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public Guid Id;
|
public struct PacketHeader : IPacket {
|
||||||
public PacketType Type;
|
// public int Length;
|
||||||
public PacketSender Sender;
|
public Guid Id;
|
||||||
|
public PacketType Type;
|
||||||
|
public void Serialize(Span<byte> data) {
|
||||||
|
// MemoryMarshal.Write(data, ref Length);
|
||||||
|
MemoryMarshal.Write(data, ref Id);
|
||||||
|
MemoryMarshal.Write(data[16..], ref Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deserialize(Span<byte> data) {
|
||||||
|
Id = MemoryMarshal.Read<Guid>(data);
|
||||||
|
Type = MemoryMarshal.Read<PacketType>(data[16..]);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Shared.Packet;
|
|
||||||
|
|
||||||
public enum PacketSender {
|
|
||||||
Server,
|
|
||||||
Client
|
|
||||||
}
|
|
|
@ -1,12 +1,14 @@
|
||||||
namespace Shared.Packet;
|
namespace Shared.Packet;
|
||||||
|
|
||||||
public enum PacketType {
|
public enum PacketType {
|
||||||
Unknown,
|
Unknown,
|
||||||
Player,
|
Player,
|
||||||
Game,
|
Cap,
|
||||||
Connect,
|
Game,
|
||||||
Disconnect,
|
Tag,
|
||||||
Costume,
|
Connect,
|
||||||
Shine,
|
Disconnect,
|
||||||
Command
|
Costume,
|
||||||
|
Shine,
|
||||||
|
Command
|
||||||
}
|
}
|
20
Shared/Packet/PacketUtils.cs
Normal file
20
Shared/Packet/PacketUtils.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Shared.Packet.Packets;
|
||||||
|
|
||||||
|
namespace Shared.Packet;
|
||||||
|
|
||||||
|
public static class PacketUtils {
|
||||||
|
public static void SerializeHeaded<T>(Span<byte> data, PacketHeader header, T t) where T : struct, IPacket {
|
||||||
|
header.Serialize(data);
|
||||||
|
t.Serialize(data[Constants.HeaderSize..]);
|
||||||
|
}
|
||||||
|
public static T Deserialize<T>(Span<byte> data) where T : IPacket, new() {
|
||||||
|
T packet = new T();
|
||||||
|
packet.Deserialize(data);
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int SizeOf<T>() where T : struct, IPacket {
|
||||||
|
return Constants.HeaderSize + Marshal.SizeOf<T>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
namespace Shared.Packet.Packets;
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
[Packet(PacketType.Command)]
|
[Packet(PacketType.Command)]
|
||||||
public struct CommandPacket : IPacket {
|
public struct CommandPacket : IPacket {
|
||||||
public void Serialize(Span<byte> data) {
|
//todo: implement something for this
|
||||||
|
public void Serialize(Span<byte> data) {
|
||||||
}
|
|
||||||
|
}
|
||||||
public void Deserialize(Span<byte> data) {
|
|
||||||
|
public void Deserialize(Span<byte> data) {
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,15 +1,15 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Shared.Packet.Packets;
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
[Packet(PacketType.Connect)]
|
[Packet(PacketType.Connect)]
|
||||||
public struct ConnectPacket : IPacket {
|
public struct ConnectPacket : IPacket {
|
||||||
public ConnectionTypes ConnectionType;
|
public ConnectionTypes ConnectionType;
|
||||||
public void Serialize(Span<byte> data) {
|
public void Serialize(Span<byte> data) {
|
||||||
MemoryMarshal.Write(data, ref ConnectionType);
|
MemoryMarshal.Write(data, ref ConnectionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deserialize(Span<byte> data) {
|
public void Deserialize(Span<byte> data) {
|
||||||
ConnectionType = MemoryMarshal.Read<ConnectionTypes>(data);
|
ConnectionType = MemoryMarshal.Read<ConnectionTypes>(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,22 +1,24 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Shared.Packet.Packets;
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
[Packet(PacketType.Costume)]
|
[Packet(PacketType.Costume)]
|
||||||
public struct CostumePacket : IPacket {
|
public struct CostumePacket : IPacket {
|
||||||
public const int CostumeNameSize = 0x20;
|
public const int CostumeNameSize = 0x20;
|
||||||
|
|
||||||
public string BodyName;
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CostumeNameSize)]
|
||||||
public string CapName;
|
public string BodyName;
|
||||||
public void Serialize(Span<byte> data) {
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CostumeNameSize)]
|
||||||
Span<char> strData = MemoryMarshal.Cast<byte, char>(data);
|
public string CapName;
|
||||||
BodyName.CopyTo(strData[..CostumeNameSize]);
|
public void Serialize(Span<byte> data) {
|
||||||
CapName.CopyTo(strData[CostumeNameSize..]);
|
Span<char> strData = MemoryMarshal.Cast<byte, char>(data);
|
||||||
}
|
BodyName.CopyTo(strData[..CostumeNameSize]);
|
||||||
|
CapName.CopyTo(strData[CostumeNameSize..]);
|
||||||
public void Deserialize(Span<byte> data) {
|
}
|
||||||
Span<char> strData = MemoryMarshal.Cast<byte, char>(data);
|
|
||||||
BodyName = new string(strData[..CostumeNameSize].TrimEnd('\0'));
|
public void Deserialize(Span<byte> data) {
|
||||||
CapName = new string(strData[CostumeNameSize..].TrimEnd('\0'));
|
Span<char> strData = MemoryMarshal.Cast<byte, char>(data);
|
||||||
}
|
BodyName = new string(strData[..CostumeNameSize].TrimEnd('\0'));
|
||||||
|
CapName = new string(strData[CostumeNameSize..].TrimEnd('\0'));
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
namespace Shared.Packet.Packets;
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
[Packet(PacketType.Disconnect)]
|
[Packet(PacketType.Disconnect)]
|
||||||
public struct DisconnectPacket : IPacket {
|
public struct DisconnectPacket : IPacket {
|
||||||
public void Serialize(Span<byte> data) {
|
//empty packet
|
||||||
|
public void Serialize(Span<byte> data) {
|
||||||
}
|
|
||||||
|
}
|
||||||
public void Deserialize(Span<byte> data) {
|
|
||||||
|
public void Deserialize(Span<byte> data) {
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
}
|
19
Shared/Packet/Packets/GamePacket.cs
Normal file
19
Shared/Packet/Packets/GamePacket.cs
Normal file
|
@ -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<byte> data) {
|
||||||
|
MemoryMarshal.Write(data, ref ScenarioNum);
|
||||||
|
Stage.CopyTo(MemoryMarshal.Cast<byte, char>(data[4..]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deserialize(Span<byte> data) {
|
||||||
|
ScenarioNum = MemoryMarshal.Read<int>(data);
|
||||||
|
Stage = new string(MemoryMarshal.Cast<byte, char>(data[4..]));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
namespace Shared.Packet.Packets;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
// Packet interface for type safety
|
namespace Shared.Packet.Packets;
|
||||||
public interface IPacket {
|
|
||||||
void Serialize(Span<byte> data);
|
// Packet interface for type safety
|
||||||
void Deserialize(Span<byte> data);
|
public interface IPacket {
|
||||||
|
void Serialize(Span<byte> data);
|
||||||
|
void Deserialize(Span<byte> data);
|
||||||
}
|
}
|
8
Shared/Packet/Packets/MovementFlags.cs
Normal file
8
Shared/Packet/Packets/MovementFlags.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum MovementFlags : byte {
|
||||||
|
IsFlat,
|
||||||
|
IsCapThrown,
|
||||||
|
IsSeeker
|
||||||
|
}
|
|
@ -1,60 +1,69 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
namespace Shared.Packet.Packets;
|
|
||||||
|
namespace Shared.Packet.Packets;
|
||||||
[Packet(PacketType.Player)]
|
|
||||||
public struct PlayerPacket : IPacket {
|
[Packet(PacketType.Player)]
|
||||||
public const int NameSize = 0x30;
|
public struct PlayerPacket : IPacket {
|
||||||
|
public const int NameSize = 0x30;
|
||||||
public Vector3 Position;
|
|
||||||
public Quaternion Rotation;
|
public Vector3 Position;
|
||||||
public float[] AnimationBlendWeights;
|
public Quaternion Rotation;
|
||||||
public float AnimationRate;
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
public bool Flat;
|
public float[] AnimationBlendWeights;
|
||||||
public bool ThrowingCap;
|
public float AnimationRate;
|
||||||
public bool Seeker;
|
public bool Is2d;
|
||||||
public int ScenarioNum;
|
public bool ThrowingCap;
|
||||||
public string Stage;
|
public bool IsIt;
|
||||||
public string Act;
|
public int ScenarioNum;
|
||||||
public string SubAct;
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)]
|
||||||
|
public string Stage;
|
||||||
public void Serialize(Span<byte> data) {
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)]
|
||||||
int offset = 0;
|
public string Act;
|
||||||
MemoryMarshal.Write(data, ref Position);
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)]
|
||||||
offset += Marshal.SizeOf<Vector3>();
|
public string SubAct;
|
||||||
MemoryMarshal.Write(data[offset..], ref Rotation);
|
|
||||||
offset += Marshal.SizeOf<Quaternion>();
|
public void Serialize(Span<byte> data) {
|
||||||
AnimationBlendWeights.CopyTo(MemoryMarshal.Cast<byte, float>(data[offset..(offset += 4 * 6)]));
|
int offset = 0;
|
||||||
MemoryMarshal.Write(data[(offset += 4)..], ref AnimationRate);
|
MemoryMarshal.Write(data, ref Position);
|
||||||
offset += 4;
|
offset += Marshal.SizeOf<Vector3>();
|
||||||
MemoryMarshal.Write(data[offset++..], ref Flat);
|
MemoryMarshal.Write(data[offset..], ref Rotation);
|
||||||
MemoryMarshal.Write(data[offset++..], ref ThrowingCap);
|
offset += Marshal.SizeOf<Quaternion>();
|
||||||
MemoryMarshal.Write(data[offset++..], ref Seeker);
|
AnimationBlendWeights.CopyTo(MemoryMarshal.Cast<byte, float>(data[offset..(offset += 4 * 6)]));
|
||||||
MemoryMarshal.Write(data[(offset += 4)..], ref ScenarioNum);
|
MemoryMarshal.Write(data[offset..], ref AnimationRate);
|
||||||
Span<char> strData = MemoryMarshal.Cast<byte, char>(data[offset..]);
|
offset += 4;
|
||||||
Stage.CopyTo(strData[..NameSize]);
|
MemoryMarshal.Write(data[offset++..], ref Is2d);
|
||||||
Act.CopyTo(strData[NameSize..(2 * NameSize)]);
|
MemoryMarshal.Write(data[offset++..], ref ThrowingCap);
|
||||||
SubAct.CopyTo(strData[(2 * NameSize)..(3 * NameSize)]);
|
MemoryMarshal.Write(data[offset++..], ref IsIt);
|
||||||
}
|
MemoryMarshal.Write(data[offset..], ref ScenarioNum);
|
||||||
|
offset += 5;
|
||||||
public void Deserialize(Span<byte> data) {
|
// Span<char> strData = MemoryMarshal.Cast<byte, char>(data[offset..]);
|
||||||
int offset = 0;
|
Encoding.UTF8.GetBytes(Stage).CopyTo(data[offset..(offset + NameSize)]);
|
||||||
Position = MemoryMarshal.Read<Vector3>(data);
|
offset += NameSize;
|
||||||
offset += Marshal.SizeOf<Vector3>();
|
Encoding.UTF8.GetBytes(Act).CopyTo(data[offset..(offset + NameSize)]);
|
||||||
Rotation = MemoryMarshal.Read<Quaternion>(data[offset..]);
|
offset += NameSize;
|
||||||
offset += Marshal.SizeOf<Quaternion>();
|
Encoding.UTF8.GetBytes(SubAct).CopyTo(data[offset..]);
|
||||||
AnimationBlendWeights = MemoryMarshal.Cast<byte, float>(data[offset..(offset + 4 * 6)]).ToArray();
|
}
|
||||||
offset += 4 * 6;
|
|
||||||
AnimationRate = MemoryMarshal.Read<float>(data[(offset += 4)..]);
|
public void Deserialize(Span<byte> data) {
|
||||||
offset += 4;
|
int offset = 0;
|
||||||
Flat = MemoryMarshal.Read<bool>(data[offset++..]);
|
Position = MemoryMarshal.Read<Vector3>(data);
|
||||||
ThrowingCap = MemoryMarshal.Read<bool>(data[offset++..]);
|
offset += Marshal.SizeOf<Vector3>();
|
||||||
Seeker = MemoryMarshal.Read<bool>(data[offset++..]);
|
Rotation = MemoryMarshal.Read<Quaternion>(data[offset..]);
|
||||||
ScenarioNum = MemoryMarshal.Read<int>(data[(offset += 4)..]);
|
offset += Marshal.SizeOf<Quaternion>();
|
||||||
Span<char> strData = MemoryMarshal.Cast<byte, char>(data[offset..]);
|
AnimationBlendWeights = MemoryMarshal.Cast<byte, float>(data[offset..(offset += 4 * 6)]).ToArray();
|
||||||
Stage = new string(strData[..NameSize].TrimEnd('\0'));
|
AnimationRate = MemoryMarshal.Read<float>(data[offset..(offset += 4)]);
|
||||||
Act = new string(strData[NameSize..(2 * NameSize)].TrimEnd('\0'));
|
Is2d = MemoryMarshal.Read<bool>(data[offset++..]);
|
||||||
SubAct = new string(strData[(2 * NameSize)..(3 * NameSize)].TrimEnd('\0'));
|
ThrowingCap = MemoryMarshal.Read<bool>(data[offset++..]);
|
||||||
}
|
IsIt = MemoryMarshal.Read<bool>(data[offset++..]);
|
||||||
|
// offset++; // padding
|
||||||
|
ScenarioNum = MemoryMarshal.Read<int>(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'));
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -8,18 +8,48 @@ using Shared.Packet;
|
||||||
using Shared.Packet.Packets;
|
using Shared.Packet.Packets;
|
||||||
|
|
||||||
TcpClient client = new TcpClient("127.0.0.1", 1027);
|
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;
|
Guid otherId = Guid.Empty;
|
||||||
Logger logger = new Logger("Client");
|
Logger logger = new Logger("Client");
|
||||||
NetworkStream stream = client.GetStream();
|
NetworkStream stream = client.GetStream();
|
||||||
PacketHeader coolHeader = new PacketHeader {
|
|
||||||
Type = PacketType.Connect,
|
|
||||||
Sender = PacketSender.Client,
|
|
||||||
Id = ownId
|
|
||||||
};
|
|
||||||
|
|
||||||
int e = 0;
|
int e = 0;
|
||||||
double d = 0;
|
double d = 0;
|
||||||
|
Vector3 basePoint = Vector3.Zero;
|
||||||
|
PlayerPacket? playerPacket = null;
|
||||||
|
|
||||||
|
async void Funny() {
|
||||||
|
Memory<byte> memory = new Memory<byte>(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() {
|
async Task S() {
|
||||||
IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(256);
|
IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(256);
|
||||||
|
@ -34,23 +64,26 @@ async Task S() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
d += Math.PI / 180;
|
header.Id = ownId;
|
||||||
|
MemoryMarshal.Write(owner.Memory.Span, ref header);
|
||||||
unsafe {
|
unsafe {
|
||||||
MemoryMarshal.Write(owner.Memory.Span, ref coolHeader);
|
fixed (byte* data = owner.Memory.Span[Constants.HeaderSize..]) {
|
||||||
// unbelievably shitty way to marshal playerpacket
|
logger.Error($"{Marshal.OffsetOf<PlayerPacket>(nameof(PlayerPacket.AnimationBlendWeights))} {Marshal.OffsetOf<PlayerPacket>(nameof(PlayerPacket.AnimationRate))}");
|
||||||
fixed (byte* basePtr = owner.Memory.Span) {
|
PlayerPacket packet = Marshal.PtrToStructure<PlayerPacket>((IntPtr) data);
|
||||||
byte* dataPtr = basePtr + Constants.HeaderSize;
|
playerPacket = packet;
|
||||||
Vector3 pos = Unsafe.Read<Vector3>(dataPtr);
|
basePoint = packet.Position;
|
||||||
pos.X += 1000f * (float)Math.Cos(d);
|
|
||||||
pos.Z += 1000f * (float)Math.Sin(d);
|
|
||||||
Unsafe.Write(dataPtr, pos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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<byte> owner = MemoryPool<byte>.Shared.Rent(256);
|
IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(256);
|
||||||
MemoryMarshal.Write(owner.Memory.Span[..], ref coolHeader);
|
MemoryMarshal.Write(owner.Memory.Span[..], ref coolHeader);
|
||||||
ConnectPacket connect = new ConnectPacket {
|
ConnectPacket connect = new ConnectPacket {
|
||||||
|
@ -60,4 +93,6 @@ MemoryMarshal.Write(owner.Memory.Span[Constants.HeaderSize..256], ref connect);
|
||||||
await stream.WriteAsync(owner.Memory);
|
await stream.WriteAsync(owner.Memory);
|
||||||
coolHeader.Type = PacketType.Player;
|
coolHeader.Type = PacketType.Player;
|
||||||
MemoryMarshal.Write(owner.Memory.Span[..], ref coolHeader);
|
MemoryMarshal.Write(owner.Memory.Span[..], ref coolHeader);
|
||||||
|
logger.Info("Connected");
|
||||||
|
Task.Run(Funny);
|
||||||
await S();
|
await S();
|
Loading…
Reference in a new issue