Files
2026-05-22 00:27:09 +08:00

171 lines
6.4 KiB
C#

using System;
namespace GameRes.Formats.Entergram
{
internal static class QLZCompressor
{
public struct QLZHeader
{
public QLZHeader(byte[] src)
{
byte b = src[0];
this.Compressible = (b & CONTAINER_Compressible) == CONTAINER_Compressible;
if (this.Compressible)
{
b -= CONTAINER_Compressible;
}
if (b != STATIC_HEADER_FIRSTBYTE)
{
throw new Exception("Invalid QLZ Header: " + b.ToString());
}
this.CompressedSize = BitConverter.ToInt32(src, 1);
this.RawSize = BitConverter.ToInt32(src, 5);
}
public const int HEADER_LENGTH = 9;
public const byte STATIC_HEADER_FIRSTBYTE = 0x5E;
public const byte CONTAINER_Compressible = 1;
public const int Level = 3;
public bool Compressible;
public int CompressedSize;
public int RawSize;
}
public static byte[] Decompress(byte[] compressed)
{
QLZCompressor.QLZHeader qlzheader = new QLZCompressor.QLZHeader(compressed);
if (qlzheader.RawSize == 0)
{
return new byte[0];
}
byte[] array = new byte[qlzheader.RawSize];
if (!qlzheader.Compressible)
{
Array.Copy(compressed, QLZHeader.HEADER_LENGTH, array, 0, qlzheader.RawSize);
}
else
{
QLZCompressor.Decompress_Unsafe(compressed, array, qlzheader.RawSize);
}
return array;
}
private unsafe static void Decompress_Unsafe(byte[] compressed, byte[] decompressed, int rawSize)
{
if (rawSize < decompressed.Length)
{
throw new Exception("Decompressed Array is not enough size");
}
fixed (byte* ptr = compressed)
{
fixed (byte* ptr2 = decompressed)
{
int src = QLZHeader.HEADER_LENGTH;
int dst = 0;
uint cword_val = 1U;
int last_matchstart = rawSize - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END - 1;
uint fetch = 0U;
for (; ; )
{
if (cword_val == 1U)
{
cword_val = ReadUInt32(ptr + src);
src += 4;
if (dst <= last_matchstart)
{
fetch = ReadUInt32(ptr + src);
}
}
if ((cword_val & 1U) == 1U)
{
cword_val >>= 1;
uint offset;
uint matchlen;
if ((fetch & 3U) == 0U)
{
offset = (fetch & 0xFFU) >> 2;
matchlen = 3U;
src++;
}
else if ((fetch & 2U) == 0U)
{
offset = (fetch & 0xFFFFU) >> 2;
matchlen = 3U;
src += 2;
}
else if ((fetch & 1U) == 0U)
{
offset = (fetch & 0xFFFFU) >> 6;
matchlen = ((fetch >> 2) & 0xFU) + 3U;
src += 2;
}
else if ((fetch & 0x7FU) != 3U)
{
offset = (fetch >> 7) & 0x1FFFFU;
matchlen = ((fetch >> 2) & 0x1FU) + 2U;
src += 3;
}
else
{
offset = fetch >> 0xF;
matchlen = ((fetch >> 7) & 0xFFU) + 3U;
src += 4;
}
uint num7 = (uint)((long)dst - (long)((ulong)offset));
ptr2[dst] = ptr2[num7];
(ptr2 + dst)[1] = (ptr2 + num7)[1];
(ptr2 + dst)[2] = (ptr2 + num7)[2];
int num8 = 3;
while ((long)num8 < (long)((ulong)matchlen))
{
(ptr2 + dst)[num8] = (ptr2 + num7)[num8];
num8++;
}
dst += (int)matchlen;
fetch = ReadUInt32(ptr + src);
}
else
{
if (dst > last_matchstart)
{
break;
}
ptr2[dst] = ptr[src];
dst++;
src++;
cword_val >>= 1;
fetch = (uint)((((int)fetch >> 8) & 0xFFFF) | ((int)(ptr + src)[2] << 0x10) | ((int)(ptr + src)[3] << 0x18));
}
}
while (dst <= rawSize - 1)
{
if (cword_val == 1U)
{
src += 4;
cword_val = 0x80000000U;
}
ptr2[dst] = ptr[src];
dst++;
src++;
cword_val >>= 1;
}
}
}
}
private unsafe static uint ReadUInt32(byte* p)
{
return *(uint*)p;
}
private const int HASH_VALUES = 0x1000;
private const int MINOFFSET = 2;
private const int UNCONDITIONAL_MATCHLEN = 6;
private const int UNCOMPRESSED_END = 4;
private const int CWORD_LEN = 4;
private const int QLZ_POINTERS_3 = 0x10;
}
}