mirror of
https://gitlab.com/Syroot/Worms.git
synced 2025-01-27 14:18:00 +03:00
Add quick Worms 2 server PoC. Makes BinaryStream FixedString extensions globally available.
This commit is contained in:
parent
420810fad2
commit
a308ae49b1
@ -47,7 +47,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = " Solution Items", " Solut
|
||||
test.xml = test.xml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.SchemeEditor", "tool\Syroot.Worms.SchemeEditor\Syroot.Worms.SchemeEditor.csproj", "{06FC5993-E3F0-4826-AE88-5639446A14B6}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Worms2.GameServer", "tool\Syroot.Worms.Worms2.GameServer\Syroot.Worms.Worms2.GameServer.csproj", "{13ABF717-5809-441D-A5D8-66E1EE75A390}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -119,10 +119,10 @@ Global
|
||||
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{06FC5993-E3F0-4826-AE88-5639446A14B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{06FC5993-E3F0-4826-AE88-5639446A14B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{06FC5993-E3F0-4826-AE88-5639446A14B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{06FC5993-E3F0-4826-AE88-5639446A14B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -144,7 +144,7 @@ Global
|
||||
{392E4CA2-61D9-4BE1-B065-8801A9F102B8} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
|
||||
{212F8090-9775-4098-BD44-9ABC01FBE553} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
|
||||
{1FAB6B9F-2585-46DC-81C0-579DC808C389} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
|
||||
{06FC5993-E3F0-4826-AE88-5639446A14B6} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
|
||||
{13ABF717-5809-441D-A5D8-66E1EE75A390} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {1CD4EDE2-A5FB-4A58-A850-3506AB7E7B69}
|
||||
|
@ -35,21 +35,6 @@ namespace Syroot.Worms.IO
|
||||
return values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
|
||||
/// </summary>
|
||||
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
|
||||
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
|
||||
/// <returns>The read string.</returns>
|
||||
public static string ReadFixedString(this BinaryStream self, int length)
|
||||
{
|
||||
// Ensure to not try to decode any bytes after the 0 termination.
|
||||
byte[] bytes = self.ReadBytes(length);
|
||||
for (length = 0; length < bytes.Length && bytes[length] != 0; length++) ;
|
||||
return self.Encoding.GetString(bytes, 0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current position of the stream at which a 4-byte placeholder has been written which can be
|
||||
/// filled later with <see cref="SatisfyOffset"/>.
|
||||
/// </summary>
|
||||
@ -95,20 +80,6 @@ namespace Syroot.Worms.IO
|
||||
foreach (Color color in colors)
|
||||
WriteColor(self, color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it.
|
||||
/// </summary>
|
||||
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
|
||||
/// <param name="value">The string to write.</param>
|
||||
/// <param name="length">The number of bytes the fixed-size block takes.</param>
|
||||
public static void WriteFixedString(this BinaryStream self, string value, int length)
|
||||
{
|
||||
byte[] bytes = new byte[length];
|
||||
if (value != null)
|
||||
self.Encoding.GetBytes(value, 0, value.Length, bytes, 0);
|
||||
self.WriteBytes(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
30
src/library/Syroot.Worms/IO/EncodingExtensions.cs
Normal file
30
src/library/Syroot.Worms/IO/EncodingExtensions.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Syroot.Worms.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents extension methods for <see cref="Encoding"/> instances.
|
||||
/// </summary>
|
||||
public static class EncodingExtensions
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
// ---- Backports ----
|
||||
|
||||
public unsafe static int GetBytes(this Encoding encoding, ReadOnlySpan<char> chars, Span<byte> bytes)
|
||||
{
|
||||
fixed (byte* pBytes = bytes)
|
||||
fixed (char* pChars = chars)
|
||||
return encoding.GetBytes(pChars, chars.Length, pBytes, bytes.Length);
|
||||
}
|
||||
|
||||
public unsafe static string GetString(this Encoding encoding, ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
fixed (byte* pBytes = bytes)
|
||||
return encoding.GetString(pBytes, bytes.Length);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Syroot.Worms.IO
|
||||
{
|
||||
@ -12,6 +14,37 @@ namespace Syroot.Worms.IO
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/> instance to read with.</param>
|
||||
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
|
||||
/// <returns>The read string.</returns>
|
||||
public static unsafe string ReadFixedString(this Stream stream, int length)
|
||||
{
|
||||
// Ensure to not try to decode any bytes after the 0 termination.
|
||||
Span<byte> bytes = stackalloc byte[length];
|
||||
stream.Read(bytes);
|
||||
for (length = 0; length < bytes.Length && bytes[length] != 0; length++) ;
|
||||
return Encoding.ASCII.GetString(bytes.Slice(0, length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/> instance to read with.</param>
|
||||
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
|
||||
/// <returns>The read string.</returns>
|
||||
public static async Task<string> ReadFixedStringAsync(this Stream stream, int length)
|
||||
{
|
||||
// Ensure to not try to decode any bytes after the 0 termination.
|
||||
byte[] bytes = new byte[length];
|
||||
await stream.ReadAsync(bytes, 0, length);
|
||||
for (length = 0; length < bytes.Length && bytes[length] != 0; length++) ;
|
||||
return Encoding.ASCII.GetString(bytes, 0, length);
|
||||
}
|
||||
|
||||
/// <s
|
||||
/// <summary>
|
||||
/// Reads the unmanaged representation of a struct of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
@ -34,6 +67,34 @@ namespace Syroot.Worms.IO
|
||||
public static void ReadStructs<T>(this Stream stream, Span<T> span) where T : unmanaged
|
||||
=> stream.Read(MemoryMarshal.Cast<T, byte>(span));
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
|
||||
/// <param name="value">The string to write.</param>
|
||||
/// <param name="length">The number of bytes the fixed-size block takes.</param>
|
||||
public static void WriteFixedString(this Stream stream, string value, int length)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[length];
|
||||
if (value != null)
|
||||
Encoding.ASCII.GetBytes(value.AsSpan(), bytes);
|
||||
stream.Write(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
|
||||
/// <param name="value">The string to write.</param>
|
||||
/// <param name="length">The number of bytes the fixed-size block takes.</param>
|
||||
public static async Task WriteFixedStringAsync(this Stream stream, string value, int length)
|
||||
{
|
||||
byte[] bytes = new byte[length];
|
||||
if (value != null)
|
||||
Encoding.ASCII.GetBytes(value.AsSpan(), bytes);
|
||||
await stream.WriteAsync(bytes, 0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the unmanaged representation of a struct of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
|
95
src/tool/Syroot.Worms.Worms2.GameServer/Packet.cs
Normal file
95
src/tool/Syroot.Worms.Worms2.GameServer/Packet.cs
Normal file
@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Syroot.BinaryData;
|
||||
using Syroot.Worms.IO;
|
||||
|
||||
namespace Syroot.Worms.Worms2.GameServer
|
||||
{
|
||||
internal class Packet
|
||||
{
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
internal int Value10;
|
||||
internal PacketCode Code;
|
||||
internal PacketFlags Flags;
|
||||
internal int Value4;
|
||||
internal int Value0;
|
||||
internal int Value3;
|
||||
internal int Value1;
|
||||
internal int Value2;
|
||||
internal int Value7;
|
||||
internal string String20 = String.Empty;
|
||||
internal string String50 = String.Empty;
|
||||
internal byte[] Data = Array.Empty<byte>();
|
||||
|
||||
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||
|
||||
internal async Task ReceiveAsync(Stream stream)
|
||||
{
|
||||
int dataLength = 0;
|
||||
Code = (PacketCode)await stream.ReadInt32Async();
|
||||
Flags = (PacketFlags)await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasValue0)) Value0 = await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasValue1)) Value1 = await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasValue2)) Value2 = await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasValue3)) Value3 = await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasValue4)) Value4 = await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasValue10)) Value10 = await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasDataLength)) dataLength = await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasData) && dataLength != 0) Data = await stream.ReadBytesAsync(dataLength);
|
||||
if (Flags.HasFlag(PacketFlags.HasValue7)) Value7 = await stream.ReadInt32Async();
|
||||
if (Flags.HasFlag(PacketFlags.HasString20)) String20 = await stream.ReadFixedStringAsync(20);
|
||||
if (Flags.HasFlag(PacketFlags.HasString50)) String50 = await stream.ReadFixedStringAsync(50);
|
||||
}
|
||||
|
||||
internal async Task SendAsync(Stream stream)
|
||||
{
|
||||
await stream.WriteInt32Async((int)Code);
|
||||
await stream.WriteInt32Async((int)Flags);
|
||||
if (Flags.HasFlag(PacketFlags.HasValue0)) await stream.WriteInt32Async(Value0);
|
||||
if (Flags.HasFlag(PacketFlags.HasValue1)) await stream.WriteInt32Async(Value1);
|
||||
if (Flags.HasFlag(PacketFlags.HasValue2)) await stream.WriteInt32Async(Value2);
|
||||
if (Flags.HasFlag(PacketFlags.HasValue3)) await stream.WriteInt32Async(Value3);
|
||||
if (Flags.HasFlag(PacketFlags.HasValue4)) await stream.WriteInt32Async(Value4);
|
||||
if (Flags.HasFlag(PacketFlags.HasValue10)) await stream.WriteInt32Async(Value10);
|
||||
if (Flags.HasFlag(PacketFlags.HasDataLength)) await stream.WriteInt32Async(Data.Length);
|
||||
if (Flags.HasFlag(PacketFlags.HasData) && Data.Length != 0) await stream.WriteBytesAsync(Data);
|
||||
if (Flags.HasFlag(PacketFlags.HasValue7)) await stream.WriteInt32Async(Value7);
|
||||
if (Flags.HasFlag(PacketFlags.HasString20)) await stream.WriteFixedStringAsync(String20, 20);
|
||||
if (Flags.HasFlag(PacketFlags.HasString50)) await stream.WriteFixedStringAsync(String50, 50);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the description of the packet contents, as seen from client-side (thus a "reply" comes from the
|
||||
/// server).
|
||||
/// </summary>
|
||||
internal enum PacketCode : int
|
||||
{
|
||||
Login = 600,
|
||||
LoginReply = 601,
|
||||
CreateRoom = 700,
|
||||
CreateRoomReply = 701
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents which <see cref="Packet"/> fields are available, as specified at the start of each.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
internal enum PacketFlags
|
||||
{
|
||||
None,
|
||||
HasValue0 = 1 << 0,
|
||||
HasValue1 = 1 << 1,
|
||||
HasValue2 = 1 << 2,
|
||||
HasValue3 = 1 << 3,
|
||||
HasValue4 = 1 << 4,
|
||||
HasValue10 = 1 << 10,
|
||||
HasDataLength = 1 << 5,
|
||||
HasData = 1 << 6,
|
||||
HasValue7 = 1 << 7,
|
||||
HasString20 = 1 << 8,
|
||||
HasString50 = 1 << 9
|
||||
}
|
||||
}
|
88
src/tool/Syroot.Worms.Worms2.GameServer/Program.cs
Normal file
88
src/tool/Syroot.Worms.Worms2.GameServer/Program.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Syroot.Worms.Worms2.GameServer
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
private static readonly object _logLock = new object();
|
||||
|
||||
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, 17000);
|
||||
TcpListener listener = new TcpListener(serverEndPoint);
|
||||
listener.Start();
|
||||
Console.WriteLine($"Listening under {serverEndPoint}...");
|
||||
|
||||
TcpClient? client;
|
||||
while ((client = await listener.AcceptTcpClientAsync()) != null)
|
||||
{
|
||||
_ = HandleClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleClient(TcpClient client)
|
||||
{
|
||||
IPEndPoint clientEndPoint = (IPEndPoint)client.Client.RemoteEndPoint;
|
||||
Stream stream = client.GetStream();
|
||||
|
||||
Packet query = new Packet();
|
||||
while (true)
|
||||
{
|
||||
// Receive query.
|
||||
await query.ReceiveAsync(stream);
|
||||
LogPacket(query, true, clientEndPoint);
|
||||
|
||||
// Send reply.
|
||||
switch (query.Code)
|
||||
{
|
||||
case PacketCode.Login:
|
||||
query.Code = PacketCode.LoginReply;
|
||||
break;
|
||||
case PacketCode.CreateRoom:
|
||||
break;
|
||||
}
|
||||
LogPacket(query, false, clientEndPoint);
|
||||
await query.SendAsync(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private static void LogPacket(Packet packet, bool received, IPEndPoint endPoint)
|
||||
{
|
||||
static string arrayToString(byte[] array)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in array)
|
||||
sb.Append($"{b:X2} ");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
lock (_logLock) // if I'd only get laid for lines like this
|
||||
{
|
||||
ConsoleColor prevColor = Console.ForegroundColor;
|
||||
Console.ForegroundColor = received ? ConsoleColor.Cyan : ConsoleColor.Magenta;
|
||||
Console.WriteLine($"{endPoint} {(received ? ">>" : "<<")} {packet.Code}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasValue0)) Console.WriteLine($" Value0 = {packet.Value0:X8}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasValue1)) Console.WriteLine($" Value1 = {packet.Value1:X8}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasValue2)) Console.WriteLine($" Value2 = {packet.Value2:X8}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasValue3)) Console.WriteLine($" Value3 = {packet.Value3:X8}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasValue4)) Console.WriteLine($" Value4 = {packet.Value4:X8}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasValue10)) Console.WriteLine($" Value10 = {packet.Value10:X8}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasDataLength)) Console.WriteLine($" DataLength = {packet.Data.Length}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasData)) Console.WriteLine($" Data = {arrayToString(packet.Data)}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasValue7)) Console.WriteLine($" Value7 = {packet.Value7:X8}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasString20)) Console.WriteLine($" String20 = {packet.String20}");
|
||||
if (packet.Flags.HasFlag(PacketFlags.HasString50)) Console.WriteLine($" String50 = {packet.String50}");
|
||||
Console.ForegroundColor = prevColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\library\Syroot.Worms\Syroot.Worms.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
Loading…
x
Reference in New Issue
Block a user