Files
scummvm-tools/descumm-tool.cpp
T
2009-12-17 02:08:22 +00:00

452 lines
10 KiB
C++

/* 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$
* $Id$
*
*/
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include "descumm.h"
#include "common/endian.h"
#include "common/util.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-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 '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 'g':
g_options.heVersion = atoi(s + 1);
g_options.scriptVersion = 6;
g_jump_opcode = 0x73;
// Skip three digits for HE version
s += 3;
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 < (uint)(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 == 100)
next_line_HE_V100(outputLineBuffer);
else if (g_options.heVersion >= 72)
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;
}