mirror of
https://gitlab.com/Syroot/Worms.git
synced 2025-03-04 09:25:22 +03:00
Clone original WWPA compression as close as possible due to bug found in original implementation.
This commit is contained in:
parent
197a958a98
commit
2d5fa21c00
@ -248,6 +248,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets
|
||||
{
|
||||
// Retrieve (decompressed) data size.
|
||||
ReadOnlySpan<byte> decompressedData = _packetDataStream.GetSpan();
|
||||
decompressedData = File.ReadAllBytes(@"D:\Pictures\data.bin");
|
||||
ReadOnlySpan<byte> compressedData = PacketCompression.Compress(decompressedData);
|
||||
|
||||
// Send head.
|
||||
|
@ -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
|
||||
/// 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. 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
|
||||
{
|
||||
// ---- 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 _cursor = 0;
|
||||
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;
|
||||
|
||||
int idx = 0;
|
||||
idx = 0;
|
||||
while (idx < decompressed.Length)
|
||||
{
|
||||
int value1 = 0;
|
||||
int value2 = -1;
|
||||
int bytesToRepeat = Math.Max(0, idx - 0xFF);
|
||||
bytesToRepeat = idx - 0xFF;
|
||||
if (idx - 0xFF < 0)
|
||||
bytesToRepeat = 0;
|
||||
idxDword = 0;
|
||||
shiftValue1 = -1;
|
||||
if (bytesToRepeat >= idx)
|
||||
{
|
||||
Write(compressor, decompressed[idx++]);
|
||||
c = decompressed[idx++];
|
||||
Write(compressor, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
int i;
|
||||
for (i = idx; i < decompressed.Length; i++)
|
||||
for (i = idx; i < decompressed.Length; ++i)
|
||||
{
|
||||
if (i - idx >= 0x111
|
||||
|| decompressed[bytesToRepeat] != decompressed[i])
|
||||
if (i - idx >= 0x111)
|
||||
break;
|
||||
bytesToRepeat++;
|
||||
if (decompressed[bytesToRepeat] != decompressed[i])
|
||||
break;
|
||||
++bytesToRepeat;
|
||||
}
|
||||
int offset = i - idx;
|
||||
if (offset >= 3 && offset > value1)
|
||||
offset = idx - i;
|
||||
v11 = i - idx;
|
||||
v12 = offset + bytesToRepeat;
|
||||
if (v11 >= 3 && v11 > idxDword)
|
||||
{
|
||||
value1 = offset;
|
||||
value2 = idx - 12;
|
||||
if (offset == 0x111)
|
||||
idxDword = v11;
|
||||
shiftValue1 = idx - 12;
|
||||
if (v11 == 0x111)
|
||||
break;
|
||||
}
|
||||
bytesToRepeat += idx - i + 1;
|
||||
} while (bytesToRepeat < idx);
|
||||
if (value1 != 0)
|
||||
bytesToRepeat = v12 + 1;
|
||||
}
|
||||
while (bytesToRepeat < idx);
|
||||
if (idxDword != 0)
|
||||
{
|
||||
TransferBuffer(compressor);
|
||||
compressor.Compress(true);
|
||||
if (value1 >= 18)
|
||||
if (idxDword >= 18)
|
||||
{
|
||||
compressor.Shift(0, 4);
|
||||
compressor.Shift(value2, 8);
|
||||
compressor.Shift(value1 - 18, 8);
|
||||
compressor.Shift(shiftValue1, 8);
|
||||
compressor.Shift(idxDword - 18, 8);
|
||||
field_10 = _field_10;
|
||||
++_field_C;
|
||||
_field_10 = idxDword - 3 + field_10;
|
||||
}
|
||||
else
|
||||
{
|
||||
compressor.Shift(value1 - 2, 4);
|
||||
compressor.Shift(value2 - 1, 8);
|
||||
compressor.Shift(idxDword - 2, 4);
|
||||
compressor.Shift(shiftValue1 - 1, 8);
|
||||
field_8 = _field_8;
|
||||
++_field_4;
|
||||
_field_8 = idxDword - 2 + field_8;
|
||||
}
|
||||
idx += value1;
|
||||
idx += idxDword;
|
||||
++_bufferDwords[idxDword];
|
||||
}
|
||||
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, 8);
|
||||
|
||||
return compressor.Buffer.AsSpan(0, compressor.Cursor);
|
||||
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 bytesToCopy;
|
||||
int idx;
|
||||
int bytesToCopyRemain;
|
||||
int idx = 0;
|
||||
int idxCopySrc;
|
||||
int v12;
|
||||
int v13;
|
||||
int bytesToCopy;
|
||||
|
||||
idx = 0;
|
||||
while (true)
|
||||
{
|
||||
while (true)
|
||||
@ -99,25 +152,27 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
bytesToCopy = decompressor.GetBytesToCopy(8);
|
||||
if (bytesToCopy != -1)
|
||||
{
|
||||
bytesToCopy++;
|
||||
++bytesToCopy;
|
||||
do
|
||||
{
|
||||
decompressed[idx++] = decompressor.Read();
|
||||
bytesToCopy--;
|
||||
--bytesToCopy;
|
||||
} while (bytesToCopy != 0);
|
||||
}
|
||||
}
|
||||
if (decompressor.GetBytesToCopy(4) + 2 <= 2)
|
||||
bytesToCopy = decompressor.GetBytesToCopy(4) + 2;
|
||||
if (bytesToCopy <= 2)
|
||||
break;
|
||||
bytesToCopy = decompressor.GetBytesToCopy(8) + 1;
|
||||
bytesToCopyRemain = bytesToCopy - 1;
|
||||
if (bytesToCopy != 0)
|
||||
{
|
||||
bytesToCopy++;
|
||||
++bytesToCopyRemain;
|
||||
do
|
||||
{
|
||||
int idxCopySrc = idx++ - bytesToCopy;
|
||||
bytesToCopyRemain--;
|
||||
idxCopySrc = idx++ - bytesToCopy;
|
||||
--bytesToCopyRemain;
|
||||
if (idxCopySrc >= 0) // Bugfix: Original implementation may calculate negative indices.
|
||||
decompressed[idx - 1] = decompressed[idxCopySrc];
|
||||
} while (bytesToCopyRemain != 0);
|
||||
}
|
||||
@ -125,16 +180,16 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
bytesToCopy = decompressor.GetBytesToCopy(8);
|
||||
if (bytesToCopy == 0)
|
||||
break;
|
||||
int v12 = decompressor.GetBytesToCopy(8);
|
||||
int v13 = v12 + 18;
|
||||
v12 = decompressor.GetBytesToCopy(8);
|
||||
v13 = v12 + 18;
|
||||
bytesToCopyRemain = v12 + 17;
|
||||
if (v13 != 0)
|
||||
{
|
||||
bytesToCopyRemain++;
|
||||
++bytesToCopyRemain;
|
||||
do
|
||||
{
|
||||
int idxCopySrc = idx++ - bytesToCopy;
|
||||
bytesToCopyRemain--;
|
||||
idxCopySrc = idx++ - bytesToCopy;
|
||||
--bytesToCopyRemain;
|
||||
decompressed[idx - 1] = decompressed[idxCopySrc];
|
||||
} while (bytesToCopyRemain != 0);
|
||||
}
|
||||
@ -146,20 +201,25 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
|
||||
private static void TransferBuffer(Compressor compressor)
|
||||
{
|
||||
if (_cursor != 0)
|
||||
int bufferCursor;
|
||||
int i;
|
||||
|
||||
bufferCursor = _bufferCursor;
|
||||
if (bufferCursor != 0)
|
||||
{
|
||||
_numBytesTransferred += bufferCursor;
|
||||
compressor.Compress(false);
|
||||
compressor.Shift(_cursor - 1, 8);
|
||||
for (int i = 0; i < _cursor; i++)
|
||||
compressor.Buffer[compressor.Cursor++] = _buffer[i];
|
||||
_cursor = 0;
|
||||
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[_cursor++] = c;
|
||||
if (_cursor >= _buffer.Length)
|
||||
_buffer[_bufferCursor++] = c;
|
||||
if (_bufferCursor >= _buffer.Length)
|
||||
TransferBuffer(compressor);
|
||||
}
|
||||
|
||||
@ -167,28 +227,41 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
|
||||
private class Compressor
|
||||
{
|
||||
internal byte[] Buffer = new byte[1024];
|
||||
internal byte[] Compressed = new byte[1024];
|
||||
internal int Cursor = 1;
|
||||
|
||||
private int _cursorCompress = 0;
|
||||
private int _shift = 0;
|
||||
|
||||
internal void Compress(bool doShift)
|
||||
internal void Compress(bool bShift)
|
||||
{
|
||||
if (doShift)
|
||||
Buffer[_cursorCompress] |= (byte)(0x80 >> _shift);
|
||||
if (++_shift == 8)
|
||||
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++;
|
||||
Buffer[_cursorCompress] = 0;
|
||||
cursorCompress = _cursorCompress;
|
||||
Cursor = cursor + 1;
|
||||
Compressed[cursorCompress] = 0;
|
||||
_shift = 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Shift(int value, int shiftPlus1)
|
||||
{
|
||||
int prevShift = 0;
|
||||
int shift = shiftPlus1 - 1;
|
||||
int shift;
|
||||
int prevShift;
|
||||
|
||||
shift = shiftPlus1 - 1;
|
||||
if (shiftPlus1 != 0)
|
||||
{
|
||||
do
|
||||
@ -198,11 +271,16 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
} while (prevShift != 0);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Write(byte c)
|
||||
{
|
||||
Compressed[Cursor++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
private class Decompressor
|
||||
{
|
||||
private byte[] _compressed;
|
||||
private readonly byte[] _compressed;
|
||||
private int _compressedCursor = 0;
|
||||
private byte _field_C;
|
||||
private byte _field_D;
|
||||
@ -212,7 +290,7 @@ namespace Syroot.Worms.Mgame.GameServer.Packets.WorldPartyAqua
|
||||
_compressed = compressed.ToArray();
|
||||
byte c = Read();
|
||||
_field_C = (byte)(2 * c | 1);
|
||||
_field_D = (byte)(c >> 7);
|
||||
_field_D = (byte)((uint)c >> 7);
|
||||
}
|
||||
|
||||
internal byte Read()
|
||||
|
65
src/Syroot.Worms.Mgame.Test/PacketCompressionTests.cs
Normal file
65
src/Syroot.Worms.Mgame.Test/PacketCompressionTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Syroot.Worms.Mgame.Test/Syroot.Worms.Mgame.Test.csproj
Normal file
12
src/Syroot.Worms.Mgame.Test/Syroot.Worms.Mgame.Test.csproj
Normal 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>
|
332
src/Syroot.Worms.Mgame/PacketCompression.cs
Normal file
332
src/Syroot.Worms.Mgame/PacketCompression.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
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
|
||||
{
|
||||
@ -14,19 +11,43 @@ namespace Syroot.Worms.Scratchpad
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
DecompressWwpaPacketData();
|
||||
TestWwpaPacketCompressionBug();
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
private static void DecompressWwpaPacketData()
|
||||
private static void TestWwpaPacketCompressionBug()
|
||||
{
|
||||
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> recompress(ReadOnlySpan<byte> data)
|
||||
{
|
||||
ReadOnlySpan<byte> compressed = PacketCompression.Compress(data);
|
||||
return PacketCompression.Decompress(compressed);
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> decompressedNew = PacketCompression.Decompress(compressed);
|
||||
Debug.Assert(decompressedNew.SequenceEqual(decompressed), "Decompression failed");
|
||||
bool test(byte[] data)
|
||||
{
|
||||
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()
|
||||
|
@ -9,7 +9,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Syroot.Worms.Armageddon.ProjectX\Syroot.Worms.Armageddon.ProjectX.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.WorldParty\Syroot.Worms.WorldParty.csproj" />
|
||||
<ProjectReference Include="..\Syroot.Worms.Worms2\Syroot.Worms.Worms2.csproj" />
|
||||
|
@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syroot.Worms.Test", "Syroot
|
||||
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}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -117,6 +123,7 @@ Global
|
||||
{EF308D4E-26A0-471C-B764-9C4EB713BEAE} = {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}
|
||||
{212F8090-9775-4098-BD44-9ABC01FBE553} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {1CD4EDE2-A5FB-4A58-A850-3506AB7E7B69}
|
||||
|
Loading…
x
Reference in New Issue
Block a user