ciderpress/nufxlib/Squeeze.c
Andy McFadden 51b5f00f5c Large set of changes to restore CiderPress build.
CiderPress and MDC now compile, and execute far enough to open
their respective "about" boxes, but I doubt they'll do much
more than that.

* Switch from MBCS to UNICODE APIs

Microsoft switched to UTF-16 (by way of UCS-2) a long time ago,
and the support for MBCS seems to be getting phased out.  So it's
time to switch to wide strings.

This is a bit awkward for CiderPress because it works with disk
and file archives with 8-bit filenames, and I want NufxLib and
DiskImgLib to continue to work on Linux (which has largely taken
the UTF-8 approach to Unicode).  The libraries will continue to
work with 8-bit filenames, with CiderPress/MDC doing the
conversion at the appropriate point.

There were a couple of places where strings from a structure
handed back by one of the libraries were used directly in the UI,
or vice-versa, which is a problem because we have nowhere to
store the result of the conversion.  These currently have fixed
place-holder "xyzzy" strings.

All UI strings are now wide.

Various format strings now use "%ls" and "%hs" to explicitly
specify wide and narrow.  This doesn't play well with gcc, so
only the Windows-specific parts use those.

* Various updates to vcxproj files

The project-file conversion had some cruft that is now largely
gone.  The build now has a common output directory for the EXEs
and libraries, avoiding the old post-build copy steps.

* Added zlib 1.2.8 and nufxlib 2.2.2 source snapshots

The old "prebuilts" directory is now gone.  The libraries are now
built as part of building the apps.

I added a minimal set of files for zlib, and a full set for nufxlib.
The Linux-specific nufxlib goodies are included for the benefit of
the Linux utilities, which are currently broken (don't build).

* Replace symbols used for include guards

Symbols with a leading "__" are reserved.
2014-11-16 21:01:53 -08:00

1147 lines
34 KiB
C

/*
* NuFX archive manipulation library
* Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
* This is free software; you can redistribute it and/or modify it under the
* terms of the BSD License, see the file COPYING-LIB.
*
* Huffman/RLE "squeeze" compression, based on SQ/USQ. This format is
* listed in the NuFX documentation, but to my knowledge has never
* actually been used (until now). Neither P8 ShrinkIt v3.4 nor II Unshrink
* handle the format correctly, so this is really only useful as an
* experiment.
*
* The algorithm appears to date back to the CP/M days. This implementation
* is based on "xsq"/"xusq" v1.7u by Richard Greenlaw (from December 1982).
* The code was also present in ARC v5.x.
*
* The "nusq.c" implementation found in NuLib was by Marcel J.E. Mol,
* who got it from Don Elton's sq3/usq2 programs for the Apple II.
*
* The SQ file format begins with this:
* +00 magic number (0xff76)
* +02 checksum on uncompressed data
* +04 filename, ending with \0
* The NuFX format skips the above, starting immediately after it:
* +00 node count
* +02 node value array [node count], two bytes each
* +xx data immediately follows array
*
* NuFX drops the magic number, checksum, and filename from the header,
* since (with v3 records) all three are redundant. You can enable this
* if you want to experiment with SQ-compatible output.
*/
#include "NufxLibPriv.h"
#ifdef ENABLE_SQ
/* if this is defined, create and unpack the full SQ header (debugging only) */
/* #define FULL_SQ_HEADER */
#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 */
/*
* ===========================================================================
* Compression
* ===========================================================================
*/
#define kNuSQNoChild (-1) /* indicates end of path through tree */
#define kNuSQNumNodes (kNuSQNumVals + kNuSQNumVals -1)
#define kNuSQMaxCount 65535 /* max value you can store in 16 bits */
/* states for the RLE encoding */
typedef enum {
kNuSQRLEStateUnknown = 0,
kNuSQRLEStateNoHist, /* nothing yet */
kNuSQRLEStateSentChar, /* lastchar set, no lookahead yet */
kNuSQRLEStateSendNewC, /* found run of two, send 2nd w/o DLE */
kNuSQRLEStateSendCnt, /* newchar set, DLE sent, send count next */
} NuSQRLEState;
/* nodes in the Huffman encoding tree */
typedef struct EncTreeNode {
int weight; /* #of appearances */
int tdepth; /* length on longest path in tree */
int lchild, rchild; /* indexes to next level */
} EncTreeNode;
/*
* State during compression.
*/
typedef struct SQState {
NuArchive* pArchive;
int doCalcCRC; /* boolean; if set, compute CRC on input */
ushort crc;
NuStraw* pStraw;
long uncompRemaining;
#ifdef FULL_SQ_HEADER
ushort checksum;
#endif
/*
* RLE state stuff.
*/
NuSQRLEState rleState;
int lastSym;
int likeCount;
/*
* Huffman state stuff.
*/
EncTreeNode node[kNuSQNumNodes];
int treeHead; /* index to head node of final tree */
/* encoding table */
int codeLen[kNuSQNumVals]; /* number of bits in code for symbol N */
ushort code[kNuSQNumVals]; /* bits for symbol N (first bit in lsb) */
ushort tmpCode; /* temporary code value */
} SQState;
/*
* Get the next byte from the input straw. Also updates the checksum
* and SQ CRC, if "doCalcCRC" is set to true.
*
* This isn't exactly fast, but then this isn't exactly a fast algorithm,
* and there's not much point in optimizing something that isn't going
* to get used much.
*
* Returns kNuSQEOFToken as the value when we're out of data.
*/
static NuError
Nu_SQGetcCRC(SQState* pSqState, int* pSym)
{
NuError err;
uchar c;
if (!pSqState->uncompRemaining) {
*pSym = kNuSQEOFToken;
return kNuErrNone;
}
err = Nu_StrawRead(pSqState->pArchive, pSqState->pStraw, &c, 1);
if (err == kNuErrNone) {
if (pSqState->doCalcCRC) {
#ifdef FULL_SQ_HEADER
pSqState->checksum += c;
#endif
pSqState->crc = Nu_CalcCRC16(pSqState->crc, &c, 1);
}
*pSym = c;
pSqState->uncompRemaining--;
}
return err;
}
/*
* Get the next byte from the post-RLE input stream.
*
* Returns kNuSQEOFToken in "*pSum" when we reach the end of the input.
*/
static NuError
Nu_SQGetcRLE(SQState* pSqState, int* pSym)
{
NuError err = kNuErrNone;
int likeCount, newSym;
switch (pSqState->rleState) {
case kNuSQRLEStateNoHist:
/* No relevant history */
pSqState->rleState = kNuSQRLEStateSentChar;
err = Nu_SQGetcCRC(pSqState, pSym);
pSqState->lastSym = *pSym;
break;
case kNuSQRLEStateSentChar:
/* lastChar is set, need lookahead */
switch (pSqState->lastSym) {
case kNuSQRLEDelim:
/* send all DLEs escaped; note this is horrible for a run of DLEs */
pSqState->rleState = kNuSQRLEStateNoHist;
*pSym = 0; /* zero len is how we define an escaped DLE */
break;
case kNuSQEOFToken:
*pSym = kNuSQEOFToken;
break;
default:
/*
* Try for a run, using the character we previous read as
* the base. Thus, if the next character we read matches,
* we have a run of two. The count describes the total
* length of the run, including the character we've already
* emitted.
*/
likeCount = 0;
do {
likeCount++;
err = Nu_SQGetcCRC(pSqState, &newSym);
if (err != kNuErrNone)
goto bail;
} while (newSym == pSqState->lastSym && likeCount < 255);
switch (likeCount) {
case 1:
/* not a run, return first one we got */
pSqState->lastSym = newSym;
*pSym = newSym;
break;
case 2:
/* not long enough for run; return second one next time thru */
pSqState->rleState = kNuSQRLEStateSendNewC;
*pSym = pSqState->lastSym; /* 1st new one */
pSqState->lastSym = newSym; /* 2nd new one */
break;
default:
pSqState->rleState = kNuSQRLEStateSendCnt;
pSqState->likeCount = likeCount;
pSqState->lastSym = newSym; /* 1st one after the run */
*pSym = kNuSQRLEDelim;
break;
}
}
break;
case kNuSQRLEStateSendNewC:
/* send first char past a run of two */
pSqState->rleState = kNuSQRLEStateSentChar;
*pSym = pSqState->lastSym;
break;
case kNuSQRLEStateSendCnt:
/* Sent DLE for repeat sequence, send count */
pSqState->rleState = kNuSQRLEStateSendNewC;
*pSym = pSqState->likeCount;
break;
default:
{
NuArchive* pArchive = pSqState->pArchive;
err = kNuErrInternal;
Nu_ReportError(NU_BLOB, err, "invalid state %d in SQ RLE encode",
pSqState->rleState);
break;
}
}
bail:
return err;
}
/*
* Comment from xsq.c:
*
* This translation uses the Huffman algorithm to develop a
* binary tree representing the decoding information for
* a variable length bit string code for each input value.
* Each string's length is in inverse proportion to its
* frequency of appearance in the incoming data stream.
* The encoding table is derived from the decoding table.
*
* The range of valid values into the Huffman algorithm are
* the values of a byte stored in an integer plus the special
* endfile value chosen to be an adjacent value. Overall, 0-SPEOF.
*
* The "node" array of structures contains the nodes of the
* binary tree. The first NUMVALS nodes are the leaves of the
* tree and represent the values of the data bytes being
* encoded and the special endfile, SPEOF.
* The remaining nodes become the internal nodes of the tree.
*
* In the original design it was believed that
* a Huffman code would fit in the same number of
* bits that will hold the sum of all the counts.
* That was disproven by a user's file and was a rare but
* infamous bug. This version attempts to choose among equally
* weighted subtrees according to their maximum depths to avoid
* unnecessarily long codes. In case that is not sufficient
* to guarantee codes <= 16 bits long, we initially scale
* the counts so the total fits in an unsigned integer, but
* if codes longer than 16 bits are generated the counts are
* rescaled to a lower ceiling and code generation is retried.
*/
/*
* Return the greater of two integers.
*/
static int
Nu_SQMax(int a, int b)
{
if (a > b)
return a;
else
return b;
}
/*
* Compare two trees, if a > b return true, else return false.
* Priority is given to weight, then depth. "a" and "b" are heaps,
* so we only need to look at the root element.
*/
static int
Nu_SQCmpTrees(SQState* pSqState, int a, int b)
{
if (pSqState->node[a].weight > pSqState->node[b].weight)
return true;
if (pSqState->node[a].weight == pSqState->node[b].weight)
if (pSqState->node[a].tdepth > pSqState->node[b].tdepth)
return true;
return false;
}
/*
* heap() and adjust() maintain a list of binary trees as a
* heap with the top indexing the binary tree on the list
* which has the least weight or, in case of equal weights,
* least depth in its longest path. The depth part is not
* strictly necessary, but tends to avoid long codes which
* might provoke rescaling.
*/
/*
* Recursively make a heap from a heap with a new top.
*/
static void
Nu_SQHeapAdjust(SQState* pSqState, int list[], int top, int bottom)
{
int k, temp;
k = 2 * top + 1; /* left child of top */
temp = list[top]; /* remember root node of top tree */
if (k <= bottom) {
if (k < bottom && Nu_SQCmpTrees(pSqState, list[k], list[k + 1]))
k++;
/* k indexes "smaller" child (in heap of trees) of top */
/* now make top index "smaller" of old top and smallest child */
if (Nu_SQCmpTrees(pSqState, temp, list[k])) {
list[top] = list[k];
list[k] = temp;
/* Make the changed list a heap */
Nu_SQHeapAdjust(pSqState, list, k, bottom); /*recursive*/
}
}
}
/*
* Create a heap.
*/
static void
Nu_SQHeap(SQState* pSqState, int list[], int length)
{
int i;
for (i = (length - 2) / 2; i >= 0; i--)
Nu_SQHeapAdjust(pSqState, list, i, length - 1);
}
/*
* Build the encoding tree.
*
* HUFFMAN ALGORITHM: develops the single element trees
* into a single binary tree by forming subtrees rooted in
* interior nodes having weights equal to the sum of weights of all
* their descendents and having depth counts indicating the
* depth of their longest paths.
*
* When all trees have been formed into a single tree satisfying
* the heap property (on weight, with depth as a tie breaker)
* then the binary code assigned to a leaf (value to be encoded)
* is then the series of left (0) and right (1)
* paths leading from the root to the leaf.
* Note that trees are removed from the heaped list by
* moving the last element over the top element and
* reheaping the shorter list.
*/
static void
Nu_SQBuildTree(SQState* pSqState, int list[], int len)
{
int freenode; /* next free node in tree */
EncTreeNode* frnp; /* free node pointer */
int lch, rch; /* temporaries for left, right children */
/*
* Initialize index to next available (non-leaf) node.
* Lower numbered nodes correspond to leaves (data values).
*/
freenode = kNuSQNumVals;
while (len > 1) {
/*
* Take from list two btrees with least weight
* and build an interior node pointing to them.
* This forms a new tree.
*/
lch = list[0]; /* This one will be left child */
/* delete top (least) tree from the list of trees */
list[0] = list[--len];
Nu_SQHeapAdjust(pSqState, list, 0, len - 1);
/* Take new top (least) tree. Reuse list slot later */
rch = list[0]; /* This one will be right child */
/*
* Form new tree from the two least trees using
* a free node as root. Put the new tree in the list.
*/
frnp = &pSqState->node[freenode]; /* address of next free node */
list[0] = freenode++; /* put at top for now */
frnp->lchild = lch;
frnp->rchild = rch;
frnp->weight =
pSqState->node[lch].weight + pSqState->node[rch].weight;
frnp->tdepth = 1 + Nu_SQMax(pSqState->node[lch].tdepth,
pSqState->node[rch].tdepth);
/* reheap list to get least tree at top*/
Nu_SQHeapAdjust(pSqState, list, 0, len - 1);
}
pSqState->treeHead = list[0]; /* head of final tree */
}
/*
* Recursive routine to walk the indicated subtree and level
* and maintain the current path code in bstree. When a leaf
* is found the entire code string and length are put into
* the encoding table entry for the leaf's data value .
*
* Returns zero on success, nonzero if codes are too long.
*/
static int
Nu_SQBuildEncTable(SQState* pSqState, int level, int root)
{
int l, r;
l = pSqState->node[root].lchild;
r = pSqState->node[root].rchild;
if (l == kNuSQNoChild && r == kNuSQNoChild) {
/* Leaf. Previous path determines bit string
* code of length level (bits 0 to level - 1).
* Ensures unused code bits are zero.
*/
pSqState->codeLen[root] = level;
pSqState->code[root] =
pSqState->tmpCode & (((ushort)~0) >> (16 - level));
return (level > 16) ? -1 : 0;
} else {
if (l != kNuSQNoChild) {
/* Clear path bit and continue deeper */
pSqState->tmpCode &= ~(1 << level);
/* NOTE RECURSION */
if (Nu_SQBuildEncTable(pSqState, level + 1, l) != 0)
return -1;
}
if (r != kNuSQNoChild) {
/* Set path bit and continue deeper */
pSqState->tmpCode |= 1 << level;
/* NOTE RECURSION */
if (Nu_SQBuildEncTable(pSqState, level + 1, r) != 0)
return -1;
}
}
return 0; /* if we got here we're ok so far */
}
/*
* The count of number of occurrances of each input value
* have already been prevented from exceeding MAXCOUNT.
* Now we must scale them so that their sum doesn't exceed
* ceiling and yet no non-zero count can become zero.
* This scaling prevents errors in the weights of the
* interior nodes of the Huffman tree and also ensures that
* the codes will fit in an unsigned integer. Rescaling is
* used if necessary to limit the code length.
*/
static void
Nu_SQScale(SQState* pSqState, int ceiling)
{
int i;
int wt, ovflw, divisor;
ushort sum;
int increased; /* flag */
do {
for (i = sum = ovflw = 0; i < kNuSQNumVals; i++) {
if (pSqState->node[i].weight > (ceiling - sum))
ovflw++;
sum += pSqState->node[i].weight;
}
divisor = ovflw + 1; /* use the high 16 bits of the sum */
/* Ensure no non-zero values are lost */
increased = false;
for (i = 0; i < kNuSQNumVals; i++) {
wt = pSqState->node[i].weight;
if (wt < divisor && wt != 0) {
/* Don't fail to provide a code if it's used at all */
pSqState->node[i].weight = divisor;
increased = true;
}
}
} while(increased);
/* scaling factor choosen and minimums are set; now do the downscale */
if (divisor > 1) {
for (i = 0; i < kNuSQNumVals; i++)
pSqState->node[i].weight /= divisor;
}
}
/*
* Build a frequency table from the post-RLE input stream, then generate
* an encoding tree from the results.
*/
static NuError
Nu_SQComputeHuffTree(SQState* pSqState)
{
NuError err = kNuErrNone;
int btreeList[kNuSQNumVals]; /* list of intermediate binary trees */
int listLen; /* length of btreeList */
int ceiling; /* limit for scaling */
int i, sym, result;
/* init tree */
for (i = 0; i < kNuSQNumNodes; i++) {
pSqState->node[i].weight = 0;
pSqState->node[i].tdepth = 0;
pSqState->node[i].lchild = kNuSQNoChild;
pSqState->node[i].rchild = kNuSQNoChild;
}
DBUG(("+++ SQ scanning...\n"));
do {
int* pWeight;
err = Nu_SQGetcRLE(pSqState, &sym);
if (err != kNuErrNone)
goto bail;
Assert(sym >= 0 && sym <= kNuSQEOFToken);
pWeight = &pSqState->node[(unsigned)sym].weight;
if (*pWeight != kNuSQMaxCount)
(*pWeight)++;
} while (sym != kNuSQEOFToken);
DBUG(("+++ SQ generating tree...\n"));
ceiling = kNuSQMaxCount;
do {
if (ceiling != kNuSQMaxCount) {
DBUG(("+++ SQ rescaling\n"));
}
/* pick a divisor and scale everything to fit in "ceiling" */
Nu_SQScale(pSqState, ceiling);
ceiling /= 2; /* in case we need to rescale */
/*
* Build list of single node binary trees having
* leaves for the input values with non-zero counts
*/
for (i = listLen = 0; i < kNuSQNumVals; i++) {
if (pSqState->node[i].weight != 0) {
pSqState->node[i].tdepth = 0;
btreeList[listLen++] = i;
}
}
/*
* Arrange list of trees into a heap with the entry
* indexing the node with the least weight a the top.
*/
Nu_SQHeap(pSqState, btreeList, listLen);
/* convert the list of trees to a single decoding tree */
Nu_SQBuildTree(pSqState, btreeList, listLen);
/* initialize encoding table */
for (i = 0; i < kNuSQNumVals; i++)
pSqState->codeLen[i] = 0;
/*
* Recursively build the encoding table; returns non-zero (failure)
* if any code is > 16 bits long.
*/
result = Nu_SQBuildEncTable(pSqState, 0, pSqState->treeHead);
} while (result != 0);
#if 0
{
int jj;
printf("init_huff\n");
for (jj = 0; jj < kNuSQNumNodes; jj++) {
printf("NODE %d: w=%d d=%d l=%d r=%d\n", jj,
pSqState->node[jj].weight,
pSqState->node[jj].tdepth,
pSqState->node[jj].lchild,
pSqState->node[jj].rchild);
}
}
#endif
bail:
return err;
}
/*
* Compress data from input to output, using the values in the "code"
* and "codeLen" arrays.
*/
static NuError
Nu_SQCompressInput(SQState* pSqState, FILE* fp, long* pCompressedLen)
{
NuError err = kNuErrNone;
int sym = kNuSQEOFToken-1;
unsigned long bits, code; /* must hold at least 23 bits */
int codeLen, gotbits;
long compressedLen;
DBUG(("+++ SQ compressing\n"));
Assert(sizeof(bits) >= 4);
compressedLen = *pCompressedLen;
bits = 0;
gotbits = 0;
while (sym != kNuSQEOFToken) {
err = Nu_SQGetcRLE(pSqState, &sym);
if (err != kNuErrNone)
goto bail;
code = pSqState->code[sym];
codeLen = pSqState->codeLen[sym];
bits |= code << gotbits;
gotbits += codeLen;
/* if we have more than a byte, output it */
while (gotbits > 7) {
putc(bits & 0xff, fp);
compressedLen++;
bits >>= 8;
gotbits -= 8;
}
}
if (gotbits) {
Assert(gotbits < 8);
putc(bits & 0xff, fp);
compressedLen++;
}
bail:
*pCompressedLen = compressedLen;
return err;
}
/*
* Write a 16-bit value in little-endian order.
*/
static NuError
Nu_SQWriteShort(FILE* outfp, short val)
{
NuError err;
uchar tmpc;
tmpc = val & 0xff;
err = Nu_FWrite(outfp, &tmpc, 1);
if (err != kNuErrNone)
goto bail;
tmpc = (val >> 8) & 0xff;
err = Nu_FWrite(outfp, &tmpc, 1);
if (err != kNuErrNone)
goto bail;
bail:
return err;
}
/*
* Compress "srcLen" bytes into SQ format, from "pStraw" to "fp".
*
* This requires two passes through the input.
*
* Bit of trivia: "sq3" on the Apple II self-destructs if you hand
* it an empty file. "xsq" works fine, creating an empty tree that
* "xusq" unpacks.
*/
NuError
Nu_CompressHuffmanSQ(NuArchive* pArchive, NuStraw* pStraw, FILE* fp,
ulong srcLen, ulong* pDstLen, ushort* pCrc)
{
NuError err = kNuErrNone;
SQState sqState;
long compressedLen;
int i, j, numNodes;
err = Nu_AllocCompressionBufferIFN(pArchive);
if (err != kNuErrNone)
return err;
sqState.pArchive = pArchive;
sqState.crc = 0;
if (pCrc == nil) {
sqState.doCalcCRC = false;
} else {
sqState.doCalcCRC = true;
sqState.crc = *pCrc;
}
#ifdef FULL_SQ_HEADER
sqState.checksum = 0;
#endif
/*
* Pass 1: analysis. Perform a frequency analysis on the post-RLE
* input file. This will calculate the file CRCs as a side effect.
*/
sqState.rleState = kNuSQRLEStateNoHist;
sqState.uncompRemaining = srcLen;
sqState.pStraw = pStraw;
(void) Nu_StrawSetProgressState(pStraw, kNuProgressAnalyzing);
err = Nu_SQComputeHuffTree(&sqState);
BailError(err);
if (pCrc != nil)
*pCrc = sqState.crc;
/*
* Pass 2: compression. Using the encoding tree we computed,
* compress the input with RLE and Huffman. Start by writing
* the file header and rewinding the input file.
*/
sqState.doCalcCRC = false; /* don't need to re-compute */
sqState.rleState = kNuSQRLEStateNoHist; /* reset */
compressedLen = 0;
/* rewind for next pass */
(void) Nu_StrawSetProgressState(pStraw, kNuProgressCompressing);
err = Nu_StrawRewind(pArchive, pStraw);
BailError(err);
sqState.uncompRemaining = srcLen;
#ifdef FULL_SQ_HEADER
/* write file header */
err = Nu_SQWriteShort(fp, kNuSQMagic);
BailError(err);
compressedLen += 2;
err = Nu_SQWriteShort(fp, sqState.checksum);
BailError(err);
compressedLen += 2;
{
static const char fakename[] = "s.qqq";
err = Nu_FWrite(fp, fakename, sizeof(fakename));
BailError(err);
compressedLen += sizeof(fakename);
}
#endif
/*
* Original description:
* Write out a simplified decoding tree. Only the interior
* nodes are written. When a child is a leaf index
* (representing a data value) it is recoded as
* -(index + 1) to distinguish it from interior indexes
* which are recoded as positive indexes in the new tree.
* Note that this tree will be empty for an empty file.
*/
if (sqState.treeHead < kNuSQNumVals)
numNodes = 0;
else
numNodes = sqState.treeHead - (kNuSQNumVals - 1);
err = Nu_SQWriteShort(fp, (short) numNodes);
BailError(err);
compressedLen += 2;
for (i = sqState.treeHead, j = 0; j < numNodes; j++, i--) {
int l, r;
l = sqState.node[i].lchild;
r = sqState.node[i].rchild;
l = l < kNuSQNumVals ? -(l + 1) : sqState.treeHead - l;
r = r < kNuSQNumVals ? -(r + 1) : sqState.treeHead - r;
err = Nu_SQWriteShort(fp, (short) l);
BailError(err);
err = Nu_SQWriteShort(fp, (short) r);
BailError(err);
compressedLen += 4;
/*DBUG(("TREE %d: %d %d\n", j, l, r));*/
}
/*
* Convert the input to RLE/Huffman.
*/
err = Nu_SQCompressInput(&sqState, fp, &compressedLen);
BailError(err);
/*
* Done!
*/
*pDstLen = compressedLen;
bail:
return err;
}
/*
* ===========================================================================
* Expansion
* ===========================================================================
*/
/*
* State during uncompression.
*/
typedef struct USQState {
ulong dataInBuffer;
uchar* dataPtr;
int bitPosn;
int bits;
/*
* Decoding tree; first "nodeCount" values are populated. Positive
* values are indicies 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
Nu_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
Nu_USQReadShort(USQState* pUsqState, short* pShort)
{
if (pUsqState->dataInBuffer < 2)
return kNuErrBufferUnderrun;
*pShort = *pUsqState->dataPtr++;
*pShort |= (*pUsqState->dataPtr++) << 8;
pUsqState->dataInBuffer -= 2;
return kNuErrNone;
}
/*
* Expand "SQ" format.
*
* Because we have a stop symbol, knowing the uncompressed length of
* the file is not essential.
*/
NuError
Nu_ExpandHuffmanSQ(NuArchive* pArchive, const NuRecord* pRecord,
const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, ushort* pCrc)
{
NuError err = kNuErrNone;
USQState usqState;
ulong compRemaining, getSize;
#ifdef FULL_SQ_HEADER
ushort magic, fileChecksum, checksum;
#endif
short nodeCount;
int i, inrep;
uchar lastc = 0;
err = Nu_AllocCompressionBufferIFN(pArchive);
if (err != kNuErrNone)
return err;
Assert(pArchive->compBuf != nil);
usqState.dataInBuffer = 0;
usqState.dataPtr = pArchive->compBuf;
usqState.bits = usqState.bitPosn = 0;
compRemaining = pThread->thCompThreadEOF;
#ifdef FULL_SQ_HEADER
if (compRemaining < 8)
#else
if (compRemaining < 3)
#endif
{
err = kNuErrBadData;
Nu_ReportError(NU_BLOB, err, "thread too short to be valid SQ data");
goto bail;
}
getSize = compRemaining;
if (getSize > kNuGenCompBufSize)
getSize = kNuGenCompBufSize;
/*
* 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.
*/
err = Nu_FRead(infp, usqState.dataPtr, getSize);
if (err != kNuErrNone) {
Nu_ReportError(NU_BLOB, err,
"failed reading compressed data (%ld bytes)", getSize);
goto bail;
}
usqState.dataInBuffer += getSize;
compRemaining -= getSize;
/*
* Read the header. We assume that the header is less than
* kNuGenCompBufSize bytes, which is pretty fair since the buffer is
* currently 20x larger than the longest possible header (sq allowed
* 300+ for the filename, plus 257*2 for the tree, plus misc).
*/
Assert(kNuGenCompBufSize > 1200);
#ifdef FULL_SQ_HEADER
err = Nu_USQReadShort(&usqState, &magic);
BailError(err);
if (magic != kNuSQMagic) {
err = kNuErrBadData;
Nu_ReportError(NU_BLOB, err, "bad magic number in SQ block");
goto bail;
}
err = Nu_USQReadShort(&usqState, &fileChecksum);
BailError(err);
checksum = 0;
while (*usqState.dataPtr++ != '\0')
usqState.dataInBuffer--;
usqState.dataInBuffer--;
#endif
err = Nu_USQReadShort(&usqState, &nodeCount);
BailError(err);
if (nodeCount < 0 || nodeCount >= kNuSQNumVals) {
err = kNuErrBadData;
Nu_ReportError(NU_BLOB, err, "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 = Nu_USQReadShort(&usqState, &usqState.decTree[i].child[0]);
err = Nu_USQReadShort(&usqState, &usqState.decTree[i].child[1]);
}
if (err != kNuErrNone) {
err = kNuErrBadData;
Nu_ReportError(NU_BLOB, err, "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(pArchive->compBuf != usqState.dataPtr);
memmove(pArchive->compBuf, usqState.dataPtr,
usqState.dataInBuffer);
}
usqState.dataPtr = pArchive->compBuf;
/*
* Next we read as much as we can.
*/
if (kNuGenCompBufSize - usqState.dataInBuffer < compRemaining)
getSize = kNuGenCompBufSize - usqState.dataInBuffer;
else
getSize = compRemaining;
err = Nu_FRead(infp, usqState.dataPtr + usqState.dataInBuffer,
getSize);
if (err != kNuErrNone) {
Nu_ReportError(NU_BLOB, err,
"failed reading compressed data (%ld bytes)", getSize);
goto bail;
}
usqState.dataInBuffer += getSize;
compRemaining -= getSize;
Assert(compRemaining < 32767*65536);
Assert(usqState.dataInBuffer <= kNuGenCompBufSize);
}
err = Nu_USQDecodeHuffSymbol(&usqState, &val);
if (err != kNuErrNone) {
Nu_ReportError(NU_BLOB, err, "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 != nil)
*pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1);
err = Nu_FunnelWrite(pArchive, pFunnel, &lastc, 1);
#ifdef FULL_SQ_HEADER
checksum += lastc;
#endif
}
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 != nil)
*pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1);
err = Nu_FunnelWrite(pArchive, pFunnel, &lastc, 1);
#ifdef FULL_SQ_HEADER
checksum += lastc;
#endif
}
}
}
if (inrep) {
err = kNuErrBadData;
Nu_ReportError(NU_BLOB, err,
"got stop symbol when run length expected");
goto bail;
}
#ifdef FULL_SQ_HEADER
/* verify the checksum stored in the SQ file */
if (checksum != fileChecksum && !pArchive->valIgnoreCRC) {
if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadDataCRC)) {
err = kNuErrBadDataCRC;
Nu_ReportError(NU_BLOB, err, "expected 0x%04x, got 0x%04x (SQ)",
fileChecksum, checksum);
(void) Nu_FunnelFlush(pArchive, pFunnel);
goto bail;
}
} else {
DBUG(("--- SQ checksums match (0x%04x)\n", checksum));
}
#endif
/*
* SQ2 adds an extra 0xff to the end, xsq doesn't. In any event, it
* appears that having an extra byte at the end is okay.
*/
if (usqState.dataInBuffer > 1) {
DBUG(("--- Found %ld bytes following compressed data (compRem=%ld)\n",
usqState.dataInBuffer, compRemaining));
Nu_ReportError(NU_BLOB, kNuErrNone, "(Warning) unexpected fluff (%ld)",
usqState.dataInBuffer);
}
bail:
return err;
}
#endif /*ENABLE_SQ*/