using System; namespace axXez.TS3.Protocol { internal static class QuickLz { private const int TableSize = 4096; private const int MaxDecompressedSize = 1024 * 1024; public static int GetDecompressedSize(byte[] data) => (data[0] & 0x02) != 0 ? BitConverter.ToInt32(data, 5) : data[2]; public static int GetCompressedSize(byte[] data) => (data[0] & 0x02) != 0 ? BitConverter.ToInt32(data, 1) : data[1]; public static byte[] Decompress(byte[] data) { byte flags = data[0]; int level = (flags >> 2) & 0x03; if (level != 1) throw new NotSupportedException($"QuickLZ level {level} is not supported."); int headerLen = (flags & 0x02) != 0 ? 9 : 3; int decompressedSize = GetDecompressedSize(data); if (decompressedSize > MaxDecompressedSize) throw new InvalidOperationException($"Decompressed size {decompressedSize} exceeds limit."); byte[] dest = new byte[decompressedSize]; if ((flags & 0x01) == 0) { Buffer.BlockCopy(data, headerLen, dest, 0, decompressedSize); return dest; } int[] hashtable = new int[TableSize]; uint control = 1; int sourcePos = headerLen; int destPos = 0; int nextHashed = 0; while (true) { if (control == 1) { control = BitConverter.ToUInt32(data, sourcePos); sourcePos += 4; } if ((control & 1) != 0) { control >>= 1; int next = data[sourcePos++]; int hashIdx = (next >> 4) | (data[sourcePos++] << 4); int matchLen = next & 0x0f; matchLen = matchLen != 0 ? matchLen + 2 : data[sourcePos++]; int offset = hashtable[hashIdx]; dest[destPos] = dest[offset]; dest[destPos + 1] = dest[offset + 1]; dest[destPos + 2] = dest[offset + 2]; for (int i = 3; i < matchLen; i++) dest[destPos + i] = dest[offset + i]; destPos += matchLen; int end = destPos + 1 - matchLen; if (nextHashed < end) { int nxt = Read24(dest, nextHashed); hashtable[Hash(nxt)] = nextHashed; for (int i = nextHashed + 1; i < end; i++) { nxt = (nxt >> 8) | (dest[i + 2] << 16); hashtable[Hash(nxt)] = i; } } nextHashed = destPos; } else if (destPos >= Math.Max(decompressedSize, 10) - 10) { while (destPos < decompressedSize) { if (control == 1) sourcePos += 4; control >>= 1; dest[destPos++] = data[sourcePos++]; } break; } else { dest[destPos++] = data[sourcePos++]; control >>= 1; int end = Math.Max(destPos - 2, 0); if (nextHashed < end) { int nxt = Read24(dest, nextHashed); hashtable[Hash(nxt)] = nextHashed; for (int i = nextHashed + 1; i < end; i++) { nxt = (nxt >> 8) | (dest[i + 2] << 16); hashtable[Hash(nxt)] = i; } } nextHashed = Math.Max(nextHashed, end); } } return dest; } private static int Read24(byte[] buf, int off) => buf[off] | (buf[off + 1] << 8) | (buf[off + 2] << 16); private static int Hash(int value) => ((value >> 12) ^ value) & 0xfff; } }