Files
ItsTheSky d6d621dc92 Add project restore files and NuGet cache for GsaViewer
- Created project.nuget.cache to store NuGet package cache information.
- Added project.packagespec.json to define project restore settings and dependencies.
- Included rider.project.restore.info for Rider IDE integration.
2026-04-09 16:56:58 +02:00

89 lines
3.3 KiB
C#

using System.IO.Compression;
namespace GsaEditor.Core.Compression;
/// <summary>
/// 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 <see cref="DeflateStream"/> for the core deflate data.
/// </summary>
public static class ZlibHelper
{
/// <summary>
/// Decompresses zlib-wrapped data into a byte array of the specified original size.
/// Skips the 2-byte zlib header before feeding data to <see cref="DeflateStream"/>.
/// </summary>
/// <param name="compressedData">The zlib-wrapped compressed data (header + deflate + checksum).</param>
/// <param name="originalLength">The expected decompressed size in bytes.</param>
/// <returns>The decompressed byte array.</returns>
/// <exception cref="InvalidDataException">Thrown when decompression fails or data is corrupt.</exception>
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;
}
/// <summary>
/// Compresses data using zlib format: 2-byte header + deflate stream + 4-byte Adler-32 checksum.
/// </summary>
/// <param name="data">The uncompressed data to compress.</param>
/// <returns>The zlib-wrapped compressed byte array.</returns>
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();
}
/// <summary>
/// Computes the Adler-32 checksum of the given data.
/// </summary>
/// <param name="data">The data to checksum.</param>
/// <returns>The 32-bit Adler-32 checksum value.</returns>
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;
}
}