using System.IO.Compression;
namespace GsaEditor.Core.Compression;
///
/// Provides zlib compression and decompression utilities for GSA archive entries.
/// The GSA format stores zlib-wrapped data: 2-byte header (CMF+FLG) + deflate stream + 4-byte Adler-32 checksum.
/// This helper skips the 2-byte zlib header and uses for the core deflate data.
///
public static class ZlibHelper
{
///
/// Decompresses zlib-wrapped data into a byte array of the specified original size.
/// Skips the 2-byte zlib header before feeding data to .
///
/// The zlib-wrapped compressed data (header + deflate + checksum).
/// The expected decompressed size in bytes.
/// The decompressed byte array.
/// Thrown when decompression fails or data is corrupt.
public static byte[] Decompress(byte[] compressedData, uint originalLength)
{
if (compressedData.Length < 2)
throw new InvalidDataException("Compressed data is too short to contain a zlib header.");
// Skip the 2-byte zlib header (typically 0x78 0x9C for default compression)
using var ms = new MemoryStream(compressedData, 2, compressedData.Length - 2);
using var deflate = new DeflateStream(ms, CompressionMode.Decompress);
var result = new byte[originalLength];
int totalRead = 0;
while (totalRead < result.Length)
{
int bytesRead = deflate.Read(result, totalRead, result.Length - totalRead);
if (bytesRead == 0)
break;
totalRead += bytesRead;
}
return result;
}
///
/// Compresses data using zlib format: 2-byte header + deflate stream + 4-byte Adler-32 checksum.
///
/// The uncompressed data to compress.
/// The zlib-wrapped compressed byte array.
public static byte[] Compress(byte[] data)
{
using var output = new MemoryStream();
// Write zlib header: CMF=0x78 (deflate method, 32K window), FLG=0x9C (default level, valid check bits)
output.WriteByte(0x78);
output.WriteByte(0x9C);
using (var deflate = new DeflateStream(output, CompressionLevel.Optimal, leaveOpen: true))
{
deflate.Write(data, 0, data.Length);
}
// Write Adler-32 checksum in big-endian byte order
uint adler = ComputeAdler32(data);
output.WriteByte((byte)(adler >> 24));
output.WriteByte((byte)(adler >> 16));
output.WriteByte((byte)(adler >> 8));
output.WriteByte((byte)adler);
return output.ToArray();
}
///
/// Computes the Adler-32 checksum of the given data.
///
/// The data to checksum.
/// The 32-bit Adler-32 checksum value.
private static uint ComputeAdler32(byte[] data)
{
const uint MOD_ADLER = 65521;
uint a = 1, b = 0;
for (int i = 0; i < data.Length; i++)
{
a = (a + data[i]) % MOD_ADLER;
b = (b + a) % MOD_ADLER;
}
return (b << 16) | a;
}
}