Skip to content

Commit

Permalink
Support multiple compression formats in context type.
Browse files Browse the repository at this point in the history
  • Loading branch information
dstien committed Jan 8, 2024
1 parent e7cee1f commit 14d2638
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 39 deletions.
61 changes: 54 additions & 7 deletions include/stunpack.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,54 @@
#define STPK_HUFF_PREFIX_MSB (1 << (STPK_HUFF_PREFIX_WIDTH - 1))
#define STPK_HUFF_WIDTH_ESC 0x40

typedef enum { STPK_VER_AUTO, STPK_VER_STUNTS10, STPK_VER_STUNTS11 } stpk_Version;
typedef enum { STPK_LOG_INFO, STPK_LOG_WARN, STPK_LOG_ERR } stpk_LogType;
typedef enum {
// Automatic format detection when decompressing.
STPK_FMT_AUTO,
// Custom compression format used by the PC versions of Stunts/4-D Sports Driving.
STPK_FMT_STUNTS,
// EA compression library by Frank Barchard used by 4-D Sports Driving for Amiga and 4-D Driving for PC98.
STPK_FMT_BARCHARD,
// Amiga RPck archiver format used for 3-d shapes in 4-D Sports Driving for Amiga.
STPK_FMT_RPCK
} stpk_FmtType;

typedef enum {
STPK_FMT_STUNTS_VER_AUTO,
STPK_FMT_STUNTS_VER_1_0,
STPK_FMT_STUNTS_VER_1_1
} stpk_FmtStuntsVer;

//typedef enum {
// STPK_FMT_STUNTS_RLE,
// STPK_FMT_STUNTS_HUFF
//} stpk_FmtStuntsMethod;

typedef struct {
stpk_FmtStuntsVer version;
int maxPasses;
} stpk_FmtStunts;

//typedef struct {
//} stpk_FmtBarchard;

//typedef struct {
//} stpk_FmtRpck;

typedef struct {
stpk_FmtType type;
union {
stpk_FmtStunts stunts;
//stpk_FmtBarchard barchard;
//stpk_FmtRpck rpck;
};
} stpk_Format;

typedef enum {
STPK_LOG_INFO,
STPK_LOG_WARN,
STPK_LOG_ERR
} stpk_LogType;

typedef void (*stpk_LogCallback)(stpk_LogType type, const char *msg, ...);
typedef void* (*stpk_AllocCallback)(size_t size);
typedef void (*stpk_DeallocCallback)(void *ptr);
Expand All @@ -66,18 +112,19 @@ typedef struct {
typedef struct {
stpk_Buffer src;
stpk_Buffer dst;
stpk_Version version;
int maxPasses;
stpk_Format format;
int verbosity;
stpk_LogCallback logCallback;
stpk_AllocCallback allocCallback;
stpk_DeallocCallback deallocCallback;
} stpk_Context;

const char *stpk_versionStr(stpk_Version version);
stpk_Context stpk_init(stpk_Version version, int maxPasses, int verbosity, stpk_LogCallback logCallback, stpk_AllocCallback allocCallback, stpk_DeallocCallback deallocCallback);
stpk_Context stpk_init(stpk_Format format, int verbosity, stpk_LogCallback logCallback, stpk_AllocCallback allocCallback, stpk_DeallocCallback deallocCallback);
void stpk_deinit(stpk_Context *ctx);
unsigned int stpk_decomp(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);
Expand Down
29 changes: 14 additions & 15 deletions src/lib/stunpack.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,21 @@ 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_Version version)
const char *stpk_versionStr(stpk_FmtStuntsVer version)
{
switch (version) {
case STPK_VER_AUTO:
case STPK_FMT_STUNTS_VER_AUTO:
return "auto";
case STPK_VER_STUNTS10:
case STPK_FMT_STUNTS_VER_1_0:
return "stunts1.0";
case STPK_VER_STUNTS11:
case STPK_FMT_STUNTS_VER_1_1:
return "stunts1.1";
default:
return "Unknown";
}
}

stpk_Context stpk_init(stpk_Version version, int maxPasses, int verbosity, stpk_LogCallback logCallback, stpk_AllocCallback allocCallback, stpk_DeallocCallback deallocCallback)
stpk_Context stpk_init(stpk_Format format, int verbosity, stpk_LogCallback logCallback, stpk_AllocCallback allocCallback, stpk_DeallocCallback deallocCallback)
{
stpk_Buffer empty = {
.data = NULL,
Expand All @@ -71,8 +71,7 @@ stpk_Context stpk_init(stpk_Version version, int maxPasses, int verbosity, stpk_
stpk_Context ctx;
ctx.src = empty;
ctx.dst = empty;
ctx.version = version;
ctx.maxPasses = maxPasses;
ctx.format = format;
ctx.verbosity = verbosity;
ctx.logCallback = logCallback;
ctx.allocCallback = allocCallback;
Expand Down Expand Up @@ -125,12 +124,12 @@ int inline stpk_isRle(stpk_Buffer *buf)
}

// Decompress sub-files in source buffer.
unsigned int stpk_decomp(stpk_Context *ctx)
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->version));
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)) {
Expand Down Expand Up @@ -175,7 +174,7 @@ unsigned int stpk_decomp(stpk_Context *ctx)
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->version == STPK_VER_AUTO
if (ctx->format.stunts.version == STPK_FMT_STUNTS_VER_AUTO
&& (
// Decompression failed.
retval == STPK_RET_ERR
Expand All @@ -186,12 +185,12 @@ unsigned int stpk_decomp(stpk_Context *ctx)
)
) {
STPK_WARN("Huffman decompression with Stunts 1.1 bit stream format failed, retrying with Stunts 1.0 format.\n");
ctx->version = STPK_VER_STUNTS10;
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->version = STPK_VER_AUTO;
ctx->format.stunts.version = STPK_FMT_STUNTS_VER_AUTO;
}

// Data left must be checked for BB Stunts 1.0 bit stream detection
Expand All @@ -210,8 +209,8 @@ unsigned int stpk_decomp(stpk_Context *ctx)
return retval;
}

if (i + 1 == ctx->maxPasses && passes != ctx->maxPasses) {
STPK_MSG("Parsing limited to %d decompression pass(es), aborting.\n", ctx->maxPasses);
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;
}

Expand Down Expand Up @@ -677,7 +676,7 @@ inline unsigned char stpk_getHuffByte(stpk_Context *ctx)
};

unsigned char byte = ctx->src.data[ctx->src.offset++];
if (ctx->version == STPK_VER_STUNTS10) {
if (ctx->format.stunts.version == STPK_FMT_STUNTS_VER_1_0) {
byte = reverseBits[byte];
}
return byte;
Expand Down
40 changes: 23 additions & 17 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,39 +40,45 @@
#define VERBOSE(msg, ...) if (verbose > 1) printf(msg, ## __VA_ARGS__)

void printHelp(char *progName);
int decompress(char *srcFileName, char *dstFileName, stpk_Version version, int passes, int verbose);
int decompress(char *srcFileName, char *dstFileName, stpk_Format format, int verbose);

int main(int argc, char **argv)
{
char *srcFileName = NULL, *dstFileName = NULL;
int retval = 0, opt, passes = 0, verbose = 1, srcFileNameLen = 0;
int retval = 0, opt, verbose = 1, srcFileNameLen = 0;
#if defined(__WATCOMC__)
const int dstFileNamePostfixLen = 0;
#else
const int dstFileNamePostfixLen = 4;
#endif
stpk_Version gameVersion = STPK_VER_AUTO;
stpk_FmtStunts stunts = {
.version = STPK_FMT_STUNTS_VER_AUTO,
.maxPasses = 0
};
stpk_Format format;
format.type = STPK_FMT_STUNTS;
format.stunts = stunts;

// Parse options.
while ((opt = getopt(argc, argv, "g:p:hqv")) != -1) {
switch (opt) {
case 'g':
if (strcasecmp(optarg, stpk_versionStr(STPK_VER_AUTO)) == 0) {
gameVersion = STPK_VER_AUTO;
if (strcasecmp(optarg, stpk_versionStr(STPK_FMT_STUNTS_VER_AUTO)) == 0) {
format.stunts.version = STPK_FMT_STUNTS_VER_AUTO;
}
else if (strcasecmp(optarg, stpk_versionStr(STPK_VER_STUNTS10)) == 0) {
gameVersion = STPK_VER_STUNTS10;
else if (strcasecmp(optarg, stpk_versionStr(STPK_FMT_STUNTS_VER_1_0)) == 0) {
format.stunts.version = STPK_FMT_STUNTS_VER_1_0;
}
else if (strcasecmp(optarg, stpk_versionStr(STPK_VER_STUNTS11)) == 0) {
gameVersion = STPK_VER_STUNTS11;
else if (strcasecmp(optarg, stpk_versionStr(STPK_FMT_STUNTS_VER_1_1)) == 0) {
format.stunts.version = STPK_FMT_STUNTS_VER_1_1;
}
else {
fprintf(stderr, "Invalid game version \"%s\".\n", optarg);
return 1;
}
break;
case 'p':
passes = atoi(optarg);
format.stunts.maxPasses = atoi(optarg);
break;
case 'h':
printHelp(argv[0]);
Expand Down Expand Up @@ -121,7 +127,7 @@ int main(int argc, char **argv)
#endif
}

retval = decompress(srcFileName, dstFileName, gameVersion, passes, verbose);
retval = decompress(srcFileName, dstFileName, format, verbose);

// Clean up.
if (dstFileName != NULL && srcFileNameLen) {
Expand All @@ -137,9 +143,9 @@ void printHelp(char *progName)

printf(USAGE, progName);
printf(" -g VER game version: \"%s\" (default), \"%s\", \"%s\"\n",
stpk_versionStr(STPK_VER_AUTO),
stpk_versionStr(STPK_VER_STUNTS10),
stpk_versionStr(STPK_VER_STUNTS11)
stpk_versionStr(STPK_FMT_STUNTS_VER_AUTO),
stpk_versionStr(STPK_FMT_STUNTS_VER_1_0),
stpk_versionStr(STPK_FMT_STUNTS_VER_1_1)
);
printf(" -p NUM limit to NUM decompression passes\n");
printf(" -v verbose output\n");
Expand Down Expand Up @@ -173,12 +179,12 @@ void logCallback(stpk_LogType type, const char *msg, ...)
va_end(args);
}

int decompress(char *srcFileName, char *dstFileName, stpk_Version version, int passes, int verbose)
int decompress(char *srcFileName, char *dstFileName, stpk_Format format, int verbose)
{
unsigned int retval = 1;
FILE *srcFile, *dstFile;

stpk_Context ctx = stpk_init(version, passes, verbose, logCallback, malloc, free);
stpk_Context ctx = stpk_init(format, verbose, logCallback, malloc, free);

if ((srcFile = fopen(srcFileName, "rb")) == NULL) {
ERR("Error opening source file \"%s\" for reading. (%s)\n", srcFileName, strerror(errno));
Expand Down Expand Up @@ -217,7 +223,7 @@ int decompress(char *srcFileName, char *dstFileName, stpk_Version version, int p
goto freeBuffers;
}

retval = stpk_decomp(&ctx);
retval = stpk_decompress(&ctx);

// Flush unpacked data to file.
if (!retval) {
Expand Down

0 comments on commit 14d2638

Please sign in to comment.