/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Implementation of SQueeze (RLE+Huffman) compression. * * This was ripped fairly directly from Squeeze.c in NufxLib. Because * there's relatively little code, and providing direct access to the * compression functions already in NuLib is a little unwieldy, I've just * cut & pasted the necessary pieces here. */ #include "stdafx.h" #include "Squeeze.h" #include "NufxArchive.h" #define kSqBufferSize 8192 /* must hold full SQ header, and % 128 */ #define kNuSQMagic 0xff76 /* magic value for file header */ #define kNuSQRLEDelim 0x90 /* RLE delimiter */ #define kNuSQEOFToken 256 /* distinguished stop symbol */ #define kNuSQNumVals 257 /* 256 symbols + stop */ /* * =========================================================================== * Unsqueeze * =========================================================================== */ /* * State during uncompression. */ typedef struct USQState { unsigned long dataInBuffer; unsigned char* dataPtr; int bitPosn; int bits; /* * Decoding tree; first "nodeCount" values are populated. Positive * values are indices to another node in the tree, negative values * are literals (+1 because "negative zero" doesn't work well). */ int nodeCount; struct { short child[2]; /* left/right kids, must be signed 16-bit */ } decTree[kNuSQNumVals-1]; } USQState; /* * Decode the next symbol from the Huffman stream. */ static NuError USQDecodeHuffSymbol(USQState* pUsqState, int* pVal) { short val = 0; int bits, bitPosn; bits = pUsqState->bits; /* local copy */ bitPosn = pUsqState->bitPosn; do { if (++bitPosn > 7) { /* grab the next byte and use that */ bits = *pUsqState->dataPtr++; bitPosn = 0; if (!pUsqState->dataInBuffer--) return kNuErrBufferUnderrun; val = pUsqState->decTree[val].child[1 & bits]; } else { /* still got bits; shift right and use it */ val = pUsqState->decTree[val].child[1 & (bits >>= 1)]; } } while (val >= 0); /* val is negative literal; add one to make it zero-based then negate it */ *pVal = -(val + 1); pUsqState->bits = bits; pUsqState->bitPosn = bitPosn; return kNuErrNone; } /* * Read two bytes of signed data out of the buffer. */ static inline NuError USQReadShort(USQState* pUsqState, short* pShort) { if (pUsqState->dataInBuffer < 2) return kNuErrBufferUnderrun; *pShort = *pUsqState->dataPtr++; *pShort |= (*pUsqState->dataPtr++) << 8; pUsqState->dataInBuffer -= 2; return kNuErrNone; } /* * Wrapper for fread(). Note the arguments resemble read(2) rather * than fread(3S). */ static NuError SQRead(FILE* fp, void* buf, size_t nbyte) { size_t result; ASSERT(buf != NULL); ASSERT(nbyte > 0); ASSERT(fp != NULL); errno = 0; result = fread(buf, 1, nbyte, fp); if (result != nbyte) return errno ? (NuError)errno : kNuErrFileRead; return kNuErrNone; } NuError UnSqueeze(FILE* fp, unsigned long realEOF, ExpandBuffer* outExp, bool fullSqHeader, int blockSize) { /* * Because we have a stop symbol, knowing the uncompressed length of * the file is not essential. */ NuError err = kNuErrNone; USQState usqState; unsigned long compRemaining, getSize; unsigned short magic, fileChecksum, checksum; // fullSqHeader only short nodeCount; int i, inrep; unsigned char* tmpBuf = NULL; unsigned char lastc = 0; tmpBuf = (unsigned char*) malloc(kSqBufferSize); if (tmpBuf == NULL) { err = kNuErrMalloc; goto bail; } usqState.dataInBuffer = 0; usqState.dataPtr = tmpBuf; compRemaining = realEOF; if ((fullSqHeader && compRemaining < 8) || (!fullSqHeader && compRemaining < 3)) { err = kNuErrBadData; LOGI("too short to be valid SQ data"); goto bail; } /* * Round up to the nearest 128-byte boundary. We need to read * everything out of the file in case this is a streaming archive. * Because the compressed data has an embedded stop symbol, it's okay * to "overrun" the expansion code. */ if (blockSize != 0) { compRemaining = ((compRemaining + blockSize-1) / blockSize) * blockSize; } /* want to grab up to kSqBufferSize bytes */ if (compRemaining > kSqBufferSize) getSize = kSqBufferSize; else getSize = compRemaining; /* * Grab a big chunk. "compRemaining" is the amount of compressed * data left in the file, usqState.dataInBuffer is the amount of * compressed data left in the buffer. * * For BNY, we want to read 128-byte blocks. */ if (getSize) { ASSERT(getSize <= kSqBufferSize); err = SQRead(fp, usqState.dataPtr, getSize); if (err != kNuErrNone) { LOGI("failed reading compressed data (%ld bytes)", getSize); goto bail; } usqState.dataInBuffer += getSize; if (getSize > compRemaining) compRemaining = 0; else compRemaining -= getSize; } /* reset dataPtr */ usqState.dataPtr = tmpBuf; /* * Read the header. We assume that the header will fit in the * compression buffer ( sq allowed 300+ for the filename, plus * 257*2 for the tree, plus misc). */ ASSERT(kSqBufferSize > 1200); if (fullSqHeader) { err = USQReadShort(&usqState, (short*)&magic); if (err != kNuErrNone) goto bail; if (magic != kNuSQMagic) { err = kNuErrBadData; LOGI("bad magic number in SQ block"); goto bail; } err = USQReadShort(&usqState, (short*)&fileChecksum); if (err != kNuErrNone) goto bail; checksum = 0; /* skip over the filename */ while (*usqState.dataPtr++ != '\0') usqState.dataInBuffer--; usqState.dataInBuffer--; } err = USQReadShort(&usqState, &nodeCount); if (err != kNuErrNone) goto bail; if (nodeCount < 0 || nodeCount >= kNuSQNumVals) { err = kNuErrBadData; LOGI("invalid decode tree in SQ (%d nodes)", nodeCount); goto bail; } usqState.nodeCount = nodeCount; /* initialize for possibly empty tree (only happens on an empty file) */ usqState.decTree[0].child[0] = -(kNuSQEOFToken+1); usqState.decTree[0].child[1] = -(kNuSQEOFToken+1); /* read the nodes, ignoring "read errors" until we're done */ for (i = 0; i < nodeCount; i++) { err = USQReadShort(&usqState, &usqState.decTree[i].child[0]); err = USQReadShort(&usqState, &usqState.decTree[i].child[1]); } if (err != kNuErrNone) { err = kNuErrBadData; LOGI("SQ data looks truncated at tree"); goto bail; } usqState.bitPosn = 99; /* force an immediate read */ /* * Start pulling data out of the file. We have to Huffman-decode * the input, and then feed that into an RLE expander. * * A completely lopsided (and broken) Huffman tree could require * 256 tree descents, so we want to try to ensure we have at least 256 * bits in the buffer. Otherwise, we could get a false buffer underrun * indication back from DecodeHuffSymbol. * * The SQ sources actually guarantee that a code will fit entirely * in 16 bits, but there's no reason not to use the larger value. */ inrep = false; while (1) { int val; if (usqState.dataInBuffer < 65 && compRemaining) { /* * Less than 256 bits, but there's more in the file. * * First thing we do is slide the old data to the start of * the buffer. */ if (usqState.dataInBuffer) { ASSERT(tmpBuf != usqState.dataPtr); memmove(tmpBuf, usqState.dataPtr, usqState.dataInBuffer); } usqState.dataPtr = tmpBuf; /* * Next we read as much as we can. */ if (kSqBufferSize - usqState.dataInBuffer < compRemaining) getSize = kSqBufferSize - usqState.dataInBuffer; else getSize = compRemaining; ASSERT(getSize <= kSqBufferSize); //LOGI("Reading from offset=%ld (compRem=%ld)", // ftell(fp), compRemaining); err = SQRead(fp, usqState.dataPtr + usqState.dataInBuffer, getSize); if (err != kNuErrNone) { LOGI("failed reading compressed data (%ld bytes, err=%d)", getSize, err); goto bail; } usqState.dataInBuffer += getSize; if (getSize > compRemaining) compRemaining = 0; else compRemaining -= getSize; ASSERT(compRemaining < 32767*65536); ASSERT(usqState.dataInBuffer <= kSqBufferSize); } err = USQDecodeHuffSymbol(&usqState, &val); if (err != kNuErrNone) { LOGI("failed decoding huff symbol"); goto bail; } if (val == kNuSQEOFToken) break; /* * Feed the symbol into the RLE decoder. */ if (inrep) { /* * Last char was RLE delim, handle this specially. We use * --val instead of val-- because we already emitted the * first occurrence of the char (right before the RLE delim). */ if (val == 0) { /* special case -- just an escaped RLE delim */ lastc = kNuSQRLEDelim; val = 2; } while (--val) { /*if (pCrc != NULL) *pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1);*/ if (outExp != NULL) outExp->Putc(lastc); if (fullSqHeader) { checksum += lastc; } } inrep = false; } else { /* last char was ordinary */ if (val == kNuSQRLEDelim) { /* set a flag and catch the count the next time around */ inrep = true; } else { lastc = val; /*if (pCrc != NULL) *pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1);*/ if (outExp != NULL) outExp->Putc(lastc); if (fullSqHeader) { checksum += lastc; } } } } if (inrep) { err = kNuErrBadData; LOGI("got stop symbol when run length expected"); goto bail; } if (fullSqHeader) { /* verify the checksum stored in the SQ file */ if (checksum != fileChecksum) { err = kNuErrBadDataCRC; LOGI("expected 0x%04x, got 0x%04x (SQ)", fileChecksum, checksum); goto bail; } else { LOGI("--- SQ checksums match (0x%04x)", checksum); } } /* * Gobble up any unused bytes in the last 128-byte block. There * shouldn't be more than that left over. */ if (compRemaining > kSqBufferSize) { err = kNuErrBadData; LOGI("wow: found %ld bytes left over", compRemaining); goto bail; } if (compRemaining) { LOGI("+++ slurping up last %ld bytes", compRemaining); err = SQRead(fp, tmpBuf, compRemaining); if (err != kNuErrNone) { LOGI("failed reading leftovers"); goto bail; } } bail: //if (outfp != NULL) // fflush(outfp); free(tmpBuf); return err; }