From 4e7c2fd40319aaae80d871344f53775d7655c9e3 Mon Sep 17 00:00:00 2001 From: Daniel Stien Date: Tue, 9 Jan 2024 03:28:54 +1100 Subject: [PATCH] Split Stunts format and compression methods to separate files. --- include/stunpack.h | 36 +-- src/lib/Makefile | 3 +- src/lib/stunpack.c | 665 +----------------------------------------- src/lib/stunts.c | 131 +++++++++ src/lib/stunts.h | 42 +++ src/lib/stunts_huff.c | 260 +++++++++++++++++ src/lib/stunts_huff.h | 40 +++ src/lib/stunts_rle.c | 248 ++++++++++++++++ src/lib/stunts_rle.h | 35 +++ src/lib/util.c | 73 +++++ src/lib/util.h | 46 +++ src/main.c | 20 +- 12 files changed, 901 insertions(+), 698 deletions(-) create mode 100644 src/lib/stunts.c create mode 100644 src/lib/stunts.h create mode 100644 src/lib/stunts_huff.c create mode 100644 src/lib/stunts_huff.h create mode 100644 src/lib/stunts_rle.c create mode 100644 src/lib/stunts_rle.h create mode 100644 src/lib/util.c create mode 100644 src/lib/util.h diff --git a/include/stunpack.h b/include/stunpack.h index 29bc59a..c75b59f 100644 --- a/include/stunpack.h +++ b/include/stunpack.h @@ -20,6 +20,8 @@ #ifndef STPK_STUNPACK_H #define STPK_STUNPACK_H +#include + #define STPK_VERSION "0.2.0" #define STPK_NAME "stunpack" #define STPK_BUGS "daniel@stien.org" @@ -28,29 +30,6 @@ #define STPK_RET_ERR 1 #define STPK_RET_ERR_DATA_LEFT 2 -#define STPK_MAX_SIZE 0xFFFFFF -#define STPK_PASSES_MASK 0x7F -#define STPK_PASSES_RECUR 0x80 - -#define STPK_TYPE_RLE 0x01 -#define STPK_TYPE_HUFF 0x02 - -#define STPK_RLE_ESCLEN_MASK 0x7F -#define STPK_RLE_ESCLEN_MAX 0x0A -#define STPK_RLE_ESCLEN_NOSEQ 0x80 -#define STPK_RLE_ESCLOOKUP_LEN 0x100 -#define STPK_RLE_ESCSEQ_POS 0x01 - -#define STPK_HUFF_LEVELS_MASK 0x7F -#define STPK_HUFF_LEVELS_MAX 0x10 -#define STPK_HUFF_LEVELS_DELTA 0x80 - -#define STPK_HUFF_ALPH_LEN 0x100 -#define STPK_HUFF_PREFIX_WIDTH 0x08 -#define STPK_HUFF_PREFIX_LEN (1 << STPK_HUFF_PREFIX_WIDTH) -#define STPK_HUFF_PREFIX_MSB (1 << (STPK_HUFF_PREFIX_WIDTH - 1)) -#define STPK_HUFF_WIDTH_ESC 0x40 - typedef enum { // Automatic format detection when decompressing. STPK_FMT_AUTO, @@ -124,15 +103,6 @@ void stpk_deinit(stpk_Context *ctx); unsigned int stpk_decompress(stpk_Context *ctx); -const char *stpk_versionStr(stpk_FmtStuntsVer version); - -unsigned int stpk_decompRLE(stpk_Context *ctx); -unsigned int stpk_rleDecodeSeq(stpk_Context *ctx, unsigned char esc); -unsigned int stpk_rleDecodeOne(stpk_Context *ctx, const unsigned char *escLookup); - -unsigned int stpk_decompHuff(stpk_Context *ctx); -unsigned int stpk_huffGenOffsets(stpk_Context *ctx, unsigned int levels, const unsigned char *leafNodesPerLevel, short *codeOffsets, unsigned short *totalCodes); -void stpk_huffGenPrefix(stpk_Context *ctx, unsigned int levels, const unsigned char *leafNodesPerLevel, const unsigned char *alphabet, unsigned char *symbols, unsigned char *widths); -unsigned int stpk_huffDecode(stpk_Context *ctx, const unsigned char *alphabet, const unsigned char *symbols, const unsigned char *widths, const short *codeOffsets, const unsigned short *totalCodes, int delta); +const char *stpk_fmtStuntsVerStr(stpk_FmtStuntsVer version); #endif diff --git a/src/lib/Makefile b/src/lib/Makefile index 454728f..c043bd0 100644 --- a/src/lib/Makefile +++ b/src/lib/Makefile @@ -1,6 +1,5 @@ -# TODO: Configurable suffix BIN = libstunpack$(LIBSUFFIX) -SRCS = stunpack.c +SRCS = stunpack.c stunts.c stunts_huff.c stunts_rle.c util.c OBJS = $(SRCS:%.c=$(BUILDDIR)/%.o) all: $(BUILDDIR)/$(BIN) diff --git a/src/lib/stunpack.c b/src/lib/stunpack.c index db01afd..2ddbad6 100644 --- a/src/lib/stunpack.c +++ b/src/lib/stunpack.c @@ -17,47 +17,9 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include -#include -#include - #include -#define STPK_LOG(show, type, msg, ...) if (show && ctx->logCallback) ctx->logCallback((type), (msg), ## __VA_ARGS__) -#define STPK_MSG(msg, ...) STPK_LOG(ctx->verbosity, STPK_LOG_INFO, (msg), ## __VA_ARGS__) -#define STPK_ERR(msg, ...) STPK_LOG(ctx->verbosity, STPK_LOG_ERR, (msg), ## __VA_ARGS__) -#define STPK_WARN(msg, ...) STPK_LOG(ctx->verbosity, STPK_LOG_WARN, (msg), ## __VA_ARGS__) -#define STPK_NOVERBOSE(msg, ...) STPK_LOG(ctx->verbosity == 1, STPK_LOG_INFO, (msg), ## __VA_ARGS__) -#define STPK_VERBOSE1(msg, ...) STPK_LOG(ctx->verbosity > 1, STPK_LOG_INFO, (msg), ## __VA_ARGS__) -#define STPK_VERBOSE2(msg, ...) STPK_LOG(ctx->verbosity > 2, STPK_LOG_INFO, (msg), ## __VA_ARGS__) -#define STPK_VERBOSE_ARR(arr, len, name) if (ctx->verbosity > 1) stpk_printArray(ctx, arr, len, name) -#define STPK_VERBOSE_HUFF(msg, ...) STPK_VERBOSE2("%6d %6d %2d %2d %04X %s %02X -> " msg "\n", \ - ctx->src.offset, ctx->dst.offset, readWidth, curWidth, curWord, \ - stpk_stringBits16(curWord), code, ## __VA_ARGS__) - -#define STPK_GET_FLAG(data, mask) ((data & mask) == mask) -#define STPK_MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) - -inline unsigned int stpk_rleCopyByte(stpk_Context *ctx, unsigned char cur, unsigned int rep); -inline unsigned char stpk_getHuffByte(stpk_Context *ctx); -inline void stpk_getLength(stpk_Buffer *buf, unsigned int *len); -inline void stpk_dst2src(stpk_Context *ctx); -char *stpk_stringBits16(unsigned short val); -void stpk_printArray(const stpk_Context *ctx, const unsigned char *arr, unsigned int len, const char *name); - -const char *stpk_versionStr(stpk_FmtStuntsVer version) -{ - switch (version) { - case STPK_FMT_STUNTS_VER_AUTO: - return "auto"; - case STPK_FMT_STUNTS_VER_1_0: - return "stunts1.0"; - case STPK_FMT_STUNTS_VER_1_1: - return "stunts1.1"; - default: - return "Unknown"; - } -} +#include "stunts.h" stpk_Context stpk_init(stpk_Format format, int verbosity, stpk_LogCallback logCallback, stpk_AllocCallback allocCallback, stpk_DeallocCallback deallocCallback) { @@ -98,624 +60,21 @@ void stpk_deinit(stpk_Context *ctx) } } -void inline stpk_dst2src(stpk_Context *ctx) -{ - if (ctx->src.data != NULL) { - ctx->deallocCallback(ctx->src.data); - } - ctx->src.data = ctx->dst.data; - ctx->src.len = ctx->dst.len; - ctx->dst.data = NULL; - ctx->src.offset = ctx->dst.offset = 0; -} - -int inline stpk_allocDst(stpk_Context *ctx) -{ - if ((ctx->dst.data = (unsigned char*)ctx->allocCallback(sizeof(unsigned char) * ctx->dst.len)) == NULL) { - STPK_ERR("Error allocating memory for destination buffer. (%s)\n", strerror(errno)); - return 1; - } - return 0; -} - -int inline stpk_isRle(stpk_Buffer *buf) -{ - return buf->data[0] == STPK_TYPE_RLE && buf->data[7] == 0; -} - -// Decompress sub-files in source buffer. unsigned int stpk_decompress(stpk_Context *ctx) { - unsigned char passes, type, i; - unsigned int retval = 1, finalLen, srcOffset; - - STPK_VERBOSE1(" %-10s %s\n", "version", stpk_versionStr(ctx->format.stunts.version)); - - passes = ctx->src.data[ctx->src.offset]; - if (STPK_GET_FLAG(passes, STPK_PASSES_RECUR)) { - ctx->src.offset++; - - passes &= STPK_PASSES_MASK; - STPK_VERBOSE1(" %-10s %d\n", "passes", passes); - - stpk_getLength(&ctx->src, &finalLen); - STPK_VERBOSE1(" %-10s %d\n", "finalLen", finalLen); - STPK_VERBOSE1(" %-8s %d\n", "srcLen", ctx->src.len); - STPK_VERBOSE1(" %-8s %.2f\n", "ratio", (float)finalLen / ctx->src.len); - } - else { - passes = 1; - } - - if (ctx->src.offset > ctx->src.len) { - STPK_ERR("Reached EOF while parsing file header\n"); - return 1; - } - - for (i = 0; i < passes; i++) { - STPK_NOVERBOSE("Pass %d/%d: ", i + 1, passes); - STPK_VERBOSE1("\nPass %d/%d\n", i + 1, passes); - - type = ctx->src.data[ctx->src.offset++]; - stpk_getLength(&ctx->src, &ctx->dst.len); - STPK_VERBOSE1(" %-10s %d\n", "dstLen", ctx->dst.len); - - if (stpk_allocDst(ctx)) { - return 1; - } - - switch (type) { - case STPK_TYPE_RLE: - STPK_VERBOSE1(" %-10s Run-length encoding\n", "type"); - retval = stpk_decompRLE(ctx); - break; - case STPK_TYPE_HUFF: - STPK_VERBOSE1(" %-10s Huffman coding\n", "type"); - srcOffset = ctx->src.offset; - retval = stpk_decompHuff(ctx); - // If selected version is "auto", check if we should retry with BB Stunts 1.0. - if (ctx->format.stunts.version == STPK_FMT_STUNTS_VER_AUTO - && ( - // Decompression failed. - retval == STPK_RET_ERR - // Decompression had source data left, but it is the last pass. - || (retval == STPK_RET_ERR_DATA_LEFT && (i == (passes - 1))) - // There are more passes, but the next is not valid RLE. - || ((i < (passes - 1)) && !stpk_isRle(&ctx->dst)) - ) - ) { - STPK_WARN("Huffman decompression with Stunts 1.1 bit stream format failed, retrying with Stunts 1.0 format.\n"); - ctx->format.stunts.version = STPK_FMT_STUNTS_VER_1_0; - ctx->src.offset = srcOffset; - ctx->dst.offset = 0; - STPK_NOVERBOSE("Pass %d/%d: ", i + 1, passes); - retval = stpk_decompHuff(ctx); - ctx->format.stunts.version = STPK_FMT_STUNTS_VER_AUTO; - } - - // Data left must be checked for BB Stunts 1.0 bit stream detection - // heuristics, but it is not an error. SDTITL.PVS in BB Stunts 1.1 - // has 95 bytes extra, which is random data that is ignored. - if (retval == STPK_RET_ERR_DATA_LEFT) { - retval = STPK_RET_OK; - } - break; - default: - STPK_ERR("Error parsing source file. Expected type 1 (run-length) or 2 (Huffman), got %02X\n", type); - return 1; - } - - if (retval) { - return retval; - } - - if (i + 1 == ctx->format.stunts.maxPasses && passes != ctx->format.stunts.maxPasses) { - STPK_MSG("Parsing limited to %d decompression pass(es), aborting.\n", ctx->format.stunts.maxPasses); - return 0; - } - - // Destination buffer is source for next pass. - if (i < (passes - 1)) { - stpk_dst2src(ctx); - } - } - - return 0; -} - -// Decompress run-length encoded sub-file. -unsigned int stpk_decompRLE(stpk_Context *ctx) -{ - unsigned int srcLen, dstLen, i; - unsigned char unk, escLen, esc[STPK_RLE_ESCLEN_MAX], escLookup[STPK_RLE_ESCLOOKUP_LEN]; - - stpk_getLength(&ctx->src, &srcLen); - STPK_VERBOSE1(" %-10s %d\n", "srcLen", srcLen); - - unk = ctx->src.data[ctx->src.offset++]; - STPK_VERBOSE1(" %-10s %02X\n", "unk", unk); - - if (unk) { - STPK_WARN("Unknown RLE header field (unk) is %02X, expected 0\n", unk); - } - - escLen = ctx->src.data[ctx->src.offset++]; - STPK_VERBOSE1(" %-10s %d (no sequences = %d)\n\n", "escLen", escLen & STPK_RLE_ESCLEN_MASK, STPK_GET_FLAG(escLen, STPK_RLE_ESCLEN_NOSEQ)); - - if ((escLen & STPK_RLE_ESCLEN_MASK) > STPK_RLE_ESCLEN_MAX) { - STPK_ERR("escLen & STPK_RLE_ESCLEN_MASK greater than max length %02X, got %02X\n", STPK_RLE_ESCLEN_MAX, escLen & STPK_RLE_ESCLEN_MASK); - return 1; - } - - // Read escape codes. - for (i = 0; i < (escLen & STPK_RLE_ESCLEN_MASK); i++) esc[i] = ctx->src.data[ctx->src.offset++]; - STPK_VERBOSE_ARR(esc, escLen & STPK_RLE_ESCLEN_MASK, "esc"); - - if (ctx->src.offset > ctx->src.len) { - STPK_ERR("Reached end of source buffer while parsing run-length header\n"); - return 1; - } - - // Generate escape code lookup table where the index is the escape code - // and the value is the escape code's positional property. - for (i = 0; i < STPK_RLE_ESCLOOKUP_LEN; i++) escLookup[i] = 0; - for (i = 0; i < (escLen & STPK_RLE_ESCLEN_MASK); i++) escLookup[esc[i]] = i + 1; - STPK_VERBOSE_ARR(escLookup, STPK_RLE_ESCLOOKUP_LEN, "escLookup"); - - STPK_NOVERBOSE("Run-length "); - - // Decode sequence run as a separate pass. - if (!STPK_GET_FLAG(escLen, STPK_RLE_ESCLEN_NOSEQ)) { - if (stpk_rleDecodeSeq(ctx, esc[STPK_RLE_ESCSEQ_POS])) { - return 1; - } - - srcLen = ctx->dst.offset; - dstLen = ctx->dst.len; - stpk_dst2src(ctx); - ctx->src.len = srcLen; - ctx->dst.len = dstLen; - - if (stpk_allocDst(ctx)) { - return 1; - } - } - - return stpk_rleDecodeOne(ctx, escLookup); -} - -// Decode sequence runs. -unsigned int stpk_rleDecodeSeq(stpk_Context *ctx, unsigned char esc) -{ - unsigned char cur; - unsigned int progress = 0, seqOffset, rep, i; - - STPK_NOVERBOSE("["); - - STPK_VERBOSE1("Decoding sequence runs... "); - STPK_VERBOSE2("\n\nsrcOff dstOff rep seq\n"); - STPK_VERBOSE2("~~~~~~ ~~~~~~ ~~~ ~~~~~~~~\n"); - - // We do not know the destination length for this pass, dst->len covers both RLE passes. - while (ctx->src.offset < ctx->src.len) { - cur = ctx->src.data[ctx->src.offset++]; - - if (cur == esc) { - seqOffset = ctx->src.offset; - - while ((cur = ctx->src.data[ctx->src.offset++]) != esc) { - if (ctx->src.offset >= ctx->src.len) { - STPK_ERR("Reached end of source buffer before finding sequence end escape code %02X\n", esc); - return 1; - } - - ctx->dst.data[ctx->dst.offset++] = cur; - } - - rep = ctx->src.data[ctx->src.offset++] - 1; // Already wrote sequence once. - STPK_VERBOSE2("%6d %6d %02X %2.*X\n", ctx->src.offset, ctx->dst.offset, rep + 1, ctx->src.offset - seqOffset - 2, ctx->src.data[seqOffset]); - - while (rep--) { - for (i = 0; i < (ctx->src.offset - seqOffset - 2); i++) { - if (ctx->dst.offset >= ctx->dst.len) { - STPK_ERR("Reached end of temporary buffer while writing repeated sequence\n"); - return 1; - } - - ctx->dst.data[ctx->dst.offset++] = ctx->src.data[seqOffset + i]; - } - } - - } - else { - ctx->dst.data[ctx->dst.offset++] = cur; - STPK_VERBOSE2("%6d %6d %02X\n", ctx->src.offset, ctx->dst.offset, cur); - - if (ctx->dst.offset > ctx->dst.len) { - STPK_ERR("Reached end of temporary buffer while writing non-RLE byte\n"); - return 1; - } - } - - // Progress bar. - if (ctx->verbosity && (ctx->verbosity < 3) && ((ctx->src.offset * 100) / ctx->src.len) >= (progress * 25)) { - ctx->logCallback(STPK_LOG_INFO, "%4d%%", progress++ * 25); - } - } - - STPK_VERBOSE1("\n"); - STPK_NOVERBOSE("] "); - - return 0; -} - -// Decode single-byte runs. -unsigned int stpk_rleDecodeOne(stpk_Context *ctx, const unsigned char *escLookup) -{ - unsigned char cur; - unsigned int progress = 0, rep; - - STPK_NOVERBOSE("["); - - STPK_VERBOSE1("Decoding single-byte runs... "); - - STPK_VERBOSE2("\n\nsrcOff dstOff rep cur\n"); - STPK_VERBOSE2("~~~~~~ ~~~~~~ ~~~~~ ~~~\n"); - - while (ctx->dst.offset < ctx->dst.len) { - cur = ctx->src.data[ctx->src.offset++]; - - if (ctx->src.offset > ctx->src.len) { - STPK_ERR("Reached unexpected end of source buffer while decoding single-byte runs\n"); - return 1; - } - - if (escLookup[cur]) { - switch (escLookup[cur]) { - // Type 1: One-byte counter for repetitions - case 1: - rep = ctx->src.data[ctx->src.offset]; - cur = ctx->src.data[ctx->src.offset + 1]; - ctx->src.offset += 2; - - if (stpk_rleCopyByte(ctx, cur, rep)) { - return 1; - } - - break; - - // Type 2: Used for sequences. Serves no purpose here, but - // would be handled by the default case if it were to occur. - - // Type 3: Two-byte counter for repetitions - case 3: - rep = ctx->src.data[ctx->src.offset] | ctx->src.data[ctx->src.offset + 1] << 8; - cur = ctx->src.data[ctx->src.offset + 2]; - ctx->src.offset += 3; - - if (stpk_rleCopyByte(ctx, cur, rep)) { - return 1; - } - - break; - - // Type n: n repetitions - default: - rep = escLookup[cur] - 1; - cur = ctx->src.data[ctx->src.offset++]; - - if (stpk_rleCopyByte(ctx, cur, rep)) { - return 1; - } - } - } - else { - ctx->dst.data[ctx->dst.offset++] = cur; - STPK_VERBOSE2("%6d %6d %02X\n", ctx->src.offset, ctx->dst.offset, cur); - } - - // Progress bar. - if (ctx->verbosity && (ctx->verbosity < 3) && ((ctx->src.offset * 100) / ctx->src.len) >= (progress * 25)) { - ctx->logCallback(STPK_LOG_INFO, "%4d%%", progress++ * 25); - } - } - - STPK_VERBOSE1("\n"); - STPK_NOVERBOSE("]\n"); - - if (ctx->src.offset < ctx->src.len) { - STPK_WARN("RLE decoding finished with unprocessed data left in source buffer (%d bytes left)\n", ctx->src.len - ctx->src.offset); - } - - return 0; -} - -inline unsigned int stpk_rleCopyByte(stpk_Context *ctx, unsigned char cur, unsigned int rep) -{ - STPK_VERBOSE2("%6d %6d %02X %02X\n", ctx->src.offset, ctx->dst.offset, rep, cur); - - while (rep--) { - if (ctx->dst.offset >= ctx->dst.len) { - STPK_ERR("Reached end of temporary buffer while writing byte run\n"); - return 1; - } - - ctx->dst.data[ctx->dst.offset++] = cur; - } - - return 0; -} - -// Decompress Huffman coded sub-file. -unsigned int stpk_decompHuff(stpk_Context *ctx) -{ - unsigned char levels, leafNodesPerLevel[STPK_HUFF_LEVELS_MAX], alphabet[STPK_HUFF_ALPH_LEN], symbols[STPK_HUFF_PREFIX_LEN], widths[STPK_HUFF_PREFIX_LEN]; - short codeOffsets[STPK_HUFF_LEVELS_MAX]; - unsigned short totalCodes[STPK_HUFF_LEVELS_MAX]; - unsigned int i, alphLen; - int delta; - - levels = ctx->src.data[ctx->src.offset++]; - delta = STPK_GET_FLAG(levels, STPK_HUFF_LEVELS_DELTA); - levels &= STPK_HUFF_LEVELS_MASK; - - STPK_VERBOSE1(" %-10s %d\n", "levels", levels); - STPK_VERBOSE1(" %-10s %d\n\n", "delta", delta); - - if (levels > STPK_HUFF_LEVELS_MAX) { - STPK_ERR("Huffman tree levels greater than %d, got %d\n", STPK_HUFF_LEVELS_MAX, levels); - return 1; - } - - for (i = 0; i < levels; i++) { - leafNodesPerLevel[i] = ctx->src.data[ctx->src.offset++]; - } - - alphLen = stpk_huffGenOffsets(ctx, levels, leafNodesPerLevel, codeOffsets, totalCodes); - - if (alphLen > STPK_HUFF_ALPH_LEN) { - STPK_ERR("Alphabet longer than than %d, got %d\n", STPK_HUFF_ALPH_LEN, alphLen); - return 1; - } - - // Read alphabet. - for (i = 0; i < alphLen; i++) alphabet[i] = ctx->src.data[ctx->src.offset++]; - STPK_VERBOSE_ARR(alphabet, alphLen, "alphabet"); - - if (ctx->src.offset > ctx->src.len) { - STPK_ERR("Reached end of source buffer while parsing Huffman header\n"); - return 1; - } - - stpk_huffGenPrefix(ctx, levels, leafNodesPerLevel, alphabet, symbols, widths); - - return stpk_huffDecode(ctx, alphabet, symbols, widths, codeOffsets, totalCodes, delta); -} - -// Generate offset table for translating Huffman codes wider than 8 bits to alphabet indices. -unsigned int stpk_huffGenOffsets(stpk_Context *ctx, unsigned int levels, const unsigned char *leafNodesPerLevel, short *codeOffsets, unsigned short *totalCodes) -{ - unsigned int level, codes = 0, alphLen = 0; - - for (level = 0; level < levels; level++) { - codes *= 2; - codeOffsets[level] = alphLen - codes; - - codes += leafNodesPerLevel[level]; - alphLen += leafNodesPerLevel[level]; - - totalCodes[level] = codes; - - STPK_VERBOSE1(" codeOffsets[%2d] = %6d totalCodes[%2d] = %6d\n", level, codeOffsets[level], level, totalCodes[level]); - } - STPK_VERBOSE1("\n"); - - return alphLen; -} - -// Generate prefix table for direct lookup of Huffman codes up to 8 bits wide. -void stpk_huffGenPrefix(stpk_Context *ctx, unsigned int levels, const unsigned char *leafNodesPerLevel, const unsigned char *alphabet, unsigned char *symbols, unsigned char *widths) -{ - unsigned int prefix, alphabetIndex, width = 1, maxWidth = STPK_MIN(levels, STPK_HUFF_PREFIX_WIDTH); - unsigned char leafNodes, totalNodes = STPK_HUFF_PREFIX_MSB, remainingNodes; - - // Fill all prefixes with data from last leaf node. - for (prefix = 0, alphabetIndex = 0; width <= maxWidth; width++, totalNodes >>= 1) { - for (leafNodes = leafNodesPerLevel[width - 1]; leafNodes > 0; leafNodes--, alphabetIndex++) { - for (remainingNodes = totalNodes; remainingNodes; remainingNodes--, prefix++) { - symbols[prefix] = alphabet[alphabetIndex]; - widths[prefix] = width; - } - } - } - STPK_VERBOSE_ARR(symbols, prefix, "symbols"); - - // Pad with escape value for codes wider than 8 bits. - for (; prefix < STPK_HUFF_ALPH_LEN; prefix++) widths[prefix] = STPK_HUFF_WIDTH_ESC; - STPK_VERBOSE_ARR(widths, prefix, "widths"); + return stunts_decompress(ctx); } -// Decode Huffman codes. -unsigned int stpk_huffDecode(stpk_Context *ctx, const unsigned char *alphabet, const unsigned char *symbols, const unsigned char *widths, const short *codeOffsets, const unsigned short *totalCodes, int delta) +const char *stpk_fmtStuntsVerStr(stpk_FmtStuntsVer version) { - unsigned char readWidth = 8, curWidth = 0, curByte, code, level, curOut = 0; - unsigned short curWord = 0; - unsigned int progress = 0; - - curWord = (stpk_getHuffByte(ctx) << 8) | stpk_getHuffByte(ctx); - - STPK_NOVERBOSE("Huffman ["); - - STPK_VERBOSE1("Decoding Huffman codes... \n"); - STPK_VERBOSE2("\nsrcOff dstOff rW cW curWord cd Description\n"); - - while (ctx->dst.offset < ctx->dst.len) { - STPK_VERBOSE2("~~~~~~ ~~~~~~ ~~ ~~ ~~~~~~~~~~~~~~~~~~~~~ ~~ ~~~~~~~~~~~~~~~~~~\n"); - - code = (curWord & 0xFF00) >> 8; - STPK_VERBOSE_HUFF("Shifted %d bits", curWidth); - - curWidth = widths[code]; - - // If code is wider than 8 bits, read more bits and decode with offset table. - if (curWidth > STPK_HUFF_PREFIX_WIDTH) { - if (curWidth != STPK_HUFF_WIDTH_ESC) { - STPK_ERR("Invalid escape value. curWidth != %02X, got %02X\n", STPK_HUFF_WIDTH_ESC, curWidth); - return STPK_RET_ERR; - } - - curByte = (curWord & 0x00FF); - curWord >>= STPK_HUFF_PREFIX_WIDTH; - STPK_VERBOSE_HUFF("Escaping to offset table"); - - // Read bit by bit until a level is found, starting at the max width of the prefix table. - for (level = STPK_HUFF_PREFIX_WIDTH; 1; level++) { - if (!readWidth) { - curByte = stpk_getHuffByte(ctx); - readWidth = 8; - STPK_VERBOSE_HUFF("Read %02X", ctx->src.data[ctx->src.offset - 1]); - } - - curWord = (curWord << 1) + STPK_GET_FLAG(curByte, STPK_HUFF_PREFIX_MSB); - curByte <<= 1; - readWidth--; - STPK_VERBOSE_HUFF("level = %d", level); - - if (level >= STPK_HUFF_LEVELS_MAX) { - STPK_ERR("Offset table out of bounds (%d >= %d)\n", level, STPK_HUFF_LEVELS_MAX); - return STPK_RET_ERR; - } - - if (curWord < totalCodes[level]) { - curWord += codeOffsets[level]; - - if (curWord > 0xFF) { - STPK_ERR("Alphabet index out of bounds (%04X > %04X)\n", curWord, STPK_HUFF_ALPH_LEN); - return STPK_RET_ERR; - } - - if (delta) { - STPK_VERBOSE_HUFF("Using symbol %02X as delta to previous output %02X", alphabet[curWord], curOut); - curOut += alphabet[curWord]; - } - else { - curOut = alphabet[curWord]; - } - - ctx->dst.data[ctx->dst.offset++] = curOut; - STPK_VERBOSE_HUFF("Wrote %02X using offset table", curOut); - - break; - } - } - - // Read another byte since the processed code was wider than a byte. - curWord = (curByte << readWidth) | stpk_getHuffByte(ctx); - curWidth = 8 - readWidth; - readWidth = 8; - STPK_VERBOSE_HUFF("Read %02X", ctx->src.data[ctx->src.offset - 1]); - } - // Code is 8 bits wide or less, do direct prefix lookup. - else { - if (delta) { - STPK_VERBOSE_HUFF("Using symbol %02X as delta to previous output %02X", symbols[code], curOut); - curOut += symbols[code]; - } - else { - curOut = symbols[code]; - } - ctx->dst.data[ctx->dst.offset++] = curOut; - STPK_VERBOSE_HUFF("Wrote %02X from prefix table", curOut); - - if (readWidth < curWidth) { - curWord <<= readWidth; - STPK_VERBOSE_HUFF("Shifted %d bits", readWidth); - - curWidth -= readWidth; - readWidth = 8; - - curWord |= stpk_getHuffByte(ctx); - STPK_VERBOSE_HUFF("Read %02X", ctx->src.data[ctx->src.offset - 1]); - } - } - - curWord <<= curWidth; - readWidth -= curWidth; - - if ((ctx->src.offset - 1) > ctx->src.len && ctx->dst.offset < ctx->dst.len) { - STPK_ERR("Reached unexpected end of source buffer while decoding Huffman codes\n"); - return STPK_RET_ERR; - } - - // Progress bar. - if (ctx->verbosity && (ctx->verbosity < 3) && ((ctx->dst.offset * 100) / ctx->dst.len) >= (progress * 10)) { - ctx->logCallback(STPK_LOG_INFO, "%4d%%", progress++ * 10); - } - } - - STPK_NOVERBOSE("]\n"); - STPK_VERBOSE1("\n"); - - if (ctx->src.offset < ctx->src.len) { - STPK_WARN("Huffman decoding finished with unprocessed data left in source buffer (%d bytes left)\n", ctx->src.len - ctx->src.offset); - return STPK_RET_ERR_DATA_LEFT; - } - - return STPK_RET_OK; -} - -// Read a byte from the Huffman code bit stream, reverse bits if game version is Brøderbund Stunts 1.0. -inline unsigned char stpk_getHuffByte(stpk_Context *ctx) -{ - // https://graphics.stanford.edu/~seander/bithacks.html#BitReverseTable - static const unsigned char reverseBits[] = { -# define R2(n) (n), (n + 2 * 64), (n + 1 * 64), (n + 3 * 64) -# define R4(n) R2(n), R2(n + 2 * 16), R2(n + 1 * 16), R2(n + 3 * 16) -# define R6(n) R4(n), R4(n + 2 * 4), R4(n + 1 * 4), R4(n + 3 * 4) - R6(0), R6(2), R6(1), R6(3) - }; - - unsigned char byte = ctx->src.data[ctx->src.offset++]; - if (ctx->format.stunts.version == STPK_FMT_STUNTS_VER_1_0) { - byte = reverseBits[byte]; - } - return byte; -} - -// Read file length: WORD remainder + BYTE multiplier * 0x10000. -inline void stpk_getLength(stpk_Buffer *buf, unsigned int *len) -{ - *len = buf->data[buf->offset] | buf->data[buf->offset + 1] << 8; // Read remainder. - *len += 0x10000 * buf->data[buf->offset + 2]; // Add multiplier. - buf->offset += 3; -} - -// Write bit values as string to stpk_b16. Used in verbose output. -char *stpk_stringBits16(unsigned short val) -{ - static char stpk_b16[16 + 1]; - int i; - for (i = 0; i < 16; i++) stpk_b16[(16 - 1) - i] = '0' + STPK_GET_FLAG(val, (1 << i)); - stpk_b16[i] = 0; - - return stpk_b16; -} - -// Print formatted array. Used in verbose output. -void stpk_printArray(const stpk_Context *ctx, const unsigned char *arr, unsigned int len, const char *name) -{ - unsigned int i = 0; - - ctx->logCallback(STPK_LOG_INFO, " %s[%02X]\n", name, len); - ctx->logCallback(STPK_LOG_INFO, " 0 1 2 3 4 5 6 7 8 9 A B C D E F\n"); - - while (i < len) { - if ((i % 0x10) == 0) ctx->logCallback(STPK_LOG_INFO, " %2X", i / 0x10); - ctx->logCallback(STPK_LOG_INFO, " %02X", arr[i++]); - if ((i % 0x10) == 0) ctx->logCallback(STPK_LOG_INFO, "\n"); + switch (version) { + case STPK_FMT_STUNTS_VER_AUTO: + return "auto"; + case STPK_FMT_STUNTS_VER_1_0: + return "stunts1.0"; + case STPK_FMT_STUNTS_VER_1_1: + return "stunts1.1"; + default: + return "Unknown"; } - - if ((i % 0x10) != 0) ctx->logCallback(STPK_LOG_INFO, "\n"); - ctx->logCallback(STPK_LOG_INFO, "\n"); } - diff --git a/src/lib/stunts.c b/src/lib/stunts.c new file mode 100644 index 0000000..079900d --- /dev/null +++ b/src/lib/stunts.c @@ -0,0 +1,131 @@ +/* + * stunpack - Stunts/4D [Sports] Driving game resource unpacker + * Copyright (C) 2008-2024 Daniel Stien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "stunts_huff.h" +#include "stunts_rle.h" +#include "util.h" + +#include "stunts.h" + +int inline stunts_isRle(stpk_Buffer *buf); + +// Decompress sub-files in source buffer. +unsigned int stunts_decompress(stpk_Context *ctx) +{ + unsigned char passes, type, i; + unsigned int retval = 1, finalLen, srcOffset; + + UTIL_VERBOSE1(" %-10s %s\n", "version", stpk_fmtStuntsVerStr(ctx->format.stunts.version)); + + passes = ctx->src.data[ctx->src.offset]; + if (UTIL_GET_FLAG(passes, STUNTS_PASSES_RECUR)) { + ctx->src.offset++; + + passes &= STUNTS_PASSES_MASK; + UTIL_VERBOSE1(" %-10s %d\n", "passes", passes); + + stunts_getLength(&ctx->src, &finalLen); + UTIL_VERBOSE1(" %-10s %d\n", "finalLen", finalLen); + UTIL_VERBOSE1(" %-8s %d\n", "srcLen", ctx->src.len); + UTIL_VERBOSE1(" %-8s %.2f\n", "ratio", (float)finalLen / ctx->src.len); + } + else { + passes = 1; + } + + if (ctx->src.offset > ctx->src.len) { + UTIL_ERR("Reached EOF while parsing file header\n"); + return 1; + } + + for (i = 0; i < passes; i++) { + UTIL_NOVERBOSE("Pass %d/%d: ", i + 1, passes); + UTIL_VERBOSE1("\nPass %d/%d\n", i + 1, passes); + + type = ctx->src.data[ctx->src.offset++]; + stunts_getLength(&ctx->src, &ctx->dst.len); + UTIL_VERBOSE1(" %-10s %d\n", "dstLen", ctx->dst.len); + + if (util_allocDst(ctx)) { + return 1; + } + + switch (type) { + case STUNTS_TYPE_RLE: + UTIL_VERBOSE1(" %-10s Run-length encoding\n", "type"); + retval = stunts_rle_decompress(ctx); + break; + case STUNTS_TYPE_HUFF: + UTIL_VERBOSE1(" %-10s Huffman coding\n", "type"); + srcOffset = ctx->src.offset; + retval = stunts_huff_decompress(ctx); + // If selected version is "auto", check if we should retry with BB Stunts 1.0. + if (ctx->format.stunts.version == STPK_FMT_STUNTS_VER_AUTO + && ( + // Decompression failed. + retval == STPK_RET_ERR + // Decompression had source data left, but it is the last pass. + || (retval == STPK_RET_ERR_DATA_LEFT && (i == (passes - 1))) + // There are more passes, but the next is not valid RLE. + || ((i < (passes - 1)) && !stunts_isRle(&ctx->dst)) + ) + ) { + UTIL_WARN("Huffman decompression with Stunts 1.1 bit stream format failed, retrying with Stunts 1.0 format.\n"); + ctx->format.stunts.version = STPK_FMT_STUNTS_VER_1_0; + ctx->src.offset = srcOffset; + ctx->dst.offset = 0; + UTIL_NOVERBOSE("Pass %d/%d: ", i + 1, passes); + retval = stunts_huff_decompress(ctx); + ctx->format.stunts.version = STPK_FMT_STUNTS_VER_AUTO; + } + + // Data left must be checked for BB Stunts 1.0 bit stream detection + // heuristics, but it is not an error. SDTITL.PVS in BB Stunts 1.1 + // has 95 bytes extra, which is random data that is ignored. + if (retval == STPK_RET_ERR_DATA_LEFT) { + retval = STPK_RET_OK; + } + break; + default: + UTIL_ERR("Error parsing source file. Expected type 1 (run-length) or 2 (Huffman), got %02X\n", type); + return 1; + } + + if (retval) { + return retval; + } + + if (i + 1 == ctx->format.stunts.maxPasses && passes != ctx->format.stunts.maxPasses) { + UTIL_MSG("Parsing limited to %d decompression pass(es), aborting.\n", ctx->format.stunts.maxPasses); + return 0; + } + + // Destination buffer is source for next pass. + if (i < (passes - 1)) { + util_dst2src(ctx); + } + } + + return 0; +} + +int inline stunts_isRle(stpk_Buffer *buf) +{ + return buf->data[0] == STUNTS_TYPE_RLE && buf->data[7] == 0; +} diff --git a/src/lib/stunts.h b/src/lib/stunts.h new file mode 100644 index 0000000..591949b --- /dev/null +++ b/src/lib/stunts.h @@ -0,0 +1,42 @@ +/* + * stunpack - Stunts/4D [Sports] Driving game resource unpacker + * Copyright (C) 2008-2024 Daniel Stien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef STPK_LIB_STUNTS_H +#define STPK_LIB_STUNTS_H + +#include + +#define STUNTS_MAX_SIZE 0xFFFFFF +#define STUNTS_PASSES_MASK 0x7F +#define STUNTS_PASSES_RECUR 0x80 + +#define STUNTS_TYPE_RLE 0x01 +#define STUNTS_TYPE_HUFF 0x02 + +unsigned int stunts_decompress(stpk_Context *ctx); + +// Read file length: WORD remainder + BYTE multiplier * 0x10000. +inline void stunts_getLength(stpk_Buffer *buf, unsigned int *len) +{ + *len = buf->data[buf->offset] | buf->data[buf->offset + 1] << 8; // Read remainder. + *len += 0x10000 * buf->data[buf->offset + 2]; // Add multiplier. + buf->offset += 3; +} + +#endif diff --git a/src/lib/stunts_huff.c b/src/lib/stunts_huff.c new file mode 100644 index 0000000..b831334 --- /dev/null +++ b/src/lib/stunts_huff.c @@ -0,0 +1,260 @@ +/* + * stunpack - Stunts/4D [Sports] Driving game resource unpacker + * Copyright (C) 2008-2024 Daniel Stien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "util.h" + +#include "stunts_huff.h" + +inline unsigned char stpk_getHuffByte(stpk_Context *ctx); + +// Decompress Huffman coded sub-file. +unsigned int stunts_huff_decompress(stpk_Context *ctx) +{ + unsigned char levels, leafNodesPerLevel[STUNTS_HUFF_LEVELS_MAX], alphabet[STUNTS_HUFF_ALPH_LEN], symbols[STUNTS_HUFF_PREFIX_LEN], widths[STUNTS_HUFF_PREFIX_LEN]; + short codeOffsets[STUNTS_HUFF_LEVELS_MAX]; + unsigned short totalCodes[STUNTS_HUFF_LEVELS_MAX]; + unsigned int i, alphLen; + int delta; + + levels = ctx->src.data[ctx->src.offset++]; + delta = UTIL_GET_FLAG(levels, STUNTS_HUFF_LEVELS_DELTA); + levels &= STUNTS_HUFF_LEVELS_MASK; + + UTIL_VERBOSE1(" %-10s %d\n", "levels", levels); + UTIL_VERBOSE1(" %-10s %d\n\n", "delta", delta); + + if (levels > STUNTS_HUFF_LEVELS_MAX) { + UTIL_ERR("Huffman tree levels greater than %d, got %d\n", STUNTS_HUFF_LEVELS_MAX, levels); + return 1; + } + + for (i = 0; i < levels; i++) { + leafNodesPerLevel[i] = ctx->src.data[ctx->src.offset++]; + } + + alphLen = stunts_huff_genOffsets(ctx, levels, leafNodesPerLevel, codeOffsets, totalCodes); + + if (alphLen > STUNTS_HUFF_ALPH_LEN) { + UTIL_ERR("Alphabet longer than than %d, got %d\n", STUNTS_HUFF_ALPH_LEN, alphLen); + return 1; + } + + // Read alphabet. + for (i = 0; i < alphLen; i++) alphabet[i] = ctx->src.data[ctx->src.offset++]; + UTIL_VERBOSE_ARR(alphabet, alphLen, "alphabet"); + + if (ctx->src.offset > ctx->src.len) { + UTIL_ERR("Reached end of source buffer while parsing Huffman header\n"); + return 1; + } + + stunts_huff_genPrefix(ctx, levels, leafNodesPerLevel, alphabet, symbols, widths); + + return stunts_huff_decode(ctx, alphabet, symbols, widths, codeOffsets, totalCodes, delta); +} + +// Generate offset table for translating Huffman codes wider than 8 bits to alphabet indices. +unsigned int stunts_huff_genOffsets(stpk_Context *ctx, unsigned int levels, const unsigned char *leafNodesPerLevel, short *codeOffsets, unsigned short *totalCodes) +{ + unsigned int level, codes = 0, alphLen = 0; + + for (level = 0; level < levels; level++) { + codes *= 2; + codeOffsets[level] = alphLen - codes; + + codes += leafNodesPerLevel[level]; + alphLen += leafNodesPerLevel[level]; + + totalCodes[level] = codes; + + UTIL_VERBOSE1(" codeOffsets[%2d] = %6d totalCodes[%2d] = %6d\n", level, codeOffsets[level], level, totalCodes[level]); + } + UTIL_VERBOSE1("\n"); + + return alphLen; +} + +// Generate prefix table for direct lookup of Huffman codes up to 8 bits wide. +void stunts_huff_genPrefix(stpk_Context *ctx, unsigned int levels, const unsigned char *leafNodesPerLevel, const unsigned char *alphabet, unsigned char *symbols, unsigned char *widths) +{ + unsigned int prefix, alphabetIndex, width = 1, maxWidth = UTIL_MIN(levels, STUNTS_HUFF_PREFIX_WIDTH); + unsigned char leafNodes, totalNodes = STUNTS_HUFF_PREFIX_MSB, remainingNodes; + + // Fill all prefixes with data from last leaf node. + for (prefix = 0, alphabetIndex = 0; width <= maxWidth; width++, totalNodes >>= 1) { + for (leafNodes = leafNodesPerLevel[width - 1]; leafNodes > 0; leafNodes--, alphabetIndex++) { + for (remainingNodes = totalNodes; remainingNodes; remainingNodes--, prefix++) { + symbols[prefix] = alphabet[alphabetIndex]; + widths[prefix] = width; + } + } + } + UTIL_VERBOSE_ARR(symbols, prefix, "symbols"); + + // Pad with escape value for codes wider than 8 bits. + for (; prefix < STUNTS_HUFF_ALPH_LEN; prefix++) widths[prefix] = STUNTS_HUFF_WIDTH_ESC; + UTIL_VERBOSE_ARR(widths, prefix, "widths"); +} + +// Decode Huffman codes. +unsigned int stunts_huff_decode(stpk_Context *ctx, const unsigned char *alphabet, const unsigned char *symbols, const unsigned char *widths, const short *codeOffsets, const unsigned short *totalCodes, int delta) +{ + unsigned char readWidth = 8, curWidth = 0, curByte, code, level, curOut = 0; + unsigned short curWord = 0; + unsigned int progress = 0; + + curWord = (stpk_getHuffByte(ctx) << 8) | stpk_getHuffByte(ctx); + + UTIL_NOVERBOSE("Huffman ["); + + UTIL_VERBOSE1("Decoding Huffman codes... \n"); + UTIL_VERBOSE2("\nsrcOff dstOff rW cW curWord cd Description\n"); + + while (ctx->dst.offset < ctx->dst.len) { + UTIL_VERBOSE2("~~~~~~ ~~~~~~ ~~ ~~ ~~~~~~~~~~~~~~~~~~~~~ ~~ ~~~~~~~~~~~~~~~~~~\n"); + + code = (curWord & 0xFF00) >> 8; + UTIL_VERBOSE_HUFF("Shifted %d bits", curWidth); + + curWidth = widths[code]; + + // If code is wider than 8 bits, read more bits and decode with offset table. + if (curWidth > STUNTS_HUFF_PREFIX_WIDTH) { + if (curWidth != STUNTS_HUFF_WIDTH_ESC) { + UTIL_ERR("Invalid escape value. curWidth != %02X, got %02X\n", STUNTS_HUFF_WIDTH_ESC, curWidth); + return STPK_RET_ERR; + } + + curByte = (curWord & 0x00FF); + curWord >>= STUNTS_HUFF_PREFIX_WIDTH; + UTIL_VERBOSE_HUFF("Escaping to offset table"); + + // Read bit by bit until a level is found, starting at the max width of the prefix table. + for (level = STUNTS_HUFF_PREFIX_WIDTH; 1; level++) { + if (!readWidth) { + curByte = stpk_getHuffByte(ctx); + readWidth = 8; + UTIL_VERBOSE_HUFF("Read %02X", ctx->src.data[ctx->src.offset - 1]); + } + + curWord = (curWord << 1) + UTIL_GET_FLAG(curByte, STUNTS_HUFF_PREFIX_MSB); + curByte <<= 1; + readWidth--; + UTIL_VERBOSE_HUFF("level = %d", level); + + if (level >= STUNTS_HUFF_LEVELS_MAX) { + UTIL_ERR("Offset table out of bounds (%d >= %d)\n", level, STUNTS_HUFF_LEVELS_MAX); + return STPK_RET_ERR; + } + + if (curWord < totalCodes[level]) { + curWord += codeOffsets[level]; + + if (curWord > 0xFF) { + UTIL_ERR("Alphabet index out of bounds (%04X > %04X)\n", curWord, STUNTS_HUFF_ALPH_LEN); + return STPK_RET_ERR; + } + + if (delta) { + UTIL_VERBOSE_HUFF("Using symbol %02X as delta to previous output %02X", alphabet[curWord], curOut); + curOut += alphabet[curWord]; + } + else { + curOut = alphabet[curWord]; + } + + ctx->dst.data[ctx->dst.offset++] = curOut; + UTIL_VERBOSE_HUFF("Wrote %02X using offset table", curOut); + + break; + } + } + + // Read another byte since the processed code was wider than a byte. + curWord = (curByte << readWidth) | stpk_getHuffByte(ctx); + curWidth = 8 - readWidth; + readWidth = 8; + UTIL_VERBOSE_HUFF("Read %02X", ctx->src.data[ctx->src.offset - 1]); + } + // Code is 8 bits wide or less, do direct prefix lookup. + else { + if (delta) { + UTIL_VERBOSE_HUFF("Using symbol %02X as delta to previous output %02X", symbols[code], curOut); + curOut += symbols[code]; + } + else { + curOut = symbols[code]; + } + ctx->dst.data[ctx->dst.offset++] = curOut; + UTIL_VERBOSE_HUFF("Wrote %02X from prefix table", curOut); + + if (readWidth < curWidth) { + curWord <<= readWidth; + UTIL_VERBOSE_HUFF("Shifted %d bits", readWidth); + + curWidth -= readWidth; + readWidth = 8; + + curWord |= stpk_getHuffByte(ctx); + UTIL_VERBOSE_HUFF("Read %02X", ctx->src.data[ctx->src.offset - 1]); + } + } + + curWord <<= curWidth; + readWidth -= curWidth; + + if ((ctx->src.offset - 1) > ctx->src.len && ctx->dst.offset < ctx->dst.len) { + UTIL_ERR("Reached unexpected end of source buffer while decoding Huffman codes\n"); + return STPK_RET_ERR; + } + + // Progress bar. + if (ctx->verbosity && (ctx->verbosity < 3) && ((ctx->dst.offset * 100) / ctx->dst.len) >= (progress * 10)) { + ctx->logCallback(STPK_LOG_INFO, "%4d%%", progress++ * 10); + } + } + + UTIL_NOVERBOSE("]\n"); + UTIL_VERBOSE1("\n"); + + if (ctx->src.offset < ctx->src.len) { + UTIL_WARN("Huffman decoding finished with unprocessed data left in source buffer (%d bytes left)\n", ctx->src.len - ctx->src.offset); + return STPK_RET_ERR_DATA_LEFT; + } + + return STPK_RET_OK; +} + +// Read a byte from the Huffman code bit stream, reverse bits if game version is Brøderbund Stunts 1.0. +inline unsigned char stpk_getHuffByte(stpk_Context *ctx) +{ + // https://graphics.stanford.edu/~seander/bithacks.html#BitReverseTable + static const unsigned char reverseBits[] = { +# define R2(n) (n), (n + 2 * 64), (n + 1 * 64), (n + 3 * 64) +# define R4(n) R2(n), R2(n + 2 * 16), R2(n + 1 * 16), R2(n + 3 * 16) +# define R6(n) R4(n), R4(n + 2 * 4), R4(n + 1 * 4), R4(n + 3 * 4) + R6(0), R6(2), R6(1), R6(3) + }; + + unsigned char byte = ctx->src.data[ctx->src.offset++]; + if (ctx->format.stunts.version == STPK_FMT_STUNTS_VER_1_0) { + byte = reverseBits[byte]; + } + return byte; +} diff --git a/src/lib/stunts_huff.h b/src/lib/stunts_huff.h new file mode 100644 index 0000000..32a4c36 --- /dev/null +++ b/src/lib/stunts_huff.h @@ -0,0 +1,40 @@ +/* + * stunpack - Stunts/4D [Sports] Driving game resource unpacker + * Copyright (C) 2008-2024 Daniel Stien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef STPK_LIB_STUNTS_HUFF_H +#define STPK_LIB_STUNTS_HUFF_H + +#include + +#define STUNTS_HUFF_LEVELS_MASK 0x7F +#define STUNTS_HUFF_LEVELS_MAX 0x10 +#define STUNTS_HUFF_LEVELS_DELTA 0x80 + +#define STUNTS_HUFF_ALPH_LEN 0x100 +#define STUNTS_HUFF_PREFIX_WIDTH 0x08 +#define STUNTS_HUFF_PREFIX_LEN (1 << STUNTS_HUFF_PREFIX_WIDTH) +#define STUNTS_HUFF_PREFIX_MSB (1 << (STUNTS_HUFF_PREFIX_WIDTH - 1)) +#define STUNTS_HUFF_WIDTH_ESC 0x40 + +unsigned int stunts_huff_decompress(stpk_Context *ctx); +unsigned int stunts_huff_genOffsets(stpk_Context *ctx, unsigned int levels, const unsigned char *leafNodesPerLevel, short *codeOffsets, unsigned short *totalCodes); +void stunts_huff_genPrefix(stpk_Context *ctx, unsigned int levels, const unsigned char *leafNodesPerLevel, const unsigned char *alphabet, unsigned char *symbols, unsigned char *widths); +unsigned int stunts_huff_decode(stpk_Context *ctx, const unsigned char *alphabet, const unsigned char *symbols, const unsigned char *widths, const short *codeOffsets, const unsigned short *totalCodes, int delta); + +#endif diff --git a/src/lib/stunts_rle.c b/src/lib/stunts_rle.c new file mode 100644 index 0000000..86b86ab --- /dev/null +++ b/src/lib/stunts_rle.c @@ -0,0 +1,248 @@ +/* + * stunpack - Stunts/4D [Sports] Driving game resource unpacker + * Copyright (C) 2008-2024 Daniel Stien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "stunts.h" +#include "util.h" + +#include "stunts_rle.h" + +inline unsigned int stunts_rle_repeatByte(stpk_Context *ctx, unsigned char cur, unsigned int rep); + +// Decompress run-length encoded sub-file. +unsigned int stunts_rle_decompress(stpk_Context *ctx) +{ + unsigned int srcLen, dstLen, i; + unsigned char unk, escLen, esc[STUNTS_RLE_ESCLEN_MAX], escLookup[STUNTS_RLE_ESCLOOKUP_LEN]; + + stunts_getLength(&ctx->src, &srcLen); + UTIL_VERBOSE1(" %-10s %d\n", "srcLen", srcLen); + + unk = ctx->src.data[ctx->src.offset++]; + UTIL_VERBOSE1(" %-10s %02X\n", "unk", unk); + + if (unk) { + UTIL_WARN("Unknown RLE header field (unk) is %02X, expected 0\n", unk); + } + + escLen = ctx->src.data[ctx->src.offset++]; + UTIL_VERBOSE1(" %-10s %d (no sequences = %d)\n\n", "escLen", escLen & STUNTS_RLE_ESCLEN_MASK, UTIL_GET_FLAG(escLen, STUNTS_RLE_ESCLEN_NOSEQ)); + + if ((escLen & STUNTS_RLE_ESCLEN_MASK) > STUNTS_RLE_ESCLEN_MAX) { + UTIL_ERR("escLen & STUNTS_RLE_ESCLEN_MASK greater than max length %02X, got %02X\n", STUNTS_RLE_ESCLEN_MAX, escLen & STUNTS_RLE_ESCLEN_MASK); + return 1; + } + + // Read escape codes. + for (i = 0; i < (escLen & STUNTS_RLE_ESCLEN_MASK); i++) esc[i] = ctx->src.data[ctx->src.offset++]; + UTIL_VERBOSE_ARR(esc, escLen & STUNTS_RLE_ESCLEN_MASK, "esc"); + + if (ctx->src.offset > ctx->src.len) { + UTIL_ERR("Reached end of source buffer while parsing run-length header\n"); + return 1; + } + + // Generate escape code lookup table where the index is the escape code + // and the value is the escape code's positional property. + for (i = 0; i < STUNTS_RLE_ESCLOOKUP_LEN; i++) escLookup[i] = 0; + for (i = 0; i < (escLen & STUNTS_RLE_ESCLEN_MASK); i++) escLookup[esc[i]] = i + 1; + UTIL_VERBOSE_ARR(escLookup, STUNTS_RLE_ESCLOOKUP_LEN, "escLookup"); + + UTIL_NOVERBOSE("Run-length "); + + // Decode sequence run as a separate pass. + if (!UTIL_GET_FLAG(escLen, STUNTS_RLE_ESCLEN_NOSEQ)) { + if (stunts_rle_decodeSeq(ctx, esc[STUNTS_RLE_ESCSEQ_POS])) { + return 1; + } + + srcLen = ctx->dst.offset; + dstLen = ctx->dst.len; + util_dst2src(ctx); + ctx->src.len = srcLen; + ctx->dst.len = dstLen; + + if (util_allocDst(ctx)) { + return 1; + } + } + + return stunts_rle_decodeOne(ctx, escLookup); +} + +// Decode sequence runs. +unsigned int stunts_rle_decodeSeq(stpk_Context *ctx, unsigned char esc) +{ + unsigned char cur; + unsigned int progress = 0, seqOffset, rep, i; + + UTIL_NOVERBOSE("["); + + UTIL_VERBOSE1("Decoding sequence runs... "); + UTIL_VERBOSE2("\n\nsrcOff dstOff rep seq\n"); + UTIL_VERBOSE2("~~~~~~ ~~~~~~ ~~~ ~~~~~~~~\n"); + + // We do not know the destination length for this pass, dst->len covers both RLE passes. + while (ctx->src.offset < ctx->src.len) { + cur = ctx->src.data[ctx->src.offset++]; + + if (cur == esc) { + seqOffset = ctx->src.offset; + + while ((cur = ctx->src.data[ctx->src.offset++]) != esc) { + if (ctx->src.offset >= ctx->src.len) { + UTIL_ERR("Reached end of source buffer before finding sequence end escape code %02X\n", esc); + return 1; + } + + ctx->dst.data[ctx->dst.offset++] = cur; + } + + rep = ctx->src.data[ctx->src.offset++] - 1; // Already wrote sequence once. + UTIL_VERBOSE2("%6d %6d %02X %2.*X\n", ctx->src.offset, ctx->dst.offset, rep + 1, ctx->src.offset - seqOffset - 2, ctx->src.data[seqOffset]); + + while (rep--) { + for (i = 0; i < (ctx->src.offset - seqOffset - 2); i++) { + if (ctx->dst.offset >= ctx->dst.len) { + UTIL_ERR("Reached end of temporary buffer while writing repeated sequence\n"); + return 1; + } + + ctx->dst.data[ctx->dst.offset++] = ctx->src.data[seqOffset + i]; + } + } + + } + else { + ctx->dst.data[ctx->dst.offset++] = cur; + UTIL_VERBOSE2("%6d %6d %02X\n", ctx->src.offset, ctx->dst.offset, cur); + + if (ctx->dst.offset > ctx->dst.len) { + UTIL_ERR("Reached end of temporary buffer while writing non-RLE byte\n"); + return 1; + } + } + + // Progress bar. + if (ctx->verbosity && (ctx->verbosity < 3) && ((ctx->src.offset * 100) / ctx->src.len) >= (progress * 25)) { + ctx->logCallback(STPK_LOG_INFO, "%4d%%", progress++ * 25); + } + } + + UTIL_VERBOSE1("\n"); + UTIL_NOVERBOSE("] "); + + return 0; +} + +// Decode single-byte runs. +unsigned int stunts_rle_decodeOne(stpk_Context *ctx, const unsigned char *escLookup) +{ + unsigned char cur; + unsigned int progress = 0, rep; + + UTIL_NOVERBOSE("["); + + UTIL_VERBOSE1("Decoding single-byte runs... "); + + UTIL_VERBOSE2("\n\nsrcOff dstOff rep cur\n"); + UTIL_VERBOSE2("~~~~~~ ~~~~~~ ~~~~~ ~~~\n"); + + while (ctx->dst.offset < ctx->dst.len) { + cur = ctx->src.data[ctx->src.offset++]; + + if (ctx->src.offset > ctx->src.len) { + UTIL_ERR("Reached unexpected end of source buffer while decoding single-byte runs\n"); + return 1; + } + + if (escLookup[cur]) { + switch (escLookup[cur]) { + // Type 1: One-byte counter for repetitions + case 1: + rep = ctx->src.data[ctx->src.offset]; + cur = ctx->src.data[ctx->src.offset + 1]; + ctx->src.offset += 2; + + if (stunts_rle_repeatByte(ctx, cur, rep)) { + return 1; + } + + break; + + // Type 2: Used for sequences. Serves no purpose here, but + // would be handled by the default case if it were to occur. + + // Type 3: Two-byte counter for repetitions + case 3: + rep = ctx->src.data[ctx->src.offset] | ctx->src.data[ctx->src.offset + 1] << 8; + cur = ctx->src.data[ctx->src.offset + 2]; + ctx->src.offset += 3; + + if (stunts_rle_repeatByte(ctx, cur, rep)) { + return 1; + } + + break; + + // Type n: n repetitions + default: + rep = escLookup[cur] - 1; + cur = ctx->src.data[ctx->src.offset++]; + + if (stunts_rle_repeatByte(ctx, cur, rep)) { + return 1; + } + } + } + else { + ctx->dst.data[ctx->dst.offset++] = cur; + UTIL_VERBOSE2("%6d %6d %02X\n", ctx->src.offset, ctx->dst.offset, cur); + } + + // Progress bar. + if (ctx->verbosity && (ctx->verbosity < 3) && ((ctx->src.offset * 100) / ctx->src.len) >= (progress * 25)) { + ctx->logCallback(STPK_LOG_INFO, "%4d%%", progress++ * 25); + } + } + + UTIL_VERBOSE1("\n"); + UTIL_NOVERBOSE("]\n"); + + if (ctx->src.offset < ctx->src.len) { + UTIL_WARN("RLE decoding finished with unprocessed data left in source buffer (%d bytes left)\n", ctx->src.len - ctx->src.offset); + } + + return 0; +} + +inline unsigned int stunts_rle_repeatByte(stpk_Context *ctx, unsigned char cur, unsigned int rep) +{ + UTIL_VERBOSE2("%6d %6d %02X %02X\n", ctx->src.offset, ctx->dst.offset, rep, cur); + + while (rep--) { + if (ctx->dst.offset >= ctx->dst.len) { + UTIL_ERR("Reached end of temporary buffer while writing byte run\n"); + return 1; + } + + ctx->dst.data[ctx->dst.offset++] = cur; + } + + return 0; +} diff --git a/src/lib/stunts_rle.h b/src/lib/stunts_rle.h new file mode 100644 index 0000000..b040458 --- /dev/null +++ b/src/lib/stunts_rle.h @@ -0,0 +1,35 @@ +/* + * stunpack - Stunts/4D [Sports] Driving game resource unpacker + * Copyright (C) 2008-2024 Daniel Stien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef STPK_LIB_STUNTS_RLE_H +#define STPK_LIB_STUNTS_RLE_H + +#include + +#define STUNTS_RLE_ESCLEN_MASK 0x7F +#define STUNTS_RLE_ESCLEN_MAX 0x0A +#define STUNTS_RLE_ESCLEN_NOSEQ 0x80 +#define STUNTS_RLE_ESCLOOKUP_LEN 0x100 +#define STUNTS_RLE_ESCSEQ_POS 0x01 + +unsigned int stunts_rle_decompress(stpk_Context *ctx); +unsigned int stunts_rle_decodeSeq(stpk_Context *ctx, unsigned char esc); +unsigned int stunts_rle_decodeOne(stpk_Context *ctx, const unsigned char *escLookup); + +#endif diff --git a/src/lib/util.c b/src/lib/util.c new file mode 100644 index 0000000..4b4f93f --- /dev/null +++ b/src/lib/util.c @@ -0,0 +1,73 @@ +/* + * stunpack - Stunts/4D [Sports] Driving game resource unpacker + * Copyright (C) 2008-2024 Daniel Stien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include "util.h" + +int util_allocDst(stpk_Context *ctx) +{ + if ((ctx->dst.data = (unsigned char*)ctx->allocCallback(sizeof(unsigned char) * ctx->dst.len)) == NULL) { + UTIL_ERR("Error allocating memory for destination buffer. (%s)\n", strerror(errno)); + return 1; + } + return 0; +} + +// Free old source buffer and set destination as new source for next pass. +void util_dst2src(stpk_Context *ctx) +{ + if (ctx->src.data != NULL) { + ctx->deallocCallback(ctx->src.data); + } + ctx->src.data = ctx->dst.data; + ctx->src.len = ctx->dst.len; + ctx->dst.data = NULL; + ctx->src.offset = ctx->dst.offset = 0; +} + + // Write bit values as string to static char[16] buffer. Used in verbose output. +char *util_stringBits16(unsigned short val) +{ + static char buf[16 + 1]; + int i; + for (i = 0; i < 16; i++) buf[(16 - 1) - i] = '0' + UTIL_GET_FLAG(val, (1 << i)); + buf[i] = 0; + + return buf; +} + +// Print formatted array. Used in verbose output. +void util_printArray(const stpk_Context *ctx, const unsigned char *arr, unsigned int len, const char *name) +{ + unsigned int i = 0; + + ctx->logCallback(STPK_LOG_INFO, " %s[%02X]\n", name, len); + ctx->logCallback(STPK_LOG_INFO, " 0 1 2 3 4 5 6 7 8 9 A B C D E F\n"); + + while (i < len) { + if ((i % 0x10) == 0) ctx->logCallback(STPK_LOG_INFO, " %2X", i / 0x10); + ctx->logCallback(STPK_LOG_INFO, " %02X", arr[i++]); + if ((i % 0x10) == 0) ctx->logCallback(STPK_LOG_INFO, "\n"); + } + + if ((i % 0x10) != 0) ctx->logCallback(STPK_LOG_INFO, "\n"); + ctx->logCallback(STPK_LOG_INFO, "\n"); +} diff --git a/src/lib/util.h b/src/lib/util.h new file mode 100644 index 0000000..76cc3cc --- /dev/null +++ b/src/lib/util.h @@ -0,0 +1,46 @@ +/* + * stunpack - Stunts/4D [Sports] Driving game resource unpacker + * Copyright (C) 2008-2024 Daniel Stien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef STPK_LIB_UTIL_H +#define STPK_LIB_UTIL_H + +#include + +#define UTIL_LOG(show, type, msg, ...) if (show && ctx->logCallback) ctx->logCallback((type), (msg), ## __VA_ARGS__) +#define UTIL_MSG(msg, ...) UTIL_LOG(ctx->verbosity, STPK_LOG_INFO, (msg), ## __VA_ARGS__) +#define UTIL_ERR(msg, ...) UTIL_LOG(ctx->verbosity, STPK_LOG_ERR, (msg), ## __VA_ARGS__) +#define UTIL_WARN(msg, ...) UTIL_LOG(ctx->verbosity, STPK_LOG_WARN, (msg), ## __VA_ARGS__) +#define UTIL_NOVERBOSE(msg, ...) UTIL_LOG(ctx->verbosity == 1, STPK_LOG_INFO, (msg), ## __VA_ARGS__) +#define UTIL_VERBOSE1(msg, ...) UTIL_LOG(ctx->verbosity > 1, STPK_LOG_INFO, (msg), ## __VA_ARGS__) +#define UTIL_VERBOSE2(msg, ...) UTIL_LOG(ctx->verbosity > 2, STPK_LOG_INFO, (msg), ## __VA_ARGS__) +#define UTIL_VERBOSE_ARR(arr, len, name) if (ctx->verbosity > 1) util_printArray(ctx, arr, len, name) +#define UTIL_VERBOSE_HUFF(msg, ...) UTIL_VERBOSE2("%6d %6d %2d %2d %04X %s %02X -> " msg "\n", \ + ctx->src.offset, ctx->dst.offset, readWidth, curWidth, curWord, \ + util_stringBits16(curWord), code, ## __VA_ARGS__) + +#define UTIL_GET_FLAG(data, mask) ((data & mask) == mask) +#define UTIL_MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) + +int util_allocDst(stpk_Context *ctx); +void util_dst2src(stpk_Context *ctx); + +char *util_stringBits16(unsigned short val); +void util_printArray(const stpk_Context *ctx, const unsigned char *arr, unsigned int len, const char *name); + +#endif diff --git a/src/main.c b/src/main.c index 863fa67..4bf5c4f 100644 --- a/src/main.c +++ b/src/main.c @@ -63,13 +63,13 @@ int main(int argc, char **argv) while ((opt = getopt(argc, argv, "g:p:hqv")) != -1) { switch (opt) { case 'g': - if (strcasecmp(optarg, stpk_versionStr(STPK_FMT_STUNTS_VER_AUTO)) == 0) { + if (strcasecmp(optarg, stpk_fmtStuntsVerStr(STPK_FMT_STUNTS_VER_AUTO)) == 0) { format.stunts.version = STPK_FMT_STUNTS_VER_AUTO; } - else if (strcasecmp(optarg, stpk_versionStr(STPK_FMT_STUNTS_VER_1_0)) == 0) { + else if (strcasecmp(optarg, stpk_fmtStuntsVerStr(STPK_FMT_STUNTS_VER_1_0)) == 0) { format.stunts.version = STPK_FMT_STUNTS_VER_1_0; } - else if (strcasecmp(optarg, stpk_versionStr(STPK_FMT_STUNTS_VER_1_1)) == 0) { + else if (strcasecmp(optarg, stpk_fmtStuntsVerStr(STPK_FMT_STUNTS_VER_1_1)) == 0) { format.stunts.version = STPK_FMT_STUNTS_VER_1_1; } else { @@ -143,9 +143,9 @@ void printHelp(char *progName) printf(USAGE, progName); printf(" -g VER game version: \"%s\" (default), \"%s\", \"%s\"\n", - stpk_versionStr(STPK_FMT_STUNTS_VER_AUTO), - stpk_versionStr(STPK_FMT_STUNTS_VER_1_0), - stpk_versionStr(STPK_FMT_STUNTS_VER_1_1) + stpk_fmtStuntsVerStr(STPK_FMT_STUNTS_VER_AUTO), + stpk_fmtStuntsVerStr(STPK_FMT_STUNTS_VER_1_0), + stpk_fmtStuntsVerStr(STPK_FMT_STUNTS_VER_1_1) ); printf(" -p NUM limit to NUM decompression passes\n"); printf(" -v verbose output\n"); @@ -203,10 +203,10 @@ int decompress(char *srcFileName, char *dstFileName, stpk_Format format, int ver goto closeSrcFile; } - if (ctx.src.len > STPK_MAX_SIZE) { - ERR("Source file \"%s\" size (%d) exceeds max size (%d).\n", srcFileName, ctx.src.len, STPK_MAX_SIZE); - goto closeSrcFile; - } + //if (ctx.src.len > STPK_MAX_SIZE) { + // ERR("Source file \"%s\" size (%d) exceeds max size (%d).\n", srcFileName, ctx.src.len, STPK_MAX_SIZE); + // goto closeSrcFile; + //} if (fseek(srcFile, 0, SEEK_SET) != 0) { ERR("Error seeking for start position in source file \"%s\". (%s)\n", srcFileName, strerror(errno));