/* DeScumm - Scumm Script Disassembler * Copyright (C) 2001 Ludvig Strigeus * Copyright (C) 2002-2006 The ScummVM Team * * 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/tools/tags/release-0-10-0/descumm-tool.cpp $ * $Id: descumm-tool.cpp 24936 2006-12-27 16:27:25Z fingolfin $ * */ #include "descumm.h" // 200kb limit on the input file (we just read it all at once into memory). // Should be no problem, the biggest scripts I have seen were in COMI and // went up to 180kb (script-457). #define MAX_FILE_SIZE (200 * 1024) void ShowHelpAndExit() { printf("SCUMM Script decompiler\n" "Syntax:\n" "\tdescumm [-o] filename\n" "Flags:\n" "\t-0\tInput Script is C64\n" "\t-1\tInput Script is v1\n" "\t-2\tInput Script is v2\n" "\t-3\tInput Script is v3\n" "\t-4\tInput Script is v4\n" "\t-5\tInput Script is v5\n" "\t-6\tInput Script is v6\n" "\t-7\tInput Script is v7\n" "\t-8\tInput Script is v8\n" "\t-p\tInput Script is from Humongous Entertainment game\n" "\t-n\tUse Indy3-256 specific hacks\n" "\t-z\tUse Zak256 specific hacks\n" "\t-u\tScript is Unblocked/has no header\n" "\t-o\tAlways Show offsets\n" "\t-i\tDon't output ifs\n" "\t-e\tDon't output else\n" "\t-f\tDon't output else-if\n" "\t-w\tDon't output while\n" "\t-b\tDon't output breaks\n" "\t-c\tDon't show opcode\n" "\t-x\tDon't show offsets\n" "\t-h\tHalt on error\n"); exit(0); } int skipVerbHeader_V12(byte *p) { byte code; int offset = 15; int minOffset = 255; if (g_options.scriptVersion == 0) offset = 14; p += offset; printf("Events:\n"); while ((code = *p++) != 0) { offset = *p++; printf(" %2X - %.4X\n", code, offset); if (minOffset > offset) minOffset = offset; } return minOffset; } int skipVerbHeader_V34(byte *p) { byte code; int offset = g_options.GF_UNBLOCKED ? 17 : 19; int minOffset = 255; p += offset; printf("Events:\n"); while ((code = *p++) != 0) { offset = READ_LE_UINT16(p); p += 2; printf(" %2X - %.4X\n", code, offset); if (minOffset > offset) minOffset = offset; } return minOffset; } int skipVerbHeader_V567(byte *p) { byte code; int offset = 8; int minOffset = 255; p += offset; printf("Events:\n"); while ((code = *p++) != 0) { offset = READ_LE_UINT16(p); p += 2; printf(" %2X - %.4X\n", code, offset); if (minOffset > offset) minOffset = offset; } return minOffset; } int skipVerbHeader_V8(byte *p) { uint32 *ptr; uint32 code; int offset; int minOffset = 255; ptr = (uint32 *)p; while ((code = READ_LE_UINT32(ptr++)) != 0) { offset = READ_LE_UINT32(ptr++); printf(" %2d - %.4X\n", code, offset); if (minOffset > offset) minOffset = offset; } return minOffset; } char *parseCommandLine(int argc, char *argv[]) { char *filename = NULL; int i; char *s; for (i = 1; i < argc; i++) { s = argv[i]; if (s && s[0] == '-') { s++; while (*s) { switch (tolower(*s)) { case '0': g_options.scriptVersion = 0; g_jump_opcode = 0x18; g_options.GF_UNBLOCKED = true; break; case '1': g_options.scriptVersion = 1; g_jump_opcode = 0x18; g_options.GF_UNBLOCKED = true; break; case '2': g_options.scriptVersion = 2; g_jump_opcode = 0x18; g_options.GF_UNBLOCKED = true; break; case '3': g_options.scriptVersion = 3; g_jump_opcode = 0x18; break; case '4': g_options.scriptVersion = 4; g_jump_opcode = 0x18; break; case '5': g_options.scriptVersion = 5; g_jump_opcode = 0x18; break; case 'n': g_options.IndyFlag = 1; // Indy3 g_options.scriptVersion = 3; g_jump_opcode = 0x18; break; case 'z': g_options.ZakFlag = 1; // Zak g_options.scriptVersion = 3; g_jump_opcode = 0x18; break; case 'u': g_options.GF_UNBLOCKED = true; break; case 'p': g_options.HumongousFlag = true; // Fall through case '6': g_options.scriptVersion = 6; g_jump_opcode = 0x73; break; case '7': g_options.scriptVersion = 7; g_jump_opcode = 0x73; break; case '8': g_options.scriptVersion = 8; g_jump_opcode = 0x66; break; case '9': g_options.heVersion = 72; g_options.scriptVersion = 6; g_jump_opcode = 0x73; break; case 'o': g_options.alwaysShowOffs = true; break; case 'i': g_options.dontOutputIfs = true; break; case 'e': g_options.dontOutputElse = true; break; case 'f': g_options.dontOutputElseif = true; break; case 'w': g_options.dontOutputWhile = true; break; case 'b': g_options.dontOutputBreaks = true; break; case 'c': g_options.dontShowOpcode = true; break; case 'x': g_options.dontShowOffsets = true; break; case 'h': g_options.haltOnError = true; break; default: ShowHelpAndExit(); } s++; } } else { if (filename) ShowHelpAndExit(); filename = s; } } return filename; } void parseHeader() { if (g_options.GF_UNBLOCKED) { if (g_scriptSize < 4) { error("File too small to be a script"); } // Hack to detect verb script: first 4 bytes should be file length if (READ_LE_UINT32(g_scriptStart) == g_scriptSize) { if (g_options.scriptVersion <= 2) currentOpcodeBlockStart = skipVerbHeader_V12(g_scriptStart); else currentOpcodeBlockStart = skipVerbHeader_V34(g_scriptStart); } else { g_scriptStart += 4; } } else if (g_options.scriptVersion >= 5) { if (g_scriptSize < (g_options.scriptVersion == 5 ? 8 : 9)) { error("File too small to be a script"); } switch (READ_BE_UINT32(g_scriptStart)) { case 'LSC2': if (g_scriptSize <= 12) { printf("File too small to be a local script\n"); } printf("Script# %d\n", READ_LE_UINT32(g_scriptStart+8)); g_scriptStart += 12; break; /* Local script */ case 'LSCR': if (g_options.scriptVersion == 8) { if (g_scriptSize <= 12) { printf("File too small to be a local script\n"); } printf("Script# %d\n", READ_LE_UINT32(g_scriptStart+8)); g_scriptStart += 12; } else if (g_options.scriptVersion == 7) { if (g_scriptSize <= 10) { printf("File too small to be a local script\n"); } printf("Script# %d\n", READ_LE_UINT16(g_scriptStart+8)); g_scriptStart += 10; } else { if (g_scriptSize <= 9) { printf("File too small to be a local script\n"); } printf("Script# %d\n", (byte)g_scriptStart[8]); g_scriptStart += 9; } break; /* Local script */ case 'SCRP': g_scriptStart += 8; break; /* Script */ case 'ENCD': g_scriptStart += 8; break; /* Entry code */ case 'EXCD': g_scriptStart += 8; break; /* Exit code */ case 'VERB': if (g_options.scriptVersion == 8) { g_scriptStart += 8; currentOpcodeBlockStart = skipVerbHeader_V8(g_scriptStart); } else currentOpcodeBlockStart = skipVerbHeader_V567(g_scriptStart); break; /* Verb */ default: error("Unknown script type"); } } else { if (g_scriptSize < 6) { error("File too small to be a script"); } switch (READ_BE_UINT16(g_scriptStart + 4)) { case 'LS': printf("Script# %d\n", (byte)g_scriptStart[6]); g_scriptStart += 7; break; /* Local script */ case 'SC': g_scriptStart += 6; break; /* Script */ case 'EN': g_scriptStart += 6; break; /* Entry code */ case 'EX': g_scriptStart += 6; break; /* Exit code */ case 'OC': currentOpcodeBlockStart = skipVerbHeader_V34(g_scriptStart); break; /* Verb */ default: error("Unknown script type"); } } } int main(int argc, char *argv[]) { FILE *in; byte *fileBuffer; char *filename; memset(&g_options, 0, sizeof(g_options)); g_options.scriptVersion = 0xff; // Parse the arguments filename = parseCommandLine(argc, argv); if (!filename || g_options.scriptVersion == 0xff) ShowHelpAndExit(); in = fopen(filename, "rb"); if (!in) { printf("Unable to open %s\n", filename); return 1; } // Read the file into memory fileBuffer = (byte *)malloc(MAX_FILE_SIZE); g_scriptSize = fread(fileBuffer, 1, MAX_FILE_SIZE, in); fclose(in); g_scriptStart = fileBuffer; g_scriptCurPos = g_scriptStart; currentOpcodeBlockStart = 0; // Read (and skip over) the file header parseHeader(); g_scriptCurPos = g_scriptStart + currentOpcodeBlockStart; while (g_scriptCurPos < g_scriptSize + fileBuffer) { byte opcode = *g_scriptCurPos; int j = g_blockStack.size(); char outputLineBuffer[8192] = ""; switch (g_options.scriptVersion) { case 0: next_line_V0(outputLineBuffer); break; case 1: case 2: next_line_V12(outputLineBuffer); break; case 3: case 4: case 5: next_line_V345(outputLineBuffer); break; case 6: if (g_options.heVersion) next_line_HE_V72(outputLineBuffer); else next_line_V67(outputLineBuffer); break; case 7: next_line_V67(outputLineBuffer); break; case 8: next_line_V8(outputLineBuffer); break; } if (outputLineBuffer[0]) { writePendingElse(); if (haveElse) { haveElse = false; j--; } outputLine(outputLineBuffer, currentOpcodeBlockStart, opcode, j); currentOpcodeBlockStart = get_curoffs(); } while (!g_blockStack.empty() && get_curoffs() >= (int)g_blockStack.top().to) { g_blockStack.pop(); outputLine("}", currentOpcodeBlockStart, -1, g_blockStack.size()); currentOpcodeBlockStart = get_curoffs(); } fflush(stdout); } printf("END\n"); /* if (g_options.scriptVersion >= 6 && num_stack != 0) { printf("Stack count: %d\n", num_stack); if (num_stack > 0) { printf("Stack contents:\n"); while (num_stack) { outputLineBuffer[0] = 0; se_astext(pop(), outputLineBuffer); printf("%s\n", outputLineBuffer); } } } */ free(fileBuffer); return 0; }