/* 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. * */ #include "made/script.h" #include "made/scriptfuncs.h" #include "made/made.h" #include "made/database.h" #include "made/screen.h" #include "common/util.h" namespace Made { /* ScriptInterpreter */ ScriptInterpreter::ScriptInterpreter(MadeEngine *vm) : _vm(vm) { #ifdef DUMP_SCRIPTS #define COMMAND(x, sig) { &ScriptInterpreter::x, #x, sig } #else #define COMMAND(x, sig) { &ScriptInterpreter::x, #x} #endif static CommandEntry commandProcs[] = { /* 01 */ COMMAND(cmd_branchTrue, "W"), COMMAND(cmd_branchFalse, "W"), COMMAND(cmd_branch, "W"), COMMAND(cmd_true, ""), /* 05 */ COMMAND(cmd_false, ""), COMMAND(cmd_push, ""), COMMAND(cmd_not, ""), COMMAND(cmd_add, ""), /* 09 */ COMMAND(cmd_sub, ""), COMMAND(cmd_mul, ""), COMMAND(cmd_div, ""), COMMAND(cmd_mod, ""), /* 13 */ COMMAND(cmd_band, ""), COMMAND(cmd_bor, ""), COMMAND(cmd_bnot, ""), COMMAND(cmd_lt, ""), /* 17 */ COMMAND(cmd_eq, ""), COMMAND(cmd_gt, ""), COMMAND(cmd_loadConstant, "w"), COMMAND(cmd_loadVariable, "w"), /* 21 */ COMMAND(cmd_getObjectProperty, ""), COMMAND(cmd_setObjectProperty, ""), COMMAND(cmd_set, "w"), COMMAND(cmd_print, ""), /* 25 */ COMMAND(cmd_terpri, ""), COMMAND(cmd_printNumber, ""), COMMAND(cmd_vref, ""), COMMAND(cmd_vset, ""), /* 29 */ COMMAND(cmd_vsize, ""), COMMAND(cmd_exit, ""), COMMAND(cmd_return, ""), COMMAND(cmd_call, "b"), /* 33 */ COMMAND(cmd_svar, ""), COMMAND(cmd_sset, ""), COMMAND(cmd_split, ""), COMMAND(cmd_snlit, ""), /* 37 */ COMMAND(cmd_yorn, ""), COMMAND(cmd_save, ""), COMMAND(cmd_restore, ""), COMMAND(cmd_arg, "b"), /* 41 */ COMMAND(cmd_aset, "b"), COMMAND(cmd_tmp, "b"), COMMAND(cmd_tset, "b"), COMMAND(cmd_tspace, "b"), /* 45 */ COMMAND(cmd_class, ""), COMMAND(cmd_objectp, ""), COMMAND(cmd_vectorp, ""), COMMAND(cmd_restart, ""), /* 49 */ COMMAND(cmd_rand, ""), COMMAND(cmd_randomize, ""), COMMAND(cmd_send, "b"), COMMAND(cmd_extend, "Eb"), /* 53 */ COMMAND(cmd_catch, ""), COMMAND(cmd_cdone, ""), COMMAND(cmd_throw, ""), COMMAND(cmd_functionp, ""), /* 57 */ COMMAND(cmd_le, ""), COMMAND(cmd_ge, ""), COMMAND(cmd_varx, ""), COMMAND(cmd_setx, "") }; _commands = commandProcs; _commandsMax = ARRAYSIZE(commandProcs) + 1; _functions = new ScriptFunctions(_vm); _functions->setupExternalsTable(); _localStackPos = 0; _runningScriptObjectIndex = 0; _codeBase = nullptr; _codeIp = nullptr; #undef COMMAND } ScriptInterpreter::~ScriptInterpreter() { delete _functions; } void ScriptInterpreter::runScript(int16 scriptObjectIndex) { uint32 opcodeSleepCounter = 0; _runningScriptObjectIndex = scriptObjectIndex; _localStackPos = _stack.getStackPos(); _codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData(); _codeIp = _codeBase; while (!_vm->shouldQuit()) { byte opcode = readByte(); if (opcode >= 1 && opcode <= _commandsMax) { debug(4, "[%04X:%04X] %s", _runningScriptObjectIndex, (uint) (_codeIp - _codeBase), _commands[opcode - 1].desc); (this->*_commands[opcode - 1].proc)(); } else { warning("ScriptInterpreter::runScript(%d) Unknown opcode %02X", _runningScriptObjectIndex, opcode); } /* We sleep a little after 500 opcodes to reduce the CPU load. */ if (++opcodeSleepCounter > 500) { _vm->_screen->updateScreenAndWait(5); opcodeSleepCounter = 0; } } } byte ScriptInterpreter::readByte() { return *_codeIp++; } int16 ScriptInterpreter::readInt16() { int16 temp = (int16)READ_LE_UINT16(_codeIp); _codeIp += 2; debug(4, "readInt16() value = %04X", temp); return temp; } void ScriptInterpreter::cmd_branchTrue() { int16 ofs = readInt16(); if (_stack.top() != 0) _codeIp = _codeBase + ofs; } void ScriptInterpreter::cmd_branchFalse() { int16 ofs = readInt16(); if (_stack.top() == 0) _codeIp = _codeBase + ofs; } void ScriptInterpreter::cmd_branch() { int16 ofs = readInt16(); _codeIp = _codeBase + ofs; } void ScriptInterpreter::cmd_true() { _stack.setTop(-1); } void ScriptInterpreter::cmd_false() { _stack.setTop(0); } void ScriptInterpreter::cmd_push() { _stack.push(); } void ScriptInterpreter::cmd_not() { if (_stack.top() == 0) _stack.setTop(-1); else _stack.setTop(0); } void ScriptInterpreter::cmd_add() { int16 value = _stack.pop(); _stack.setTop(_stack.top() + value); } void ScriptInterpreter::cmd_sub() { int16 value = _stack.pop(); _stack.setTop(_stack.top() - value); } void ScriptInterpreter::cmd_mul() { int16 value = _stack.pop(); _stack.setTop(_stack.top() * value); } void ScriptInterpreter::cmd_div() { int16 value = _stack.pop(); if (value == 0) _stack.setTop(0); else _stack.setTop(_stack.top() / value); } void ScriptInterpreter::cmd_mod() { int16 value = _stack.pop(); if (value == 0) _stack.setTop(0); else _stack.setTop(_stack.top() % value); } void ScriptInterpreter::cmd_band() { int16 value = _stack.pop(); _stack.setTop(_stack.top() & value); } void ScriptInterpreter::cmd_bor() { int16 value = _stack.pop(); _stack.setTop(_stack.top() | value); } void ScriptInterpreter::cmd_bnot() { _stack.setTop(~_stack.top()); } void ScriptInterpreter::cmd_lt() { int16 value = _stack.pop(); if (_stack.top() < value) _stack.setTop(-1); else _stack.setTop(0); } void ScriptInterpreter::cmd_eq() { int16 value = _stack.pop(); if (_stack.top() == value) _stack.setTop(-1); else _stack.setTop(0); } void ScriptInterpreter::cmd_gt() { int16 value = _stack.pop(); if (_stack.top() > value) _stack.setTop(-1); else _stack.setTop(0); } void ScriptInterpreter::cmd_loadConstant() { int16 value = readInt16(); debug(4, "value = %04X (%d)", value, value); _stack.setTop(value); } void ScriptInterpreter::cmd_loadVariable() { int16 variable = readInt16(); int16 value = _vm->_dat->getVar(variable); debug(4, "variable = %d; value = %d (%04X)", variable, value, value); _stack.setTop(value); } void ScriptInterpreter::cmd_getObjectProperty() { int16 propertyId = _stack.pop(); int16 objectIndex = _stack.top(); int16 value = _vm->_dat->getObjectProperty(objectIndex, propertyId); debug(4, "value = %04X(%d)", value, value); _stack.setTop(value); } void ScriptInterpreter::cmd_setObjectProperty() { int16 value = _stack.pop(); int16 propertyId = _stack.pop(); int16 objectIndex = _stack.top(); value = _vm->_dat->setObjectProperty(objectIndex, propertyId, value); _stack.setTop(value); } void ScriptInterpreter::cmd_set() { int16 variable = readInt16(); debug(4, "var(%d) = %04d (%d)", variable, _stack.top(), _stack.top()); _vm->_dat->setVar(variable, _stack.top()); } void ScriptInterpreter::cmd_print() { // TODO: This opcode was used for printing debug messages const char *text = _vm->_dat->getObjectString(_stack.top()); debug(4, "%s", text); _stack.setTop(0); } void ScriptInterpreter::cmd_terpri() { // TODO: This opcode was used for printing debug messages debug(4, "\n"); _stack.setTop(0); } void ScriptInterpreter::cmd_printNumber() { // TODO: This opcode was used for printing debug messages debug(4, "%d", _stack.top()); } void ScriptInterpreter::cmd_vref() { int16 value = 0; int16 index = _stack.pop(); int16 objectIndex = _stack.top(); debug(4, "index = %d; objectIndex = %d", index, objectIndex); if (objectIndex > 0) { Object *obj = _vm->_dat->getObject(objectIndex); value = obj->getVectorItem(index); } _stack.setTop(value); debug(4, "--> value = %d", value); } void ScriptInterpreter::cmd_vset() { int16 value = _stack.pop(); int16 index = _stack.pop(); int16 objectIndex = _stack.top(); debug(4, "index = %d; objectIndex = %d; value = %d", index, objectIndex, value); if (objectIndex > 0) { Object *obj = _vm->_dat->getObject(objectIndex); obj->setVectorItem(index, value); } _stack.setTop(value); } void ScriptInterpreter::cmd_vsize() { int16 objectIndex = _stack.top(); int16 size = 0; if (objectIndex > 0) { Object *obj = _vm->_dat->getObject(objectIndex); size = obj->getVectorSize(); } _stack.setTop(size); } void ScriptInterpreter::cmd_exit() { _vm->quitGame(); // Make sure the "quit" event is handled immediately _vm->handleEvents(); } void ScriptInterpreter::cmd_return() { // Check if returning from main function if (_localStackPos == kScriptStackSize) { _vm->quitGame(); // Make sure the "quit" event is handled immediately _vm->handleEvents(); return; } int16 funcResult = _stack.top(); _stack.setStackPos(_localStackPos); _localStackPos = kScriptStackLimit - _stack.pop(); _runningScriptObjectIndex = _stack.pop(); _codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData(); _codeIp = _codeBase + _stack.pop(); byte argc = _stack.pop(); _stack.free(argc); _stack.setTop(funcResult); debug(4, "LEAVE: stackPtr = %d; _localStackPos = %d\n", _stack.getStackPos(), _localStackPos); } void ScriptInterpreter::cmd_call() { debug(4, "\nENTER: stackPtr = %d; _localStackPos = %d", _stack.getStackPos(), _localStackPos); byte argc = readByte(); _stack.push(argc); _stack.push(_codeIp - _codeBase); _stack.push(_runningScriptObjectIndex); _stack.push(kScriptStackLimit - _localStackPos); _localStackPos = _stack.getStackPos(); _runningScriptObjectIndex = _stack.peek(_localStackPos + argc + 4); debug(4, "argc = %d; _runningScriptObjectIndex = %04X", argc, _runningScriptObjectIndex); _codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData(); _codeIp = _codeBase; } void ScriptInterpreter::cmd_svar() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_svar"); } void ScriptInterpreter::cmd_sset() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_sset"); } void ScriptInterpreter::cmd_split() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_split"); } void ScriptInterpreter::cmd_snlit() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_snlit"); } void ScriptInterpreter::cmd_yorn() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_yorn"); } void ScriptInterpreter::cmd_save() { int16 result = 0; int16 stringOfs = _stack.top(); const char *filename = _vm->_dat->getString(stringOfs); result = _vm->_dat->savegame(filename, "", 0); _stack.setTop(result); } void ScriptInterpreter::cmd_restore() { int16 result = 0; int16 stringOfs = _stack.top(); const char *filename = _vm->_dat->getString(stringOfs); result = _vm->_dat->loadgame(filename, 0); _stack.setTop(result); } void ScriptInterpreter::cmd_arg() { int16 argIndex = readByte(); debug(4, "argIndex = %d; value = %04X (%d)", argIndex, _stack.peek(_localStackPos + 4 + argIndex), _stack.peek(_localStackPos + 4 + argIndex)); _stack.setTop(_stack.peek(_localStackPos + 4 + argIndex)); } void ScriptInterpreter::cmd_aset() { int16 argIndex = readByte(); debug(4, "argIndex = %d; value = %d", argIndex, _stack.peek(_localStackPos + 4 + argIndex)); _stack.poke(_localStackPos + 4 + argIndex, _stack.top()); } void ScriptInterpreter::cmd_tmp() { int16 tempIndex = readByte(); debug(4, "tempIndex = %d; value = %d", tempIndex, _stack.peek(_localStackPos - tempIndex - 1)); _stack.setTop(_stack.peek(_localStackPos - tempIndex - 1)); } void ScriptInterpreter::cmd_tset() { int16 tempIndex = readByte(); debug(4, "tempIndex = %d; value = %d", tempIndex, _stack.top()); _stack.poke(_localStackPos - tempIndex - 1, _stack.top()); } void ScriptInterpreter::cmd_tspace() { int16 tempCount = readByte(); debug(4, "tempCount = %d", tempCount); _stack.alloc(tempCount); } void ScriptInterpreter::cmd_class() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_class"); } void ScriptInterpreter::cmd_objectp() { Object *obj = _vm->_dat->getObject(_stack.top()); if (obj->isObject()) _stack.setTop(-1); else _stack.setTop(0); } void ScriptInterpreter::cmd_vectorp() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_vectorp"); } void ScriptInterpreter::cmd_restart() { _vm->_dat->reload(); _vm->_screen->clearChannels(); _vm->resetAllTimers(); _stack.setTop(0); } void ScriptInterpreter::cmd_rand() { _stack.setTop(_vm->_rnd->getRandomNumber(_stack.top() - 1)); } void ScriptInterpreter::cmd_randomize() { _vm->_rnd->setSeed(g_system->getMillis()); _stack.setTop(0); } void ScriptInterpreter::cmd_send() { debug(4, "\nENTER: stackPtr = %d; _localStackPos = %d", _stack.getStackPos(), _localStackPos); byte argc = readByte(); debug(4, "argc = %d", argc); _stack.push(argc); _stack.push(_codeIp - _codeBase); _stack.push(_runningScriptObjectIndex); _stack.push(kScriptStackLimit - _localStackPos); _localStackPos = _stack.getStackPos(); int16 propertyId = _stack.peek(_localStackPos + argc + 2); int16 objectIndex = _stack.peek(_localStackPos + argc + 4); debug(4, "objectIndex = %d (%04X); propertyId = %d(%04X)", objectIndex, objectIndex, propertyId, propertyId); if (objectIndex != 0) { objectIndex = _vm->_dat->getObject(objectIndex)->getClass(); } else { objectIndex = _stack.peek(_localStackPos + argc + 3); } debug(4, "--> objectIndex = %d(%04X)", objectIndex, objectIndex); if (objectIndex != 0) { _runningScriptObjectIndex = _vm->_dat->getObjectProperty(objectIndex, propertyId); if (_runningScriptObjectIndex != 0) { _codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData(); _codeIp = _codeBase; } else { _stack.push(0); cmd_return(); } } else { _stack.push(0); cmd_return(); } } void ScriptInterpreter::cmd_extend() { byte func = readByte(); byte argc = readByte(); int16 *argv = _stack.getStackPtr(); debug(4, "func = %d (%s); argc = %d", func, _functions->getFuncName(func), argc); for (int i = 0; i < argc; i++) debug(2, "argv[%02d] = %04X (%d)", i, argv[i], argv[i]); int16 result = _functions->callFunction(func, argc, argv); debug(2, "result = %04X (%d)", result, result); _stack.free(argc); _stack.setTop(result); } void ScriptInterpreter::cmd_catch() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_catch"); } void ScriptInterpreter::cmd_cdone() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_cdone"); } void ScriptInterpreter::cmd_throw() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_throw"); } void ScriptInterpreter::cmd_functionp() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_functionp"); } void ScriptInterpreter::cmd_le() { int16 value = _stack.pop(); if (_stack.top() <= value) _stack.setTop(-1); else _stack.setTop(0); } void ScriptInterpreter::cmd_ge() { int16 value = _stack.pop(); if (_stack.top() >= value) _stack.setTop(-1); else _stack.setTop(0); } void ScriptInterpreter::cmd_varx() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_varx"); } void ScriptInterpreter::cmd_setx() { // Never used in LGOP2, RTZ, Manhole:NE, Rodney warning("Unimplemented command: cmd_setx"); } #ifdef DUMP_SCRIPTS void ScriptInterpreter::dumpScript(int16 objectIndex, int *opcodeStats, int *externStats) { debug(1, "Dumping code for object %04X", objectIndex); Object *obj = _vm->_dat->getObject(objectIndex); byte *code = obj->getData(), *codeStart = code, *codeEnd = code + obj->getSize(); while (code < codeEnd) { byte opcode = *code++; if (opcode >= 1 && opcode <= _commandsMax) { Common::String codeLine; const char *desc = _commands[opcode - 1].desc; const char *sig = _commands[opcode - 1].sig; int valueType; /* 0: dec; 1: hex; 2: extended function */ int16 value; opcodeStats[opcode - 1]++; codeLine += Common::String::format("[%04X] ", (uint16)(code - codeStart - 1)); codeLine += desc; for (; *sig != '\0'; sig++) { codeLine += " "; switch (*sig) { case 'b': valueType = 0; value = *code++; break; case 'B': valueType = 1; value = *code++; break; case 'w': valueType = 0; value = READ_LE_UINT16(code); code += 2; break; case 'W': valueType = 1; value = READ_LE_UINT16(code); code += 2; break; case 'E': valueType = 2; value = *code++; break; } Common::String tempStr; switch (valueType) { case 0: tempStr = Common::String::format("%d", value); break; case 1: tempStr = Common::String::format("0x%X", value); break; case 2: if (value < _functions->getCount()) { tempStr = Common::String::format("%s", _functions->getFuncName(value)); externStats[value]++; } else { tempStr = Common::String::format("invalid: %d", value); } break; } codeLine += tempStr; } debug(1, "%s", codeLine.c_str()); } else { error("ScriptInterpreter::dumpScript(%d) Unknown opcode %02X", objectIndex, opcode); } } debug(1, "-------------------------------------------"); } void ScriptInterpreter::dumpAllScripts() { int *opcodeStats = new int[_commandsMax - 1]; int *externStats = new int[_functions->getCount()]; for (int i = 0; i < _commandsMax; i++) opcodeStats[i] = 0; for (int i = 0; i < _functions->getCount(); i++) externStats[i] = 0; for (uint objectIndex = 1; objectIndex <= _vm->_dat->getObjectCount(); objectIndex++) { Object *obj = _vm->_dat->getObject(objectIndex); // Check if it's a byte array which might contain code if (obj->getClass() != 0x7FFF) continue; // Code objects aren't excplicitly marked as such, we need to check if // the last byte is a cmd_return opcode. byte *retByte = obj->getData() + obj->getSize() - 1; if (*retByte == 0x1F) { dumpScript(objectIndex, opcodeStats, externStats); } } debug(1, "OPCODE statistics:"); for (int i = 0; i < _commandsMax - 1; i++) if (opcodeStats[i] > 0) debug(1, "%-30s: %d", _commands[i].desc, opcodeStats[i]); debug(1, "UNUSED OPCODE statistics:"); for (int i = 0; i < _commandsMax - 1; i++) if (opcodeStats[i] == 0) debug(1, "%-30s: %d", _commands[i].desc, opcodeStats[i]); debug(1, "."); debug(1, "EXTERN statistics (%d):", _functions->getCount()); for (int i = 0; i < _functions->getCount(); i++) if (externStats[i] > 0) debug(1, "%-30s: %d", _functions->getFuncName(i), externStats[i]); debug(1, "UNUSED EXTERN statistics (%d):", _functions->getCount()); for (int i = 0; i < _functions->getCount(); i++) if (externStats[i] == 0) debug(1, "%-30s: %d", _functions->getFuncName(i), externStats[i]); debug(1, "."); delete[] opcodeStats; delete[] externStats; } #endif } // End of namespace Made