mirror of
https://github.com/sp-tarkov/modules.git
synced 2025-02-13 09:50:43 -05:00
[enhancement] Simplify zlib (!43)
## Preface EFT has been reworking the `bsg.componentace.compression.libs.zlib` alot the past versions, including various enhancements and introduced bugs (zero-tail decompression infinite looping). This also includes working towards deprecating `SimpleZlib` and improving `ZOutputStream`'s performance. While working on Haru, @waffle.lord and I have been reworking the zlib code to be much simpler. These changes are compatible with Aki. ## The issue - The current code is complex to understand without experience with the zlib library in question - `Zlib.IsCompressed` has a bug when operating on < 3 bytes. The third statement is reached and thus out of bounds. ## Why fix this - Simplifying the code improves future readability. - Using `ZOutputStream` enables usage of further performance improvements made to `ZOutputStream` by BSG. - `Zlib.IsCompressed` will work again on `byte[2]` and below. ## Why was it like this in the first place? At the time of writing this code, there was poor understanding of how the zlib library worked. The implementation was a best-guess from decompiled code. ## What's affected? Only Zlib utility's internal code. No external libraries are affected. Co-authored-by: Merijn Hendriks <merijn.d.hendriks@gmail.com> Reviewed-on: SPT-AKI/Modules#43 Co-authored-by: Merijn Hendriks <senko-san@noreply.dev.sp-tarkov.com> Co-committed-by: Merijn Hendriks <senko-san@noreply.dev.sp-tarkov.com>
This commit is contained in:
parent
820619b0dc
commit
3c23adebe2
@ -1,127 +1,86 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ComponentAce.Compression.Libs.zlib;
|
using ComponentAce.Compression.Libs.zlib;
|
||||||
|
|
||||||
namespace Aki.Common.Utils
|
namespace Aki.Common.Utils
|
||||||
{
|
{
|
||||||
public enum ZlibCompression
|
public enum ZlibCompression
|
||||||
{
|
{
|
||||||
Store = 0,
|
Store = 0,
|
||||||
Fastest = 1,
|
Fastest = 1,
|
||||||
Fast = 3,
|
Fast = 3,
|
||||||
Normal = 5,
|
Normal = 5,
|
||||||
Ultra = 7,
|
Ultra = 7,
|
||||||
Maximum = 9
|
Maximum = 9
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Zlib
|
public static class Zlib
|
||||||
{
|
{
|
||||||
// Level | CM/CI FLG
|
/// <summary>
|
||||||
// ----- | ---------
|
/// Check if the file is ZLib compressed
|
||||||
// 1 | 78 01
|
/// </summary>
|
||||||
// 2 | 78 5E
|
/// <param name="data">Data</param>
|
||||||
// 3 | 78 5E
|
/// <returns>If the file is Zlib compressed</returns>
|
||||||
// 4 | 78 5E
|
public static bool IsCompressed(byte[] data)
|
||||||
// 5 | 78 5E
|
{
|
||||||
// 6 | 78 9C
|
if (data == null || data.Length < 3)
|
||||||
// 7 | 78 DA
|
{
|
||||||
// 8 | 78 DA
|
return false;
|
||||||
// 9 | 78 DA
|
}
|
||||||
|
|
||||||
/// <summary>
|
// data[0]: Info (CM/CINFO) Header; must be 0x78
|
||||||
/// Check if the file is ZLib compressed
|
if (data[0] != 0x78)
|
||||||
/// </summary>
|
{
|
||||||
/// <param name="Data">Data</param>
|
return false;
|
||||||
/// <returns>If the file is Zlib compressed</returns>
|
}
|
||||||
public static bool IsCompressed(byte[] Data)
|
|
||||||
{
|
|
||||||
// We need the first two bytes;
|
|
||||||
// First byte: Info (CM/CINFO) Header, should always be 0x78
|
|
||||||
// Second byte: Flags (FLG) Header, should define our compression level.
|
|
||||||
|
|
||||||
if (Data == null || Data.Length < 3 || Data[0] != 0x78)
|
// data[1]: Flags (FLG) Header; compression level.
|
||||||
{
|
switch (data[1])
|
||||||
return false;
|
{
|
||||||
}
|
case 0x01: // [0x78 0x01] level 0-2: fastest
|
||||||
|
case 0x5E: // [0x78 0x5E] level 3-4: low
|
||||||
|
case 0x9C: // [0x78 0x9C] level 5-6: normal
|
||||||
|
case 0xDA: // [0x78 0xDA] level 7-9: max
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
switch (Data[1])
|
return false;
|
||||||
{
|
}
|
||||||
case 0x01: // fastest
|
|
||||||
case 0x5E: // low
|
|
||||||
case 0x9C: // normal
|
|
||||||
case 0xDA: // max
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
private static byte[] Run(byte[] data, ZlibCompression level)
|
||||||
}
|
{
|
||||||
|
// ZOutputStream.Close() flushes itself.
|
||||||
|
// ZOutputStream.Flush() flushes the target stream.
|
||||||
|
// It's fucking stupid, but whatever.
|
||||||
|
// -- Waffle.Lord, 2022-12-01
|
||||||
|
|
||||||
/// <summary>
|
using (var ms = new MemoryStream())
|
||||||
/// Deflate data.
|
{
|
||||||
/// </summary>
|
using (var zs = (level > ZlibCompression.Store)
|
||||||
public static byte[] Compress(byte[] data, ZlibCompression level)
|
? new ZOutputStream(ms, (int)level)
|
||||||
{
|
: new ZOutputStream(ms))
|
||||||
byte[] buffer = new byte[data.Length + 24];
|
{
|
||||||
|
zs.Write(data, 0, data.Length);
|
||||||
|
}
|
||||||
|
// <-- zs flushes everything here
|
||||||
|
|
||||||
ZStream zs = new ZStream()
|
return ms.ToArray();
|
||||||
{
|
}
|
||||||
avail_in = data.Length,
|
}
|
||||||
next_in = data,
|
|
||||||
next_in_index = 0,
|
|
||||||
avail_out = buffer.Length,
|
|
||||||
next_out = buffer,
|
|
||||||
next_out_index = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
zs.deflateInit((int)level);
|
/// <summary>
|
||||||
zs.deflate(zlibConst.Z_FINISH);
|
/// Deflate data.
|
||||||
|
/// </summary>
|
||||||
data = new byte[zs.next_out_index];
|
public static byte[] Compress(byte[] data, ZlibCompression level)
|
||||||
Array.Copy(zs.next_out, 0, data, 0, zs.next_out_index);
|
{
|
||||||
|
return Run(data, level);
|
||||||
return data;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inflate data.
|
/// Inflate data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static byte[] Decompress(byte[] data)
|
public static byte[] Decompress(byte[] data)
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[4096];
|
return Run(data, ZlibCompression.Store);
|
||||||
|
}
|
||||||
ZStream zs = new ZStream()
|
}
|
||||||
{
|
|
||||||
avail_in = data.Length,
|
|
||||||
next_in = data,
|
|
||||||
next_in_index = 0,
|
|
||||||
avail_out = buffer.Length,
|
|
||||||
next_out = buffer,
|
|
||||||
next_out_index = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
zs.inflateInit();
|
|
||||||
|
|
||||||
using (MemoryStream ms = new MemoryStream())
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
zs.avail_out = buffer.Length;
|
|
||||||
zs.next_out = buffer;
|
|
||||||
zs.next_out_index = 0;
|
|
||||||
|
|
||||||
int result = zs.inflate(0);
|
|
||||||
|
|
||||||
if (result != 0 && result != 1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.Write(zs.next_out, 0, zs.next_out_index);
|
|
||||||
}
|
|
||||||
while (zs.avail_in > 0 || zs.avail_out == 0);
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user