using System; namespace xServer.Core.Compression { // QuickLZ data compression library // Copyright (C) 2006-2011 Lasse Mikkel Reinhold // lar@quicklz.com // // QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything // released into public must be open source) or under a commercial license if such // has been acquired (see http://www.quicklz.com/order.html). The commercial license // does not cover derived or ported versions created by third parties under GPL. // // Only a subset of the C library has been ported, namely level 1 and 3 not in // streaming mode. // // Version: 1.5.0 final public class SafeQuickLZ { public const int QLZ_VERSION_MAJOR = 1; public const int QLZ_VERSION_MINOR = 5; public const int QLZ_VERSION_REVISION = 0; // Streaming mode not supported public const int QLZ_STREAMING_BUFFER = 0; // Bounds checking not supported Use try...catch instead public const int QLZ_MEMORY_SAFE = 0; // Decrease QLZ_POINTERS_3 to increase level 3 compression speed. Do not edit any other values! private const int HASH_VALUES = 4096; 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 DEFAULT_HEADERLEN = 9; private const int QLZ_POINTERS_1 = 1; private const int QLZ_POINTERS_3 = 16; private int HeaderLen(byte[] source, int offset) { return ((source[offset] & 2) == 2) ? 9 : 3; } public int SizeDecompressed(byte[] source, int offset) { if (HeaderLen(source, offset) == 9) return source[offset + 5] | (source[offset + 6] << 8) | (source[offset + 7] << 16) | (source[offset + 8] << 24); else return source[offset + 2]; } public int SizeCompressed(byte[] source, int offset) { if (HeaderLen(source, offset) == 9) return source[offset + 1] | (source[offset + 2] << 8) | (source[offset + 3] << 16) | (source[offset + 4] << 24); else return source[offset + 1]; } private void WriteHeader(byte[] dst, int level, bool compressible, int size_compressed, int size_decompressed) { dst[0] = (byte) (2 | (compressible ? 1 : 0)); dst[0] |= (byte) (level << 2); dst[0] |= (1 << 6); dst[0] |= (0 << 4); FastWrite(dst, 1, size_decompressed, 4); FastWrite(dst, 5, size_compressed, 4); } public byte[] Compress(byte[] source, int Offset, int Length, int level) { if (Length == 0) return new byte[0]; int src = Offset; int dst = DEFAULT_HEADERLEN + CWORD_LEN; uint cword_val = 0x80000000; int cword_ptr = DEFAULT_HEADERLEN; byte[] destination = new byte[Length + 400]; int[,] hashtable; int[] cachetable = new int[HASH_VALUES]; byte[] hash_counter = new byte[HASH_VALUES]; byte[] d2; int fetch = 0; int last_matchstart = (Length - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END - 1); int lits = 0; switch (level) { case 1: hashtable = new int[HASH_VALUES, QLZ_POINTERS_1]; break; case 3: hashtable = new int[HASH_VALUES, QLZ_POINTERS_3]; break; default: throw new ArgumentException("C# version only supports level 1 and 3"); } if (src <= last_matchstart) fetch = source[src] | (source[src + 1] << 8) | (source[src + 2] << 16); while (src <= last_matchstart) { if ((cword_val & 1) == 1) { if (src > Length >> 1 && dst > src - (src >> 5)) { d2 = new byte[Length + DEFAULT_HEADERLEN]; WriteHeader(d2, level, false, Length, Length + DEFAULT_HEADERLEN); Array.Copy(source, 0, d2, DEFAULT_HEADERLEN, Length); return d2; } FastWrite(destination, cword_ptr, (int) ((cword_val >> 1) | 0x80000000), 4); cword_ptr = dst; dst += CWORD_LEN; cword_val = 0x80000000; } if (level == 1) { int hash = ((fetch >> 12) ^ fetch) & (HASH_VALUES - 1); int o = hashtable[hash, 0]; int cache = cachetable[hash] ^ fetch; cachetable[hash] = fetch; hashtable[hash, 0] = src; if (cache == 0 && hash_counter[hash] != 0 && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > 3 && source[src] == source[src - 3] && source[src] == source[src - 2] && source[src] == source[src - 1] && source[src] == source[src + 1] && source[src] == source[src + 2]))) { cword_val = ((cword_val >> 1) | 0x80000000); if (source[o + 3] != source[src + 3]) { int f = 3 - 2 | (hash << 4); destination[dst + 0] = (byte) (f >> 0*8); destination[dst + 1] = (byte) (f >> 1*8); src += 3; dst += 2; } else { int old_src = src; int remaining = ((Length - UNCOMPRESSED_END - src + 1 - 1) > 255 ? 255 : (Length - UNCOMPRESSED_END - src + 1 - 1)); src += 4; if (source[o + src - old_src] == source[src]) { src++; if (source[o + src - old_src] == source[src]) { src++; while (source[o + (src - old_src)] == source[src] && (src - old_src) < remaining) src++; } } int matchlen = src - old_src; hash <<= 4; if (matchlen < 18) { int f = (hash | (matchlen - 2)); destination[dst + 0] = (byte) (f >> 0*8); destination[dst + 1] = (byte) (f >> 1*8); dst += 2; } else { FastWrite(destination, dst, hash | (matchlen << 16), 3); dst += 3; } } fetch = source[src] | (source[src + 1] << 8) | (source[src + 2] << 16); lits = 0; } else { lits++; hash_counter[hash] = 1; destination[dst] = source[src]; cword_val = (cword_val >> 1); src++; dst++; fetch = ((fetch >> 8) & 0xffff) | (source[src + 2] << 16); } } else { fetch = source[src] | (source[src + 1] << 8) | (source[src + 2] << 16); int o, offset2; int matchlen, k, m, best_k = 0; byte c; int remaining = ((Length - UNCOMPRESSED_END - src + 1 - 1) > 255 ? 255 : (Length - UNCOMPRESSED_END - src + 1 - 1)); int hash = ((fetch >> 12) ^ fetch) & (HASH_VALUES - 1); c = hash_counter[hash]; matchlen = 0; offset2 = 0; for (k = 0; k < QLZ_POINTERS_3 && c > k; k++) { o = hashtable[hash, k]; if ((byte) fetch == source[o] && (byte) (fetch >> 8) == source[o + 1] && (byte) (fetch >> 16) == source[o + 2] && o < src - MINOFFSET) { m = 3; while (source[o + m] == source[src + m] && m < remaining) m++; if ((m > matchlen) || (m == matchlen && o > offset2)) { offset2 = o; matchlen = m; best_k = k; } } } o = offset2; hashtable[hash, c & (QLZ_POINTERS_3 - 1)] = src; c++; hash_counter[hash] = c; if (matchlen >= 3 && src - o < 131071) { int offset = src - o; for (int u = 1; u < matchlen; u++) { fetch = source[src + u] | (source[src + u + 1] << 8) | (source[src + u + 2] << 16); hash = ((fetch >> 12) ^ fetch) & (HASH_VALUES - 1); c = hash_counter[hash]++; hashtable[hash, c & (QLZ_POINTERS_3 - 1)] = src + u; } src += matchlen; cword_val = ((cword_val >> 1) | 0x80000000); if (matchlen == 3 && offset <= 63) { FastWrite(destination, dst, offset << 2, 1); dst++; } else if (matchlen == 3 && offset <= 16383) { FastWrite(destination, dst, (offset << 2) | 1, 2); dst += 2; } else if (matchlen <= 18 && offset <= 1023) { FastWrite(destination, dst, ((matchlen - 3) << 2) | (offset << 6) | 2, 2); dst += 2; } else if (matchlen <= 33) { FastWrite(destination, dst, ((matchlen - 2) << 2) | (offset << 7) | 3, 3); dst += 3; } else { FastWrite(destination, dst, ((matchlen - 3) << 7) | (offset << 15) | 3, 4); dst += 4; } lits = 0; } else { destination[dst] = source[src]; cword_val = (cword_val >> 1); src++; dst++; } } } while (src <= Length - 1) { if ((cword_val & 1) == 1) { FastWrite(destination, cword_ptr, (int) ((cword_val >> 1) | 0x80000000), 4); cword_ptr = dst; dst += CWORD_LEN; cword_val = 0x80000000; } destination[dst] = source[src]; src++; dst++; cword_val = (cword_val >> 1); } while ((cword_val & 1) != 1) { cword_val = (cword_val >> 1); } FastWrite(destination, cword_ptr, (int) ((cword_val >> 1) | 0x80000000), CWORD_LEN); WriteHeader(destination, level, true, Length, dst); d2 = new byte[dst]; Array.Copy(destination, d2, dst); return d2; } private void FastWrite(byte[] a, int i, int value, int numbytes) { for (int j = 0; j < numbytes; j++) a[i + j] = (byte) (value >> (j*8)); } public byte[] Decompress(byte[] source, int Offset, int Length) { int level = (source[Offset] >> 2) & 0x3; if (level != 1 && level != 3) throw new ArgumentException("C# version only supports level 1 and 3"); int size = SizeDecompressed(source, Offset); int src = HeaderLen(source, Offset) + Offset; int dst = 0; uint cword_val = 1; byte[] destination = new byte[size]; int[] hashtable = new int[4096]; byte[] hash_counter = new byte[4096]; int last_matchstart = size - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END - 1; int last_hashed = -1; int hash; uint fetch = 0; if ((source[Offset] & 1) != 1) { byte[] d2 = new byte[size]; Array.Copy(source, HeaderLen(source, Offset), d2, Offset, size); return d2; } for (;;) { if (cword_val == 1) { cword_val = (uint) (source[src] | (source[src + 1] << 8) | (source[src + 2] << 16) | (source[src + 3] << 24)); src += 4; if (dst <= last_matchstart) { if (level == 1) fetch = (uint) (source[src] | (source[src + 1] << 8) | (source[src + 2] << 16)); else fetch = (uint) (source[src] | (source[src + 1] << 8) | (source[src + 2] << 16) | (source[src + 3] << 24)); } } if ((cword_val & 1) == 1) { uint matchlen; uint offset2; cword_val = cword_val >> 1; if (level == 1) { hash = ((int) fetch >> 4) & 0xfff; offset2 = (uint) hashtable[hash]; if ((fetch & 0xf) != 0) { matchlen = (fetch & 0xf) + 2; src += 2; } else { matchlen = source[src + 2]; src += 3; } } else { uint offset; if ((fetch & 3) == 0) { offset = (fetch & 0xff) >> 2; matchlen = 3; src++; } else if ((fetch & 2) == 0) { offset = (fetch & 0xffff) >> 2; matchlen = 3; src += 2; } else if ((fetch & 1) == 0) { offset = (fetch & 0xffff) >> 6; matchlen = ((fetch >> 2) & 15) + 3; src += 2; } else if ((fetch & 127) != 3) { offset = (fetch >> 7) & 0x1ffff; matchlen = ((fetch >> 2) & 0x1f) + 2; src += 3; } else { offset = (fetch >> 15); matchlen = ((fetch >> 7) & 255) + 3; src += 4; } offset2 = (uint) (dst - offset); } destination[dst + 0] = destination[offset2 + 0]; destination[dst + 1] = destination[offset2 + 1]; destination[dst + 2] = destination[offset2 + 2]; for (int i = 3; i < matchlen; i += 1) { destination[dst + i] = destination[offset2 + i]; } dst += (int) matchlen; if (level == 1) { fetch = (uint) (destination[last_hashed + 1] | (destination[last_hashed + 2] << 8) | (destination[last_hashed + 3] << 16)); while (last_hashed < dst - matchlen) { last_hashed++; hash = (int) (((fetch >> 12) ^ fetch) & (HASH_VALUES - 1)); hashtable[hash] = last_hashed; hash_counter[hash] = 1; fetch = (uint) (fetch >> 8 & 0xffff | destination[last_hashed + 3] << 16); } fetch = (uint) (source[src] | (source[src + 1] << 8) | (source[src + 2] << 16)); } else { fetch = (uint) (source[src] | (source[src + 1] << 8) | (source[src + 2] << 16) | (source[src + 3] << 24)); } last_hashed = dst - 1; } else { if (dst <= last_matchstart) { destination[dst] = source[src]; dst += 1; src += 1; cword_val = cword_val >> 1; if (level == 1) { while (last_hashed < dst - 3) { last_hashed++; int fetch2 = destination[last_hashed] | (destination[last_hashed + 1] << 8) | (destination[last_hashed + 2] << 16); hash = ((fetch2 >> 12) ^ fetch2) & (HASH_VALUES - 1); hashtable[hash] = last_hashed; hash_counter[hash] = 1; } fetch = (uint) (fetch >> 8 & 0xffff | source[src + 2] << 16); } else { fetch = (uint) (fetch >> 8 & 0xffff | source[src + 2] << 16 | source[src + 3] << 24); } } else { while (dst <= size - 1) { if (cword_val == 1) { src += CWORD_LEN; cword_val = 0x80000000; } destination[dst] = source[src]; dst++; src++; cword_val = cword_val >> 1; } return destination; } } } } } }