Files
scummvm/sword2/interpreter.cpp
T
Torbjörn Andersson 3d012651fd The script engine frequently needs to pass pointers to various structures
etc. to the different opcodes. Until now it has done so by casting the
pointer to an int32 (opcode parameters are represented as arrays of int32)
and then the opcode function casts it back to whatever pointer it needs.

At least in C there is no guarantee that a pointer can be represented as an
integer type (though apparently C99 may define such a type), so this has
struck me as unsafe ever since I first noticed it.

However, since all such pointers appear to point to the memory block owned
by the memory manager, we can easily convert them to integers by treating
them as offsets into the memory block. So that's what I have done. I hope I
caught all the occurences in the opcode functions, or we're going to have
some pretty interesting regressions on our hands...

svn-id: r11241
2003-11-10 07:52:15 +00:00

635 lines
18 KiB
C++

/* 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 "sword2/driver/driver96.h"
#include "sword2/console.h"
#include "sword2/interpreter.h"
#include "sword2/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: Is the handling of script local variables really alignment-safe?
#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) {
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)) {
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 local 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, memory->ptrToInt((const uint8 *) (variables + parameter)));
PUSHONSTACK(memory->ptrToInt((uint8 *) (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);
POPOFFSTACK(value);
_globals[parameter] += value;
debug(5, "+= %d into global var %d->%d", value, parameter, _globals[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, _globals[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(memory->ptrToInt((const uint8 *) (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)", memory->ptrToInt((const uint8 *) (objectData + sizeof(int32) + sizeof(_standardHeader) + sizeof(_object_hub) + parameter)));
PUSHONSTACK(memory->ptrToInt((const uint8 *) (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:
error("Interpreter error: Invalid token %d", curCommand);
return 3;
}
}
return 1;
}
} // End of namespace Sword2