Dynamically implement channel commands.

This commit is contained in:
Ray Koopa 2019-01-22 01:23:20 +01:00
parent a8073367f4
commit 38035742d6
12 changed files with 70 additions and 199 deletions

View File

@ -7,17 +7,30 @@ namespace Syroot.Worms.Mgame.GameServer
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public void HandleWwpaChannelCmd(CmdQuery packet)
public void HandleWwpaChannelCmd(CmdStartSingleGameQuery packet)
{
switch (packet.Data)
SendPacket(new CmdStartSingleGameReply
{
case StartSingleGameQueryData startSingleGame:
HandleWwpaChannelCmdStartGame(startSingleGame);
break;
case Unknown6QueryData unknown6:
HandleWwpaChannelCmdUnknown6(unknown6);
break;
}
Stuff = new List<StartSingleGameReplyStuff>
{
new StartSingleGameReplyStuff
{
UnknownA = 1,
UnknownB = 2,
UnknownC = 3,
UnknownD = 4,
UnknownE = 5
}
}
});
}
public void HandleWwpaChannelCmd(CmdUnknown6Query packet)
{
SendPacket(new CmdUnknown6Reply
{
Result = Unknown6Status.Disconnect
});
}
public void HandleWwpaChannelDisconnect(DisconnectNotice packet) { }
@ -56,35 +69,5 @@ namespace Syroot.Worms.Mgame.GameServer
UnknownF = 0x55,
});
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
// ---- Cmds ----
private void HandleWwpaChannelCmdStartGame(StartSingleGameQueryData data)
{
SendPacket(new CmdReply(new StartSingleGameReplyData
{
Stuff = new List<StartSingleGameReplyStuff>
{
new StartSingleGameReplyStuff
{
UnknownA = 1,
UnknownB = 2,
UnknownC = 3,
UnknownD = 4,
UnknownE = 5
}
}
}));
}
private void HandleWwpaChannelCmdUnknown6(Unknown6QueryData unknown6)
{
SendPacket(new CmdReply(new Unknown6ReplyData
{
Result = Unknown6Status.Disconnect
}));
}
}
}

View File

@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
/// 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
public class OWChannelPacketAttribute : Attribute
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -15,7 +15,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
/// 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)
public OWChannelPacketAttribute(byte id)
{
ID = id;
}
@ -25,6 +25,6 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
/// <summary>
/// Gets the ID of the packet the decorated class represents.
/// </summary>
internal byte ID { get; }
public byte ID { get; }
}
}

View File

@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
/// 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
public class OWServerPacketAttribute : Attribute
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -15,7 +15,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
/// 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)
public OWServerPacketAttribute(ushort id)
{
ID = id;
}
@ -25,6 +25,6 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.OnlineWorms
/// <summary>
/// Gets the ID of the packet the decorated class represents.
/// </summary>
internal ushort ID { get; }
public ushort ID { get; }
}
}

View File

@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Syroot.BinaryData.Memory;
using Syroot.Worms.Mgame.GameServer.Core.IO;
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
{
// TODO: Ugly, but requires bigger redesign to allow a second identifier in the packets.
/// <summary>
/// Represents the base class for <see cref="CmdQuery"/> and <see cref="CmdReply"/> packets.
/// </summary>
internal abstract class CmdPacket : IPacket
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public ICmdData Data { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public void Load(ref SpanReader reader)
{
Cmd cmd = reader.ReadEnum<Cmd>();
Data = GetDataClasses().TryGetValue(cmd, out Type type)
? (ICmdData)Activator.CreateInstance(type)
: new RawCmdData(cmd);
Data.Load(ref reader);
}
public void Save(ref SpanWriter writer)
{
Cmd cmd;
switch (Data)
{
case RawCmdData rawChannelCmdData:
cmd = rawChannelCmdData.Cmd;
break;
default:
Type dataType = Data.GetType();
cmd = GetDataClasses().First(x => x.Value == dataType).Key;
break;
}
writer.WriteEnum(cmd);
Data.Save(ref writer);
}
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
protected abstract IDictionary<Cmd, Type> GetDataClasses();
}
internal enum Cmd : int
{
StartSingleGameQuery = 5,
Unknown6 = 6
}
internal interface ICmdData
{
void Load(ref SpanReader reader);
void Save(ref SpanWriter writer);
}
internal class RawCmdData : ICmdData
{
public RawCmdData(Cmd cmd) => Cmd = cmd;
internal Cmd Cmd { get; }
internal byte[] Data { get; set; }
public void Load(ref SpanReader reader) => Data = reader.ReadToEnd();
public void Save(ref SpanWriter writer) => writer.WriteBytes(Data);
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
{
/// <summary>
/// Represents the client request for a <see cref="CmdReply"/>.
/// </summary>
[WwpaPacket(0x10A)]
internal class CmdQuery : CmdPacket
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private static readonly Dictionary<Cmd, Type> _queryClasses = new Dictionary<Cmd, Type>
{
[Cmd.StartSingleGameQuery] = typeof(StartSingleGameQueryData),
[Cmd.Unknown6] = typeof(Unknown6QueryData)
};
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
protected override IDictionary<Cmd, Type> GetDataClasses() => _queryClasses;
}
}

View File

@ -1,31 +0,0 @@
using System;
using System.Collections.Generic;
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
{
/// <summary>
/// Represents the server response to a <see cref="CmdQuery"/>.
/// </summary>
[WwpaPacket(0x10B)]
internal class CmdReply : CmdPacket
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private static readonly Dictionary<Cmd, Type> _replyClasses = new Dictionary<Cmd, Type>
{
[Cmd.StartSingleGameQuery] = typeof(StartSingleGameReplyData),
[Cmd.Unknown6] = typeof(Unknown6ReplyData)
};
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
internal CmdReply(ICmdData data)
{
Data = data;
}
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
protected override IDictionary<Cmd, Type> GetDataClasses() => _replyClasses;
}
}

View File

@ -3,9 +3,10 @@
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
{
/// <summary>
/// Represents the <see cref="CmdQuery"/> client request for a <see cref="StartSingleGameReplyData"/>.
/// Represents the <see cref="CmdQuery"/> client request for a <see cref="CmdStartSingleGameReply"/>.
/// </summary>
internal class StartSingleGameQueryData : ICmdData
[WwpaPacket(0x10A, Command = 5)]
internal class CmdStartSingleGameQuery : IPacket
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------

View File

@ -5,9 +5,10 @@ using Syroot.BinaryData.Memory;
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
{
/// <summary>
/// Represents the <see cref="CmdQuery"/> server response to a <see cref="StartSingleGameQueryData"/>.
/// Represents the <see cref="CmdQuery"/> server response to a <see cref="CmdStartSingleGameQuery"/>.
/// </summary>
internal class StartSingleGameReplyData : ICmdData
[WwpaPacket(0x10B, Command = 5)]
internal class CmdStartSingleGameReply : IPacket
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------

View File

@ -4,9 +4,10 @@ using Syroot.BinaryData.Memory;
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
{
/// <summary>
/// Represents the <see cref="ChannelCmdQuery"/> client request for a <see cref="Unknown6ReplyData"/>.
/// Represents the client request for a <see cref="CmdUnknown6Reply"/>.
/// </summary>
internal class Unknown6QueryData : ICmdData
[WwpaPacket(0x10A, Command = 6)]
internal class CmdUnknown6Query : IPacket
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------

View File

@ -4,9 +4,10 @@ using Syroot.BinaryData.Memory;
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua.Channel
{
/// <summary>
/// Represents the <see cref="CmdQuery"/> server response to a <see cref="Unknown6QueryData"/>.
/// Represents the server response to a <see cref="CmdUnknown6Query"/>.
/// </summary>
internal class Unknown6ReplyData : ICmdData
[WwpaPacket(0x10B, Command = 6)]
internal class CmdUnknown6Reply : IPacket
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------

View File

@ -6,7 +6,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
/// 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
public class WwpaPacketAttribute : Attribute
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -15,7 +15,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
/// the packet it represents.
/// </summary>
/// <param name="id">The ID of the packet which the decorated class represents.</param>
internal WwpaPacketAttribute(int id)
public WwpaPacketAttribute(int id)
{
ID = id;
}
@ -25,11 +25,11 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
/// <summary>
/// Gets the ID of the packet the decorated class represents.
/// </summary>
internal int ID { get; }
public int ID { get; }
/// <summary>
/// Gets or sets an optional command number which the packet represents.
/// </summary>
internal int Command { get; set; }
public int Command { get; set; }
}
}

View File

@ -17,6 +17,8 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
private const ushort _tagStart = 0x2E9E;
private const ushort _tagEnd = 0x7F3F;
private const int _channelCmdQueryID = 0x10A;
private const int _channelCmdReplyID = 0x10B;
private const int _maxDataSize = 1024;
// ---- FIELDS -------------------------------------------------------------------------------------------------
@ -55,8 +57,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
// Instantiate, deserialize, and return packet.
SpanReader reader = new SpanReader(PacketCompression.Decompress(compressedData), encoding: Encodings.Korean);
int id = reader.ReadInt32();
IPacket packet = GetPacket(id);
IPacket packet = GetPacket(ref reader);
if (packet == null)
return null;
SpanReader dataReader = reader.Slice();
@ -66,13 +67,15 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
public bool TrySave(Stream stream, IPacket packet)
{
int? id = GetID(packet);
if (!id.HasValue)
WwpaPacketAttribute attrib = GetAttribute(packet);
if (attrib == null)
return false;
// Retrieve (decompressed) data.
SpanWriter writer = new SpanWriter(new byte[_maxDataSize], encoding: Encodings.Korean);
writer.WriteInt32(id.Value);
writer.WriteInt32(attrib.ID);
if (attrib.Command != default)
writer.WriteInt32(attrib.Command);
SpanWriter dataWriter = writer.Slice();
packet.Save(ref dataWriter);
writer.Position += dataWriter.Position;
@ -107,18 +110,29 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private IPacket GetPacket(int id)
private IPacket GetPacket(ref SpanReader reader)
{
Type packetType = _cachedClasses.Where(x => x.attrib.ID == id).FirstOrDefault().type;
return packetType == null
? new RawPacket { ID = id }
: (IPacket)Activator.CreateInstance(packetType);
// Try to find a unique packet via ID only.
int id = reader.ReadInt32();
var packets = _cachedClasses.Where(x => x.attrib.ID == id).ToList();
if (packets.Count == 1)
return (IPacket)Activator.CreateInstance(packets[0].type);
// No unique packet found, check for matching command.
int command = reader.ReadInt32();
packets = packets.Where(x => x.attrib.Command == command).ToList();
if (packets.Count == 1)
return (IPacket)Activator.CreateInstance(packets[0].type);
// No unique packet found, return fallback.
reader.Position -= sizeof(int);
return new RawPacket { ID = id };
}
private int? GetID(IPacket packet)
private WwpaPacketAttribute GetAttribute(IPacket packet)
{
Type packetType = packet.GetType();
return _cachedClasses.Where(x => x.type == packetType).FirstOrDefault().attrib?.ID;
return _cachedClasses.Where(x => x.type == packetType).FirstOrDefault().attrib;
}
}
}