From 197a958a98dc79bb65b15a6ddacf97a67109cce5 Mon Sep 17 00:00:00 2001 From: Ray Koopa Date: Wed, 16 Jan 2019 01:27:34 +0100 Subject: [PATCH] Port WWPA packet decompression to C#. --- src/Syroot.Worms.Mgame.GameServer/Client.cs | 20 +- .../Packets/AppConnection.cs | 6 +- .../WorldPartyAqua/PacketCompression.cs | 187 +++++++++++++----- src/Syroot.Worms.Mgame.GameServer/Program.cs | 9 - .../Syroot.Worms.Mgame.GameServer.csproj | 3 +- src/Syroot.Worms.Scratchpad/Program.cs | 18 +- .../Syroot.Worms.Scratchpad.csproj | 3 + 7 files changed, 173 insertions(+), 73 deletions(-) diff --git a/src/Syroot.Worms.Mgame.GameServer/Client.cs b/src/Syroot.Worms.Mgame.GameServer/Client.cs index 93c797f..d8909cc 100644 --- a/src/Syroot.Worms.Mgame.GameServer/Client.cs +++ b/src/Syroot.Worms.Mgame.GameServer/Client.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Drawing; using System.Net; using System.Net.Sockets; @@ -180,23 +179,30 @@ namespace Syroot.Worms.Mgame.GameServer } #if DEBUG - public void HandleRaw(RawPacket packet) { } + public void HandleRaw(RawPacket packet) + { + SendPacket(new RawPacket(PacketFormat.Wwpa, 0x0000, + new byte[] { 0x01, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00 })); + } #endif // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ protected override void OnPrePacketHandle(Packet packet) { - _server.Log.Write(LogCategory.Client, - $"{TcpClient.Client.RemoteEndPoint} >> {packet}{Environment.NewLine}{ObjectDumper.Dump(packet)}"); + _server.Log.Write(LogCategory.Client, FormatPacket(packet, ">>")); } protected override void OnPrePacketSend(Packet packet) { - _server.Log.Write(LogCategory.Server, - $"{TcpClient.Client.RemoteEndPoint} << {packet}{Environment.NewLine}{ObjectDumper.Dump(packet)}"); + _server.Log.Write(LogCategory.Server, FormatPacket(packet, "<<")); if (_server.Config.SendDelay > 0) Thread.Sleep(_server.Config.SendDelay); } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + private string FormatPacket(Packet packet, string direction) + => $"{TcpClient.Client.RemoteEndPoint} {direction} {packet.GetType().Name}{ObjectDumper.Dump(packet)}"; } } \ No newline at end of file diff --git a/src/Syroot.Worms.Mgame.GameServer/Packets/AppConnection.cs b/src/Syroot.Worms.Mgame.GameServer/Packets/AppConnection.cs index 28536db..f9dd043 100644 --- a/src/Syroot.Worms.Mgame.GameServer/Packets/AppConnection.cs +++ b/src/Syroot.Worms.Mgame.GameServer/Packets/AppConnection.cs @@ -81,7 +81,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets { if (disposing) { - TcpClient.Close(); + TcpClient.Dispose(); _packetDataStream.Dispose(); } @@ -205,8 +205,8 @@ namespace Syroot.Worms.Mgame.GameServer.Packets // Instantiate, deserialize, and return packet. int id = 0; Packet packet = PacketFactory.Create(PacketFormat.Wwpa, id); - byte[] decompressedData = PacketCompression.Decompress(compressedData); - using (PacketDataStream stream = new PacketDataStream(decompressedData)) + ReadOnlySpan decompressedData = PacketCompression.Decompress(compressedData); + using (PacketDataStream stream = new PacketDataStream(decompressedData.ToArray())) packet.LoadData(stream); return packet; } diff --git a/src/Syroot.Worms.Mgame.GameServer/Packets/WorldPartyAqua/PacketCompression.cs b/src/Syroot.Worms.Mgame.GameServer/Packets/WorldPartyAqua/PacketCompression.cs index b2ee54e..9474ca0 100644 --- a/src/Syroot.Worms.Mgame.GameServer/Packets/WorldPartyAqua/PacketCompression.cs +++ b/src/Syroot.Worms.Mgame.GameServer/Packets/WorldPartyAqua/PacketCompression.cs @@ -2,35 +2,29 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua { - internal static class PacketCompression + /// + /// Represents packet data encryption as used in WWPA. Note that most code is a direct translation from the original + /// game assembly. + /// + public static class PacketCompression { // ---- FIELDS ------------------------------------------------------------------------------------------------- - private static int _field_0 = 0; - - private static int _field_4 = 4; - private static int _field_8 = 0; - - private static int _field_C = 0; - private static int _field_10 = 0; - - private static int[] _bufferDwords = new int[512]; - private static byte[] _buffer = new byte[256]; - private static int _bufferCursor = 0; + private static int _cursor = 0; // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - internal static ReadOnlySpan Compress(ReadOnlySpan decompressed) + public static ReadOnlySpan Compress(ReadOnlySpan decompressed) { Compressor compressor = new Compressor(); int idx = 0; while (idx < decompressed.Length) { + int value1 = 0; + int value2 = -1; int bytesToRepeat = Math.Max(0, idx - 0xFF); - int idxDword = 0; - int shiftValue1 = -1; if (bytesToRepeat >= idx) { Write(compressor, decompressed[idx++]); @@ -42,45 +36,37 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua int i; for (i = idx; i < decompressed.Length; i++) { - if (i - idx >= 0x111) - break; - if (decompressed[bytesToRepeat] != decompressed[i]) + if (i - idx >= 0x111 + || decompressed[bytesToRepeat] != decompressed[i]) break; bytesToRepeat++; } - int offset = idx - i; - int v11 = i - idx; - int v12 = offset + bytesToRepeat; - if (v11 >= 3 && v11 > idxDword) + int offset = i - idx; + if (offset >= 3 && offset > value1) { - idxDword = v11; - shiftValue1 = idx - 12; - if (v11 == 0x111) + value1 = offset; + value2 = idx - 12; + if (offset == 0x111) break; } - bytesToRepeat = v12 + 1; + bytesToRepeat += idx - i + 1; } while (bytesToRepeat < idx); - if (idxDword != 0) + if (value1 != 0) { TransferBuffer(compressor); compressor.Compress(true); - if (idxDword >= 18) + if (value1 >= 18) { compressor.Shift(0, 4); - compressor.Shift(shiftValue1, 8); - compressor.Shift(idxDword - 18, 8); - _field_C++; - _field_10 = idxDword - 3 + _field_10; + compressor.Shift(value2, 8); + compressor.Shift(value1 - 18, 8); } else { - compressor.Shift(idxDword - 2, 4); - compressor.Shift(shiftValue1 - 1, 8); - _field_4++; - _field_8 = idxDword - 2 + _field_8; + compressor.Shift(value1 - 2, 4); + compressor.Shift(value2 - 1, 8); } - idx += idxDword; - _bufferDwords[idxDword]++; + idx += value1; } else { @@ -96,30 +82,84 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua return compressor.Buffer.AsSpan(0, compressor.Cursor); } - internal static byte[] Decompress(ReadOnlySpan compressed) + public static ReadOnlySpan Decompress(ReadOnlySpan compressed) { - return new byte[1]; + Decompressor decompressor = new Decompressor(compressed); + byte[] decompressed = new byte[2048]; + + int bytesToCopy; + int bytesToCopyRemain; + int idx = 0; + while (true) + { + while (true) + { + while (decompressor.sub_4C0BA0() == 0) + { + bytesToCopy = decompressor.GetBytesToCopy(8); + if (bytesToCopy != -1) + { + bytesToCopy++; + do + { + decompressed[idx++] = decompressor.Read(); + bytesToCopy--; + } while (bytesToCopy != 0); + } + } + if (decompressor.GetBytesToCopy(4) + 2 <= 2) + break; + bytesToCopy = decompressor.GetBytesToCopy(8) + 1; + bytesToCopyRemain = bytesToCopy - 1; + if (bytesToCopy != 0) + { + bytesToCopy++; + do + { + int idxCopySrc = idx++ - bytesToCopy; + bytesToCopyRemain--; + decompressed[idx - 1] = decompressed[idxCopySrc]; + } while (bytesToCopyRemain != 0); + } + } + bytesToCopy = decompressor.GetBytesToCopy(8); + if (bytesToCopy == 0) + break; + int v12 = decompressor.GetBytesToCopy(8); + int v13 = v12 + 18; + bytesToCopyRemain = v12 + 17; + if (v13 != 0) + { + bytesToCopyRemain++; + do + { + int idxCopySrc = idx++ - bytesToCopy; + bytesToCopyRemain--; + decompressed[idx - 1] = decompressed[idxCopySrc]; + } while (bytesToCopyRemain != 0); + } + } + return decompressed.AsSpan(0, idx); } // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- private static void TransferBuffer(Compressor compressor) { - if (_bufferCursor != 0) + if (_cursor != 0) { - _field_0 += _bufferCursor; compressor.Compress(false); - compressor.Shift(_bufferCursor - 1, 8); - for (int i = 0; i < _bufferCursor; i++) + compressor.Shift(_cursor - 1, 8); + for (int i = 0; i < _cursor; i++) compressor.Buffer[compressor.Cursor++] = _buffer[i]; - _bufferCursor = 0; + _cursor = 0; } } private static void Write(Compressor compressor, byte c) { - _buffer[_bufferCursor++] = c; - if (_bufferCursor >= 256) + _buffer[_cursor++] = c; + if (_cursor >= _buffer.Length) TransferBuffer(compressor); } @@ -133,12 +173,10 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua private int _cursorCompress = 0; private int _shift = 0; - internal void Compress(bool bShift) + internal void Compress(bool doShift) { - if (bShift) - { + if (doShift) Buffer[_cursorCompress] |= (byte)(0x80 >> _shift); - } if (++_shift == 8) { _cursorCompress = Cursor++; @@ -162,5 +200,52 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua } } + private class Decompressor + { + private byte[] _compressed; + private int _compressedCursor = 0; + private byte _field_C; + private byte _field_D; + + internal Decompressor(ReadOnlySpan compressed) + { + _compressed = compressed.ToArray(); + byte c = Read(); + _field_C = (byte)(2 * c | 1); + _field_D = (byte)(c >> 7); + } + + internal byte Read() + { + return _compressed[_compressedCursor++]; + } + + internal int GetBytesToCopy(int numCalls) + { + if (numCalls == 0) + return 0; + int bytesToCopy = 0; + do + { + numCalls--; + bytesToCopy = sub_4C0BA0() + 2 * bytesToCopy; + } while (numCalls != 0); + return bytesToCopy; + } + + internal int sub_4C0BA0() + { + byte field_D = _field_D; + _field_D = (byte)(_field_C >> 7); + _field_C *= 2; + if (_field_C == 0) + { + byte c = Read(); + _field_C = (byte)(2 * c | 1); + _field_D = (byte)(c >> 7); + } + return field_D; + } + } } } diff --git a/src/Syroot.Worms.Mgame.GameServer/Program.cs b/src/Syroot.Worms.Mgame.GameServer/Program.cs index 6837671..5bb64d8 100644 --- a/src/Syroot.Worms.Mgame.GameServer/Program.cs +++ b/src/Syroot.Worms.Mgame.GameServer/Program.cs @@ -1,7 +1,4 @@ using System; -using System.Diagnostics; -using Syroot.BinaryData; -using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua; namespace Syroot.Worms.Mgame.GameServer { @@ -14,12 +11,6 @@ namespace Syroot.Worms.Mgame.GameServer private static void Main(string[] args) { - byte[] decompressed = new byte[sizeof(int)]; - ByteConverter.System.GetBytes(0x8001, decompressed); - ReadOnlySpan compressed = PacketCompression.Compress(decompressed); - Debug.Assert(compressed.SequenceEqual(new byte[] { 0x01, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00 }), - "Compression failed"); - try { // Start a new server. diff --git a/src/Syroot.Worms.Mgame.GameServer/Syroot.Worms.Mgame.GameServer.csproj b/src/Syroot.Worms.Mgame.GameServer/Syroot.Worms.Mgame.GameServer.csproj index a51a6b4..443f00e 100644 --- a/src/Syroot.Worms.Mgame.GameServer/Syroot.Worms.Mgame.GameServer.csproj +++ b/src/Syroot.Worms.Mgame.GameServer/Syroot.Worms.Mgame.GameServer.csproj @@ -1,9 +1,10 @@  + false Server + latest Exe netcoreapp2.1 - latest diff --git a/src/Syroot.Worms.Scratchpad/Program.cs b/src/Syroot.Worms.Scratchpad/Program.cs index 0657864..30ba6c2 100644 --- a/src/Syroot.Worms.Scratchpad/Program.cs +++ b/src/Syroot.Worms.Scratchpad/Program.cs @@ -1,7 +1,10 @@ using System; +using System.Diagnostics; using System.Drawing.Imaging; using System.IO; +using Syroot.BinaryData; using Syroot.Worms.Mgame; +using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua; namespace Syroot.Worms.Scratchpad { @@ -11,8 +14,19 @@ namespace Syroot.Worms.Scratchpad private static void Main(string[] args) { - ConvertIgdImages(); - Console.ReadLine(); + DecompressWwpaPacketData(); + } + + private static void DecompressWwpaPacketData() + { + byte[] decompressed = new byte[sizeof(int)]; + ByteConverter.System.GetBytes(0x8001, decompressed); + ReadOnlySpan compressed = PacketCompression.Compress(decompressed); + Debug.Assert(compressed.SequenceEqual(new byte[] { 0x01, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00 }), + "Compression failed"); + + ReadOnlySpan decompressedNew = PacketCompression.Decompress(compressed); + Debug.Assert(decompressedNew.SequenceEqual(decompressed), "Decompression failed"); } private static void ConvertIgdImages() diff --git a/src/Syroot.Worms.Scratchpad/Syroot.Worms.Scratchpad.csproj b/src/Syroot.Worms.Scratchpad/Syroot.Worms.Scratchpad.csproj index 9edf8ea..40d3374 100644 --- a/src/Syroot.Worms.Scratchpad/Syroot.Worms.Scratchpad.csproj +++ b/src/Syroot.Worms.Scratchpad/Syroot.Worms.Scratchpad.csproj @@ -3,6 +3,9 @@ Exe netcoreapp2.1 + + +