Port WWPA packet decompression to C#.

This commit is contained in:
Ray Koopa 2019-01-16 01:27:34 +01:00
parent efc1a9fcff
commit 197a958a98
7 changed files with 173 additions and 73 deletions

View File

@ -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)}";
}
}

View File

@ -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<byte> decompressedData = PacketCompression.Decompress(compressedData);
using (PacketDataStream stream = new PacketDataStream(decompressedData.ToArray()))
packet.LoadData(stream);
return packet;
}

View File

@ -2,35 +2,29 @@
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
{
internal static class PacketCompression
/// <summary>
/// Represents packet data encryption as used in WWPA. Note that most code is a direct translation from the original
/// game assembly.
/// </summary>
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<byte> Compress(ReadOnlySpan<byte> decompressed)
public static ReadOnlySpan<byte> Compress(ReadOnlySpan<byte> 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<byte> compressed)
public static ReadOnlySpan<byte> Decompress(ReadOnlySpan<byte> 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<byte> 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;
}
}
}
}

View File

@ -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<byte> compressed = PacketCompression.Compress(decompressed);
Debug.Assert(compressed.SequenceEqual(new byte[] { 0x01, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00 }),
"Compression failed");
try
{
// Start a new server.

View File

@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AssemblyName>Server</AssemblyName>
<LangVersion>latest</LangVersion>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />

View File

@ -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<byte> compressed = PacketCompression.Compress(decompressed);
Debug.Assert(compressed.SequenceEqual(new byte[] { 0x01, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00 }),
"Compression failed");
ReadOnlySpan<byte> decompressedNew = PacketCompression.Decompress(compressed);
Debug.Assert(decompressedNew.SequenceEqual(decompressed), "Decompression failed");
}
private static void ConvertIgdImages()

View File

@ -3,6 +3,9 @@
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Syroot.BinaryData" Version="5.1.0-beta1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms.Armageddon.ProjectX\Syroot.Worms.Armageddon.ProjectX.csproj" />
<ProjectReference Include="..\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" />