context: // 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
  
  
  using System;
  
  namespace Libarius.Compression.QuickLZ
  {
      /// <summary>
      ///     QuickLZ data compression library
      /// </summary>
      public static class QuickLZ
      {
          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 static int headerLen(byte[] source)
          {
              return ((source[0] & 2) == 2) ? 9 : 3;
          }
  
          /// <summary>
          ///     Returns the size in bytes needed to store the decompressed data.
          /// </summary>
          /// <param name="source">Compressed content.</param>
          /// <returns>The decompressed size in bytes.</returns>
          public static int sizeDecompressed(byte[] source)
          {
              if (headerLen(source) == 9)
                  return source[5] | (source[6] << 8) | (source[7] << 16) | (source[8] << 24);
              return source[2];
          }
  
          /// <summary>
          ///     Returns the size in bytes needed to store the compressed data.
          /// </summary>
          /// <param name="source">Uncompressed content.</param>
          /// <returns>The compressed size in bytes.</returns>
          public static int sizeCompressed(byte[] source)
          {
              if (headerLen(source) == 9)
                  return source[1] | (source[2] << 8) | (source[3] << 16) | (source[4] << 24);
              return source[1];
          }
  
          private static void write_header(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);
              fast_write(dst, 1, size_decompressed, 4);
              fast_write(dst, 5, size_compressed, 4);
          }
  
          /// <summary>
          ///     Compresses the supplied byte array at the given level.
          /// </summary>
          /// <param name="source">The uncompressed content.</param>
          /// <param name="level">The compression level to use.</param>
          /// <returns></returns>
          public static byte[] compress(byte[] source, int level)
          {
              var src = 0;
              var dst = DEFAULT_HEADERLEN + CWORD_LEN;
              var cword_val = 0x80000000;
              var cword_ptr = DEFAULT_HEADERLEN;
              var destination = new byte[source.Length + 400];
              int[,] hashtable;
              var cachetable = new int[HASH_VALUES];
              var hash_counter = new byte[HASH_VALUES];
              byte[] d2;
              var fetch = 0;
              var last_matchstart = (source.Length - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END - 1);
              var lits = 0;
  
              if (level != 1 && level != 3)
                  throw new ArgumentException("C# version only supports level 1 and 3");
  
              if (level == 1)
                  hashtable = new int[HASH_VALUES, QLZ_POINTERS_1];
              else
                  hashtable = new int[HASH_VALUES, QLZ_POINTERS_3];
  
              if (source.Length == 0)
                  return new byte[0];
  
              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 > source.Length >> 1 && dst > src - (src >> 5))
                      {
                          d2 = new byte[source.Length + DEFAULT_HEADERLEN];
                          write_header(d2, level, false, source.Length, source.Length + DEFAULT_HEADERLEN);
                          Array.Copy(source, 0, d2, DEFAULT_HEADERLEN, source.Length);
                          return d2;
                      }
  
                      fast_write(destination, cword_ptr, (int) ((cword_val >> 1) | 0x80000000), 4);
                      cword_ptr = dst;
                      dst += CWORD_LEN;
                      cword_val = 0x80000000;
                  }
  
                  if (level == 1)
                  {
                      var hash = ((fetch >> 12) ^ fetch) & (HASH_VALUES - 1);
                      var o = hashtable[hash, 0];
                      var 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])
                          {
                              var f = 3 - 2 | (hash << 4);
                              destination[dst + 0] = (byte) (f >> 0*8);
                              destination[dst + 1] = (byte) (f >> 1*8);
                              src += 3;
                              dst += 2;
                          }
                          else
                          {
                              var old_src = src;
                              var remaining = ((source.Length - UNCOMPRESSED_END - src + 1 - 1) > 255
                                  ? 255
                                  : (source.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++;
                                  }
                              }
  
                              var matchlen = src - old_src;
  
                              hash <<= 4;
                              if (matchlen < 18)
                              {
                                  var f = (hash | (matchlen - 2));
                                  destination[dst + 0] = (byte) (f >> 0*8);
                                  destination[dst + 1] = (byte) (f >> 1*8);
                                  dst += 2;
                              }
                              else
                              {
                                  fast_write(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;
                      var remaining = ((source.Length - UNCOMPRESSED_END - src + 1 - 1) > 255
                          ? 255
                          : (source.Length - UNCOMPRESSED_END - src + 1 - 1));
                      var 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)
                      {
                          var offset = src - o;
  
                          for (var 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)
                          {
                              fast_write(destination, dst, offset << 2, 1);
                              dst++;
                          }
                          else if (matchlen == 3 && offset <= 16383)
                          {
                              fast_write(destination, dst, (offset << 2) | 1, 2);
                              dst += 2;
                          }
                          else if (matchlen <= 18 && offset <= 1023)
                          {
                              fast_write(destination, dst, ((matchlen - 3) << 2) | (offset << 6) | 2, 2);
                              dst += 2;
                          }
                          else if (matchlen <= 33)
                          {
                              fast_write(destination, dst, ((matchlen - 2) << 2) | (offset << 7) | 3, 3);
                              dst += 3;
                          }
                          else
                          {
                              fast_write(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 <= source.Length - 1)
              {
                  if ((cword_val & 1) == 1)
                  {
                      fast_write(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);
              }
              fast_write(destination, cword_ptr, (int) ((cword_val >> 1) | 0x80000000), CWORD_LEN);
              write_header(destination, level, true, source.Length, dst);
              d2 = new byte[dst];
              Array.Copy(destination, d2, dst);
              return d2;
          }
  
          private static void fast_write(byte[] a, int i, int value, int numbytes)
          {
              for (var j = 0; j < numbytes; j++)
                  a[i + j] = (byte) (value >> (j*8));
          }
  
          public static byte[] decompress(byte[] source)
          {
              int level;
              var size = sizeDecompressed(source);
              var src = headerLen(source);
              var dst = 0;
              uint cword_val = 1;
              var destination = new byte[size];
              var hashtable = new int[4096];
              var hash_counter = new byte[4096];
              var last_matchstart = size - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END - 1;
              var last_hashed = -1;
              int hash;
              uint fetch = 0;
  
              level = (source[0] >> 2) & 0x3;
  
              if (level != 1 && level != 3)
                  throw new ArgumentException("C# version only supports level 1 and 3");
  
              if ((source[0] & 1) != 1)
              {
                  var d2 = new byte[size];
                  Array.Copy(source, headerLen(source), d2, 0, 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
                          {
  
gt:                             matchlen = source[src + 2];