aboutsummaryrefslogtreecommitdiff
path: root/engines/tinsel/pcode.cpp
diff options
context:
space:
mode:
authorMax Horn2008-07-23 09:02:47 +0000
committerMax Horn2008-07-23 09:02:47 +0000
commitc441c5261ff3e6ad114c0384ef818b942655f3a6 (patch)
tree6563ef6632ec89f7db2bbb62ad42aea680b507e6 /engines/tinsel/pcode.cpp
parente7c1a88dd3bf024e87ef62ff0c851b6a551ae2a5 (diff)
downloadscummvm-rg350-c441c5261ff3e6ad114c0384ef818b942655f3a6.tar.gz
scummvm-rg350-c441c5261ff3e6ad114c0384ef818b942655f3a6.tar.bz2
scummvm-rg350-c441c5261ff3e6ad114c0384ef818b942655f3a6.zip
Added Tinsel engine to main repos (no news item for it ON PURPOSE)
svn-id: r33230
Diffstat (limited to 'engines/tinsel/pcode.cpp')
-rw-r--r--engines/tinsel/pcode.cpp597
1 files changed, 597 insertions, 0 deletions
diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp
new file mode 100644
index 0000000000..22b7ca8275
--- /dev/null
+++ b/engines/tinsel/pcode.cpp
@@ -0,0 +1,597 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Virtual processor.
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // 'POINTED' etc.
+#include "tinsel/handle.h" // LockMem()
+#include "tinsel/inventory.h" // for inventory id's
+#include "tinsel/pcode.h" // opcodes etc.
+#include "tinsel/scn.h" // FindChunk()
+#include "tinsel/serializer.h"
+#include "tinsel/tinlib.h" // Library routines
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const PINT_CONTEXT pic, RESUME_STATE *pResumeState);
+
+//----------------- LOCAL DEFINES --------------------
+
+/** list of all opcodes */
+enum OPCODE {
+ OP_HALT = 0, //!< end of program
+ OP_IMM = 1, //!< loads signed immediate onto stack
+ OP_ZERO = 2, //!< loads zero onto stack
+ OP_ONE = 3, //!< loads one onto stack
+ OP_MINUSONE = 4, //!< loads minus one onto stack
+ OP_STR = 5, //!< loads string offset onto stack
+ OP_FILM = 6, //!< loads film offset onto stack
+ OP_FONT = 7, //!< loads font offset onto stack
+ OP_PAL = 8, //!< loads palette offset onto stack
+ OP_LOAD = 9, //!< loads local variable onto stack
+ OP_GLOAD = 10, //!< loads global variable onto stack - long offset to variable
+ OP_STORE = 11, //!< pops stack and stores in local variable - long offset to variable
+ OP_GSTORE = 12, //!< pops stack and stores in global variable - long offset to variable
+ OP_CALL = 13, //!< procedure call
+ OP_LIBCALL = 14, //!< library procedure call - long offset to procedure
+ OP_RET = 15, //!< procedure return
+ OP_ALLOC = 16, //!< allocate storage on stack
+ OP_JUMP = 17, //!< unconditional jump - signed word offset
+ OP_JMPFALSE = 18, //!< conditional jump - signed word offset
+ OP_JMPTRUE = 19, //!< conditional jump - signed word offset
+ OP_EQUAL = 20, //!< tests top two items on stack for equality
+ OP_LESS, //!< tests top two items on stack
+ OP_LEQUAL, //!< tests top two items on stack
+ OP_NEQUAL, //!< tests top two items on stack
+ OP_GEQUAL, //!< tests top two items on stack
+ OP_GREAT = 25, //!< tests top two items on stack
+ OP_PLUS, //!< adds top two items on stack and replaces with result
+ OP_MINUS, //!< subs top two items on stack and replaces with result
+ OP_LOR, //!< logical or of top two items on stack and replaces with result
+ OP_MULT, //!< multiplies top two items on stack and replaces with result
+ OP_DIV = 30, //!< divides top two items on stack and replaces with result
+ OP_MOD, //!< divides top two items on stack and replaces with modulus
+ OP_AND, //!< bitwise ands top two items on stack and replaces with result
+ OP_OR, //!< bitwise ors top two items on stack and replaces with result
+ OP_EOR, //!< bitwise exclusive ors top two items on stack and replaces with result
+ OP_LAND = 35, //!< logical ands top two items on stack and replaces with result
+ OP_NOT, //!< logical nots top item on stack
+ OP_COMP, //!< complements top item on stack
+ OP_NEG, //!< negates top item on stack
+ OP_DUP, //!< duplicates top item on stack
+ OP_ESCON = 40, //!< start of escapable sequence
+ OP_ESCOFF = 41, //!< end of escapable sequence
+ OP_CIMM, //!< loads signed immediate onto stack (special to case statements)
+ OP_CDFILM //!< loads film offset onto stack but not in current scene
+};
+
+// modifiers for the above opcodes
+#define OPSIZE8 0x40 //!< when this bit is set - the operand size is 8 bits
+#define OPSIZE16 0x80 //!< when this bit is set - the operand size is 16 bits
+
+#define OPMASK 0x3F //!< mask to isolate the opcode
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int32 *pGlobals = 0; // global vars
+
+static int numGlobals = 0; // How many global variables to save/restore
+
+static PINT_CONTEXT icList = 0;
+
+/**
+ * Keeps the code array pointer up to date.
+ */
+void LockCode(PINT_CONTEXT ic) {
+ if (ic->GSort == GS_MASTER)
+ ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE);
+ else
+ ic->code = (byte *)LockMem(ic->hCode);
+}
+
+/**
+ * Find a free interpret context and allocate it to the calling process.
+ */
+static PINT_CONTEXT AllocateInterpretContext(GSORT gsort) {
+ PINT_CONTEXT pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort == GS_NONE) {
+ pic->pProc = CurrentProcess();
+ pic->GSort = gsort;
+ return pic;
+ }
+#ifdef DEBUG
+ else {
+ if (pic->pProc == CurrentProcess())
+ error("Found unreleased interpret context");
+ }
+#endif
+ }
+
+ error("Out of interpret contexts");
+}
+
+/**
+ * Normal release of an interpret context.
+ * Called from the end of Interpret().
+ */
+static void FreeInterpretContextPi(PINT_CONTEXT pic) {
+ pic->GSort = GS_NONE;
+}
+
+/**
+ * Free interpret context owned by a dying process.
+ * Ensures that interpret contexts don't get lost when an Interpret()
+ * call doesn't complete.
+ */
+void FreeInterpretContextPr(PROCESS *pProc) {
+ PINT_CONTEXT pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort != GS_NONE && pic->pProc == pProc) {
+ pic->GSort = GS_NONE;
+ break;
+ }
+ }
+}
+
+/**
+ * Free all interpret contexts except for the master script's
+ */
+void FreeMostInterpretContexts(void) {
+ PINT_CONTEXT pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort != GS_MASTER) {
+ pic->GSort = GS_NONE;
+ }
+ }
+}
+
+/**
+ * Free the master script's interpret context.
+ */
+void FreeMasterInterpretContext(void) {
+ PINT_CONTEXT pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort == GS_MASTER) {
+ pic->GSort = GS_NONE;
+ return;
+ }
+ }
+}
+
+/**
+ * Allocate and initialise an interpret context.
+ * Called from a process prior to Interpret().
+ * @param gsort which sort of code
+ * @param hCode Handle to code to execute
+ * @param event Causal event
+ * @param hpoly Associated polygon (if any)
+ * @param actorId Associated actor (if any)
+ * @param pinvo Associated inventory object
+ */
+PINT_CONTEXT InitInterpretContext(GSORT gsort, SCNHANDLE hCode, USER_EVENT event,
+ HPOLYGON hpoly, int actorid, PINV_OBJECT pinvo) {
+ PINT_CONTEXT ic;
+
+ ic = AllocateInterpretContext(gsort);
+
+ // Previously parameters to Interpret()
+ ic->hCode = hCode;
+ LockCode(ic);
+ ic->event = event;
+ ic->hpoly = hpoly;
+ ic->actorid = actorid;
+ ic->pinvo = pinvo;
+
+ // Previously local variables in Interpret()
+ ic->bHalt = false; // set to exit interpeter
+ ic->escOn = false;
+ ic->myescEvent = 0; // only initialised to prevent compiler warning!
+ ic->sp = 0;
+ ic->bp = ic->sp + 1;
+ ic->ip = 0; // start of code
+
+ ic->resumeState = RES_NOT;
+
+ return ic;
+}
+
+/**
+ * Allocate and initialise an interpret context with restored data.
+ */
+PINT_CONTEXT RestoreInterpretContext(PINT_CONTEXT ric) {
+ PINT_CONTEXT ic;
+
+ ic = AllocateInterpretContext(GS_NONE); // Sort will soon be overridden
+
+ memcpy(ic, ric, sizeof(INT_CONTEXT));
+ ic->pProc = CurrentProcess();
+ ic->resumeState = RES_1;
+
+ LockCode(ic);
+
+ return ic;
+}
+
+/**
+ * Allocates enough RAM to hold the global Glitter variables.
+ */
+void RegisterGlobals(int num) {
+ if (pGlobals == NULL) {
+ numGlobals = num;
+
+ // Allocate RAM for pGlobals and make sure it's allocated
+ pGlobals = (int32 *)calloc(numGlobals, sizeof(int32));
+ if (pGlobals == NULL) {
+ error("Cannot allocate memory for global data");
+ }
+
+ // Allocate RAM for interpret contexts and make sure it's allocated
+ icList = (PINT_CONTEXT)calloc(MAX_INTERPRET, sizeof(INT_CONTEXT));
+ if (icList == NULL) {
+ error("Cannot allocate memory for interpret contexts");
+ }
+
+ SetResourceCallback(FreeInterpretContextPr);
+ } else {
+ // Check size is still the same
+ assert(numGlobals == num);
+
+ memset(pGlobals, 0, numGlobals * sizeof(int32));
+ memset(icList, 0, MAX_INTERPRET * sizeof(INT_CONTEXT));
+ }
+}
+
+void FreeGlobals(void) {
+ if (pGlobals) {
+ free(pGlobals);
+ pGlobals = NULL;
+ }
+
+ if (icList) {
+ free(icList);
+ icList = NULL;
+ }
+}
+
+/**
+ * (Un)serialize the global data for save/restore game.
+ */
+void syncGlobInfo(Serializer &s) {
+ for (int i = 0; i < numGlobals; i++) {
+ s.syncAsSint32LE(pGlobals[i]);
+ }
+}
+
+/**
+ * (Un)serialize an interpreter context for save/restore game.
+ */
+void INT_CONTEXT::syncWithSerializer(Serializer &s) {
+ if (s.isLoading()) {
+ // Null out the pointer fields
+ pProc = NULL;
+ code = NULL;
+ pinvo = NULL;
+ }
+ // Write out used fields
+ s.syncAsUint32LE(GSort);
+ s.syncAsUint32LE(hCode);
+ s.syncAsUint32LE(event);
+ s.syncAsSint32LE(hpoly);
+ s.syncAsSint32LE(actorid);
+
+ for (int i = 0; i < PCODE_STACK_SIZE; ++i)
+ s.syncAsSint32LE(stack[i]);
+
+ s.syncAsSint32LE(sp);
+ s.syncAsSint32LE(bp);
+ s.syncAsSint32LE(ip);
+ s.syncAsUint32LE(bHalt);
+ s.syncAsUint32LE(escOn);
+ s.syncAsSint32LE(myescEvent);
+}
+
+/**
+ * Return pointer to and size of global data for save/restore game.
+ */
+void SaveInterpretContexts(PINT_CONTEXT sICInfo) {
+ memcpy(sICInfo, icList, MAX_INTERPRET * sizeof(INT_CONTEXT));
+}
+
+/**
+ * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code
+ * stream and advance the instruction pointer accordingly.
+ */
+static int32 Fetch(byte opcode, byte *code, int &ip) {
+ int32 tmp;
+ if (opcode & OPSIZE8) {
+ // Fetch and sign extend a 8 bit value to 32 bits.
+ tmp = *(int8 *)(code + ip);
+ ip += 1;
+ } else if (opcode & OPSIZE16) {
+ // Fetch and sign extend a 16 bit value to 32 bits.
+ tmp = (int16)READ_LE_UINT16(code + ip);
+ ip += 2;
+ } else {
+ // Fetch a 32 bit value.
+ tmp = (int32)READ_LE_UINT32(code + ip);
+ ip += 4;
+ }
+ return tmp;
+}
+
+/**
+ * Interprets the PCODE instructions in the code array.
+ */
+void Interpret(CORO_PARAM, PINT_CONTEXT ic) {
+ do {
+ int tmp, tmp2;
+ int ip = ic->ip;
+ byte opcode = ic->code[ip++];
+ debug(7, " Opcode %d (-> %d)", opcode, opcode & OPMASK);
+ switch (opcode & OPMASK) {
+ case OP_HALT: // end of program
+
+ ic->bHalt = true;
+ break;
+
+ case OP_IMM: // loads immediate data onto stack
+ case OP_STR: // loads string handle onto stack
+ case OP_FILM: // loads film handle onto stack
+ case OP_CDFILM: // loads film handle onto stack
+ case OP_FONT: // loads font handle onto stack
+ case OP_PAL: // loads palette handle onto stack
+
+ ic->stack[++ic->sp] = Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_ZERO: // loads zero onto stack
+ ic->stack[++ic->sp] = 0;
+ break;
+
+ case OP_ONE: // loads one onto stack
+ ic->stack[++ic->sp] = 1;
+ break;
+
+ case OP_MINUSONE: // loads minus one onto stack
+ ic->stack[++ic->sp] = -1;
+ break;
+
+ case OP_LOAD: // loads local variable onto stack
+
+ ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, ip)];
+ break;
+
+ case OP_GLOAD: // loads global variable onto stack
+
+ tmp = Fetch(opcode, ic->code, ip);
+ assert(0 <= tmp && tmp < numGlobals);
+ ic->stack[++ic->sp] = pGlobals[tmp];
+ break;
+
+ case OP_STORE: // pops stack and stores in local variable
+
+ ic->stack[ic->bp + Fetch(opcode, ic->code, ip)] = ic->stack[ic->sp--];
+ break;
+
+ case OP_GSTORE: // pops stack and stores in global variable
+
+ tmp = Fetch(opcode, ic->code, ip);
+ assert(0 <= tmp && tmp < numGlobals);
+ pGlobals[tmp] = ic->stack[ic->sp--];
+ break;
+
+ case OP_CALL: // procedure call
+
+ tmp = Fetch(opcode, ic->code, ip);
+ //assert(0 <= tmp && tmp < codeSize); // TODO: Verify jumps are not out of bounds
+ ic->stack[ic->sp + 1] = 0; // static link
+ ic->stack[ic->sp + 2] = ic->bp; // dynamic link
+ ic->stack[ic->sp + 3] = ip; // return address
+ ic->bp = ic->sp + 1; // set new base pointer
+ ip = tmp; // set ip to procedure address
+ break;
+
+ case OP_LIBCALL: // library procedure or function call
+
+ tmp = Fetch(opcode, ic->code, ip);
+ // NOTE: Interpret() itself is not using the coroutine facilities,
+ // but still accepts a CORO_PARAM, so from the outside it looks
+ // like a coroutine. In fact it may still acts as a kind of "proxy"
+ // for some underlying coroutine. To enable this, we just pass on
+ // 'coroParam' to CallLibraryRoutine(). If we then detect that
+ // coroParam was set to a non-zero value, this means that some
+ // coroutine code did run at some point, and we are now supposed
+ // to sleep or die -- hence, we 'return' if coroParam != 0.
+ //
+ // This works because Interpret() is fully re-entrant: If we return
+ // now and are later called again, then we will end up in the very
+ // same spot (i.e. here).
+ //
+ // The reasons we do it this way, instead of turning Interpret into
+ // a 'proper' coroutine are (1) we avoid implementation problems
+ // (CORO_INVOKE involves adding 'case' statements, but Interpret
+ // already has a huge switch/case, so that would not work out of the
+ // box), (2) we incurr less overhead, (3) it's easier to debug,
+ // (4) it's simply cool ;).
+ tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState);
+ if (coroParam)
+ return;
+ ic->sp += tmp2;
+ LockCode(ic);
+ break;
+
+ case OP_RET: // procedure return
+
+ ic->sp = ic->bp - 1; // restore stack
+ ip = ic->stack[ic->sp + 3]; // return address
+ ic->bp = ic->stack[ic->sp + 2]; // restore previous base pointer
+ break;
+
+ case OP_ALLOC: // allocate storage on stack
+
+ ic->sp += Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_JUMP: // unconditional jump
+
+ ip = Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_JMPFALSE: // conditional jump
+
+ tmp = Fetch(opcode, ic->code, ip);
+ if (ic->stack[ic->sp--] == 0) {
+ // condition satisfied - do the jump
+ ip = tmp;
+ }
+ break;
+
+ case OP_JMPTRUE: // conditional jump
+
+ tmp = Fetch(opcode, ic->code, ip);
+ if (ic->stack[ic->sp--] != 0) {
+ // condition satisfied - do the jump
+ ip = tmp;
+ }
+ break;
+
+ case OP_EQUAL: // tests top two items on stack for equality
+ case OP_LESS: // tests top two items on stack
+ case OP_LEQUAL: // tests top two items on stack
+ case OP_NEQUAL: // tests top two items on stack
+ case OP_GEQUAL: // tests top two items on stack
+ case OP_GREAT: // tests top two items on stack
+ case OP_LOR: // logical or of top two items on stack and replaces with result
+ case OP_LAND: // logical ands top two items on stack and replaces with result
+
+ // pop one operand
+ ic->sp--;
+ assert(ic->sp >= 0);
+ tmp = ic->stack[ic->sp];
+ tmp2 = ic->stack[ic->sp + 1];
+
+ // replace other operand with result of operation
+ switch (opcode) {
+ case OP_EQUAL: tmp = (tmp == tmp2); break;
+ case OP_LESS: tmp = (tmp < tmp2); break;
+ case OP_LEQUAL: tmp = (tmp <= tmp2); break;
+ case OP_NEQUAL: tmp = (tmp != tmp2); break;
+ case OP_GEQUAL: tmp = (tmp >= tmp2); break;
+ case OP_GREAT: tmp = (tmp > tmp2); break;
+
+ case OP_LOR: tmp = (tmp || tmp2); break;
+ case OP_LAND: tmp = (tmp && tmp2); break;
+ }
+
+ ic->stack[ic->sp] = tmp;
+ break;
+
+ case OP_PLUS: // adds top two items on stack and replaces with result
+ case OP_MINUS: // subs top two items on stack and replaces with result
+ case OP_MULT: // multiplies top two items on stack and replaces with result
+ case OP_DIV: // divides top two items on stack and replaces with result
+ case OP_MOD: // divides top two items on stack and replaces with modulus
+ case OP_AND: // bitwise ands top two items on stack and replaces with result
+ case OP_OR: // bitwise ors top two items on stack and replaces with result
+ case OP_EOR: // bitwise exclusive ors top two items on stack and replaces with result
+
+ // pop one operand
+ ic->sp--;
+ assert(ic->sp >= 0);
+ tmp = ic->stack[ic->sp];
+ tmp2 = ic->stack[ic->sp + 1];
+
+ // replace other operand with result of operation
+ switch (opcode) {
+ case OP_PLUS: tmp += tmp2; break;
+ case OP_MINUS: tmp -= tmp2; break;
+ case OP_MULT: tmp *= tmp2; break;
+ case OP_DIV: tmp /= tmp2; break;
+ case OP_MOD: tmp %= tmp2; break;
+ case OP_AND: tmp &= tmp2; break;
+ case OP_OR: tmp |= tmp2; break;
+ case OP_EOR: tmp ^= tmp2; break;
+ }
+ ic->stack[ic->sp] = tmp;
+ break;
+
+ case OP_NOT: // logical nots top item on stack
+
+ ic->stack[ic->sp] = !ic->stack[ic->sp];
+ break;
+
+ case OP_COMP: // complements top item on stack
+ ic->stack[ic->sp] = ~ic->stack[ic->sp];
+ break;
+
+ case OP_NEG: // negates top item on stack
+ ic->stack[ic->sp] = -ic->stack[ic->sp];
+ break;
+
+ case OP_DUP: // duplicates top item on stack
+ ic->stack[ic->sp + 1] = ic->stack[ic->sp];
+ ic->sp++;
+ break;
+
+ case OP_ESCON:
+ ic->escOn = true;
+ ic->myescEvent = GetEscEvents();
+ break;
+
+ case OP_ESCOFF:
+ ic->escOn = false;
+ break;
+
+ default:
+ error("Interpret() - Unknown opcode");
+ }
+
+ // check for stack under-overflow
+ assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE);
+ ic->ip = ip;
+ } while (!ic->bHalt);
+
+ // make sure stack is unwound
+ assert(ic->sp == 0);
+
+ FreeInterpretContextPi(ic);
+}
+
+} // end of namespace Tinsel