From 0c1606546332423d966d537149332b4e67c340bd Mon Sep 17 00:00:00 2001 From: Kari Salminen Date: Mon, 25 Aug 2008 15:04:48 +0000 Subject: Added Cinematique archive file unpacker and script disassembler to tools. NOT polished code so caveat emptor. svn-id: r34153 --- tools/cine_tools/Makefile | 6 + tools/cine_tools/cine_decode.cpp | 864 +++++++++++++++++++++++++++++++++++++++ tools/cine_tools/cine_unpack.cpp | 323 +++++++++++++++ tools/cine_tools/cine_unpack.h | 87 ++++ tools/cine_tools/endian.h | 30 ++ tools/cine_tools/typedefs.h | 14 + 6 files changed, 1324 insertions(+) create mode 100644 tools/cine_tools/Makefile create mode 100644 tools/cine_tools/cine_decode.cpp create mode 100644 tools/cine_tools/cine_unpack.cpp create mode 100644 tools/cine_tools/cine_unpack.h create mode 100644 tools/cine_tools/endian.h create mode 100644 tools/cine_tools/typedefs.h (limited to 'tools') diff --git a/tools/cine_tools/Makefile b/tools/cine_tools/Makefile new file mode 100644 index 0000000000..430743bb1e --- /dev/null +++ b/tools/cine_tools/Makefile @@ -0,0 +1,6 @@ +# $URL$ +# $Id$ + +all: + g++ cine_decode.cpp -o cine_decode + g++ cine_unpack.cpp -o cine_unpack diff --git a/tools/cine_tools/cine_decode.cpp b/tools/cine_tools/cine_decode.cpp new file mode 100644 index 0000000000..16411d25bf --- /dev/null +++ b/tools/cine_tools/cine_decode.cpp @@ -0,0 +1,864 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This is a utility to decode Delphine's Cinematique engine's script files. + * The script files can be recognized from their extension .prc or .rel. + * Should at least work with Future Wars and Operation Stealth. + * + * Note that this isn't polished code so caveat emptor. + * + * TODO: Unify opcode namings between ScummVM's Cine engine and this utility. + */ + +#include +#include +#include +#include + +#include "typedefs.h" +#include "endian.h" + +static const uint8 *_baseScriptData, *_scriptData; + +static uint8 fetchScriptByte() { + return *_scriptData++; +} + +static uint16 fetchScriptWord() { + uint16 w = READ_BE_UINT16(_scriptData); + _scriptData += 2; + return w; +} + +static const char *fetchScriptString() { + const char *str = (const char *)_scriptData; + _scriptData += strlen(str) + 1; + return str; +} + +//typedef void (*DecodeOpcode)(); + +static const char *objectField[] = { + "", + "x", + "y", + "mask", + "frame", + "costume", + "part" +}; + +static bool decodeOpcode(int isV2) { + uint8 opcode; + unsigned int addr = _scriptData - _baseScriptData; + + opcode = fetchScriptByte(); + if (opcode == 0) { + printf("[%04X] (FF) ", addr); + } else { + --opcode; + printf("[%04X] (%02X) ", addr, opcode); + switch (opcode) { + case 0x00: { + uint8 num = fetchScriptByte(); + uint8 field = fetchScriptByte(); + int16 value = fetchScriptWord(); + if (field >= 7) return false; + printf("SET object[%d].%s, %d", num, objectField[field], value); + } + break; + case 0x01: { + uint8 num = fetchScriptByte(); + uint8 field = fetchScriptByte(); + uint8 var = fetchScriptByte(); + if (field >= 7) return false; + printf("LOAD localvars[%d], object[%d].%s", var, num, objectField[field]); + } + break; + case 0x02: { + uint8 num = fetchScriptByte(); + uint8 field = fetchScriptByte(); + int16 value = fetchScriptWord(); + if (field >= 7) return false; + printf("ADD object[%d].%s, %d", num, objectField[field], value); + } + break; + case 0x03: { + uint8 num = fetchScriptByte(); + uint8 field = fetchScriptByte(); + int16 value = fetchScriptWord(); + if (field >= 7) return false; + printf("SUB object[%d].%s, %d", num, objectField[field], value); + } + break; + case 0x06: { + uint8 num = fetchScriptByte(); + uint8 field = fetchScriptByte(); + int16 value = fetchScriptWord(); + if (field >= 7) return false; + printf("CMP object[%d].%s, %d", num, objectField[field], value); + } + break; + case 0x07: { + uint8 num = fetchScriptByte(); + int16 x = fetchScriptWord(); + int16 y = fetchScriptWord(); + int16 mask = fetchScriptWord(); + int16 frame = fetchScriptWord(); + printf("SETUP object[%d] x=%d y=%d mask=%d frame=%d", num, x, y, mask, frame); + } + break; + case 0x08: { + uint8 num = fetchScriptByte(); + int16 x = fetchScriptWord(); + int16 y = fetchScriptWord(); + int16 count = fetchScriptWord(); + int16 index = fetchScriptWord(); + printf("CHECK_COLLISION objects[%d] x=%d y=%d count=%d index=%d", num, x, y, count, index); + } + break; + case 0x09: { + uint8 var = fetchScriptByte(); + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("SET localvars[%d], %d", var, value); + } else { + uint8 num = fetchScriptByte(); + switch (type) { + case 1: + printf("localvars[%d] = localvars[%d]", var, num); + break; + case 2: + printf("localvars[%d] = globalvars[%d]", var, num); + break; + case 3: + printf("localvars[%d] = mouse.x", var); + break; + case 4: + printf("localvars[%d] = mouse.y", var); + break; + case 5: + printf("localvars[%d] = random(%d)", var, num); + break; + case 8: + printf("localvars[%d] = datas[%d].packed_size", var, num); + break; + case 9: + printf("localvars[%d] = datas[%d].unpacked_size", var, num); + break; + default: + return false; + break; + } + } + } + break; + case 0x0A: { + uint8 var = fetchScriptByte(); + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("ADD localvars[%d], %d", var, value); + } else { + uint8 var2 = fetchScriptByte(); + printf("ADD localvars[%d], localvars[%d]", var, var2); + } + } + break; + case 0x0B: { + uint8 var = fetchScriptByte(); + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("SUB localvars[%d], %d", var, value); + } else { + uint8 var2 = fetchScriptByte(); + printf("SUB localvars[%d], localvars[%d]", var, var2); + } + } + break; + case 0x0C: { + uint8 var = fetchScriptByte(); + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("MUL localvars[%d], %d", var, value); + } else { + uint8 var2 = fetchScriptByte(); + printf("MUL localvars[%d], localvars[%d]", var, var2); + } + } + break; + case 0x0D: { + uint8 var = fetchScriptByte(); + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("DIV localvars[%d], %d", var, value); + } else { + uint8 var2 = fetchScriptByte(); + printf("DIV localvars[%d], localvars[%d]", var, var2); + } + } + break; + case 0x0E: { + uint8 var = fetchScriptByte(); + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("CMP localvars[%d], %d", var, value); + } else { + uint8 var2 = fetchScriptByte(); + if (type == 1) { + printf("CMP localvars[%d], localvars[%d]", var, var2); + } else if (type == 2) { + printf("CMP localvars[%d], globalvars[%d]", var, var2); + } else { + return false; + } + } + } + break; + case 0x0F: { + uint8 num = fetchScriptByte(); + uint8 field = fetchScriptByte(); + int16 var = fetchScriptByte(); + if (field >= 7) return false; + printf("SET object[%d].%s, localvars[%d]", num, objectField[field], var); + } + break; + case 0x13: { + uint8 num = fetchScriptByte(); + printf("ADD_OVERLAY object=%d, type=0", num); + } + break; + case 0x14: { + uint8 num = fetchScriptByte(); + printf("REMOVE_OVERLAY object=%d, type=0", num); + } + break; + case 0x15: { + uint8 num = fetchScriptByte(); + printf("ADD_OBJECT_TO_BACKGROUND_LIST objects[%d]", num); + } + break; + case 0x16: { + uint8 num = fetchScriptByte(); + printf("ADD_OVERLAY object=%d, type=1", num); + } + break; + case 0x17: { + uint8 num = fetchScriptByte(); + printf("REMOVE_OVERLAY object=%d, type=1", num); + } + break; + case 0x18: { + uint8 num = fetchScriptByte(); + printf("ADD_OVERLAY object=%d, type=4", num); + } + break; + case 0x19: { + uint8 num = fetchScriptByte(); + printf("REMOVE_OVERLAY object=%d, type=4", num); + } + break; + case 0x1A: { + uint8 num = fetchScriptByte(); + printf("ADD_SPRITE_TO_BACKGROUND_LIST objects[%d], type=4", num); + } + break; + case 0x1B: { + printf("CLEAR_BACKGROUND_LIST"); + } + break; + case 0x1D: { + uint8 num = fetchScriptByte(); + printf("LABEL_%d:", num); + } + break; + case 0x1E: { + uint8 num = fetchScriptByte(); + printf("JUMP LABEL_%d", num); + } + break; + case 0x1F: { + uint8 num = fetchScriptByte(); + printf("JUMP G LABEL_%d", num); + } + break; + case 0x20: { + uint8 num = fetchScriptByte(); + printf("JUMP GEQ LABEL_%d", num); + } + break; + case 0x21: { + uint8 num = fetchScriptByte(); + printf("JUMP L LABEL_%d", num); + } + break; + case 0x22: { + uint8 num = fetchScriptByte(); + printf("JUMP LEQ LABEL_%d", num); + } + break; + case 0x23: { + uint8 num = fetchScriptByte(); + printf("JUMP EQ LABEL_%d", num); + } + break; + case 0x24: { + uint8 num = fetchScriptByte(); + printf("JUMP NOT_EQ LABEL_%d", num); + } + break; + case 0x26: { + uint8 var = fetchScriptByte(); + uint8 num = fetchScriptByte(); + printf("--localvars[%d]; JUMP GEQ LABEL_%d", var, num); + } + break; + case 0x31: { + uint8 num = fetchScriptByte(); + printf("START_GLOBAL_SCRIPT num=%d", num); + } + break; + case 0x32: { + uint8 num = fetchScriptByte(); + printf("END_GLOBAL_SCRIPT num=%d", num); + } + break; + case 0x3B: { + const char *name = fetchScriptString(); + printf("LOAD_ANIM name='%s'", name); + } + break; + case 0x3C: { + const char *name = fetchScriptString(); + printf("LOAD_BACKGROUND_IMAGE name='%s'", name); + } + break; + case 0x3D: { + const char *name = fetchScriptString(); + printf("LOAD_CT name='%s'", name); + } + break; + case 0x3F: { + const char *name = fetchScriptString(); + printf("OPEN_RESOURCE_FILE name='%s'", name); + } + break; + case 0x40: { + printf("CLOSE_RESOURCE_FILE"); + } + break; + case 0x41: { + static const char *dataType[] = { "prc", "rel", "obj", "msg" }; + uint8 type = fetchScriptByte(); + const char *name = fetchScriptString(); + if (type >= 4) return false; + printf("SET_LOADING_DATA type='%s', name='%s'", dataType[type], name); + } + break; + case 0x42: { + printf("LOAD_PENDING_DATA"); + } + break; + case 0x45: { + printf("FADE_IN"); + } + break; + case 0x46: { + printf("FADE_OUT"); + } + break; + case 0x47: { + uint8 start = fetchScriptByte(); + uint8 count = fetchScriptByte(); + int16 r = fetchScriptWord(); + int16 g = fetchScriptWord(); + int16 b = fetchScriptWord(); + printf("TRANSFORM_PALETTE start=%d, count=%d, delta_red=%d, delta_green=%d, delta_blue=%d", start, count, r, g, b); + } + break; + case 0x49: { + uint8 color = fetchScriptByte(); + printf("SET_MENU_COLOR_1 %d", color); + } + break; + case 0x4A: { + uint8 firstColor = fetchScriptByte(); + uint8 endColor = fetchScriptByte(); + uint8 type = fetchScriptByte(); + printf("ROTATE_PALETTE first_color=%d, end_color=%d, type=%d", firstColor, endColor, type); + } + break; + case 0x4F: { + printf("BREAK"); + } + break; + case 0x50: { + printf("END"); + _baseScriptData = 0; + } + break; + case 0x51: { + uint8 num = fetchScriptByte(); + int16 x = fetchScriptWord(); + int16 y = fetchScriptWord(); + int16 width = fetchScriptWord(); + int16 color = fetchScriptWord(); + printf("ADD_MESSAGE num=%d x=%d y=%d width=%d color=%d", num, x, y, width, color); + } + break; + case 0x52: { + uint8 var = fetchScriptByte(); + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("SET globalvars[%d], %d", var, value); + } else { + uint8 var2 = fetchScriptByte(); + if (type == 1) { + printf("SET globalvars[%d], localvars[%d]", var, var2); + } else { + printf("SET globalvars[%d], globalvars[%d]", var, var2); + } + } + break; + } + case 0x53: { + uint8 var = fetchScriptByte(); + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("CMP globalvars[%d], %d", var, value); + } else { + uint8 var2 = fetchScriptByte(); + printf("CMP globalvars[%d], localvars[%d]", var, var2); + } + } + break; + case 0x59: { + const char *str = fetchScriptString(); + printf("/* %s */", str); + } + break; + case 0x5A: { + uint8 start = fetchScriptByte(); + uint8 count = fetchScriptByte(); + printf("FREE_FRAMES start=%d, count=%d", start, count); + } + break; + case 0x5B: { + printf("UNLOAD_OVERLAYS"); + } + break; + case 0x65: { + printf("CLEAR_ZONE_DATA"); + } + break; + case 0x66: { + uint8 num = fetchScriptByte(); + int16 value = fetchScriptWord(); + printf("SET zones[%d], %d", num, value); + } + break; + case 0x68: { + uint8 color = fetchScriptByte(); + printf("SET_MENU_COLOR_2 %d", color); + } + break; + case 0x69: { + printf("ENABLE_PLAYER_INPUT"); + } + break; + case 0x6A: { + printf("DISABLE_PLAYER_INPUT"); + } + break; + case 0x6B: { + uint8 num = fetchScriptByte(); + printf("SET_CURRENT_DISK num=%d", num); + } + break; + case 0x6D: { + const char *name = fetchScriptString(); + printf("LOAD_MUSIC name='%s'", name); + } + break; + case 0x6E: { + printf("PLAY_MUSIC"); + } + break; + case 0x6F: { + printf("FADE_OUT_MUSIC"); + } + break; + case 0x70: { + printf("STOP_MUSIC"); + } + break; + case 0x77: + case 0x78: { + uint8 frame = fetchScriptByte(); + uint8 channel = fetchScriptByte(); + int16 freq = fetchScriptWord(); + uint8 repeat = fetchScriptByte(); + int16 volume = fetchScriptWord(); + int16 size = fetchScriptWord(); + printf("PLAY_SOUND frame=%d channel=%d freq=%d repeat=%d volume=%d size=%d", frame, channel, freq, repeat, volume, size); + } + break; + case 0x79: { + uint8 param = fetchScriptByte(); + printf("DISABLE_SYSTEM_MENU flag=%d", param); + } + break; + case 0x7A: { + uint8 num = fetchScriptByte(); + printf("ADD_OVERLAY object=%d, type=5", num); + } + break; + case 0x7B: { + uint8 num = fetchScriptByte(); + printf("REMOVE_OVERLAY object=%d, type=5", num); + } + break; + case 0x7F: + assert(isV2); { + uint8 a = fetchScriptByte(); + uint8 b = fetchScriptByte(); + uint8 c = fetchScriptByte(); + uint8 d = fetchScriptByte(); + int16 e = fetchScriptWord(); + int16 f = fetchScriptWord(); + int16 g = fetchScriptWord(); + printf("ADD_ANIM_SEQUENCE %d %d %d %d %d %d %d", a, b, c, d, e, f, g); + } + break; + case 0x80: + assert(isV2); { + uint8 a = fetchScriptByte(); + uint8 b = fetchScriptByte(); + printf("REMOVE_ANIM_SEQUENCE %d %d", a, b); + } + break; + case 0x82: + assert(isV2); { + uint8 a = fetchScriptByte(); + uint8 b = fetchScriptByte(); + int16 c = fetchScriptWord(); + int16 d = fetchScriptWord(); + uint8 e = fetchScriptByte(); + printf("OP_0x82 %d %d %d %d %d", a, b, c, d, e); + } + break; + case 0x83: + assert(isV2); { + uint8 a = fetchScriptByte(); + uint8 b = fetchScriptByte(); + printf("IS_ANIM_SEQUENCE_PLAYING %d %d", a, b); + } + break; + case 0x84: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("JUMP G LABEL_%d", num); + } + break; + case 0x85: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("JUMP GEQ LABEL_%d", num); + } + break; + case 0x86: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("JUMP L LABEL_%d", num); + } + break; + case 0x88: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("JUMP EQ LABEL_%d", num); + } + break; + case 0x89: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("JUMP NOT_EQ LABEL_%d", num); + } + break; + case 0x8B: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("START_OBJECT_SCRIPT num=%d", num); + } + break; + case 0x8C: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("STOP_OBJECT_SCRIPT num=%d", num); + } + break; + case 0x8D: + assert(isV2); { + int16 a = fetchScriptWord(); + int16 b = fetchScriptWord(); + int16 c = fetchScriptWord(); + int16 d = fetchScriptWord(); + int16 e = fetchScriptWord(); + int16 f = fetchScriptWord(); + int16 g = fetchScriptWord(); + int16 h = fetchScriptWord(); + printf("IS_OBJECT_INTERSECTING_OBJECT %d %d %d %d %d %d %d %d", a, b, c, d, e, f, g, h); + } + break; + case 0x8E: + assert(isV2); { + uint8 num = fetchScriptByte(); + const char *name = fetchScriptString(); + printf("ADD_BACKGROUND name='%s' num=%d", name, num); + } + break; + case 0x8F: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("REMOVE_BACKGROUND num=%d", num); + } + break; + case 0x90: + assert(isV2); { + uint8 pos = fetchScriptByte(); + const char *name = fetchScriptString(); + printf("LOAD_DATA_ABSOLUTE name='%s' pos=%d", name, pos); + } + break; + case 0x91: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("SET_CURRENT_BACKGROUND num=%d", num); + } + break; + case 0x9A: + assert(isV2); { + uint8 a = fetchScriptByte(); + printf("OP_0x9A %d", a); + } + break; + case 0x9D: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("SET_NEXT_BACKGROUND %d", num); + } + break; + case 0x9E: + assert(isV2); { + uint8 type = fetchScriptByte(); + if (type == 0) { + int16 value = fetchScriptWord(); + printf("SET_SCROLLING_BACKGROUND %d", value); + } else { + uint8 var = fetchScriptByte(); + printf("SET_SCROLLING_BACKGROUND localvars[%d]", var); + } + } + break; + case 0xA0: + assert(isV2); { + int16 a = fetchScriptWord(); + int16 b = fetchScriptWord(); + printf("OP_0xA0 %d %d", a, b); + } + break; + case 0xA1: + assert(isV2); { + int16 a = fetchScriptWord(); + int16 b = fetchScriptWord(); + printf("OP_0xA1 %d %d", a, b); + } + break; + case 0xA2: + assert(isV2); { + int16 a = fetchScriptWord(); + int16 b = fetchScriptWord(); + printf("OP_0xA2 %d %d", a, b); + } + break; + case 0xA3: + assert(isV2); { + int16 a = fetchScriptWord(); + int16 b = fetchScriptWord(); + printf("OP_0xA3 %d %d", a, b); + } + break; + case 0xA4: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("ADD_OVERLAY object=%d, type=22", num); + } + break; + case 0xA5: + assert(isV2); { + uint8 num = fetchScriptByte(); + printf("REMOVE_OVERLAY object=%d, type=22", num); + } + break; + default: + printf("Unhandled script opcode 0x%X", opcode); + return false; + } + } + printf("\n"); + return true; +} + +typedef void (*DecodeFile)(int isV2, const uint8 *data, int dataSize); + +/* .prc procedure */ +static void decodePrc(int isV2, const uint8 *data, int dataSize) { + int i, num; + struct { + uint16 size; + } hdr[256]; + + num = READ_BE_UINT16(data); data += 2; + if (num >= 256) { + printf("// Number of scripts (%d) in PRC-file is too large (i.e. >= 256)\n\n", num); + return; + } + + for (i = 0; i < num; ++i) { + hdr[i].size = READ_BE_UINT16(data); data += 2; + } + for (i = 0; i < num; ++i) { + if (hdr[i].size != 0) { + printf("\n"); + printf("// script.prc %d size 0x%X\n", i, hdr[i].size); + _baseScriptData = _scriptData = data; data += hdr[i].size; + bool status = true; + while (_baseScriptData && _scriptData < data && status) { + status = decodeOpcode(isV2); + } + if (!status) { + printf("// Error handling opcode\n\n"); + return; + } + printf("\n"); + } + } +} + +/* .rel relation */ +static void decodeRel(int isV2, const uint8 *data, int dataSize) { + int i, num; + struct { + uint16 size; + uint16 param1; + uint16 index; + uint16 param2; + } hdr[256]; + + num = READ_BE_UINT16(data); data += 2; + if (num >= 256) { + printf("// Number of scripts (%d) in REL-file is too large (i.e. >= 256)\n\n", num); + return; + } + for (i = 0; i < num; ++i) { + hdr[i].size = READ_BE_UINT16(data); data += 2; + hdr[i].param1 = READ_BE_UINT16(data); data += 2; + hdr[i].index = READ_BE_UINT16(data); data += 2; + hdr[i].param2 = READ_BE_UINT16(data); data += 2; + } + for (i = 0; i < num; ++i) { + if (hdr[i].size != 0) { + printf("\n"); + printf("// script.rel %d size 0x%X param1 %d index %d param2 %d\n", i, hdr[i].size, hdr[i].param1, hdr[i].index, hdr[i].param2); + _baseScriptData = _scriptData = data; data += hdr[i].size; + bool status = true; + while (_baseScriptData && _scriptData < data && status) { + status = decodeOpcode(isV2); + } + if (!status) { + printf("// Error handling opcode\n\n"); + return; + } + printf("\n"); + } + } +} + +int showUsage() { + printf("USAGE: cine_decode [type] [version] [filename]\n" \ + "type: -prc or -rel (Use -prc for *.prc-files, -rel for *.rel-files)\n" \ + "version: -v1 or -v2 (Use -v1 for Future Wars, -v2 for Operation Stealth)\n" \ + "filename: The name of the script file to decode\n"); + return -1; +} + +int main(int argc, char *argv[]) { + if (argc == 4) { + FILE *fp; + uint8 *data; + int dataSize, isV2; + DecodeFile decodeFile; + + if (strcmp(argv[1], "-prc") == 0) { + decodeFile = decodePrc; + } else if (strcmp(argv[1], "-rel") == 0) { + decodeFile = decodeRel; + } else { + return showUsage(); + } + + if (strcmp(argv[2], "-v1") == 0) { + isV2 = 0; + } else if (strcmp(argv[2], "-v2") == 0) { + isV2 = 1; + } else { + return showUsage(); + } + + fp = fopen(argv[3], "rb"); + if (fp) { + fseek(fp, 0, SEEK_END); + dataSize = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (uint8 *)malloc(dataSize); + if (data) { + fread(data, dataSize, 1, fp); + (*decodeFile)(isV2, data, dataSize); + free(data); + } + fclose(fp); + } else { + printf("Couldn't open script file '%s'\n", argv[3]); + return -1; + } + } else { + return showUsage(); + } + return 0; +} diff --git a/tools/cine_tools/cine_unpack.cpp b/tools/cine_tools/cine_unpack.cpp new file mode 100644 index 0000000000..14ac3df795 --- /dev/null +++ b/tools/cine_tools/cine_unpack.cpp @@ -0,0 +1,323 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This is a utility to unpack Delphine's Cinematique engine's archive files. + * Should at least work with Future Wars and Operation Stealth. + * Supports using Operation Stealth's 'vol.cnf' file as input for selecting + * which archive files to unpack. + * + * Note that this isn't polished code so caveat emptor. + */ + +#include +#include +#include +#include +#include + +#include "cine_unpack.h" +#include "typedefs.h" +#include "endian.h" + +//////////////////////////////////////////////////////////////////////////// + +uint32 CineUnpacker::readSource() { + if (_src < _srcBegin || _src + 4 > _srcEnd) { + _error = true; + return 0; // The source pointer is out of bounds, returning a default value + } + uint32 value = READ_BE_UINT32(_src); + _src -= 4; + return value; +} + +uint CineUnpacker::rcr(bool inputCarry) { + uint outputCarry = (_chunk32b & 1); + _chunk32b >>= 1; + if (inputCarry) { + _chunk32b |= 0x80000000; + } + return outputCarry; +} + +uint CineUnpacker::nextBit() { + uint carry = rcr(false); + // Normally if the chunk becomes zero then the carry is one as + // the end of chunk marker is always the last to be shifted out. + if (_chunk32b == 0) { + _chunk32b = readSource(); + _crc ^= _chunk32b; + carry = rcr(true); // Put the end of chunk marker in the most significant bit + } + return carry; +} + +uint CineUnpacker::getBits(uint numBits) { + uint c = 0; + while (numBits--) { + c <<= 1; + c |= nextBit(); + } + return c; +} + +void CineUnpacker::unpackRawBytes(uint numBytes) { + if (_dst >= _dstEnd || _dst - numBytes + 1 < _dstBegin) { + _error = true; + return; // Destination pointer is out of bounds for this operation + } + while (numBytes--) { + *_dst = (byte)getBits(8); + --_dst; + } +} + +void CineUnpacker::copyRelocatedBytes(uint offset, uint numBytes) { + if (_dst + offset >= _dstEnd || _dst - numBytes + 1 < _dstBegin) { + _error = true; + return; // Destination pointer is out of bounds for this operation + } + while (numBytes--) { + *_dst = *(_dst + offset); + --_dst; + } +} + +bool CineUnpacker::unpack(const byte *src, uint srcLen, byte *dst, uint dstLen) { + // Initialize variables used for detecting errors during unpacking + _error = false; + _srcBegin = src; + _srcEnd = src + srcLen; + _dstBegin = dst; + _dstEnd = dst + dstLen; + + // Initialize other variables + _src = _srcBegin + srcLen - 4; + uint32 unpackedLength = readSource(); // Unpacked length in bytes + _dst = _dstBegin + unpackedLength - 1; + _crc = readSource(); + _chunk32b = readSource(); + _crc ^= _chunk32b; + + while (_dst >= _dstBegin && !_error) { + /* + Bits => Action: + 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..8) + 1 1 1 => unpackRawBytes(8 bits + 9) i.e. unpackRawBytes(9..264) + 0 1 => copyRelocatedBytes(8 bits, 2) i.e. copyRelocatedBytes(0..255, 2) + 1 0 0 => copyRelocatedBytes(9 bits, 3) i.e. copyRelocatedBytes(0..511, 3) + 1 0 1 => copyRelocatedBytes(10 bits, 4) i.e. copyRelocatedBytes(0..1023, 4) + 1 1 0 => copyRelocatedBytes(12 bits, 8 bits + 1) i.e. copyRelocatedBytes(0..4095, 1..256) + */ + if (!nextBit()) { // 0... + if (!nextBit()) { // 0 0 + uint numBytes = getBits(3) + 1; + unpackRawBytes(numBytes); + } else { // 0 1 + uint numBytes = 2; + uint offset = getBits(8); + copyRelocatedBytes(offset, numBytes); + } + } else { // 1... + uint c = getBits(2); + if (c == 3) { // 1 1 1 + uint numBytes = getBits(8) + 9; + unpackRawBytes(numBytes); + } else if (c < 2) { // 1 0 x + uint numBytes = c + 3; + uint offset = getBits(c + 9); + copyRelocatedBytes(offset, numBytes); + } else { // 1 1 0 + uint numBytes = getBits(8) + 1; + uint offset = getBits(12); + copyRelocatedBytes(offset, numBytes); + } + } + } + return !_error && (_crc == 0); +} + +//////////////////////////////////////////////////////////////////////////// + +static void unpackFile(FILE *fp, const char *outDir) { + char filePath[512], fileName[15]; + + uint entryCount = freadUint16BE(fp); // How many entries? + uint entrySize = freadUint16BE(fp); // How many bytes per entry? + assert(entrySize == 0x1e); + while (entryCount--) { + fread(fileName, 14, 1, fp); + fileName[14] = '\0'; + sprintf(filePath, "%s/%s", outDir, fileName); + FILE *fpOut = fopen(filePath, "wb"); + + uint32 offset = freadUint32BE(fp); + unsigned int packedSize = freadUint32BE(fp); + unsigned int unpackedSize = freadUint32BE(fp); + freadUint32BE(fp); + uint savedPos = ftell(fp); + + if (!fpOut) { + printf("ERROR: unable to open '%s' for writing\n", filePath); + continue; + } + printf("unpacking '%s' ... ", filePath); + + fseek(fp, offset, SEEK_SET); + assert(unpackedSize >= packedSize); + uint8 *data = (uint8 *)calloc(unpackedSize, 1); + uint8 *packedData = (uint8 *)calloc(packedSize, 1); + assert(data); + assert(packedData); + fread(packedData, packedSize, 1, fp); + bool status = true; + if (packedSize != unpackedSize) { + CineUnpacker cineUnpacker; + status = cineUnpacker.unpack(packedData, packedSize, data, unpackedSize); + } else { + memcpy(data, packedData, packedSize); + } + free(packedData); + fwrite(data, unpackedSize, 1, fpOut); + fclose(fpOut); + free(data); + + if (!status) { + printf("CRC ERROR"); + } else { + printf("ok"); + } + printf(", packedSize %u unpackedSize %u\n", packedSize, unpackedSize); + fseek(fp, savedPos, SEEK_SET); + } +} + +void fixVolCnfFileName(char *dst, const uint8 *src) { + char *ext, *end; + + memcpy(dst, src, 8); + src += 8; + dst[8] = 0; + ext = strchr(dst, ' '); + if (!ext) { + ext = &dst[8]; + } + if (*src == ' ') { + *ext = 0; + } else { + *ext++ = '.'; + memcpy(ext, src, 3); + end = strchr(ext, ' '); + if (!end) { + end = &ext[3]; + } + *end = 0; + } +} + +void unpackAllResourceFiles(const char *filename, const char *outDir) { + FILE *fp = fopen(filename, "rb"); + if (!fp) { + printf("Unable to open file '%s'\n", filename); + exit(1); + } + + uint32 unpackedSize, packedSize; + { + char header[8]; + fread(header, 8, 1, fp); + if (memcmp(header, "ABASECP", 7) == 0) { + unpackedSize = freadUint32BE(fp); + packedSize = freadUint32BE(fp); + } else { + fseek(fp, 0, SEEK_END); + unpackedSize = packedSize = ftell(fp); /* Get file size */ + fseek(fp, 0, SEEK_SET); + } + } + assert(unpackedSize >= packedSize); + uint8 *buf = (uint8 *)calloc(unpackedSize, 1); + assert(buf); + fread(buf, packedSize, 1, fp); + fclose(fp); + if (packedSize != unpackedSize) { + CineUnpacker cineUnpacker; + if (!cineUnpacker.unpack(buf, packedSize, buf, unpackedSize)) { + printf("Error while unpacking 'vol.cnf' data"); + exit(1); + } + } + uint resourceFilesCount = READ_BE_UINT16(&buf[0]); + uint entrySize = READ_BE_UINT16(&buf[2]); + printf("--- Unpacking all %d resource files from 'vol.cnf' (entrySize = %d):\n", resourceFilesCount, entrySize); + char resourceFileName[9]; + for (unsigned int i = 0; i < resourceFilesCount; ++i) { + memcpy(resourceFileName, &buf[4 + i * entrySize], 8); + resourceFileName[8] = 0; + FILE *fpResFile = fopen(resourceFileName, "rb"); + if (fpResFile) { + printf("--- Unpacking resource file %s:\n", resourceFileName); + unpackFile(fpResFile, outDir); + fclose(fpResFile); + } else { + printf("ERROR: Unable to open resource file %s\n", resourceFileName); + } + } + + free(buf); +} + +int showUsage() { + printf("USAGE: cine_unpack [input file] [output directory]\n" \ + "Supports using Operation Stealth's 'vol.cnf' file as input.\n"); + return -1; +} + +int main(int argc, char *argv[]) { + int i; + char tmp[512]; + + if (argc == 3) { + strcpy(tmp, argv[1]); + for (i = 0; tmp[i] != 0; i++) { + tmp[i] = toupper(tmp[i]); + } + if (!strcmp(tmp, "VOL.CNF")) { + /* Unpack all archive files listed in 'vol.cnf' */ + unpackAllResourceFiles(argv[1], argv[2]); + } else { + /* Unpack a single archive file */ + FILE *fp = fopen(argv[1], "rb"); + if (fp) { + unpackFile(fp, argv[2]); + fclose(fp); + } else { + printf("Couldn't open input file '%s'\n", argv[1]); + return -1; + } + } + } else { + return showUsage(); + } + return 0; +} diff --git a/tools/cine_tools/cine_unpack.h b/tools/cine_tools/cine_unpack.h new file mode 100644 index 0000000000..66a39a05af --- /dev/null +++ b/tools/cine_tools/cine_unpack.h @@ -0,0 +1,87 @@ +#ifndef CINE_UNPACK_H +#define CINE_UNPACK_H + +#include "typedefs.h" + +/** + * A LZ77 style decompressor for Delphine's data files + * used in at least Future Wars and Operation Stealth. + * @note Works backwards in the source and destination buffers. + * @note Can work with source and destination in the same buffer if there's space. + */ +class CineUnpacker { +public: + /** + * Unpacks packed data from the source buffer to the destination buffer. + * @warning Do NOT call this on data that is not packed. + * @note Source and destination buffer pointers can be the same as long as there's space for the unpacked data. + * @param src Pointer to the source buffer. + * @param srcLen Length of the source buffer. + * @param dst Pointer to the destination buffer. + * @param dstLen Length of the destination buffer. + * @return True if no errors were detected in the source data and unpacking was successful, otherwise false. + */ + bool unpack(const byte *src, uint srcLen, byte *dst, uint dstLen); +private: + /** + * Reads an unsigned big endian 32-bit integer from the source stream and goes backwards 4 bytes. + * @return If the operation is valid, an unsigned big endian 32-bit integer read from the source stream. + * @return If the operation is invalid, zero. + * @note Sets internal error state if the read operation would be out of source bounds. + */ + uint32 readSource(); + + /** + * Shifts the current internal 32-bit chunk to the right by one. + * Puts input carry into internal chunk's topmost (i.e. leftmost) bit. + * @return The least significant bit that was shifted out from the chunk. + */ + uint rcr(bool inputCarry); + + /** + * Get the next bit from the source stream. + * @note Changes the bit position in the source stream. + * @return The next bit from the source stream. + */ + uint nextBit(); + + /** + * Get bits from the source stream. + * @note Changes the bit position in the source stream. + * @param numBits Number of bits to read from the source stream. + * @return Integer value consisting of the bits read from the source stream (In range [0, (2 ** numBits) - 1]). + * @return Later the bit was read from the source, the less significant it is in the return value. + */ + uint getBits(uint numBits); + + /** + * Copy raw bytes from the input stream and write them to the destination stream. + * This is used when no adequately long match is found in the sliding window. + * @note Sets internal error state if the operation would be out of bounds. + * @param numBytes Amount of bytes to copy from the input stream + */ + void unpackRawBytes(uint numBytes); + + /** + * Copy bytes from the sliding window in the destination buffer. + * This is used when a match of two bytes or longer is found. + * @note Sets internal error state if the operation would be out of bounds. + * @param offset Offset in the sliding window + * @param numBytes Amount of bytes to copy + */ + void copyRelocatedBytes(uint offset, uint numBytes); +private: + uint32 _crc; //!< Error-detecting code (This should be zero after successful unpacking) + uint32 _chunk32b; //!< The current internal 32-bit chunk of source data + byte *_dst; //!< Pointer to the current position in the destination buffer + const byte *_src; //!< Pointer to the current position in the source buffer + + // These are used for detecting errors (e.g. out of bounds issues) during unpacking + bool _error; //!< Did an error occur during unpacking? + const byte *_srcBegin; //!< Source buffer's beginning + const byte *_srcEnd; //!< Source buffer's end + byte *_dstBegin; //!< Destination buffer's beginning + byte *_dstEnd; //!< Destination buffer's end +}; + +#endif diff --git a/tools/cine_tools/endian.h b/tools/cine_tools/endian.h new file mode 100644 index 0000000000..8c4471557e --- /dev/null +++ b/tools/cine_tools/endian.h @@ -0,0 +1,30 @@ +#ifndef CINE_ENDIAN_H +#define CINE_ENDIAN_H + +#include // fread etc +#include "typedefs.h" // uint8 etc + +/* NOTE: These big endian reading functions assume we're on a little endian machine. */ +static uint16 READ_BE_UINT16(const void *ptr) { + const uint8 *b = (const uint8 *)ptr; + return (b[0] << 8) | b[1]; +} + +static uint32 READ_BE_UINT32(const void *ptr) { + const uint8 *b = (const uint8 *)ptr; + return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; +} + +static uint16 freadUint16BE(FILE *fp) { + char data[2]; + fread(data, 2, 1, fp); + return READ_BE_UINT16(data); +} + +static uint32 freadUint32BE(FILE *fp) { + char data[4]; + fread(data, 4, 1, fp); + return READ_BE_UINT32(data); +} + +#endif diff --git a/tools/cine_tools/typedefs.h b/tools/cine_tools/typedefs.h new file mode 100644 index 0000000000..03de18d41a --- /dev/null +++ b/tools/cine_tools/typedefs.h @@ -0,0 +1,14 @@ +#ifndef CINE_TYPEDEFS_H +#define CINE_TYPEDEFS_H + +typedef unsigned char uint8; +typedef signed char int8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned long uint32; +typedef signed long int32; + +typedef unsigned int uint; +typedef unsigned char byte; + +#endif -- cgit v1.2.3