From 26ee630756ebdd7c96bccede0881a8c8b98e8f2b Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sat, 11 Feb 2006 22:45:04 +0000 Subject: Moved engines to the new engines/ directory svn-id: r20582 --- engines/sword2/interpreter.cpp | 753 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 753 insertions(+) create mode 100644 engines/sword2/interpreter.cpp (limited to 'engines/sword2/interpreter.cpp') diff --git a/engines/sword2/interpreter.cpp b/engines/sword2/interpreter.cpp new file mode 100644 index 0000000000..1a6e7080b8 --- /dev/null +++ b/engines/sword2/interpreter.cpp @@ -0,0 +1,753 @@ +/* Copyright (C) 1994-1998 Revolution Software Ltd. + * Copyright (C) 2003-2006 The ScummVM project + * + * 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 "common/stdafx.h" +#include "common/util.h" +#include "sword2/sword2.h" +#include "sword2/defs.h" +#include "sword2/interpreter.h" +#include "sword2/logic.h" +#include "sword2/memory.h" +#include "sword2/resman.h" + +namespace Sword2 { + +#define STACK_SIZE 10 + +// The machine code table + +#ifndef REDUCE_MEMORY_USAGE +# define OPCODE(x) { &Logic::x, #x } +#else +# define OPCODE(x) { &Logic::x, "" } +#endif + +typedef int32 (Logic::*OpcodeProc)(int32 *); +struct OpcodeEntry { + OpcodeProc proc; + const char *desc; +}; + +static const OpcodeEntry opcodes[] = { + /* 00 */ + OPCODE(fnTestFunction), + OPCODE(fnTestFlags), + OPCODE(fnRegisterStartPoint), + OPCODE(fnInitBackground), + /* 04 */ + OPCODE(fnSetSession), + OPCODE(fnBackSprite), + OPCODE(fnSortSprite), + OPCODE(fnForeSprite), + /* 08 */ + OPCODE(fnRegisterMouse), + OPCODE(fnAnim), + OPCODE(fnRandom), + OPCODE(fnPreLoad), + /* 0C */ + OPCODE(fnAddSubject), + OPCODE(fnInteract), + OPCODE(fnChoose), + OPCODE(fnWalk), + /* 10 */ + OPCODE(fnWalkToAnim), + OPCODE(fnTurn), + OPCODE(fnStandAt), + OPCODE(fnStand), + /* 14 */ + OPCODE(fnStandAfterAnim), + OPCODE(fnPause), + OPCODE(fnMegaTableAnim), + OPCODE(fnAddMenuObject), + /* 18 */ + OPCODE(fnStartConversation), + OPCODE(fnEndConversation), + OPCODE(fnSetFrame), + OPCODE(fnRandomPause), + /* 1C */ + OPCODE(fnRegisterFrame), + OPCODE(fnNoSprite), + OPCODE(fnSendSync), + OPCODE(fnUpdatePlayerStats), + /* 20 */ + OPCODE(fnPassGraph), + OPCODE(fnInitFloorMouse), + OPCODE(fnPassMega), + OPCODE(fnFaceXY), + /* 24 */ + OPCODE(fnEndSession), + OPCODE(fnNoHuman), + OPCODE(fnAddHuman), + OPCODE(fnWeWait), + /* 28 */ + OPCODE(fnTheyDoWeWait), + OPCODE(fnTheyDo), + OPCODE(fnWalkToTalkToMega), + OPCODE(fnFadeDown), + /* 2C */ + OPCODE(fnISpeak), + OPCODE(fnTotalRestart), + OPCODE(fnSetWalkGrid), + OPCODE(fnSpeechProcess), + /* 30 */ + OPCODE(fnSetScaling), + OPCODE(fnStartEvent), + OPCODE(fnCheckEventWaiting), + OPCODE(fnRequestSpeech), + /* 34 */ + OPCODE(fnGosub), + OPCODE(fnTimedWait), + OPCODE(fnPlayFx), + OPCODE(fnStopFx), + /* 38 */ + OPCODE(fnPlayMusic), + OPCODE(fnStopMusic), + OPCODE(fnSetValue), + OPCODE(fnNewScript), + /* 3C */ + OPCODE(fnGetSync), + OPCODE(fnWaitSync), + OPCODE(fnRegisterWalkGrid), + OPCODE(fnReverseMegaTableAnim), + /* 40 */ + OPCODE(fnReverseAnim), + OPCODE(fnAddToKillList), + OPCODE(fnSetStandbyCoords), + OPCODE(fnBackPar0Sprite), + /* 44 */ + OPCODE(fnBackPar1Sprite), + OPCODE(fnForePar0Sprite), + OPCODE(fnForePar1Sprite), + OPCODE(fnSetPlayerActionEvent), + /* 48 */ + OPCODE(fnSetScrollCoordinate), + OPCODE(fnStandAtAnim), + OPCODE(fnSetScrollLeftMouse), + OPCODE(fnSetScrollRightMouse), + /* 4C */ + OPCODE(fnColour), + OPCODE(fnFlash), + OPCODE(fnPreFetch), + OPCODE(fnGetPlayerSaveData), + /* 50 */ + OPCODE(fnPassPlayerSaveData), + OPCODE(fnSendEvent), + OPCODE(fnAddWalkGrid), + OPCODE(fnRemoveWalkGrid), + /* 54 */ + OPCODE(fnCheckForEvent), + OPCODE(fnPauseForEvent), + OPCODE(fnClearEvent), + OPCODE(fnFaceMega), + /* 58 */ + OPCODE(fnPlaySequence), + OPCODE(fnShadedSprite), + OPCODE(fnUnshadedSprite), + OPCODE(fnFadeUp), + /* 5C */ + OPCODE(fnDisplayMsg), + OPCODE(fnSetObjectHeld), + OPCODE(fnAddSequenceText), + OPCODE(fnResetGlobals), + /* 60 */ + OPCODE(fnSetPalette), + OPCODE(fnRegisterPointerText), + OPCODE(fnFetchWait), + OPCODE(fnRelease), + /* 64 */ + OPCODE(fnPrepareMusic), + OPCODE(fnSoundFetch), + OPCODE(fnPrepareMusic), // Again, apparently + OPCODE(fnSmackerLeadIn), + /* 68 */ + OPCODE(fnSmackerLeadOut), + OPCODE(fnStopAllFx), + OPCODE(fnCheckPlayerActivity), + OPCODE(fnResetPlayerActivityDelay), + /* 6C */ + OPCODE(fnCheckMusicPlaying), + OPCODE(fnPlayCredits), + OPCODE(fnSetScrollSpeedNormal), + OPCODE(fnSetScrollSpeedSlow), + /* 70 */ + OPCODE(fnRemoveChooser), + OPCODE(fnSetFxVolAndPan), + OPCODE(fnSetFxVol), + OPCODE(fnRestoreGame), + /* 74 */ + OPCODE(fnRefreshInventory), + OPCODE(fnChangeShadows) +}; + +#define push(value) \ +do { \ + assert(stackPtr < ARRAYSIZE(stack)); \ + stack[stackPtr++] = (value); \ +} while (false) + +#define push_ptr(ptr) push(_vm->_memory->encodePtr(ptr)) + +#define pop() (assert(stackPtr < ARRAYSIZE(stack)), stack[--stackPtr]) + +int Logic::runResScript(uint32 scriptRes, uint32 offset) { + byte *scriptAddr; + int result; + + scriptAddr = _vm->_resman->openResource(scriptRes); + result = runScript(scriptAddr, scriptAddr, offset); + _vm->_resman->closeResource(scriptRes); + + return result; +} + +int Logic::runResObjScript(uint32 scriptRes, uint32 objRes, uint32 offset) { + byte *scriptAddr, *objAddr; + int result; + + scriptAddr = _vm->_resman->openResource(scriptRes); + objAddr = _vm->_resman->openResource(objRes); + result = runScript(scriptAddr, objAddr, offset); + _vm->_resman->closeResource(objRes); + _vm->_resman->closeResource(scriptRes); + + return result; +} + +int Logic::runScript(byte *scriptData, byte *objectData, uint32 offset) { + byte pc[4]; + + WRITE_LE_UINT32(pc, offset); + return runScript2(scriptData, objectData, pc); +} + +// This form of the runScript function is only called directly from +// the processSession() function, which uses it to update the script PC in the +// current object hub. For reasons which I do not understand, I couldn't get it +// to work if I called the function first with a dummy offset variable, and +// and then updated the object hub myself. + +int Logic::runScript2(byte *scriptData, byte *objectData, byte *offsetPtr) { + // Interestingly, unlike our BASS engine the stack is a local variable. + // I don't know whether or not this is relevant to the working of the + // BS2 engine. + + int32 stack[STACK_SIZE]; + int32 stackPtr = 0; + + uint32 offset = READ_LE_UINT32(offsetPtr); + + ResHeader header; + + header.read(scriptData); + + scriptData += ResHeader::size() + ObjectHub::size(); + + // The script data format: + // int32_TYPE 1 Size of variable space in bytes + // ... The variable space + // int32_TYPE 1 numberOfScripts + // int32_TYPE numberOfScripts The offsets for each script + + // Initialise some stuff + + uint32 ip = 0; // Code pointer + int scriptNumber; + + // Get the start of variables and start of code + + byte *localVars = scriptData + 4; + byte *code = scriptData + READ_LE_UINT32(scriptData) + 4; + uint32 noScripts = READ_LE_UINT32(code); + + code += 4; + + byte *offsetTable = code; + + if (offset < noScripts) { + ip = READ_LE_UINT32(offsetTable + offset * 4); + scriptNumber = offset; + debug(8, "Starting script %d from %d", scriptNumber, ip); + } else { + uint i; + + ip = offset; + + for (i = 1; i < noScripts; i++) { + if (READ_LE_UINT32(offsetTable + 4 * i) >= ip) + break; + } + + scriptNumber = i - 1; + debug(8, "Resuming script %d from %d", scriptNumber, ip); + } + + // There are a couple of known script bugs related to interacting with + // certain objects. We try to work around a few of them. + + bool checkMopBug = false; + bool checkPyramidBug = false; + bool checkElevatorBug = false; + + if (scriptNumber == 2) { + if (strcmp((char *)header.name, "mop_73") == 0) + checkMopBug = true; + else if (strcmp((char *)header.name, "titipoco_81") == 0) + checkPyramidBug = true; + else if (strcmp((char *)header.name, "lift_82") == 0) + checkElevatorBug = true; + } + + code += noScripts * 4; + + // Code should now be pointing at an identifier and a checksum + byte *checksumBlock = code; + + code += 4 * 3; + + if (READ_LE_UINT32(checksumBlock) != 12345678) { + error("Invalid script in object %s", header.name); + return 0; + } + + int32 codeLen = READ_LE_UINT32(checksumBlock + 4); + int32 checksum = 0; + + for (int i = 0; i < codeLen; i++) + checksum += (unsigned char) code[i]; + + if (checksum != (int32)READ_LE_UINT32(checksumBlock + 8)) { + debug(1, "Checksum error in object %s", header.name); + // This could be bad, but there has been a report about someone + // who had problems running the German version because of + // checksum errors. Could there be a version where checksums + // weren't properly calculated? + } + + bool runningScript = true; + + int parameterReturnedFromMcodeFunction = 0; // Allow scripts to return things + int savedStartOfMcode = 0; // For saving start of mcode commands + + while (runningScript) { + int i; + int32 a, b; + int curCommand, parameter, value; // Command and parameter variables + int retVal; + int caseCount; + bool foundCase; + byte *ptr; + + curCommand = code[ip++]; + + switch (curCommand) { + + // Script-related opcodes + + case CP_END_SCRIPT: + // End the script + runningScript = false; + + // WORKAROUND: The dreaded pyramid bug makes the torch + // untakeable when you speak to Titipoco. This is + // because one of the conditions for the torch to be + // takeable is that Titipoco isn't doing anything out + // of the ordinary. Global variable 913 has to be 0 to + // signify that he is in his "idle" state. + // + // Unfortunately, simply the act of speaking to him + // sets variable 913 to 1 (probably to stop him from + // turning around every now and then). The script may + // then go on to set the variable to different values + // to trigger various behaviours in him, but if you + // have run out of these cases the script won't ever + // set it back to 0 again. + // + // So if his click hander finishes, and variable 913 is + // 1, we set it back to 0 manually. + + if (checkPyramidBug && readVar(913) == 1) { + warning("Working around pyramid bug: Resetting Titipoco's state"); + writeVar(913, 0); + } + + // WORKAROUND: The not-so-known-but-should-be-dreaded + // elevator bug. + // + // The click handler for the top of the elevator only + // handles using the elevator, not examining it. When + // examining it, the mouse cursor is removed but never + // restored. + + if (checkElevatorBug && readVar(RIGHT_BUTTON)) { + warning("Working around elevator bug: Restoring mouse pointer"); + fnAddHuman(NULL); + } + + debug(9, "CP_END_SCRIPT"); + break; + case CP_QUIT: + // Quit out for a cycle + WRITE_LE_UINT32(offsetPtr, ip); + debug(9, "CP_QUIT"); + return 0; + case CP_TERMINATE: + // Quit out immediately without affecting the offset + // pointer + debug(9, "CP_TERMINATE"); + return 3; + case CP_RESTART_SCRIPT: + // Start the script again + ip = FROM_LE_32(offsetTable[scriptNumber]); + debug(9, "CP_RESTART_SCRIPT"); + break; + + // Stack-related opcodes + + case CP_PUSH_INT32: + // Push a long word value on to the stack + Read32ip(parameter); + push(parameter); + debug(9, "CP_PUSH_INT32: %d", parameter); + break; + case CP_PUSH_LOCAL_VAR32: + // Push the contents of a local variable + Read16ip(parameter); + push(READ_LE_UINT32(localVars + parameter)); + debug(9, "CP_PUSH_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, READ_LE_UINT32(localVars + parameter)); + break; + case CP_PUSH_GLOBAL_VAR32: + // Push a global variable + Read16ip(parameter); + push(readVar(parameter)); + debug(9, "CP_PUSH_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, readVar(parameter)); + break; + case CP_PUSH_LOCAL_ADDR: + // Push the address of a local variable + + // From what I understand, some scripts store data + // (e.g. mouse pointers) in their local variable space + // from the very beginning, and use this mechanism to + // pass that data to the opcode function. I don't yet + // know the conceptual difference between this and the + // CP_PUSH_DEREFERENCED_STRUCTURE opcode. + + Read16ip(parameter); + push_ptr(localVars + parameter); + debug(9, "CP_PUSH_LOCAL_ADDR: &localVars[%d] => %p", parameter / 4, localVars + parameter); + break; + case CP_PUSH_STRING: + // Push the address of a string on to the stack + // Get the string size + Read8ip(parameter); + + // ip now points to the string + ptr = code + ip; + push_ptr(ptr); + debug(9, "CP_PUSH_STRING: \"%s\"", ptr); + ip += (parameter + 1); + break; + case CP_PUSH_DEREFERENCED_STRUCTURE: + // Push the address of a dereferenced structure + Read32ip(parameter); + ptr = objectData + 4 + ResHeader::size() + ObjectHub::size() + parameter; + push_ptr(ptr); + debug(9, "CP_PUSH_DEREFERENCED_STRUCTURE: %d => %p", parameter, ptr); + break; + case CP_POP_LOCAL_VAR32: + // Pop a value into a local word variable + Read16ip(parameter); + value = pop(); + WRITE_LE_UINT32(localVars + parameter, value); + debug(9, "CP_POP_LOCAL_VAR32: localVars[%d] = %d", parameter / 4, value); + break; + case CP_POP_GLOBAL_VAR32: + // Pop a global variable + Read16ip(parameter); + value = pop(); + + // WORKAROUND for bug #1214168: The not-at-all dreaded + // mop bug. + // + // At the London Docks, global variable 1003 keeps + // track of Nico: + // + // 0: Hiding behind the first crate. + // 1: Hiding behind the second crate. + // 2: Standing in plain view on the deck. + // 3: Hiding on the roof. + // + // The bug happens when trying to pick up the mop while + // hiding on the roof. Nico climbs down, the mop is + // picked up, but the variable remains set to 3. + // Visually, everything looks ok. But as far as the + // scripts are concerned, she's still hiding up on the + // roof. This is not fatal, but leads to a number of + // glitches until the state is corrected. E.g. trying + // to climb back up the ladder will cause Nico to climb + // down again. + // + // Global variable 1017 keeps track of the mop. Setting + // it to 2 means that the mop has been picked up. We + // use that as the signal that Nico's state needs to be + // updated as well. + + if (checkMopBug && parameter == 1017 && readVar(1003) != 2) { + warning("Working around mop bug: Setting Nico's state"); + writeVar(1003, 2); + } + + writeVar(parameter, value); + debug(9, "CP_POP_GLOBAL_VAR32: scriptsVars[%d] = %d", parameter, value); + break; + case CP_ADDNPOP_LOCAL_VAR32: + Read16ip(parameter); + value = READ_LE_UINT32(localVars + parameter) + pop(); + WRITE_LE_UINT32(localVars + parameter, value); + debug(9, "CP_ADDNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value); + break; + case CP_SUBNPOP_LOCAL_VAR32: + Read16ip(parameter); + value = READ_LE_UINT32(localVars + parameter) - pop(); + WRITE_LE_UINT32(localVars + parameter, value); + debug(9, "CP_SUBNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value); + break; + case CP_ADDNPOP_GLOBAL_VAR32: + // Add and pop a global variable + Read16ip(parameter); + value = readVar(parameter) + pop(); + writeVar(parameter, value); + debug(9, "CP_ADDNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value); + break; + case CP_SUBNPOP_GLOBAL_VAR32: + // Sub and pop a global variable + Read16ip(parameter); + value = readVar(parameter) - pop(); + writeVar(parameter, value); + debug(9, "CP_SUBNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value); + break; + + // Jump opcodes + + case CP_SKIPONTRUE: + // Skip if the value on the stack is true + Read32ipLeaveip(parameter); + value = pop(); + if (!value) { + ip += 4; + debug(9, "CP_SKIPONTRUE: %d (IS FALSE (NOT SKIPPED))", parameter); + } else { + ip += parameter; + debug(9, "CP_SKIPONTRUE: %d (IS TRUE (SKIPPED))", parameter); + } + break; + case CP_SKIPONFALSE: + // Skip if the value on the stack is false + Read32ipLeaveip(parameter); + value = pop(); + if (value) { + ip += 4; + debug(9, "CP_SKIPONFALSE: %d (IS TRUE (NOT SKIPPED))", parameter); + } else { + ip += parameter; + debug(9, "CP_SKIPONFALSE: %d (IS FALSE (SKIPPED))", parameter); + } + break; + case CP_SKIPALWAYS: + // skip a block + Read32ipLeaveip(parameter); + ip += parameter; + debug(9, "CP_SKIPALWAYS: %d", parameter); + break; + case CP_SWITCH: + // switch + value = pop(); + Read32ip(caseCount); + + // Search the cases + foundCase = false; + for (i = 0; i < caseCount && !foundCase; i++) { + if (value == (int32)READ_LE_UINT32(code + ip)) { + // We have found the case, so lets + // jump to it + foundCase = true; + ip += READ_LE_UINT32(code + ip + 4); + } else + ip += 4 * 2; + } + + // If we found no matching case then use the default + + if (!foundCase) + ip += READ_LE_UINT32(code + ip); + + debug(9, "CP_SWITCH: [SORRY, NO DEBUG INFO]"); + break; + case CP_SAVE_MCODE_START: + // Save the start position on an mcode instruction in + // case we need to restart it again + savedStartOfMcode = ip - 1; + debug(9, "CP_SAVE_MCODE_START"); + break; + case CP_CALL_MCODE: + // Call an mcode routine + Read16ip(parameter); + assert(parameter < ARRAYSIZE(opcodes)); + // amount to adjust stack by (no of parameters) + Read8ip(value); + debug(9, "CP_CALL_MCODE: '%s', %d", opcodes[parameter].desc, value); + stackPtr -= value; + assert(stackPtr >= 0); + retVal = (this->*opcodes[parameter].proc)(&stack[stackPtr]); + + switch (retVal & 7) { + case IR_STOP: + // Quit out for a cycle + WRITE_LE_UINT32(offsetPtr, ip); + return 0; + case IR_CONT: + // Continue as normal + break; + case IR_TERMINATE: + // Return without updating the offset + return 2; + case IR_REPEAT: + // Return setting offset to start of this + // function call + WRITE_LE_UINT32(offsetPtr, savedStartOfMcode); + return 0; + case IR_GOSUB: + // that's really neat + WRITE_LE_UINT32(offsetPtr, ip); + return 2; + default: + error("Bad return code (%d) from '%s'", retVal & 7, opcodes[parameter].desc); + } + parameterReturnedFromMcodeFunction = retVal >> 3; + break; + case CP_JUMP_ON_RETURNED: + // Jump to a part of the script depending on + // the return value from an mcode routine + + // Get the maximum value + Read8ip(parameter); + debug(9, "CP_JUMP_ON_RETURNED: %d => %d", + parameterReturnedFromMcodeFunction, + READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4)); + ip += READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4); + break; + + // Operators + + case OP_ISEQUAL: + b = pop(); + a = pop(); + push(a == b); + debug(9, "OP_ISEQUAL: RESULT = %d", a == b); + break; + case OP_NOTEQUAL: + b = pop(); + a = pop(); + push(a != b); + debug(9, "OP_NOTEQUAL: RESULT = %d", a != b); + break; + case OP_GTTHAN: + b = pop(); + a = pop(); + push(a > b); + debug(9, "OP_GTTHAN: RESULT = %d", a > b); + break; + case OP_LSTHAN: + b = pop(); + a = pop(); + push(a < b); + debug(9, "OP_LSTHAN: RESULT = %d", a < b); + break; + case OP_GTTHANE: + b = pop(); + a = pop(); + push(a >= b); + debug(9, "OP_GTTHANE: RESULT = %d", a >= b); + break; + case OP_LSTHANE: + b = pop(); + a = pop(); + push(a <= b); + debug(9, "OP_LSTHANE: RESULT = %d", a <= b); + break; + case OP_PLUS: + b = pop(); + a = pop(); + push(a + b); + debug(9, "OP_PLUS: RESULT = %d", a + b); + break; + case OP_MINUS: + b = pop(); + a = pop(); + push(a - b); + debug(9, "OP_MINUS: RESULT = %d", a - b); + break; + case OP_TIMES: + b = pop(); + a = pop(); + push(a * b); + debug(9, "OP_TIMES: RESULT = %d", a * b); + break; + case OP_DIVIDE: + b = pop(); + a = pop(); + push(a / b); + debug(9, "OP_DIVIDE: RESULT = %d", a / b); + break; + case OP_ANDAND: + b = pop(); + a = pop(); + push(a && b); + debug(9, "OP_ANDAND: RESULT = %d", a && b); + break; + case OP_OROR: + b = pop(); + a = pop(); + push(a || b); + debug(9, "OP_OROR: RESULT = %d", a || b); + break; + + // Debugging opcodes, I think + + case CP_DEBUGON: + debug(9, "CP_DEBUGON"); + break; + case CP_DEBUGOFF: + debug(9, "CP_DEBUGOFF"); + break; + case CP_TEMP_TEXT_PROCESS: + Read32ip(parameter); + debug(9, "CP_TEMP_TEXT_PROCESS: %d", parameter); + break; + default: + error("Invalid script command %d", curCommand); + return 3; + } + } + + return 1; +} + +} // End of namespace Sword2 -- cgit v1.2.3