Clone original WWPA compression as close as possible due to bug found in original implementation.

This commit is contained in:
Ray Koopa 2019-01-16 06:28:14 +01:00
parent 197a958a98
commit 2d5fa21c00
8 changed files with 587 additions and 72 deletions

View File

@ -248,6 +248,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets
{ {
// Retrieve (decompressed) data size. // Retrieve (decompressed) data size.
ReadOnlySpan<byte> decompressedData = _packetDataStream.GetSpan(); ReadOnlySpan<byte> decompressedData = _packetDataStream.GetSpan();
decompressedData = File.ReadAllBytes(@"D:\Pictures\data.bin");
ReadOnlySpan<byte> compressedData = PacketCompression.Compress(decompressedData); ReadOnlySpan<byte> compressedData = PacketCompression.Compress(decompressedData);
// Send head. // Send head.

View File

@ -6,71 +6,119 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
/// Represents packet data encryption as used in WWPA. Note that most code is a direct translation from the original /// Represents packet data encryption as used in WWPA. Note that most code is a direct translation from the original
/// game assembly. /// game assembly.
/// </summary> /// </summary>
/// <remarks>
/// The original implementation has the following bugs caused by specific, but rare patterns (in 1000 random blobs
/// of sizes between 1 and 1000 bytes, ~0.5% were decompressed badly):
/// - Bad calculation of decompressed bytes. The same wrong bytes are calculated by this implementation. Such data
/// may be discarded by the game as soon as it causes the final length to differ.
/// - Bad negative source buffer indices cause out-of-bounds memory to be written. Combatted in this implementation
/// by ignoring such writes and keeping 0 bytes instead.
/// </remarks>
public static class PacketCompression public static class PacketCompression
{ {
// ---- FIELDS ------------------------------------------------------------------------------------------------- // ---- FIELDS -------------------------------------------------------------------------------------------------
private static int _numBytesTransferred = 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 _cursor = 0; private static int _bufferCursor = 0;
// ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Compresses the given <paramref name="decompressed"/> data.
/// </summary>
/// <param name="decompressed">The data to compress.</param>
/// <returns>The compressed data.</returns>
public 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;
int bytesToRepeat;
int idxDword;
int i;
int offset;
int v11;
int v12;
int field_8;
int field_10;
byte c;
int shiftValue1;
int idx = 0; idx = 0;
while (idx < decompressed.Length) while (idx < decompressed.Length)
{ {
int value1 = 0; bytesToRepeat = idx - 0xFF;
int value2 = -1; if (idx - 0xFF < 0)
int bytesToRepeat = Math.Max(0, idx - 0xFF); bytesToRepeat = 0;
idxDword = 0;
shiftValue1 = -1;
if (bytesToRepeat >= idx) if (bytesToRepeat >= idx)
{ {
Write(compressor, decompressed[idx++]); c = decompressed[idx++];
Write(compressor, c);
} }
else else
{ {
do do
{ {
int i; for (i = idx; i < decompressed.Length; ++i)
for (i = idx; i < decompressed.Length; i++)
{ {
if (i - idx >= 0x111 if (i - idx >= 0x111)
|| decompressed[bytesToRepeat] != decompressed[i])
break; break;
bytesToRepeat++; if (decompressed[bytesToRepeat] != decompressed[i])
break;
++bytesToRepeat;
} }
int offset = i - idx; offset = idx - i;
if (offset >= 3 && offset > value1) v11 = i - idx;
v12 = offset + bytesToRepeat;
if (v11 >= 3 && v11 > idxDword)
{ {
value1 = offset; idxDword = v11;
value2 = idx - 12; shiftValue1 = idx - 12;
if (offset == 0x111) if (v11 == 0x111)
break; break;
} }
bytesToRepeat += idx - i + 1; bytesToRepeat = v12 + 1;
} while (bytesToRepeat < idx); }
if (value1 != 0) while (bytesToRepeat < idx);
if (idxDword != 0)
{ {
TransferBuffer(compressor); TransferBuffer(compressor);
compressor.Compress(true); compressor.Compress(true);
if (value1 >= 18) if (idxDword >= 18)
{ {
compressor.Shift(0, 4); compressor.Shift(0, 4);
compressor.Shift(value2, 8); compressor.Shift(shiftValue1, 8);
compressor.Shift(value1 - 18, 8); compressor.Shift(idxDword - 18, 8);
field_10 = _field_10;
++_field_C;
_field_10 = idxDword - 3 + field_10;
} }
else else
{ {
compressor.Shift(value1 - 2, 4); compressor.Shift(idxDword - 2, 4);
compressor.Shift(value2 - 1, 8); compressor.Shift(shiftValue1 - 1, 8);
field_8 = _field_8;
++_field_4;
_field_8 = idxDword - 2 + field_8;
} }
idx += value1; idx += idxDword;
++_bufferDwords[idxDword];
} }
else else
{ {
Write(compressor, decompressed[idx++]); c = decompressed[idx++];
Write(compressor, c);
} }
} }
} }
@ -79,17 +127,22 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
compressor.Shift(0, 4); compressor.Shift(0, 4);
compressor.Shift(0, 8); compressor.Shift(0, 8);
return compressor.Buffer.AsSpan(0, compressor.Cursor); return compressor.Compressed.AsSpan(0, compressor.Cursor);
} }
public static ReadOnlySpan<byte> Decompress(ReadOnlySpan<byte> compressed) public static ReadOnlySpan<byte> Decompress(ReadOnlySpan<byte> compressed)
{ {
Decompressor decompressor = new Decompressor(compressed); Decompressor decompressor = new Decompressor(compressed);
byte[] decompressed = new byte[2048]; byte[] decompressed = new byte[2048];
int idx;
int bytesToCopy;
int bytesToCopyRemain; int bytesToCopyRemain;
int idx = 0; int idxCopySrc;
int v12;
int v13;
int bytesToCopy;
idx = 0;
while (true) while (true)
{ {
while (true) while (true)
@ -99,42 +152,44 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
bytesToCopy = decompressor.GetBytesToCopy(8); bytesToCopy = decompressor.GetBytesToCopy(8);
if (bytesToCopy != -1) if (bytesToCopy != -1)
{ {
bytesToCopy++; ++bytesToCopy;
do do
{ {
decompressed[idx++] = decompressor.Read(); decompressed[idx++] = decompressor.Read();
bytesToCopy--; --bytesToCopy;
} while (bytesToCopy != 0); } while (bytesToCopy != 0);
} }
} }
if (decompressor.GetBytesToCopy(4) + 2 <= 2) bytesToCopy = decompressor.GetBytesToCopy(4) + 2;
if (bytesToCopy <= 2)
break; break;
bytesToCopy = decompressor.GetBytesToCopy(8) + 1; bytesToCopy = decompressor.GetBytesToCopy(8) + 1;
bytesToCopyRemain = bytesToCopy - 1; bytesToCopyRemain = bytesToCopy - 1;
if (bytesToCopy != 0) if (bytesToCopy != 0)
{ {
bytesToCopy++; ++bytesToCopyRemain;
do do
{ {
int idxCopySrc = idx++ - bytesToCopy; idxCopySrc = idx++ - bytesToCopy;
bytesToCopyRemain--; --bytesToCopyRemain;
decompressed[idx - 1] = decompressed[idxCopySrc]; if (idxCopySrc >= 0) // Bugfix: Original implementation may calculate negative indices.
decompressed[idx - 1] = decompressed[idxCopySrc];
} while (bytesToCopyRemain != 0); } while (bytesToCopyRemain != 0);
} }
} }
bytesToCopy = decompressor.GetBytesToCopy(8); bytesToCopy = decompressor.GetBytesToCopy(8);
if (bytesToCopy == 0) if (bytesToCopy == 0)
break; break;
int v12 = decompressor.GetBytesToCopy(8); v12 = decompressor.GetBytesToCopy(8);
int v13 = v12 + 18; v13 = v12 + 18;
bytesToCopyRemain = v12 + 17; bytesToCopyRemain = v12 + 17;
if (v13 != 0) if (v13 != 0)
{ {
bytesToCopyRemain++; ++bytesToCopyRemain;
do do
{ {
int idxCopySrc = idx++ - bytesToCopy; idxCopySrc = idx++ - bytesToCopy;
bytesToCopyRemain--; --bytesToCopyRemain;
decompressed[idx - 1] = decompressed[idxCopySrc]; decompressed[idx - 1] = decompressed[idxCopySrc];
} while (bytesToCopyRemain != 0); } while (bytesToCopyRemain != 0);
} }
@ -146,20 +201,25 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
private static void TransferBuffer(Compressor compressor) private static void TransferBuffer(Compressor compressor)
{ {
if (_cursor != 0) int bufferCursor;
int i;
bufferCursor = _bufferCursor;
if (bufferCursor != 0)
{ {
_numBytesTransferred += bufferCursor;
compressor.Compress(false); compressor.Compress(false);
compressor.Shift(_cursor - 1, 8); compressor.Shift(_bufferCursor - 1, 8);
for (int i = 0; i < _cursor; i++) for (i = 0; i < _bufferCursor; ++i)
compressor.Buffer[compressor.Cursor++] = _buffer[i]; compressor.Write(_buffer[i]);
_cursor = 0; _bufferCursor = 0;
} }
} }
private static void Write(Compressor compressor, byte c) private static void Write(Compressor compressor, byte c)
{ {
_buffer[_cursor++] = c; _buffer[_bufferCursor++] = c;
if (_cursor >= _buffer.Length) if (_bufferCursor >= _buffer.Length)
TransferBuffer(compressor); TransferBuffer(compressor);
} }
@ -167,28 +227,41 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
private class Compressor private class Compressor
{ {
internal byte[] Buffer = new byte[1024]; internal byte[] Compressed = new byte[1024];
internal int Cursor = 1; internal int Cursor = 1;
private int _cursorCompress = 0; private int _cursorCompress = 0;
private int _shift = 0; private int _shift = 0;
internal void Compress(bool doShift) internal void Compress(bool bShift)
{ {
if (doShift) int shift;
Buffer[_cursorCompress] |= (byte)(0x80 >> _shift); int cursor;
if (++_shift == 8) int cursorCompress;
if (bShift)
{ {
Compressed[_cursorCompress] |= (byte)(0x80 >> _shift);
}
shift = _shift + 1;
_shift = shift;
if (shift == 8)
{
cursor = Cursor;
_cursorCompress = Cursor++; _cursorCompress = Cursor++;
Buffer[_cursorCompress] = 0; cursorCompress = _cursorCompress;
Cursor = cursor + 1;
Compressed[cursorCompress] = 0;
_shift = 0; _shift = 0;
} }
} }
internal void Shift(int value, int shiftPlus1) internal void Shift(int value, int shiftPlus1)
{ {
int prevShift = 0; int shift;
int shift = shiftPlus1 - 1; int prevShift;
shift = shiftPlus1 - 1;
if (shiftPlus1 != 0) if (shiftPlus1 != 0)
{ {
do do
@ -198,11 +271,16 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
} while (prevShift != 0); } while (prevShift != 0);
} }
} }
internal void Write(byte c)
{
Compressed[Cursor++] = c;
}
} }
private class Decompressor private class Decompressor
{ {
private byte[] _compressed; private readonly byte[] _compressed;
private int _compressedCursor = 0; private int _compressedCursor = 0;
private byte _field_C; private byte _field_C;
private byte _field_D; private byte _field_D;
@ -212,7 +290,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
_compressed = compressed.ToArray(); _compressed = compressed.ToArray();
byte c = Read(); byte c = Read();
_field_C = (byte)(2 * c | 1); _field_C = (byte)(2 * c | 1);
_field_D = (byte)(c >> 7); _field_D = (byte)((uint)c >> 7);
} }
internal byte Read() internal byte Read()

View File

@ -0,0 +1,65 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Syroot.Worms.Mgame.Test
{
[TestClass]
public class PacketCompressionTests
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
// Known to cause 3 wrong bytes:
private static readonly byte[] _origBadBytes = new byte[] { 0x68, 0x56, 0xF9, 0x1D, 0x39, 0x5F, 0xD0, 0x57, 0x13, 0x9B, 0xF3, 0x13, 0xF5, 0x97, 0x41, 0xD0, 0x57, 0x13, 0x1E, 0x6C, 0x4A };
private static readonly byte[] _rcmpBadBytes = new byte[] { 0x68, 0x56, 0xF9, 0x1D, 0x39, 0x5F, 0xD0, 0x57, 0x13, 0x9B, 0xF3, 0x13, 0xF5, 0x97, 0x41, 0xF5, 0x97, 0x41, 0x1E, 0x6C, 0x4A };
// Known to cause negative index bug writing undefined bytes:
private static readonly byte[] _origBadIndex = new byte[] { 0xFC, 0xFC, 0xC2, 0x92, 0x8F, 0x66, 0x33, 0x44, 0xEF, 0x40, 0xE9, 0xAB, 0x44, 0xEF, 0x40, 0x36 };
private static readonly byte[] _rcmpBadIndex = new byte[] { 0xFC, 0xFC, 0xC2, 0x92, 0x8F, 0x66, 0x33, 0x44, 0xEF, 0x40, 0xE9, 0xAB, 0x00, 0x00, 0x00, 0x36 };
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
[TestMethod]
public void TestBadBytes()
{
Assert.IsTrue(Recompress(_origBadBytes).SequenceEqual(_rcmpBadBytes));
}
[TestMethod]
public void TestBadIndex()
{
var recompressed = Recompress(_origBadIndex);
Assert.IsTrue(recompressed.SequenceEqual(_rcmpBadIndex));
}
[TestMethod]
public void TestRandom()
{
const int runs = 1000;
int fails = 0;
Random random = new Random();
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
for (int i = 0; i < runs; i++)
{
byte[] data = new byte[random.Next(1, 1000)];
rng.GetBytes(data);
ReadOnlySpan<byte> recompressed = Recompress(data);
if (!data.AsSpan().SequenceEqual(recompressed))
fails++;
}
}
// If less than 1% fails, the compression is seen as working as in the original implementation.
Assert.IsTrue(fails / (float)runs < 0.01);
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static ReadOnlySpan<byte> Recompress(ReadOnlySpan<byte> data)
{
ReadOnlySpan<byte> compressed = PacketCompression.Compress(data);
return PacketCompression.Decompress(compressed);
}
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
<PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
<ProjectReference Include="..\Syroot.Worms.Mgame\Syroot.Worms.Mgame.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,332 @@
using System;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents packet data encryption as used in WWPA. Note that most code is a direct translation from the original
/// game assembly.
/// </summary>
/// <remarks>
/// The original implementation has the following bugs caused by specific, but rare patterns (in 1000 random blobs
/// of sizes between 1 and 1000 bytes, ~0.5% were decompressed badly):
/// - Bad calculation of decompressed bytes. The same wrong bytes are calculated by this implementation.
/// - Bad negative source buffer indices cause out-of-bounds memory to be written. Combatted in this implementation
/// by ignoring such writes and keeping 0 bytes instead.
/// The game discards decompression results of a length longer than specified in the packet header, but does not
/// check the data itself if the length is still fine.
/// </remarks>
public static class PacketCompression
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private static int _numBytesTransferred = 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;
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Compresses the given <paramref name="decompressed"/> data.
/// </summary>
/// <param name="decompressed">The data to compress.</param>
/// <returns>The compressed data.</returns>
public static ReadOnlySpan<byte> Compress(ReadOnlySpan<byte> decompressed)
{
Compressor compressor = new Compressor();
int idx;
int bytesToRepeat;
int idxDword;
int i;
int offset;
int v11;
int v12;
int field_8;
int field_10;
byte c;
int shiftValue1;
idx = 0;
while (idx < decompressed.Length)
{
bytesToRepeat = idx - 0xFF;
if (idx - 0xFF < 0)
bytesToRepeat = 0;
idxDword = 0;
shiftValue1 = -1;
if (bytesToRepeat >= idx)
{
c = decompressed[idx++];
Write(compressor, c);
}
else
{
do
{
for (i = idx; i < decompressed.Length; ++i)
{
if (i - idx >= 0x111)
break;
if (decompressed[bytesToRepeat] != decompressed[i])
break;
++bytesToRepeat;
}
offset = idx - i;
v11 = i - idx;
v12 = offset + bytesToRepeat;
if (v11 >= 3 && v11 > idxDword)
{
idxDword = v11;
shiftValue1 = idx - 12;
if (v11 == 0x111)
break;
}
bytesToRepeat = v12 + 1;
}
while (bytesToRepeat < idx);
if (idxDword != 0)
{
TransferBuffer(compressor);
compressor.Compress(true);
if (idxDword >= 18)
{
compressor.Shift(0, 4);
compressor.Shift(shiftValue1, 8);
compressor.Shift(idxDword - 18, 8);
field_10 = _field_10;
++_field_C;
_field_10 = idxDword - 3 + field_10;
}
else
{
compressor.Shift(idxDword - 2, 4);
compressor.Shift(shiftValue1 - 1, 8);
field_8 = _field_8;
++_field_4;
_field_8 = idxDword - 2 + field_8;
}
idx += idxDword;
++_bufferDwords[idxDword];
}
else
{
c = decompressed[idx++];
Write(compressor, c);
}
}
}
TransferBuffer(compressor);
compressor.Compress(true);
compressor.Shift(0, 4);
compressor.Shift(0, 8);
return compressor.Compressed.AsSpan(0, compressor.Cursor);
}
public static ReadOnlySpan<byte> Decompress(ReadOnlySpan<byte> compressed)
{
Decompressor decompressor = new Decompressor(compressed);
byte[] decompressed = new byte[2048];
int idx;
int v5;
int v6;
int idxCopySrc;
int v11;
int v12;
int v13;
int bytesToCopy;
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);
}
}
v5 = decompressor.GetBytesToCopy(4) + 2;
if (v5 <= 2)
break;
v6 = decompressor.GetBytesToCopy(8) + 1;
bytesToCopy = v5 - 1;
if (v5 != 0)
{
++bytesToCopy;
do
{
idxCopySrc = idx++ - v6;
--bytesToCopy;
if (idxCopySrc >= 0) // Bugfix: Original implementation may calculate negative indices.
decompressed[idx - 1] = decompressed[idxCopySrc];
} while (bytesToCopy != 0);
}
}
v11 = decompressor.GetBytesToCopy(8);
if (v11 == 0)
break;
v12 = decompressor.GetBytesToCopy(8);
v13 = v12 + 18;
bytesToCopy = v12 + 17;
if (v13 != 0)
{
++bytesToCopy;
do
{
idxCopySrc = idx++ - v11;
--bytesToCopy;
decompressed[idx - 1] = decompressed[idxCopySrc];
} while (bytesToCopy != 0);
}
}
return decompressed.AsSpan(0, idx);
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void TransferBuffer(Compressor compressor)
{
int bufferCursor;
int i;
bufferCursor = _bufferCursor;
if (bufferCursor != 0)
{
_numBytesTransferred += bufferCursor;
compressor.Compress(false);
compressor.Shift(_bufferCursor - 1, 8);
for (i = 0; i < _bufferCursor; ++i)
compressor.Write(_buffer[i]);
_bufferCursor = 0;
}
}
private static void Write(Compressor compressor, byte c)
{
_buffer[_bufferCursor++] = c;
if (_bufferCursor >= _buffer.Length)
TransferBuffer(compressor);
}
// ---- CLASSES, STRUCTS & ENUMS -------------------------------------------------------------------------------
private class Compressor
{
internal byte[] Compressed = new byte[1024];
internal int Cursor = 1;
private int _cursorCompress = 0;
private int _shift = 0;
internal void Compress(bool bShift)
{
int shift;
int cursor;
int cursorCompress;
if (bShift)
{
Compressed[_cursorCompress] |= (byte)(0x80 >> _shift);
}
shift = _shift + 1;
_shift = shift;
if (shift == 8)
{
cursor = Cursor;
_cursorCompress = Cursor++;
cursorCompress = _cursorCompress;
Cursor = cursor + 1;
Compressed[cursorCompress] = 0;
_shift = 0;
}
}
internal void Shift(int value, int shiftPlus1)
{
int shift;
int prevShift;
shift = shiftPlus1 - 1;
if (shiftPlus1 != 0)
{
do
{
Compress(((value >> shift) & 1) != 0);
prevShift = shift--;
} while (prevShift != 0);
}
}
internal void Write(byte c)
{
Compressed[Cursor++] = c;
}
}
private class Decompressor
{
private readonly 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)((uint)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,10 +1,7 @@
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
{ {
@ -14,19 +11,43 @@ namespace Syroot.Worms.Scratchpad
private static void Main(string[] args) private static void Main(string[] args)
{ {
DecompressWwpaPacketData(); TestWwpaPacketCompressionBug();
Console.ReadLine();
} }
private static void DecompressWwpaPacketData() private static void TestWwpaPacketCompressionBug()
{ {
byte[] decompressed = new byte[sizeof(int)]; ReadOnlySpan<byte> recompress(ReadOnlySpan<byte> data)
ByteConverter.System.GetBytes(0x8001, decompressed); {
ReadOnlySpan<byte> compressed = PacketCompression.Compress(decompressed); ReadOnlySpan<byte> compressed = PacketCompression.Compress(data);
Debug.Assert(compressed.SequenceEqual(new byte[] { 0x01, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00 }), return PacketCompression.Decompress(compressed);
"Compression failed"); }
ReadOnlySpan<byte> decompressedNew = PacketCompression.Decompress(compressed); bool test(byte[] data)
Debug.Assert(decompressedNew.SequenceEqual(decompressed), "Decompression failed"); {
ReadOnlySpan<byte> recompressed = recompress(data);
if (!recompressed.SequenceEqual(data))
{
Console.WriteLine("Decompression failed.");
if (recompressed.Length < data.Length)
{
Console.WriteLine($" New length smaller by {data.Length - recompressed.Length} bytes.");
}
else
{
for (int i = 0; i < data.Length; i++)
{
if (data[i] != recompressed[i])
Console.WriteLine($" Byte 0x{i:X4} differs: 0x{data[i]:X2} != 0x{recompressed[i]:X2}");
}
}
return false;
}
return true;
}
// Known to cause negative index bug writing undefined bytes: ??, ??, ??, ??
test(new byte[] { 0xFC, 0xFC, 0xC2, 0x92, 0x8F, 0x66, 0x33, 0x44, 0xEF, 0x40, 0xE9, 0xAB, 0x44, 0xEF, 0x40, 0x36 });
} }
private static void ConvertIgdImages() private static void ConvertIgdImages()

View File

@ -9,7 +9,6 @@
<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" />
<ProjectReference Include="..\Syroot.Worms.Mgame.GameServer\Syroot.Worms.Mgame.GameServer.csproj" />
<ProjectReference Include="..\Syroot.Worms.Mgame\Syroot.Worms.Mgame.csproj" /> <ProjectReference Include="..\Syroot.Worms.Mgame\Syroot.Worms.Mgame.csproj" />
<ProjectReference Include="..\Syroot.Worms.WorldParty\Syroot.Worms.WorldParty.csproj" /> <ProjectReference Include="..\Syroot.Worms.WorldParty\Syroot.Worms.WorldParty.csproj" />
<ProjectReference Include="..\Syroot.Worms.Worms2\Syroot.Worms.Worms2.csproj" /> <ProjectReference Include="..\Syroot.Worms.Worms2\Syroot.Worms.Worms2.csproj" />

View File

@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syroot.Worms.Test", "Syroot
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Mgame.GameServer", "Syroot.Worms.Mgame.GameServer\Syroot.Worms.Mgame.GameServer.csproj", "{392E4CA2-61D9-4BE1-B065-8801A9F102B8}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Mgame.GameServer", "Syroot.Worms.Mgame.GameServer\Syroot.Worms.Mgame.GameServer.csproj", "{392E4CA2-61D9-4BE1-B065-8801A9F102B8}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syroot.Worms.Mgame.Test", "Syroot.Worms.Mgame.Test\Syroot.Worms.Mgame.Test.csproj", "{212F8090-9775-4098-BD44-9ABC01FBE553}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -99,6 +101,10 @@ Global
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|Any CPU.Build.0 = Debug|Any CPU {392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|Any CPU.Build.0 = Release|Any CPU {392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|Any CPU.Build.0 = Release|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Debug|Any CPU.Build.0 = Debug|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Release|Any CPU.ActiveCfg = Release|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -117,6 +123,7 @@ Global
{EF308D4E-26A0-471C-B764-9C4EB713BEAE} = {99E56312-A064-4AD3-8443-0B56A5F76E6B} {EF308D4E-26A0-471C-B764-9C4EB713BEAE} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
{351B93B0-301F-42E1-82A0-7FA217154F5D} = {99E56312-A064-4AD3-8443-0B56A5F76E6B} {351B93B0-301F-42E1-82A0-7FA217154F5D} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
{392E4CA2-61D9-4BE1-B065-8801A9F102B8} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE} {392E4CA2-61D9-4BE1-B065-8801A9F102B8} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{212F8090-9775-4098-BD44-9ABC01FBE553} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1CD4EDE2-A5FB-4A58-A850-3506AB7E7B69} SolutionGuid = {1CD4EDE2-A5FB-4A58-A850-3506AB7E7B69}