mirror of
https://gitlab.com/Syroot/Worms.git
synced 2025-04-09 19:00:06 +03:00
Add server framework (WIP).
This commit is contained in:
parent
26363c9165
commit
677b9eb9c7
55
src/Syroot.OnlineWorms.Server/Client.cs
Normal file
55
src/Syroot.OnlineWorms.Server/Client.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System.Net.Sockets;
|
||||||
|
using Syroot.Worms.OnlineWorms.Server.Net;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a connection with an Online Worms client which replies to received packets appropriately.
|
||||||
|
/// </summary>
|
||||||
|
internal class Client : PacketHandler
|
||||||
|
{
|
||||||
|
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private readonly Server _server;
|
||||||
|
|
||||||
|
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Client"/> class accepted by the given <paramref name="server"/>
|
||||||
|
/// through the provided <see cref="tcpClient"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tcpClient">The <see cref="TcpClient"/> representing the connection to the client.</param>
|
||||||
|
/// <param name="server">The <see cref="Server"/> instance with which this client communicates.</param>
|
||||||
|
public Client(TcpClient tcpClient, Server server)
|
||||||
|
: base(tcpClient)
|
||||||
|
{
|
||||||
|
_server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private Packet HandleConnect(ConnectQueryPacket connectPacket)
|
||||||
|
{
|
||||||
|
return new ConnectReplyPacket
|
||||||
|
{
|
||||||
|
Unknown = "Online Worms Private Server",
|
||||||
|
Version = "114"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//private Packet HandleLogin(LoginQueryPacket loginPacket)
|
||||||
|
//{
|
||||||
|
// return new LoginReplyPacket
|
||||||
|
// {
|
||||||
|
// LoginResult = LoginResult.Success
|
||||||
|
// };
|
||||||
|
//}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private Packet HandleRaw(RawQueryPacket rawPacket)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
26
src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs
Normal file
26
src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents common mathematical operations.
|
||||||
|
/// </summary>
|
||||||
|
internal static class MathFuncs
|
||||||
|
{
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the nearest power of two bigger than the given <paramref name="value"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to get the nearest, bigger power of two for.</param>
|
||||||
|
/// <returns>The nearest, bigger power of two.</returns>
|
||||||
|
internal static int NextPowerOfTwo(int value)
|
||||||
|
{
|
||||||
|
value--;
|
||||||
|
value |= value >> 1;
|
||||||
|
value |= value >> 2;
|
||||||
|
value |= value >> 4;
|
||||||
|
value |= value >> 8;
|
||||||
|
value |= value >> 16;
|
||||||
|
return ++value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
src/Syroot.OnlineWorms.Server/Net/GameConnection.cs
Normal file
128
src/Syroot.OnlineWorms.Server/Net/GameConnection.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using Syroot.BinaryData;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stream of <see cref="Packet"/> instances send between a game client and server.
|
||||||
|
/// </summary>
|
||||||
|
internal class GameConnection
|
||||||
|
{
|
||||||
|
// A complete packet consists of the following:
|
||||||
|
// - ushort id
|
||||||
|
// - ushort dataSize
|
||||||
|
// - byte[dataSize] data
|
||||||
|
|
||||||
|
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private const int _maxDataSize = 2048;
|
||||||
|
|
||||||
|
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private readonly Stream _tcpStream;
|
||||||
|
private readonly byte[] _receiveBuffer = new byte[_maxDataSize];
|
||||||
|
private readonly byte[] _sendDataBuffer = new byte[_maxDataSize];
|
||||||
|
private readonly BinaryStream _writeStream;
|
||||||
|
|
||||||
|
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameConnection"/> class, receiving and sending packets from and
|
||||||
|
/// to the given <paramref name="tcpClient"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tcpClient">The <see cref="TcpClient"/> to communicate with.</param>
|
||||||
|
internal GameConnection(TcpClient tcpClient)
|
||||||
|
{
|
||||||
|
_tcpStream = tcpClient.GetStream();
|
||||||
|
_writeStream = new BinaryStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true),
|
||||||
|
encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="Packet"/> read from the stream. If the packet could not be read, <see langword="null">
|
||||||
|
/// is returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The read <see cref="Packet"/> instance or <see langword="null"> if the packet could not be read.
|
||||||
|
/// </returns>
|
||||||
|
internal Packet ReceivePacket()
|
||||||
|
{
|
||||||
|
// Receive the raw packet data.
|
||||||
|
ushort id;
|
||||||
|
ushort dataSize;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
id = _tcpStream.ReadUInt16();
|
||||||
|
dataSize = _tcpStream.ReadUInt16();
|
||||||
|
ReadComplete(_receiveBuffer, 0, dataSize);
|
||||||
|
}
|
||||||
|
catch (IOException) { return null; } // The underlying socket closed.
|
||||||
|
catch (ObjectDisposedException) { return null; } // The underlying stream closed.
|
||||||
|
|
||||||
|
// Deserialize and return the packet.
|
||||||
|
Packet packet = PacketFactory.Create(id);
|
||||||
|
using (BinaryStream readStream = new BinaryStream(new MemoryStream(_receiveBuffer, 0, dataSize, false),
|
||||||
|
encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount))
|
||||||
|
{
|
||||||
|
packet.Deserialize(readStream);
|
||||||
|
}
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the given <paramref name="packet"/> and returns <see langword="true"/> if the packet was sent
|
||||||
|
/// successfully.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet">The <see cref="Packet"/> to send.</param>
|
||||||
|
/// <returns><see langword="true"/> when the packet was sent.</returns>
|
||||||
|
internal bool SendPacket(Packet packet)
|
||||||
|
{
|
||||||
|
// Serialize the raw packet data.
|
||||||
|
_writeStream.Position = 0;
|
||||||
|
packet.Serialize(_writeStream);
|
||||||
|
ushort dataSize = (ushort)_writeStream.Position;
|
||||||
|
|
||||||
|
// Send the data and return success.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_tcpStream.WriteUInt16(PacketFactory.GetID(packet));
|
||||||
|
_tcpStream.WriteUInt16(dataSize);
|
||||||
|
_tcpStream.Write(_sendDataBuffer, 0, dataSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IOException) { return false; } // A network error appeared, and communication should end.
|
||||||
|
catch (ObjectDisposedException) { return false; } // The underlying stream was most apparently closed.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads <paramref name="count"/> number of bytes to the given <paramref name="buffer"/>, starting at the
|
||||||
|
/// specified <paramref name="offset"/>, and returns the number of bytes read. Since this method ensures that
|
||||||
|
/// the requested number of bytes is always read, it always returns <paramref name="count"/>, and otherwise
|
||||||
|
/// throws an <see cref="IOException"/> if the required bytes could not be read.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The byte array to write the read data to.</param>
|
||||||
|
/// <param name="offset">The offset into <paramref name="buffer"/> at which to start writing data.</param>
|
||||||
|
/// <param name="count">The number of bytes to read into <paramref name="buffer"/>.</param>
|
||||||
|
/// <exception cref="ArgumentException">The <paramref name="buffer"/> cannot store the requested number of
|
||||||
|
/// bytes.</exception>
|
||||||
|
/// <exception cref="IOException">The requested number of bytes could not be read.</exception>
|
||||||
|
private void ReadComplete(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
int totalRead = 0;
|
||||||
|
while (totalRead < count)
|
||||||
|
{
|
||||||
|
// Read returns 0 only when the underlying socket is closed, otherwise it blocks.
|
||||||
|
int read = _tcpStream.Read(buffer, offset + totalRead, count - totalRead);
|
||||||
|
if (read == 0)
|
||||||
|
throw new IOException("The underlying stream has closed, 0 bytes were retrieved.");
|
||||||
|
totalRead += read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs
Normal file
19
src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||||
|
internal class PacketAttribute : Attribute
|
||||||
|
{
|
||||||
|
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public PacketAttribute(ushort id)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public ushort ID { get; }
|
||||||
|
}
|
||||||
|
}
|
67
src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs
Normal file
67
src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a factory creating <see cref="Packet"/> instances by mapping their ID to types to instantiate.
|
||||||
|
/// </summary>
|
||||||
|
internal static class PacketFactory
|
||||||
|
{
|
||||||
|
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static readonly Dictionary<ushort, Type> _packetTypeMap = new Dictionary<ushort, Type>();
|
||||||
|
|
||||||
|
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static PacketFactory()
|
||||||
|
{
|
||||||
|
// Cache the ID-to-packet-class map.
|
||||||
|
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
|
||||||
|
{
|
||||||
|
foreach (PacketAttribute packetAttribute in type.GetCustomAttributes<PacketAttribute>())
|
||||||
|
{
|
||||||
|
if (_packetTypeMap.ContainsKey(packetAttribute.ID))
|
||||||
|
throw new InvalidOperationException($"Packet {packetAttribute.ID} mapped to multiple classes.");
|
||||||
|
_packetTypeMap.Add(packetAttribute.ID, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="Packet"/> instance mapped to the given <paramref name="id"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The ID of the packet class to instantiate.</param>
|
||||||
|
/// <returns>The created <see cref="Packet"/> instance.</returns>
|
||||||
|
/// <exception cref="KeyNotFoundException">No class was mapped to the given packet ID.</exception>
|
||||||
|
internal static Packet Create(ushort id)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
return _packetTypeMap.TryGetValue(id, out Type type)
|
||||||
|
? (Packet)Activator.CreateInstance(type, true)
|
||||||
|
: new RawQueryPacket(id);
|
||||||
|
#else
|
||||||
|
return (Packet)Activator.CreateInstance(_packetTypeMap[id], true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID for the class of the given <paramref name="packet"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the <see cref="Packet"/>.</typeparam>
|
||||||
|
/// <param name="packet">The packet, whose class ID will be returned.</param>
|
||||||
|
/// <returns>The ID of the <see cref="Packet"/> class.</returns>
|
||||||
|
internal static ushort GetID<T>(T packet) where T : Packet
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (packet is RawQueryPacket rawPacket)
|
||||||
|
return rawPacket.ID;
|
||||||
|
#endif
|
||||||
|
return _packetTypeMap.Where(x => x.Value == packet.GetType()).First().Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs
Normal file
100
src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class capable of dispatching received <see cref="Packet"/> instances to a corresponding method.
|
||||||
|
/// </summary>
|
||||||
|
internal class PacketHandler : IDisposable
|
||||||
|
{
|
||||||
|
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, Dictionary<Type, MethodInfo>> _packetHandlers
|
||||||
|
= new Dictionary<Type, Dictionary<Type, MethodInfo>>();
|
||||||
|
|
||||||
|
private readonly TcpClient _tcpClient;
|
||||||
|
private readonly Dictionary<Type, MethodInfo> _handlerMethods;
|
||||||
|
private readonly GameConnection _connection;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal PacketHandler(TcpClient tcpClient)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{tcpClient.Client.RemoteEndPoint} connected");
|
||||||
|
_tcpClient = tcpClient;
|
||||||
|
_handlerMethods = GetHandlerMethods();
|
||||||
|
_connection = new GameConnection(_tcpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in Dispose(bool disposing).
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts handling packets incoming from this client and calls corresponding methods.
|
||||||
|
/// </summary>
|
||||||
|
internal void Accept()
|
||||||
|
{
|
||||||
|
Packet packet;
|
||||||
|
while ((packet = _connection.ReceivePacket()) != null)
|
||||||
|
HandlePacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
_tcpClient.Dispose();
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private Dictionary<Type, MethodInfo> GetHandlerMethods()
|
||||||
|
{
|
||||||
|
Type classType = GetType();
|
||||||
|
if (!_packetHandlers.TryGetValue(classType, out Dictionary<Type, MethodInfo> handlerMethods))
|
||||||
|
{
|
||||||
|
handlerMethods = new Dictionary<Type, MethodInfo>();
|
||||||
|
// Find all packet handling methods which are methods accepting and returning a specific packet.
|
||||||
|
foreach (MethodInfo method in classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
Type inPacket = method.GetParameters().FirstOrDefault()?.ParameterType;
|
||||||
|
if (typeof(Packet).IsAssignableFrom(inPacket) && typeof(Packet).IsAssignableFrom(method.ReturnType))
|
||||||
|
handlerMethods.Add(inPacket, method);
|
||||||
|
}
|
||||||
|
_packetHandlers.Add(classType, handlerMethods);
|
||||||
|
}
|
||||||
|
return handlerMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePacket(Packet inPacket)
|
||||||
|
{
|
||||||
|
Type packetType = inPacket.GetType();
|
||||||
|
Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} >> {packetType.Name}");
|
||||||
|
|
||||||
|
// Invoke the handler and send back any packet resulting from it.
|
||||||
|
if (_handlerMethods[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} << {outPacket.GetType().Name}");
|
||||||
|
_connection.SendPacket(outPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using Syroot.BinaryData;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the client request for a <see cref="ConnectReplyPacket"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Packet(0x800E)]
|
||||||
|
internal class ConnectQueryPacket : Packet
|
||||||
|
{
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal override void Deserialize(BinaryStream stream) { }
|
||||||
|
|
||||||
|
internal override void Serialize(BinaryStream stream) => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using Syroot.BinaryData;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the server response to a <see cref="ConnectQueryPacket"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Packet(0x800F)]
|
||||||
|
internal class ConnectReplyPacket : Packet
|
||||||
|
{
|
||||||
|
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public string Unknown { get; set; }
|
||||||
|
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal override void Deserialize(BinaryStream stream) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
internal override void Serialize(BinaryStream stream)
|
||||||
|
{
|
||||||
|
stream.WriteString(Unknown);
|
||||||
|
stream.WriteString(Version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using Syroot.BinaryData;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the server response to a <see cref="LoginQueryPacket"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Packet(0x8001)]
|
||||||
|
internal class LoginReplyPacket : Packet
|
||||||
|
{
|
||||||
|
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public LoginResult LoginResult { get; set; }
|
||||||
|
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal override void Deserialize(BinaryStream stream) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
internal override void Serialize(BinaryStream stream)
|
||||||
|
{
|
||||||
|
stream.WriteBoolean(LoginResult == LoginResult.Success);
|
||||||
|
stream.WriteEnum(LoginResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum LoginResult : byte
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
IDAlreadyInUse = 3,
|
||||||
|
UnqualifiedID = 4,
|
||||||
|
IncorrectID = 6,
|
||||||
|
IncorrectPassword = 7,
|
||||||
|
DuplicateIDs = 9,
|
||||||
|
IncorrectVersion = 10,
|
||||||
|
BannedID = 11,
|
||||||
|
PublicAccessBanned = 12,
|
||||||
|
TemporarilyBannedID = 13
|
||||||
|
}
|
||||||
|
}
|
17
src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs
Normal file
17
src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using Syroot.BinaryData;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a packet with an ID specifying its contents. To allow the server to instantiate child packet classes,
|
||||||
|
/// decorate it with the <see cref="PacketAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract class Packet
|
||||||
|
{
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal abstract void Deserialize(BinaryStream stream);
|
||||||
|
|
||||||
|
internal abstract void Serialize(BinaryStream stream);
|
||||||
|
}
|
||||||
|
}
|
38
src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs
Normal file
38
src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#if DEBUG
|
||||||
|
using Syroot.BinaryData;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a special fallback packet which simply stores any ID and the raw client packet data.
|
||||||
|
/// </summary>
|
||||||
|
internal class RawQueryPacket : Packet
|
||||||
|
{
|
||||||
|
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal RawQueryPacket(ushort id)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal ushort ID { get; }
|
||||||
|
|
||||||
|
internal byte[] Data { get; set; }
|
||||||
|
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal override void Deserialize(BinaryStream stream)
|
||||||
|
{
|
||||||
|
Data = new byte[stream.Length];
|
||||||
|
stream.Read(Data, 0, Data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(BinaryStream stream)
|
||||||
|
{
|
||||||
|
stream.Write(Data, 0, Data.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
32
src/Syroot.OnlineWorms.Server/Program.cs
Normal file
32
src/Syroot.OnlineWorms.Server/Program.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the main class of the application containing the entry point.
|
||||||
|
/// </summary>
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static Program()
|
||||||
|
{
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static void Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new Server().Listen(17022);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Unhandled exception: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
src/Syroot.OnlineWorms.Server/Server.cs
Normal file
45
src/Syroot.OnlineWorms.Server/Server.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Syroot.Worms.OnlineWorms.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a server listening for incoming client connections and dispatching them into <see cref="Client"/>
|
||||||
|
/// instances.
|
||||||
|
/// </summary>
|
||||||
|
public class Server
|
||||||
|
{
|
||||||
|
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private readonly List<Client> _clients = new List<Client>();
|
||||||
|
|
||||||
|
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the server by accepting new client connections under the given <paramref name="port"/> and
|
||||||
|
/// dispatching them into asynchronous handling threads. This call is blocking.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port">The port on which to listen for new client connections.</param>
|
||||||
|
internal void Listen(int port)
|
||||||
|
{
|
||||||
|
TcpListener tcpListener = new TcpListener(IPAddress.Any, port);
|
||||||
|
tcpListener.Start();
|
||||||
|
Console.WriteLine($"Listening on port {port}...");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Client client = new Client(tcpListener.AcceptTcpClient(), this);
|
||||||
|
_clients.Add(client);
|
||||||
|
|
||||||
|
Task.Run(client.Accept).ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
_clients.Remove(client);
|
||||||
|
client.Dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Syroot.BinaryData" Version="5.0.0" />
|
||||||
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -11,28 +11,45 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Scratchpad", "
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.OnlineWorms.Launcher", "Syroot.Worms.OnlineWorms.Launcher\Syroot.Worms.OnlineWorms.Launcher.csproj", "{510EE83E-9C52-40FD-AC7E-C4981EBF4182}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.OnlineWorms.Launcher", "Syroot.Worms.OnlineWorms.Launcher\Syroot.Worms.OnlineWorms.Launcher.csproj", "{510EE83E-9C52-40FD-AC7E-C4981EBF4182}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.OnlineWorms.Server", "Syroot.OnlineWorms.Server\Syroot.Worms.OnlineWorms.Server.csproj", "{2A06124C-EA75-4946-9959-4AD3DC754B90}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
ReleaseSigned|Any CPU = ReleaseSigned|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.Build.0 = Release|Any CPU
|
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU
|
||||||
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{20ACA971-A9D0-4424-9958-9CEE24F43C9C}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU
|
||||||
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU
|
||||||
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|Any CPU.Build.0 = Release|Any CPU
|
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2A06124C-EA75-4946-9959-4AD3DC754B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2A06124C-EA75-4946-9959-4AD3DC754B90}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2A06124C-EA75-4946-9959-4AD3DC754B90}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2A06124C-EA75-4946-9959-4AD3DC754B90}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2A06124C-EA75-4946-9959-4AD3DC754B90}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2A06124C-EA75-4946-9959-4AD3DC754B90}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
Loading…
x
Reference in New Issue
Block a user