/* 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$ * */ #include "common/debug.h" #include "common/debug-channels.h" #include "common/stack.h" #include "common/config-manager.h" #include "sci/sci.h" #include "sci/console.h" #include "sci/resource.h" #include "sci/engine/features.h" #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/script.h" #include "sci/engine/seg_manager.h" #include "sci/engine/selector.h" // for SELECTOR #include "sci/engine/gc.h" #include "sci/engine/workarounds.h" namespace Sci { const reg_t NULL_REG = {0, 0}; const reg_t SIGNAL_REG = {0, SIGNAL_OFFSET}; const reg_t TRUE_REG = {0, 1}; //#define VM_DEBUG_SEND #define SCI_XS_CALLEE_LOCALS ((SegmentId)-1) /** * Adds an entry to the top of the execution stack. * * @param[in] s The state with which to execute * @param[in] pc The initial program counter * @param[in] sp The initial stack pointer * @param[in] objp Pointer to the beginning of the current object * @param[in] argc Number of parameters to call with * @param[in] argp Heap pointer to the first parameter * @param[in] selector The selector by which it was called or * NULL_SELECTOR if n.a. For debugging. * @param[in] exportId The exportId by which it was called or * -1 if n.a. For debugging. * @param[in] sendp Pointer to the object which the message was * sent to. Equal to objp for anything but super. * @param[in] origin Number of the execution stack element this * entry was created by (usually the current TOS * number, except for multiple sends). * @param[in] local_segment The segment to use for local variables, * or SCI_XS_CALLEE_LOCALS to use obj's segment. * @return A pointer to the new exec stack TOS entry */ static ExecStack *add_exec_stack_entry(Common::List &execStack, reg_t pc, StackPtr sp, reg_t objp, int argc, StackPtr argp, Selector selector, int exportId, int localCallOffset, reg_t sendp, int origin, SegmentId local_segment); /** * Adds one varselector access to the execution stack. * This function is called from send_selector only. * @param[in] s The EngineState to use * @param[in] objp Pointer to the object owning the selector * @param[in] argc 1 for writing, 0 for reading * @param[in] argp Pointer to the address of the data to write -2 * @param[in] selector Selector name * @param[in] address Heap address of the selector * @param[in] origin Stack frame which the access originated from * @return Pointer to the new exec-TOS element */ static ExecStack *add_exec_stack_varselector(Common::List &execStack, reg_t objp, int argc, StackPtr argp, Selector selector, const ObjVarRef& address, int origin); // validation functionality static reg_t &validate_property(Object *obj, int index) { // A static dummy reg_t, which we return if obj or index turn out to be // invalid. Note that we cannot just return NULL_REG, because client code // may modify the value of the returned reg_t. static reg_t dummyReg = NULL_REG; // If this occurs, it means there's probably something wrong with the garbage // collector, so don't hide it with fake return values if (!obj) error("validate_property: Sending to disposed object"); if (index < 0 || (uint)index >= obj->getVarCount()) { // This is same way sierra does it and there are some games, that contain such scripts like // iceman script 998 (fred::canBeHere, executed right at the start) debugC(2, kDebugLevelVM, "[VM] Invalid property #%d (out of [0..%d]) requested!", index, obj->getVarCount()); return dummyReg; } return obj->getVariableRef(index); } static StackPtr validate_stack_addr(EngineState *s, StackPtr sp) { if (sp >= s->stack_base && sp < s->stack_top) return sp; error("[VM] Stack index %d out of valid range [%d..%d]", (int)(sp - s->stack_base), 0, (int)(s->stack_top - s->stack_base - 1)); return 0; } static int validate_arithmetic(reg_t reg) { if (reg.segment) { // The results of this are likely unpredictable... It most likely means that a kernel function is returning something wrong. // If such an error occurs, we usually need to find the last kernel function called and check its return value. error("[VM] Attempt to read arithmetic value from non-zero segment [%04x]. Address: %04x:%04x", reg.segment, PRINT_REG(reg)); return 0; } return reg.offset; } static int signed_validate_arithmetic(reg_t reg) { return (int16)validate_arithmetic(reg); } static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, int index) { const char *names[4] = {"global", "local", "temp", "param"}; if (index < 0 || index >= max) { Common::String txt = Common::String::printf( "[VM] Attempt to use invalid %s variable %04x ", names[type], index); if (max == 0) txt += "(variable type invalid)"; else txt += Common::String::printf("(out of range [%d..%d])", 0, max - 1); if (type == VAR_PARAM || type == VAR_TEMP) { int total_offset = r - stack_base; if (total_offset < 0 || total_offset >= VM_STACK_SIZE) { // Fatal, as the game is trying to do an OOB access error("%s. [VM] Access would be outside even of the stack (%d); access denied", txt.c_str(), total_offset); return false; } else { debugC(2, kDebugLevelVM, "%s", txt.c_str()); debugC(2, kDebugLevelVM, "[VM] Access within stack boundaries; access granted."); return true; } } return false; } return true; } static bool validate_unsignedInteger(reg_t reg, uint16 &integer) { if (reg.segment) return false; integer = reg.offset; return true; } static bool validate_signedInteger(reg_t reg, int16 &integer) { if (reg.segment) return false; integer = (int16)reg.offset; return true; } extern const char *opcodeNames[]; // from scriptdebug.cpp static reg_t arithmetic_lookForWorkaround(const byte opcode, const SciWorkaroundEntry *workaroundList, reg_t value1, reg_t value2) { SciTrackOriginReply originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, workaroundList, &originReply); if (solution.type == WORKAROUND_NONE) error("%s on non-integer (%04x:%04x, %04x:%04x) from method %s::%s (script %d, room %d, localCall %x)", opcodeNames[opcode], PRINT_REG(value1), PRINT_REG(value2), originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, g_sci->getEngineState()->currentRoomNumber(), originReply.localCallOffset); assert(solution.type == WORKAROUND_FAKE); return make_reg(0, solution.value); } static reg_t validate_read_var(reg_t *r, reg_t *stack_base, int type, int max, int index, reg_t default_value) { if (validate_variable(r, stack_base, type, max, index)) { if (r[index].segment == 0xffff) { switch (type) { case VAR_TEMP: { // Uninitialized read on a temp // We need to find correct replacements for each situation manually SciTrackOriginReply originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply); if (solution.type == WORKAROUND_NONE) { #ifdef RELEASE_BUILD // If we are running an official ScummVM release -> fake 0 in unknown cases warning("Uninitialized read for temp %d from method %s::%s (script %d, room %d, localCall %x)", index, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, g_sci->getEngineState()->currentRoomNumber(), originReply.localCallOffset); r[index] = NULL_REG; break; #else error("Uninitialized read for temp %d from method %s::%s (script %d, room %d, localCall %x)", index, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, g_sci->getEngineState()->currentRoomNumber(), originReply.localCallOffset); #endif } assert(solution.type == WORKAROUND_FAKE); r[index] = make_reg(0, solution.value); break; } case VAR_PARAM: // Out-of-bounds read for a parameter that goes onto stack and hits an uninitialized temp // We return 0 currently in that case debugC(2, kDebugLevelVM, "[VM] Read for a parameter goes out-of-bounds, onto the stack and gets uninitialized temp"); return NULL_REG; default: break; } } return r[index]; } else return default_value; } static void validate_write_var(reg_t *r, reg_t *stack_base, int type, int max, int index, reg_t value, SegManager *segMan, Kernel *kernel) { if (validate_variable(r, stack_base, type, max, index)) { // WORKAROUND: This code is needed to work around a probable script bug, or a // limitation of the original SCI engine, which can be observed in LSL5. // // In some games, ego walks via the "Grooper" object, in particular its "stopGroop" // child. In LSL5, during the game, ego is swapped from Larry to Patti. When this // happens in the original interpreter, the new actor is loaded in the same memory // location as the old one, therefore the client variable in the stopGroop object // points to the new actor. This is probably why the reference of the stopGroop // object is never updated (which is why I mentioned that this is either a script // bug or some kind of limitation). // // In our implementation, each new object is loaded in a different memory location, // and we can't overwrite the old one. This means that in our implementation, // whenever ego is changed, we need to update the "client" variable of the // stopGroop object, which points to ego, to the new ego object. If this is not // done, ego's movement will not be updated properly, so the result is // unpredictable (for example in LSL5, Patti spins around instead of walking). if (index == 0 && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) { // global 0 is ego reg_t stopGroopPos = segMan->findObjectByName("stopGroop"); if (!stopGroopPos.isNull()) { // does the game have a stopGroop object? // Find the "client" member variable of the stopGroop object, and update it ObjVarRef varp; if (lookupSelector(segMan, stopGroopPos, SELECTOR(client), &varp, NULL) == kSelectorVariable) { reg_t *clientVar = varp.getPointer(segMan); *clientVar = value; } } } // If we are writing an uninitialized value into a temp, we remove the uninitialized segment // this happens at least in sq1/room 44 (slot-machine), because a send is missing parameters, then // those parameters are taken from uninitialized stack and afterwards they are copied back into temps // if we don't remove the segment, we would get false-positive uninitialized reads later if (type == VAR_TEMP && value.segment == 0xffff) value.segment = 0; r[index] = value; } } #define READ_VAR(type, index) validate_read_var(s->variables[type], s->stack_base, type, s->variablesMax[type], index, s->r_acc) #define WRITE_VAR(type, index, value) validate_write_var(s->variables[type], s->stack_base, type, s->variablesMax[type], index, value, s->_segMan, g_sci->getKernel()) #define WRITE_VAR16(type, index, value) WRITE_VAR(type, index, make_reg(0, value)); // Operating on the stack // 16 bit: #define PUSH(v) PUSH32(make_reg(0, v)) // 32 bit: #define PUSH32(a) (*(validate_stack_addr(s, (s->xs->sp)++)) = (a)) #define POP32() (*(validate_stack_addr(s, --(s->xs->sp)))) bool SciEngine::checkExportBreakpoint(uint16 script, uint16 pubfunct) { if (_debugState._activeBreakpointTypes & BREAK_EXPORT) { uint32 bpaddress; bpaddress = (script << 16 | pubfunct); Common::List::const_iterator bp; for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) { if (bp->type == BREAK_EXPORT && bp->address == bpaddress) { _console->DebugPrintf("Break on script %d, export %d\n", script, pubfunct); _debugState.debugging = true; _debugState.breakpointWasHit = true; return true; } } } return false; } ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackPtr sp, reg_t calling_obj, uint16 argc, StackPtr argp) { int seg = s->_segMan->getScriptSegment(script); Script *scr = s->_segMan->getScriptIfLoaded(seg); if (!scr || scr->isMarkedAsDeleted()) { // Script not present yet? seg = s->_segMan->instantiateScript(script); scr = s->_segMan->getScript(seg); } const int temp = scr->validateExportFunc(pubfunct); if (!temp) { #ifdef ENABLE_SCI32 // HACK: Temporarily switch to a warning in SCI32 games until we can figure out why Torin has // an invalid exported function. if (getSciVersion() >= SCI_VERSION_2) warning("Request for invalid exported function 0x%x of script %d", pubfunct, script); else #endif error("Request for invalid exported function 0x%x of script %d", pubfunct, script); return NULL; } // Check if a breakpoint is set on this method g_sci->checkExportBreakpoint(script, pubfunct); return add_exec_stack_entry(s->_executionStack, make_reg(seg, temp), sp, calling_obj, argc, argp, -1, pubfunct, -1, calling_obj, s->_executionStack.size()-1, seg); } static void _exec_varselectors(EngineState *s) { // Executes all varselector read/write ops on the TOS while (!s->_executionStack.empty() && s->_executionStack.back().type == EXEC_STACK_TYPE_VARSELECTOR) { ExecStack &xs = s->_executionStack.back(); reg_t *var = xs.getVarPointer(s->_segMan); if (!var) { error("Invalid varselector exec stack entry"); } else { // varselector access? if (xs.argc) { // write? *var = xs.variables_argp[1]; } else // No, read s->r_acc = *var; } s->_executionStack.pop_back(); } } /** This struct is used to buffer the list of send calls in send_selector() */ struct CallsStruct { reg_t addr_func; reg_t varp_objp; union { reg_t func; ObjVarRef var; } address; StackPtr argp; int argc; Selector selector; StackPtr sp; /**< Stack pointer */ int type; /**< Same as ExecStack.type */ }; bool SciEngine::checkSelectorBreakpoint(BreakpointType breakpointType, reg_t send_obj, int selector) { char method_name[256]; sprintf(method_name, "%s::%s", _gamestate->_segMan->getObjectName(send_obj), getKernel()->getSelectorName(selector).c_str()); Common::List::const_iterator bp; for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) { int cmplen = bp->name.size(); if (bp->name.lastChar() != ':') cmplen = 256; if (bp->type == breakpointType && !strncmp(bp->name.c_str(), method_name, cmplen)) { _console->DebugPrintf("Break on %s (in [%04x:%04x])\n", method_name, PRINT_REG(send_obj)); _debugState.debugging = true; _debugState.breakpointWasHit = true; return true; } } return false; } ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPtr sp, int framesize, StackPtr argp) { // send_obj and work_obj are equal for anything but 'super' // Returns a pointer to the TOS exec_stack element assert(s); reg_t funcp; int selector; int argc; int origin = s->_executionStack.size()-1; // Origin: Used for debugging // We return a pointer to the new active ExecStack // The selector calls we catch are stored below: Common::Stack sendCalls; int activeBreakpointTypes = g_sci->_debugState._activeBreakpointTypes; while (framesize > 0) { selector = validate_arithmetic(*argp++); argc = validate_arithmetic(*argp); if (argc > 0x800) { // More arguments than the stack could possibly accomodate for error("send_selector(): More than 0x800 arguments to function call"); } #ifdef VM_DEBUG_SEND printf("Send to %04x:%04x (%s), selector %04x (%s):", PRINT_REG(send_obj), s->_segMan->getObjectName(send_obj), selector, g_sci->getKernel()->getSelectorName(selector).c_str()); #endif // VM_DEBUG_SEND ObjVarRef varp; switch (lookupSelector(s->_segMan, send_obj, selector, &varp, &funcp)) { case kSelectorNone: error("Send to invalid selector 0x%x of object at %04x:%04x", 0xffff & selector, PRINT_REG(send_obj)); break; case kSelectorVariable: #ifdef VM_DEBUG_SEND if (argc) printf("Varselector: Write %04x:%04x\n", PRINT_REG(argp[1])); else printf("Varselector: Read\n"); #endif // VM_DEBUG_SEND // argc == 0: read selector // argc != 0: write selector if (!argc) { // read selector if (activeBreakpointTypes & BREAK_SELECTORREAD) { if (g_sci->checkSelectorBreakpoint(BREAK_SELECTORREAD, send_obj, selector)) debug("[read selector]\n"); } } else { // write selector if (activeBreakpointTypes & BREAK_SELECTORWRITE) { if (g_sci->checkSelectorBreakpoint(BREAK_SELECTORWRITE, send_obj, selector)) { reg_t oldReg = *varp.getPointer(s->_segMan); reg_t newReg = argp[1]; warning("[write to selector (%s:%s): change %04x:%04x to %04x:%04x]\n", s->_segMan->getObjectName(send_obj), g_sci->getKernel()->getSelectorName(selector).c_str(), PRINT_REG(oldReg), PRINT_REG(newReg)); } } } if (argc > 1) { // argc can indeed be bigger than 1 in some cases, and it's usually the // result of a script bug. Usually these aren't fatal. const char *objectName = s->_segMan->getObjectName(send_obj); reg_t oldReg = *varp.getPointer(s->_segMan); reg_t newReg = argp[1]; const char *selectorName = g_sci->getKernel()->getSelectorName(selector).c_str(); debug(2, "send_selector(): argc = %d while modifying variable selector " "%x (%s) of object %04x:%04x (%s) from %04x:%04x to %04x:%04x", argc, selector, selectorName, PRINT_REG(send_obj), objectName, PRINT_REG(oldReg), PRINT_REG(newReg)); } { CallsStruct call; call.address.var = varp; // register the call call.argp = argp; call.argc = argc; call.selector = selector; call.type = EXEC_STACK_TYPE_VARSELECTOR; // Register as a varselector sendCalls.push(call); } break; case kSelectorMethod: #ifndef VM_DEBUG_SEND if (activeBreakpointTypes & BREAK_SELECTOREXEC) { if (g_sci->checkSelectorBreakpoint(BREAK_SELECTOREXEC, send_obj, selector)) { printf("[execute selector]"); int displaySize = 0; for (int argNr = 1; argNr <= argc; argNr++) { if (argNr == 1) printf(" - "); reg_t curParam = argp[argNr]; if (curParam.segment) { printf("[%04x:%04x] ", PRINT_REG(curParam)); displaySize += 12; } else { printf("[%04x] ", curParam.offset); displaySize += 7; } if (displaySize > 50) { if (argNr < argc) printf("..."); break; } } printf("\n"); } } #else // VM_DEBUG_SEND if (activeBreakpointTypes & BREAK_SELECTOREXEC) g_sci->checkSelectorBreakpoint(BREAK_SELECTOREXEC, send_obj, selector); printf("Funcselector("); for (int i = 0; i < argc; i++) { printf("%04x:%04x", PRINT_REG(argp[i+1])); if (i + 1 < argc) printf(", "); } printf(") at %04x:%04x\n", PRINT_REG(funcp)); #endif // VM_DEBUG_SEND { CallsStruct call; call.address.func = funcp; // register call call.argp = argp; call.argc = argc; call.selector = selector; call.type = EXEC_STACK_TYPE_CALL; call.sp = sp; sp = CALL_SP_CARRY; // Destroy sp, as it will be carried over sendCalls.push(call); } break; } // switch (lookupSelector()) framesize -= (2 + argc); argp += argc + 1; } // Iterate over all registered calls in the reverse order. This way, the first call is // placed on the TOS; as soon as it returns, it will cause the second call to be executed. while (!sendCalls.empty()) { CallsStruct call = sendCalls.pop(); if (call.type == EXEC_STACK_TYPE_VARSELECTOR) // Write/read variable? add_exec_stack_varselector(s->_executionStack, work_obj, call.argc, call.argp, call.selector, call.address.var, origin); else add_exec_stack_entry(s->_executionStack, call.address.func, call.sp, work_obj, call.argc, call.argp, call.selector, -1, -1, send_obj, origin, SCI_XS_CALLEE_LOCALS); } _exec_varselectors(s); return s->_executionStack.empty() ? NULL : &(s->_executionStack.back()); } static ExecStack *add_exec_stack_varselector(Common::List &execStack, reg_t objp, int argc, StackPtr argp, Selector selector, const ObjVarRef& address, int origin) { ExecStack *xstack = add_exec_stack_entry(execStack, NULL_REG, 0, objp, argc, argp, selector, -1, -1, objp, origin, SCI_XS_CALLEE_LOCALS); // Store selector address in sp xstack->addr.varp = address; xstack->type = EXEC_STACK_TYPE_VARSELECTOR; return xstack; } static ExecStack *add_exec_stack_entry(Common::List &execStack, reg_t pc, StackPtr sp, reg_t objp, int argc, StackPtr argp, Selector selector, int exportId, int localCallOffset, reg_t sendp, int origin, SegmentId _localsSegment) { // Returns new TOS element for the execution stack // _localsSegment may be -1 if derived from the called object //printf("Exec stack: [%d/%d], origin %d, at %p\n", s->execution_stack_pos, s->_executionStack.size(), origin, s->execution_stack); ExecStack xstack; xstack.objp = objp; if (_localsSegment != SCI_XS_CALLEE_LOCALS) xstack.local_segment = _localsSegment; else xstack.local_segment = pc.segment; xstack.sendp = sendp; xstack.addr.pc = pc; xstack.fp = xstack.sp = sp; xstack.argc = argc; xstack.variables_argp = argp; // Parameters *argp = make_reg(0, argc); // SCI code relies on the zeroeth argument to equal argc // Additional debug information xstack.debugSelector = selector; xstack.debugExportId = exportId; xstack.debugLocalCallOffset = localCallOffset; xstack.debugOrigin = origin; xstack.type = EXEC_STACK_TYPE_CALL; // Normal call execStack.push_back(xstack); return &(execStack.back()); } static reg_t pointer_add(EngineState *s, reg_t base, int offset) { SegmentObj *mobj = s->_segMan->getSegmentObj(base.segment); if (!mobj) { error("[VM] Error: Attempt to add %d to invalid pointer %04x:%04x", offset, PRINT_REG(base)); return NULL_REG; } switch (mobj->getType()) { case SEG_TYPE_LOCALS: case SEG_TYPE_SCRIPT: case SEG_TYPE_STACK: case SEG_TYPE_DYNMEM: base.offset += offset; return base; default: // FIXME: Changed this to warning, because iceman does this during dancing with girl. // Investigate why that is so and either fix the underlying issue or implement a more // specialized workaround! warning("[VM] Error: Attempt to add %d to pointer %04x:%04x, type %d: Pointer arithmetics of this type unsupported", offset, PRINT_REG(base), mobj->getType()); return NULL_REG; } } static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int argc, reg_t *argv) { // Add stack frame to indicate we're executing a callk. // This is useful in debugger backtraces if this // kernel function calls a script itself. ExecStack *xstack; xstack = add_exec_stack_entry(s->_executionStack, NULL_REG, NULL, NULL_REG, argc, argv - 1, 0, -1, -1, NULL_REG, s->_executionStack.size()-1, SCI_XS_CALLEE_LOCALS); xstack->debugSelector = kernelCallNr; xstack->type = EXEC_STACK_TYPE_KERNEL; } static void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *kernelSubCall, EngineState *s, int argc, reg_t *argv, reg_t result) { Kernel *kernel = g_sci->getKernel(); if (!kernelSubCall) { printf("k%s: ", kernelCall->name); } else { int callNameLen = strlen(kernelCall->name); if (strncmp(kernelCall->name, kernelSubCall->name, callNameLen) == 0) { const char *subCallName = kernelSubCall->name + callNameLen; printf("k%s(%s): ", kernelCall->name, subCallName); } else { printf("k%s(%s): ", kernelCall->name, kernelSubCall->name); } } for (int parmNr = 0; parmNr < argc; parmNr++) { if (parmNr) printf(", "); uint16 regType = kernel->findRegType(argv[parmNr]); if (regType & SIG_TYPE_NULL) printf("0"); else if (regType & SIG_TYPE_UNINITIALIZED) printf("UNINIT"); else if (regType & SIG_IS_INVALID) printf("INVALID"); else if (regType & SIG_TYPE_INTEGER) printf("%d", argv[parmNr].offset); else { printf("%04x:%04x", PRINT_REG(argv[parmNr])); switch (regType) { case SIG_TYPE_OBJECT: printf(" (%s)", s->_segMan->getObjectName(argv[parmNr])); break; case SIG_TYPE_REFERENCE: if (kernelCall->function == kSaid) { SegmentRef saidSpec = s->_segMan->dereference(argv[parmNr]); if (saidSpec.isRaw) { printf(" ('"); g_sci->getVocabulary()->debugDecipherSaidBlock(saidSpec.raw); printf("')"); } else { printf(" (non-raw said-spec)"); } } else { printf(" ('%s')", s->_segMan->getString(argv[parmNr]).c_str()); } default: break; } } } if (result.segment) printf(" = %04x:%04x\n", PRINT_REG(result)); else printf(" = %d\n", result.offset); } static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { Kernel *kernel = g_sci->getKernel(); if (kernelCallNr >= (int)kernel->_kernelFuncs.size()) error("Invalid kernel function 0x%x requested", kernelCallNr); const KernelFunction &kernelCall = kernel->_kernelFuncs[kernelCallNr]; reg_t *argv = s->xs->sp + 1; if (kernelCall.signature && !kernel->signatureMatch(kernelCall.signature, argc, argv)) { // signature mismatch, check if a workaround is available SciTrackOriginReply originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply); switch (solution.type) { case WORKAROUND_NONE: kernel->signatureDebug(kernelCall.signature, argc, argv); error("[VM] k%s[%x]: signature mismatch via method %s::%s (script %d, room %d, localCall 0x%x)", kernelCall.name, kernelCallNr, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, s->currentRoomNumber(), originReply.localCallOffset); break; case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone return; case WORKAROUND_STILLCALL: // call kernel anyway break; case WORKAROUND_FAKE: // don't do kernel call, fake acc s->r_acc = make_reg(0, solution.value); return; default: error("unknown workaround type"); } } // Call kernel function if (!kernelCall.subFunctionCount) { addKernelCallToExecStack(s, kernelCallNr, argc, argv); s->r_acc = kernelCall.function(s, argc, argv); if (kernelCall.debugLogging) logKernelCall(&kernelCall, NULL, s, argc, argv, s->r_acc); if (kernelCall.debugBreakpoint) { printf("Break on k%s\n", kernelCall.name); g_sci->_debugState.debugging = true; g_sci->_debugState.breakpointWasHit = true; } } else { // Sub-functions available, check signature and call that one directly if (argc < 1) error("[VM] k%s[%x]: no subfunction-id parameter given", kernelCall.name, kernelCallNr); if (argv[0].segment) error("[VM] k%s[%x]: given subfunction-id is actually a pointer", kernelCall.name, kernelCallNr); const uint16 subId = argv[0].toUint16(); // Skip over subfunction-id argc--; argv++; if (subId >= kernelCall.subFunctionCount) error("[VM] k%s: subfunction-id %d requested, but not available", kernelCall.name, subId); const KernelSubFunction &kernelSubCall = kernelCall.subFunctions[subId]; if (kernelSubCall.signature && !kernel->signatureMatch(kernelSubCall.signature, argc, argv)) { // Signature mismatch SciTrackOriginReply originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply); switch (solution.type) { case WORKAROUND_NONE: { kernel->signatureDebug(kernelSubCall.signature, argc, argv); int callNameLen = strlen(kernelCall.name); if (strncmp(kernelCall.name, kernelSubCall.name, callNameLen) == 0) { const char *subCallName = kernelSubCall.name + callNameLen; error("[VM] k%s(%s): signature mismatch via method %s::%s (script %d, room %d, localCall %x)", kernelCall.name, subCallName, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, s->currentRoomNumber(), originReply.localCallOffset); } error("[VM] k%s: signature mismatch via method %s::%s (script %d, room %d, localCall %x)", kernelSubCall.name, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, s->currentRoomNumber(), originReply.localCallOffset); break; } case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone return; case WORKAROUND_STILLCALL: // call kernel anyway break; case WORKAROUND_FAKE: // don't do kernel call, fake acc s->r_acc = make_reg(0, solution.value); return; default: error("unknown workaround type"); } } if (!kernelSubCall.function) error("[VM] k%s: subfunction-id %d requested, but not available", kernelCall.name, subId); addKernelCallToExecStack(s, kernelCallNr, argc, argv); s->r_acc = kernelSubCall.function(s, argc, argv); if (kernelSubCall.debugLogging) logKernelCall(&kernelCall, &kernelSubCall, s, argc, argv, s->r_acc); if (kernelSubCall.debugBreakpoint) { printf("Break on k%s\n", kernelSubCall.name); g_sci->_debugState.debugging = true; g_sci->_debugState.breakpointWasHit = true; } } // Remove callk stack frame again, if there's still an execution stack if (s->_executionStack.begin() != s->_executionStack.end()) s->_executionStack.pop_back(); } static void gcCountDown(EngineState *s) { if (s->gcCountDown-- <= 0) { s->gcCountDown = s->scriptGCInterval; run_gc(s); } } int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]) { uint offset = 0; extOpcode = src[offset++]; // Get "extended" opcode (lower bit has special meaning) const byte opcode = extOpcode >> 1; // get the actual opcode memset(opparams, 0, sizeof(opparams)); for (int i = 0; g_opcode_formats[opcode][i]; ++i) { //printf("Opcode: 0x%x, Opnumber: 0x%x, temp: %d\n", opcode, opcode, temp); assert(i < 3); switch (g_opcode_formats[opcode][i]) { case Script_Byte: opparams[i] = src[offset++]; break; case Script_SByte: opparams[i] = (int8)src[offset++]; break; case Script_Word: opparams[i] = READ_SCI11ENDIAN_UINT16(src + offset); offset += 2; break; case Script_SWord: opparams[i] = (int16)READ_SCI11ENDIAN_UINT16(src + offset); offset += 2; break; case Script_Variable: case Script_Property: case Script_Local: case Script_Temp: case Script_Global: case Script_Param: case Script_Offset: if (extOpcode & 1) { opparams[i] = src[offset++]; } else { opparams[i] = READ_SCI11ENDIAN_UINT16(src + offset); offset += 2; } break; case Script_SVariable: case Script_SRelative: if (extOpcode & 1) { opparams[i] = (int8)src[offset++]; } else { opparams[i] = (int16)READ_SCI11ENDIAN_UINT16(src + offset); offset += 2; } break; case Script_None: case Script_End: break; case Script_Invalid: default: error("opcode %02x: Invalid", extOpcode); } } return offset; } void run_vm(EngineState *s) { assert(s); int temp; reg_t r_temp; // Temporary register StackPtr s_temp; // Temporary stack pointer int16 opparams[4]; // opcode parameters s->restAdjust = 0; // &rest adjusts the parameter count by this value // Current execution data: s->xs = &(s->_executionStack.back()); ExecStack *xs_new = NULL; Object *obj = s->_segMan->getObject(s->xs->objp); Script *scr = 0; Script *local_script = s->_segMan->getScriptIfLoaded(s->xs->local_segment); int old_executionStackBase = s->executionStackBase; // Used to detect the stack bottom, for "physical" returns if (!local_script) error("run_vm(): program counter gone astray (local_script pointer is null)"); s->executionStackBase = s->_executionStack.size() - 1; s->variablesSegment[VAR_TEMP] = s->variablesSegment[VAR_PARAM] = s->_segMan->findSegmentByType(SEG_TYPE_STACK); s->variablesBase[VAR_TEMP] = s->variablesBase[VAR_PARAM] = s->stack_base; s->_executionStackPosChanged = true; // Force initialization while (1) { int var_type; // See description below int var_number; g_sci->_debugState.old_pc_offset = s->xs->addr.pc.offset; g_sci->_debugState.old_sp = s->xs->sp; if (s->abortScriptProcessing != kAbortNone) return; // Stop processing if (s->_executionStackPosChanged) { scr = s->_segMan->getScriptIfLoaded(s->xs->addr.pc.segment); if (!scr) error("No script in segment %d", s->xs->addr.pc.segment); s->xs = &(s->_executionStack.back()); s->_executionStackPosChanged = false; obj = s->_segMan->getObject(s->xs->objp); local_script = s->_segMan->getScriptIfLoaded(s->xs->local_segment); if (!local_script) { error("Could not find local script from segment %x", s->xs->local_segment); } else { s->variablesSegment[VAR_LOCAL] = local_script->_localsSegment; if (local_script->_localsBlock) s->variablesBase[VAR_LOCAL] = s->variables[VAR_LOCAL] = local_script->_localsBlock->_locals.begin(); else s->variablesBase[VAR_LOCAL] = s->variables[VAR_LOCAL] = NULL; if (local_script->_localsBlock) s->variablesMax[VAR_LOCAL] = local_script->_localsBlock->_locals.size(); else s->variablesMax[VAR_LOCAL] = 0; s->variablesMax[VAR_TEMP] = s->xs->sp - s->xs->fp; s->variablesMax[VAR_PARAM] = s->xs->argc + 1; } s->variables[VAR_TEMP] = s->xs->fp; s->variables[VAR_PARAM] = s->xs->variables_argp; } if (s->abortScriptProcessing != kAbortNone) return; // Stop processing // Debug if this has been requested: // TODO: re-implement sci_debug_flags if (g_sci->_debugState.debugging /* sci_debug_flags*/) { g_sci->scriptDebug(); g_sci->_debugState.breakpointWasHit = false; } Console *con = g_sci->getSciDebugger(); con->onFrame(); if (s->xs->sp < s->xs->fp) error("run_vm(): stack underflow, sp: %04x:%04x, fp: %04x:%04x", PRINT_REG(*s->xs->sp), PRINT_REG(*s->xs->fp)); s->variablesMax[VAR_TEMP] = s->xs->sp - s->xs->fp; if (s->xs->addr.pc.offset >= scr->getBufSize()) error("run_vm(): program counter gone astray, addr: %d, code buffer size: %d", s->xs->addr.pc.offset, scr->getBufSize()); // Get opcode byte extOpcode; s->xs->addr.pc.offset += readPMachineInstruction(scr->getBuf() + s->xs->addr.pc.offset, extOpcode, opparams); const byte opcode = extOpcode >> 1; switch (opcode) { case op_bnot: { // 0x00 (00) // Binary not int16 value; if (validate_signedInteger(s->r_acc, value)) s->r_acc = make_reg(0, 0xffff ^ value); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, NULL_REG); break; } case op_add: // 0x01 (01) r_temp = POP32(); // Happens in SQ1, room 28, when throwing the water at Orat if (s->r_acc.segment == 0xFFFF) { // WORKAROUND: init uninitialized variable to 0 warning("op_add: attempt to write to uninitialized variable"); s->r_acc = NULL_REG; } if (r_temp.segment || s->r_acc.segment) { reg_t r_ptr = NULL_REG; int offset; // Pointer arithmetics! if (s->r_acc.segment) { if (r_temp.segment) { error("Attempt to add two pointers, stack=%04x:%04x and acc=%04x:%04x", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); offset = 0; } else { r_ptr = s->r_acc; offset = r_temp.offset; } } else { r_ptr = r_temp; offset = s->r_acc.offset; } s->r_acc = pointer_add(s, r_ptr, offset); } else s->r_acc = make_reg(0, r_temp.offset + s->r_acc.offset); break; case op_sub: // 0x02 (02) r_temp = POP32(); if (r_temp.segment != s->r_acc.segment) { reg_t r_ptr = NULL_REG; int offset; // Pointer arithmetics! if (s->r_acc.segment) { if (r_temp.segment) { error("Attempt to subtract two pointers, stack=%04x:%04x and acc=%04x:%04x", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); offset = 0; } else { r_ptr = s->r_acc; offset = r_temp.offset; } } else { r_ptr = r_temp; offset = s->r_acc.offset; } s->r_acc = pointer_add(s, r_ptr, -offset); } else { // We can subtract numbers, or pointers with the same segment, // an operation which will yield a number like in C s->r_acc = make_reg(0, r_temp.offset - s->r_acc.offset); } break; case op_mul: { // 0x03 (03) r_temp = POP32(); int16 value1, value2; if (validate_signedInteger(s->r_acc, value1) && validate_signedInteger(r_temp, value2)) s->r_acc = make_reg(0, value1 * value2); else s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeMulWorkarounds, s->r_acc, r_temp); break; } case op_div: { // 0x04 (04) r_temp = POP32(); int16 divisor, dividend; if (validate_signedInteger(s->r_acc, divisor) && validate_signedInteger(r_temp, dividend)) s->r_acc = make_reg(0, (divisor != 0 ? dividend / divisor : 0)); else s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeDivWorkarounds, s->r_acc, r_temp); break; } case op_mod: { // 0x05 (05) r_temp = POP32(); if (getSciVersion() <= SCI_VERSION_0_LATE) { uint16 modulo, value; if (validate_unsignedInteger(s->r_acc, modulo) && validate_unsignedInteger(r_temp, value)) s->r_acc = make_reg(0, (modulo != 0 ? value % modulo : 0)); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, r_temp); } else { // In Iceman (and perhaps from SCI0 0.000.685 onwards in general), // handling for negative numbers was added. Since Iceman doesn't // seem to have issues with the older code, we exclude it for now // for simplicity's sake and use the new code for SCI01 and newer // games. Fixes the battlecruiser mini game in SQ5 (room 850), // bug #3035755 int16 modulo, value, result; if (validate_signedInteger(s->r_acc, modulo) && validate_signedInteger(r_temp, value)) { modulo = ABS(modulo); result = (modulo != 0 ? value % modulo : 0); if (result < 0) result += modulo; s->r_acc = make_reg(0, result); } else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, r_temp); } break; } case op_shr: { // 0x06 (06) // Shift right logical r_temp = POP32(); uint16 value, shiftCount; if (validate_unsignedInteger(r_temp, value) && validate_unsignedInteger(s->r_acc, shiftCount)) s->r_acc = make_reg(0, value >> shiftCount); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); break; } case op_shl: { // 0x07 (07) // Shift left logical r_temp = POP32(); uint16 value, shiftCount; if (validate_unsignedInteger(r_temp, value) && validate_unsignedInteger(s->r_acc, shiftCount)) s->r_acc = make_reg(0, value << shiftCount); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); break; } case op_xor: { // 0x08 (08) r_temp = POP32(); uint16 value1, value2; if (validate_unsignedInteger(r_temp, value1) && validate_unsignedInteger(s->r_acc, value2)) s->r_acc = make_reg(0, value1 ^ value2); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); break; } case op_and: { // 0x09 (09) r_temp = POP32(); uint16 value1, value2; if (validate_unsignedInteger(r_temp, value1) && validate_unsignedInteger(s->r_acc, value2)) s->r_acc = make_reg(0, value1 & value2); else s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeAndWorkarounds, r_temp, s->r_acc); break; } case op_or: { // 0x0a (10) r_temp = POP32(); uint16 value1, value2; if (validate_unsignedInteger(r_temp, value1) && validate_unsignedInteger(s->r_acc, value2)) s->r_acc = make_reg(0, value1 | value2); else s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeOrWorkarounds, r_temp, s->r_acc); break; } case op_neg: { // 0x0b (11) int16 value; if (validate_signedInteger(s->r_acc, value)) s->r_acc = make_reg(0, -value); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, NULL_REG); break; } case op_not: // 0x0c (12) s->r_acc = make_reg(0, !(s->r_acc.offset || s->r_acc.segment)); // Must allow pointers to be negated, as this is used for checking whether objects exist break; case op_eq_: // 0x0d (13) // == s->r_prev = s->r_acc; r_temp = POP32(); s->r_acc = make_reg(0, r_temp == s->r_acc); // Explicitly allow pointers to be compared break; case op_ne_: // 0x0e (14) // != s->r_prev = s->r_acc; r_temp = POP32(); s->r_acc = make_reg(0, r_temp != s->r_acc); // Explicitly allow pointers to be compared break; case op_gt_: // 0x0f (15) // > s->r_prev = s->r_acc; r_temp = POP32(); if (r_temp.segment && s->r_acc.segment) { // Signed pointer comparison. We do unsigned comparison instead, as that is probably what was intended. if (r_temp.segment != s->r_acc.segment) warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset); } else if (r_temp.segment && !s->r_acc.segment) { if (s->r_acc.offset >= 1000) error("[VM] op_gt: comparison between a pointer and number"); // Pseudo-WORKAROUND: Sierra allows any pointer <-> value comparison // Happens in SQ1, room 28, when throwing the water at Orat s->r_acc = make_reg(0, 1); } else { int16 compare1, compare2; if (validate_signedInteger(r_temp, compare1) && validate_signedInteger(s->r_acc, compare2)) s->r_acc = make_reg(0, compare1 > compare2); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); } break; case op_ge_: // 0x10 (16) // >= s->r_prev = s->r_acc; r_temp = POP32(); if (r_temp.segment && s->r_acc.segment) { if (r_temp.segment != s->r_acc.segment) warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset >= s->r_acc.offset); } else { int16 compare1, compare2; if (validate_signedInteger(r_temp, compare1) && validate_signedInteger(s->r_acc, compare2)) s->r_acc = make_reg(0, compare1 >= compare2); else s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeGeWorkarounds, r_temp, s->r_acc); } break; case op_lt_: // 0x11 (17) // < s->r_prev = s->r_acc; r_temp = POP32(); if (r_temp.segment && s->r_acc.segment) { if (r_temp.segment != s->r_acc.segment) warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset < s->r_acc.offset); } else if (r_temp.segment && !s->r_acc.segment) { if (s->r_acc.offset >= 1000) error("[VM] op_lt: comparison between a pointer and number"); // Pseudo-WORKAROUND: Sierra allows any pointer <-> value comparison // Happens in SQ1, room 58, when giving id-card to robot s->r_acc = make_reg(0, 1); } else { int16 compare1, compare2; if (validate_signedInteger(r_temp, compare1) && validate_signedInteger(s->r_acc, compare2)) s->r_acc = make_reg(0, compare1 < compare2); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); } break; case op_le_: // 0x12 (18) // <= s->r_prev = s->r_acc; r_temp = POP32(); if (r_temp.segment && s->r_acc.segment) { if (r_temp.segment != s->r_acc.segment) warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset <= s->r_acc.offset); } else { int16 compare1, compare2; if (validate_signedInteger(r_temp, compare1) && validate_signedInteger(s->r_acc, compare2)) s->r_acc = make_reg(0, compare1 <= compare2); else s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeLeWorkarounds, r_temp, s->r_acc); } break; case op_ugt_: // 0x13 (19) // > (unsigned) s->r_prev = s->r_acc; r_temp = POP32(); // SCI0/SCI1 scripts use this to check whether a // parameter is a pointer or a far text // reference. It is used e.g. by the standard library // Print function to distinguish two ways of calling it: // // (Print "foo") // Pointer to a string // (Print 420 5) // Reference to the fifth message in text resource 420 // It works because in those games, the maximum resource number is 999, // so any parameter value above that threshold must be a pointer. if (r_temp.segment && (s->r_acc == make_reg(0, 1000))) s->r_acc = make_reg(0, 1); else if (r_temp.segment && s->r_acc.segment) s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset); else { uint16 compare1, compare2; if (validate_unsignedInteger(r_temp, compare1) && validate_unsignedInteger(s->r_acc, compare2)) s->r_acc = make_reg(0, compare1 > compare2); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); } break; case op_uge_: // 0x14 (20) // >= (unsigned) s->r_prev = s->r_acc; r_temp = POP32(); // See above if (r_temp.segment && (s->r_acc == make_reg(0, 1000))) s->r_acc = make_reg(0, 1); else if (r_temp.segment && s->r_acc.segment) s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset >= s->r_acc.offset); else { uint16 compare1, compare2; if (validate_unsignedInteger(r_temp, compare1) && validate_unsignedInteger(s->r_acc, compare2)) s->r_acc = make_reg(0, compare1 >= compare2); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); } break; case op_ult_: // 0x15 (21) // < (unsigned) s->r_prev = s->r_acc; r_temp = POP32(); // See above // PQ2 japanese compares pointers to 2000 to find out if its a pointer or a resourceid if (r_temp.segment && (s->r_acc == make_reg(0, 1000) || (s->r_acc == make_reg(0, 2000)))) s->r_acc = NULL_REG; else if (r_temp.segment && s->r_acc.segment) s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset < s->r_acc.offset); else { uint16 compare1, compare2; if (validate_unsignedInteger(r_temp, compare1) && validate_unsignedInteger(s->r_acc, compare2)) s->r_acc = make_reg(0, compare1 < compare2); else s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeUltWorkarounds, r_temp, s->r_acc); } break; case op_ule_: // 0x16 (22) // <= (unsigned) s->r_prev = s->r_acc; r_temp = POP32(); // See above if (r_temp.segment && (s->r_acc == make_reg(0, 1000))) s->r_acc = NULL_REG; else if (r_temp.segment && s->r_acc.segment) s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset <= s->r_acc.offset); else { uint16 compare1, compare2; if (validate_unsignedInteger(r_temp, compare1) && validate_unsignedInteger(s->r_acc, compare2)) s->r_acc = make_reg(0, compare1 <= compare2); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); } break; case op_bt: // 0x17 (23) // Branch relative if true if (s->r_acc.offset || s->r_acc.segment) s->xs->addr.pc.offset += opparams[0]; break; case op_bnt: // 0x18 (24) // Branch relative if not true if (!(s->r_acc.offset || s->r_acc.segment)) s->xs->addr.pc.offset += opparams[0]; break; case op_jmp: // 0x19 (25) s->xs->addr.pc.offset += opparams[0]; break; case op_ldi: // 0x1a (26) // Load data immediate s->r_acc = make_reg(0, opparams[0]); break; case op_push: // 0x1b (27) // Push to stack PUSH32(s->r_acc); break; case op_pushi: // 0x1c (28) // Push immediate PUSH(opparams[0]); break; case op_toss: // 0x1d (29) // TOS (Top Of Stack) subtract s->xs->sp--; break; case op_dup: // 0x1e (30) // Duplicate TOD (Top Of Stack) element r_temp = s->xs->sp[-1]; PUSH32(r_temp); break; case op_link: // 0x1f (31) // We shouldn't initialize temp variables at all // We put special segment 0xFFFF in there, so that uninitialized reads can get detected for (int i = 0; i < opparams[0]; i++) s->xs->sp[i] = make_reg(0xffff, 0); s->xs->sp += opparams[0]; break; case op_call: { // 0x20 (32) // Call a script subroutine int argc = (opparams[1] >> 1) // Given as offset, but we need count + 1 + s->restAdjust; StackPtr call_base = s->xs->sp - argc; s->xs->sp[1].offset += s->restAdjust; uint16 localCallOffset = s->xs->addr.pc.offset + opparams[0]; xs_new = add_exec_stack_entry(s->_executionStack, make_reg(s->xs->addr.pc.segment, localCallOffset), s->xs->sp, s->xs->objp, (validate_arithmetic(*call_base)) + s->restAdjust, call_base, NULL_SELECTOR, -1, localCallOffset, s->xs->objp, s->_executionStack.size()-1, s->xs->local_segment); s->restAdjust = 0; // Used up the &rest adjustment s->xs->sp = call_base; s->_executionStackPosChanged = true; break; } case op_callk: { // 0x21 (33) // Call kernel function gcCountDown(s); s->xs->sp -= (opparams[1] >> 1) + 1; bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); if (!oldScriptHeader) s->xs->sp -= s->restAdjust; int argc = validate_arithmetic(s->xs->sp[0]); if (!oldScriptHeader) argc += s->restAdjust; callKernelFunc(s, opparams[0], argc); if (!oldScriptHeader) s->restAdjust = 0; // Calculate xs again: The kernel function might // have spawned a new VM xs_new = &(s->_executionStack.back()); s->_executionStackPosChanged = true; // If a game is being loaded, stop processing if (s->abortScriptProcessing != kAbortNone) return; // Stop processing break; } case op_callb: // 0x22 (34) // Call base script temp = ((opparams[1] >> 1) + s->restAdjust + 1); s_temp = s->xs->sp; s->xs->sp -= temp; s->xs->sp[0].offset += s->restAdjust; xs_new = execute_method(s, 0, opparams[0], s_temp, s->xs->objp, s->xs->sp[0].offset, s->xs->sp); s->restAdjust = 0; // Used up the &rest adjustment if (xs_new) // in case of error, keep old stack s->_executionStackPosChanged = true; break; case op_calle: // 0x23 (35) // Call external script temp = ((opparams[2] >> 1) + s->restAdjust + 1); s_temp = s->xs->sp; s->xs->sp -= temp; s->xs->sp[0].offset += s->restAdjust; xs_new = execute_method(s, opparams[0], opparams[1], s_temp, s->xs->objp, s->xs->sp[0].offset, s->xs->sp); s->restAdjust = 0; // Used up the &rest adjustment if (xs_new) // in case of error, keep old stack s->_executionStackPosChanged = true; break; case op_ret: // 0x24 (36) // Return from an execution loop started by call, calle, callb, send, self or super do { StackPtr old_sp2 = s->xs->sp; StackPtr old_fp = s->xs->fp; ExecStack *old_xs = &(s->_executionStack.back()); if ((int)s->_executionStack.size() - 1 == s->executionStackBase) { // Have we reached the base? s->executionStackBase = old_executionStackBase; // Restore stack base s->_executionStack.pop_back(); s->_executionStackPosChanged = true; return; // "Hard" return } if (old_xs->type == EXEC_STACK_TYPE_VARSELECTOR) { // varselector access? reg_t *var = old_xs->getVarPointer(s->_segMan); if (old_xs->argc) // write? *var = old_xs->variables_argp[1]; else // No, read s->r_acc = *var; } // Not reached the base, so let's do a soft return s->_executionStack.pop_back(); s->_executionStackPosChanged = true; s->xs = &(s->_executionStack.back()); if (s->xs->sp == CALL_SP_CARRY // Used in sends to 'carry' the stack pointer || s->xs->type != EXEC_STACK_TYPE_CALL) { s->xs->sp = old_sp2; s->xs->fp = old_fp; } } while (s->xs->type == EXEC_STACK_TYPE_VARSELECTOR); // Iterate over all varselector accesses s->_executionStackPosChanged = true; xs_new = s->xs; break; case op_send: // 0x25 (37) // Send for one or more selectors s_temp = s->xs->sp; s->xs->sp -= ((opparams[0] >> 1) + s->restAdjust); // Adjust stack s->xs->sp[1].offset += s->restAdjust; xs_new = send_selector(s, s->r_acc, s->r_acc, s_temp, (int)(opparams[0] >> 1) + (uint16)s->restAdjust, s->xs->sp); if (xs_new && xs_new != s->xs) s->_executionStackPosChanged = true; s->restAdjust = 0; break; case 0x26: // (38) case 0x27: // (39) error("Dummy opcode 0x%x called", opcode); // should never happen break; case op_class: // 0x28 (40) // Get class address s->r_acc = s->_segMan->getClassAddress((unsigned)opparams[0], SCRIPT_GET_LOCK, s->xs->addr.pc); break; case 0x29: // (41) error("Dummy opcode 0x%x called", opcode); // should never happen break; case op_self: // 0x2a (42) // Send to self s_temp = s->xs->sp; s->xs->sp -= ((opparams[0] >> 1) + s->restAdjust); // Adjust stack s->xs->sp[1].offset += s->restAdjust; xs_new = send_selector(s, s->xs->objp, s->xs->objp, s_temp, (int)(opparams[0] >> 1) + (uint16)s->restAdjust, s->xs->sp); if (xs_new && xs_new != s->xs) s->_executionStackPosChanged = true; s->restAdjust = 0; break; case op_super: // 0x2b (43) // Send to any class r_temp = s->_segMan->getClassAddress(opparams[0], SCRIPT_GET_LOAD, s->xs->addr.pc); if (!r_temp.segment) error("[VM]: Invalid superclass in object"); else { s_temp = s->xs->sp; s->xs->sp -= ((opparams[1] >> 1) + s->restAdjust); // Adjust stack s->xs->sp[1].offset += s->restAdjust; xs_new = send_selector(s, r_temp, s->xs->objp, s_temp, (int)(opparams[1] >> 1) + (uint16)s->restAdjust, s->xs->sp); if (xs_new && xs_new != s->xs) s->_executionStackPosChanged = true; s->restAdjust = 0; } break; case op_rest: // 0x2c (44) // Pushes all or part of the parameter variable list on the stack temp = (uint16) opparams[0]; // First argument s->restAdjust = MAX(s->xs->argc - temp + 1, 0); // +1 because temp counts the paramcount while argc doesn't for (; temp <= s->xs->argc; temp++) PUSH32(s->xs->variables_argp[temp]); break; case op_lea: // 0x2d (45) // Load Effective Address temp = (uint16) opparams[0] >> 1; var_number = temp & 0x03; // Get variable type // Get variable block offset r_temp.segment = s->variablesSegment[var_number]; r_temp.offset = s->variables[var_number] - s->variablesBase[var_number]; if (temp & 0x08) // Add accumulator offset if requested r_temp.offset += signed_validate_arithmetic(s->r_acc); r_temp.offset += opparams[1]; // Add index r_temp.offset *= 2; // variables are 16 bit // That's the immediate address now s->r_acc = r_temp; break; case op_selfID: // 0x2e (46) // Get 'self' identity s->r_acc = s->xs->objp; break; case 0x2f: // (47) error("Dummy opcode 0x%x called", opcode); // should never happen break; case op_pprev: // 0x30 (48) // Pushes the value of the prev register, set by the last comparison // bytecode (eq?, lt?, etc.), on the stack PUSH32(s->r_prev); break; case op_pToa: // 0x31 (49) // Property To Accumulator s->r_acc = validate_property(obj, (opparams[0] >> 1)); break; case op_aTop: // 0x32 (50) // Accumulator To Property validate_property(obj, (opparams[0] >> 1)) = s->r_acc; break; case op_pTos: // 0x33 (51) // Property To Stack PUSH32(validate_property(obj, opparams[0] >> 1)); break; case op_sTop: // 0x34 (52) // Stack To Property validate_property(obj, (opparams[0] >> 1)) = POP32(); break; case op_ipToa: { // 0x35 (53) // Increment Property and copy To Accumulator reg_t &opProperty = validate_property(obj, opparams[0] >> 1); uint16 valueProperty; if (validate_unsignedInteger(opProperty, valueProperty)) s->r_acc = make_reg(0, valueProperty + 1); else s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG); opProperty = s->r_acc; break; } case op_dpToa: { // 0x36 (54) // Decrement Property and copy To Accumulator reg_t &opProperty = validate_property(obj, opparams[0] >> 1); uint16 valueProperty; if (validate_unsignedInteger(opProperty, valueProperty)) s->r_acc = make_reg(0, valueProperty - 1); else s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeDptoaWorkarounds, opProperty, NULL_REG); opProperty = s->r_acc; break; } case op_ipTos: { // 0x37 (55) // Increment Property and push to Stack reg_t &opProperty = validate_property(obj, opparams[0] >> 1); uint16 valueProperty; if (validate_unsignedInteger(opProperty, valueProperty)) valueProperty++; else valueProperty = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG).offset; opProperty = make_reg(0, valueProperty); PUSH(valueProperty); break; } case op_dpTos: { // 0x38 (56) // Decrement Property and push to Stack reg_t &opProperty = validate_property(obj, opparams[0] >> 1); uint16 valueProperty; if (validate_unsignedInteger(opProperty, valueProperty)) valueProperty--; else valueProperty = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG).offset; opProperty = make_reg(0, valueProperty); PUSH(valueProperty); break; } case op_lofsa: // 0x39 (57) // Load Offset to Accumulator s->r_acc.segment = s->xs->addr.pc.segment; switch (g_sci->_features->detectLofsType()) { case SCI_VERSION_1_1: s->r_acc.offset = opparams[0] + local_script->getScriptSize(); break; case SCI_VERSION_1_MIDDLE: s->r_acc.offset = opparams[0]; break; default: s->r_acc.offset = s->xs->addr.pc.offset + opparams[0]; } if (s->r_acc.offset >= scr->getBufSize()) { error("VM: lofsa operation overflowed: %04x:%04x beyond end" " of script (at %04x)\n", PRINT_REG(s->r_acc), scr->getBufSize()); } break; case op_lofss: // 0x3a (58) // Load Offset to Stack r_temp.segment = s->xs->addr.pc.segment; switch (g_sci->_features->detectLofsType()) { case SCI_VERSION_1_1: r_temp.offset = opparams[0] + local_script->getScriptSize(); break; case SCI_VERSION_1_MIDDLE: r_temp.offset = opparams[0]; break; default: r_temp.offset = s->xs->addr.pc.offset + opparams[0]; } if (r_temp.offset >= scr->getBufSize()) { error("VM: lofss operation overflowed: %04x:%04x beyond end" " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize()); } PUSH32(r_temp); break; case op_push0: // 0x3b (59) PUSH(0); break; case op_push1: // 0x3c (60) PUSH(1); break; case op_push2: // 0x3d (61) PUSH(2); break; case op_pushSelf: // 0x3e (62) if (!(extOpcode & 1)) { PUSH32(s->xs->objp); } else { // Debug opcode op_file, skip null-terminated string (file name) const byte *code_buf = scr->getBuf(); while (code_buf[s->xs->addr.pc.offset++]) ; } break; case op_line: // 0x3f (63) // Debug opcode (line number) break; case op_lag: // 0x40 (64) case op_lal: // 0x41 (65) case op_lat: // 0x42 (66) case op_lap: // 0x43 (67) // Load global, local, temp or param variable into the accumulator var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; s->r_acc = READ_VAR(var_type, var_number); break; case op_lsg: // 0x44 (68) case op_lsl: // 0x45 (69) case op_lst: // 0x46 (70) case op_lsp: // 0x47 (71) // Load global, local, temp or param variable into the stack var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; PUSH32(READ_VAR(var_type, var_number)); break; case op_lagi: // 0x48 (72) case op_lali: // 0x49 (73) case op_lati: // 0x4a (74) case op_lapi: { // 0x4b (75) // Load global, local, temp or param variable into the accumulator, // using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p int16 value; if (!validate_signedInteger(s->r_acc, value)) value = arithmetic_lookForWorkaround(opcode, opcodeLaiWorkarounds, s->r_acc, NULL_REG).offset; var_number = opparams[0] + value; s->r_acc = READ_VAR(var_type, var_number); break; } case op_lsgi: // 0x4c (76) case op_lsli: // 0x4d (77) case op_lsti: // 0x4e (78) case op_lspi: { // 0x4f (79) // Load global, local, temp or param variable into the stack, // using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p int16 value; if (!validate_signedInteger(s->r_acc, value)) value = arithmetic_lookForWorkaround(opcode, opcodeLsiWorkarounds, s->r_acc, NULL_REG).offset; var_number = opparams[0] + value; PUSH32(READ_VAR(var_type, var_number)); break; } case op_sag: // 0x50 (80) case op_sal: // 0x51 (81) case op_sat: // 0x52 (82) case op_sap: // 0x53 (83) // Save the accumulator into the global, local, temp or param variable var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; WRITE_VAR(var_type, var_number, s->r_acc); break; case op_ssg: // 0x54 (84) case op_ssl: // 0x55 (85) case op_sst: // 0x56 (86) case op_ssp: // 0x57 (87) // Save the stack into the global, local, temp or param variable var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; WRITE_VAR(var_type, var_number, POP32()); break; case op_sagi: // 0x58 (88) case op_sali: // 0x59 (89) case op_sati: // 0x5a (90) case op_sapi: // 0x5b (91) // Save the accumulator into the global, local, temp or param variable, // using the accumulator as an additional index // Special semantics because it wouldn't really make a whole lot // of sense otherwise, with acc being used for two things // simultaneously... var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); s->r_acc = POP32(); WRITE_VAR(var_type, var_number, s->r_acc); break; case op_ssgi: // 0x5c (92) case op_ssli: // 0x5d (93) case op_ssti: // 0x5e (94) case op_sspi: // 0x5f (95) // Save the stack into the global, local, temp or param variable, // using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); WRITE_VAR(var_type, var_number, POP32()); break; case op_plusag: // 0x60 (96) case op_plusal: // 0x61 (97) case op_plusat: // 0x62 (98) case op_plusap: // 0x63 (99) // Increment the global, local, temp or param variable and save it // to the accumulator var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! s->r_acc = pointer_add(s, r_temp, 1); } else s->r_acc = make_reg(0, r_temp.offset + 1); WRITE_VAR(var_type, var_number, s->r_acc); break; case op_plussg: // 0x64 (100) case op_plussl: // 0x65 (101) case op_plusst: // 0x66 (102) case op_plussp: // 0x67 (103) // Increment the global, local, temp or param variable and save it // to the stack var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! r_temp = pointer_add(s, r_temp, 1); } else r_temp = make_reg(0, r_temp.offset + 1); PUSH32(r_temp); WRITE_VAR(var_type, var_number, r_temp); break; case op_plusagi: // 0x68 (104) case op_plusali: // 0x69 (105) case op_plusati: // 0x6a (106) case op_plusapi: // 0x6b (107) // Increment the global, local, temp or param variable and save it // to the accumulator, using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! s->r_acc = pointer_add(s, r_temp, 1); } else s->r_acc = make_reg(0, r_temp.offset + 1); WRITE_VAR(var_type, var_number, s->r_acc); break; case op_plussgi: // 0x6c (108) case op_plussli: // 0x6d (109) case op_plussti: // 0x6e (110) case op_plusspi: // 0x6f (111) // Increment the global, local, temp or param variable and save it // to the stack, using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! r_temp = pointer_add(s, r_temp, 1); } else r_temp = make_reg(0, r_temp.offset + 1); PUSH32(r_temp); WRITE_VAR(var_type, var_number, r_temp); break; case op_minusag: // 0x70 (112) case op_minusal: // 0x71 (113) case op_minusat: // 0x72 (114) case op_minusap: // 0x73 (115) // Decrement the global, local, temp or param variable and save it // to the accumulator var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! s->r_acc = pointer_add(s, r_temp, -1); } else s->r_acc = make_reg(0, r_temp.offset - 1); WRITE_VAR(var_type, var_number, s->r_acc); break; case op_minussg: // 0x74 (116) case op_minussl: // 0x75 (117) case op_minusst: // 0x76 (118) case op_minussp: // 0x77 (119) // Decrement the global, local, temp or param variable and save it // to the stack var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! r_temp = pointer_add(s, r_temp, -1); } else r_temp = make_reg(0, r_temp.offset - 1); PUSH32(r_temp); WRITE_VAR(var_type, var_number, r_temp); break; case op_minusagi: // 0x78 (120) case op_minusali: // 0x79 (121) case op_minusati: // 0x7a (122) case op_minusapi: // 0x7b (123) // Decrement the global, local, temp or param variable and save it // to the accumulator, using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! s->r_acc = pointer_add(s, r_temp, -1); } else s->r_acc = make_reg(0, r_temp.offset - 1); WRITE_VAR(var_type, var_number, s->r_acc); break; case op_minussgi: // 0x7c (124) case op_minussli: // 0x7d (125) case op_minussti: // 0x7e (126) case op_minusspi: // 0x7f (127) // Decrement the global, local, temp or param variable and save it // to the stack, using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! r_temp = pointer_add(s, r_temp, -1); } else r_temp = make_reg(0, r_temp.offset - 1); PUSH32(r_temp); WRITE_VAR(var_type, var_number, r_temp); break; default: error("run_vm(): illegal opcode %x", opcode); } // switch (opcode) if (s->_executionStackPosChanged) // Force initialization s->xs = xs_new; if (s->xs != &(s->_executionStack.back())) { error("xs is stale (%p vs %p); last command was %02x", (void *)s->xs, (void *)&(s->_executionStack.back()), opcode); } ++s->scriptStepCounter; } } reg_t *ObjVarRef::getPointer(SegManager *segMan) const { Object *o = segMan->getObject(obj); return o ? &o->getVariableRef(varindex) : 0; } reg_t *ExecStack::getVarPointer(SegManager *segMan) const { assert(type == EXEC_STACK_TYPE_VARSELECTOR); return addr.varp.getPointer(segMan); } } // End of namespace Sci