/* Copyright (C) 1994-2003 Revolution Software Ltd * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ */ #include "stdafx.h" #include "bs2/driver/driver96.h" #include "bs2/console.h" #include "bs2/interpreter.h" #include "bs2/logic.h" namespace Sword2 { // This file serves two purposes. It is compiled as part of the test functions // of Linc, and also as part of the game // I assume Linc was the name of some sort of development tool. Anyway, I've // removed the pieces of code that were labelled as INSIDE_LINC, because we // don't have it, and probably wouldn't have much use for it if we did. // The machine code table #define MAX_FN_NUMBER 117 #define OPCODE(x, y) { x, &Logic::y, #y } void Logic::setupOpcodes(void) { static const OpcodeEntry opcodes[MAX_FN_NUMBER + 1] = { /* 00 */ OPCODE(1, fnTestFunction), OPCODE(1, fnTestFlags), OPCODE(2, fnRegisterStartPoint), OPCODE(2, fnInitBackground), /* 04 */ OPCODE(1, fnSetSession), OPCODE(1, fnBackSprite), OPCODE(1, fnSortSprite), OPCODE(1, fnForeSprite), /* 08 */ OPCODE(1, fnRegisterMouse), OPCODE(3, fnAnim), OPCODE(2, fnRandom), OPCODE(1, fnPreLoad), /* 0C */ OPCODE(2, fnAddSubject), OPCODE(1, fnInteract), OPCODE(0, fnChoose), OPCODE(7, fnWalk), /* 10 */ OPCODE(5, fnWalkToAnim), OPCODE(6, fnTurn), OPCODE(5, fnStandAt), OPCODE(3, fnStand), /* 14 */ OPCODE(3, fnStandAfterAnim), OPCODE(2, fnPause), OPCODE(4, fnMegaTableAnim), OPCODE(1, fnAddMenuObject), /* 18 */ OPCODE(0, fnStartConversation), OPCODE(0, fnEndConversation), OPCODE(3, fnSetFrame), OPCODE(3, fnRandomPause), /* 1C */ OPCODE(3, fnRegisterFrame), OPCODE(1, fnNoSprite), OPCODE(2, fnSendSync), OPCODE(1, fnUpdatePlayerStats), /* 20 */ OPCODE(1, fnPassGraph), OPCODE(1, fnInitFloorMouse), OPCODE(1, fnPassMega), OPCODE(6, fnFaceXY), /* 24 */ OPCODE(1, fnEndSession), OPCODE(0, fnNoHuman), OPCODE(0, fnAddHuman), OPCODE(1, fnWeWait), /* 28 */ OPCODE(8, fnTheyDoWeWait), OPCODE(7, fnTheyDo), OPCODE(6, fnWalkToTalkToMega), OPCODE(0, fnFadeDown), /* 2C */ OPCODE(0, fnISpeak), OPCODE(0, fnTotalRestart), OPCODE(0, fnSetWalkGrid), OPCODE(5, fnSpeechProcess), /* 30 */ OPCODE(3, fnSetScaling), OPCODE(0, fnStartEvent), OPCODE(0, fnCheckEventWaiting), OPCODE(1, fnRequestSpeech), /* 34 */ OPCODE(1, fnGosub), OPCODE(3, fnTimedWait), OPCODE(5, fnPlayFx), OPCODE(1, fnStopFx), /* 38 */ OPCODE(2, fnPlayMusic), OPCODE(0, fnStopMusic), OPCODE(2, fnSetValue), OPCODE(1, fnNewScript), /* 3C */ OPCODE(0, fnGetSync), OPCODE(0, fnWaitSync), OPCODE(0, fnRegisterWalkGrid), OPCODE(4, fnReverseMegaTableAnim), /* 40 */ OPCODE(3, fnReverseAnim), OPCODE(0, fnAddToKillList), OPCODE(3, fnSetStandbyCoords), OPCODE(1, fnBackPar0Sprite), /* 44 */ OPCODE(1, fnBackPar1Sprite), OPCODE(1, fnForePar0Sprite), OPCODE(1, fnForePar1Sprite), OPCODE(1, fnSetPlayerActionEvent), /* 48 */ OPCODE(2, fnSetScrollCoordinate), OPCODE(3, fnStandAtAnim), OPCODE(1, fnSetScrollLeftMouse), OPCODE(1, fnSetScrollRightMouse), /* 4C */ OPCODE(1, fnColour), OPCODE(1, fnFlash), OPCODE(1, fnPreFetch), OPCODE(3, fnGetPlayerSaveData), /* 50 */ OPCODE(3, fnPassPlayerSaveData), OPCODE(2, fnSendEvent), OPCODE(1, fnAddWalkGrid), OPCODE(1, fnRemoveWalkGrid), /* 54 */ OPCODE(0, fnCheckForEvent), OPCODE(2, fnPauseForEvent), OPCODE(0, fnClearEvent), OPCODE(5, fnFaceMega), /* 58 */ OPCODE(2, fnPlaySequence), OPCODE(1, fnShadedSprite), OPCODE(1, fnUnshadedSprite), OPCODE(0, fnFadeUp), /* 60 */ OPCODE(1, fnDisplayMsg), OPCODE(0, fnSetObjectHeld), OPCODE(3, fnAddSequenceText), OPCODE(0, fnResetGlobals), /* 64 */ OPCODE(1, fnSetPalette), OPCODE(1, fnRegisterPointerText), OPCODE(1, fnFetchWait), OPCODE(1, fnRelease), /* 68 */ OPCODE(1, fnPrepareMusic), OPCODE(1, fnSoundFetch), OPCODE(1, fnPrepareMusic), // Again, apparently OPCODE(1, fnSmackerLeadIn), /* 6C */ OPCODE(1, fnSmackerLeadOut), OPCODE(0, fnStopAllFx), OPCODE(1, fnCheckPlayerActivity), OPCODE(0, fnResetPlayerActivityDelay), /* 70 */ OPCODE(0, fnCheckMusicPlaying), OPCODE(0, fnPlayCredits), OPCODE(0, fnSetScrollSpeedNormal), OPCODE(0, fnSetScrollSpeedSlow), /* 74 */ OPCODE(0, fnRemoveChooser), OPCODE(3, fnSetFxVolAndPan), OPCODE(3, fnSetFxVol), OPCODE(0, fnRestoreGame), /* 78 */ OPCODE(0, fnRefreshInventory), OPCODE(0, fnChangeShadows) }; _opcodes = opcodes; }; int32 Logic::executeOpcode(int i, int32 *params) { OpcodeProc op = _opcodes[i].proc; return (this->*op) (params); } // FIXME: The script engine assumes it can freely cast from pointer to in32 // and back again with no ill effects. As far as I know, there is absolutely // no guarantee that this will work. // // I also have a feeling the handling of a script's local variables may be // alignment-unsafe. #define CHECKSTACKPOINTER2 assert(stackPointer2 >= 0 && stackPointer2 < STACK_SIZE); #define PUSHONSTACK(x) { stack2[stackPointer2] = (x); stackPointer2++; CHECKSTACKPOINTER2 } #define POPOFFSTACK(x) { x = stack2[stackPointer2 - 1]; stackPointer2--; CHECKSTACKPOINTER2 } #define DOOPERATION(x) { stack2[stackPointer2 - 2] = (x); stackPointer2--; CHECKSTACKPOINTER2 } void Logic::setGlobalInterpreterVariables(int32 *vars) { _globals = vars; } int Logic::runScript(char *scriptData, char *objectData, uint32 *offset) { #define STACK_SIZE 10 _standardHeader *header = (_standardHeader *) scriptData; scriptData += sizeof(_standardHeader) + sizeof(_object_hub); // 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 int ip = 0; // Code pointer int curCommand,parameter, value; // Command and parameter variables int32 stack2[STACK_SIZE]; // The current stack int32 stackPointer2 = 0; // Position within stack int parameterReturnedFromMcodeFunction = 0; // Allow scripts to return things int savedStartOfMcode = 0; // For saving start of mcode commands int count; int retVal; int caseCount, foundCase; int scriptNumber, foundScript; const char *tempScrPtr; // Get the start of variables and start of code debug(5, "Enter interpreter data %x, object %x, offset %d", scriptData, objectData, *offset); // FIXME: 'scriptData' and 'variables' used to be const. However, // this code writes into 'variables' so it can not be const. char *variables = scriptData + sizeof(int32); const char *code = scriptData + (int32) READ_LE_UINT32(scriptData) + sizeof(int32); uint32 noScripts = (int32) READ_LE_UINT32(code); if (*offset < noScripts) { ip = READ_LE_UINT32((const int *) code + *offset + 1); debug(5, "Start script %d with offset %d", *offset, ip); } else { ip = *offset; debug(5, "Start script with offset %d", ip); } code += noScripts * sizeof(int32) + sizeof(int32); #ifdef DONTPROCESSSCRIPTCHECKSUM code += sizeof(int32) * 3; #else // Code should nop be pointing at an identifier and a checksum const int *checksumBlock = (const int *) code; code += sizeof(int32) * 3; if (READ_LE_UINT32(checksumBlock) != 12345678) { Con_fatal_error("Invalid script in object %s", header->name); return 0; } int codeLen = READ_LE_UINT32(checksumBlock + 1); int checksum = 0; for (count = 0; count < codeLen; count++) checksum += (unsigned char) code[count]; if (checksum != (int32) READ_LE_UINT32(checksumBlock + 2)) { Con_fatal_error("Checksum error in object %s", header->name); return 0; } #endif int runningScript = 1; while (runningScript) { curCommand = code[ip++]; switch (curCommand) { case CP_END_SCRIPT: // End the script debug(5, "End script",0); runningScript = 0; break; case CP_PUSH_LOCAL_VAR32: // Push the contents of a local variable Read16ip(parameter); debug(5, "Push local var %d (%d)", parameter, *(int32 *) (variables + parameter)); PUSHONSTACK(*(int32 *) (variables + parameter)); break; case CP_PUSH_GLOBAL_VAR32: // Push a global variable Read16ip(parameter); assert(_globals); debug(5, "Push global var %d (%d)", parameter, _globals[parameter]); PUSHONSTACK(_globals[parameter]); break; case CP_POP_LOCAL_VAR32: // Pop a value into a local word variable Read16ip(parameter); POPOFFSTACK(value); debug(5, "Pop %d into var %d", value, parameter); *((int32 *) (variables + parameter)) = value; break; case CP_CALL_MCODE: // Call an mcode routine Read16ip(parameter); assert(parameter <= MAX_FN_NUMBER); // amount to adjust stack by (no of parameters) Read8ip(value); debug(5, "Call mcode %d with stack = %x", parameter, stack2 + stackPointer2 - value); retVal = executeOpcode(parameter, stack2 + stackPointer2 - value); stackPointer2 -= value; CHECKSTACKPOINTER2 switch (retVal & 7) { case IR_STOP: // Quit out for a cycle *offset = 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 *offset = savedStartOfMcode; return 0; case IR_GOSUB: // that's really neat *offset = ip; return 2; default: assert(false); } parameterReturnedFromMcodeFunction = retVal >> 3; break; case CP_PUSH_LOCAL_ADDR: // push the address of a local variable Read16ip(parameter); debug(5, "Push address of local variable %d (%x)", parameter, (int32) (variables + parameter)); PUSHONSTACK((int32) (variables + parameter)); break; case CP_PUSH_INT32: // Push a long word value on to the stack Read32ip(parameter); debug(5, "Push int32 %d (%x)", parameter, parameter); PUSHONSTACK(parameter); break; case CP_SKIPONFALSE: // Skip if the value on the stack is false Read32ipLeaveip(parameter); POPOFFSTACK(value); debug(5, "Skip %d if %d is false", parameter, value); if (value) ip += sizeof(int32); else ip += parameter; break; case CP_SKIPALWAYS: // skip a block Read32ipLeaveip(parameter); debug(5, "Skip %d", parameter); ip += parameter; break; case CP_SWITCH: // 9 switch POPOFFSTACK(value); Read32ip(caseCount); // Search the cases foundCase = 0; for (count = 0; count < caseCount && !foundCase; count++) { if (value == (int32) READ_LE_UINT32(code + ip)) { // We have found the case, so lets // jump to it foundCase = 1; ip += READ_LE_UINT32(code + ip + sizeof(int32)); } else ip += sizeof(int32) * 2; } // If we found no matching case then use the // default if (!foundCase) ip += READ_LE_UINT32(code + ip); break; case CP_ADDNPOP_LOCAL_VAR32: Read16ip(parameter); POPOFFSTACK(value); *((int32 *) (variables + parameter)) += value; debug(5, "+= %d into local var %d->%d", value, parameter, *(int32 *) (variables + parameter)); break; case CP_SUBNPOP_LOCAL_VAR32: Read16ip(parameter); POPOFFSTACK(value); *((int32 *) (variables + parameter)) -= value; debug(5, "-= %d into local var %d->%d", value, parameter, *(int32 *) (variables + parameter)); break; case CP_SKIPONTRUE: // Skip if the value on the stack is TRUE Read32ipLeaveip(parameter); POPOFFSTACK(value); debug(5, "Skip %d if %d is false", parameter, value); if (!value) ip += sizeof(int32); else ip += parameter; break; case CP_POP_GLOBAL_VAR32: // Pop a global variable Read16ip(parameter); POPOFFSTACK(value); debug(5, "Pop %d into global var %d", value, parameter); #ifdef TRACEGLOBALVARIABLESET TRACEGLOBALVARIABLESET(parameter, value); #endif _globals[parameter] = value; break; case CP_ADDNPOP_GLOBAL_VAR32: // Add and pop a global variable Read16ip(parameter); // parameter = *((int16_TYPE *) (code + ip)); // ip += 2; POPOFFSTACK(value); _globals[parameter] += value; debug(5, "+= %d into global var %d->%d", value, parameter, *(int32 *) (variables + parameter)); break; case CP_SUBNPOP_GLOBAL_VAR32: // Sub and pop a global variable Read16ip(parameter); POPOFFSTACK(value); _globals[parameter] -= value; debug(5, "-= %d into global var %d->%d", value, parameter, *(int32 *) (variables + parameter)); break; case CP_DEBUGON: // Turn debugging on _debugFlag = true; break; case CP_DEBUGOFF: // Turn debugging on _debugFlag = false; break; case CP_QUIT: // Quit out for a cycle *offset = ip; return 0; case CP_TERMINATE: // Quit out immediately without affecting the offset // pointer return 3; // Operators case OP_ISEQUAL: // '==' debug(5, "%d == %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] == stack2[stackPointer2 - 1]); DOOPERATION (stack2[stackPointer2 - 2] == stack2[stackPointer2 - 1]); break; case OP_PLUS: // '+' debug(5, "%d + %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] + stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] + stack2[stackPointer2 - 1]); break; case OP_MINUS: // '-' debug(5, "%d - %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] - stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] - stack2[stackPointer2 - 1]); break; case OP_TIMES: // '*' debug(5, "%d * %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] * stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] * stack2[stackPointer2 - 1]); break; case OP_DIVIDE: // '/' debug(5, "%d / %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] / stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] / stack2[stackPointer2 - 1]); break; case OP_NOTEQUAL: // '!=' debug(5, "%d != %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] != stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] != stack2[stackPointer2 - 1]); break; case OP_ANDAND: // '&&' debug(5, "%d != %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] && stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] && stack2[stackPointer2 - 1]); break; case OP_GTTHAN: // '>' debug(5, "%d > %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] > stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] > stack2[stackPointer2 - 1]); break; case OP_LSTHAN: // '<' debug(5, "%d < %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] < stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] < stack2[stackPointer2 - 1]); 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); ip += READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4); break; case CP_TEMP_TEXT_PROCESS: // Process a text line // This was apparently used in Linc Read32ip(parameter); debug(5, "Process text id %d", parameter); 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; break; case CP_RESTART_SCRIPT: // Start the script again // Do a ip search to find the script we are running tempScrPtr = scriptData + READ_LE_UINT32(scriptData) + sizeof(int32); scriptNumber = 0; foundScript = 0; for (count = 1; count < (int) noScripts && !foundScript; count++) { if (ip < ((const int *) tempScrPtr)[count + 1]) { scriptNumber = count - 1; foundScript = 1; } } if (!foundScript) scriptNumber = count - 1; // So we know what script we are running, lets restart // it ip = ((const int *) tempScrPtr)[scriptNumber + 1]; break; case CP_PUSH_STRING: // Push the address of a string on to the stack // Get the string size Read8ip(parameter); // ip points to the string PUSHONSTACK((int32) (code + ip)); ip += (parameter + 1); break; case CP_PUSH_DEREFERENCED_STRUCTURE: // Push the address of a dereferenced structure Read32ip(parameter); debug(5, "Push address of far variable (%x)", (int32) (variables + parameter)); PUSHONSTACK((int32) (objectData + sizeof(int32) + sizeof(_standardHeader) + sizeof(_object_hub) + parameter)); break; case OP_GTTHANE: // '>=' debug(5, "%d > %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] >= stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] >= stack2[stackPointer2 - 1]); break; case OP_LSTHANE: // '<=' debug(5, "%d < %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] <= stack2[stackPointer2 - 1]); DOOPERATION(stack2[stackPointer2 - 2] <= stack2[stackPointer2 - 1]); break; case OP_OROR: // '||' debug(5, "%d || %d -> %d", stack2[stackPointer2 - 2], stack2[stackPointer2 - 1], stack2[stackPointer2 - 2] || stack2[stackPointer2 - 1]); DOOPERATION (stack2[stackPointer2 - 2] || stack2[stackPointer2 - 1]); break; default: Con_fatal_error("Interpreter error: Invalid token %d", curCommand); return 3; } } return 1; } } // End of namespace Sword2