From 2997ce511085c99dc02d30fa84c3948916dc1186 Mon Sep 17 00:00:00 2001 From: Daniel Stien Date: Wed, 10 Jan 2024 02:25:16 +1100 Subject: [PATCH] Implement RPck decompression. --- src/lib/Makefile | 2 +- src/lib/rpck.c | 107 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/rpck.h | 56 ++++++++++++++++++++++++ src/lib/stunpack.c | 9 ++-- src/lib/util.c | 11 +++++ src/lib/util.h | 1 + 6 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 src/lib/rpck.c create mode 100644 src/lib/rpck.h diff --git a/src/lib/Makefile b/src/lib/Makefile index c043bd0..544a4ce 100644 --- a/src/lib/Makefile +++ b/src/lib/Makefile @@ -1,5 +1,5 @@ BIN = libstunpack$(LIBSUFFIX) -SRCS = stunpack.c stunts.c stunts_huff.c stunts_rle.c util.c +SRCS = rpck.c 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/rpck.c b/src/lib/rpck.c new file mode 100644 index 0000000..65e25c7 --- /dev/null +++ b/src/lib/rpck.c @@ -0,0 +1,107 @@ +/* + * 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 "rpck.h" + +#include "util.h" + +int rpck_isValid(stpk_Context *ctx) +{ + if (ctx->src.len < RPCK_SIZE_MIN) { + return 0; + } + + uint32_t finalLength = rpck_peekLength(ctx->src.data, 4); + uint32_t savedLength = rpck_peekLength(ctx->src.data, 8); + + return rpck_checkMagic(ctx) + && (finalLength - savedLength + RPCK_SIZE_MIN) == ctx->src.len; +} + +unsigned int rpck_decompress(stpk_Context *ctx) +{ + if (ctx->src.len < RPCK_SIZE_MIN) { + UTIL_ERR("Unexpected EOF while reading RPck header.\n"); + return 0; + } + if (!rpck_checkMagic(ctx)) { + unsigned char magic[5]; + UTIL_ERR("Invalid magic bytes. Expected \"RPck\", got \"%s\"\n", magic, util_stringCharsSafe(ctx->src.data, magic, sizeof(magic))); + return 0; + } + ctx->src.offset += 4; + UTIL_VERBOSE1("Format: RPck\n"); + + uint32_t finalLength = rpck_readLength(&ctx->src); + UTIL_VERBOSE1("Final length %d\n", finalLength); + UTIL_VERBOSE1("Source length %d\n", ctx->src.len); + + uint32_t savedLength = rpck_readLength(&ctx->src); + UTIL_VERBOSE1("Saved length %d\n", savedLength); + UTIL_VERBOSE1("Ratio %.2f\n", (float)finalLength / ctx->src.len); + + ctx->dst.len = finalLength; + if (util_allocDst(ctx)) { + return 1; + } + + while (ctx->src.offset < ctx->src.len) { + signed char ctrl = ctx->src.data[ctx->src.offset++]; + UTIL_VERBOSE2("Offset %04X Read ctrl %d ", ctx->src.offset - 1, ctrl); + if (ctrl < 0) { + if (ctx->src.offset - ctrl > ctx->src.len) { + UTIL_ERR("Attempted to read %d byte(s) past end of source buffer at offset %04X", + (ctx->src.offset - ctrl) - ctx->src.len, + ctx->src.offset); + return 1; + } + if (ctx->dst.offset - ctrl > ctx->dst.len) { + UTIL_ERR("Attempted to write %d byte(s) past end of destination buffer at offset %04X", + (ctx->dst.offset - ctrl) - ctx->dst.len, + ctx->dst.offset); + return 1; + } + for (; ctrl; ctrl++) { + UTIL_VERBOSE2(" %02X", ctx->src.data[ctx->src.offset]); + ctx->dst.data[ctx->dst.offset++] = ctx->src.data[ctx->src.offset++]; + } + UTIL_VERBOSE2("\n"); + } + else { + if (ctx->src.offset >= ctx->src.len) { + UTIL_ERR("Attempted to read 1 byte past end of source buffer at offset %04X", + ctx->src.offset); + return 1; + } + unsigned char data = ctx->src.data[ctx->src.offset++]; + if (ctx->dst.offset + ctrl + 1 > ctx->dst.len) { + UTIL_ERR("Attempted to write %d byte(s) past end of destination buffer at offset %04X", + (ctx->dst.offset + ctrl + 1) - ctx->dst.len, + ctx->dst.offset); + return 1; + } + UTIL_VERBOSE2(" x %02X\n", data); + for (int i = ctrl + 1; i; i--) { + ctx->dst.data[ctx->dst.offset++] = data; + } + } + } + + return 0; +} diff --git a/src/lib/rpck.h b/src/lib/rpck.h new file mode 100644 index 0000000..986dd6a --- /dev/null +++ b/src/lib/rpck.h @@ -0,0 +1,56 @@ +/* + * 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_RPCK_H +#define STPK_LIB_RPCK_H + +#include +#include + +#define RPCK_SIZE_MIN 14 + +int rpck_isValid(stpk_Context *ctx); +unsigned int rpck_decompress(stpk_Context *ctx); + +inline int rpck_checkMagic(stpk_Context *ctx) +{ + return ctx->src.data[0] == 'R' + && ctx->src.data[1] == 'P' + && ctx->src.data[2] == 'c' + && ctx->src.data[3] == 'k'; +} + +// Peek at 32-bit big endian data length. +inline uint32_t rpck_peekLength(unsigned char *data, unsigned offset) +{ + return data[offset + 0] << 24 + | data[offset + 1] << 16 + | data[offset + 2] << 8 + | data[offset + 3]; +} + +// Read 32-bit big endian data length and advance buffer offset. +inline uint32_t rpck_readLength(stpk_Buffer *buf) +{ + uint32_t len = rpck_peekLength(buf->data, buf->offset); + buf->offset += 4; + return len; +} + +#endif diff --git a/src/lib/stunpack.c b/src/lib/stunpack.c index a0cb160..32f249c 100644 --- a/src/lib/stunpack.c +++ b/src/lib/stunpack.c @@ -19,6 +19,7 @@ #include +#include "rpck.h" #include "stunts.h" stpk_Context stpk_init(stpk_Format format, int verbosity, stpk_LogCallback logCallback, stpk_AllocCallback allocCallback, stpk_DeallocCallback deallocCallback) @@ -63,6 +64,8 @@ void stpk_deinit(stpk_Context *ctx) unsigned int stpk_decompress(stpk_Context *ctx) { switch (stpk_getFmtType(ctx)) { + case STPK_FMT_RPCK: + return rpck_decompress(ctx); case STPK_FMT_STUNTS: return stunts_decompress(ctx); default: @@ -74,11 +77,7 @@ unsigned int stpk_decompress(stpk_Context *ctx) stpk_FmtType stpk_getFmtType(stpk_Context *ctx) { if (ctx->format.type == STPK_FMT_AUTO) { - // TODO: Check other header details, cleanup, move to rpck.c. - if (ctx->src.data[0] == 'R' - && ctx->src.data[1] == 'P' - && ctx->src.data[2] == 'c' - && ctx->src.data[3] == 'k') { + if (rpck_isValid(ctx)) { ctx->format.type = STPK_FMT_RPCK; } // TODO: Check other header details, cleanup, move to barchard.c. diff --git a/src/lib/util.c b/src/lib/util.c index 4b4f93f..8731855 100644 --- a/src/lib/util.c +++ b/src/lib/util.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include #include #include @@ -54,6 +55,16 @@ char *util_stringBits16(unsigned short val) return buf; } +// Copy n-1 bytes from source to fixed destination buffer as printable ASCII characters. +unsigned char *util_stringCharsSafe(const unsigned char *src, unsigned char *dst, unsigned int len) +{ + for (unsigned i = 0; i < len - 1; i++) { + dst[i] = isprint(src[i]) ? src[i] : '.'; + } + dst[len] = 0; + return dst; +} + // Print formatted array. Used in verbose output. void util_printArray(const stpk_Context *ctx, const unsigned char *arr, unsigned int len, const char *name) { diff --git a/src/lib/util.h b/src/lib/util.h index 1954db2..b76a6e5 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -42,6 +42,7 @@ int util_allocDst(stpk_Context *ctx); void util_dst2src(stpk_Context *ctx); char *util_stringBits16(unsigned short val); +unsigned char *util_stringCharsSafe(const unsigned char *src, unsigned char *dst, unsigned int len); void util_printArray(const stpk_Context *ctx, const unsigned char *arr, unsigned int len, const char *name); #endif