2061 lines
63 KiB
C++
Raw Normal View History

2009-02-17 15:02:16 +00:00
/* 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/stack.h"
#include "common/config-manager.h"
#include "sci/sci.h"
#include "sci/console.h"
#include "sci/debug.h" // for g_debugState
#include "sci/resource.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/kernel_types.h"
#include "sci/engine/seg_manager.h"
#include "sci/engine/script.h"
#include "sci/engine/gc.h"
namespace Sci {
const reg_t NULL_REG = {0, 0};
const reg_t SIGNAL_REG = {0, SIGNAL_OFFSET};
2009-02-20 23:09:29 +00:00
//#define VM_DEBUG_SEND
ScriptState scriptState;
int script_abort_flag = 0; // Set to 1 to abort execution. Set to 2 to force a replay afterwards // FIXME: Avoid non-const global vars
int script_step_counter = 0; // Counts the number of steps executed // FIXME: Avoid non-const global vars
int script_gc_interval = GC_INTERVAL; // Number of steps in between gcs // FIXME: Avoid non-const global vars
static bool breakpointFlag = false; // FIXME: Avoid non-const global vars
2009-02-20 23:09:29 +00:00
// validation functionality
#ifndef DISABLE_VALIDATIONS
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 return reg_t.
static reg_t dummyReg = NULL_REG;
if (!obj) {
debugC(2, kDebugLevelVM, "[VM] Sending to disposed object!\n");
return dummyReg;
}
if (index < 0 || (uint)index >= obj->getVarCount()) {
debugC(2, kDebugLevelVM, "[VM] Invalid property #%d (out of [0..%d]) requested!\n",
index, obj->getVarCount());
return dummyReg;
}
return obj->_variables[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) {
// When using a pointer in number arithmetic, we substitute a large value as
// some scripts rely on this (cf. Hoyle hack in kAbs).
warning("[VM] Attempt to read arithmetic value from non-zero segment [%04x]", reg.segment);
return 0x3e9;
}
return reg.offset;
}
static int signed_validate_arithmetic(reg_t reg) {
if (reg.segment) {
warning("[VM] Attempt to read arithmetic value from non-zero segment [%04x]", reg.segment);
return 0x3e9;
}
if (reg.offset & 0x8000)
return (signed)(reg.offset) - 65536;
else
return reg.offset;
}
static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, int index, int line) {
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);
warning("%s", txt.c_str());
if (type == VAR_PARAM || type == VAR_TEMP) {
int total_offset = r - stack_base;
if (total_offset < 0 || total_offset >= VM_STACK_SIZE) {
warning("[VM] Access would be outside even of the stack (%d); access denied", total_offset);
return false;
} else {
debugC(2, kDebugLevelVM, "[VM] Access within stack boundaries; access granted.\n");
return true;
}
}
return false;
}
return true;
}
static reg_t validate_read_var(reg_t *r, reg_t *stack_base, int type, int max, int index, int line, reg_t default_value) {
if (validate_variable(r, stack_base, type, max, index, line))
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, int line, reg_t value, SegManager *segMan, Kernel *kernel) {
if (validate_variable(r, stack_base, type, max, index, line)) {
// 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) { // global 0 is ego
reg_t stopGroopPos = segMan->findObjectByName("stopGroop");
if (!stopGroopPos.isNull()) { // does the game have a stopGroop object?
2009-10-22 05:42:14 +00:00
// Find the "client" member variable of the stopGroop object, and update it
ObjVarRef varp;
if (lookup_selector(segMan, stopGroopPos, kernel->_selectorCache.client, &varp, NULL) == kSelectorVariable) {
reg_t *clientVar = varp.getPointer(segMan);
*clientVar = value;
}
}
}
r[index] = value;
}
}
#else
2009-02-20 23:09:29 +00:00
// Non-validating alternatives
# define validate_stack_addr(s, sp) sp
# define validate_arithmetic(r) ((r).offset)
2009-02-20 23:09:29 +00:00
# define signed_validate_arithmetic(r) ((int) ((r).offset) & 0x8000 ? (signed) ((r).offset) - 65536 : ((r).offset))
# define validate_variable(r, sb, t, m, i, l)
# define validate_read_var(r, sb, t, m, i, l, dv) ((r)[i])
# define validate_write_var(r, sb, t, m, i, l, v, sm, k) ((r)[i] = (v))
# define validate_property(o, p) ((o)->_variables[p])
#endif
#define READ_VAR(type, index, def) validate_read_var(scriptState.variables[type], s->stack_base, type, scriptState.variables_max[type], index, __LINE__, def)
#define WRITE_VAR(type, index, value) validate_write_var(scriptState.variables[type], s->stack_base, type, scriptState.variables_max[type], index, __LINE__, value, s->_segMan, s->_kernel)
#define WRITE_VAR16(type, index, value) WRITE_VAR(type, index, make_reg(0, value));
#define ACC_ARITHMETIC_L(op) make_reg(0, (op validate_arithmetic(s->r_acc)))
#define ACC_AUX_LOAD() aux_acc = signed_validate_arithmetic(s->r_acc)
#define ACC_AUX_STORE() s->r_acc = make_reg(0, aux_acc)
#define OBJ_PROPERTY(o, p) (validate_property(o, p))
2009-02-20 23:09:29 +00:00
// Operating on the stack
// 16 bit:
#define PUSH(v) PUSH32(make_reg(0, v))
#define POP() (validate_arithmetic(POP32()))
2009-02-20 23:09:29 +00:00
// 32 bit:
#define PUSH32(a) (*(validate_stack_addr(s, (scriptState.xs->sp)++)) = (a))
#define POP32() (*(validate_stack_addr(s, --(scriptState.xs->sp))))
2009-02-20 23:09:29 +00:00
// Getting instruction parameters
#define GET_OP_BYTE() ((uint8)code_buf[(scriptState.xs->addr.pc.offset)++])
#define GET_OP_WORD() (READ_LE_UINT16(code_buf + ((scriptState.xs->addr.pc.offset) += 2) - 2))
#define GET_OP_FLEX() ((opcode & 1)? GET_OP_BYTE() : GET_OP_WORD())
#define GET_OP_SIGNED_BYTE() ((int8)(code_buf[(scriptState.xs->addr.pc.offset)++]))
#define GET_OP_SIGNED_WORD() (((int16)READ_LE_UINT16(code_buf + ((scriptState.xs->addr.pc.offset) += 2) - 2)))
#define GET_OP_SIGNED_FLEX() ((opcode & 1)? GET_OP_SIGNED_BYTE() : GET_OP_SIGNED_WORD())
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 = script_instantiate(s->resMan, s->_segMan, script);
const int temp = s->_segMan->validateExportFunc(pubfunct, seg);
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 0x%x", pubfunct, script);
else
#endif
error("Request for invalid exported function 0x%x of script 0x%x", pubfunct, script);
return NULL;
}
2009-02-20 23:09:29 +00:00
// Check if a breakpoint is set on this method
if (s->have_bp & BREAK_EXPORT) {
Breakpoint *bp;
uint32 bpaddress;
bpaddress = (script << 16 | pubfunct);
bp = s->bp_list;
while (bp) {
if (bp->type == BREAK_EXPORT && bp->data.address == bpaddress) {
Console *con = ((SciEngine *)g_engine)->getSciDebugger();
con->DebugPrintf("Break on script %d, export %d\n", script, pubfunct);
g_debugState.debugging = true;
breakpointFlag = true;
break;
}
bp = bp->next;
}
}
return add_exec_stack_entry(s, make_reg(seg, temp), sp, calling_obj, argc, argp, -1, calling_obj, s->_executionStack.size()-1, seg);
}
2009-02-22 13:11:43 +00:00
static void _exec_varselectors(EngineState *s) {
2009-02-20 23:09:29 +00:00
// 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) {
warning("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 */
};
ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPtr sp, int framesize, StackPtr argp) {
2009-02-20 23:09:29 +00:00
// 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
int print_send_action = 0;
// We return a pointer to the new active ExecStack
2009-02-20 23:09:29 +00:00
// The selector calls we catch are stored below:
Common::Stack<CallsStruct> sendCalls;
while (framesize > 0) {
selector = validate_arithmetic(*argp++);
argc = validate_arithmetic(*argp);
2009-02-20 23:09:29 +00:00
if (argc > 0x800) { // More arguments than the stack could possibly accomodate for
error("send_selector(): More than 0x800 arguments to function call");
}
2009-02-20 23:09:29 +00:00
// Check if a breakpoint is set on this method
if (s->have_bp & BREAK_SELECTOR) {
Breakpoint *bp;
char method_name [256];
sprintf(method_name, "%s::%s", s->_segMan->getObjectName(send_obj), s->_kernel->getSelectorName(selector).c_str());
bp = s->bp_list;
while (bp) {
int cmplen = strlen(bp->data.name);
if (bp->data.name[cmplen - 1] != ':')
cmplen = 256;
if (bp->type == BREAK_SELECTOR && !strncmp(bp->data.name, method_name, cmplen)) {
Console *con = ((SciEngine *)g_engine)->getSciDebugger();
con->DebugPrintf("Break on %s (in [%04x:%04x])\n", method_name, PRINT_REG(send_obj));
print_send_action = 1;
breakpointFlag = true;
g_debugState.debugging = true;
break;
}
bp = bp->next;
}
}
#ifdef VM_DEBUG_SEND
printf("Send to %04x:%04x, selector %04x (%s):", PRINT_REG(send_obj), selector, ((SciEngine*)g_engine)->getKernel()->getSelectorName(selector).c_str());
2009-02-20 23:09:29 +00:00
#endif // VM_DEBUG_SEND
ObjVarRef varp;
switch (lookup_selector(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");
2009-02-20 23:09:29 +00:00
#endif // VM_DEBUG_SEND
// argc == 0: read selector
// argc == 1: write selector
// argc > 1: write selector?
if (print_send_action && argc == 0) { // read selector
printf("[read selector]\n");
print_send_action = 0;
}
if (print_send_action && argc > 0) {
reg_t oldReg = *varp.getPointer(s->_segMan);
reg_t newReg = argp[1];
printf("[write to selector: change %04x:%04x to %04x:%04x]\n", PRINT_REG(oldReg), PRINT_REG(newReg));
print_send_action = 0;
}
if (argc > 1)
warning("send_selector(): more than 1 parameter (%d) while modifying a variable selector", argc);
{
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:
#ifdef VM_DEBUG_SEND
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));
2009-02-20 23:09:29 +00:00
#endif // VM_DEBUG_SEND
if (print_send_action) {
printf("[invoke selector]\n");
print_send_action = 0;
}
{
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 (lookup_selector())
framesize -= (2 + argc);
argp += argc + 1;
}
2009-02-20 23:09:29 +00:00
// 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, work_obj, call.argc, call.argp,
call.selector, call.address.var, origin);
else
add_exec_stack_entry(s, call.address.func, call.sp, work_obj,
call.argc, call.argp,
call.selector, send_obj, origin, SCI_XS_CALLEE_LOCALS);
}
_exec_varselectors(s);
if (s->_executionStack.empty())
return NULL;
return &(s->_executionStack.back());
}
ExecStack *add_exec_stack_varselector(EngineState *s, reg_t objp, int argc, StackPtr argp, Selector selector, const ObjVarRef& address, int origin) {
ExecStack *xstack = add_exec_stack_entry(s, NULL_REG, 0, objp, argc, argp, selector, objp, origin, SCI_XS_CALLEE_LOCALS);
2009-02-20 23:09:29 +00:00
// Store selector address in sp
xstack->addr.varp = address;
xstack->type = EXEC_STACK_TYPE_VARSELECTOR;
return xstack;
}
ExecStack *add_exec_stack_entry(EngineState *s, reg_t pc, StackPtr sp, reg_t objp, int argc,
StackPtr argp, Selector selector, reg_t sendp, int origin, SegmentId _localsSegment) {
2009-02-20 23:09:29 +00:00
// 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
2009-02-20 23:09:29 +00:00
*argp = make_reg(0, argc); // SCI code relies on the zeroeth argument to equal argc
2009-02-20 23:09:29 +00:00
// Additional debug information
xstack.selector = selector;
xstack.origin = origin;
xstack.type = EXEC_STACK_TYPE_CALL; // Normal call
s->_executionStack.push_back(xstack);
return &(s->_executionStack.back());
}
#ifdef DISABLE_VALIDATIONS
# define kernel_matches_signature(a, b, c, d) 1
#endif
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;
break;
default:
// Changed this to warning, because iceman does this during dancing with girl
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 gc_countdown(EngineState *s) {
if (s->gc_countdown-- <= 0) {
s->gc_countdown = script_gc_interval;
run_gc(s);
}
}
2009-03-12 03:26:58 +00:00
static const byte _fake_return_buffer[2] = {op_ret << 1, op_ret << 1};
void run_vm(EngineState *s, int restoring) {
assert(s);
#ifndef DISABLE_VALIDATIONS
2009-02-20 23:09:29 +00:00
unsigned int code_buf_size = 0 ; // (Avoid spurious warning)
#endif
int temp;
int16 aux_acc; // Auxiliary 16 bit accumulator
2009-02-20 23:09:29 +00:00
reg_t r_temp; // Temporary register
StackPtr s_temp; // Temporary stack pointer
int16 opparams[4]; // opcode parameters
bool loadFromGMM = ConfMan.hasKey("save_slot") ? true : false;
if (loadFromGMM) {
if (ConfMan.getInt("save_slot") < 0)
loadFromGMM = false; // already loaded
}
2009-02-20 23:09:29 +00:00
scriptState.restAdjust = s->restAdjust;
2009-02-20 23:09:29 +00:00
// &rest adjusts the parameter count by this value
// Current execution data:
scriptState.xs = &(s->_executionStack.back());
ExecStack *xs_new = NULL;
Object *obj = s->_segMan->getObject(scriptState.xs->objp);
Script *local_script = s->_segMan->getScriptIfLoaded(scriptState.xs->local_segment);
2009-02-20 23:09:29 +00:00
int old_execution_stack_base = s->execution_stack_base;
2009-02-22 13:11:43 +00:00
// Used to detect the stack bottom, for "physical" returns
2009-03-12 03:26:58 +00:00
const byte *code_buf = NULL; // (Avoid spurious warning)
if (!local_script) {
error("run_vm(): program counter gone astray (local_script pointer is null)");
}
if (!restoring)
s->execution_stack_base = s->_executionStack.size()-1;
#ifndef DISABLE_VALIDATIONS
2009-02-20 23:09:29 +00:00
// Initialize maximum variable count
if (s->script_000->_localsBlock)
scriptState.variables_max[VAR_GLOBAL] = s->script_000->_localsBlock->_locals.size();
else
scriptState.variables_max[VAR_GLOBAL] = 0;
#endif
scriptState.variables_seg[VAR_GLOBAL] = s->script_000->_localsSegment;
scriptState.variables_seg[VAR_TEMP] = scriptState.variables_seg[VAR_PARAM] = s->stack_segment;
scriptState.variables_base[VAR_TEMP] = scriptState.variables_base[VAR_PARAM] = s->stack_base;
2009-02-20 23:09:29 +00:00
// SCI code reads the zeroeth argument to determine argc
if (s->script_000->_localsBlock)
scriptState.variables_base[VAR_GLOBAL] = scriptState.variables[VAR_GLOBAL] = s->script_000->_localsBlock->_locals.begin();
else
scriptState.variables_base[VAR_GLOBAL] = scriptState.variables[VAR_GLOBAL] = NULL;
s->_executionStackPosChanged = true; // Force initialization
while (1) {
byte opcode;
byte opnumber;
2009-02-20 23:09:29 +00:00
int var_type; // See description below
int var_number;
g_debugState.old_pc_offset = scriptState.xs->addr.pc.offset;
g_debugState.old_sp = scriptState.xs->sp;
if (s->_executionStackPosChanged) {
Script *scr;
scriptState.xs = &(s->_executionStack.back());
s->_executionStackPosChanged = false;
scr = s->_segMan->getScriptIfLoaded(scriptState.xs->addr.pc.segment);
if (!scr) {
2009-02-20 23:09:29 +00:00
// No script? Implicit return via fake instruction buffer
warning("Running on non-existant script in segment %x", scriptState.xs->addr.pc.segment);
code_buf = _fake_return_buffer;
#ifndef DISABLE_VALIDATIONS
code_buf_size = 2;
#endif
scriptState.xs->addr.pc.offset = 1;
scr = NULL;
obj = NULL;
} else {
obj = s->_segMan->getObject(scriptState.xs->objp);
code_buf = scr->_buf;
#ifndef DISABLE_VALIDATIONS
code_buf_size = scr->_bufSize;
#endif
local_script = s->_segMan->getScriptIfLoaded(scriptState.xs->local_segment);
if (!local_script) {
warning("Could not find local script from segment %x", scriptState.xs->local_segment);
local_script = NULL;
scriptState.variables_base[VAR_LOCAL] = scriptState.variables[VAR_LOCAL] = NULL;
#ifndef DISABLE_VALIDATIONS
scriptState.variables_max[VAR_LOCAL] = 0;
#endif
} else {
scriptState.variables_seg[VAR_LOCAL] = local_script->_localsSegment;
if (local_script->_localsBlock)
scriptState.variables_base[VAR_LOCAL] = scriptState.variables[VAR_LOCAL] = local_script->_localsBlock->_locals.begin();
else
scriptState.variables_base[VAR_LOCAL] = scriptState.variables[VAR_LOCAL] = NULL;
#ifndef DISABLE_VALIDATIONS
if (local_script->_localsBlock)
scriptState.variables_max[VAR_LOCAL] = local_script->_localsBlock->_locals.size();
else
scriptState.variables_max[VAR_LOCAL] = 0;
scriptState.variables_max[VAR_TEMP] = scriptState.xs->sp - scriptState.xs->fp;
scriptState.variables_max[VAR_PARAM] = scriptState.xs->argc + 1;
#endif
}
scriptState.variables[VAR_TEMP] = scriptState.xs->fp;
scriptState.variables[VAR_PARAM] = scriptState.xs->variables_argp;
}
}
if (script_abort_flag || g_engine->shouldQuit())
2009-02-20 23:09:29 +00:00
return; // Emergency
2009-02-20 23:09:29 +00:00
// Debug if this has been requested:
// TODO: re-implement sci_debug_flags
if (g_debugState.debugging /* sci_debug_flags*/) {
script_debug(s, breakpointFlag);
breakpointFlag = false;
}
Console *con = ((Sci::SciEngine*)g_engine)->getSciDebugger();
if (con->isAttached()) {
con->onFrame();
}
#ifndef DISABLE_VALIDATIONS
if (scriptState.xs->sp < scriptState.xs->fp)
error("run_vm(): stack underflow");
scriptState.variables_max[VAR_TEMP] = scriptState.xs->sp - scriptState.xs->fp;
if (scriptState.xs->addr.pc.offset >= code_buf_size)
error("run_vm(): program counter gone astray");
#endif
2009-02-20 23:09:29 +00:00
opcode = GET_OP_BYTE(); // Get opcode
opnumber = opcode >> 1;
for (temp = 0; g_opcode_formats[opnumber][temp]; temp++) {
//printf("Opcode: 0x%x, Opnumber: 0x%x, temp: %d\n", opcode, opnumber, temp);
switch (g_opcode_formats[opnumber][temp]) {
case Script_Byte:
opparams[temp] = GET_OP_BYTE();
break;
case Script_SByte:
opparams[temp] = GET_OP_SIGNED_BYTE();
break;
case Script_Word:
opparams[temp] = GET_OP_WORD();
break;
case Script_SWord:
opparams[temp] = GET_OP_SIGNED_WORD();
break;
case Script_Variable:
case Script_Property:
case Script_Local:
case Script_Temp:
case Script_Global:
case Script_Param:
opparams[temp] = GET_OP_FLEX();
break;
case Script_SVariable:
case Script_SRelative:
opparams[temp] = GET_OP_SIGNED_FLEX();
break;
case Script_Offset:
opparams[temp] = GET_OP_FLEX();
break;
case Script_None:
case Script_End:
break;
case Script_Invalid:
default:
error("opcode %02x: Invalid", opcode);
}
}
switch (opnumber) {
case op_bnot: // 0x00 (00)
s->r_acc = ACC_ARITHMETIC_L(0xffff ^ /*acc*/);
break;
case op_add: // 0x01 (01)
r_temp = POP32();
if (r_temp.segment || s->r_acc.segment) {
reg_t r_ptr = NULL_REG;
int offset;
2009-02-20 23:09:29 +00:00
// 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;
2009-02-20 23:09:29 +00:00
// 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)
s->r_acc = ACC_ARITHMETIC_L(((int16)POP()) * (int16)/*acc*/);
break;
case op_div: // 0x04 (04)
ACC_AUX_LOAD();
aux_acc = aux_acc != 0 ? ((int16)POP()) / aux_acc : 0;
ACC_AUX_STORE();
break;
case op_mod: // 0x05 (05)
ACC_AUX_LOAD();
aux_acc = aux_acc != 0 ? ((int16)POP()) % aux_acc : 0;
ACC_AUX_STORE();
break;
case op_shr: // 0x06 (06)
s->r_acc = ACC_ARITHMETIC_L(((uint16)POP()) >> /*acc*/);
break;
case op_shl: // 0x07 (07)
s->r_acc = ACC_ARITHMETIC_L(((uint16)POP()) << /*acc*/);
break;
case op_xor: // 0x08 (08)
s->r_acc = ACC_ARITHMETIC_L(POP() ^ /*acc*/);
break;
case op_and: // 0x09 (09)
s->r_acc = ACC_ARITHMETIC_L(POP() & /*acc*/);
break;
case op_or: // 0x0a (10)
s->r_acc = ACC_ARITHMETIC_L(POP() | /*acc*/);
break;
case op_neg: // 0x0b (11)
s->r_acc = ACC_ARITHMETIC_L(-/*acc*/);
break;
case op_not: // 0x0c (12)
s->r_acc = make_reg(0, !(s->r_acc.offset || s->r_acc.segment));
2009-02-22 13:11:43 +00:00
// 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);
2009-02-20 23:09:29 +00:00
// 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);
2009-02-20 23:09:29 +00:00
// 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.
// FIXME: Add a warning when pointers in different segments are compared
s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset);
} else
s->r_acc = ACC_ARITHMETIC_L((int16)validate_arithmetic(r_temp) > (int16)/*acc*/);
break;
case op_ge_: // 0x10 (16)
s->r_prev = s->r_acc;
r_temp = POP32();
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
s->r_acc = ACC_ARITHMETIC_L((int16)validate_arithmetic(r_temp) >= (int16)/*acc*/);
break;
case op_lt_: // 0x11 (17)
s->r_prev = s->r_acc;
r_temp = POP32();
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
s->r_acc = ACC_ARITHMETIC_L((int16)validate_arithmetic(r_temp) < (int16)/*acc*/);
break;
case op_le_: // 0x12 (18)
s->r_prev = s->r_acc;
r_temp = POP32();
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
s->r_acc = ACC_ARITHMETIC_L((int16)validate_arithmetic(r_temp) <= (int16)/*acc*/);
break;
case op_ugt_: // 0x13 (19)
s->r_prev = s->r_acc;
r_temp = POP32();
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
s->r_acc = ACC_ARITHMETIC_L(validate_arithmetic(r_temp) > /*acc*/);
break;
case op_uge_: // 0x14 (20)
s->r_prev = s->r_acc;
r_temp = POP32();
2010-01-29 22:29:03 +00:00
// 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 (s->r_acc == make_reg(0, 0x3e8))
s->r_acc = make_reg(0, r_temp.segment);
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
s->r_acc = ACC_ARITHMETIC_L(validate_arithmetic(r_temp) >= /*acc*/);
break;
case op_ult_: // 0x15 (21)
s->r_prev = s->r_acc;
r_temp = POP32();
2010-01-29 22:29:03 +00:00
// See above
if (s->r_acc == make_reg(0, 0x3e8))
s->r_acc = make_reg(0, !r_temp.segment);
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
s->r_acc = ACC_ARITHMETIC_L(validate_arithmetic(r_temp) < /*acc*/);
break;
case op_ule_: // 0x16 (22)
s->r_prev = s->r_acc;
r_temp = POP32();
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
s->r_acc = ACC_ARITHMETIC_L(validate_arithmetic(r_temp) <= /*acc*/);
break;
case op_bt: // 0x17 (23)
if (s->r_acc.offset || s->r_acc.segment)
scriptState.xs->addr.pc.offset += opparams[0];
break;
case op_bnt: // 0x18 (24)
if (!(s->r_acc.offset || s->r_acc.segment))
scriptState.xs->addr.pc.offset += opparams[0];
break;
case op_jmp: // 0x19 (25)
scriptState.xs->addr.pc.offset += opparams[0];
break;
case op_ldi: // 0x1a (26)
s->r_acc = make_reg(0, opparams[0]);
break;
case op_push: // 0x1b (27)
PUSH32(s->r_acc);
break;
case op_pushi: // 0x1c (28)
PUSH(opparams[0]);
break;
case op_toss: // 0x1d (29)
scriptState.xs->sp--;
break;
case op_dup: // 0x1e (30)
r_temp = scriptState.xs->sp[-1];
PUSH32(r_temp);
break;
case op_link: // 0x1f (31)
for (int i = 0; i < opparams[0]; i++)
scriptState.xs->sp[i] = NULL_REG;
scriptState.xs->sp += opparams[0];
break;
case op_call: { // 0x20 (32)
2009-02-22 13:11:43 +00:00
int argc = (opparams[1] >> 1) // Given as offset, but we need count
+ 1 + scriptState.restAdjust;
StackPtr call_base = scriptState.xs->sp - argc;
scriptState.xs->sp[1].offset += scriptState.restAdjust;
xs_new = add_exec_stack_entry(s, make_reg(scriptState.xs->addr.pc.segment,
scriptState.xs->addr.pc.offset + opparams[0]),
scriptState.xs->sp, scriptState.xs->objp,
(validate_arithmetic(*call_base)) + scriptState.restAdjust,
call_base, NULL_SELECTOR, scriptState.xs->objp,
s->_executionStack.size()-1, scriptState.xs->local_segment);
scriptState.restAdjust = 0; // Used up the &rest adjustment
scriptState.xs->sp = call_base;
s->_executionStackPosChanged = true;
break;
}
case op_callk: { // 0x21 (33)
gc_countdown(s);
scriptState.xs->sp -= (opparams[1] >> 1) + 1;
bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
2009-09-17 13:20:58 +00:00
if (!oldScriptHeader) {
scriptState.xs->sp -= scriptState.restAdjust;
s->restAdjust = 0; // We just used up the scriptState.restAdjust, remember?
}
if (opparams[0] >= (int)s->_kernel->_kernelFuncs.size()) {
error("Invalid kernel function 0x%x requested", opparams[0]);
} else {
const KernelFuncWithSignature &kfun = s->_kernel->_kernelFuncs[opparams[0]];
int argc = validate_arithmetic(scriptState.xs->sp[0]);
if (!oldScriptHeader)
argc += scriptState.restAdjust;
2009-09-17 13:20:58 +00:00
if (kfun.signature
&& !kernel_matches_signature(s->_segMan, kfun.signature, argc, scriptState.xs->sp + 1)) {
error("[VM] Invalid arguments to kernel call %x", opparams[0]);
} else {
reg_t *argv = scriptState.xs->sp + 1;
if (!kfun.isDummy) {
// 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, NULL_REG, NULL, NULL_REG, argc, argv - 1, 0, NULL_REG,
s->_executionStack.size()-1, SCI_XS_CALLEE_LOCALS);
xstack->selector = opparams[0];
xstack->type = EXEC_STACK_TYPE_KERNEL;
//warning("callk %s", kfun.orig_name.c_str());
// TODO: SCI2/SCI2.1+ equivalent, once saving/loading works in SCI2/SCI2.1+
if (loadFromGMM && opparams[0] == 0x8) {
// A game is being loaded from GMM, and kDisplay is called, all initialization has taken
// place (i.e. menus have been constructed etc). Therefore, inject a kRestoreGame call
// here, instead of the requested function
int saveSlot = ConfMan.getInt("save_slot");
ConfMan.setInt("save_slot", -1); // invalidate slot
loadFromGMM = false;
if (saveSlot < 0)
error("Requested to load invalid save slot"); // should never happen, really
2010-01-21 22:44:25 +00:00
reg_t restoreArgv[2] = { NULL_REG, make_reg(0, saveSlot) }; // special GMM call (argv[0] is NULL)
kRestoreGame(s, 2, restoreArgv);
} else {
// Call kernel function
s->r_acc = kfun.fun(s, argc, argv);
}
// Remove callk stack frame again
s->_executionStack.pop_back();
} else {
Common::String warningMsg = "Dummy function " + kfun.orig_name +
Common::String::printf("[0x%x]", opparams[0]) +
" invoked - ignoring. Params: " +
Common::String::printf("%d", argc) + " (";
for (int i = 0; i < argc; i++) {
warningMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i]));
warningMsg += (i == argc - 1 ? ")" : ", ");
}
warning("%s", warningMsg.c_str());
}
}
2009-02-20 23:09:29 +00:00
// Calculate xs again: The kernel function might
2009-02-22 13:11:43 +00:00
// have spawned a new VM
xs_new = &(s->_executionStack.back());
s->_executionStackPosChanged = true;
if (!oldScriptHeader)
scriptState.restAdjust = s->restAdjust;
}
break;
2009-09-17 13:20:58 +00:00
}
case op_callb: // 0x22 (34)
temp = ((opparams[1] >> 1) + scriptState.restAdjust + 1);
s_temp = scriptState.xs->sp;
scriptState.xs->sp -= temp;
scriptState.xs->sp[0].offset += scriptState.restAdjust;
xs_new = execute_method(s, 0, opparams[0], s_temp, scriptState.xs->objp,
scriptState.xs->sp[0].offset, scriptState.xs->sp);
scriptState.restAdjust = 0; // Used up the &rest adjustment
2009-02-22 13:11:43 +00:00
if (xs_new) // in case of error, keep old stack
s->_executionStackPosChanged = true;
break;
case op_calle: // 0x23 (35)
temp = ((opparams[2] >> 1) + scriptState.restAdjust + 1);
s_temp = scriptState.xs->sp;
scriptState.xs->sp -= temp;
scriptState.xs->sp[0].offset += scriptState.restAdjust;
xs_new = execute_method(s, opparams[0], opparams[1], s_temp, scriptState.xs->objp,
scriptState.xs->sp[0].offset, scriptState.xs->sp);
scriptState.restAdjust = 0; // Used up the &rest adjustment
2009-02-22 13:11:43 +00:00
if (xs_new) // in case of error, keep old stack
s->_executionStackPosChanged = true;
break;
case op_ret: // 0x24 (36)
do {
StackPtr old_sp2 = scriptState.xs->sp;
StackPtr old_fp = scriptState.xs->fp;
ExecStack *old_xs = &(s->_executionStack.back());
if ((int)s->_executionStack.size()-1 == s->execution_stack_base) { // Have we reached the base?
2009-02-22 13:11:43 +00:00
s->execution_stack_base = old_execution_stack_base; // Restore stack base
s->_executionStack.pop_back();
s->_executionStackPosChanged = true;
s->restAdjust = scriptState.restAdjust; // Update &rest
2009-02-22 13:11:43 +00:00
return; // "Hard" return
}
if (old_xs->type == EXEC_STACK_TYPE_VARSELECTOR) {
2009-02-22 13:11:43 +00:00
// varselector access?
reg_t *var = old_xs->getVarPointer(s->_segMan);
2009-02-22 13:11:43 +00:00
if (old_xs->argc) // write?
2009-09-21 21:39:00 +00:00
*var = old_xs->variables_argp[1];
2009-02-22 13:11:43 +00:00
else // No, read
2009-09-21 21:39:00 +00:00
s->r_acc = *var;
}
2009-02-22 13:11:43 +00:00
// Not reached the base, so let's do a soft return
s->_executionStack.pop_back();
s->_executionStackPosChanged = true;
scriptState.xs = &(s->_executionStack.back());
if (scriptState.xs->sp == CALL_SP_CARRY // Used in sends to 'carry' the stack pointer
|| scriptState.xs->type != EXEC_STACK_TYPE_CALL) {
scriptState.xs->sp = old_sp2;
scriptState.xs->fp = old_fp;
}
} while (scriptState.xs->type == EXEC_STACK_TYPE_VARSELECTOR);
2009-02-22 13:11:43 +00:00
// Iterate over all varselector accesses
s->_executionStackPosChanged = true;
xs_new = scriptState.xs;
break;
case op_send: // 0x25 (37)
s_temp = scriptState.xs->sp;
scriptState.xs->sp -= ((opparams[0] >> 1) + scriptState.restAdjust); // Adjust stack
scriptState.xs->sp[1].offset += scriptState.restAdjust;
xs_new = send_selector(s, s->r_acc, s->r_acc, s_temp,
(int)(opparams[0] >> 1) + (uint16)scriptState.restAdjust, scriptState.xs->sp);
if (xs_new && xs_new != scriptState.xs)
s->_executionStackPosChanged = true;
scriptState.restAdjust = 0;
break;
case 0x26: // (38)
case 0x27: // (39)
error("Dummy opcode 0x%x called", opnumber); // should never happen
break;
case op_class: // 0x28 (40)
s->r_acc = s->_segMan->getClassAddress((unsigned)opparams[0], SCRIPT_GET_LOCK,
scriptState.xs->addr.pc);
break;
case 0x29: // (41)
error("Dummy opcode 0x%x called", opnumber); // should never happen
break;
case op_self: // 0x2a (42)
s_temp = scriptState.xs->sp;
scriptState.xs->sp -= ((opparams[0] >> 1) + scriptState.restAdjust); // Adjust stack
scriptState.xs->sp[1].offset += scriptState.restAdjust;
xs_new = send_selector(s, scriptState.xs->objp, scriptState.xs->objp,
s_temp, (int)(opparams[0] >> 1) + (uint16)scriptState.restAdjust,
scriptState.xs->sp);
if (xs_new && xs_new != scriptState.xs)
s->_executionStackPosChanged = true;
scriptState.restAdjust = 0;
break;
case op_super: // 0x2b (43)
r_temp = s->_segMan->getClassAddress(opparams[0], SCRIPT_GET_LOAD, scriptState.xs->addr.pc);
if (!r_temp.segment)
error("[VM]: Invalid superclass in object");
else {
s_temp = scriptState.xs->sp;
scriptState.xs->sp -= ((opparams[1] >> 1) + scriptState.restAdjust); // Adjust stack
scriptState.xs->sp[1].offset += scriptState.restAdjust;
xs_new = send_selector(s, r_temp, scriptState.xs->objp, s_temp,
(int)(opparams[1] >> 1) + (uint16)scriptState.restAdjust,
scriptState.xs->sp);
if (xs_new && xs_new != scriptState.xs)
s->_executionStackPosChanged = true;
scriptState.restAdjust = 0;
}
break;
case op_rest: // 0x2c (44)
2009-02-22 13:11:43 +00:00
temp = (uint16) opparams[0]; // First argument
scriptState.restAdjust = MAX<int16>(scriptState.xs->argc - temp + 1, 0); // +1 because temp counts the paramcount while argc doesn't
for (; temp <= scriptState.xs->argc; temp++)
PUSH32(scriptState.xs->variables_argp[temp]);
break;
case op_lea: // 0x2d (45)
temp = (uint16) opparams[0] >> 1;
2009-02-22 13:11:43 +00:00
var_number = temp & 0x03; // Get variable type
2009-02-22 13:11:43 +00:00
// Get variable block offset
r_temp.segment = scriptState.variables_seg[var_number];
r_temp.offset = scriptState.variables[var_number] - scriptState.variables_base[var_number];
2009-02-22 13:11:43 +00:00
if (temp & 0x08) // Add accumulator offset if requested
r_temp.offset += signed_validate_arithmetic(s->r_acc);
2009-02-22 13:11:43 +00:00
r_temp.offset += opparams[1]; // Add index
r_temp.offset *= 2; // variables are 16 bit
2009-02-22 13:11:43 +00:00
// That's the immediate address now
s->r_acc = r_temp;
break;
case op_selfID: // 0x2e (46)
s->r_acc = scriptState.xs->objp;
break;
case 0x2f: // (47)
error("Dummy opcode 0x%x called", opnumber); // should never happen
break;
case op_pprev: // 0x30 (48)
PUSH32(s->r_prev);
break;
case op_pToa: // 0x31 (49)
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1));
break;
case op_aTop: // 0x32 (50)
OBJ_PROPERTY(obj, (opparams[0] >> 1)) = s->r_acc;
break;
case op_pTos: // 0x33 (51)
PUSH32(OBJ_PROPERTY(obj, opparams[0] >> 1));
break;
case op_sTop: // 0x34 (52)
OBJ_PROPERTY(obj, (opparams[0] >> 1)) = POP32();
break;
case op_ipToa: // 0x35 (53)
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1));
2009-02-20 23:09:29 +00:00
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(1 + /*acc*/);
break;
case op_dpToa: { // 0x36 (54)
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1));
#if 0
// Speed throttling is possible here as well
// although this opens other issues like mud wrestling in lsl5 uses another local variable for delays
Object *var_container = obj;
if (!(obj->getInfoSelector().offset & SCRIPT_INFO_CLASS))
var_container = s->_segMan->getObject(obj->getSuperClassSelector());
uint16 varSelector = var_container->getVarSelector(opparams[0] >> 1);
// printf("%X\n", varSelector);
// printf("%s\n", s->_kernel->getSelectorName(varSelector).c_str());
if ((varSelector == 0x84) || (varSelector == 0x92))) {
// selectors cycles, cycleCnt from lsl5 hardcoded
uint32 curTime = g_system->getMillis();
if (s->_lastAnimateTime + 30 > curTime)
break;
s->_lastAnimateTime = curTime;
}
#endif
2009-02-20 23:09:29 +00:00
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(-1 + /*acc*/);
break;
}
case op_ipTos: // 0x37 (55)
validate_arithmetic(OBJ_PROPERTY(obj, (opparams[0] >> 1)));
temp = ++OBJ_PROPERTY(obj, (opparams[0] >> 1)).offset;
PUSH(temp);
break;
case op_dpTos: // 0x38 (56)
validate_arithmetic(OBJ_PROPERTY(obj, (opparams[0] >> 1)));
temp = --OBJ_PROPERTY(obj, (opparams[0] >> 1)).offset;
PUSH(temp);
break;
case op_lofsa: // 0x39 (57)
s->r_acc.segment = scriptState.xs->addr.pc.segment;
switch (s->detectLofsType()) {
case SCI_VERSION_1_1:
s->r_acc.offset = opparams[0] + local_script->_scriptSize;
break;
case SCI_VERSION_1_MIDDLE:
s->r_acc.offset = opparams[0];
break;
default:
s->r_acc.offset = scriptState.xs->addr.pc.offset + opparams[0];
}
#ifndef DISABLE_VALIDATIONS
if (s->r_acc.offset >= code_buf_size) {
error("VM: lofsa operation overflowed: %04x:%04x beyond end"
2009-02-20 23:09:29 +00:00
" of script (at %04x)\n", PRINT_REG(s->r_acc), code_buf_size);
}
#endif
break;
case op_lofss: // 0x3a (58)
r_temp.segment = scriptState.xs->addr.pc.segment;
switch (s->detectLofsType()) {
case SCI_VERSION_1_1:
r_temp.offset = opparams[0] + local_script->_scriptSize;
break;
case SCI_VERSION_1_MIDDLE:
r_temp.offset = opparams[0];
break;
default:
r_temp.offset = scriptState.xs->addr.pc.offset + opparams[0];
}
#ifndef DISABLE_VALIDATIONS
if (r_temp.offset >= code_buf_size) {
error("VM: lofss operation overflowed: %04x:%04x beyond end"
" of script (at %04x)", PRINT_REG(r_temp), code_buf_size);
}
#endif
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)
2010-01-28 19:22:58 +00:00
if (!(opcode & 1)) {
PUSH32(scriptState.xs->objp);
} else {
// Debug opcode op_file, skip null-terminated string (file name)
while (GET_OP_BYTE()) ;
}
break;
2010-01-28 19:22:58 +00:00
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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
s->r_acc = READ_VAR(var_type, var_number, s->r_acc);
break;
case op_lsg: // 0x44 (68)
case op_lsl: // 0x45 (69)
case op_lst: // 0x46 (70)
case op_lsp: // 0x47 (71)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
PUSH32(READ_VAR(var_type, var_number, s->r_acc));
break;
case op_lagi: // 0x48 (72)
case op_lali: // 0x49 (73)
case op_lati: // 0x4a (74)
case op_lapi: // 0x4b (75)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0] + signed_validate_arithmetic(s->r_acc);
s->r_acc = READ_VAR(var_type, var_number, s->r_acc);
break;
case op_lsgi: // 0x4c (76)
case op_lsli: // 0x4d (77)
case op_lsti: // 0x4e (78)
case op_lspi: // 0x4f (79)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0] + signed_validate_arithmetic(s->r_acc);
PUSH32(READ_VAR(var_type, var_number, s->r_acc));
break;
case op_sag: // 0x50 (80)
case op_sal: // 0x51 (81)
case op_sat: // 0x52 (82)
case op_sap: // 0x53 (83)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 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)
2009-02-20 23:09:29 +00:00
// Special semantics because it wouldn't really make a whole lot
// of sense otherwise, with acc being used for two things
2009-02-22 13:11:43 +00:00
// simultaneously...
var_type = (opcode >> 1) & 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, s->r_acc = POP32());
break;
case op_ssgi: // 0x5c (92)
case op_ssli: // 0x5d (93)
case op_ssti: // 0x5e (94)
case op_sspi: // 0x5f (95)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
r_temp = READ_VAR(var_type, var_number, s->r_acc);
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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
r_temp = READ_VAR(var_type, var_number, s->r_acc);
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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 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, s->r_acc);
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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 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, s->r_acc);
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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
r_temp = READ_VAR(var_type, var_number, s->r_acc);
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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
r_temp = READ_VAR(var_type, var_number, s->r_acc);
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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 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, s->r_acc);
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)
2009-02-22 13:11:43 +00:00
var_type = (opcode >> 1) & 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, s->r_acc);
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", opnumber);
} // switch (opcode >> 1)
if (s->_executionStackPosChanged) // Force initialization
scriptState.xs = xs_new;
//#ifndef DISABLE_VALIDATIONS
if (scriptState.xs != &(s->_executionStack.back())) {
warning("xs is stale (%p vs %p); last command was %02x",
(void *)scriptState.xs, (void *)&(s->_executionStack.back()),
opnumber);
}
//#endif
++script_step_counter;
}
}
#define INST_LOOKUP_CLASS(id) ((id == 0xffff)? NULL_REG : segMan->getClassAddress(id, SCRIPT_GET_LOCK, reg))
int script_instantiate_common(ResourceManager *resMan, SegManager *segMan, int script_nr, Resource **script, Resource **heap, int *was_new) {
*was_new = 1;
*script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0);
if (getSciVersion() >= SCI_VERSION_1_1)
*heap = resMan->findResource(ResourceId(kResourceTypeHeap, script_nr), 0);
if (!*script || (getSciVersion() >= SCI_VERSION_1_1 && !heap)) {
warning("Script 0x%x requested but not found", script_nr);
if (getSciVersion() >= SCI_VERSION_1_1) {
if (*heap)
warning("Inconsistency: heap resource WAS found");
else if (*script)
warning("Inconsistency: script resource WAS found");
}
return 0;
}
SegmentId seg_id = segMan->getScriptSegment(script_nr);
Script *scr = segMan->getScriptIfLoaded(seg_id);
if (scr) {
if (!scr->isMarkedAsDeleted()) {
scr->incrementLockers();
return seg_id;
} else {
scr->freeScript();
}
} else {
scr = segMan->allocateScript(script_nr, &seg_id);
if (!scr) { // ALL YOUR SCRIPT BASE ARE BELONG TO US
error("Not enough heap space for script size 0x%x of script 0x%x (Should this happen?)", (*script)->size, script_nr);
return 0;
}
}
scr->init(script_nr, resMan);
reg_t reg;
reg.segment = seg_id;
reg.offset = 0;
2009-02-22 13:11:43 +00:00
// Set heap position (beyond the size word)
scr->setLockers(1);
scr->setExportTableOffset(0);
scr->setSynonymsOffset(0);
scr->setSynonymsNr(0);
*was_new = 0;
return seg_id;
}
int script_instantiate_sci0(ResourceManager *resMan, SegManager *segMan, int script_nr) {
int objtype;
unsigned int objlength;
int relocation = -1;
2009-02-22 13:11:43 +00:00
int magic_pos_adder; // Usually 0; 2 for older SCI versions
Resource *script;
int was_new;
bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
const int seg_id = script_instantiate_common(resMan, segMan, script_nr, &script, NULL, &was_new);
2009-02-20 23:09:29 +00:00
if (was_new)
return seg_id;
Script *scr = segMan->getScript(seg_id);
if (oldScriptHeader) {
2009-02-20 23:09:29 +00:00
//
int locals_nr = READ_LE_UINT16(script->data);
2009-02-22 13:11:43 +00:00
// Old script block
// There won't be a localvar block in this case
2009-02-20 23:09:29 +00:00
// Instead, the script starts with a 16 bit int specifying the
2009-02-22 13:11:43 +00:00
// number of locals we need; these are then allocated and zeroed.
scr->mcpyInOut(0, script->data, script->size);
2009-02-22 13:11:43 +00:00
magic_pos_adder = 2; // Step over the funny prefix
if (locals_nr)
2009-09-06 12:59:34 +00:00
segMan->scriptInitialiseLocalsZero(seg_id, locals_nr);
} else {
scr->mcpyInOut(0, script->data, script->size);
magic_pos_adder = 0;
}
2009-02-20 23:09:29 +00:00
// Now do a first pass through the script objects to find the
// export table and local variable block
2009-09-06 12:59:34 +00:00
reg_t reg;
reg.segment = seg_id;
reg.offset = magic_pos_adder;
2009-09-06 12:59:34 +00:00
objlength = 0;
do {
reg_t data_base;
reg_t addr;
2009-02-22 13:11:43 +00:00
reg.offset += objlength; // Step over the last checked object
objtype = scr->getHeap(reg.offset);
2009-08-26 09:36:54 +00:00
if (!objtype)
break;
objlength = scr->getHeap(reg.offset + 2);
// This happens in some demos (e.g. the EcoQuest 1 demo). Not sure what is the
// actual cause of it, but the scripts of these demos can't be loaded properly
// and we're stuck forever in this loop, as objlength never changes
if (!objlength) {
warning("script_instantiate_sci0: objlength is 0, unable to parse script");
return 0;
}
data_base = reg;
data_base.offset += 4;
addr = data_base;
switch (objtype) {
case SCI_OBJ_EXPORTS: {
scr->setExportTableOffset(data_base.offset);
}
break;
case SCI_OBJ_SYNONYMS:
scr->setSynonymsOffset(addr.offset); // +4 is to step over the header
scr->setSynonymsNr((objlength) / 4);
break;
case SCI_OBJ_LOCALVARS:
segMan->scriptInitialiseLocals(data_base);
break;
case SCI_OBJ_CLASS: {
int classpos = addr.offset - SCRIPT_OBJECT_MAGIC_OFFSET;
int species;
species = scr->getHeap(addr.offset - SCRIPT_OBJECT_MAGIC_OFFSET + SCRIPT_SPECIES_OFFSET);
if (species < 0 || species >= (int)segMan->_classtable.size()) {
if (species == (int)segMan->_classtable.size()) {
// Happens in the LSL2 demo
warning("Applying workaround for an off-by-one invalid species access");
segMan->_classtable.resize(segMan->_classtable.size() + 1);
} else {
warning("Invalid species %d(0x%x) not in interval "
"[0,%d) while instantiating script %d\n",
species, species, segMan->_classtable.size(),
script_nr);
return 0;
}
}
segMan->_classtable[species].reg = addr;
segMan->_classtable[species].reg.offset = classpos;
2009-02-22 13:11:43 +00:00
// Set technical class position-- into the block allocated for it
}
break;
default:
break;
}
} while (objtype != 0);
2009-08-26 09:36:54 +00:00
2009-02-22 13:11:43 +00:00
// And now a second pass to adjust objects and class pointers, and the general pointers
objlength = 0;
2009-02-22 13:11:43 +00:00
reg.offset = magic_pos_adder; // Reset counter
do {
reg_t addr;
2009-02-22 13:11:43 +00:00
reg.offset += objlength; // Step over the last checked object
objtype = scr->getHeap(reg.offset);
2009-08-26 09:36:54 +00:00
if (!objtype)
break;
objlength = scr->getHeap(reg.offset + 2);
addr = reg;
addr.offset += 4; // Step over header
switch (objtype) {
case SCI_OBJ_CODE:
scr->scriptAddCodeBlock(addr);
break;
case SCI_OBJ_OBJECT:
case SCI_OBJ_CLASS: { // object or class?
Object *obj = scr->scriptObjInit(addr);
2009-02-22 13:11:43 +00:00
// Instantiate the superclass, if neccessary
obj->setSpeciesSelector(INST_LOOKUP_CLASS(obj->getSpeciesSelector().offset));
Object *baseObj = segMan->getObject(obj->getSpeciesSelector());
if (baseObj) {
obj->setVarCount(baseObj->getVarCount());
// Copy base from species class, as we need its selector IDs
obj->_baseObj = baseObj->_baseObj;
obj->setSuperClassSelector(INST_LOOKUP_CLASS(obj->getSuperClassSelector().offset));
} else {
warning("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr));
scr->scriptObjRemove(addr);
}
2009-02-22 13:11:43 +00:00
} // if object or class
break;
case SCI_OBJ_POINTERS: // A relocation table
relocation = addr.offset;
break;
default:
break;
}
2009-08-26 09:36:54 +00:00
} while (objtype != 0 && reg.offset < script->size - 2);
if (relocation >= 0)
scr->scriptRelocate(make_reg(seg_id, relocation));
2009-02-22 13:11:43 +00:00
return reg.segment; // instantiation successful
}
int script_instantiate_sci11(ResourceManager *resMan, SegManager *segMan, int script_nr) {
Resource *script, *heap;
int _heapStart;
reg_t reg;
int was_new;
const int seg_id = script_instantiate_common(resMan, segMan, script_nr, &script, &heap, &was_new);
2009-02-20 23:09:29 +00:00
if (was_new)
return seg_id;
Script *scr = segMan->getScript(seg_id);
_heapStart = script->size;
if (script->size & 2)
_heapStart ++;
scr->mcpyInOut(0, script->data, script->size);
scr->mcpyInOut(_heapStart, heap->data, heap->size);
if (READ_LE_UINT16(script->data + 6) > 0)
scr->setExportTableOffset(6);
reg.segment = seg_id;
reg.offset = _heapStart + 4;
segMan->scriptInitialiseLocals(reg);
segMan->scriptRelocateExportsSci11(seg_id);
segMan->scriptInitialiseObjectsSci11(seg_id);
reg.offset = READ_LE_UINT16(heap->data);
scr->heapRelocate(reg);
return seg_id;
}
int script_instantiate(ResourceManager *resMan, SegManager *segMan, int script_nr) {
if (getSciVersion() >= SCI_VERSION_1_1)
return script_instantiate_sci11(resMan, segMan, script_nr);
else
return script_instantiate_sci0(resMan, segMan, script_nr);
}
void script_uninstantiate_sci0(SegManager *segMan, int script_nr, SegmentId seg) {
bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
reg_t reg = make_reg(seg, oldScriptHeader ? 2 : 0);
int objtype, objlength;
Script *scr = segMan->getScript(seg);
2009-02-22 13:11:43 +00:00
// Make a pass over the object in order uninstantiate all superclasses
objlength = 0;
do {
2009-02-22 13:11:43 +00:00
reg.offset += objlength; // Step over the last checked object
objtype = scr->getHeap(reg.offset);
if (!objtype)
break;
objlength = scr->getHeap(reg.offset + 2); // use SEG_UGET_HEAP ??
2009-02-20 23:09:29 +00:00
reg.offset += 4; // Step over header
if ((objtype == SCI_OBJ_OBJECT) || (objtype == SCI_OBJ_CLASS)) { // object or class?
int superclass;
reg.offset -= SCRIPT_OBJECT_MAGIC_OFFSET;
superclass = scr->getHeap(reg.offset + SCRIPT_SUPERCLASS_OFFSET); // Get superclass...
if (superclass >= 0) {
int superclass_script = segMan->_classtable[superclass].script;
if (superclass_script == script_nr) {
if (scr->getLockers())
scr->decrementLockers(); // Decrease lockers if this is us ourselves
} else
script_uninstantiate(segMan, superclass_script);
2009-02-20 23:09:29 +00:00
// Recurse to assure that the superclass lockers number gets decreased
}
reg.offset += SCRIPT_OBJECT_MAGIC_OFFSET;
2009-02-20 23:09:29 +00:00
} // if object or class
2009-02-20 23:09:29 +00:00
reg.offset -= 4; // Step back on header
} while (objtype != 0);
}
void script_uninstantiate(SegManager *segMan, int script_nr) {
SegmentId segment = segMan->getScriptSegment(script_nr);
Script *scr = segMan->getScriptIfLoaded(segment);
if (!scr) { // Is it already loaded?
2009-02-28 13:41:43 +00:00
//warning("unloading script 0x%x requested although not loaded", script_nr);
2009-02-20 23:09:29 +00:00
// This is perfectly valid SCI behaviour
return;
}
scr->decrementLockers(); // One less locker
if (scr->getLockers() > 0)
return;
2009-02-20 23:09:29 +00:00
// Free all classtable references to this script
for (uint i = 0; i < segMan->_classtable.size(); i++)
if (segMan->_classtable[i].reg.segment == segment)
segMan->_classtable[i].reg = NULL_REG;
if (getSciVersion() < SCI_VERSION_1_1)
script_uninstantiate_sci0(segMan, script_nr, segment);
// FIXME: Add proper script uninstantiation for SCI 1.1
if (scr->getLockers())
2009-02-20 23:09:29 +00:00
return; // if xxx.lockers > 0
2009-02-20 23:09:29 +00:00
// Otherwise unload it completely
// Explanation: I'm starting to believe that this work is done by SCI itself.
scr->markDeleted();
debugC(kDebugLevelScripts, "Unloaded script 0x%x.\n", script_nr);
return;
}
static void _init_stack_base_with_selector(EngineState *s, Selector selector) {
s->stack_base[0] = make_reg(0, (uint16)selector);
s->stack_base[1] = NULL_REG;
}
static EngineState *_game_run(EngineState *&s, int restoring) {
EngineState *successor = NULL;
int game_is_finished = 0;
if (Common::isDebugChannelEnabled(kDebugLevelOnStartup))
((Sci::SciEngine*)g_engine)->getSciDebugger()->attach();
do {
s->_executionStackPosChanged = false;
run_vm(s, (successor || restoring) ? 1 : 0);
2009-02-20 23:09:29 +00:00
if (s->restarting_flags & SCI_GAME_IS_RESTARTING_NOW) { // Restart was requested?
successor = NULL;
s->_executionStack.clear();
s->_executionStackPosChanged = false;
game_exit(s);
script_init_engine(s);
game_init(s);
#ifdef USE_OLD_MUSIC_FUNCTIONS
s->_sound.sfx_reset_player();
#endif
_init_stack_base_with_selector(s, s->_kernel->_selectorCache.play);
send_selector(s, s->_gameObj, s->_gameObj, s->stack_base, 2, s->stack_base);
script_abort_flag = 0;
2009-02-20 23:09:29 +00:00
s->restarting_flags = SCI_GAME_WAS_RESTARTED | SCI_GAME_WAS_RESTARTED_AT_LEAST_ONCE;
} else {
successor = s->successor;
if (successor) {
game_exit(s);
delete s;
s = successor;
if (script_abort_flag == 2) {
debugC(2, kDebugLevelVM, "Restarting with replay()\n");
s->_executionStack.clear(); // Restart with replay
_init_stack_base_with_selector(s, s->_kernel->_selectorCache.replay);
send_selector(s, s->_gameObj, s->_gameObj, s->stack_base, 2, s->stack_base);
}
script_abort_flag = 0;
} else
game_is_finished = 1;
}
} while (!game_is_finished);
return s;
}
int game_run(EngineState **_s) {
EngineState *s = *_s;
debugC(2, kDebugLevelVM, "Calling %s::play()\n", s->_gameId.c_str());
_init_stack_base_with_selector(s, s->_kernel->_selectorCache.play); // Call the play selector
2009-02-20 23:09:29 +00:00
// Now: Register the first element on the execution stack-
if (!send_selector(s, s->_gameObj, s->_gameObj, s->stack_base, 2, s->stack_base)) {
Console *con = ((SciEngine *)g_engine)->getSciDebugger();
con->printObject(s->_gameObj);
warning("Failed to run the game! Aborting...");
return 1;
}
2009-02-20 23:09:29 +00:00
// and ENGAGE!
_game_run(*_s, 0);
debugC(2, kDebugLevelVM, "Game::play() finished.\n");
2009-02-20 23:09:29 +00:00
return 0;
}
2009-02-20 23:09:29 +00:00
void quit_vm() {
script_abort_flag = 1; // Terminate VM
g_debugState.seeking = kDebugSeekNothing;
g_debugState.runningStep = 0;
}
void shrink_execution_stack(EngineState *s, uint size) {
assert(s->_executionStack.size() >= size);
Common::List<ExecStack>::iterator iter;
iter = s->_executionStack.begin();
for (uint i = 0; i < size; ++i)
++iter;
s->_executionStack.erase(iter, s->_executionStack.end());
}
reg_t* ObjVarRef::getPointer(SegManager *segMan) const {
Object *o = segMan->getObject(obj);
if (!o) return 0;
return &(o->_variables[varindex]);
}
reg_t* ExecStack::getVarPointer(SegManager *segMan) const {
assert(type == EXEC_STACK_TYPE_VARSELECTOR);
return addr.varp.getPointer(segMan);
}
} // End of namespace Sci