mirror of
https://gitlab.com/Syroot/Worms.git
synced 2025-03-04 09:25:22 +03:00
Delegate packet handling to IPacketFormat.
This commit is contained in:
parent
cc84ed3d5f
commit
a8073367f4
@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Channel;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer
|
||||
{
|
||||
internal partial class Client
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
public void HandleOWChannelConnect(LoginQuery packet)
|
||||
{
|
||||
SendPacket(new LoginReply
|
||||
{
|
||||
Result = ChannelConnectResult.Success,
|
||||
Player = new ChannelConnectPlayerInfo
|
||||
{
|
||||
ID = packet.Players[0].UserName,
|
||||
Name = "Your Name",
|
||||
Experience = 1337,
|
||||
Gold = 1000000,
|
||||
Rank = 19,
|
||||
GuildMarkIndex = 1,
|
||||
GuildName = "Your Guild"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void HandleOWChannelTop20(Top20Query packet)
|
||||
{
|
||||
// Send some simulated player rank infos.
|
||||
Top20Reply reply = new Top20Reply
|
||||
{
|
||||
UnknownA = "Test",
|
||||
Top20 = new List<ChannelTop20Player>(20)
|
||||
};
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
reply.Top20.Add(new ChannelTop20Player
|
||||
{
|
||||
Name = $"GoodPlayer{(char)('A' + i)}",
|
||||
Rank = (ushort)((i + 6) / 3),
|
||||
Experience = (ulong)(20 - i) * 957
|
||||
});
|
||||
}
|
||||
SendPacket(reply);
|
||||
|
||||
// This is the last channel info packet, tell the client to go to the channel screen.
|
||||
SendPacket(new EnterFinishReply());
|
||||
}
|
||||
|
||||
public void HandleOWChannelStartSingleGameQuery(StartSingleGameQuery packet)
|
||||
{
|
||||
SendPacket(new StartSingleGameReply
|
||||
{
|
||||
Success = packet.RoundType == GameStartRoundType.First
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing;
|
||||
using System.Net;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.Data;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer
|
||||
{
|
||||
@ -10,7 +9,7 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
public void HandleOWConnect(ConnectQuery packet)
|
||||
public void HandleOWServerConnect(ConnectQuery packet)
|
||||
{
|
||||
SendPacket(new ConnectReply
|
||||
{
|
||||
@ -20,7 +19,7 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
});
|
||||
}
|
||||
|
||||
public void HandleOWLogin(LoginQuery packet)
|
||||
public void HandleOWServerLogin(LoginQuery packet)
|
||||
{
|
||||
// Send login result.
|
||||
// Create player infos from the given credentials. This would be the place to check for actual accounts.
|
||||
@ -37,7 +36,7 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
});
|
||||
|
||||
// Send info text.
|
||||
SendPacket(new ServerInfoReply
|
||||
SendPacket(new InfoReply
|
||||
{
|
||||
Text = "Welcome to the Online Worms Server."
|
||||
});
|
||||
@ -96,7 +95,7 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
});
|
||||
}
|
||||
|
||||
public void HandleOWChannelEnter(ChannelEnterQuery packet)
|
||||
public void HandleOWServerChannelEnter(ChannelEnterQuery packet)
|
||||
{
|
||||
// Simply allow joining a channel running on the same server port.
|
||||
SendPacket(new ChannelEnterReply
|
||||
@ -104,54 +103,5 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
EndPoint = _server.Config.EndPoint
|
||||
});
|
||||
}
|
||||
|
||||
public void HandleOWChannelConnect(ChannelLoginQuery packet)
|
||||
{
|
||||
SendPacket(new ChannelLoginReply
|
||||
{
|
||||
Result = ChannelConnectResult.Success,
|
||||
Player = new ChannelConnectPlayerInfo
|
||||
{
|
||||
ID = packet.Players[0].UserName,
|
||||
Name = "Your Name",
|
||||
Experience = 1337,
|
||||
Gold = 1000000,
|
||||
Rank = 19,
|
||||
GuildMarkIndex = 1,
|
||||
GuildName = "Your Guild"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void HandleOWChannelTop20(ChannelTop20Query packet)
|
||||
{
|
||||
// Send some simulated player rank infos.
|
||||
ChannelTop20Reply reply = new ChannelTop20Reply
|
||||
{
|
||||
UnknownA = "Test",
|
||||
Top20 = new List<ChannelTop20Player>(20)
|
||||
};
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
reply.Top20.Add(new ChannelTop20Player
|
||||
{
|
||||
Name = $"GoodPlayer{(char)('A' + i)}",
|
||||
Rank = (ushort)((i + 6) / 3),
|
||||
Experience = (ulong)(20 - i) * 957
|
||||
});
|
||||
}
|
||||
SendPacket(reply);
|
||||
|
||||
// This is the last channel info packet, tell the client to go to the channel screen.
|
||||
SendPacket(new ChannelEnterFinishReply());
|
||||
}
|
||||
|
||||
public void HandleOWStartSingleGameQuery(StartSingleGameQuery packet)
|
||||
{
|
||||
SendPacket(new StartSingleGameReply
|
||||
{
|
||||
Success = packet.RoundType == GameStartRoundType.First
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer
|
||||
@ -107,5 +108,7 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
{
|
||||
SendPacket(new OptionsSaveReply { Success = true });
|
||||
}
|
||||
|
||||
public void HandleWwpaRawPacket(RawPacket packet) { }
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,15 @@ using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using Syroot.Worms.Mgame.GameServer.Core;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a connection with an Online Worms client which replies to received packets appropriately.
|
||||
/// Represents a connection with a game client which replies to received packets appropriately.
|
||||
/// </summary>
|
||||
internal partial class Client : AppConnection
|
||||
internal partial class Client : PacketConnection
|
||||
{
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -24,25 +26,15 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
/// </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>
|
||||
internal Client(TcpClient tcpClient, Server server)
|
||||
: base(tcpClient)
|
||||
internal Client(TcpClient tcpClient, Server server) : base(tcpClient,
|
||||
WwpaPacketFormat.Instance, OWChannelPacketFormat.Instance, OWServerPacketFormat.Instance)
|
||||
{
|
||||
_server = server;
|
||||
PrePacketHandle += (s, e) => _server.Log.Write(LogCategory.Client, FormatPacket(e, ">>"));
|
||||
PrePacketSend += (s, e) =>
|
||||
{
|
||||
_server.Log.Write(LogCategory.Server, FormatPacket(e, "<<"));
|
||||
if (_server.Config.SendDelay > 0)
|
||||
Thread.Sleep(_server.Config.SendDelay);
|
||||
};
|
||||
PrePacketHandle += OnPrePacketHandle;
|
||||
PrePacketSend += OnPrePacketSend;
|
||||
UnhandledException += OnUnhandledException;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
public void HandleRawPacket(RawPacket packet) { }
|
||||
#endif
|
||||
|
||||
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||
|
||||
private string FormatPacket(IPacket packet, string direction)
|
||||
@ -50,5 +42,24 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
string packetName = String.Join(' ', packet.GetType().FullName.Split('.').TakeLast(2));
|
||||
return $"{TcpClient.Client.RemoteEndPoint} {direction} {packetName}{ObjectDumper.Dump(packet)}";
|
||||
}
|
||||
|
||||
// ---- EVENTHANDLERS ------------------------------------------------------------------------------------------
|
||||
|
||||
private void OnPrePacketHandle(object sender, IPacket packet)
|
||||
{
|
||||
_server.Log.Write(LogCategory.Client, FormatPacket(packet, ">>"));
|
||||
}
|
||||
|
||||
private void OnPrePacketSend(object sender, IPacket packet)
|
||||
{
|
||||
_server.Log.Write(LogCategory.Server, FormatPacket(packet, "<<"));
|
||||
if (_server.Config.SendDelay > 0)
|
||||
Thread.Sleep(_server.Config.SendDelay);
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, Exception ex)
|
||||
{
|
||||
_server.Log.Write(LogCategory.Error, $"{TcpClient.Client.RemoteEndPoint} {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
63
src/Syroot.Worms.Mgame.GameServer/Core/IO/CachedStream.cs
Normal file
63
src/Syroot.Worms.Mgame.GameServer/Core/IO/CachedStream.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Core.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="Stream"/> which caches the last bytes in a buffer and allows virtual back-seeking even
|
||||
/// in non-seekable streams.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the wrapped <see cref="Stream"/>.</typeparam>
|
||||
internal class CachedStream<T> : WrapperStream<T>
|
||||
where T : Stream
|
||||
{
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
private readonly byte[] _cache;
|
||||
private int _virtualPosition;
|
||||
private int _cacheEnd;
|
||||
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
internal CachedStream(T baseStream, int cacheSize, bool leaveOpen = false) : base(baseStream, leaveOpen)
|
||||
{
|
||||
_cache = new byte[cacheSize];
|
||||
}
|
||||
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// Return bytes from cache first, if possible.
|
||||
int cacheBytes = Math.Min(_cacheEnd - _virtualPosition, count);
|
||||
if (cacheBytes > 0)
|
||||
{
|
||||
Buffer.BlockCopy(_cache, _virtualPosition, buffer, offset, cacheBytes);
|
||||
_virtualPosition += cacheBytes;
|
||||
offset += cacheBytes;
|
||||
}
|
||||
|
||||
// Read more bytes from the stream if the cache is not covering all.
|
||||
int streamBytes = count - cacheBytes;
|
||||
if (streamBytes > 0)
|
||||
{
|
||||
if (_cache.Length - _cacheEnd < streamBytes)
|
||||
throw new IOException("Cache cannot hold more bytes.");
|
||||
streamBytes = BaseStream.Read(_cache, _cacheEnd, streamBytes);
|
||||
_cacheEnd += streamBytes;
|
||||
|
||||
Buffer.BlockCopy(_cache, _virtualPosition, buffer, offset, count);
|
||||
_virtualPosition += streamBytes;
|
||||
}
|
||||
|
||||
return cacheBytes + streamBytes;
|
||||
}
|
||||
|
||||
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||
|
||||
internal void MoveToCacheStart()
|
||||
{
|
||||
_virtualPosition = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ namespace Syroot.Worms.Mgame.GameServer.Core.IO
|
||||
{
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
internal SafeWrapperStream(T baseStream) : base(baseStream) { }
|
||||
internal SafeWrapperStream(T baseStream, bool leaveOpen = false) : base(baseStream, leaveOpen) { }
|
||||
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -9,11 +9,23 @@ namespace Syroot.Worms.Mgame.GameServer.Core.IO
|
||||
internal class WrapperStream<T> : Stream
|
||||
where T : Stream
|
||||
{
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
private readonly bool _leaveOpen;
|
||||
private bool _disposed;
|
||||
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
internal WrapperStream(T baseStream)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WrapperStream{T}"/> class wrapping the given
|
||||
/// <paramref name="baseStream"/> which it may <paramref name="leaveOpen"/> on dispose.
|
||||
/// </summary>
|
||||
/// <param name="baseStream">The <see cref="Stream"/> to wrap.</param>
|
||||
/// <param name="leaveOpen"><see langword="true"/> to leave the stream open on <see cref="Dispose()"/>.</param>
|
||||
internal WrapperStream(T baseStream, bool leaveOpen = false)
|
||||
{
|
||||
BaseStream = baseStream;
|
||||
_leaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
@ -40,5 +52,17 @@ namespace Syroot.Worms.Mgame.GameServer.Core.IO
|
||||
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);
|
||||
|
||||
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
if (disposing && !_leaveOpen)
|
||||
BaseStream.Dispose();
|
||||
_disposed = true;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Core.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents utility methods helping with reflection.
|
||||
/// </summary>
|
||||
internal static class ReflectionTools
|
||||
{
|
||||
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||
|
||||
internal static IList<(Type type, TAttrib attrib)> GetAttributedClasses<TAttrib>()
|
||||
where TAttrib : Attribute
|
||||
{
|
||||
List<(Type, TAttrib)> classes = new List<(Type, TAttrib)>();
|
||||
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
|
||||
foreach (TAttrib attrib in type.GetCustomAttributes<TAttrib>())
|
||||
classes.Add((type, attrib));
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ namespace Syroot.Worms.Mgame.GameServer
|
||||
internal enum LogCategory
|
||||
{
|
||||
Info = ConsoleColor.White,
|
||||
Error = ConsoleColor.Red,
|
||||
Connect = ConsoleColor.Cyan,
|
||||
Disconnect = ConsoleColor.Magenta,
|
||||
Client = ConsoleColor.DarkCyan,
|
||||
|
@ -1,271 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using Syroot.BinaryData;
|
||||
using Syroot.BinaryData.Memory;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.Net;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.Reflection;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a class capable of dispatching received <see cref="IPacket"/> instances to a corresponding method.
|
||||
/// </summary>
|
||||
internal class AppConnection : IDisposable
|
||||
{
|
||||
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
|
||||
|
||||
private const int _maxDataSize = 1024;
|
||||
private const ushort _owTagStartChannel = 0xFFFE;
|
||||
private const ushort _owTagEndChannel = 0xFEFF;
|
||||
private const ushort _wwpaTagStart = 0x2E9E;
|
||||
private const ushort _wwpaTagEnd = 0x7F3F;
|
||||
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
private readonly MethodHandler<IPacket> _methodHandler;
|
||||
private readonly SafeNetworkStream _tcpStream;
|
||||
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<IPacket>(this);
|
||||
TcpClient = tcpClient;
|
||||
_tcpStream = TcpClient.GetSafeStream();
|
||||
}
|
||||
|
||||
// ---- EVENTS -------------------------------------------------------------------------------------------------
|
||||
|
||||
protected event EventHandler<IPacket> PrePacketHandle;
|
||||
protected event EventHandler<IPacket> PrePacketSend;
|
||||
|
||||
// ---- 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()
|
||||
{
|
||||
IPacket packet;
|
||||
while ((packet = ReceivePacket()) != null)
|
||||
{
|
||||
PrePacketHandle?.Invoke(this, packet);
|
||||
_methodHandler.Handle(packet);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
TcpClient.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool SendPacket(IPacket packet)
|
||||
{
|
||||
PrePacketSend?.Invoke(this, packet);
|
||||
try
|
||||
{
|
||||
// Handle specific packet formats.
|
||||
PacketAttribute attribute = PacketFactory.GetAttribute(packet);
|
||||
switch (attribute.Format)
|
||||
{
|
||||
case PacketFormat.OWChannel:
|
||||
SendOWChannelPacket(attribute.ID, packet);
|
||||
break;
|
||||
case PacketFormat.OWServer:
|
||||
SendOWServerPacket(attribute.ID, packet);
|
||||
break;
|
||||
case PacketFormat.Wwpa:
|
||||
SendWwpaPacket(attribute.ID, packet, false);
|
||||
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 IPacket 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 or sent invalid data.
|
||||
catch (ObjectDisposedException) { return null; } // The underlying stream closed.
|
||||
}
|
||||
|
||||
private IPacket 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.
|
||||
SpanReader reader = new SpanReader(_tcpStream.ReadBytes(dataSize), encoding: Encodings.Korean);
|
||||
// Read tail.
|
||||
ushort endTag = _tcpStream.ReadUInt16();
|
||||
if (endTag != _owTagEndChannel)
|
||||
throw new IOException("Invalid OW channel packet end tag.");
|
||||
|
||||
// Instantiate, deserialize, and return packet.
|
||||
IPacket packet = PacketFactory.Create(PacketFormat.OWChannel, id);
|
||||
packet.Load(ref reader);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private IPacket ReceiveOWServerPacket(int tag)
|
||||
{
|
||||
// Read head.
|
||||
int dataSize = _tcpStream.ReadUInt16();
|
||||
// Read data.
|
||||
SpanReader reader = new SpanReader(_tcpStream.ReadBytes(dataSize), encoding: Encodings.Korean);
|
||||
|
||||
// Instantiate, deserialize, and return packet.
|
||||
IPacket packet = PacketFactory.Create(PacketFormat.OWServer, tag);
|
||||
packet.Load(ref reader);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private IPacket ReceiveWwpaPacket()
|
||||
{
|
||||
// Read head.
|
||||
if (!_tcpStream.ReadBoolean())
|
||||
throw new IOException("Unexpected WWPA packet bHead2.");
|
||||
if (_tcpStream.ReadBoolean())
|
||||
throw new IOException("Unexpected WWPA packet bHead3.");
|
||||
if (!_tcpStream.ReadBoolean())
|
||||
throw new IOException("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 IOException("Invalid WWPA packet index.");
|
||||
if (_tcpStream.ReadUInt16() != _wwpaTagEnd)
|
||||
throw new IOException("Invalid WWPA packet end tag.");
|
||||
|
||||
// Instantiate, deserialize, and return packet.
|
||||
SpanReader reader = new SpanReader(PacketCompression.Decompress(compressedData), encoding: Encodings.Korean);
|
||||
int id = reader.ReadInt32();
|
||||
IPacket packet = PacketFactory.Create(PacketFormat.Wwpa, id);
|
||||
SpanReader dataReader = reader.Slice();
|
||||
packet.Load(ref dataReader);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private void SendOWChannelPacket(int id, IPacket packet)
|
||||
{
|
||||
// Retrieve data. Must have at least 1 byte.
|
||||
SpanWriter writer = new SpanWriter(new byte[_maxDataSize], encoding: Encodings.Korean);
|
||||
packet.Save(ref writer);
|
||||
writer.Position = Math.Max(1, writer.Position);
|
||||
|
||||
// Send head.
|
||||
_tcpStream.WriteUInt16(_owTagStartChannel);
|
||||
_tcpStream.WriteByte(1);
|
||||
_tcpStream.WriteUInt16((ushort)writer.Position);
|
||||
_tcpStream.WriteByte((byte)id);
|
||||
|
||||
// Send data.
|
||||
_tcpStream.Write(writer.Span);
|
||||
|
||||
// Send tail.
|
||||
_tcpStream.WriteUInt16(_owTagEndChannel);
|
||||
}
|
||||
|
||||
private void SendOWServerPacket(int id, IPacket packet)
|
||||
{
|
||||
// Retrieve data.
|
||||
SpanWriter writer = new SpanWriter(new byte[_maxDataSize], encoding: Encodings.Korean);
|
||||
packet.Save(ref writer);
|
||||
|
||||
// Send head.
|
||||
_tcpStream.WriteUInt16((ushort)id);
|
||||
_tcpStream.WriteUInt16((ushort)writer.Position);
|
||||
|
||||
// Write data.
|
||||
_tcpStream.Write(writer.Span);
|
||||
}
|
||||
|
||||
private void SendWwpaPacket(int id, IPacket packet, bool compress)
|
||||
{
|
||||
// Retrieve (decompressed) data.
|
||||
SpanWriter writer = new SpanWriter(new byte[_maxDataSize], encoding: Encodings.Korean);
|
||||
writer.WriteInt32(id);
|
||||
SpanWriter dataWriter = writer.Slice();
|
||||
packet.Save(ref dataWriter);
|
||||
writer.Position += dataWriter.Position;
|
||||
|
||||
// Send head and data.
|
||||
_tcpStream.WriteUInt16(_wwpaTagStart);
|
||||
_tcpStream.WriteBoolean(true); // bHead2 (unknown)
|
||||
_tcpStream.WriteBoolean(false); // bHead3 (unknown)
|
||||
_tcpStream.WriteBoolean(compress);
|
||||
if (compress)
|
||||
{
|
||||
ReadOnlySpan<byte> compressedData = PacketCompression.Compress(writer.Span);
|
||||
_tcpStream.WriteUInt16((ushort)writer.Position); // decompressed size
|
||||
_tcpStream.WriteUInt32((uint)compressedData.Length); // compressed size
|
||||
_tcpStream.WriteInt32(1); // index
|
||||
_tcpStream.Write(compressedData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tcpStream.WriteUInt16(0); // unused
|
||||
_tcpStream.WriteUInt32((ushort)writer.Position); // raw size
|
||||
_tcpStream.WriteInt32(1); // index
|
||||
_tcpStream.Write(writer.Span.Slice(0, writer.Position));
|
||||
}
|
||||
|
||||
// Send tail.
|
||||
_tcpStream.WriteInt32(1); // index (must equal other)
|
||||
_tcpStream.WriteUInt16(_wwpaTagEnd);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Channel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an additional server response to a <see cref="ChannelTop20Query"/>, causing the client to switch
|
||||
/// Represents an additional server response to a <see cref="Top20Query"/>, causing the client to switch
|
||||
/// to the channel screen (game lobby).
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWChannel, 0x44)]
|
||||
internal class ChannelEnterFinishReply : IPacket
|
||||
[OWChannelPacket(0x44)]
|
||||
internal class EnterFinishReply : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -4,13 +4,13 @@ using System.Net;
|
||||
using Syroot.BinaryData.Memory;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.Data;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Channel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="ChannelLoginReply"/>.
|
||||
/// Represents the client request for a <see cref="LoginReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWChannel, 0x10)]
|
||||
internal class ChannelLoginQuery : IPacket
|
||||
[OWChannelPacket(0x10)]
|
||||
internal class LoginQuery : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Channel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="ChannelLoginQuery"/>.
|
||||
/// Represents the server response to a <see cref="LoginQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWChannel, 0x11)]
|
||||
internal class ChannelLoginReply : IPacket
|
||||
[OWChannelPacket(0x11)]
|
||||
internal class LoginReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Channel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="StartSingleGameReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWChannel, 0x38)]
|
||||
[OWChannelPacket(0x38)]
|
||||
internal class StartSingleGameQuery : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Channel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="StartSingleGameQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWChannel, 0x39)]
|
||||
[OWChannelPacket(0x39)]
|
||||
internal class StartSingleGameReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Channel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="ChannelLoginReply"/>.
|
||||
/// Represents the client request for a <see cref="LoginReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWChannel, 0x37)]
|
||||
internal class ChannelTop20Query : IPacket
|
||||
[OWChannelPacket(0x37)]
|
||||
internal class Top20Query : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -2,13 +2,13 @@
|
||||
using System.Collections.Generic;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Channel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="ChannelTop20Query"/>.
|
||||
/// Represents the server response to a <see cref="Top20Query"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWChannel, 0x36)]
|
||||
internal class ChannelTop20Reply : IPacket
|
||||
[OWChannelPacket(0x36)]
|
||||
internal class Top20Reply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
{
|
||||
/// <summary>
|
||||
/// Decorates an <see cref="IPacket"/> child class with the ID of the OW channel packet it represents.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
internal class OWChannelPacketAttribute : Attribute
|
||||
{
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OWChannelPacketAttribute"/> class, decorating a class with the
|
||||
/// ID of the packet it represents.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the packet which the decorated class represents.</param>
|
||||
internal OWChannelPacketAttribute(byte id)
|
||||
{
|
||||
ID = id;
|
||||
}
|
||||
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the packet the decorated class represents.
|
||||
/// </summary>
|
||||
internal byte ID { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Syroot.BinaryData;
|
||||
using Syroot.BinaryData.Memory;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.Reflection;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the factory of packets in the OW channel packet format.
|
||||
/// </summary>
|
||||
internal class OWChannelPacketFormat : IPacketFormat
|
||||
{
|
||||
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
|
||||
|
||||
private const ushort _tagStart = 0xFFFE;
|
||||
private const ushort _tagEnd = 0xFEFF;
|
||||
private const int _maxDataSize = 1024;
|
||||
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
private static readonly IList<(Type type, OWChannelPacketAttribute attrib)> _cachedClasses
|
||||
= ReflectionTools.GetAttributedClasses<OWChannelPacketAttribute>();
|
||||
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
private OWChannelPacketFormat() { }
|
||||
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
||||
internal static OWChannelPacketFormat Instance { get; } = new OWChannelPacketFormat();
|
||||
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
public IPacket TryLoad(Stream stream)
|
||||
{
|
||||
// Read head.
|
||||
if (stream.ReadUInt16() != _tagStart || stream.Read1Byte() != 1)
|
||||
return null;
|
||||
int dataSize = stream.ReadUInt16();
|
||||
IPacket packet = GetPacket(stream.Read1Byte());
|
||||
if (packet == null)
|
||||
return null;
|
||||
|
||||
// Read data.
|
||||
SpanReader reader = new SpanReader(stream.ReadBytes(dataSize), encoding: Encodings.Korean);
|
||||
|
||||
// Read tail.
|
||||
if (stream.ReadUInt16() != _tagEnd)
|
||||
return null;
|
||||
|
||||
// Deserialize and return packet.
|
||||
packet.Load(ref reader);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public bool TrySave(Stream stream, IPacket packet)
|
||||
{
|
||||
byte? id = GetID(packet);
|
||||
if (!id.HasValue)
|
||||
return false;
|
||||
|
||||
// Retrieve data. Must have at least 1 byte.
|
||||
SpanWriter writer = new SpanWriter(new byte[_maxDataSize], encoding: Encodings.Korean);
|
||||
packet.Save(ref writer);
|
||||
writer.Position = Math.Max(1, writer.Position);
|
||||
|
||||
// Send head.
|
||||
stream.WriteUInt16(_tagStart);
|
||||
stream.WriteByte(1);
|
||||
stream.WriteUInt16((ushort)writer.Position);
|
||||
stream.WriteByte(id.Value);
|
||||
|
||||
// Send data.
|
||||
stream.Write(writer.Span);
|
||||
|
||||
// Send tail.
|
||||
stream.WriteUInt16(_tagEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||
|
||||
private IPacket GetPacket(byte id)
|
||||
{
|
||||
Type packetType = _cachedClasses.Where(x => x.attrib.ID == id).FirstOrDefault().type;
|
||||
return packetType == null ? null : (IPacket)Activator.CreateInstance(packetType);
|
||||
}
|
||||
|
||||
private byte? GetID(IPacket packet)
|
||||
{
|
||||
Type packetType = packet.GetType();
|
||||
return _cachedClasses.Where(x => x.type == packetType).FirstOrDefault().attrib?.ID;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
{
|
||||
/// <summary>
|
||||
/// Decorates an <see cref="IPacket"/> child class with the ID of the OW server packet it represents.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
internal class OWServerPacketAttribute : Attribute
|
||||
{
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OWServerPacketAttribute"/> class, decorating a class with the
|
||||
/// ID of the packet it represents.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the packet which the decorated class represents.</param>
|
||||
internal OWServerPacketAttribute(ushort id)
|
||||
{
|
||||
ID = id;
|
||||
}
|
||||
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the packet the decorated class represents.
|
||||
/// </summary>
|
||||
internal ushort ID { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Syroot.BinaryData;
|
||||
using Syroot.BinaryData.Memory;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.Reflection;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the factory of packets in the OW server packet format.
|
||||
/// </summary>
|
||||
internal class OWServerPacketFormat : IPacketFormat
|
||||
{
|
||||
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
|
||||
|
||||
private const int _maxDataSize = 1024;
|
||||
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
private static readonly IList<(Type type, OWServerPacketAttribute attrib)> _cachedClasses
|
||||
= ReflectionTools.GetAttributedClasses<OWServerPacketAttribute>();
|
||||
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
private OWServerPacketFormat() { }
|
||||
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
||||
internal static OWServerPacketFormat Instance { get; } = new OWServerPacketFormat();
|
||||
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
public IPacket TryLoad(Stream stream)
|
||||
{
|
||||
// Read head.
|
||||
ushort id = stream.ReadUInt16();
|
||||
IPacket packet = GetPacket(id);
|
||||
if (packet == null)
|
||||
return null;
|
||||
int dataSize = stream.ReadUInt16();
|
||||
|
||||
// Read data.
|
||||
SpanReader reader = new SpanReader(stream.ReadBytes(dataSize), encoding: Encodings.Korean);
|
||||
|
||||
// Deserialize and return packet.
|
||||
packet.Load(ref reader);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public bool TrySave(Stream stream, IPacket packet)
|
||||
{
|
||||
ushort? id = GetID(packet);
|
||||
if (!id.HasValue)
|
||||
return false;
|
||||
|
||||
// Retrieve data.
|
||||
SpanWriter writer = new SpanWriter(new byte[_maxDataSize], encoding: Encodings.Korean);
|
||||
packet.Save(ref writer);
|
||||
|
||||
// Send head.
|
||||
stream.WriteUInt16(id.Value);
|
||||
stream.WriteUInt16((ushort)writer.Position);
|
||||
|
||||
// Write data.
|
||||
stream.Write(writer.Span);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||
|
||||
private IPacket GetPacket(ushort id)
|
||||
{
|
||||
Type packetType = _cachedClasses.Where(x => x.attrib.ID == id).FirstOrDefault().type;
|
||||
return packetType == null ? null : (IPacket)Activator.CreateInstance(packetType);
|
||||
}
|
||||
|
||||
private ushort? GetID(IPacket packet)
|
||||
{
|
||||
Type packetType = packet.GetType();
|
||||
return _cachedClasses.Where(x => x.type == packetType).FirstOrDefault().attrib?.ID;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,12 +2,12 @@
|
||||
using System.Net;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="ChannelEnterReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWServer, 0x8034)]
|
||||
[OWServerPacket(0x8034)]
|
||||
internal class ChannelEnterQuery : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
@ -2,12 +2,12 @@
|
||||
using System.Net;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="ChannelEnterQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWServer, 0x8035)]
|
||||
[OWServerPacket(0x8035)]
|
||||
internal class ChannelEnterReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
@ -5,12 +5,12 @@ using System.Net;
|
||||
using Syroot.BinaryData.Memory;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.IO;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an additional server response to a <see cref="LoginQuery"/>, providing available server channels.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWServer, 0x80C9)]
|
||||
[OWServerPacket(0x80C9)]
|
||||
internal class ChannelListReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="ConnectReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWServer, 0x800E)]
|
||||
[OWServerPacket(0x800E)]
|
||||
internal class ConnectQuery : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="ConnectQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWServer, 0x800F)]
|
||||
[OWServerPacket(0x800F)]
|
||||
internal class ConnectReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an additional server response to a <see cref="LoginQuery"/>, providing informational server
|
||||
/// screen text.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWServer, 0x8033)]
|
||||
internal class ServerInfoReply : IPacket
|
||||
[OWServerPacket(0x8033)]
|
||||
internal class InfoReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -3,12 +3,12 @@ using System.Net;
|
||||
using Syroot.BinaryData.Memory;
|
||||
using Syroot.Worms.Mgame.GameServer.Packets.Data;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="LoginReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWServer, 0x8000)]
|
||||
[OWServerPacket(0x8000)]
|
||||
internal class LoginQuery : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="LoginQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.OWServer, 0x8001)]
|
||||
[OWServerPacket(0x8001)]
|
||||
internal class LoginReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
@ -3,10 +3,10 @@
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
{
|
||||
/// <summary>
|
||||
/// Decorates a <see cref="IPacket"/> child class with the ID of the packet it represents.
|
||||
/// Decorates an <see cref="IPacket"/> child class with the ID of the packet it represents.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
internal class PacketAttribute : Attribute
|
||||
internal abstract class PacketAttribute : Attribute
|
||||
{
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
@ -15,9 +15,8 @@ namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
/// packet it represents.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the packet which the decorated class represents.</param>
|
||||
public PacketAttribute(PacketFormat format, int id)
|
||||
internal PacketAttribute(int id)
|
||||
{
|
||||
Format = format;
|
||||
ID = id;
|
||||
}
|
||||
|
||||
@ -26,24 +25,6 @@ namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
/// <summary>
|
||||
/// Gets the ID of the packet the decorated class represents.
|
||||
/// </summary>
|
||||
public int ID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the format in which the packet is serialized and sent over the network.
|
||||
/// </summary>
|
||||
public PacketFormat Format { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the known packet formats.
|
||||
/// </summary>
|
||||
internal enum PacketFormat
|
||||
{
|
||||
/// <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
|
||||
internal int ID { get; }
|
||||
}
|
||||
}
|
||||
|
138
src/Syroot.Worms.Mgame.GameServer/Packets/PacketConnection.cs
Normal file
138
src/Syroot.Worms.Mgame.GameServer/Packets/PacketConnection.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.IO;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.Net;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.Reflection;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a class capable of dispatching received <see cref="IPacket"/> instances to a corresponding method.
|
||||
/// </summary>
|
||||
internal class PacketConnection : IDisposable
|
||||
{
|
||||
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
|
||||
|
||||
private const int _maxCacheSize = 2048;
|
||||
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
private readonly MethodHandler<IPacket> _methodHandler;
|
||||
private readonly SafeNetworkStream _tcpStream;
|
||||
private readonly IPacketFormat[] _packetFormatPipe;
|
||||
private bool _disposed;
|
||||
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PacketConnection"/> class, handling the given
|
||||
/// <paramref name="tcpClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="tcpClient">The <see cref="System.Net.Sockets.TcpClient"/> to communicate with.</param>
|
||||
/// <param name="packetFormatPipe">The array of <see cref="IPacketFormat"/> instances which are called in order
|
||||
/// to handle a packet.</param>
|
||||
internal PacketConnection(TcpClient tcpClient, params IPacketFormat[] packetFormatPipe)
|
||||
{
|
||||
TcpClient = tcpClient;
|
||||
_packetFormatPipe = packetFormatPipe;
|
||||
_tcpStream = TcpClient.GetSafeStream();
|
||||
_methodHandler = new MethodHandler<IPacket>(this);
|
||||
}
|
||||
|
||||
// ---- EVENTS -------------------------------------------------------------------------------------------------
|
||||
|
||||
protected event EventHandler<IPacket> PrePacketHandle;
|
||||
protected event EventHandler<IPacket> PrePacketSend;
|
||||
protected event EventHandler<Exception> UnhandledException;
|
||||
|
||||
// ---- 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()
|
||||
{
|
||||
try
|
||||
{
|
||||
IPacket packet;
|
||||
while ((packet = ReceivePacket()) != null)
|
||||
{
|
||||
PrePacketHandle?.Invoke(this, packet);
|
||||
_methodHandler.Handle(packet);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnhandledException?.Invoke(this, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
TcpClient.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void SendPacket(IPacket packet)
|
||||
{
|
||||
PrePacketSend?.Invoke(this, packet);
|
||||
try
|
||||
{
|
||||
// Let each packet format try to serialize the data.
|
||||
foreach (IPacketFormat format in _packetFormatPipe)
|
||||
{
|
||||
if (format.TrySave(_tcpStream, packet))
|
||||
return;
|
||||
}
|
||||
throw new NotImplementedException("Cannot send unhandled packet format.");
|
||||
}
|
||||
catch (IOException) { } // A network error appeared, and communication should end.
|
||||
catch (ObjectDisposedException) { } // The underlying stream was most apparently closed.
|
||||
}
|
||||
|
||||
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||
|
||||
private IPacket ReceivePacket()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Let each packet format read from the stream without consuming data.
|
||||
using (var cachedStream = new CachedStream<SafeNetworkStream>(_tcpStream, _maxCacheSize, true))
|
||||
{
|
||||
foreach (IPacketFormat format in _packetFormatPipe)
|
||||
{
|
||||
IPacket packet = format.TryLoad(cachedStream);
|
||||
if (packet != null)
|
||||
return packet;
|
||||
cachedStream.MoveToCacheStart();
|
||||
}
|
||||
}
|
||||
throw new NotImplementedException("Cannot receive unhandled packet format.");
|
||||
}
|
||||
catch (IOException) { return null; } // The underlying socket closed or sent invalid data.
|
||||
catch (ObjectDisposedException) { return null; } // The underlying stream closed.
|
||||
}
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a factory creating <see cref="IPacket"/> instances by mapping their ID to types to instantiate.
|
||||
/// </summary>
|
||||
internal static class PacketFactory
|
||||
{
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
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 (_packetCache.ContainsKey(attrib))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{attrib.Format} packet with ID {attrib.ID} mapped to multiple classes.");
|
||||
}
|
||||
_packetCache.Add(attrib, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IPacket"/> instance mapped to the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="format">The <see cref="PacketFormat"/> to instantiate.</param>
|
||||
/// <param name="id">The ID of the packet class to instantiate.</param>
|
||||
/// <returns>The created <see cref="IPacket"/> instance.</returns>
|
||||
/// <exception cref="KeyNotFoundException">No class was mapped to the given packet ID.</exception>
|
||||
internal static IPacket Create(PacketFormat format, int id)
|
||||
{
|
||||
foreach (KeyValuePair<PacketAttribute, Type> packetMeta in _packetCache)
|
||||
{
|
||||
if (packetMeta.Key.ID == id)
|
||||
return (IPacket)Activator.CreateInstance(packetMeta.Value, true);
|
||||
}
|
||||
// No packet metadata matching.
|
||||
#if DEBUG
|
||||
return new RawPacket(format, id);
|
||||
#endif
|
||||
throw new ArgumentException($"{format} packet with ID {id} could not be created.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="PacketAttribute"/> metadata for the class of the given <paramref name="packet"/>.
|
||||
/// </summary>
|
||||
/// <param name="packet">The <see cref="IPacket"/> whose metadata will be returned.</param>
|
||||
/// <returns>The metadata of the packet class.</returns>
|
||||
internal static PacketAttribute GetAttribute(IPacket packet)
|
||||
{
|
||||
#if DEBUG
|
||||
if (packet is RawPacket rawPacket)
|
||||
return new PacketAttribute(rawPacket.Type, rawPacket.ID);
|
||||
else
|
||||
#endif
|
||||
return _packetCache.Where(x => x.Value == packet.GetType()).First().Key;
|
||||
}
|
||||
}
|
||||
}
|
29
src/Syroot.Worms.Mgame.GameServer/Packets/PacketFormat.cs
Normal file
29
src/Syroot.Worms.Mgame.GameServer/Packets/PacketFormat.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the implementation of a specific packet format which can instantiate corresponding classes.
|
||||
/// </summary>
|
||||
internal interface IPacketFormat
|
||||
{
|
||||
// ---- METHODS ------------------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="IPacket"/> instance read from the given <paramref name="stream"/> or
|
||||
/// <see langword="null"/> if the data was not compatible with this format.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/> to read data from.</param
|
||||
/// <returns>The instantiated <see cref="IPacket"/> or <see langword="null"/>.</returns>
|
||||
IPacket TryLoad(Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this format could save the <paramref name="packet"/> to the given
|
||||
/// <paramref name="stream"/>.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/> to write data to.</param>
|
||||
/// <param name="packet">The <see cref="IPacket"/> to serialize, if possibile.</param>
|
||||
/// <returns><see langword="true"/> if a packet was serialized; otherwise, <see langword="false"/>.</returns>
|
||||
bool TrySave(Stream stream, IPacket packet);
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#if DEBUG
|
||||
using Syroot.BinaryData.Memory;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a special fallback packet which simply stores any ID and the raw packet data.
|
||||
/// </summary>
|
||||
internal class RawPacket : IPacket
|
||||
{
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
internal RawPacket(PacketFormat type, int id, params byte[] data)
|
||||
{
|
||||
Type = type;
|
||||
ID = id;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
||||
internal PacketFormat Type { get; }
|
||||
|
||||
internal int ID { get; }
|
||||
|
||||
internal byte[] Data { get; private set; }
|
||||
|
||||
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
|
||||
|
||||
public void Load(ref SpanReader reader) => Data = reader.ReadBytes(reader.Length);
|
||||
|
||||
public void Save(ref SpanWriter writer) => writer.WriteBytes(Data);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="CmdReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x10A)]
|
||||
[WwpaPacket(0x10A)]
|
||||
internal class CmdQuery : CmdPacket
|
||||
{
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="CmdQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x10B)]
|
||||
[WwpaPacket(0x10B)]
|
||||
internal class CmdReply : CmdPacket
|
||||
{
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
@ -5,7 +5,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the client notice of disconnecting.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x105)]
|
||||
[WwpaPacket(0x105)]
|
||||
internal class DisconnectNotice : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -10,7 +10,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="LoginReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x101)]
|
||||
[WwpaPacket(0x101)]
|
||||
internal class LoginQuery : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="LoginQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x102)]
|
||||
[WwpaPacket(0x102)]
|
||||
internal class LoginReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -5,7 +5,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="LogoutReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x103)]
|
||||
[WwpaPacket(0x103)]
|
||||
internal class LogoutQuery : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="LogoutQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x104)]
|
||||
[WwpaPacket(0x104)]
|
||||
internal class LogoutReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -7,7 +7,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// Invalid description: "Represents the server response to a <see cref="LoginQuery"/>."
|
||||
/// </summary>
|
||||
[Obsolete("While this packet exists, it is currently unclear where it is used.")]
|
||||
[Packet(PacketFormat.Wwpa, 0x145)]
|
||||
[WwpaPacket(0x145)]
|
||||
internal class NotLoginReply : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -5,7 +5,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="Unknown11FReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x11E)]
|
||||
[WwpaPacket(0x11E)]
|
||||
internal class Unknown11EQuery : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="Unknown11EQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x11F)]
|
||||
[WwpaPacket(0x11F)]
|
||||
internal class Unknown11FReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -5,7 +5,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="Unknown15BReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x15A)]
|
||||
[WwpaPacket(0x15A)]
|
||||
internal class Unknown15AQuery : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="Unknown15AQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x15B)]
|
||||
[WwpaPacket(0x15B)]
|
||||
internal class Unknown15BReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -0,0 +1,29 @@
|
||||
using Syroot.BinaryData.Memory;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.IO;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a fallback WWPA packet.
|
||||
/// </summary>
|
||||
public class RawPacket : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
||||
public int ID { get; set; }
|
||||
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
public void Load(ref SpanReader reader)
|
||||
{
|
||||
Data = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
public void Save(ref SpanWriter writer)
|
||||
{
|
||||
writer.WriteBytes(Data);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="ChannelListReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x801E)]
|
||||
[WwpaPacket(0x801E)]
|
||||
internal class ChannelListQuery : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -10,7 +10,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the server reply to a <see cref="ChannelListQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x801F)]
|
||||
[WwpaPacket(0x801F)]
|
||||
internal class ChannelListReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -5,7 +5,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="ConnectReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x8001)]
|
||||
[WwpaPacket(0x8001)]
|
||||
internal class ConnectQuery : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="ConnectQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x8002)]
|
||||
[WwpaPacket(0x8002)]
|
||||
internal class ConnectReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -5,7 +5,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the client notice of disconnecting.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x801B)]
|
||||
[WwpaPacket(0x801B)]
|
||||
internal class DisconnectNotice : IPacket
|
||||
{
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
@ -8,7 +8,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the client request for a <see cref="LoginReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x8019)]
|
||||
[WwpaPacket(0x8019)]
|
||||
internal class LoginQuery : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="LoginQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x801A)]
|
||||
[WwpaPacket(0x801A)]
|
||||
internal class LoginReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the client request for an <see cref="OptionsReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x8046)]
|
||||
[WwpaPacket(0x8046)]
|
||||
internal class OptionsQuery : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -7,7 +7,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the server response to a <see cref="OptionsQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x8047)]
|
||||
[WwpaPacket(0x8047)]
|
||||
internal class OptionsReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -7,7 +7,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the client request for an <see cref="OptionsSaveReply"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x8048)]
|
||||
[WwpaPacket(0x8048)]
|
||||
internal class OptionsSaveQuery : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Server
|
||||
/// <summary>
|
||||
/// Represents the server response to an <see cref="OptionsSaveQuery"/>.
|
||||
/// </summary>
|
||||
[Packet(PacketFormat.Wwpa, 0x8049)]
|
||||
[WwpaPacket(0x8049)]
|
||||
internal class OptionsSaveReply : IPacket
|
||||
{
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
{
|
||||
/// <summary>
|
||||
/// Decorates an <see cref="IPacket"/> child class with the ID of the OW server packet it represents.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
internal class WwpaPacketAttribute : Attribute
|
||||
{
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WwpaPacketAttribute"/> class, decorating a class with the ID of
|
||||
/// the packet it represents.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the packet which the decorated class represents.</param>
|
||||
internal WwpaPacketAttribute(int id)
|
||||
{
|
||||
ID = id;
|
||||
}
|
||||
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the packet the decorated class represents.
|
||||
/// </summary>
|
||||
internal int ID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional command number which the packet represents.
|
||||
/// </summary>
|
||||
internal int Command { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Syroot.BinaryData;
|
||||
using Syroot.BinaryData.Memory;
|
||||
using Syroot.Worms.Mgame.GameServer.Core.Reflection;
|
||||
|
||||
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the implementation of a specific packet format which can instantiate corresponding classes.
|
||||
/// </summary>
|
||||
internal class WwpaPacketFormat : IPacketFormat
|
||||
{
|
||||
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
|
||||
|
||||
private const ushort _tagStart = 0x2E9E;
|
||||
private const ushort _tagEnd = 0x7F3F;
|
||||
private const int _maxDataSize = 1024;
|
||||
|
||||
// ---- FIELDS -------------------------------------------------------------------------------------------------
|
||||
|
||||
private static readonly IList<(Type type, WwpaPacketAttribute attrib)> _cachedClasses
|
||||
= ReflectionTools.GetAttributedClasses<WwpaPacketAttribute>();
|
||||
|
||||
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
|
||||
|
||||
private WwpaPacketFormat() { }
|
||||
|
||||
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
|
||||
|
||||
internal static WwpaPacketFormat Instance { get; } = new WwpaPacketFormat();
|
||||
|
||||
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
|
||||
|
||||
public IPacket TryLoad(Stream stream)
|
||||
{
|
||||
// Read head.
|
||||
if (stream.ReadUInt16() != _tagStart
|
||||
|| !stream.ReadBoolean() // bHead2, always true
|
||||
|| stream.ReadBoolean() // bHead3, always false
|
||||
|| !stream.ReadBoolean()) // bCompressed, always true
|
||||
return null;
|
||||
int decompressedSize = stream.ReadUInt16();
|
||||
int compressedSize = stream.ReadInt32();
|
||||
int idxPacket = stream.ReadInt32();
|
||||
|
||||
// Read data.
|
||||
byte[] compressedData = stream.ReadBytes(compressedSize);
|
||||
|
||||
// Read tail.
|
||||
if (stream.ReadInt32() != idxPacket || stream.ReadUInt16() != _tagEnd)
|
||||
return null;
|
||||
|
||||
// Instantiate, deserialize, and return packet.
|
||||
SpanReader reader = new SpanReader(PacketCompression.Decompress(compressedData), encoding: Encodings.Korean);
|
||||
int id = reader.ReadInt32();
|
||||
IPacket packet = GetPacket(id);
|
||||
if (packet == null)
|
||||
return null;
|
||||
SpanReader dataReader = reader.Slice();
|
||||
packet.Load(ref dataReader);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public bool TrySave(Stream stream, IPacket packet)
|
||||
{
|
||||
int? id = GetID(packet);
|
||||
if (!id.HasValue)
|
||||
return false;
|
||||
|
||||
// Retrieve (decompressed) data.
|
||||
SpanWriter writer = new SpanWriter(new byte[_maxDataSize], encoding: Encodings.Korean);
|
||||
writer.WriteInt32(id.Value);
|
||||
SpanWriter dataWriter = writer.Slice();
|
||||
packet.Save(ref dataWriter);
|
||||
writer.Position += dataWriter.Position;
|
||||
|
||||
// Send head and data.
|
||||
stream.WriteUInt16(_tagStart);
|
||||
stream.WriteBoolean(true); // bHead2 (unknown)
|
||||
stream.WriteBoolean(false); // bHead3 (unknown)
|
||||
bool compress = false; // TODO: Does not get accepted by client if compressed.
|
||||
stream.WriteBoolean(compress);
|
||||
if (compress)
|
||||
{
|
||||
ReadOnlySpan<byte> compressedData = PacketCompression.Compress(writer.Span);
|
||||
stream.WriteUInt16((ushort)writer.Position); // decompressed size
|
||||
stream.WriteUInt32((uint)compressedData.Length); // compressed size
|
||||
stream.WriteInt32(1); // index
|
||||
stream.Write(compressedData);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteUInt16(0); // unused
|
||||
stream.WriteUInt32((uint)writer.Position); // raw size
|
||||
stream.WriteInt32(1); // index
|
||||
stream.Write(writer.Span.Slice(0, writer.Position));
|
||||
}
|
||||
|
||||
// Send tail.
|
||||
stream.WriteInt32(1); // index (must equal other)
|
||||
stream.WriteUInt16(_tagEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
|
||||
|
||||
private IPacket GetPacket(int id)
|
||||
{
|
||||
Type packetType = _cachedClasses.Where(x => x.attrib.ID == id).FirstOrDefault().type;
|
||||
return packetType == null
|
||||
? new RawPacket { ID = id }
|
||||
: (IPacket)Activator.CreateInstance(packetType);
|
||||
}
|
||||
|
||||
private int? GetID(IPacket packet)
|
||||
{
|
||||
Type packetType = packet.GetType();
|
||||
return _cachedClasses.Where(x => x.type == packetType).FirstOrDefault().attrib?.ID;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user