Implement WWPA packet format (WIP).

This commit is contained in:
Ray Koopa 2019-01-15 22:48:56 +01:00
parent c0cab3ecf5
commit efc1a9fcff
45 changed files with 898 additions and 439 deletions

View File

@ -37,8 +37,8 @@ developed by Team17:
## Available tools
* Mgame Launcher: Creates a fake launch configuration to start OW or WWPA clients with.
* Mgame Server: Simulates a server communicating with OW or WWPA clients (WIP).
* Syroot.Worms.Mgame.Launcher: Creates a fake launch configuration to start OW or WWPA clients with.
* Syroot.Worms.Mgame.GameServer: Simulates a server communicating with OW or WWPA clients (WIP).
## Support
The libraries are available as a [NuGet package](https://www.nuget.org/profiles/Syroot).

View File

@ -1,16 +1,19 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Syroot.Worms.Mgame.Server.Net;
using Syroot.Worms.Mgame.GameServer.Core;
using Syroot.Worms.Mgame.GameServer.Packets;
using Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms;
namespace Syroot.Worms.Mgame.Server
namespace Syroot.Worms.Mgame.GameServer
{
/// <summary>
/// Represents a connection with an Online Worms client which replies to received packets appropriately.
/// </summary>
internal class Client : GameConnection
internal class Client : AppConnection
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
@ -184,12 +187,14 @@ namespace Syroot.Worms.Mgame.Server
protected override void OnPrePacketHandle(Packet packet)
{
_server.Log.Write(LogCategory.Client, $"{TcpClient.Client.RemoteEndPoint} >> {packet}");
_server.Log.Write(LogCategory.Client,
$"{TcpClient.Client.RemoteEndPoint} >> {packet}{Environment.NewLine}{ObjectDumper.Dump(packet)}");
}
protected override void OnPrePacketSend(Packet packet)
{
_server.Log.Write(LogCategory.Server, $"{TcpClient.Client.RemoteEndPoint} << {packet}");
_server.Log.Write(LogCategory.Server,
$"{TcpClient.Client.RemoteEndPoint} << {packet}{Environment.NewLine}{ObjectDumper.Dump(packet)}");
if (_server.Config.SendDelay > 0)
Thread.Sleep(_server.Config.SendDelay);
}

View File

@ -1,6 +1,6 @@
using System.Net;
namespace Syroot.Worms.Mgame.Server
namespace Syroot.Worms.Mgame.GameServer
{
internal class Config
{

View File

@ -0,0 +1,67 @@
using System;
using System.IO;
namespace Syroot.Worms.Mgame.GameServer.Core.IO
{
/// <summary>
/// Represents a stream which ensures to read the specified number of bytes and does not return prematurely.
/// </summary>
/// <typeparam name="T">The type of the wrapped <see cref="Stream"/>.</typeparam>
internal class SafeWrapperStream<T> : WrapperStream<T>
where T : Stream
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
internal SafeWrapperStream(T baseStream) : base(baseStream) { }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <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="IOException">The requested number of bytes could not be read.</exception>
/// <returns>The number of bytes requested.</returns>
public override int Read(byte[] buffer, int offset, int size)
{
int totalRead = 0;
while (totalRead < size)
{
// For network streams, read returns 0 only when the underlying socket is closed, otherwise it blocks.
int read = BaseStream.Read(buffer, offset + totalRead, size - totalRead);
if (read == 0)
throw new IOException("The underlying stream closed, 0 bytes were retrieved.");
totalRead += read;
}
return totalRead;
}
/// <summary>
/// Reads the required number of bytes to fill the given <paramref name="buffer"/>, and returns the number of
/// bytes read. Since this method ensures that the requested number of bytes is always read, it always returns
/// the length of the buffer, and otherwise throws an <see cref="IOException"/> if the required bytes could not
/// be read.
/// </summary>
/// <param name="buffer">The <see cref="Span{byte}"/> to write the read data to.</param>
/// <exception cref="IOException">The requested number of bytes could not be read.</exception>
/// <returns>The number of bytes requested.</returns>
public override int Read(Span<byte> buffer)
{
int totalRead = 0;
while (totalRead < buffer.Length)
{
// For network streams, read returns 0 only when the underlying socket is closed, otherwise it blocks.
int read = BaseStream.Read(buffer.Slice(totalRead));
if (read == 0)
throw new IOException("The underlying stream closed, 0 bytes were retrieved.");
totalRead += read;
}
return totalRead;
}
}
}

View File

@ -0,0 +1,44 @@
using System.IO;
namespace Syroot.Worms.Mgame.GameServer.Core.IO
{
/// <summary>
/// Represents a <see cref="Stream"/> wrapping around another, implementing default methods.
/// </summary>
/// <typeparam name="T">The type of the wrapped <see cref="Stream"/>.</typeparam>
internal class WrapperStream<T> : Stream
where T : Stream
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
internal WrapperStream(T baseStream)
{
BaseStream = baseStream;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public override bool CanRead => BaseStream.CanRead;
public override bool CanSeek => BaseStream.CanSeek;
public override bool CanWrite => BaseStream.CanWrite;
public override long Length => BaseStream.Length;
public override long Position
{
get => BaseStream.Position;
set => BaseStream.Position = value;
}
/// <summary>
/// Gets the underlying stream of type <see cref="T"/>.
/// </summary>
internal T BaseStream { get; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public override void Flush() => BaseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) => BaseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) => BaseStream.Seek(offset, origin);
public override void SetLength(long value) => BaseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) => BaseStream.Write(buffer, offset, count);
}
}

View File

@ -0,0 +1,17 @@
using System.Net.Sockets;
using Syroot.Worms.Mgame.GameServer.Core.IO;
namespace Syroot.Worms.Mgame.GameServer.Core.Net
{
/// <summary>
/// Represents a <see cref="NetworkStream"/> which ensures to read the specified number of bytes and does not return
/// prematurely.
/// </summary>
/// <typeparam name="T">The type of the wrapped <see cref="Stream"/>.</typeparam>
internal class SafeNetworkStream : SafeWrapperStream<NetworkStream>
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
internal SafeNetworkStream(NetworkStream baseStream) : base(baseStream) { }
}
}

View File

@ -0,0 +1,22 @@
using System.Net.Sockets;
namespace Syroot.Worms.Mgame.GameServer.Core.Net
{
/// <summary>
/// Represents extension methods for <see cref="TcpClient"/> instances.
/// </summary>
internal static class TcpClientExtensions
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Returns the <see cref="SafeNetworkStream"/> used to send and receive data.
/// </summary>
/// <param name="self">The extended <see cref="TcpClient"/> instance.</param>
/// <returns>The <see cref="SafeNetworkStream"/> instance.</returns>
internal static SafeNetworkStream GetSafeStream(this TcpClient self)
{
return new SafeNetworkStream(self.GetStream());
}
}
}

View File

@ -6,35 +6,23 @@ using System.Net;
using System.Reflection;
using System.Text;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Core
{
/// <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"/>.
/// Represents tools for <see cref="Object"/> instances.
/// </summary>
internal abstract class Packet
internal static class ObjectDumper
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public override string ToString()
{
#if DEBUG
return String.Concat(GetType().Name, DumpClass(this, 1));
#else
return GetType().Name;
#endif
}
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal abstract void Deserialize(PacketStream stream);
internal abstract void Serialize(PacketStream stream);
internal static string Dump(object obj)
{
return Dump(obj, 0);
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
#if DEBUG
private string DumpClass(object obj, int indentLevel)
private static string Dump(object obj, int indentLevel)
{
StringBuilder sb = new StringBuilder();
string indent = new string(' ', indentLevel * 4);
@ -54,7 +42,7 @@ namespace Syroot.Worms.Mgame.Server.Net
sb.Append(indent);
sb.Append("[ ");
foreach (object element in iEnumerableValue)
sb.Append(DumpClass(element, indentLevel)).Append(" ");
sb.Append(Dump(element, indentLevel)).Append(" ");
sb.Append("]");
break;
case Byte byteValue:
@ -98,7 +86,7 @@ namespace Syroot.Worms.Mgame.Server.Net
sb.AppendLine();
sb.Append((indent + property.Name).PadRight(20));
sb.Append(" ");
sb.Append(DumpClass(property.GetValue(obj), indentLevel + 1));
sb.Append(Dump(property.GetValue(obj), indentLevel + 1));
}
sb.AppendLine();
}
@ -106,6 +94,5 @@ namespace Syroot.Worms.Mgame.Server.Net
}
return sb.ToString().TrimEnd();
}
#endif
}
}

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Syroot.Worms.Mgame.GameServer.Core.Reflection
{
/// <summary>
/// Represents an instance dispatching parameters of type <typeparamref name="T"/> to methods accepting them.
/// </summary>
/// <typeparam name="T">The type of the parameter to dispatch to methods.</typeparam>
internal class MethodHandler<T>
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private static readonly Dictionary<Type, Dictionary<Type, MethodInfo>> _classCache;
private readonly object _instance;
private readonly Dictionary<Type, MethodInfo> _handlers;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
static MethodHandler()
{
_classCache = new Dictionary<Type, Dictionary<Type, MethodInfo>>();
}
internal MethodHandler(object instance)
{
_instance = instance;
_handlers = GetHandlers(_instance);
}
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal void Handle(T parameter)
{
Type parameterType = parameter.GetType();
if (!_handlers.TryGetValue(parameterType, out MethodInfo method))
throw new ArgumentException($"No method handles type {parameterType}.");
method.Invoke(_instance, new object[] { parameter });
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private Dictionary<Type, MethodInfo> GetHandlers(object instance)
{
Type classType = instance.GetType();
if (!_classCache.TryGetValue(classType, out Dictionary<Type, MethodInfo> handlerMethods))
{
// Find all methods which accept a specific parameter and return nothing.
handlerMethods = new Dictionary<Type, MethodInfo>();
foreach (MethodInfo method in classType.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
Type parameterType = method.GetParameters().FirstOrDefault()?.ParameterType;
if (parameterType == null)
continue;
if (handlerMethods.ContainsKey(parameterType))
throw new InvalidOperationException($"Parameter {parameterType} handled by multiple methods.");
if (method.ReturnType == typeof(void) && typeof(T).IsAssignableFrom(parameterType))
handlerMethods.Add(parameterType, method);
}
_classCache.Add(classType, handlerMethods);
}
return handlerMethods;
}
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace Syroot.Worms.Mgame.Server
namespace Syroot.Worms.Mgame.GameServer
{
/// <summary>
/// Represents simplistic textual logging.

View File

@ -0,0 +1,270 @@
using System;
using System.IO;
using System.Net.Sockets;
using Syroot.BinaryData;
using Syroot.Worms.Mgame.GameServer.Core.Net;
using Syroot.Worms.Mgame.GameServer.Core.Reflection;
using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua;
namespace Syroot.Worms.Mgame.GameServer.Packets
{
/// <summary>
/// Represents a class capable of dispatching received <see cref="Packet"/> instances to a corresponding method.
/// </summary>
internal class AppConnection : IDisposable
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _maxDataSize = 2048;
private const ushort _owTagStartChannel = 0xFFFE;
private const ushort _owTagEndChannel = 0xFEFF;
private const ushort _wwpaTagStart = 0x2E9E;
private const ushort _wwpaTagEnd = 0x7F3F;
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly MethodHandler<Packet> _methodHandler;
private readonly SafeNetworkStream _tcpStream;
private readonly PacketDataStream _packetDataStream;
private bool _disposed;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="AppConnection"/> class, handling the given
/// <paramref name="tcpClient"/>.
/// </summary>
/// <param name="tcpClient">The <see cref="System.Net.Sockets.TcpClient"/> to communicate with.</param>
internal AppConnection(TcpClient tcpClient)
{
_methodHandler = new MethodHandler<Packet>(this);
TcpClient = tcpClient;
_tcpStream = TcpClient.GetSafeStream();
_packetDataStream = new PacketDataStream();
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="TcpClient"/> with which the connection communicates.
/// </summary>
internal TcpClient TcpClient { get; }
// ---- 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 connection and dispatches them to corresponding methods.
/// </summary>
internal void Listen()
{
Packet packet;
while ((packet = ReceivePacket()) != null)
{
OnPrePacketHandle(packet);
_methodHandler.Handle(packet);
}
}
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
TcpClient.Close();
_packetDataStream.Dispose();
}
_disposed = true;
}
}
protected virtual void OnPrePacketHandle(Packet packet) { }
protected virtual void OnPrePacketSend(Packet packet) { }
protected bool SendPacket(Packet packet)
{
OnPrePacketSend(packet);
// Serialize the packet data to retrieve its size.
_packetDataStream.Position = 0;
packet.SaveData(_packetDataStream);
// Send the total packet data including head and tail.
try
{
PacketAttribute attribute = PacketFactory.GetAttribute(packet);
switch (attribute.Format)
{
case PacketFormat.OWChannel:
SendOWChannelPacket(attribute.ID);
break;
case PacketFormat.OWServer:
SendOWServerPacket(attribute.ID);
break;
case PacketFormat.Wwpa:
SaveWwpaPacket(packet);
break;
default:
throw new IOException("Cannot send unknown packet format.");
}
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) --------------------------------------------------------------------------------------
private Packet ReceivePacket()
{
try
{
// Handle specific packet formats.
ushort tag = _tcpStream.ReadUInt16();
switch (tag)
{
case _owTagStartChannel:
return ReceiveOWChannelPacket();
case _wwpaTagStart:
return ReceiveWwpaPacket();
default:
return ReceiveOWServerPacket(tag);
}
}
catch (IOException) { return null; } // The underlying socket closed.
catch (ObjectDisposedException) { return null; } // The underlying stream closed.
}
private Packet ReceiveOWChannelPacket()
{
// Read head.
if (_tcpStream.Read1Byte() != 1)
throw new IOException("Invalid OW channel packet start tag.");
int dataSize = _tcpStream.ReadUInt16();
int id = _tcpStream.Read1Byte();
// Read data.
byte[] data = _tcpStream.ReadBytes(dataSize);
// Read tail.
ushort endTag = _tcpStream.ReadUInt16();
if (endTag != _owTagEndChannel)
throw new IOException("Invalid OW channel packet end tag.");
// Instantiate, deserialize, and return packet.
Packet packet = PacketFactory.Create(PacketFormat.OWChannel, id);
using (PacketDataStream stream = new PacketDataStream(data))
packet.LoadData(stream);
return packet;
}
private Packet ReceiveOWServerPacket(int tag)
{
// Read head.
int dataSize = _tcpStream.ReadUInt16();
// Read data.
byte[] data = _tcpStream.ReadBytes(dataSize);
// Instantiate, deserialize, and return packet.
Packet packet = PacketFactory.Create(PacketFormat.OWServer, tag);
using (PacketDataStream stream = new PacketDataStream(data))
packet.LoadData(stream);
return packet;
}
private Packet ReceiveWwpaPacket()
{
// Read head.
if (!_tcpStream.ReadBoolean())
throw new PacketException("Unexpected WWPA packet head2.");
if (_tcpStream.ReadBoolean())
throw new PacketException("Unexpected WWPA packet head3.");
if (!_tcpStream.ReadBoolean())
throw new PacketException("Unexpected WWPA packet bIsCompressed.");
int decompressedSize = _tcpStream.ReadUInt16();
int compressedSize = _tcpStream.ReadInt32();
int idxPacket = _tcpStream.ReadInt32();
// Read data.
byte[] compressedData = _tcpStream.ReadBytes(compressedSize);
// Read tail.
if (_tcpStream.ReadInt32() != idxPacket)
throw new PacketException("Invalid WWPA packet index.");
if (_tcpStream.ReadUInt16() != _wwpaTagEnd)
throw new PacketException("Invalid WWPA packet end tag.");
// Instantiate, deserialize, and return packet.
int id = 0;
Packet packet = PacketFactory.Create(PacketFormat.Wwpa, id);
byte[] decompressedData = PacketCompression.Decompress(compressedData);
using (PacketDataStream stream = new PacketDataStream(decompressedData))
packet.LoadData(stream);
return packet;
}
private void SendOWChannelPacket(int id)
{
// Retrieve data size. Must have at least 1 byte.
if (_packetDataStream.Position == 0)
_packetDataStream.WriteByte(0);
ushort dataSize = (ushort)_packetDataStream.Position;
// Send head.
_tcpStream.WriteUInt16(_owTagStartChannel);
_tcpStream.WriteByte(1);
_tcpStream.WriteUInt16(dataSize);
_tcpStream.WriteByte((byte)id);
// Send data.
_tcpStream.Write(_packetDataStream.GetSpan());
// Send tail.
_tcpStream.WriteUInt16(_owTagEndChannel);
}
private void SendOWServerPacket(int id)
{
// Retrieve data size.
ushort dataSize = (ushort)_packetDataStream.Position;
// Send head.
_tcpStream.WriteUInt16((ushort)id);
_tcpStream.WriteUInt16(dataSize);
// Write data.
_tcpStream.Write(_packetDataStream.GetSpan());
}
private void SaveWwpaPacket(Packet packet)
{
// Retrieve (decompressed) data size.
ReadOnlySpan<byte> decompressedData = _packetDataStream.GetSpan();
ReadOnlySpan<byte> compressedData = PacketCompression.Compress(decompressedData);
// Send head.
_tcpStream.WriteUInt16(_wwpaTagStart);
_tcpStream.WriteBoolean(true);
_tcpStream.WriteBoolean(false);
_tcpStream.WriteBoolean(true); // isCompressed
_tcpStream.WriteUInt16((ushort)decompressedData.Length);
_tcpStream.WriteUInt32((uint)compressedData.Length);
_tcpStream.WriteInt32(1); // Apparently only needs to be same as in tail.
// Send data.
_tcpStream.Write(compressedData);
// Send tail.
_tcpStream.WriteInt32(1); // Apparently only needs to be same as in head.
_tcpStream.WriteUInt16(_wwpaTagEnd);
}
}
}

View File

@ -3,12 +3,12 @@ using System.Collections.Generic;
using System.Net;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the client request for a <see cref="ChannelConnectReply"/>.
/// </summary>
[Packet(PacketFormat.Channel, 0x10)]
[Packet(PacketFormat.OWChannel, 0x10)]
internal class ChannelConnectQuery : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -25,7 +25,7 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream)
internal override void LoadData(PacketDataStream stream)
{
Players = new List<ChannelConnectPlayerCredentials>
{
@ -51,7 +51,7 @@ namespace Syroot.Worms.Mgame.Server.Net
UnknownB = stream.Read1Byte();
}
internal override void Serialize(PacketStream stream) => throw new NotImplementedException();
internal override void SaveData(PacketDataStream stream) => throw new NotImplementedException();
}
internal class ChannelConnectPlayerCredentials

View File

@ -2,12 +2,12 @@
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the server response to a <see cref="ChannelConnectQuery"/>.
/// </summary>
[Packet(PacketFormat.Channel, 0x11)]
[Packet(PacketFormat.OWChannel, 0x11)]
internal class ChannelConnectReply : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -18,9 +18,9 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
if (Result == ChannelConnectResult.Success)
{

View File

@ -1,18 +1,18 @@
using System;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents an additional server response to a <see cref="ChannelTop20Query"/>, causing the client to switch
/// to the channel screen (game lobby).
/// </summary>
[Packet(PacketFormat.Channel, 0x44)]
[Packet(PacketFormat.OWChannel, 0x44)]
internal class ChannelEnterFinishReply : Packet
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream) { }
internal override void SaveData(PacketDataStream stream) { }
}
}

View File

@ -1,13 +1,12 @@
using System;
using System.Net;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the client request for a <see cref="ChannelEnterReply"/>.
/// </summary>
[Packet(PacketFormat.Server, 0x8034)]
[Packet(PacketFormat.OWServer, 0x8034)]
internal class ChannelEnterQuery : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -18,12 +17,12 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream)
internal override void LoadData(PacketDataStream stream)
{
PlayerID = stream.ReadString();
ChannelEndPoint = new IPEndPoint(IPAddress.Parse(stream.ReadString()), stream.ReadUInt16());
}
internal override void Serialize(PacketStream stream) => throw new NotImplementedException();
internal override void SaveData(PacketDataStream stream) => throw new NotImplementedException();
}
}

View File

@ -1,13 +1,12 @@
using System;
using System.Net;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the server response to a <see cref="ChannelEnterQuery"/>.
/// </summary>
[Packet(PacketFormat.Server, 0x8035)]
[Packet(PacketFormat.OWServer, 0x8035)]
internal class ChannelEnterReply : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -19,9 +18,9 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
stream.WriteString(EndPoint.Address.ToString());
stream.WriteUInt16((ushort)EndPoint.Port);

View File

@ -4,12 +4,12 @@ using System.Drawing;
using System.Net;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents an additional server response to a <see cref="LoginQuery"/>, providing available server channels.
/// </summary>
[Packet(PacketFormat.Server, 0x80C9)]
[Packet(PacketFormat.OWServer, 0x80C9)]
internal class ChannelInfosReply : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -18,9 +18,9 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
stream.WriteUInt16((ushort)Channels.Count);
foreach (ChannelInfo channel in Channels)

View File

@ -1,12 +1,11 @@
using System;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the client request for a <see cref="ChannelConnectReply"/>.
/// </summary>
[Packet(PacketFormat.Channel, 0x37)]
[Packet(PacketFormat.OWChannel, 0x37)]
internal class ChannelTop20Query : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -18,11 +17,11 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream)
internal override void LoadData(PacketDataStream stream)
{
Count = stream.ReadUInt16();
}
internal override void Serialize(PacketStream stream) => throw new NotImplementedException();
internal override void SaveData(PacketDataStream stream) => throw new NotImplementedException();
}
}

View File

@ -3,12 +3,12 @@ using System.Collections.Generic;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the server response to a <see cref="ChannelTop20Query"/>.
/// </summary>
[Packet(PacketFormat.Channel, 0x36)]
[Packet(PacketFormat.OWChannel, 0x36)]
internal class ChannelTop20Reply : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -19,9 +19,9 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
stream.WriteString(UnknownA, 30);
foreach (ChannelTop20Player top20Player in Top20)

View File

@ -1,17 +1,17 @@
using System;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the client request for a <see cref="ConnectReply"/>.
/// </summary>
[Packet(PacketFormat.Server, 0x800E)]
[Packet(PacketFormat.OWServer, 0x800E)]
internal class ConnectQuery : Packet
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) { }
internal override void LoadData(PacketDataStream stream) { }
internal override void Serialize(PacketStream stream) => throw new NotImplementedException();
internal override void SaveData(PacketDataStream stream) => throw new NotImplementedException();
}
}

View File

@ -1,12 +1,11 @@
using System;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the server response to a <see cref="ConnectQuery"/>.
/// </summary>
[Packet(PacketFormat.Server, 0x800F)]
[Packet(PacketFormat.OWServer, 0x800F)]
internal class ConnectReply : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -19,9 +18,9 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
stream.WriteString(Unknown);
stream.WriteByte(0);

View File

@ -1,13 +1,12 @@
using System;
using System.Net;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the client request for a <see cref="LoginReply"/>.
/// </summary>
[Packet(PacketFormat.Server, 0x8000)]
[Packet(PacketFormat.OWServer, 0x8000)]
internal class LoginQuery : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -20,7 +19,7 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream)
internal override void LoadData(PacketDataStream stream)
{
Unknown1 = stream.ReadUInt16();
Logins = new LoginCredentials[stream.ReadUInt16()];
@ -35,7 +34,7 @@ namespace Syroot.Worms.Mgame.Server.Net
ClientIP = IPAddress.Parse(stream.ReadString());
}
internal override void Serialize(PacketStream stream) => throw new NotImplementedException();
internal override void SaveData(PacketDataStream stream) => throw new NotImplementedException();
}
internal class LoginCredentials

View File

@ -1,12 +1,12 @@
using System;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the server response to a <see cref="LoginQuery"/>.
/// </summary>
[Packet(PacketFormat.Server, 0x8001)]
[Packet(PacketFormat.OWServer, 0x8001)]
internal class LoginReply : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -23,9 +23,9 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
bool loginSuccessful = Result == LoginResult.Success;
stream.WriteBoolean(loginSuccessful);

View File

@ -1,12 +1,12 @@
using System;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents an additional server response to a <see cref="LoginQuery"/>, providing informational server
/// screen text.
/// </summary>
[Packet(PacketFormat.Server, 0x8033)]
[Packet(PacketFormat.OWServer, 0x8033)]
internal class ServerInfoReply : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -18,9 +18,9 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
stream.WriteString(Text.Replace(Environment.NewLine, "\n"));
}

View File

@ -1,12 +1,12 @@
using System;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the client request for a <see cref="StartSingleGameReply"/>.
/// </summary>
[Packet(PacketFormat.Channel, 0x38)]
[Packet(PacketFormat.OWChannel, 0x38)]
internal class StartSingleGameQuery : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -17,14 +17,14 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream)
internal override void LoadData(PacketDataStream stream)
{
RoundType = stream.ReadEnum<GameStartRoundType>(true);
UnknownB = stream.Read1Byte();
UnknownC = stream.Read1Byte();
}
internal override void Serialize(PacketStream stream) => throw new NotImplementedException();
internal override void SaveData(PacketDataStream stream) => throw new NotImplementedException();
}
internal enum GameStartRoundType : byte

View File

@ -1,12 +1,12 @@
using System;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
{
/// <summary>
/// Represents the server response to a <see cref="StartSingleGameQuery"/>.
/// </summary>
[Packet(PacketFormat.Channel, 0x39)]
[Packet(PacketFormat.OWChannel, 0x39)]
internal class StartSingleGameReply : Packet
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -15,9 +15,9 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
internal override void LoadData(PacketDataStream stream) => throw new NotImplementedException();
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
stream.WriteBoolean(Success, BooleanCoding.Word);
}

View File

@ -0,0 +1,15 @@
namespace Syroot.Worms.Mgame.GameServer.Packets
{
/// <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 LoadData(PacketDataStream stream);
internal abstract void SaveData(PacketDataStream stream);
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets
{
/// <summary>
/// Decorates a <see cref="Packet"/> child class with the ID of the packet it represents.
@ -34,9 +34,16 @@ namespace Syroot.Worms.Mgame.Server.Net
public PacketFormat Format { get; }
}
/// <summary>
/// Represents the known packet formats.
/// </summary>
internal enum PacketFormat
{
Server,
Channel
/// <summary>An Online Worms server packet.</summary>
OWServer,
/// <summary>An Online Worms channel packet.</summary>
OWChannel,
/// <summary>A Worms World Party Aqua packet.</summary>
Wwpa
}
}

View File

@ -0,0 +1,64 @@
using System;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.GameServer
{
/// <summary>
/// Represents an in-memory stream formatting data for being sent or received from <see cref="Packet"/> instances.
/// </summary>
internal class PacketDataStream : BinaryStream
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly MemoryStream _baseStream;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
static PacketDataStream()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
internal PacketDataStream() : this(new MemoryStream()) { }
internal PacketDataStream(byte[] buffer) : this(new MemoryStream(buffer)) { }
private PacketDataStream(MemoryStream baseStream)
: base(baseStream, encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount)
{
_baseStream = baseStream;
}
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Returns a <see cref="Span{Byte}"/> containing the actually used memory buffer bytes.
/// </summary>
/// <returns>The span containing the used buffer bytes.</returns>
internal Span<byte> GetSpan() => _baseStream.GetBuffer().AsSpan(0, (int)Position);
/// <summary>
/// Reads the remaining bytes in the buffer.
/// </summary>
/// <returns>The remaining bytes.</returns>
internal byte[] ReadToEnd()
{
return ReadBytes((int)(Length - Position));
}
/// <summary>
/// Writes the given <paramref name="color"/> as an RGB0 integer value.
/// </summary>
/// <param name="color">The <see cref="Color"/> to write.</param>
internal void WriteColor(Color color)
{
WriteByte(color.R);
WriteByte(color.G);
WriteByte(color.B);
WriteByte(0);
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Runtime.Serialization;
namespace Syroot.Worms.Mgame.GameServer.Packets
{
public class PacketException : Exception
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
public PacketException() { }
public PacketException(string message) : base(message) { }
public PacketException(string message, Exception innerException) : base(message, innerException) { }
protected PacketException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
}

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets
{
/// <summary>
/// Represents a factory creating <see cref="Packet"/> instances by mapping their ID to types to instantiate.
@ -12,23 +12,24 @@ namespace Syroot.Worms.Mgame.Server.Net
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private static readonly Dictionary<PacketAttribute, Type> _packetMetas = new Dictionary<PacketAttribute, Type>();
private static readonly Dictionary<PacketAttribute, Type> _packetCache;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
static PacketFactory()
{
// Cache the ID-to-packet-class map.
_packetCache = new Dictionary<PacketAttribute, Type>();
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
foreach (PacketAttribute attrib in type.GetCustomAttributes<PacketAttribute>())
{
if (_packetMetas.ContainsKey(attrib))
if (_packetCache.ContainsKey(attrib))
{
throw new InvalidOperationException(
$"{attrib.Format} packet with ID {attrib.ID} mapped to multiple classes.");
}
_packetMetas.Add(attrib, type);
_packetCache.Add(attrib, type);
}
}
}
@ -43,7 +44,7 @@ namespace Syroot.Worms.Mgame.Server.Net
/// <exception cref="KeyNotFoundException">No class was mapped to the given packet ID.</exception>
internal static Packet Create(PacketFormat type, int id)
{
foreach (KeyValuePair<PacketAttribute, Type> packetMeta in _packetMetas)
foreach (KeyValuePair<PacketAttribute, Type> packetMeta in _packetCache)
{
if (packetMeta.Key.Format == type && packetMeta.Key.ID == id)
return (Packet)Activator.CreateInstance(packetMeta.Value, true);
@ -67,7 +68,7 @@ namespace Syroot.Worms.Mgame.Server.Net
return new PacketAttribute(rawPacket.Type, rawPacket.ID);
else
#endif
return _packetMetas.Where(x => x.Value == packet.GetType()).First().Key;
return _packetCache.Where(x => x.Value == packet.GetType()).First().Key;
}
}
}

View File

@ -1,7 +1,7 @@
#if DEBUG
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
namespace Syroot.Worms.Mgame.GameServer.Packets
{
/// <summary>
/// Represents a special fallback packet which simply stores any ID and the raw packet data.
@ -27,12 +27,12 @@ namespace Syroot.Worms.Mgame.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal override void Deserialize(PacketStream stream)
internal override void LoadData(PacketDataStream stream)
{
Data = stream.ReadBytes((int)stream.Length);
}
internal override void Serialize(PacketStream stream)
internal override void SaveData(PacketDataStream stream)
{
stream.WriteBytes(Data);
}

View File

@ -0,0 +1,166 @@
using System;
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
{
internal static class PacketCompression
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private static int _field_0 = 0;
private static int _field_4 = 4;
private static int _field_8 = 0;
private static int _field_C = 0;
private static int _field_10 = 0;
private static int[] _bufferDwords = new int[512];
private static byte[] _buffer = new byte[256];
private static int _bufferCursor = 0;
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal static ReadOnlySpan<byte> Compress(ReadOnlySpan<byte> decompressed)
{
Compressor compressor = new Compressor();
int idx = 0;
while (idx < decompressed.Length)
{
int bytesToRepeat = Math.Max(0, idx - 0xFF);
int idxDword = 0;
int shiftValue1 = -1;
if (bytesToRepeat >= idx)
{
Write(compressor, decompressed[idx++]);
}
else
{
do
{
int i;
for (i = idx; i < decompressed.Length; i++)
{
if (i - idx >= 0x111)
break;
if (decompressed[bytesToRepeat] != decompressed[i])
break;
bytesToRepeat++;
}
int offset = idx - i;
int v11 = i - idx;
int v12 = offset + bytesToRepeat;
if (v11 >= 3 && v11 > idxDword)
{
idxDword = v11;
shiftValue1 = idx - 12;
if (v11 == 0x111)
break;
}
bytesToRepeat = v12 + 1;
} while (bytesToRepeat < idx);
if (idxDword != 0)
{
TransferBuffer(compressor);
compressor.Compress(true);
if (idxDword >= 18)
{
compressor.Shift(0, 4);
compressor.Shift(shiftValue1, 8);
compressor.Shift(idxDword - 18, 8);
_field_C++;
_field_10 = idxDword - 3 + _field_10;
}
else
{
compressor.Shift(idxDword - 2, 4);
compressor.Shift(shiftValue1 - 1, 8);
_field_4++;
_field_8 = idxDword - 2 + _field_8;
}
idx += idxDword;
_bufferDwords[idxDword]++;
}
else
{
Write(compressor, decompressed[idx++]);
}
}
}
TransferBuffer(compressor);
compressor.Compress(true);
compressor.Shift(0, 4);
compressor.Shift(0, 8);
return compressor.Buffer.AsSpan(0, compressor.Cursor);
}
internal static byte[] Decompress(ReadOnlySpan<byte> compressed)
{
return new byte[1];
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void TransferBuffer(Compressor compressor)
{
if (_bufferCursor != 0)
{
_field_0 += _bufferCursor;
compressor.Compress(false);
compressor.Shift(_bufferCursor - 1, 8);
for (int i = 0; i < _bufferCursor; i++)
compressor.Buffer[compressor.Cursor++] = _buffer[i];
_bufferCursor = 0;
}
}
private static void Write(Compressor compressor, byte c)
{
_buffer[_bufferCursor++] = c;
if (_bufferCursor >= 256)
TransferBuffer(compressor);
}
// ---- CLASSES, STRUCTS & ENUMS -------------------------------------------------------------------------------
private class Compressor
{
internal byte[] Buffer = new byte[1024];
internal int Cursor = 1;
private int _cursorCompress = 0;
private int _shift = 0;
internal void Compress(bool bShift)
{
if (bShift)
{
Buffer[_cursorCompress] |= (byte)(0x80 >> _shift);
}
if (++_shift == 8)
{
_cursorCompress = Cursor++;
Buffer[_cursorCompress] = 0;
_shift = 0;
}
}
internal void Shift(int value, int shiftPlus1)
{
int prevShift = 0;
int shift = shiftPlus1 - 1;
if (shiftPlus1 != 0)
{
do
{
Compress(((value >> shift) & 1) != 0);
prevShift = shift--;
} while (prevShift != 0);
}
}
}
}
}

View File

@ -1,6 +1,9 @@
using System;
using System.Diagnostics;
using Syroot.BinaryData;
using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua;
namespace Syroot.Worms.Mgame.Server
namespace Syroot.Worms.Mgame.GameServer
{
/// <summary>
/// Represents the main class of the application containing the entry point.
@ -11,6 +14,12 @@ namespace Syroot.Worms.Mgame.Server
private static void Main(string[] args)
{
byte[] decompressed = new byte[sizeof(int)];
ByteConverter.System.GetBytes(0x8001, decompressed);
ReadOnlySpan<byte> compressed = PacketCompression.Compress(decompressed);
Debug.Assert(compressed.SequenceEqual(new byte[] { 0x01, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00 }),
"Compression failed");
try
{
// Start a new server.

View File

@ -4,7 +4,7 @@ using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
namespace Syroot.Worms.Mgame.Server
namespace Syroot.Worms.Mgame.GameServer
{
/// <summary>
/// Represents a server listening for incoming client connections and dispatching them into <see cref="Client"/>
@ -22,7 +22,7 @@ namespace Syroot.Worms.Mgame.Server
{
// Create and read the configuration.
Config = new ConfigurationBuilder()
.AddJsonFile("OWServerConfig.json", true)
.AddJsonFile("ServerConfig.json", true)
.Build()
.Get<Config>();
}

View File

@ -2,10 +2,10 @@
// Server settings
"IP": "127.0.0.1", // external IP sent to clients to connect to
"Port": 17022,
"Name": "Online Worms Private Server",
"Name": "Mgame Worms Private Server",
"Region": "Global",
"Version": 114,
// Debugging settings (optional)
"SendDelay": 100 // milliseconds to sleep before sending packets (simulate network load)
"SendDelay": 0 // milliseconds to sleep before sending packets (simulate network load)
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Server</AssemblyName>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />
<None Update="ServerConfig.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -3,7 +3,7 @@
"ServerIP": "127.0.0.1",
"ServerPort": 17022,
// Autologin credentials (optional)
// Autologin credentials
"UserName": "UserName",
"Password": "Password",

View File

@ -1,211 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
{
/// <summary>
/// Represents a class capable of dispatching received <see cref="Packet"/> instances to a corresponding method.
/// </summary>
internal class GameConnection : IDisposable
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _maxDataSize = 2048;
private const ushort _channelPacketStartTag = 0xFFFE;
private const byte _channelPacketStartTag2 = 1;
private const ushort _channelPacketEndTag = 0xFEFF;
// ---- FIELDS -------------------------------------------------------------------------------------------------
private static readonly Dictionary<Type, Dictionary<Type, MethodInfo>> _connectionClassesCache
= new Dictionary<Type, Dictionary<Type, MethodInfo>>();
private readonly Dictionary<Type, MethodInfo> _handlers;
private readonly byte[] _receiveBuffer;
private readonly byte[] _sendDataBuffer;
private readonly PacketStream _receiveStream;
private readonly PacketStream _sendStream;
private bool _disposed;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="GameConnection"/> class, handling the given
/// <paramref name="tcpClient"/>.
/// </summary>
/// <param name="tcpClient">The <see cref="System.Net.Sockets.TcpClient"/> to communicate with.</param>
internal GameConnection(TcpClient tcpClient)
{
TcpClient = tcpClient;
_handlers = GetHandlers();
_receiveBuffer = new byte[_maxDataSize];
_sendDataBuffer = new byte[_maxDataSize];
_receiveStream = new PacketStream(TcpClient.GetStream());
_sendStream = new PacketStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true));
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="TcpClient"/> with which the connection communicates.
/// </summary>
internal TcpClient TcpClient { get; }
// ---- 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 connection and dispatches them to corresponding methods.
/// </summary>
internal void Listen()
{
Packet packet;
while ((packet = ReceivePacket()) != null)
HandlePacket(packet);
}
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
TcpClient.Close();
_sendStream.Dispose();
}
_disposed = true;
}
}
protected virtual void OnPrePacketHandle(Packet packet) { }
protected virtual void OnPrePacketSend(Packet packet) { }
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private Dictionary<Type, MethodInfo> GetHandlers()
{
Type classType = GetType();
if (!_connectionClassesCache.TryGetValue(classType, out Dictionary<Type, MethodInfo> handlerMethods))
{
handlerMethods = new Dictionary<Type, MethodInfo>();
// Find all packet handling methods which are methods accepting a specific packet and returning nothing.
foreach (MethodInfo method in classType.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
Type inPacket = method.GetParameters().FirstOrDefault()?.ParameterType;
if (method.ReturnType == typeof(void) && typeof(Packet).IsAssignableFrom(inPacket))
handlerMethods.Add(inPacket, method);
}
_connectionClassesCache.Add(classType, handlerMethods);
}
return handlerMethods;
}
private void HandlePacket(Packet inPacket)
{
OnPrePacketHandle(inPacket);
_handlers[inPacket.GetType()].Invoke(this, new[] { inPacket });
}
private Packet ReceivePacket()
{
// Receive the raw packet data.
PacketFormat type;
int id;
ushort dataSize;
try
{
// Handle both server and channel specific packet formats.
ushort tag = _receiveStream.ReadUInt16();
if (tag == _channelPacketStartTag)
{
type = PacketFormat.Channel;
if (_receiveStream.Read1Byte() != 1)
throw new IOException("Invalid channel packet start tag.");
dataSize = _receiveStream.ReadUInt16();
id = _receiveStream.Read1Byte();
_receiveStream.ReadAll(_receiveBuffer, 0, dataSize);
ushort endTag = _receiveStream.ReadUInt16();
if (endTag != _channelPacketEndTag)
throw new IOException("Invalid channel packet end tag.");
}
else
{
type = PacketFormat.Server;
id = tag;
dataSize = _receiveStream.ReadUInt16();
_receiveStream.ReadAll(_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(type, id);
using (PacketStream readStream = new PacketStream(new MemoryStream(_receiveBuffer, 0, dataSize, false)))
packet.Deserialize(readStream);
return packet;
}
protected bool SendPacket(Packet packet)
{
OnPrePacketSend(packet);
// Serialize the packet data.
_sendStream.Position = 0;
packet.Serialize(_sendStream);
ushort dataSize;
// Send the packet in one of the two possible formats and return success.
try
{
PacketAttribute attribute = PacketFactory.GetAttribute(packet);
switch (attribute.Format)
{
case PacketFormat.Server:
// Retrieve data size.
dataSize = (ushort)_sendStream.Position;
// Send head and data.
_receiveStream.WriteUInt16((ushort)attribute.ID);
_receiveStream.WriteUInt16(dataSize);
_receiveStream.Write(_sendDataBuffer, 0, dataSize);
break;
case PacketFormat.Channel:
// Retrieve data size. Data must have at least 1 byte.
if (_sendStream.Position == 0)
_sendStream.WriteByte(0);
dataSize = (ushort)_sendStream.Position;
// Send head, data, and tail.
_receiveStream.WriteUInt16(_channelPacketStartTag);
_receiveStream.WriteByte(1);
_receiveStream.WriteUInt16(dataSize);
_receiveStream.WriteByte((byte)attribute.ID);
_receiveStream.Write(_sendDataBuffer, 0, dataSize);
_receiveStream.Write(_channelPacketEndTag);
break;
default:
throw new IOException("Cannot send unknown packet format.");
}
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.
}
}
}

View File

@ -1,76 +0,0 @@
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame.Server.Net
{
/// <summary>
/// Represents a stream formatting data for being sent or received from <see cref="Packet"/> instances.
/// </summary>
internal class PacketStream : BinaryStream
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private static readonly Encoding _win949Encoding;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
static PacketStream()
{
#if NETCOREAPP2_1
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#endif
_win949Encoding = Encoding.GetEncoding(949);
}
internal PacketStream(Stream baseStream)
: base(baseStream, encoding: _win949Encoding, stringCoding: StringCoding.Int16CharCount) { }
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <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="IOException">The requested number of bytes could not be read.</exception>
internal void ReadAll(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 = BaseStream.Read(buffer, offset + totalRead, count - totalRead);
if (read == 0)
throw new IOException("The underlying stream has closed, 0 bytes were retrieved.");
totalRead += read;
}
}
/// <summary>
/// Reads the remaining bytes in the buffer.
/// </summary>
/// <returns>The remaining bytes.</returns>
internal byte[] ReadToEnd()
{
return BaseStream.ReadBytes((int)(Length - Position));
}
/// <summary>
/// Writes the given <paramref name="color"/> as an RGB0 integer value.
/// </summary>
/// <param name="color">The <see cref="Color"/> to write.</param>
internal void WriteColor(Color color)
{
BaseStream.WriteByte(color.R);
BaseStream.WriteByte(color.G);
BaseStream.WriteByte(color.B);
BaseStream.WriteByte(0);
}
}
}

View File

@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Syroot.BinaryData" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms.Armageddon.ProjectX\Syroot.Worms.Armageddon.ProjectX.csproj" />
<ProjectReference Include="..\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" />
<ProjectReference Include="..\Syroot.Worms.Mgame.Launcher\Syroot.Worms.Mgame.Launcher.csproj" />
<ProjectReference Include="..\Syroot.Worms.Mgame\Syroot.Worms.Mgame.csproj" />
<ProjectReference Include="..\Syroot.Worms.WorldParty\Syroot.Worms.WorldParty.csproj" />
<ProjectReference Include="..\Syroot.Worms.Worms2\Syroot.Worms.Worms2.csproj" />
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="OWServerConfig.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -6,6 +6,7 @@
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms.Armageddon.ProjectX\Syroot.Worms.Armageddon.ProjectX.csproj" />
<ProjectReference Include="..\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" />
<ProjectReference Include="..\Syroot.Worms.Mgame.GameServer\Syroot.Worms.Mgame.GameServer.csproj" />
<ProjectReference Include="..\Syroot.Worms.Mgame\Syroot.Worms.Mgame.csproj" />
<ProjectReference Include="..\Syroot.Worms.WorldParty\Syroot.Worms.WorldParty.csproj" />
<ProjectReference Include="..\Syroot.Worms.Worms2\Syroot.Worms.Worms2.csproj" />

View File

@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Scratchpad", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Mgame.Launcher", "Syroot.Worms.Mgame.Launcher\Syroot.Worms.Mgame.Launcher.csproj", "{510EE83E-9C52-40FD-AC7E-C4981EBF4182}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Mgame.Server", "Syroot.Worms.Mgame.Server\Syroot.Worms.Mgame.Server.csproj", "{2A06124C-EA75-4946-9959-4AD3DC754B90}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Armageddon", "Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj", "{D9694108-539C-4DEE-B5DD-284208D48B46}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Armageddon.ProjectX", "Syroot.Worms.Armageddon.ProjectX\Syroot.Worms.Armageddon.ProjectX.csproj", "{AC9F52FA-F552-49E0-83AE-79759BF44FED}"
@ -37,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syroot.Worms.Armageddon.Pro
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syroot.Worms.Test", "Syroot.Worms.Test\Syroot.Worms.Test.csproj", "{351B93B0-301F-42E1-82A0-7FA217154F5D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Mgame.GameServer", "Syroot.Worms.Mgame.GameServer\Syroot.Worms.Mgame.GameServer.csproj", "{392E4CA2-61D9-4BE1-B065-8801A9F102B8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -55,10 +55,6 @@ Global
{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.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
{D9694108-539C-4DEE-B5DD-284208D48B46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -99,6 +95,10 @@ Global
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Release|Any CPU.Build.0 = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -106,7 +106,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1} = {9E964426-D744-405A-9EAA-D8D7A310B614}
{510EE83E-9C52-40FD-AC7E-C4981EBF4182} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{2A06124C-EA75-4946-9959-4AD3DC754B90} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{D9694108-539C-4DEE-B5DD-284208D48B46} = {9E964426-D744-405A-9EAA-D8D7A310B614}
{AC9F52FA-F552-49E0-83AE-79759BF44FED} = {9E964426-D744-405A-9EAA-D8D7A310B614}
{4E124BBA-15B5-422E-93D5-96EA7D4180F3} = {9E964426-D744-405A-9EAA-D8D7A310B614}
@ -117,6 +116,7 @@ Global
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
{EF308D4E-26A0-471C-B764-9C4EB713BEAE} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
{351B93B0-301F-42E1-82A0-7FA217154F5D} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
{392E4CA2-61D9-4BE1-B065-8801A9F102B8} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1CD4EDE2-A5FB-4A58-A850-3506AB7E7B69}

View File

@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Syroot.BinaryData.Serialization" Version="5.0.0" />
<PackageReference Include="Syroot.BinaryData" Version="5.0.0" />
<PackageReference Include="Syroot.BinaryData" Version="5.1.0-beta1" />
<PackageReference Include="System.Drawing.Common" Version="4.5.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
</ItemGroup>