From 7e801057acca93a9f298a6d79683b12ad221cd60 Mon Sep 17 00:00:00 2001 From: Ray Koopa Date: Fri, 4 Jan 2019 03:21:28 +0100 Subject: [PATCH] Support channel packet format. --- src/Syroot.Worms.OnlineWorms.Server/Client.cs | 14 +++-- .../Net/GameConnection.cs | 55 ++++++++++++++--- .../Net/PacketAttribute.cs | 15 ++++- .../Net/PacketFactory.cs | 42 +++++++------ .../Net/PacketStream.cs | 16 +++++ .../Net/Packets/ChannelConnectQuery.cs | 59 +++++++++++++++++++ .../Net/Packets/ChannelEnterQuery.cs | 2 +- .../Net/Packets/ChannelEnterReply.cs | 2 +- .../Net/Packets/ChannelInfosReply.cs | 21 +++++-- .../Net/Packets/ConnectQuery.cs | 2 +- .../Net/Packets/ConnectReply.cs | 2 +- .../Net/Packets/LoginQuery.cs | 2 +- .../Net/Packets/LoginReply.cs | 2 +- .../Net/Packets/RawQuery.cs | 25 ++++---- .../Net/Packets/ServerInfoReply.cs | 2 +- 15 files changed, 201 insertions(+), 60 deletions(-) create mode 100644 src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelConnectQuery.cs diff --git a/src/Syroot.Worms.OnlineWorms.Server/Client.cs b/src/Syroot.Worms.OnlineWorms.Server/Client.cs index 386a43e..dcb4deb 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Client.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Client.cs @@ -74,7 +74,7 @@ namespace Syroot.Worms.OnlineWorms.Server EndPoint = new IPEndPoint(IPAddress.Loopback, 1), Type = ChannelType.Normal, Color = Color.LightGreen, - UserLoad = 5 + Load = ChannelLoad.Highest }, new ChannelInfo { @@ -87,7 +87,7 @@ namespace Syroot.Worms.OnlineWorms.Server Name = "Boredom Time", EndPoint = new IPEndPoint(IPAddress.Loopback, 3), Type = ChannelType.Normal, - UserLoad = 3 + Load = ChannelLoad.Medium }, new ChannelInfo { @@ -96,7 +96,7 @@ namespace Syroot.Worms.OnlineWorms.Server Type = ChannelType.Roping, Color = Color.Orange, Coins = 2, - UserLoad = 6 + Load = ChannelLoad.Closed }, new ChannelInfo { @@ -105,7 +105,7 @@ namespace Syroot.Worms.OnlineWorms.Server Type = ChannelType.Roping, Color = Color.Orange, Coins = 1, - UserLoad = 5 + Load = ChannelLoad.Full }, new ChannelInfo { @@ -126,8 +126,12 @@ namespace Syroot.Worms.OnlineWorms.Server }); } + public void HandleChannelConnect(ChannelConnectQuery packet) + { + } + #if DEBUG - public void HandleRaw(RawQuery packet) { } + public void HandleRaw(RawPacket packet) { } #endif // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/GameConnection.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/GameConnection.cs index 4f24b49..f9ac5cb 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/GameConnection.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/GameConnection.cs @@ -16,6 +16,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net // ---- CONSTANTS ---------------------------------------------------------------------------------------------- private const int _maxDataSize = 2048; + private const ushort _channelPacketStartTag = 0xFFFE; + private const byte _channelPacketStartTag2 = 1; + private const ushort _channelPacketEndTag = 0xFEFF; // ---- FIELDS ------------------------------------------------------------------------------------------------- @@ -123,19 +126,39 @@ namespace Syroot.Worms.OnlineWorms.Server.Net private Packet ReceivePacket() { // Receive the raw packet data. - ushort id; + PacketType type; + int id; ushort dataSize; try { - id = _receiveStream.ReadUInt16(); - dataSize = _receiveStream.ReadUInt16(); - _receiveStream.ReadAll(_receiveBuffer, 0, dataSize); + // Handle both server and channel specific packet formats. + ushort tag = _receiveStream.ReadUInt16(); + if (tag == _channelPacketStartTag) + { + type = PacketType.Channel; + if (_receiveStream.Read1Byte() != 1) + throw new IOException("Invalid channel packet start tag."); + dataSize = _receiveStream.ReadUInt16(); + id = _receiveStream.Read1Byte(); + _receiveStream.ReadAll(_receiveBuffer, 0, dataSize - sizeof(byte)); + _ = _receiveStream.Read1Byte(); // ? + ushort endTag = _receiveStream.ReadUInt16(); + if (endTag != _channelPacketEndTag) + throw new IOException("Invalid channel packet end tag."); + } + else + { + type = PacketType.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(id); + Packet packet = PacketFactory.Create(type, id); using (PacketStream readStream = new PacketStream(new MemoryStream(_receiveBuffer, 0, dataSize, false))) packet.Deserialize(readStream); return packet; @@ -153,9 +176,25 @@ namespace Syroot.Worms.OnlineWorms.Server.Net // Send the data and return success. try { - _receiveStream.WriteUInt16(PacketFactory.GetID(packet)); - _receiveStream.WriteUInt16(dataSize); - _receiveStream.Write(_sendDataBuffer, 0, dataSize); + PacketAttribute attribute = PacketFactory.GetAttribute(packet); + switch (attribute.PacketType) + { + case PacketType.Channel: + _receiveStream.WriteUInt16(_channelPacketStartTag); + _receiveStream.WriteByte(1); + _receiveStream.WriteUInt16(dataSize); + _receiveStream.Write(_sendDataBuffer, 0, dataSize); + _receiveStream.WriteByte(0); // ? + _receiveStream.Write(_channelPacketEndTag); + break; + case PacketType.Server: + _receiveStream.WriteUInt16((ushort)attribute.PacketID); + _receiveStream.WriteUInt16(dataSize); + _receiveStream.Write(_sendDataBuffer, 0, dataSize); + break; + default: + throw new InvalidOperationException("Cannot send unknown packet type."); + } return true; } catch (IOException) { return false; } // A network error appeared, and communication should end. diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/PacketAttribute.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/PacketAttribute.cs index 0785227..1447d34 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/PacketAttribute.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/PacketAttribute.cs @@ -15,9 +15,10 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// packet it represents. /// /// The ID of the packet which the decorated class represents. - public PacketAttribute(ushort id) + public PacketAttribute(PacketType type, int id) { - ID = id; + PacketType = type; + PacketID = id; } // ---- PROPERTIES --------------------------------------------------------------------------------------------- @@ -25,6 +26,14 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// /// Gets the ID of the packet the decorated class represents. /// - public ushort ID { get; } + public int PacketID { get; } + + public PacketType PacketType { get; } + } + + internal enum PacketType + { + Server, + Channel } } diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/PacketFactory.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/PacketFactory.cs index cb69057..64bc670 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/PacketFactory.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/PacketFactory.cs @@ -12,7 +12,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net { // ---- FIELDS ------------------------------------------------------------------------------------------------- - private static readonly Dictionary _packetTypes = new Dictionary(); + private static readonly Dictionary _packetMetas = new Dictionary(); // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ @@ -21,11 +21,14 @@ namespace Syroot.Worms.OnlineWorms.Server.Net // Cache the ID-to-packet-class map. foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()) { - foreach (PacketAttribute packetAttribute in type.GetCustomAttributes()) + foreach (PacketAttribute attrib in type.GetCustomAttributes()) { - if (_packetTypes.ContainsKey(packetAttribute.ID)) - throw new InvalidOperationException($"Packet {packetAttribute.ID} mapped to multiple classes."); - _packetTypes.Add(packetAttribute.ID, type); + if (_packetMetas.ContainsKey(attrib)) + { + throw new InvalidOperationException( + $"{attrib.PacketType} packet with ID {attrib.PacketID} mapped to multiple classes."); + } + _packetMetas.Add(attrib, type); } } } @@ -38,30 +41,33 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// The ID of the packet class to instantiate. /// The created instance. /// No class was mapped to the given packet ID. - internal static Packet Create(ushort id) + internal static Packet Create(PacketType type, int id) { + foreach (KeyValuePair packetMeta in _packetMetas) + { + if (packetMeta.Key.PacketType == type && packetMeta.Key.PacketID == id) + return (Packet)Activator.CreateInstance(packetMeta.Value, true); + } + // No packet metadata matching. #if DEBUG - return _packetTypes.TryGetValue(id, out Type type) - ? (Packet)Activator.CreateInstance(type, true) - : new RawQuery(id); -#else - return (Packet)Activator.CreateInstance(_packetTypes[id], true); + return new RawPacket(type, id); #endif + throw new ArgumentException($"{type} packet with ID {id} could not be created."); } /// - /// Gets the ID for the class of the given . + /// Gets the metadata for the class of the given . /// - /// The packet, whose class ID will be returned. - /// The ID of the class. - internal static ushort GetID(Packet packet) + /// The whose metadata will be returned. + /// The metadata of the packet class. + internal static PacketAttribute GetAttribute(Packet packet) { #if DEBUG - if (packet is RawQuery rawPacket) - return rawPacket.ID; + if (packet is RawPacket rawPacket) + return new PacketAttribute(rawPacket.Type, rawPacket.ID); else #endif - return _packetTypes.Where(x => x.Value == packet.GetType()).First().Key; + return _packetMetas.Where(x => x.Value == packet.GetType()).First().Key; } } } diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/PacketStream.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/PacketStream.cs index 2ef13b6..d886d27 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/PacketStream.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/PacketStream.cs @@ -91,6 +91,15 @@ namespace Syroot.Worms.OnlineWorms.Server.Net } } + internal string ReadString(int bufferSize) + { + // Ensure to not try to decode any bytes after the 0 termination. + byte[] bytes = _baseStream.ReadBytes(bufferSize); + int length = bufferSize; + while (bytes[--length] == 0 && length > 0) { } + return _win949Encoding.GetString(bytes, 0, length + 1); + } + /// /// Reads a 2-byte length-prefixed, Windows-949 enoded string. /// @@ -121,6 +130,13 @@ namespace Syroot.Worms.OnlineWorms.Server.Net _baseStream.WriteByte(0); } + internal void WriteString(string value, int bufferSize) + { + byte[] bytes = new byte[bufferSize]; + _win949Encoding.GetBytes(value, 0, value.Length, bytes, 0); + _baseStream.WriteBytes(bytes); + } + /// /// Writes a 2-byte length-prefixed, Windows-949 encoded string. /// diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelConnectQuery.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelConnectQuery.cs new file mode 100644 index 0000000..17fcdcf --- /dev/null +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelConnectQuery.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Syroot.BinaryData; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents the client request for a . + /// + [Packet(PacketType.Channel, 0x10)] + internal class ChannelConnectQuery : Packet + { + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + public IList Players { get; set; } + + public IPAddress ClientIP { get; set; } + + public ushort ClientVersion { get; set; } + + public ushort UnknownA { get; set; } // Always 1? + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + internal override void Deserialize(PacketStream stream) + { + Players = new List + { + new ChannelConnectPlayer + { + ID = stream.ReadString(12), + Password = stream.ReadString(12) + } + }; + ClientIP = IPAddress.Parse(stream.ReadString(15)); + ClientVersion = stream.ReadUInt16(); + UnknownA = stream.ReadUInt16(); + + ushort additionalPlayers = stream.ReadUInt16(); + for (int i = 0; i < additionalPlayers; i++) + { + Players.Add(new ChannelConnectPlayer + { + ID = stream.ReadString(12), + Password = stream.ReadString(12) + }); + } + } + + internal override void Serialize(PacketStream stream) => throw new NotImplementedException(); + } + + internal class ChannelConnectPlayer + { + public string ID { get; set; } // Max. 12 characters. + public string Password { get; set; } // Max. 12 characters. + } +} diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelEnterQuery.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelEnterQuery.cs index 49604e7..274923c 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelEnterQuery.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelEnterQuery.cs @@ -7,7 +7,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// /// Represents the client request for a . /// - [Packet(0x8034)] + [Packet(PacketType.Server, 0x8034)] internal class ChannelEnterQuery : Packet { // ---- PROPERTIES --------------------------------------------------------------------------------------------- diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelEnterReply.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelEnterReply.cs index 7f7629e..cb538da 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelEnterReply.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelEnterReply.cs @@ -7,7 +7,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// /// Represents the server response to a . /// - [Packet(0x8035)] + [Packet(PacketType.Server, 0x8035)] internal class ChannelEnterReply : Packet { // ---- PROPERTIES --------------------------------------------------------------------------------------------- diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelInfosReply.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelInfosReply.cs index 5d4d4a5..f3038b4 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelInfosReply.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ChannelInfosReply.cs @@ -9,7 +9,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// /// Represents an additional server response to a , providing available server channels. /// - [Packet(0x80C9)] + [Packet(PacketType.Server, 0x80C9)] internal class ChannelInfosReply : Packet { // ---- PROPERTIES --------------------------------------------------------------------------------------------- @@ -35,7 +35,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net stream.WriteBytes(channel.EndPoint.Address.GetAddressBytes()); stream.WriteUInt16((ushort)channel.EndPoint.Port); - stream.WriteByte(channel.UserLoad); + stream.WriteEnum(channel.Load); } } } @@ -68,10 +68,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net internal Color Color { get; set; } = Color.White; /// - /// Gets or sets an indicator of the number of players in this channel. 0 hides the channel, and 6 represents a - /// full channel. + /// Gets or sets an indicator or state visualizing the number of players in this channel. /// - internal byte UserLoad { get; set; } = 1; + internal ChannelLoad Load { get; set; } = ChannelLoad.Lowest; } internal enum ChannelType : byte @@ -80,4 +79,16 @@ namespace Syroot.Worms.OnlineWorms.Server.Net Roping = 1, Special = 2 } + + internal enum ChannelLoad : byte + { + Hidden, + Lowest, + Low, + Medium, + High, + Highest, + Full, + Closed = 99 + } } diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ConnectQuery.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ConnectQuery.cs index 91bd18f..851b711 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ConnectQuery.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ConnectQuery.cs @@ -5,7 +5,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// /// Represents the client request for a . /// - [Packet(0x800E)] + [Packet(PacketType.Server, 0x800E)] internal class ConnectQuery : Packet { // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ConnectReply.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ConnectReply.cs index e510f2d..91f4646 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ConnectReply.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ConnectReply.cs @@ -6,7 +6,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// /// Represents the server response to a . /// - [Packet(0x800F)] + [Packet(PacketType.Server, 0x800F)] internal class ConnectReply : Packet { // ---- PROPERTIES --------------------------------------------------------------------------------------------- diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/LoginQuery.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/LoginQuery.cs index 2029cc6..fd7d397 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/LoginQuery.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/LoginQuery.cs @@ -7,7 +7,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// /// Represents the client request for a . /// - [Packet(0x8000)] + [Packet(PacketType.Server, 0x8000)] internal class LoginQuery : Packet { // ---- PROPERTIES --------------------------------------------------------------------------------------------- diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/LoginReply.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/LoginReply.cs index a128406..e9dd26f 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/LoginReply.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/LoginReply.cs @@ -6,7 +6,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// /// Represents the server response to a . /// - [Packet(0x8001)] + [Packet(PacketType.Server, 0x8001)] internal class LoginReply : Packet { // ---- PROPERTIES --------------------------------------------------------------------------------------------- diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/RawQuery.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/RawQuery.cs index e20e868..5f8385b 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/RawQuery.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/RawQuery.cs @@ -1,36 +1,33 @@ #if DEBUG +using Syroot.BinaryData; + namespace Syroot.Worms.OnlineWorms.Server.Net { /// - /// Represents a special fallback packet which simply stores any ID and the raw client packet data. + /// Represents a special fallback packet which simply stores any ID and the raw packet data. /// - internal class RawQuery : Packet + internal class RawPacket : Packet { // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ - internal RawQuery(ushort id) + internal RawPacket(PacketType type, int id) { + Type = type; ID = id; } // ---- PROPERTIES --------------------------------------------------------------------------------------------- - internal ushort ID { get; } + internal PacketType Type { get; } + + internal int ID { get; } internal byte[] Data { get; set; } // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - internal override void Deserialize(PacketStream stream) - { - Data = new byte[stream.Length]; - stream.Read(Data, 0, Data.Length); - } - - internal override void Serialize(PacketStream stream) - { - stream.Write(Data, 0, Data.Length); - } + internal override void Deserialize(PacketStream stream) => Data = stream.ReadBytes((int)stream.Length); + internal override void Serialize(PacketStream stream) => stream.WriteBytes(Data); } } #endif diff --git a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ServerInfoReply.cs b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ServerInfoReply.cs index 17a6094..fc71baf 100644 --- a/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ServerInfoReply.cs +++ b/src/Syroot.Worms.OnlineWorms.Server/Net/Packets/ServerInfoReply.cs @@ -6,7 +6,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// Represents an additional server response to a , providing informational server /// screen text. /// - [Packet(0x8033)] + [Packet(PacketType.Server, 0x8033)] internal class ServerInfoReply : Packet { // ---- PROPERTIES ---------------------------------------------------------------------------------------------