Push current additions to Worms 2 server with test proxy.

This commit is contained in:
Ray Koopa 2020-07-08 23:48:28 +02:00
parent ebdf7f5168
commit 17b0edf67f
10 changed files with 737 additions and 145 deletions

View File

@ -0,0 +1,113 @@
namespace Syroot.Worms.Worms2.GameServer
{
/// <summary>
/// Represents the flag sent with in a <see cref="SessionInfo"/>.
/// </summary>
/// <remarks>Names are in ISO 3166 Alpha-2 notation.</remarks>
public enum Nation : byte
{
None,
/// <summary>United Kingdom</summary>
UK,
/// <summary>Argentinia</summary>
AR,
/// <summary>Australia</summary>
AU,
/// <summary>Austria</summary>
AT,
/// <summary>Beglium</summary>
BE,
/// <summary>Brazil</summary>
BR,
/// <summary>Canada</summary>
CA,
/// <summary>Croatia</summary>
HR,
/// <summary>Bosnia and Herzegovina (old flag)</summary>
BA,
/// <summary>Cyprus</summary>
CY,
/// <summary>Czech Republic</summary>
CZ,
/// <summary>Denmark</summary>
DK,
/// <summary>Finland</summary>
FI,
/// <summary>France</summary>
FR,
/// <summary>Georgia</summary>
GE,
/// <summary>Germany</summary>
DE,
/// <summary>Greece</summary>
GR,
/// <summary>Hong Kong SAR</summary>
HK,
/// <summary>Hungary</summary>
HU,
/// <summary>Iceland</summary>
IS,
/// <summary>India</summary>
IN,
/// <summary>Indonesia</summary>
ID,
/// <summary>Iran</summary>
IR,
/// <summary>Iraq</summary>
IQ,
/// <summary>Ireland</summary>
IE,
/// <summary>Israel</summary>
IL,
/// <summary>Italy</summary>
IT,
/// <summary>Japan</summary>
JP,
/// <summary>Liechtenstein</summary>
LI,
/// <summary>Luxembourg</summary>
LU,
/// <summary>Malaysia</summary>
MY,
/// <summary>Malta</summary>
MT,
/// <summary>Mexico</summary>
MX,
/// <summary>Morocco</summary>
MA,
/// <summary>Netherlands</summary>
NL,
/// <summary>New Zealand</summary>
NZ,
/// <summary>Norway</summary>
NO,
/// <summary>Poland</summary>
PL,
/// <summary>Portugal</summary>
PT,
/// <summary>Puerto Rico</summary>
PR,
/// <summary>Romania</summary>
RO,
/// <summary>Russian Federation</summary>
RU,
/// <summary>Singapore</summary>
SG,
/// <summary>South Africa</summary>
ZA,
/// <summary>Spain</summary>
ES,
/// <summary>Sweden</summary>
SE,
/// <summary>Switzerland</summary>
CH,
/// <summary>Turkey</summary>
TR,
/// <summary>United States</summary>
US,
/// <summary>Custom skull flag</summary>
Skull,
/// <summary>Custom Team17 flag</summary>
Team17
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Syroot.BinaryData;
using Syroot.Worms.IO;
@ -8,88 +9,187 @@ namespace Syroot.Worms.Worms2.GameServer
{
internal class Packet
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
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>();
internal Packet() { }
internal Packet(PacketCode code,
int? value0 = null, int? value1 = null, int? value2 = null, int? value3 = null, int? value4 = null,
int? value10 = null, byte[]? data = null, int? error = null,
string? name = null, SessionInfo? session = null)
{
Code = code;
Value0 = value0;
Value1 = value1;
Value2 = value2;
Value3 = value3;
Value4 = value4;
Value10 = value10;
Data = data;
Error = error;
Name = name;
Session = session;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
internal PacketCode Code { get; set; }
internal int? Value0 { get; set; }
internal int? Value1 { get; set; }
internal int? Value2 { get; set; }
internal int? Value3 { get; set; }
internal int? Value4 { get; set; }
internal int? Value10 { get; set; }
internal byte[]? Data { get; set; }
internal int? Error { get; set; }
internal string? Name { get; set; }
internal SessionInfo? Session { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"{Code:D} {Code}");
if (Value0.HasValue) sb.AppendLine($"{nameof(Value0)} = {Value0:X8}");
if (Value1.HasValue) sb.AppendLine($"{nameof(Value1)} = {Value1:X8}");
if (Value2.HasValue) sb.AppendLine($"{nameof(Value2)} = {Value2:X8}");
if (Value3.HasValue) sb.AppendLine($"{nameof(Value3)} = {Value3:X8}");
if (Value4.HasValue) sb.AppendLine($"{nameof(Value4)} = {Value4:X8}");
if (Value10.HasValue) sb.AppendLine($"{nameof(Value10)} = {Value10:X8}");
if (Data != null)
{
sb.Append($"{nameof(Data)}[{Data.Length}] = ");
foreach (byte b in Data)
sb.Append($"{b:X2} ");
sb.AppendLine();
}
if (Error.HasValue) sb.AppendLine($"{nameof(Error)} = {Error:X8}");
if (Name != null) sb.AppendLine($"{nameof(Name)} = {Name}");
if (Session.HasValue) sb.AppendLine($"{nameof(Session)} = {Session}");
return sb.ToString();
}
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal void Receive(Stream stream)
{
int dataLength = 0;
Code = (PacketCode)stream.ReadInt32();
Flags flags = (Flags)stream.ReadInt32();
if (flags.HasFlag(Flags.HasValue0)) Value0 = stream.ReadInt32();
if (flags.HasFlag(Flags.HasValue1)) Value1 = stream.ReadInt32();
if (flags.HasFlag(Flags.HasValue2)) Value2 = stream.ReadInt32();
if (flags.HasFlag(Flags.HasValue3)) Value3 = stream.ReadInt32();
if (flags.HasFlag(Flags.HasValue4)) Value4 = stream.ReadInt32();
if (flags.HasFlag(Flags.HasValue10)) Value10 = stream.ReadInt32();
if (flags.HasFlag(Flags.HasDataLength)) dataLength = stream.ReadInt32();
if (flags.HasFlag(Flags.HasData) && dataLength != 0) Data = stream.ReadBytes(dataLength);
if (flags.HasFlag(Flags.HasError)) Error = stream.ReadInt32();
if (flags.HasFlag(Flags.HasUserName)) Name = stream.ReadFixedString(20);
if (flags.HasFlag(Flags.HasUserInfo)) Session = stream.ReadStruct<SessionInfo>();
}
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);
Flags flags = (Flags)await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasValue0)) Value0 = await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasValue1)) Value1 = await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasValue2)) Value2 = await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasValue3)) Value3 = await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasValue4)) Value4 = await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasValue10)) Value10 = await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasDataLength)) dataLength = await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasData) && dataLength != 0) Data = await stream.ReadBytesAsync(dataLength);
if (flags.HasFlag(Flags.HasError)) Error = await stream.ReadInt32Async();
if (flags.HasFlag(Flags.HasUserName)) Name = await stream.ReadFixedStringAsync(20);
if (flags.HasFlag(Flags.HasUserInfo)) Session = stream.ReadStruct<SessionInfo>();
}
internal void Send(Stream stream)
{
stream.WriteInt32((int)Code);
stream.WriteInt32((int)GetFlags());
if (Value0.HasValue) stream.WriteInt32(Value0.Value);
if (Value1.HasValue) stream.WriteInt32(Value1.Value);
if (Value2.HasValue) stream.WriteInt32(Value2.Value);
if (Value3.HasValue) stream.WriteInt32(Value3.Value);
if (Value4.HasValue) stream.WriteInt32(Value4.Value);
if (Value10.HasValue) stream.WriteInt32(Value10.Value);
if (Data != null)
{
stream.WriteInt32(Data.Length);
stream.WriteBytes(Data);
}
if (Error.HasValue) stream.WriteInt32(Error.Value);
if (Name != null) stream.WriteFixedString(Name, 20);
if (Session.HasValue) stream.WriteStruct(Session.Value);
}
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);
await stream.WriteInt32Async((int)GetFlags());
if (Value0.HasValue) await stream.WriteInt32Async(Value0.Value);
if (Value1.HasValue) await stream.WriteInt32Async(Value1.Value);
if (Value2.HasValue) await stream.WriteInt32Async(Value2.Value);
if (Value3.HasValue) await stream.WriteInt32Async(Value3.Value);
if (Value4.HasValue) await stream.WriteInt32Async(Value4.Value);
if (Value10.HasValue) await stream.WriteInt32Async(Value10.Value);
if (Data != null)
{
await stream.WriteInt32Async(Data.Length);
await stream.WriteBytesAsync(Data);
}
if (Error.HasValue) await stream.WriteInt32Async(Error.Value);
if (Name != null) await stream.WriteFixedStringAsync(Name, 20);
if (Session.HasValue) stream.WriteStruct(Session.Value);
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private Flags GetFlags()
{
Flags flags = Flags.None;
if (Value0.HasValue) flags |= Flags.HasValue0;
if (Value1.HasValue) flags |= Flags.HasValue1;
if (Value2.HasValue) flags |= Flags.HasValue2;
if (Value3.HasValue) flags |= Flags.HasValue3;
if (Value4.HasValue) flags |= Flags.HasValue4;
if (Value10.HasValue) flags |= Flags.HasValue10;
if (Data != null)
{
flags |= Flags.HasDataLength;
flags |= Flags.HasData;
}
if (Error.HasValue) flags |= Flags.HasError;
if (Name != null) flags |= Flags.HasUserName;
if (Session.HasValue) flags |= Flags.HasUserInfo;
return flags;
}
// ---- CLASSES, STRUCTS & ENUMS -------------------------------------------------------------------------------
[Flags]
private enum Flags : int
{
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,
HasError = 1 << 7,
HasUserName = 1 << 8,
HasUserInfo = 1 << 9
}
}
/// <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
}
}

View File

@ -0,0 +1,30 @@
namespace Syroot.Worms.Worms2.GameServer
{
/// <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
{
ListRooms = 200,
ListItem = 350,
ListEnd = 351,
ListUsers = 400,
ListGames = 500,
Login = 600,
LoginReply = 601,
CreateRoom = 700,
CreateRoomReply = 701,
Join = 800,
JoinReply = 801,
LeaveRoom = 900,
LeaveRoomReply = 901,
LeaveRoom2 = 1100,
LeaveRoomReply2 = 1101,
CreateGame = 1200,
CreateGameReply = 1201,
ChatRoom = 1300,
ChatRoomReply = 1301,
JoinGame = 1326,
}
}

View File

@ -0,0 +1,87 @@
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Syroot.Worms.Worms2.GameServer
{
internal class PacketConnection
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly Stream _stream;
private readonly SemaphoreSlim _recvSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1, 1);
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
internal PacketConnection(TcpClient tcpClient)
{
_stream = tcpClient.GetStream();
RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
internal IPEndPoint RemoteEndPoint { get; }
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal Packet Receive()
{
_recvSemaphore.Wait();
try
{
Packet packet = new Packet();
packet.Receive(_stream);
return packet;
}
finally
{
_recvSemaphore.Release();
}
}
internal async Task<Packet> ReceiveAsync()
{
await _recvSemaphore.WaitAsync();
try
{
Packet packet = new Packet();
await packet.ReceiveAsync(_stream);
return packet;
}
finally
{
_recvSemaphore.Release();
}
}
internal void Send(Packet packet)
{
_sendSemaphore.Wait();
try
{
packet.Send(_stream);
}
finally
{
_sendSemaphore.Release();
}
}
internal async Task SendAsync(Packet packet)
{
await _sendSemaphore.WaitAsync();
try
{
await packet.SendAsync(_stream);
}
finally
{
_sendSemaphore.Release();
}
}
}
}

View File

@ -1,88 +1,18 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Net;
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)
private static void Main()
{
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, 17000);
TcpListener listener = new TcpListener(serverEndPoint);
listener.Start();
Console.WriteLine($"Listening under {serverEndPoint}...");
Proxy.Start();
return;
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;
}
Server server = new Server(new IPEndPoint(IPAddress.Any, 17000));
server.Run();
}
}
}

View File

@ -0,0 +1,49 @@
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Syroot.ColoredConsole;
namespace Syroot.Worms.Worms2.GameServer
{
internal class Proxy
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal static void Start()
{
// Start listening for clients to intercept.
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 17000);
TcpListener listener = new TcpListener(localEndPoint);
listener.Start();
ColorConsole.WriteLine($"Listening under {localEndPoint}...");
TcpClient? client;
while ((client = listener.AcceptTcpClient()) != null)
{
// Connect to server.
TcpClient server = new TcpClient();
server.Connect("uk1.servers.worms2.com", 17171);
PacketConnection clientConnection = new PacketConnection(client);
PacketConnection serverConnection = new PacketConnection(server);
Task.Run(() => Forward(clientConnection, serverConnection, true));
Task.Run(() => Forward(serverConnection, clientConnection, false));
}
}
private static void Forward(PacketConnection from, PacketConnection to, bool fromClient)
{
while (true)
{
Packet packet = from.Receive();
if (fromClient)
ColorConsole.WriteLine(Color.Cyan, $"{from.RemoteEndPoint} >> {to.RemoteEndPoint} | {packet}");
else
ColorConsole.WriteLine(Color.Magenta, $"{to.RemoteEndPoint} << {from.RemoteEndPoint} | {packet}");
to.Send(packet);
}
}
}
}

View File

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Syroot.ColoredConsole;
namespace Syroot.Worms.Worms2.GameServer
{
internal class Server
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private int _idCounter;
private readonly TcpListener _listener;
private readonly Dictionary<PacketCode, Action<PacketConnection, Packet>> _packetHandlers;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
internal Server(IPEndPoint localEndPoint)
{
_listener = new TcpListener(localEndPoint);
_packetHandlers = new Dictionary<PacketCode, Action<PacketConnection, Packet>>
{
[PacketCode.ListRooms] = OnListRooms,
[PacketCode.ListUsers] = OnListUsers,
[PacketCode.ListGames] = OnListGames,
[PacketCode.Login] = OnLogin,
[PacketCode.CreateRoom] = OnCreateRoom,
[PacketCode.Join] = OnJoinRoom,
[PacketCode.LeaveRoom] = OnLeaveRoom,
[PacketCode.CreateGame] = OnCreateGame,
[PacketCode.ChatRoom] = OnChatRoom,
};
}
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal void Run()
{
_listener.Start();
Console.WriteLine($"Listening under {_listener.LocalEndpoint}...");
TcpClient? client;
while ((client = _listener.AcceptTcpClient()) != null)
Task.Run(() => HandleClient(client));
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void SendPacket(PacketConnection connection, Packet packet)
{
LogPacket(connection, packet, false);
connection.Send(packet);
}
private static void LogPacket(PacketConnection connection, Packet packet, bool fromClient)
{
if (fromClient)
ColorConsole.WriteLine(Color.Cyan, $"{connection.RemoteEndPoint} >> {packet}");
else
ColorConsole.WriteLine(Color.Magenta, $"{connection.RemoteEndPoint} << {packet}");
}
private void HandleClient(TcpClient client)
{
PacketConnection connection = new PacketConnection(client);
try
{
while (true)
{
// Receive and log query.
Packet packet = connection.Receive();
LogPacket(connection, packet, true);
// Handle query.
if (_packetHandlers.TryGetValue(packet.Code, out Action<PacketConnection, Packet>? handler))
handler(connection, packet);
else
ColorConsole.WriteLine(Color.Red, $"Unhandled packet {packet.Code}.");
}
}
catch (EndOfStreamException) { }
}
// ---- Packet Handlers ----
private void OnListRooms(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.ListItem,
value1: 1234,
data: Encoding.ASCII.GetBytes("12.34.45.56"),
name: "Room Name",
session: new SessionInfo
{
Unknown0 = 0x17171717,
Unknown4 = 0x02010101,
Nation = Nation.CY,
GameVersion = 49,
GameRelease = 49,
Action0 = 0x01,
Action1 = 0x01,
Action2 = 0x01,
Action3 = 0x00
}));
SendPacket(connection, new Packet(PacketCode.ListEnd));
}
private void OnListUsers(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.ListEnd));
}
private void OnListGames(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.ListItem,
value1: 12, // user ID,
data: Encoding.ASCII.GetBytes("12.34.45.67"),
name: "SomeUser",
session: new SessionInfo
{
GameVersion = 49,
Nation = Nation.IT
}));
SendPacket(connection, new Packet(PacketCode.ListEnd));
}
private void OnLogin(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.LoginReply,
value1: Interlocked.Increment(ref _idCounter), // user ID
error: 0));
}
private void OnCreateRoom(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.CreateRoomReply,
value1: Interlocked.Increment(ref _idCounter), // room ID
error: 0));
}
private void OnJoinRoom(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.JoinReply,
error: 0));
}
private void OnLeaveRoom(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.LeaveRoomReply,
error: 0));
}
private void OnCreateGame(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.CreateGameReply,
value1: 123456,
error: 0));
}
private void OnChatRoom(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.ChatRoomReply,
error: 0));
}
}
}

View File

@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
namespace Syroot.Worms.Worms2.GameServer
{
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 50)]
internal struct SessionInfo
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
internal uint Unknown0;
internal uint Unknown4;
internal Nation Nation;
internal byte GameVersion;
internal byte GameRelease;
internal byte Action0;
internal byte Action1;
internal byte Action2;
internal byte Action3;
internal byte Unused15;
internal ulong Unused16;
internal ulong Unused24;
internal ulong Unused32;
internal ulong Unused40;
internal ushort Unused48;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public override string ToString() => $"{Unknown0:X8}-{Unknown4:X8} {Nation} {GameVersion}/{GameRelease} "
+ $"{Action0:X2}-{Action1:X2}-{Action2:X2}-{Action3:X2} "
+ $"{Unused15:X2}{Unused16:X8}{Unused24:X8}{Unused24:X8}{Unused40:X8}{Unused48:X2}";
}
}

View File

@ -1,11 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Syroot.ColoredConsole" Version="1.0.0-beta.1" />
<ProjectReference Include="..\..\library\Syroot.Worms\Syroot.Worms.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
-->
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>