hellbound/Assets/Scripts/Core/Utils/LZ4.cs

598 lines
12 KiB
C#

#define CHECK_ARGS
#define CHECK_EOF
//#define LOCAL_SHADOW
using System;
using System.IO;
public class Lz4DecoderStream : Stream
{
long nTotalReadBytes;
public long TotalReadBytes
{
get { return nTotalReadBytes; }
}
public Lz4DecoderStream()
{}
public Lz4DecoderStream( Stream input, long inputLength = long.MaxValue )
{
Reset( input, inputLength );
}
public void Reset( Stream input, long inputLength = long.MaxValue )
{
nTotalReadBytes = 0;
this.inputLength = inputLength;
this.input = input;
phase = DecodePhase.ReadToken;
decodeBufferPos = 0;
litLen = 0;
matLen = 0;
matDst = 0;
inBufPos = DecBufLen;
inBufEnd = DecBufLen;
}
//private byte[] skipBuffer = new byte[1024];
//public void SkipBytes(long count)
//{
// for(long i=0;i<count;)
// {
// long left = count - i;
// int len = left > (long)skipBuffer.Length ? skipBuffer.Length : (int)left;
// Read(skipBuffer, 0, len);
// i += len;
// }
//}
public override void Close()
{
this.input = null;
}
private long inputLength;
private Stream input;
//because we might not be able to match back across invocations,
//we have to keep the last window's worth of bytes around for reuse
//we use a circular buffer for this - every time we write into this
//buffer, we also write the same into our output buffer
private const int DecBufLen = 0x10000;
private const int DecBufMask = 0xFFFF;
private const int InBufLen = 128;
private byte[] decodeBuffer = new byte[DecBufLen + InBufLen];
private int decodeBufferPos, inBufPos, inBufEnd;
//we keep track of which phase we're in so that we can jump right back
//into the correct part of decoding
private DecodePhase phase;
public int DecodeBufLen
{
get { return decodeBuffer.Length; }
}
private enum DecodePhase
{
ReadToken,
ReadExLiteralLength,
CopyLiteral,
ReadOffset,
ReadExMatchLength,
CopyMatch,
}
//state within interruptable phases and across phase boundaries is
//kept here - again, so that we can punt out and restart freely
private int litLen, matLen, matDst;
private byte[] onebyte = new byte[1];
public override int ReadByte( )
{
int read = Read(onebyte, 0, 1);
int bt = (read == -1) ? -1 : (int)onebyte[0];
//Log.Debug("BYTE: " + bt);
return bt;
}
public override int Read( byte[] buffer, int offset, int count )
{
nTotalReadBytes += count;
#if CHECK_ARGS
if( buffer == null )
throw new ArgumentNullException( "buffer" );
if( offset < 0 || count < 0 || buffer.Length - count < offset )
throw new ArgumentOutOfRangeException();
if( input == null )
throw new InvalidOperationException();
#endif
int nRead, nToRead = count;
var decBuf = decodeBuffer;
//the stringy gotos are obnoxious, but their purpose is to
//make it *blindingly* obvious how the state machine transitions
//back and forth as it reads - remember, we can yield out of
//this routine in several places, and we must be able to re-enter
//and pick up where we left off!
#if LOCAL_SHADOW
var phase = this.phase;
var inBufPos = this.inBufPos;
var inBufEnd = this.inBufEnd;
#endif
switch( phase )
{
case DecodePhase.ReadToken:
goto readToken;
case DecodePhase.ReadExLiteralLength:
goto readExLiteralLength;
case DecodePhase.CopyLiteral:
goto copyLiteral;
case DecodePhase.ReadOffset:
goto readOffset;
case DecodePhase.ReadExMatchLength:
goto readExMatchLength;
case DecodePhase.CopyMatch:
goto copyMatch;
}
readToken:
int tok;
if( inBufPos < inBufEnd )
{
tok = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
tok = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if( tok == -1 )
goto finish;
#endif
}
litLen = tok >> 4;
matLen = (tok & 0xF) + 4;
//Log.Debug("READ TOKEN " + tok + " " + litLen + " " + matLen);
switch( litLen )
{
case 0:
phase = DecodePhase.ReadOffset;
goto readOffset;
case 0xF:
phase = DecodePhase.ReadExLiteralLength;
goto readExLiteralLength;
default:
phase = DecodePhase.CopyLiteral;
goto copyLiteral;
}
readExLiteralLength:
int exLitLen;
if( inBufPos < inBufEnd )
{
exLitLen = decBuf[inBufPos++];
//Log.Debug("READ EX LIT LEN " + exLitLen);
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
exLitLen = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
//Log.Debug("READ EX LIT LEN " + exLitLen);
#if CHECK_EOF
if( exLitLen == -1 )
goto finish;
#endif
}
litLen += exLitLen;
if( exLitLen == 255 )
goto readExLiteralLength;
phase = DecodePhase.CopyLiteral;
goto copyLiteral;
copyLiteral:
int nReadLit = litLen < nToRead ? litLen : nToRead;
//Log.Debug("COPY LIT " + nReadLit + " " + litLen);
if( nReadLit != 0 )
{
if( inBufPos + nReadLit <= inBufEnd )
{
//Log.Debug("COPY LIT FROM BUF");
int ofs = offset;
for( int c = nReadLit; c-- != 0; )
buffer[ofs++] = decBuf[inBufPos++];
nRead = nReadLit;
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
//Log.Debug("READ CORE LIT");
nRead = ReadCore( buffer, offset, nReadLit );
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if( nRead == 0 )
{
//Log.Debug("GO TO FINISH 0");
goto finish;
}
#endif
}
offset += nRead;
nToRead -= nRead;
litLen -= nRead;
if( litLen != 0 )
goto copyLiteral;
}
if( nToRead == 0 )
{
//Log.Debug("GO TO FINISH 1");
goto finish;
}
phase = DecodePhase.ReadOffset;
goto readOffset;
readOffset:
//Log.Debug("READ OFFSET");
if( inBufPos + 1 < inBufEnd )
{
matDst = (decBuf[inBufPos + 1] << 8) | decBuf[inBufPos];
inBufPos += 2;
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
matDst = ReadOffsetCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if( matDst == -1 )
goto finish;
#endif
}
if( matLen == 15 + 4 )
{
phase = DecodePhase.ReadExMatchLength;
goto readExMatchLength;
}
else
{
phase = DecodePhase.CopyMatch;
goto copyMatch;
}
readExMatchLength:
//Log.Debug("READ EX MLEN");
int exMatLen;
if( inBufPos < inBufEnd )
{
exMatLen = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
exMatLen = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if( exMatLen == -1 )
goto finish;
#endif
}
matLen += exMatLen;
if( exMatLen == 255 )
goto readExMatchLength;
phase = DecodePhase.CopyMatch;
goto copyMatch;
copyMatch:
int nCpyMat = matLen < nToRead ? matLen : nToRead;
//Log.Debug("COPY MATCH " + nCpyMat);
if( nCpyMat != 0 )
{
nRead = count - nToRead;
int bufDst = matDst - nRead;
if( bufDst > 0 )
{
//offset is fairly far back, we need to pull from the buffer
int bufSrc = decodeBufferPos - bufDst;
if( bufSrc < 0 )
bufSrc += DecBufLen;
int bufCnt = bufDst < nCpyMat ? bufDst : nCpyMat;
for( int c = bufCnt; c-- != 0; )
buffer[offset++] = decBuf[bufSrc++ & DecBufMask];
}
else
{
bufDst = 0;
}
int sOfs = offset - matDst;
for( int i = bufDst; i < nCpyMat; i++ )
buffer[offset++] = buffer[sOfs++];
nToRead -= nCpyMat;
matLen -= nCpyMat;
}
if( nToRead == 0 )
goto finish;
phase = DecodePhase.ReadToken;
goto readToken;
finish:
nRead = count - nToRead;
int nToBuf = nRead < DecBufLen ? nRead : DecBufLen;
int repPos = offset - nToBuf;
//Log.Debug("FINISH: READ " + nToBuf);
if( nToBuf == DecBufLen )
{
Buffer.BlockCopy( buffer, repPos, decBuf, 0, DecBufLen );
decodeBufferPos = 0;
}
else
{
int decPos = decodeBufferPos;
while( nToBuf-- != 0 )
decBuf[decPos++ & DecBufMask] = buffer[repPos++];
decodeBufferPos = decPos & DecBufMask;
}
#if LOCAL_SHADOW
this.phase = phase;
this.inBufPos = inBufPos;
#endif
return nRead;
}
private int ReadByteCore()
{
var buf = decodeBuffer;
if( inBufPos == inBufEnd )
{
int nRead = input.Read( buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength );
//Log.Debug("INPUT READ " + nRead);
#if CHECK_EOF
if( nRead == 0 )
return -1;
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
}
return buf[inBufPos++];
}
private int ReadOffsetCore()
{
var buf = decodeBuffer;
if( inBufPos == inBufEnd )
{
int nRead = input.Read( buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength );
#if CHECK_EOF
if( nRead == 0 )
return -1;
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
}
if( inBufEnd - inBufPos == 1 )
{
buf[DecBufLen] = buf[inBufPos];
int nRead = input.Read( buf, DecBufLen + 1,
InBufLen - 1 < inputLength ? InBufLen - 1 : (int)inputLength );
#if CHECK_EOF
if( nRead == 0 )
{
inBufPos = DecBufLen;
inBufEnd = DecBufLen + 1;
return -1;
}
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead + 1;
}
int ret = (buf[inBufPos + 1] << 8) | buf[inBufPos];
inBufPos += 2;
return ret;
}
private int ReadCore( byte[] buffer, int offset, int count )
{
int nToRead = count;
var buf = decodeBuffer;
int inBufLen = inBufEnd - inBufPos;
int fromBuf = nToRead < inBufLen ? nToRead : inBufLen;
if( fromBuf != 0 )
{
var bufPos = inBufPos;
for( int c = fromBuf; c-- != 0; )
buffer[offset++] = buf[bufPos++];
inBufPos = bufPos;
nToRead -= fromBuf;
}
if( nToRead != 0 )
{
int nRead;
if( nToRead >= InBufLen )
{
nRead = input.Read( buffer, offset,
nToRead < inputLength ? nToRead : (int)inputLength );
nToRead -= nRead;
}
else
{
nRead = input.Read( buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength );
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
fromBuf = nToRead < nRead ? nToRead : nRead;
var bufPos = inBufPos;
for( int c = fromBuf; c-- != 0; )
buffer[offset++] = buf[bufPos++];
inBufPos = bufPos;
nToRead -= fromBuf;
}
inputLength -= nRead;
}
return count - nToRead;
}
#region Stream internals
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override void Flush()
{
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override long Seek( long offset, SeekOrigin origin )
{
throw new NotSupportedException();
}
public override void SetLength( long value )
{
throw new NotSupportedException();
}
public override void Write( byte[] buffer, int offset, int count )
{
throw new NotSupportedException();
}
#endregion
}