mirror of
https://github.com/Sanae6/SmoOnlineServer.git
synced 2024-11-24 20:25:18 +00:00
Initial Commit
This commit is contained in:
commit
3f4a3fee9e
24 changed files with 441 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
/packages/
|
||||||
|
riderModule.iml
|
||||||
|
/_ReSharper.Caches/
|
||||||
|
.idea/
|
23
Server/Client.cs
Normal file
23
Server/Client.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Server;
|
||||||
|
|
||||||
|
public class Client : IDisposable {
|
||||||
|
public Socket? Socket;
|
||||||
|
public bool Connected => Socket?.Connected ?? false;
|
||||||
|
public Guid Id;
|
||||||
|
public readonly Dictionary<string, object> Metadata = new Dictionary<string, object>(); // can be used to store any information about a player
|
||||||
|
|
||||||
|
public async Task Send(Memory<byte> data) {
|
||||||
|
if (!Connected) return;
|
||||||
|
await Socket!.SendAsync(data, SocketFlags.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
Socket?.Disconnect(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Client? left, Client? right) => left is { } leftClient && right is { } rightClient && leftClient.Id == rightClient.Id;
|
||||||
|
public static bool operator !=(Client? left, Client? right) => !(left == right);
|
||||||
|
}
|
6
Server/Program.cs
Normal file
6
Server/Program.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
Server.Server server = new Server.Server();
|
||||||
|
|
||||||
|
await server.Listen(1027);
|
162
Server/Server.cs
Normal file
162
Server/Server.cs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
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<byte> memoryPool = MemoryPool<byte>.Shared;
|
||||||
|
public readonly List<Client> Clients = new List<Client>();
|
||||||
|
public readonly Logger Logger = new Logger("Server");
|
||||||
|
private static int HeaderSize => Marshal.SizeOf<PacketHeader>();
|
||||||
|
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<T>(PacketHeader header, T packet, Memory<byte> memory) where T : unmanaged, IPacket {
|
||||||
|
Span<byte> data = memory.Span;
|
||||||
|
|
||||||
|
MemoryMarshal.Write(data, ref header);
|
||||||
|
MemoryMarshal.Write(data[HeaderSize..], ref packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcast packets to all clients
|
||||||
|
public async void Broadcast<T>(T packet, Client? sender = null) where T : unmanaged, IPacket {
|
||||||
|
IMemoryOwner<byte> memory = memoryPool.Rent(Marshal.SizeOf<T>() + 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes ownership of data and disposes once done.
|
||||||
|
/// </summary>
|
||||||
|
/// <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) {
|
||||||
|
await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data.Memory)));
|
||||||
|
data.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcasts memory whose memory shouldn't be disposed, should only be fired by server code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Memory to send to the clients</param>
|
||||||
|
/// <param name="sender">Optional sender to not broadcast data to</param>
|
||||||
|
public async void Broadcast(Memory<byte> 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<byte> 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectPacket connect = MemoryMarshal.Read<ConnectPacket>(memory.Memory.Span[HeaderSize..size]);
|
||||||
|
lock (Clients) {
|
||||||
|
switch (connect.ConnectionType) {
|
||||||
|
case ConnectionTypes.FirstConnection: {
|
||||||
|
// do any cleanup required when it comes to new clients
|
||||||
|
List<Client> 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) 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<byte> data) {
|
||||||
|
//no need to error check, the client will disconnect when the packet is invalid :)
|
||||||
|
return MemoryMarshal.Read<PacketHeader>(data);
|
||||||
|
}
|
||||||
|
}
|
14
Server/Server.csproj
Normal file
14
Server/Server.csproj
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Shared\Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
17
Shared/Constants.cs
Normal file
17
Shared/Constants.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using Shared.Packet;
|
||||||
|
using Shared.Packet.Packets;
|
||||||
|
|
||||||
|
namespace Shared;
|
||||||
|
|
||||||
|
public static class Constants {
|
||||||
|
public const int MaxPacketSize = 256;
|
||||||
|
public const int MaxClients = 4;
|
||||||
|
|
||||||
|
// dictionary of packet types to packet
|
||||||
|
public static readonly Dictionary<Type, PacketAttribute> Packets = Assembly
|
||||||
|
.GetExecutingAssembly()
|
||||||
|
.GetTypes()
|
||||||
|
.Where(type => type.IsAssignableTo(typeof(IPacket)))
|
||||||
|
.ToDictionary(type => type, type => type.GetCustomAttribute<PacketAttribute>()!);
|
||||||
|
}
|
11
Shared/Extensions.cs
Normal file
11
Shared/Extensions.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Shared;
|
||||||
|
|
||||||
|
public static class Extensions {
|
||||||
|
public static string Hex(this Span<byte> span) {
|
||||||
|
return span.ToArray().Hex();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Hex(this IEnumerable<byte> array) => string.Join(' ', array.ToArray().Select(x => x.ToString("X2")));
|
||||||
|
}
|
23
Shared/Logger.cs
Normal file
23
Shared/Logger.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
namespace Shared;
|
||||||
|
|
||||||
|
public class Logger {
|
||||||
|
public string Name { get; }
|
||||||
|
public Logger(string name) {
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Info(string text) {
|
||||||
|
Console.ResetColor();
|
||||||
|
Console.WriteLine($"Info [{Name}] {text}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Warn(string text) {
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.WriteLine($"Warn [{Name}] {text}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string text) {
|
||||||
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
Console.WriteLine($"Warn [{Name}] {text}");
|
||||||
|
}
|
||||||
|
}
|
9
Shared/Packet/PacketAttribute.cs
Normal file
9
Shared/Packet/PacketAttribute.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Shared.Packet;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Struct)]
|
||||||
|
public class PacketAttribute : Attribute {
|
||||||
|
public PacketType Type { get; }
|
||||||
|
public PacketAttribute(PacketType type) {
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
10
Shared/Packet/PacketHeader.cs
Normal file
10
Shared/Packet/PacketHeader.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Shared.Packet;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct PacketHeader {
|
||||||
|
public Guid Id;
|
||||||
|
public PacketType Type;
|
||||||
|
public PacketSender Sender;
|
||||||
|
}
|
6
Shared/Packet/PacketSender.cs
Normal file
6
Shared/Packet/PacketSender.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Shared.Packet;
|
||||||
|
|
||||||
|
public enum PacketSender {
|
||||||
|
Server,
|
||||||
|
Client
|
||||||
|
}
|
12
Shared/Packet/PacketType.cs
Normal file
12
Shared/Packet/PacketType.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Shared.Packet;
|
||||||
|
|
||||||
|
public enum PacketType {
|
||||||
|
Unknown,
|
||||||
|
Connect,
|
||||||
|
Player,
|
||||||
|
Game,
|
||||||
|
Disconnect,
|
||||||
|
Costume,
|
||||||
|
Shine,
|
||||||
|
Command
|
||||||
|
}
|
5
Shared/Packet/Packets/CommandPacket.cs
Normal file
5
Shared/Packet/Packets/CommandPacket.cs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
|
[Packet(PacketType.Command)]
|
||||||
|
public struct CommandPacket : IPacket {
|
||||||
|
}
|
6
Shared/Packet/Packets/ConnectPacket.cs
Normal file
6
Shared/Packet/Packets/ConnectPacket.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
|
[Packet(PacketType.Connect)]
|
||||||
|
public struct ConnectPacket : IPacket {
|
||||||
|
public ConnectionTypes ConnectionType;
|
||||||
|
}
|
6
Shared/Packet/Packets/ConnectionTypes.cs
Normal file
6
Shared/Packet/Packets/ConnectionTypes.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
|
public enum ConnectionTypes {
|
||||||
|
FirstConnection,
|
||||||
|
Reconnecting
|
||||||
|
}
|
13
Shared/Packet/Packets/CostumePacket.cs
Normal file
13
Shared/Packet/Packets/CostumePacket.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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;
|
||||||
|
}
|
5
Shared/Packet/Packets/DisconnectPacket.cs
Normal file
5
Shared/Packet/Packets/DisconnectPacket.cs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
|
[Packet(PacketType.Disconnect)]
|
||||||
|
public struct DisconnectPacket : IPacket {
|
||||||
|
}
|
5
Shared/Packet/Packets/IPacket.cs
Normal file
5
Shared/Packet/Packets/IPacket.cs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
|
// Packet interface for type safety
|
||||||
|
public interface IPacket {
|
||||||
|
}
|
25
Shared/Packet/Packets/PlayerPacket.cs
Normal file
25
Shared/Packet/Packets/PlayerPacket.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
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;
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
|
public float[] AnimationBlendWeights;
|
||||||
|
public float AnimationRate;
|
||||||
|
public bool Flat;
|
||||||
|
public bool ThrowingCap;
|
||||||
|
public bool Seeker;
|
||||||
|
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;
|
||||||
|
}
|
6
Shared/Packet/Packets/ShinePacket.cs
Normal file
6
Shared/Packet/Packets/ShinePacket.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Shared.Packet.Packets;
|
||||||
|
|
||||||
|
[Packet(PacketType.Shine)]
|
||||||
|
public struct ShinePacket : IPacket {
|
||||||
|
public int ShineId;
|
||||||
|
}
|
13
Shared/Shared.csproj
Normal file
13
Shared/Shared.csproj
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
28
SmoMultiplayerServer.sln
Normal file
28
SmoMultiplayerServer.sln
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{AB5DE676-F731-481C-909C-48905B6E5274}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "TestClient\TestClient.csproj", "{6CA2B314-2D79-46DC-9706-48E003A025BD}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{CE222C1F-FBCE-4690-BD14-B7D6290A473E}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{AB5DE676-F731-481C-909C-48905B6E5274}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AB5DE676-F731-481C-909C-48905B6E5274}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AB5DE676-F731-481C-909C-48905B6E5274}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AB5DE676-F731-481C-909C-48905B6E5274}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6CA2B314-2D79-46DC-9706-48E003A025BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6CA2B314-2D79-46DC-9706-48E003A025BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6CA2B314-2D79-46DC-9706-48E003A025BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6CA2B314-2D79-46DC-9706-48E003A025BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CE222C1F-FBCE-4690-BD14-B7D6290A473E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CE222C1F-FBCE-4690-BD14-B7D6290A473E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CE222C1F-FBCE-4690-BD14-B7D6290A473E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CE222C1F-FBCE-4690-BD14-B7D6290A473E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
16
TestClient/Program.cs
Normal file
16
TestClient/Program.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Shared;
|
||||||
|
using Shared.Packet;
|
||||||
|
using Shared.Packet.Packets;
|
||||||
|
|
||||||
|
TcpClient client = new TcpClient("127.0.0.1", 1027);
|
||||||
|
Guid ownId = new Guid();
|
||||||
|
Logger logger = new Logger("Client");
|
||||||
|
NetworkStream stream = client.GetStream();
|
||||||
|
|
||||||
|
|
||||||
|
// void WritePacket(Span<byte> data, IPacket packet) {
|
||||||
|
// MemoryMarshal.Write();
|
||||||
|
// }
|
||||||
|
// stream.Write();
|
14
TestClient/TestClient.csproj
Normal file
14
TestClient/TestClient.csproj
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Shared\Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
Loading…
Reference in a new issue