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.Drawing;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
@ -180,23 +179,30 @@ namespace Syroot.Worms.Mgame.GameServer
} }
#if DEBUG #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 #endif
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
protected override void OnPrePacketHandle(Packet packet) protected override void OnPrePacketHandle(Packet packet)
{ {
_server.Log.Write(LogCategory.Client, _server.Log.Write(LogCategory.Client, FormatPacket(packet, ">>"));
$"{TcpClient.Client.RemoteEndPoint} >> {packet}{Environment.NewLine}{ObjectDumper.Dump(packet)}");
} }
protected override void OnPrePacketSend(Packet packet) protected override void OnPrePacketSend(Packet packet)
{ {
_server.Log.Write(LogCategory.Server, _server.Log.Write(LogCategory.Server, FormatPacket(packet, "<<"));
$"{TcpClient.Client.RemoteEndPoint} << {packet}{Environment.NewLine}{ObjectDumper.Dump(packet)}");
if (_server.Config.SendDelay > 0) if (_server.Config.SendDelay > 0)
Thread.Sleep(_server.Config.SendDelay); 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) if (disposing)
{ {
TcpClient.Close(); TcpClient.Dispose();
_packetDataStream.Dispose(); _packetDataStream.Dispose();
} }
@ -205,8 +205,8 @@ namespace Syroot.Worms.Mgame.GameServer.Packets
// Instantiate, deserialize, and return packet. // Instantiate, deserialize, and return packet.
int id = 0; int id = 0;
Packet packet = PacketFactory.Create(PacketFormat.Wwpa, id); Packet packet = PacketFactory.Create(PacketFormat.Wwpa, id);
byte[] decompressedData = PacketCompression.Decompress(compressedData); ReadOnlySpan<byte> decompressedData = PacketCompression.Decompress(compressedData);
using (PacketDataStream stream = new PacketDataStream(decompressedData)) using (PacketDataStream stream = new PacketDataStream(decompressedData.ToArray()))
packet.LoadData(stream); packet.LoadData(stream);
return packet; return packet;
} }

View File

@ -2,35 +2,29 @@
namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua 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 ------------------------------------------------------------------------------------------------- // ---- 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 byte[] _buffer = new byte[256];
private static int _bufferCursor = 0; private static int _cursor = 0;
// ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal static ReadOnlySpan<byte> Compress(ReadOnlySpan<byte> decompressed) public static ReadOnlySpan<byte> Compress(ReadOnlySpan<byte> decompressed)
{ {
Compressor compressor = new Compressor(); Compressor compressor = new Compressor();
int idx = 0; int idx = 0;
while (idx < decompressed.Length) while (idx < decompressed.Length)
{ {
int value1 = 0;
int value2 = -1;
int bytesToRepeat = Math.Max(0, idx - 0xFF); int bytesToRepeat = Math.Max(0, idx - 0xFF);
int idxDword = 0;
int shiftValue1 = -1;
if (bytesToRepeat >= idx) if (bytesToRepeat >= idx)
{ {
Write(compressor, decompressed[idx++]); Write(compressor, decompressed[idx++]);
@ -42,45 +36,37 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
int i; int i;
for (i = idx; i < decompressed.Length; i++) for (i = idx; i < decompressed.Length; i++)
{ {
if (i - idx >= 0x111) if (i - idx >= 0x111
break; || decompressed[bytesToRepeat] != decompressed[i])
if (decompressed[bytesToRepeat] != decompressed[i])
break; break;
bytesToRepeat++; bytesToRepeat++;
} }
int offset = idx - i; int offset = i - idx;
int v11 = i - idx; if (offset >= 3 && offset > value1)
int v12 = offset + bytesToRepeat;
if (v11 >= 3 && v11 > idxDword)
{ {
idxDword = v11; value1 = offset;
shiftValue1 = idx - 12; value2 = idx - 12;
if (v11 == 0x111) if (offset == 0x111)
break; break;
} }
bytesToRepeat = v12 + 1; bytesToRepeat += idx - i + 1;
} while (bytesToRepeat < idx); } while (bytesToRepeat < idx);
if (idxDword != 0) if (value1 != 0)
{ {
TransferBuffer(compressor); TransferBuffer(compressor);
compressor.Compress(true); compressor.Compress(true);
if (idxDword >= 18) if (value1 >= 18)
{ {
compressor.Shift(0, 4); compressor.Shift(0, 4);
compressor.Shift(shiftValue1, 8); compressor.Shift(value2, 8);
compressor.Shift(idxDword - 18, 8); compressor.Shift(value1 - 18, 8);
_field_C++;
_field_10 = idxDword - 3 + _field_10;
} }
else else
{ {
compressor.Shift(idxDword - 2, 4); compressor.Shift(value1 - 2, 4);
compressor.Shift(shiftValue1 - 1, 8); compressor.Shift(value2 - 1, 8);
_field_4++;
_field_8 = idxDword - 2 + _field_8;
} }
idx += idxDword; idx += value1;
_bufferDwords[idxDword]++;
} }
else else
{ {
@ -96,30 +82,84 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
return compressor.Buffer.AsSpan(0, compressor.Cursor); 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) -------------------------------------------------------------------------------------- // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void TransferBuffer(Compressor compressor) private static void TransferBuffer(Compressor compressor)
{ {
if (_bufferCursor != 0) if (_cursor != 0)
{ {
_field_0 += _bufferCursor;
compressor.Compress(false); compressor.Compress(false);
compressor.Shift(_bufferCursor - 1, 8); compressor.Shift(_cursor - 1, 8);
for (int i = 0; i < _bufferCursor; i++) for (int i = 0; i < _cursor; i++)
compressor.Buffer[compressor.Cursor++] = _buffer[i]; compressor.Buffer[compressor.Cursor++] = _buffer[i];
_bufferCursor = 0; _cursor = 0;
} }
} }
private static void Write(Compressor compressor, byte c) private static void Write(Compressor compressor, byte c)
{ {
_buffer[_bufferCursor++] = c; _buffer[_cursor++] = c;
if (_bufferCursor >= 256) if (_cursor >= _buffer.Length)
TransferBuffer(compressor); TransferBuffer(compressor);
} }
@ -133,12 +173,10 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
private int _cursorCompress = 0; private int _cursorCompress = 0;
private int _shift = 0; private int _shift = 0;
internal void Compress(bool bShift) internal void Compress(bool doShift)
{ {
if (bShift) if (doShift)
{
Buffer[_cursorCompress] |= (byte)(0x80 >> _shift); Buffer[_cursorCompress] |= (byte)(0x80 >> _shift);
}
if (++_shift == 8) if (++_shift == 8)
{ {
_cursorCompress = Cursor++; _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;
using System.Diagnostics;
using Syroot.BinaryData;
using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua;
namespace Syroot.Worms.Mgame.GameServer namespace Syroot.Worms.Mgame.GameServer
{ {
@ -14,12 +11,6 @@ namespace Syroot.Worms.Mgame.GameServer
private static void Main(string[] args) 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 try
{ {
// Start a new server. // Start a new server.

View File

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

View File

@ -1,7 +1,10 @@
using System; using System;
using System.Diagnostics;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Mgame; using Syroot.Worms.Mgame;
using Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua;
namespace Syroot.Worms.Scratchpad namespace Syroot.Worms.Scratchpad
{ {
@ -11,8 +14,19 @@ namespace Syroot.Worms.Scratchpad
private static void Main(string[] args) private static void Main(string[] args)
{ {
ConvertIgdImages(); DecompressWwpaPacketData();
Console.ReadLine(); }
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() private static void ConvertIgdImages()

View File

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