You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
9.7 KiB
431 lines
9.7 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// LZSS Codec. Designed for fast cheap gametime encoding/decoding. Compression results |
|
// are not aggresive as other alogrithms, but gets 2:1 on most arbitrary uncompressed data. |
|
// |
|
//=====================================================================================// |
|
|
|
#include "tier0/platform.h" |
|
#include "tier0/dbg.h" |
|
#include "tier0/vprof.h" |
|
#include "tier0/etwprof.h" |
|
#include "tier1/lzss.h" |
|
#include "tier1/utlbuffer.h" |
|
|
|
#define LZSS_LOOKSHIFT 4 |
|
#define LZSS_LOOKAHEAD ( 1 << LZSS_LOOKSHIFT ) |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns true if buffer is compressed. |
|
//----------------------------------------------------------------------------- |
|
bool CLZSS::IsCompressed( const unsigned char *pInput ) |
|
{ |
|
lzss_header_t *pHeader = (lzss_header_t *)pInput; |
|
if ( pHeader && pHeader->id == LZSS_ID ) |
|
{ |
|
return true; |
|
} |
|
|
|
// unrecognized |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns uncompressed size of compressed input buffer. Used for allocating output |
|
// buffer for decompression. Returns 0 if input buffer is not compressed. |
|
//----------------------------------------------------------------------------- |
|
unsigned int CLZSS::GetActualSize( const unsigned char *pInput ) |
|
{ |
|
lzss_header_t *pHeader = (lzss_header_t *)pInput; |
|
if ( pHeader && pHeader->id == LZSS_ID ) |
|
{ |
|
return LittleLong( pHeader->actualSize ); |
|
} |
|
|
|
// unrecognized |
|
return 0; |
|
} |
|
|
|
void CLZSS::BuildHash( const unsigned char *pData ) |
|
{ |
|
lzss_list_t *pList; |
|
lzss_node_t *pTarget; |
|
|
|
intp targetindex = (intp)pData & ( m_nWindowSize - 1 ); |
|
pTarget = &m_pHashTarget[targetindex]; |
|
if ( pTarget->pData ) |
|
{ |
|
pList = &m_pHashTable[*pTarget->pData]; |
|
if ( pTarget->pPrev ) |
|
{ |
|
pList->pEnd = pTarget->pPrev; |
|
pTarget->pPrev->pNext = 0; |
|
} |
|
else |
|
{ |
|
pList->pEnd = 0; |
|
pList->pStart = 0; |
|
} |
|
} |
|
|
|
pList = &m_pHashTable[*pData]; |
|
pTarget->pData = pData; |
|
pTarget->pPrev = 0; |
|
pTarget->pNext = pList->pStart; |
|
if ( pList->pStart ) |
|
{ |
|
pList->pStart->pPrev = pTarget; |
|
} |
|
else |
|
{ |
|
pList->pEnd = pTarget; |
|
} |
|
pList->pStart = pTarget; |
|
} |
|
|
|
unsigned char *CLZSS::CompressNoAlloc( const unsigned char *pInput, int inputLength, unsigned char *pOutputBuf, unsigned int *pOutputSize ) |
|
{ |
|
if ( inputLength <= sizeof( lzss_header_t ) + 8 ) |
|
{ |
|
return NULL; |
|
} |
|
VPROF( "CLZSS::CompressNoAlloc" ); |
|
ETWMark1I("CompressNoAlloc", inputLength ); |
|
|
|
// create the compression work buffers, small enough (~64K) for stack |
|
m_pHashTable = (lzss_list_t *)stackalloc( 256 * sizeof( lzss_list_t ) ); |
|
memset( m_pHashTable, 0, 256 * sizeof( lzss_list_t ) ); |
|
m_pHashTarget = (lzss_node_t *)stackalloc( m_nWindowSize * sizeof( lzss_node_t ) ); |
|
memset( m_pHashTarget, 0, m_nWindowSize * sizeof( lzss_node_t ) ); |
|
|
|
// allocate the output buffer, compressed buffer is expected to be less, caller will free |
|
unsigned char *pStart = pOutputBuf; |
|
// prevent compression failure (inflation), leave enough to allow dribble eof bytes |
|
unsigned char *pEnd = pStart + inputLength - sizeof ( lzss_header_t ) - 8; |
|
|
|
// set the header |
|
lzss_header_t *pHeader = (lzss_header_t *)pStart; |
|
pHeader->id = LZSS_ID; |
|
pHeader->actualSize = LittleLong( inputLength ); |
|
|
|
unsigned char *pOutput = pStart + sizeof (lzss_header_t); |
|
const unsigned char *pLookAhead = pInput; |
|
const unsigned char *pWindow = pInput; |
|
const unsigned char *pEncodedPosition = NULL; |
|
unsigned char *pCmdByte = NULL; |
|
int putCmdByte = 0; |
|
|
|
while ( inputLength > 0 ) |
|
{ |
|
pWindow = pLookAhead - m_nWindowSize; |
|
if ( pWindow < pInput ) |
|
{ |
|
pWindow = pInput; |
|
} |
|
|
|
if ( !putCmdByte ) |
|
{ |
|
pCmdByte = pOutput++; |
|
*pCmdByte = 0; |
|
} |
|
putCmdByte = ( putCmdByte + 1 ) & 0x07; |
|
|
|
int encodedLength = 0; |
|
int lookAheadLength = inputLength < LZSS_LOOKAHEAD ? inputLength : LZSS_LOOKAHEAD; |
|
|
|
lzss_node_t *pHash = m_pHashTable[pLookAhead[0]].pStart; |
|
while ( pHash ) |
|
{ |
|
int matchLength = 0; |
|
int length = lookAheadLength; |
|
while ( length-- && pHash->pData[matchLength] == pLookAhead[matchLength] ) |
|
{ |
|
matchLength++; |
|
} |
|
if ( matchLength > encodedLength ) |
|
{ |
|
encodedLength = matchLength; |
|
pEncodedPosition = pHash->pData; |
|
} |
|
if ( matchLength == lookAheadLength ) |
|
{ |
|
break; |
|
} |
|
pHash = pHash->pNext; |
|
} |
|
|
|
if ( encodedLength >= 3 ) |
|
{ |
|
*pCmdByte = ( *pCmdByte >> 1 ) | 0x80; |
|
*pOutput++ = ( ( pLookAhead-pEncodedPosition-1 ) >> LZSS_LOOKSHIFT ); |
|
*pOutput++ = ( ( pLookAhead-pEncodedPosition-1 ) << LZSS_LOOKSHIFT ) | ( encodedLength-1 ); |
|
} |
|
else |
|
{ |
|
encodedLength = 1; |
|
*pCmdByte = ( *pCmdByte >> 1 ); |
|
*pOutput++ = *pLookAhead; |
|
} |
|
|
|
for ( int i=0; i<encodedLength; i++ ) |
|
{ |
|
BuildHash( pLookAhead++ ); |
|
} |
|
|
|
inputLength -= encodedLength; |
|
|
|
if ( pOutput >= pEnd ) |
|
{ |
|
// compression is worse, abandon |
|
return NULL; |
|
} |
|
} |
|
|
|
if ( inputLength != 0 ) |
|
{ |
|
// unexpected failure |
|
Assert( 0 ); |
|
return NULL; |
|
} |
|
|
|
if ( !putCmdByte ) |
|
{ |
|
pCmdByte = pOutput++; |
|
*pCmdByte = 0x01; |
|
} |
|
else |
|
{ |
|
*pCmdByte = ( ( *pCmdByte >> 1 ) | 0x80 ) >> ( 7 - putCmdByte ); |
|
} |
|
|
|
*pOutput++ = 0; |
|
*pOutput++ = 0; |
|
|
|
if ( pOutputSize ) |
|
{ |
|
*pOutputSize = pOutput - pStart; |
|
} |
|
|
|
return pStart; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Compress an input buffer. Caller must free output compressed buffer. |
|
// Returns NULL if compression failed (i.e. compression yielded worse results) |
|
//----------------------------------------------------------------------------- |
|
unsigned char* CLZSS::Compress( const unsigned char *pInput, int inputLength, unsigned int *pOutputSize ) |
|
{ |
|
unsigned char *pStart = (unsigned char *)malloc( inputLength ); |
|
unsigned char *pFinal = CompressNoAlloc( pInput, inputLength, pStart, pOutputSize ); |
|
if ( !pFinal ) |
|
{ |
|
free( pStart ); |
|
return NULL; |
|
} |
|
|
|
return pStart; |
|
} |
|
|
|
/* |
|
// BUG BUG: This code is flaky, don't use until it's debugged!!! |
|
unsigned int CLZSS::Uncompress( unsigned char *pInput, CUtlBuffer &buf ) |
|
{ |
|
int cmdByte = 0; |
|
int getCmdByte = 0; |
|
|
|
unsigned int actualSize = GetActualSize( pInput ); |
|
if ( !actualSize ) |
|
{ |
|
// unrecognized |
|
return 0; |
|
} |
|
|
|
unsigned char *pBase = ( unsigned char * )buf.Base(); |
|
|
|
pInput += sizeof( lzss_header_t ); |
|
|
|
while ( !buf.IsValid() ) |
|
{ |
|
if ( !getCmdByte ) |
|
{ |
|
cmdByte = *pInput++; |
|
} |
|
getCmdByte = ( getCmdByte + 1 ) & 0x07; |
|
|
|
if ( cmdByte & 0x01 ) |
|
{ |
|
int position = *pInput++ << LZSS_LOOKSHIFT; |
|
position |= ( *pInput >> LZSS_LOOKSHIFT ); |
|
int count = ( *pInput++ & 0x0F ) + 1; |
|
if ( count == 1 ) |
|
{ |
|
break; |
|
} |
|
unsigned int pos = buf.TellPut(); |
|
unsigned char *pSource = ( pBase + pos ) - position - 1; |
|
|
|
// BUGBUG: |
|
// This is failing!!! |
|
// buf.WriteBytes( pSource, count ); |
|
// So have to iterate them manually |
|
for ( int i =0; i < count; ++i ) |
|
{ |
|
buf.PutUnsignedChar( *pSource++ ); |
|
} |
|
} |
|
else |
|
{ |
|
buf.PutUnsignedChar( *pInput++ ); |
|
} |
|
cmdByte = cmdByte >> 1; |
|
} |
|
|
|
if ( buf.TellPut() != (int)actualSize ) |
|
{ |
|
// unexpected failure |
|
Assert( 0 ); |
|
return 0; |
|
} |
|
|
|
return buf.TellPut(); |
|
} |
|
*/ |
|
|
|
unsigned int CLZSS::SafeUncompress( const unsigned char *pInput, unsigned int inputSize, unsigned char *pOutput, unsigned int unBufSize ) |
|
{ |
|
unsigned int totalBytes = 0; |
|
int cmdByte = 0; |
|
int getCmdByte = 0; |
|
|
|
unsigned int actualSize = GetActualSize( pInput ); |
|
|
|
if ( !actualSize || |
|
actualSize > unBufSize || |
|
inputSize <= sizeof( lzss_header_t ) ) |
|
return 0; |
|
|
|
const unsigned char *pInputEnd = pInput+inputSize-1; |
|
const unsigned char *pOrigOutput = pOutput; |
|
|
|
pInput += sizeof( lzss_header_t ); |
|
|
|
for ( ;; ) |
|
{ |
|
if ( !getCmdByte ) |
|
{ |
|
if( pInput > pInputEnd ) |
|
return 0; |
|
|
|
cmdByte = *pInput++; |
|
} |
|
getCmdByte = ( getCmdByte + 1 ) & 0x07; |
|
|
|
if ( cmdByte & 0x01 ) |
|
{ |
|
if( pInput+1 > pInputEnd ) |
|
return 0; |
|
|
|
int position = *pInput++ << LZSS_LOOKSHIFT; |
|
position |= ( *pInput >> LZSS_LOOKSHIFT ); |
|
int count = ( *pInput++ & 0x0F ) + 1; |
|
if ( count == 1 ) |
|
break; |
|
|
|
unsigned char *pSource = pOutput - position - 1; |
|
|
|
if ( totalBytes + count > unBufSize || |
|
pSource < pOrigOutput ) |
|
return 0; |
|
|
|
for ( int i=0; i<count; i++ ) |
|
*pOutput++ = *pSource++; |
|
|
|
totalBytes += count; |
|
} |
|
else |
|
{ |
|
if ( totalBytes + 1 > unBufSize || |
|
pInput > pInputEnd ) |
|
return 0; |
|
|
|
*pOutput++ = *pInput++; |
|
totalBytes++; |
|
} |
|
cmdByte = cmdByte >> 1; |
|
} |
|
|
|
if ( totalBytes != actualSize ) |
|
{ |
|
// unexpected failure |
|
Assert( 0 ); |
|
return 0; |
|
} |
|
|
|
return totalBytes; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Uncompress a buffer, Returns the uncompressed size. Caller must provide an |
|
// adequate sized output buffer or memory corruption will occur. |
|
//----------------------------------------------------------------------------- |
|
unsigned int CLZSS::Uncompress( const unsigned char *pInput, unsigned char *pOutput ) |
|
{ |
|
unsigned int totalBytes = 0; |
|
int cmdByte = 0; |
|
int getCmdByte = 0; |
|
|
|
unsigned int actualSize = GetActualSize( pInput ); |
|
if ( !actualSize ) |
|
{ |
|
// unrecognized |
|
return 0; |
|
} |
|
|
|
pInput += sizeof( lzss_header_t ); |
|
|
|
for ( ;; ) |
|
{ |
|
if ( !getCmdByte ) |
|
{ |
|
cmdByte = *pInput++; |
|
} |
|
getCmdByte = ( getCmdByte + 1 ) & 0x07; |
|
|
|
if ( cmdByte & 0x01 ) |
|
{ |
|
int position = *pInput++ << LZSS_LOOKSHIFT; |
|
position |= ( *pInput >> LZSS_LOOKSHIFT ); |
|
int count = ( *pInput++ & 0x0F ) + 1; |
|
if ( count == 1 ) |
|
{ |
|
break; |
|
} |
|
unsigned char *pSource = pOutput - position - 1; |
|
for ( int i=0; i<count; i++ ) |
|
{ |
|
*pOutput++ = *pSource++; |
|
} |
|
totalBytes += count; |
|
} |
|
else |
|
{ |
|
*pOutput++ = *pInput++; |
|
totalBytes++; |
|
} |
|
cmdByte = cmdByte >> 1; |
|
} |
|
|
|
if ( totalBytes != actualSize ) |
|
{ |
|
// unexpected failure |
|
Assert( 0 ); |
|
return 0; |
|
} |
|
|
|
return totalBytes; |
|
} |
|
|
|
|
|
|