diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/Game.cs b/src/tool/Syroot.Worms.Worms2.GameServer/Game.cs
new file mode 100644
index 0000000..7c221e3
--- /dev/null
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/Game.cs
@@ -0,0 +1,57 @@
+using System.Net;
+
+namespace Syroot.Worms.Worms2.GameServer
+{
+ ///
+ /// Represents a game hosted in a room which users can join.
+ ///
+ internal class Game
+ {
+ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
+
+ ///
+ /// Initializes a new instance of the class with the given identification.
+ ///
+ /// The unique numerical identifier of the game.
+ /// The name of the room being the name of the creator.
+ /// The flag displayed with the game.
+ /// The ID of the room the game is hosted in.
+ /// The IP address of the host of the game.
+ /// The access modifier of the game.
+ internal Game(int id, string name, Nation nation, int roomID, IPAddress ipAddress, SessionAccess access)
+ {
+ ID = id;
+ Name = name;
+ RoomID = roomID;
+ IPAddress = ipAddress;
+ Session = new SessionInfo(nation, SessionType.Game, access);
+ }
+
+ // ---- PROPERTIES ---------------------------------------------------------------------------------------------
+
+ ///
+ /// Gets the unique numerical identifier of the game.
+ ///
+ internal int ID { get; }
+
+ ///
+ /// Gets the name of the room being the name of the creator.
+ ///
+ internal string Name { get; set; }
+
+ ///
+ /// Gets the ID of the room the game is hosted in.
+ ///
+ internal int RoomID { get; }
+
+ ///
+ /// Gets the IP address of the host of the game.
+ ///
+ internal IPAddress IPAddress { get; set; }
+
+ ///
+ /// Gets the describing the game.
+ ///
+ internal SessionInfo Session { get; }
+ }
+}
diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/Packet.cs b/src/tool/Syroot.Worms.Worms2.GameServer/Packet.cs
index 4b601ca..8bc06f6 100644
--- a/src/tool/Syroot.Worms.Worms2.GameServer/Packet.cs
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/Packet.cs
@@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Text;
-using System.Threading.Tasks;
using Syroot.BinaryData;
using Syroot.Worms.IO;
@@ -15,7 +14,7 @@ namespace Syroot.Worms.Worms2.GameServer
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,
+ int? value10 = null, string? data = null, int? error = null,
string? name = null, SessionInfo? session = null)
{
Code = code;
@@ -40,7 +39,7 @@ namespace Syroot.Worms.Worms2.GameServer
internal int? Value3 { get; set; }
internal int? Value4 { get; set; }
internal int? Value10 { get; set; }
- internal byte[]? Data { get; set; }
+ internal string? Data { get; set; }
internal int? Error { get; set; }
internal string? Name { get; set; }
internal SessionInfo? Session { get; set; }
@@ -53,24 +52,18 @@ namespace Syroot.Worms.Worms2.GameServer
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}");
+ if (Value0.HasValue) sb.AppendLine($" {nameof(Value0),7}: {Value0:X8}");
+ if (Value1.HasValue) sb.AppendLine($" {nameof(Value1),7}: {Value1:X8}");
+ if (Value2.HasValue) sb.AppendLine($" {nameof(Value2),7}: {Value2:X8}");
+ if (Value3.HasValue) sb.AppendLine($" {nameof(Value3),7}: {Value3:X8}");
+ if (Value4.HasValue) sb.AppendLine($" {nameof(Value4),7}: {Value4:X8}");
+ if (Value10.HasValue) sb.AppendLine($" {nameof(Value10),7}: {Value10:X8}");
+ if (Data != null) sb.AppendLine($" {nameof(Data),7}: {Data}");
+ if (Error.HasValue) sb.AppendLine($" {nameof(Error),7}: {Error:X8}");
+ if (Name != null) sb.AppendLine($" {nameof(Name),7}: {Name}");
+ if (Session.HasValue) sb.AppendLine($" {nameof(Session),7}: {Session}");
- return sb.ToString();
+ return sb.ToString().TrimEnd();
}
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
@@ -87,30 +80,12 @@ namespace Syroot.Worms.Worms2.GameServer
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.HasData) && dataLength != 0) Data = stream.ReadFixedString(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();
}
- internal async Task ReceiveAsync(Stream stream)
- {
- int dataLength = 0;
- Code = (PacketCode)await stream.ReadInt32Async();
- 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();
- }
-
internal void Send(Stream stream)
{
stream.WriteInt32((int)Code);
@@ -124,33 +99,13 @@ namespace Syroot.Worms.Worms2.GameServer
if (Data != null)
{
stream.WriteInt32(Data.Length);
- stream.WriteBytes(Data);
+ stream.WriteFixedString(Data, Data.Length);
}
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)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()
diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/PacketConnection.cs b/src/tool/Syroot.Worms.Worms2.GameServer/PacketConnection.cs
index 08ad9de..1c2dd5a 100644
--- a/src/tool/Syroot.Worms.Worms2.GameServer/PacketConnection.cs
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/PacketConnection.cs
@@ -1,87 +1,64 @@
using System.IO;
using System.Net;
using System.Net.Sockets;
-using System.Threading;
-using System.Threading.Tasks;
namespace Syroot.Worms.Worms2.GameServer
{
+ ///
+ /// Represents a duplex connection to a client, allowing to receive and send instances.
+ ///
internal class PacketConnection
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly Stream _stream;
- private readonly SemaphoreSlim _recvSemaphore = new SemaphoreSlim(1, 1);
- private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1, 1);
+ private readonly object _recvLock = new object();
+ private readonly object _sendLock = new object();
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
- internal PacketConnection(TcpClient tcpClient)
+ ///
+ /// Initializes a new instance of the class communicating with the given
+ /// .
+ ///
+ /// The to communicate with.
+ internal PacketConnection(TcpClient client)
{
- _stream = tcpClient.GetStream();
- RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint;
+ _stream = client.GetStream();
+ RemoteEndPoint = (IPEndPoint)client.Client.RemoteEndPoint;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
+ ///
+ /// Gets the client's .
+ ///
internal IPEndPoint RemoteEndPoint { get; }
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
+ ///
+ /// Blocks until a was received, and returns it.
+ ///
+ /// The received .
internal Packet Receive()
{
- _recvSemaphore.Wait();
- try
+ lock (_recvLock)
{
Packet packet = new Packet();
packet.Receive(_stream);
return packet;
}
- finally
- {
- _recvSemaphore.Release();
- }
- }
-
- internal async Task ReceiveAsync()
- {
- await _recvSemaphore.WaitAsync();
- try
- {
- Packet packet = new Packet();
- await packet.ReceiveAsync(_stream);
- return packet;
- }
- finally
- {
- _recvSemaphore.Release();
- }
}
+ ///
+ /// Blocks until the given was sent.
+ ///
+ /// The to send.
internal void Send(Packet packet)
{
- _sendSemaphore.Wait();
- try
- {
+ lock (_sendLock)
packet.Send(_stream);
- }
- finally
- {
- _sendSemaphore.Release();
- }
- }
-
- internal async Task SendAsync(Packet packet)
- {
- await _sendSemaphore.WaitAsync();
- try
- {
- await packet.SendAsync(_stream);
- }
- finally
- {
- _sendSemaphore.Release();
- }
}
}
}
diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/Program.cs b/src/tool/Syroot.Worms.Worms2.GameServer/Program.cs
index a589fa1..0e067f0 100644
--- a/src/tool/Syroot.Worms.Worms2.GameServer/Program.cs
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/Program.cs
@@ -2,17 +2,18 @@
namespace Syroot.Worms.Worms2.GameServer
{
+ ///
+ /// Represents the main class of the application containing the program entry point.
+ ///
internal class Program
{
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void Main()
{
- Proxy.Start();
- return;
-
- Server server = new Server(new IPEndPoint(IPAddress.Any, 17000));
- server.Run();
+ //Proxy.Run(); return;
+ Server server = new Server();
+ server.Run(new IPEndPoint(IPAddress.Any, 17000));
}
}
}
diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/Proxy.cs b/src/tool/Syroot.Worms.Worms2.GameServer/Proxy.cs
index 4d500d0..5804e0a 100644
--- a/src/tool/Syroot.Worms.Worms2.GameServer/Proxy.cs
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/Proxy.cs
@@ -6,17 +6,20 @@ using Syroot.ColoredConsole;
namespace Syroot.Worms.Worms2.GameServer
{
- internal class Proxy
+ ///
+ /// Represents a proxy dumping Worms 2 network traffic to console for debug purposes.
+ ///
+ internal static class Proxy
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
- internal static void Start()
+ internal static void Run()
{
// 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}...");
+ ColorConsole.WriteLine($"Proxy listening under {localEndPoint}...");
TcpClient? client;
while ((client = listener.AcceptTcpClient()) != null)
@@ -27,12 +30,15 @@ namespace Syroot.Worms.Worms2.GameServer
PacketConnection clientConnection = new PacketConnection(client);
PacketConnection serverConnection = new PacketConnection(server);
+ ColorConsole.WriteLine(Color.Green, $"{clientConnection.RemoteEndPoint} connected.");
Task.Run(() => Forward(clientConnection, serverConnection, true));
Task.Run(() => Forward(serverConnection, clientConnection, false));
}
}
+ // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
+
private static void Forward(PacketConnection from, PacketConnection to, bool fromClient)
{
while (true)
diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/Room.cs b/src/tool/Syroot.Worms.Worms2.GameServer/Room.cs
new file mode 100644
index 0000000..ec43f60
--- /dev/null
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/Room.cs
@@ -0,0 +1,40 @@
+namespace Syroot.Worms.Worms2.GameServer
+{
+ ///
+ /// Represents a room in which users can meet, chat, and host games.
+ ///
+ internal class Room
+ {
+ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
+
+ ///
+ /// Initializes a new instance of the class with the given identificatoin.
+ ///
+ /// The unique numerical identifier of the room.
+ /// The name of the room as given by the creator.
+ /// The flag displayed with the room.
+ internal Room(int id, string name, Nation nation)
+ {
+ ID = id;
+ Name = name;
+ Session = new SessionInfo(nation, SessionType.Room);
+ }
+
+ // ---- PROPERTIES ---------------------------------------------------------------------------------------------
+
+ ///
+ /// Gets the unique numerical identifier of the room.
+ ///
+ internal int ID { get; }
+
+ ///
+ /// Gets the name of the room as given by the creator.
+ ///
+ internal string Name { get; set; }
+
+ ///
+ /// Gets the describing the room.
+ ///
+ internal SessionInfo Session { get; }
+ }
+}
diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/Server.cs b/src/tool/Syroot.Worms.Worms2.GameServer/Server.cs
index ed766c2..a4cc977 100644
--- a/src/tool/Syroot.Worms.Worms2.GameServer/Server.cs
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/Server.cs
@@ -1,54 +1,61 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
-using System.IO;
+using System.Linq;
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
{
+ ///
+ /// Represents a simplistic game server managing users, rooms, and games.
+ ///
internal class Server
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
- private int _idCounter;
- private readonly TcpListener _listener;
+ private int _lastID;
+ private readonly List _users = new List();
+ private readonly List _rooms = new List();
+ private readonly List _games = new List();
+ private readonly BlockingCollection _jobs = new BlockingCollection();
private readonly Dictionary> _packetHandlers;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
- internal Server(IPEndPoint localEndPoint)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal Server() => _packetHandlers = new Dictionary>
{
- _listener = new TcpListener(localEndPoint);
- _packetHandlers = new Dictionary>
- {
- [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,
- [PacketCode.ConnectGame] = OnConnectGame
- };
- }
+ [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,
+ [PacketCode.ConnectGame] = OnConnectGame
+ };
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
- internal void Run()
+ ///
+ /// Begins listening for new clients connecting to the given and dispatches
+ /// them into their own threads.
+ ///
+ internal void Run(IPEndPoint localEndPoint)
{
- _listener.Start();
- Console.WriteLine($"Listening under {_listener.LocalEndpoint}...");
-
- TcpClient? client;
- while ((client = _listener.AcceptTcpClient()) != null)
- Task.Run(() => HandleClient(client));
+ // Begin handling any queued jobs.
+ Task.Run(() => HandleJobs());
+ // Begin listening for new connections. Currently synchronous and blocking.
+ HandleConnections(localEndPoint);
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
@@ -67,9 +74,40 @@ namespace Syroot.Worms.Worms2.GameServer
ColorConsole.WriteLine(Color.Magenta, $"{connection.RemoteEndPoint} << {packet}");
}
+ private int GetNextID() => Interlocked.Increment(ref _lastID);
+
+ private User? GetUser(PacketConnection connection)
+ {
+ foreach (User user in _users)
+ if (user.Connection == connection)
+ return user;
+ return null;
+ }
+
+ private void HandleJobs()
+ {
+ foreach (Action job in _jobs.GetConsumingEnumerable())
+ job();
+ }
+
+ private void HandleConnections(IPEndPoint localEndPoint)
+ {
+ // Start a new listener for new incoming connections.
+ TcpListener listener = new TcpListener(localEndPoint);
+ listener.Start();
+ Console.WriteLine($"Server listening under {listener.LocalEndpoint}...");
+
+ // Dispatch each connection into its own thread.
+ TcpClient? client;
+ while ((client = listener.AcceptTcpClient()) != null)
+ Task.Run(() => HandleClient(client));
+ }
+
private void HandleClient(TcpClient client)
{
PacketConnection connection = new PacketConnection(client);
+ ColorConsole.WriteLine(Color.Green, $"{connection.RemoteEndPoint} connected.");
+
try
{
while (true)
@@ -78,98 +116,164 @@ namespace Syroot.Worms.Worms2.GameServer
Packet packet = connection.Receive();
LogPacket(connection, packet, true);
- // Handle query.
+ // Queue handling of known queries.
if (_packetHandlers.TryGetValue(packet.Code, out Action? handler))
- handler(connection, packet);
+ _jobs.Add(() => handler(connection, packet));
else
- ColorConsole.WriteLine(Color.Red, $"Unhandled packet {packet.Code}.");
+ ColorConsole.WriteLine(Color.Red, $"{connection.RemoteEndPoint} unhandled {packet.Code}.");
}
}
- catch (EndOfStreamException) { }
+ catch (Exception ex)
+ {
+ ColorConsole.WriteLine(Color.Red, $"{connection.RemoteEndPoint} disconnected. {ex.Message}");
+ _jobs.Add(() => OnDisconnectUser(connection));
+ }
}
- // ---- Packet Handlers ----
+ // ---- 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: "SomeRoom",
- session: new SessionInfo
- {
- Unknown0 = 0x17171717,
- Unknown4 = 0x02010101,
- Nation = Nation.CY,
- GameVersion = 49,
- GameRelease = 49,
- Action0 = 0x01,
- Action1 = 0x01,
- Action2 = 0x01,
- Action3 = 0x00
- }));
-
+ foreach (Room room in _rooms)
+ {
+ SendPacket(connection, new Packet(PacketCode.ListItem,
+ value1: room.ID,
+ data: String.Empty, // do not report creator IP
+ name: room.Name,
+ session: room.Session));
+ }
SendPacket(connection, new Packet(PacketCode.ListEnd));
}
private void OnListUsers(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
- {
- Unknown0 = 0x17171717,
- Unknown4 = 0x02010101,
- GameVersion = 49,
- GameRelease = 49,
- Nation = Nation.IT,
- Action0 = 01,
- Action1 = 01,
- Action2 = 01,
- }));
+ User? fromUser = GetUser(connection);
+ if (fromUser == null)
+ return;
+
+ foreach (User user in _users.Where(x => x.RoomID == fromUser.RoomID)) // notably includes the user itself
+ {
+ SendPacket(connection, new Packet(PacketCode.ListItem,
+ value1: user.ID,
+ name: user.Name,
+ session: user.Session));
+ }
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
- {
- Unknown0 = 0x17171717,
- Unknown4 = 0x02010101,
- GameVersion = 49,
- GameRelease = 49,
- Nation = Nation.IT,
- Action0 = 01,
- Action1 = 02,
- Action2 = 01,
- }));
+ User? fromUser = GetUser(connection);
+ if (fromUser == null)
+ return;
+
+ foreach (Game game in _games.Where(x => x.RoomID == fromUser.RoomID))
+ {
+ SendPacket(connection, new Packet(PacketCode.ListItem,
+ value1: game.ID,
+ data: game.IPAddress.ToString(),
+ name: game.Name,
+ session: game.Session));
+ }
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));
+ // Check if user name is valid and not already taken.
+ if (!String.IsNullOrWhiteSpace(packet.Name)
+ && packet.Session.HasValue
+ && !_users.Any(x => x.Name.Equals(packet.Name, StringComparison.InvariantCultureIgnoreCase)))
+ {
+ User newUser = new User(connection, GetNextID(), packet.Name, packet.Session.Value.Nation);
+
+ // Notify other users about new user.
+ foreach (User user in _users)
+ {
+ SendPacket(user.Connection, new Packet(PacketCode.Login,
+ value1: newUser.ID,
+ value4: 0,
+ name: newUser.Name,
+ session: newUser.Session));
+ }
+
+ // Register new user and send reply to him.
+ _users.Add(newUser);
+ SendPacket(connection, new Packet(PacketCode.LoginReply,
+ value1: newUser.ID,
+ error: 0));
+ }
+ else
+ {
+ SendPacket(connection, new Packet(PacketCode.LoginReply,
+ error: 1));
+ }
}
private void OnCreateRoom(PacketConnection connection, Packet packet)
{
- SendPacket(connection, new Packet(PacketCode.CreateRoomReply,
- value1: Interlocked.Increment(ref _idCounter), // room ID
- error: 0));
+ User? fromUser = GetUser(connection);
+ if (fromUser == null)
+ return;
+
+ // Check if room name is valid is not already taken.
+ if (!String.IsNullOrWhiteSpace(packet.Name)
+ && packet.Session.HasValue
+ && !_rooms.Any(x => x.Name.Equals(packet.Name, StringComparison.InvariantCultureIgnoreCase)))
+ {
+ Room newRoom = new Room(GetNextID(), packet.Name, packet.Session.Value.Nation);
+ _rooms.Add(newRoom);
+
+ // Send reply to creator.
+ SendPacket(connection, new Packet(PacketCode.CreateRoomReply,
+ value1: newRoom.ID,
+ error: 0));
+
+ // Notify other users about new room.
+ foreach (User user in _users.Where(x => x != fromUser))
+ {
+ SendPacket(user.Connection, new Packet(PacketCode.CreateRoom,
+ value1: newRoom.ID,
+ data: String.Empty, // do not report creator IP
+ name: newRoom.Name,
+ session: newRoom.Session));
+ }
+ }
+ else
+ {
+ SendPacket(connection, new Packet(PacketCode.CreateRoomReply,
+ error: 1));
+ }
}
private void OnJoinRoom(PacketConnection connection, Packet packet)
{
- SendPacket(connection, new Packet(PacketCode.JoinReply,
- error: 0));
+ User? fromUser = GetUser(connection);
+ if (fromUser == null)
+ return;
+
+ // Check if room ID is valid.
+ if (packet.Value2.HasValue && _rooms.Any(x => x.ID == packet.Value2))
+ {
+ fromUser.RoomID = packet.Value2.Value;
+
+ // Notify other users about the join.
+ foreach (User user in _users.Where(x => x != fromUser))
+ {
+ SendPacket(user.Connection, new Packet(PacketCode.Join,
+ value1: fromUser.RoomID,
+ value10: fromUser.ID));
+ }
+
+ // Send reply to joiner.
+ SendPacket(connection, new Packet(PacketCode.JoinReply,
+ error: 0));
+ }
+ else
+ {
+ SendPacket(connection, new Packet(PacketCode.JoinReply,
+ error: 1));
+ }
}
private void OnLeaveRoom(PacketConnection connection, Packet packet)
@@ -178,6 +282,22 @@ namespace Syroot.Worms.Worms2.GameServer
error: 0));
}
+ private void OnDisconnectUser(PacketConnection connection)
+ {
+ User? disconnectedUser = GetUser(connection);
+ if (disconnectedUser == null)
+ return;
+
+ _users.Remove(disconnectedUser);
+
+ // Notify other users.
+ foreach (User user in _users)
+ {
+ SendPacket(user.Connection, new Packet(PacketCode.DisconnectUser,
+ value10: disconnectedUser.ID));
+ }
+ }
+
private void OnCreateGame(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.CreateGameReply,
@@ -194,7 +314,7 @@ namespace Syroot.Worms.Worms2.GameServer
private void OnConnectGame(PacketConnection connection, Packet packet)
{
SendPacket(connection, new Packet(PacketCode.ConnectGameReply,
- data: Encoding.ASCII.GetBytes("12.34.45.56")));
+ data: "12.34.45.56"));
}
}
}
diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/SessionInfo.cs b/src/tool/Syroot.Worms.Worms2.GameServer/SessionInfo.cs
index 818bb22..729fb8a 100644
--- a/src/tool/Syroot.Worms.Worms2.GameServer/SessionInfo.cs
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/SessionInfo.cs
@@ -12,10 +12,11 @@ namespace Syroot.Worms.Worms2.GameServer
internal Nation Nation;
internal byte GameVersion;
internal byte GameRelease;
- internal byte Action0;
- internal byte Action1;
- internal byte Action2;
- internal byte Action3;
+ internal SessionType Type;
+ internal SessionAccess Access;
+ internal byte Unknown13;
+ internal byte Unknown14;
+
internal byte Unused15;
internal ulong Unused16;
internal ulong Unused24;
@@ -23,11 +24,46 @@ namespace Syroot.Worms.Worms2.GameServer
internal ulong Unused40;
internal ushort Unused48;
+ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
+
+ internal SessionInfo(Nation nation, SessionType type, SessionAccess access = SessionAccess.Public)
+ {
+ Unknown0 = 0x17171717;
+ Unknown4 = 0x02010101;
+ Nation = nation;
+ GameVersion = 49;
+ GameRelease = 49;
+ Type = type;
+ Access = access;
+ Unknown13 = 1;
+ Unknown14 = 0;
+
+ Unused15 = default;
+ Unused16 = default;
+ Unused24 = default;
+ Unused32 = default;
+ Unused40 = default;
+ Unused48 = default;
+ }
+
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
///
public override string ToString() => $"{Unknown0:X8}-{Unknown4:X8} {Nation} {GameVersion}/{GameRelease} "
- + $"{Action0:X2}-{Action1:X2}-{Action2:X2}-{Action3:X2} "
- + $"{Unused15:X2}{Unused16:X16}{Unused24:X16}{Unused24:X16}{Unused40:X16}{Unused48:X4}";
+ + $"{Type}/{Access}/{Unknown13:X2}/{Unknown14:X2} "
+ + $"({Unused15}-{Unused16}-{Unused24}-{Unused24}-{Unused40}-{Unused48})";
+ }
+
+ internal enum SessionType : byte
+ {
+ Room = 1,
+ Game = 4,
+ User = 5
+ }
+
+ internal enum SessionAccess : byte
+ {
+ Public = 1,
+ Protected = 2
}
}
diff --git a/src/tool/Syroot.Worms.Worms2.GameServer/User.cs b/src/tool/Syroot.Worms.Worms2.GameServer/User.cs
new file mode 100644
index 0000000..d1f7484
--- /dev/null
+++ b/src/tool/Syroot.Worms.Worms2.GameServer/User.cs
@@ -0,0 +1,52 @@
+namespace Syroot.Worms.Worms2.GameServer
+{
+ ///
+ /// Represents information on a client connected to the server.
+ ///
+ internal class User
+ {
+ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
+
+ ///
+ /// Initializes a new instance of the class with the given identification.
+ ///
+ /// The to communicate through.
+ /// The unique numerical identifier of the user.
+ /// The textual login name displayed to others.
+ /// The flag displayed to others.
+ internal User(PacketConnection connection, int id, string name, Nation nation)
+ {
+ Connection = connection;
+ ID = id;
+ Name = name;
+ Session = new SessionInfo(nation, SessionType.User);
+ }
+
+ // ---- PROPERTIES ---------------------------------------------------------------------------------------------
+
+ ///
+ /// Gets the to communicate through.
+ ///
+ internal PacketConnection Connection { get; }
+
+ ///
+ /// Gets the unique numerical identifier of the user.
+ ///
+ internal int ID { get; }
+
+ ///
+ /// Gets the textual login name displayed to others.
+ ///
+ internal string Name { get; }
+
+ ///
+ /// Gets the describing the user.
+ ///
+ internal SessionInfo Session { get; }
+
+ ///
+ /// Gets or sets the ID of the the user is in, or 0 for no room.
+ ///
+ internal int RoomID { get; set; }
+ }
+}