mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-06 13:48:57 +08:00
171 lines
6.4 KiB
C#
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;
|
|
}
|
|
}
|