DotRecastNetSim/src/DotRecast.Detour.TileCache/Io/Compress/FastLz.cs

697 lines
24 KiB
C#

/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
using System;
namespace DotRecast.Detour.TileCache.Io.Compress
{
/**
* Core of FastLZ compression algorithm.
*
* This class provides methods for compression and decompression of buffers and saves
* constants which use by FastLzFrameEncoder and FastLzFrameDecoder.
*
* This is refactored code of <a href="https://code.google.com/p/jfastlz/">jfastlz</a>
* library written by William Kinney.
*/
public class FastLz
{
private static readonly int MAX_DISTANCE = 8191;
private static readonly int MAX_FARDISTANCE = 65535 + MAX_DISTANCE - 1;
private static readonly int HASH_LOG = 13;
private static readonly int HASH_SIZE = 1 << HASH_LOG; // 8192
private static readonly int HASH_MASK = HASH_SIZE - 1;
private static readonly int MAX_COPY = 32;
private static readonly int MAX_LEN = 256 + 8;
private static readonly int MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 = 1024 * 64;
static readonly int MAGIC_NUMBER = 'F' << 16 | 'L' << 8 | 'Z';
static readonly byte BLOCK_TYPE_NON_COMPRESSED = 0x00;
static readonly byte BLOCK_TYPE_COMPRESSED = 0x01;
static readonly byte BLOCK_WITHOUT_CHECKSUM = 0x00;
static readonly byte BLOCK_WITH_CHECKSUM = 0x10;
static readonly int OPTIONS_OFFSET = 3;
static readonly int CHECKSUM_OFFSET = 4;
static readonly int MAX_CHUNK_LENGTH = 0xFFFF;
/**
* Do not call {@link #compress(byte[], int, int, byte[], int, int)} for input buffers
* which length less than this value.
*/
static readonly int MIN_LENGTH_TO_COMPRESSION = 32;
/**
* In this case {@link #compress(byte[], int, int, byte[], int, int)} will choose level
* automatically depending on the length of the input buffer. If length less than
* {@link #MIN_RECOMENDED_LENGTH_FOR_LEVEL_2} {@link #LEVEL_1} will be choosen,
* otherwise {@link #LEVEL_2}.
*/
static readonly int LEVEL_AUTO = 0;
/**
* Level 1 is the fastest compression and generally useful for short data.
*/
static readonly int LEVEL_1 = 1;
/**
* Level 2 is slightly slower but it gives better compression ratio.
*/
static readonly int LEVEL_2 = 2;
/**
* The output buffer must be at least 6% larger than the input buffer and can not be smaller than 66 bytes.
* @param inputLength length of input buffer
* @return Maximum output buffer length
*/
public static int calculateOutputBufferLength(int inputLength)
{
int tempOutputLength = (int)(inputLength * 1.06);
return Math.Max(tempOutputLength, 66);
}
/**
* Compress a block of data in the input buffer and returns the size of compressed block.
* The size of input buffer is specified by length. The minimum input buffer size is 32.
*
* If the input is not compressible, the return value might be larger than length (input buffer size).
*/
public static int compress(byte[] input, int inOffset, int inLength,
byte[] output, int outOffset, int proposedLevel)
{
int level;
if (proposedLevel == LEVEL_AUTO)
{
level = inLength < MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 ? LEVEL_1 : LEVEL_2;
}
else
{
level = proposedLevel;
}
int ip = 0;
int ipBound = ip + inLength - 2;
int ipLimit = ip + inLength - 12;
int op = 0;
// const flzuint8* htab[HASH_SIZE];
int[] htab = new int[HASH_SIZE];
// const flzuint8** hslot;
int hslot;
// flzuint32 hval;
// int OK b/c address starting from 0
int hval;
// flzuint32 copy;
// int OK b/c address starting from 0
int copy;
/* sanity check */
if (inLength < 4)
{
if (inLength != 0)
{
// *op++ = length-1;
output[outOffset + op++] = (byte)(inLength - 1);
ipBound++;
while (ip <= ipBound)
{
output[outOffset + op++] = input[inOffset + ip++];
}
return inLength + 1;
}
// else
return 0;
}
/* initializes hash table */
// for (hslot = htab; hslot < htab + HASH_SIZE; hslot++)
for (hslot = 0; hslot < HASH_SIZE; hslot++)
{
//*hslot = ip;
htab[hslot] = ip;
}
/* we start with literal copy */
copy = 2;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
output[outOffset + op++] = input[inOffset + ip++];
output[outOffset + op++] = input[inOffset + ip++];
/* main loop */
while (ip < ipLimit)
{
int refs = 0;
long distance = 0;
/* minimum match length */
// flzuint32 len = 3;
// int OK b/c len is 0 and octal based
int len = 3;
/* comparison starting-point */
int anchor = ip;
bool matchLabel = false;
/* check for a run */
if (level == LEVEL_2)
{
//if(ip[0] == ip[-1] && FASTLZ_READU16(ip-1)==FASTLZ_READU16(ip+1))
if (input[inOffset + ip] == input[inOffset + ip - 1] &&
readU16(input, inOffset + ip - 1) == readU16(input, inOffset + ip + 1))
{
distance = 1;
ip += 3;
refs = anchor - 1 + 3;
/*
* goto match;
*/
matchLabel = true;
}
}
if (!matchLabel)
{
/* find potential match */
// HASH_FUNCTION(hval,ip);
hval = hashFunction(input, inOffset + ip);
// hslot = htab + hval;
hslot = hval;
// refs = htab[hval];
refs = htab[hval];
/* calculate distance to the match */
distance = anchor - refs;
/* update hash table */
//*hslot = anchor;
htab[hslot] = anchor;
/* is this a match? check the first 3 bytes */
if (distance == 0
|| (level == LEVEL_1 ? distance >= MAX_DISTANCE : distance >= MAX_FARDISTANCE)
|| input[inOffset + refs++] != input[inOffset + ip++]
|| input[inOffset + refs++] != input[inOffset + ip++]
|| input[inOffset + refs++] != input[inOffset + ip++])
{
/*
* goto literal;
*/
output[outOffset + op++] = input[inOffset + anchor++];
ip = anchor;
copy++;
if (copy == MAX_COPY)
{
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
continue;
}
if (level == LEVEL_2)
{
/* far, needs at least 5-byte match */
if (distance >= MAX_DISTANCE)
{
if (input[inOffset + ip++] != input[inOffset + refs++]
|| input[inOffset + ip++] != input[inOffset + refs++])
{
/*
* goto literal;
*/
output[outOffset + op++] = input[inOffset + anchor++];
ip = anchor;
copy++;
if (copy == MAX_COPY)
{
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
continue;
}
len += 2;
}
}
} // end if(!matchLabel)
/*
* match:
*/
/* last matched byte */
ip = anchor + len;
/* distance is biased */
distance--;
if (distance == 0)
{
/* zero distance means a run */
//flzuint8 x = ip[-1];
byte x = input[inOffset + ip - 1];
while (ip < ipBound)
{
if (input[inOffset + refs++] != x)
{
break;
}
else
{
ip++;
}
}
}
else
{
for (;;)
{
/* safe because the outer check against ip limit */
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
while (ip < ipBound)
{
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
}
break;
}
}
/* if we have copied something, adjust the copy count */
if (copy != 0)
{
/* copy is biased, '0' means 1 byte copy */
// *(op-copy-1) = copy-1;
output[outOffset + op - copy - 1] = (byte)(copy - 1);
}
else
{
/* back, to overwrite the copy count */
op--;
}
/* reset literal counter */
copy = 0;
/* length is biased, '1' means a match of 3 bytes */
ip -= 3;
len = ip - anchor;
/* encode the match */
if (level == LEVEL_2)
{
if (distance < MAX_DISTANCE)
{
if (len < 7)
{
output[outOffset + op++] = (byte)((len << 5) + (int)((ulong)distance >> 8));
output[outOffset + op++] = (byte)(distance & 255);
}
else
{
output[outOffset + op++] = (byte)((7 << 5) + ((ulong)distance >> 8));
for (len -= 7; len >= 255; len -= 255)
{
output[outOffset + op++] = (byte)255;
}
output[outOffset + op++] = (byte)len;
output[outOffset + op++] = (byte)(distance & 255);
}
}
else
{
/* far away, but not yet in the another galaxy... */
if (len < 7)
{
distance -= MAX_DISTANCE;
output[outOffset + op++] = (byte)((len << 5) + 31);
output[outOffset + op++] = (byte)255;
output[outOffset + op++] = (byte)((ulong)distance >> 8);
output[outOffset + op++] = (byte)(distance & 255);
}
else
{
distance -= MAX_DISTANCE;
output[outOffset + op++] = (byte)((7 << 5) + 31);
for (len -= 7; len >= 255; len -= 255)
{
output[outOffset + op++] = (byte)255;
}
output[outOffset + op++] = (byte)len;
output[outOffset + op++] = (byte)255;
output[outOffset + op++] = (byte)((ulong)distance >> 8);
output[outOffset + op++] = (byte)(distance & 255);
}
}
}
else
{
if (len > MAX_LEN - 2)
{
while (len > MAX_LEN - 2)
{
output[outOffset + op++] = (byte)((7 << 5) + ((ulong)distance >> 8));
output[outOffset + op++] = (byte)(MAX_LEN - 2 - 7 - 2);
output[outOffset + op++] = (byte)(distance & 255);
len -= MAX_LEN - 2;
}
}
if (len < 7)
{
output[outOffset + op++] = (byte)((len << 5) + (int)((ulong)distance >> 8));
output[outOffset + op++] = (byte)(distance & 255);
}
else
{
output[outOffset + op++] = (byte)((7 << 5) + (int)((ulong)distance >> 8));
output[outOffset + op++] = (byte)(len - 7);
output[outOffset + op++] = (byte)(distance & 255);
}
}
/* update the hash at match boundary */
//HASH_FUNCTION(hval,ip);
hval = hashFunction(input, inOffset + ip);
htab[hval] = ip++;
//HASH_FUNCTION(hval,ip);
hval = hashFunction(input, inOffset + ip);
htab[hval] = ip++;
/* assuming literal copy */
output[outOffset + op++] = (byte)(MAX_COPY - 1);
continue;
// Moved to be inline, with a 'continue'
/*
* literal:
*
output[outOffset + op++] = input[inOffset + anchor++];
ip = anchor;
copy++;
if(copy == MAX_COPY){
copy = 0;
output[outOffset + op++] = MAX_COPY-1;
}
*/
}
/* left-over as literal copy */
ipBound++;
while (ip <= ipBound)
{
output[outOffset + op++] = input[inOffset + ip++];
copy++;
if (copy == MAX_COPY)
{
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
}
/* if we have copied something, adjust the copy length */
if (copy != 0)
{
//*(op-copy-1) = copy-1;
output[outOffset + op - copy - 1] = (byte)(copy - 1);
}
else
{
op--;
}
if (level == LEVEL_2)
{
/* marker for fastlz2 */
output[outOffset] |= 1 << 5;
}
return op;
}
/**
* Decompress a block of compressed data and returns the size of the decompressed block.
* If error occurs, e.g. the compressed data is corrupted or the output buffer is not large
* enough, then 0 (zero) will be returned instead.
*
* Decompression is memory safe and guaranteed not to write the output buffer
* more than what is specified in outLength.
*/
public static int decompress(byte[] input, int inOffset, int inLength,
byte[] output, int outOffset, int outLength)
{
//int level = ((*(const flzuint8*)input) >> 5) + 1;
int level = (input[inOffset] >> 5) + 1;
if (level != LEVEL_1 && level != LEVEL_2)
{
throw new Exception($"invalid level: {level} (expected: {LEVEL_1} or {LEVEL_2})");
}
// const flzuint8* ip = (const flzuint8*) input;
int ip = 0;
// flzuint8* op = (flzuint8*) output;
int op = 0;
// flzuint32 ctrl = (*ip++) & 31;
long ctrl = input[inOffset + ip++] & 31;
int loop = 1;
do
{
// const flzuint8* refs = op;
int refs = op;
// flzuint32 len = ctrl >> 5;
long len = ctrl >> 5;
// flzuint32 ofs = (ctrl & 31) << 8;
long ofs = (ctrl & 31) << 8;
if (ctrl >= 32)
{
len--;
// refs -= ofs;
refs -= (int)ofs;
int code;
if (len == 6)
{
if (level == LEVEL_1)
{
// len += *ip++;
len += input[inOffset + ip++] & 0xFF;
}
else
{
do
{
code = input[inOffset + ip++] & 0xFF;
len += code;
} while (code == 255);
}
}
if (level == LEVEL_1)
{
// refs -= *ip++;
refs -= input[inOffset + ip++] & 0xFF;
}
else
{
code = input[inOffset + ip++] & 0xFF;
refs -= code;
/* match from 16-bit distance */
// if(FASTLZ_UNEXPECT_CONDITIONAL(code==255))
// if(FASTLZ_EXPECT_CONDITIONAL(ofs==(31 << 8)))
if (code == 255 && ofs == 31 << 8)
{
ofs = (input[inOffset + ip++] & 0xFF) << 8;
ofs += input[inOffset + ip++] & 0xFF;
refs = (int)(op - ofs - MAX_DISTANCE);
}
}
// if the output index + length of block(?) + 3(?) is over the output limit?
if (op + len + 3 > outLength)
{
return 0;
}
// if (FASTLZ_UNEXPECT_CONDITIONAL(refs-1 < (flzuint8 *)output))
// if the address space of refs-1 is < the address of output?
// if we are still at the beginning of the output address?
if (refs - 1 < 0)
{
return 0;
}
if (ip < inLength)
{
ctrl = input[inOffset + ip++] & 0xFF;
}
else
{
loop = 0;
}
if (refs == op)
{
/* optimize copy for a run */
// flzuint8 b = refs[-1];
byte b = output[outOffset + refs - 1];
output[outOffset + op++] = b;
output[outOffset + op++] = b;
output[outOffset + op++] = b;
while (len != 0)
{
output[outOffset + op++] = b;
--len;
}
}
else
{
/* copy from reference */
refs--;
// *op++ = *refs++;
output[outOffset + op++] = output[outOffset + refs++];
output[outOffset + op++] = output[outOffset + refs++];
output[outOffset + op++] = output[outOffset + refs++];
while (len != 0)
{
output[outOffset + op++] = output[outOffset + refs++];
--len;
}
}
}
else
{
ctrl++;
if (op + ctrl > outLength)
{
return 0;
}
if (ip + ctrl > inLength)
{
return 0;
}
//*op++ = *ip++;
output[outOffset + op++] = input[inOffset + ip++];
for (--ctrl; ctrl != 0; ctrl--)
{
// *op++ = *ip++;
output[outOffset + op++] = input[inOffset + ip++];
}
loop = ip < inLength ? 1 : 0;
if (loop != 0)
{
// ctrl = *ip++;
ctrl = input[inOffset + ip++] & 0xFF;
}
}
// while(FASTLZ_EXPECT_CONDITIONAL(loop));
} while (loop != 0);
// return op - (flzuint8*)output;
return op;
}
private static int hashFunction(byte[] p, int offset)
{
int v = readU16(p, offset);
v ^= readU16(p, offset + 1) ^ v >> 16 - HASH_LOG;
v &= HASH_MASK;
return v;
}
private static int readU16(byte[] data, int offset)
{
if (offset + 1 >= data.Length)
{
return data[offset] & 0xff;
}
return (data[offset + 1] & 0xff) << 8 | data[offset] & 0xff;
}
private FastLz()
{
}
}
}