scummvm/engines/sci/engine/vm.cpp
Filippos Karapetis e940bcff23 - Simplified SCI version detection a bit and clarified the different version feature flags (not used yet)
- Removed the version verification functions (they were only used for two specific cases, but the SCI executable reader is able to detect the exact SCI game version anyway, so there is no point in having these)
- Removed the empty GameFlags structure and replaced it with a 32-bit integer instead

svn-id: r40524
2009-05-13 16:52:41 +00:00

2105 lines
61 KiB
C++

/* 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 "sci/scicore/resource.h"
#include "sci/engine/state.h"
#include "sci/scicore/versions.h"
#include "sci/engine/intmap.h"
#include "sci/engine/kdebug.h"
#include "sci/engine/kernel.h"
#include "sci/engine/kernel_types.h"
#include "sci/engine/seg_manager.h"
#include "sci/engine/gc.h"
#include "sci/sfx/player.h"
namespace Sci {
reg_t NULL_REG = {0, 0};
//#define VM_DEBUG_SEND
#undef STRICT_SEND // Disallows variable sends with more than one parameter
#undef STRICT_READ // Disallows reading from out-of-bounds parameters and locals
int script_abort_flag = 0; // Set to 1 to abort execution
int script_error_flag = 0; // Set to 1 if an error occured, reset each round by the VM
int script_checkloads_flag = 0; // Print info when scripts get (un)loaded
int script_step_counter = 0; // Counts the number of steps executed
int script_gc_interval = GC_INTERVAL; // Number of steps in between gcs
extern int _debug_step_running;
extern int _debug_seeking;
extern int _weak_validations;
static bool breakpointFlag = false;
static reg_t _dummy_register;
// validation functionality
#ifndef DISABLE_VALIDATIONS
static reg_t &validate_property(Object *obj, int index) {
if (!obj) {
if (sci_debug_flags & 4)
sciprintf("[VM] Sending to disposed object!\n");
_dummy_register = NULL_REG;
return _dummy_register;
}
if (index < 0 || (uint)index >= obj->_variables.size()) {
if (sci_debug_flags & 4)
sciprintf("[VM] Invalid property #%d (out of [0..%d]) requested!\n", index,
obj->_variables.size());
_dummy_register = NULL_REG;
return _dummy_register;
}
return obj->_variables[index];
}
static StackPtr validate_stack_addr(EngineState *s, StackPtr sp) {
if (sp >= s->stack_base && sp < s->stack_top)
return sp;
script_debug_flag = script_error_flag = 1;
if (sci_debug_flags & 4)
sciprintf("[VM] Stack index %d out of valid range [%d..%d]\n", (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) {
if (!_weak_validations)
script_debug_flag = script_error_flag = 1;
if (sci_debug_flags & 4)
sciprintf("[VM] Attempt to read arithmetic value from non-zero segment [%04x]\n", reg.segment);
return 0;
}
return reg.offset;
}
static int signed_validate_arithmetic(reg_t reg) {
if (reg.segment) {
if (!_weak_validations)
script_debug_flag = script_error_flag = 1;
if (sci_debug_flags & 4)
sciprintf("[VM] Attempt to read arithmetic value from non-zero segment [%04x]\n", reg.segment);
return 0;
}
if (reg.offset&0x8000)
return (signed)(reg.offset) - 65536;
else
return reg.offset;
}
static int 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) {
sciprintf("[VM] Attempt to use invalid %s variable %04x ", names[type], index);
if (max == 0)
sciprintf("(variable type invalid)");
else
sciprintf("(out of range [%d..%d])", 0, max - 1);
sciprintf(" in %s, line %d\n", __FILE__, line);
if (!_weak_validations)
script_debug_flag = script_error_flag = 1;
#ifdef STRICT_READ
return 1;
#else // !STRICT_READ
if (type == VAR_PARAM || type == VAR_TEMP) {
int total_offset = r - stack_base;
if (total_offset < 0 || total_offset >= VM_STACK_SIZE) {
sciprintf("[VM] Access would be outside even of the stack (%d); access denied\n", total_offset);
return 1;
} else {
sciprintf("[VM] Access within stack boundaries; access granted.\n");
return 0;
}
};
#endif
}
return 0;
}
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) {
if (!validate_variable(r, stack_base, type, max, index, line))
r[index] = value;
}
# define ASSERT_ARITHMETIC(v) validate_arithmetic(v)
#else
// Non-validating alternatives
# define validate_stack_addr(s, sp) sp
# define validate_arithmetic(r) ((r).offset)
# 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) ((r)[i])
# define validate_write_var(r, sb, t, m, i, l, v) ((r)[i] = (v))
# define validate_property(o, p) ((o)->_variables[p])
# define ASSERT_ARITHMETIC(v) (v).offset
#endif
#define READ_VAR(type, index, def) validate_read_var(variables[type], s->stack_base, type, variables_max[type], index, __LINE__, def)
#define WRITE_VAR(type, index, value) validate_write_var(variables[type], s->stack_base, type, variables_max[type], index, __LINE__, value)
#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))
int script_error(EngineState *s, const char *file, int line, const char *reason) {
sciprintf("Script error in file %s, line %d: %s\n", file, line, reason);
script_debug_flag = script_error_flag = 1;
return 0;
}
#define CORE_ERROR(area, msg) script_error(s, "[" area "] " __FILE__, __LINE__, msg)
reg_t get_class_address(EngineState *s, int classnr, int lock, reg_t caller) {
if (NULL == s) {
warning("vm.c: get_class_address(): NULL passed for \"s\"");
return NULL_REG;
}
if (classnr < 0 || (int)s->_classtable.size() <= classnr || s->_classtable[classnr].script < 0) {
warning("[VM] Attempt to dereference class %x, which doesn't exist (max %x)", classnr, s->_classtable.size());
script_error_flag = script_debug_flag = 1;
return NULL_REG;
} else {
Class *the_class = &s->_classtable[classnr];
if (!the_class->reg.segment) {
script_get_segment(s, the_class->script, lock);
if (!the_class->reg.segment) {
warning("[VM] Trying to instantiate class %x by instantiating script 0x%x (%03d) failed;"
" Entering debugger.", classnr, the_class->script, the_class->script);
script_error_flag = script_debug_flag = 1;
return NULL_REG;
}
} else
if (caller.segment != the_class->reg.segment)
s->seg_manager->incrementLockers(the_class->reg.segment, SEG_ID);
return the_class->reg;
}
}
// Operating on the stack
// 16 bit:
#define PUSH(v) PUSH32(make_reg(0, v))
#define POP() (validate_arithmetic(POP32()))
// 32 bit:
#define PUSH32(a) (*(validate_stack_addr(s, (xs->sp)++)) = (a))
#define POP32() (*(validate_stack_addr(s, --(xs->sp))))
// Getting instruction parameters
#define GET_OP_BYTE() ((uint8)code_buf[(xs->addr.pc.offset)++])
#define GET_OP_WORD() (READ_LE_UINT16(code_buf + ((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[(xs->addr.pc.offset)++]))
#define GET_OP_SIGNED_WORD() (((int16)READ_LE_UINT16(code_buf + ((xs->addr.pc.offset) += 2) - 2)))
#define GET_OP_SIGNED_FLEX() ((opcode & 1)? GET_OP_SIGNED_BYTE() : GET_OP_SIGNED_WORD())
#define SEG_GET_HEAP(s, reg) s->seg_manager->getHeap(reg)
#define OBJ_SPECIES(s, reg) SEG_GET_HEAP(s, make_reg(reg.segment, reg.offset + SCRIPT_SPECIES_OFFSET))
// Returns an object's species
#define OBJ_SUPERCLASS(s, reg) SEG_GET_HEAP(s, make_reg(reg.segment, reg.offset + SCRIPT_SUPERCLASS_OFFSET))
// Returns an object's superclass
ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackPtr sp, reg_t calling_obj, uint16 argc, StackPtr argp) {
int seg;
uint16 temp;
if (!s->seg_manager->scriptIsLoaded(script, SCRIPT_ID)) // Script not present yet?
script_instantiate(s, script);
else
s->seg_manager->unmarkScriptDeleted(script);
seg = s->seg_manager->segGet(script);
temp = s->seg_manager->validateExportFunc(pubfunct, seg);
if (!temp) {
sciprintf("Request for invalid exported function 0x%x of script 0x%x\n", pubfunct, script);
script_error_flag = script_debug_flag = 1;
return NULL;
}
// 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) {
sciprintf("Break on script %d, export %d\n", script, pubfunct);
script_debug_flag = 1;
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->execution_stack_pos, seg);
}
static void _exec_varselectors(EngineState *s) {
// Executes all varselector read/write ops on the TOS
// Now check the TOS to execute all varselector entries
if (s->execution_stack_pos >= 0)
while (s->_executionStack[s->execution_stack_pos].type == EXEC_STACK_TYPE_VARSELECTOR) {
// varselector access?
if (s->_executionStack[s->execution_stack_pos].argc) { // write?
reg_t temp = s->_executionStack[s->execution_stack_pos].variables_argp[1];
*(s->_executionStack[s->execution_stack_pos].addr.varp) = temp;
} else // No, read
s->r_acc = *(s->_executionStack[s->execution_stack_pos].addr.varp);
--(s->execution_stack_pos);
}
}
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
#ifdef VM_DEBUG_SEND
int i;
#endif
reg_t *varp;
reg_t funcp;
int selector;
int argc;
int origin = s->execution_stack_pos; // Origin: Used for debugging
int print_send_action = 0;
// We return a pointer to the new active ExecStack
// The selector calls we catch are stored below:
Common::Stack<CallsStruct> sendCalls;
if (NULL == s) {
sciprintf("vm.c: ExecStack(): NULL passed for \"s\"\n");
return NULL;
}
while (framesize > 0) {
selector = validate_arithmetic(*argp++);
argc = validate_arithmetic(*argp);
if (argc > 0x800) { // More arguments than the stack could possibly accomodate for
CORE_ERROR("SEND", "More than 0x800 arguments to function call\n");
return NULL;
}
// 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", obj_get_name(s, send_obj), s->_selectorNames[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)) {
sciprintf("Break on %s (in ["PREG"])\n", method_name, PRINT_REG(send_obj));
script_debug_flag = print_send_action = 1;
breakpointFlag = true;
break;
}
bp = bp->next;
}
}
#ifdef VM_DEBUG_SEND
sciprintf("Send to "PREG", selector %04x (%s):", PRINT_REG(send_obj), selector, s->_selectorNames[selector].c_str());
#endif // VM_DEBUG_SEND
switch (lookup_selector(s, send_obj, selector, &varp, &funcp)) {
case kSelectorNone:
sciprintf("Send to invalid selector 0x%x of object at "PREG"\n", 0xffff & selector, PRINT_REG(send_obj));
script_error_flag = script_debug_flag = 1;
break;
case kSelectorVariable:
#ifdef VM_DEBUG_SEND
sciprintf("Varselector: ");
if (argc)
sciprintf("Write "PREG"\n", PRINT_REG(argp[1]));
else
sciprintf("Read\n");
#endif // VM_DEBUG_SEND
switch (argc) {
case 0: // Read selector
if (print_send_action) {
sciprintf("[read selector]\n");
print_send_action = 0;
}
// fallthrough
case 1:
#ifndef STRICT_SEND
default:
#endif
{ // Argument is supplied -> Selector should be set
if (print_send_action) {
reg_t oldReg = *varp;
reg_t newReg = argp[1];
sciprintf("[write to selector: change "PREG" to "PREG"]\n", PRINT_REG(oldReg), PRINT_REG(newReg));
print_send_action = 0;
}
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;
#ifdef STRICT_SEND
default:
sciprintf("Send error: Variable selector %04x in "PREG" called with %04x params\n", selector, PRINT_REG(send_obj), argc);
script_debug_flag = 1; // Enter debug mode
_debug_seeking = _debug_step_running = 0;
#endif
}
break;
case kSelectorMethod:
#ifdef VM_DEBUG_SEND
sciprintf("Funcselector(");
for (i = 0; i < argc; i++) {
sciprintf(PREG, PRINT_REG(argp[i+1]));
if (i + 1 < argc)
sciprintf(", ");
}
sciprintf(") at "PREG"\n", PRINT_REG(funcp));
#endif // VM_DEBUG_SEND
if (print_send_action) {
sciprintf("[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;
}
// 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);
return &(s->_executionStack[s->execution_stack_pos]);
}
ExecStack *add_exec_stack_varselector(EngineState *s, reg_t objp, int argc, StackPtr argp, Selector selector, reg_t *address, int origin) {
ExecStack *xstack = add_exec_stack_entry(s, NULL_REG, address, objp, argc, argp, selector, objp, origin, SCI_XS_CALLEE_LOCALS);
// 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 locals_segment) {
// Returns new TOS element for the execution stack
// locals_segment may be -1 if derived from the called object
++s->execution_stack_pos;
if (s->execution_stack_pos >= (int)s->_executionStack.size()) // Out of stack space?
s->_executionStack.resize(s->execution_stack_pos+1);
//sciprintf("Exec stack: [%d/%d], origin %d, at %p\n", s->execution_stack_pos, s->_executionStack.size(), origin, s->execution_stack);
ExecStack *xstack = &(s->_executionStack[s->execution_stack_pos]);
xstack->objp = objp;
if (locals_segment != SCI_XS_CALLEE_LOCALS)
xstack->local_segment = locals_segment;
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->selector = selector;
xstack->origin = origin;
xstack->type = EXEC_STACK_TYPE_CALL; // Normal call
return xstack;
}
#ifdef DISABLE_VALIDATONS
# define kernel_matches_signature(a, b, c, d) 1
#endif
void vm_handle_fatal_error(EngineState *s, int line, const char *file) {
fprintf(stderr, "Fatal VM error in %s, L%d; aborting...\n", file, line);
error("Could not recover, exitting...\n");
}
static Script *script_locate_by_segment(EngineState *s, SegmentId seg) {
MemObject *memobj = GET_SEGMENT(*s->seg_manager, seg, MEM_OBJ_SCRIPT);
if (memobj)
return (Script *)memobj;
return NULL;
}
static reg_t pointer_add(EngineState *s, reg_t base, int offset) {
MemObject *mobj = GET_SEGMENT_ANY(*s->seg_manager, base.segment);
if (!mobj) {
script_debug_flag = script_error_flag = 1;
sciprintf("[VM] Error: Attempt to add %d to invalid pointer "PREG"!", offset, PRINT_REG(base));
return NULL_REG;
}
switch (mobj->getType()) {
case MEM_OBJ_LOCALS:
base.offset += 2 * offset;
return base;
case MEM_OBJ_SCRIPT:
case MEM_OBJ_STACK:
case MEM_OBJ_DYNMEM:
base.offset += offset;
return base;
break;
default:
sciprintf("[VM] Error: Attempt to add %d to pointer "PREG": Pointer arithmetics of this type unsupported!", offset, PRINT_REG(base));
return NULL_REG;
}
}
static void gc_countdown(EngineState *s) {
if (s->gc_countdown-- <= 0) {
s->gc_countdown = script_gc_interval;
run_gc(s);
}
}
static const byte _fake_return_buffer[2] = {op_ret << 1, op_ret << 1};
void run_vm(EngineState *s, int restoring) {
reg_t *variables[4]; // global, local, temp, param, as immediate pointers
reg_t *variables_base[4]; // Used for referencing VM ops
SegmentId variables_seg[4]; // Same as above, contains segment IDs
#ifndef DISABLE_VALIDATIONS
int variables_max[4]; // Max. values for all variables
unsigned int code_buf_size = 0 ; // (Avoid spurious warning)
#endif
int temp;
int16 aux_acc; // Auxiliary 16 bit accumulator
reg_t r_temp; // Temporary register
StackPtr s_temp; // Temporary stack pointer
int16 opparams[4]; // opcode parameters
int restadjust = s->r_amp_rest;
// &rest adjusts the parameter count by this value
// Current execution data:
ExecStack *xs = &(s->_executionStack[s->execution_stack_pos]);
ExecStack *xs_new = NULL;
Object *obj = obj_get(s, xs->objp);
Script *local_script = script_locate_by_segment(s, xs->local_segment);
int old_execution_stack_base = s->execution_stack_base;
// Used to detect the stack bottom, for "physical" returns
const byte *code_buf = NULL; // (Avoid spurious warning)
if (!local_script) {
script_error(s, __FILE__, __LINE__, "Program Counter gone astray");
return;
}
if (NULL == s) {
sciprintf("vm.c: run_vm(): NULL passed for \"s\"\n");
return;
}
if (!restoring)
s->execution_stack_base = s->execution_stack_pos;
#ifndef DISABLE_VALIDATIONS
// Initialize maximum variable count
if (s->script_000->locals_block)
variables_max[VAR_GLOBAL] = s->script_000->locals_block->_locals.size();
else
variables_max[VAR_GLOBAL] = 0;
#endif
variables_seg[VAR_GLOBAL] = s->script_000->locals_segment;
variables_seg[VAR_TEMP] = variables_seg[VAR_PARAM] = s->stack_segment;
variables_base[VAR_TEMP] = variables_base[VAR_PARAM] = s->stack_base;
// SCI code reads the zeroeth argument to determine argc
if (s->script_000->locals_block)
variables_base[VAR_GLOBAL] = variables[VAR_GLOBAL] = s->script_000->locals_block->_locals.begin();
else
variables_base[VAR_GLOBAL] = variables[VAR_GLOBAL] = NULL;
s->_executionStackPosChanged = true; // Force initialization
while (1) {
byte opcode;
int old_pc_offset;
StackPtr old_sp = xs->sp;
byte opnumber;
int var_type; // See description below
int var_number;
old_pc_offset = xs->addr.pc.offset;
if (s->_executionStackPosChanged) {
Script *scr;
xs = &(s->_executionStack[s->execution_stack_pos]);
s->_executionStackPosChanged = false;
scr = script_locate_by_segment(s, xs->addr.pc.segment);
if (!scr) {
// No script? Implicit return via fake instruction buffer
warning("Running on non-existant script in segment %x!", xs->addr.pc.segment);
code_buf = _fake_return_buffer;
#ifndef DISABLE_VALIDATIONS
code_buf_size = 2;
#endif
xs->addr.pc.offset = 1;
scr = NULL;
obj = NULL;
} else {
obj = obj_get(s, xs->objp);
code_buf = scr->buf;
#ifndef DISABLE_VALIDATIONS
code_buf_size = scr->buf_size;
#endif
/*if (!obj) {
SCIkdebug(SCIkWARNING, "Running with non-existant self= "PREG"\n", PRINT_REG(xs->objp));
}*/
local_script = script_locate_by_segment(s, xs->local_segment);
if (!local_script) {
warning("Could not find local script from segment %x", xs->local_segment);
local_script = NULL;
variables_base[VAR_LOCAL] = variables[VAR_LOCAL] = NULL;
#ifndef DISABLE_VALIDATIONS
variables_max[VAR_LOCAL] = 0;
#endif
} else {
variables_seg[VAR_LOCAL] = local_script->locals_segment;
if (local_script->locals_block)
variables_base[VAR_LOCAL] = variables[VAR_LOCAL] = local_script->locals_block->_locals.begin();
else
variables_base[VAR_LOCAL] = variables[VAR_LOCAL] = NULL;
#ifndef DISABLE_VALIDATIONS
if (local_script->locals_block)
variables_max[VAR_LOCAL] = local_script->locals_block->_locals.size();
else
variables_max[VAR_LOCAL] = 0;
variables_max[VAR_TEMP] = xs->sp - xs->fp;
variables_max[VAR_PARAM] = xs->argc + 1;
#endif
}
variables[VAR_TEMP] = xs->fp;
variables[VAR_PARAM] = xs->variables_argp;
}
}
script_error_flag = 0; // Set error condition to false
if (script_abort_flag)
return; // Emergency
// Debug if this has been requested:
if (script_debug_flag || sci_debug_flags) {
script_debug(s, &(xs->addr.pc), &(xs->sp), &(xs->fp), &(xs->objp), &restadjust, variables_seg, variables, variables_base,
#ifdef DISABLE_VALIDATIONS
NULL,
#else
variables_max,
#endif
breakpointFlag);
breakpointFlag = false;
}
#ifndef DISABLE_VALIDATIONS
if (xs->sp < xs->fp)
script_error(s, "[VM] "__FILE__, __LINE__, "Stack underflow");
variables_max[VAR_TEMP] = xs->sp - xs->fp;
if (xs->addr.pc.offset >= code_buf_size)
script_error(s, "[VM] "__FILE__, __LINE__, "Program Counter gone astray");
#endif
opcode = GET_OP_BYTE(); // Get opcode
opnumber = opcode >> 1;
for (temp = 0; g_opcode_formats[opnumber][temp]; 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:
sciprintf("opcode %02x: Invalid!", opcode);
script_debug_flag = script_error_flag = 1;
}
// TODO: Replace the following by an opcode table, and several methods for
// each opcode.
switch (opnumber) {
case 0x00: // bnot
s->r_acc = ACC_ARITHMETIC_L(0xffff ^ /*acc*/);
break;
case 0x01: // add
r_temp = POP32();
if (r_temp.segment || s->r_acc.segment) {
reg_t r_ptr;
int offset;
// Pointer arithmetics!
if (s->r_acc.segment) {
if (r_temp.segment) {
sciprintf("Error: Attempt to add two pointers, stack="PREG" and acc="PREG"!\n",
PRINT_REG(r_temp), PRINT_REG(s->r_acc));
script_debug_flag = script_error_flag = 1;
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 0x02: // sub
r_temp = POP32();
if (r_temp.segment || s->r_acc.segment) {
reg_t r_ptr;
int offset;
// Pointer arithmetics!
if (s->r_acc.segment) {
if (r_temp.segment) {
sciprintf("Error: Attempt to subtract two pointers, stack="PREG" and acc="PREG"!\n",
PRINT_REG(r_temp), PRINT_REG(s->r_acc));
script_debug_flag = script_error_flag = 1;
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 0x03: // mul
s->r_acc = ACC_ARITHMETIC_L(((int16)POP()) * (int16)/*acc*/);
break;
case 0x04: // div
ACC_AUX_LOAD();
aux_acc = aux_acc != 0 ? ((int16)POP()) / aux_acc : 0;
ACC_AUX_STORE();
break;
case 0x05: // mod
ACC_AUX_LOAD();
aux_acc = aux_acc != 0 ? ((int16)POP()) % aux_acc : 0;
ACC_AUX_STORE();
break;
case 0x06: // shr
s->r_acc = ACC_ARITHMETIC_L(((uint16) POP()) >> /*acc*/);
break;
case 0x07: // shl
s->r_acc = ACC_ARITHMETIC_L(((uint16)POP()) << /*acc*/);
break;
case 0x08: // xor
s->r_acc = ACC_ARITHMETIC_L(POP() ^ /*acc*/);
break;
case 0x09: // and
s->r_acc = ACC_ARITHMETIC_L(POP() & /*acc*/);
break;
case 0x0a: // or
s->r_acc = ACC_ARITHMETIC_L(POP() | /*acc*/);
break;
case 0x0b: // neg
s->r_acc = ACC_ARITHMETIC_L(-/*acc*/);
break;
case 0x0c: // not
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 0x0d: // eq?
s->r_prev = s->r_acc;
r_temp = POP32();
s->r_acc = make_reg(0, REG_EQ(r_temp, s->r_acc));
// Explicitly allow pointers to be compared
break;
case 0x0e: // ne?
s->r_prev = s->r_acc;
r_temp = POP32();
s->r_acc = make_reg(0, !REG_EQ(r_temp, s->r_acc));
// Explicitly allow pointers to be compared
break;
case 0x0f: // gt?
s->r_prev = s->r_acc;
s->r_acc = ACC_ARITHMETIC_L((int16)POP() > (int16)/*acc*/);
break;
case 0x10: // ge?
s->r_prev = s->r_acc;
s->r_acc = ACC_ARITHMETIC_L((int16)POP() >= (int16)/*acc*/);
break;
case 0x11: // lt?
s->r_prev = s->r_acc;
s->r_acc = ACC_ARITHMETIC_L((int16)POP() < (int16)/*acc*/);
break;
case 0x12: // le?
s->r_prev = s->r_acc;
s->r_acc = ACC_ARITHMETIC_L((int16)POP() <= (int16)/*acc*/);
break;
case 0x13: // ugt?
s->r_prev = s->r_acc;
r_temp = POP32();
s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset);
break;
case 0x14: // uge?
s->r_prev = s->r_acc;
r_temp = POP32();
s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset >= s->r_acc.offset);
break;
case 0x15: // ult?
s->r_prev = s->r_acc;
r_temp = POP32();
s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset < s->r_acc.offset);
break;
case 0x16: // ule?
s->r_prev = s->r_acc;
r_temp = POP32();
s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset <= s->r_acc.offset);
break;
case 0x17: // bt
if (s->r_acc.offset || s->r_acc.segment)
xs->addr.pc.offset += opparams[0];
break;
case 0x18: // bnt
if (!(s->r_acc.offset || s->r_acc.segment))
xs->addr.pc.offset += opparams[0];
break;
case 0x19: // jmp
xs->addr.pc.offset += opparams[0];
break;
case 0x1a: // ldi
s->r_acc = make_reg(0, opparams[0]);
break;
case 0x1b: // push
PUSH32(s->r_acc);
break;
case 0x1c: // pushi
PUSH(opparams[0]);
break;
case 0x1d: // toss
xs->sp--;
break;
case 0x1e: // dup
r_temp = xs->sp[-1];
PUSH32(r_temp);
break;
case 0x1f: { // link
int i;
for (i = 0; i < opparams[0]; i++)
xs->sp[i] = NULL_REG;
xs->sp += opparams[0];
break;
}
case 0x20: { // call
int argc = (opparams[1] >> 1) // Given as offset, but we need count
+ 1 + restadjust;
StackPtr call_base = xs->sp - argc;
xs->sp[1].offset += restadjust;
xs_new = add_exec_stack_entry(s, make_reg(xs->addr.pc.segment, xs->addr.pc.offset + opparams[0]),
xs->sp, xs->objp, (validate_arithmetic(*call_base)) + restadjust,
call_base, NULL_SELECTOR, xs->objp, s->execution_stack_pos, xs->local_segment);
restadjust = 0; // Used up the &rest adjustment
xs->sp = call_base;
s->_executionStackPosChanged = true;
break;
}
case 0x21: // callk
gc_countdown(s);
xs->sp -= (opparams[1] >> 1) + 1;
if (s->version >= SCI_VERSION_FTU_NEW_SCRIPT_HEADER) {
xs->sp -= restadjust;
s->r_amp_rest = 0; // We just used up the restadjust, remember?
}
if (opparams[0] >= (int)s->_kfuncTable.size()) {
sciprintf("Invalid kernel function 0x%x requested\n", opparams[0]);
script_debug_flag = script_error_flag = 1;
} else {
int argc = ASSERT_ARITHMETIC(xs->sp[0]);
if (s->version >= SCI_VERSION_FTU_NEW_SCRIPT_HEADER)
argc += restadjust;
if (s->_kfuncTable[opparams[0]].signature
&& !kernel_matches_signature(s, s->_kfuncTable[opparams[0]].signature, argc, xs->sp + 1)) {
sciprintf("[VM] Invalid arguments to kernel call %x\n", opparams[0]);
script_debug_flag = script_error_flag = 1;
} else {
s->r_acc = s->_kfuncTable[opparams[0]].fun(s, opparams[0], argc, xs->sp + 1);
}
// Call kernel function
// Calculate xs again: The kernel function might
// have spawned a new VM
xs_new = &(s->_executionStack[s->execution_stack_pos]);
s->_executionStackPosChanged = true;
if (s->version >= SCI_VERSION_FTU_NEW_SCRIPT_HEADER)
restadjust = s->r_amp_rest;
}
break;
case 0x22: // callb
temp = ((opparams[1] >> 1) + restadjust + 1);
s_temp = xs->sp;
xs->sp -= temp;
xs->sp[0].offset += restadjust;
xs_new = execute_method(s, 0, opparams[0], s_temp, xs->objp, xs->sp[0].offset, xs->sp);
restadjust = 0; // Used up the &rest adjustment
if (xs_new) // in case of error, keep old stack
s->_executionStackPosChanged = true;
break;
case 0x23: // calle
temp = ((opparams[2] >> 1) + restadjust + 1);
s_temp = xs->sp;
xs->sp -= temp;
xs->sp[0].offset += restadjust;
xs_new = execute_method(s, opparams[0], opparams[1], s_temp, xs->objp, xs->sp[0].offset, xs->sp);
restadjust = 0; // Used up the &rest adjustment
if (xs_new) // in case of error, keep old stack
s->_executionStackPosChanged = true;
break;
case 0x24: // ret
do {
StackPtr old_sp2 = xs->sp;
StackPtr old_fp = xs->fp;
ExecStack *old_xs = &(s->_executionStack[s->execution_stack_pos]);
if (s->execution_stack_pos == s->execution_stack_base) { // Have we reached the base?
s->execution_stack_base = old_execution_stack_base; // Restore stack base
--(s->execution_stack_pos);
s->_executionStackPosChanged = true;
s->r_amp_rest = restadjust; // Update &rest
return; // "Hard" return
}
if (old_xs->type == EXEC_STACK_TYPE_VARSELECTOR) {
// varselector access?
if (old_xs->argc) // write?
*(old_xs->addr.varp) = old_xs->variables_argp[1];
else // No, read
s->r_acc = *(old_xs->addr.varp);
}
// Not reached the base, so let's do a soft return
--(s->execution_stack_pos);
xs = old_xs - 1;
s->_executionStackPosChanged = true;
xs = &(s->_executionStack[s->execution_stack_pos]);
if (xs->sp == CALL_SP_CARRY // Used in sends to 'carry' the stack pointer
|| xs->type != EXEC_STACK_TYPE_CALL) {
xs->sp = old_sp2;
xs->fp = old_fp;
}
} while (xs->type == EXEC_STACK_TYPE_VARSELECTOR);
// Iterate over all varselector accesses
s->_executionStackPosChanged = true;
xs_new = xs;
break;
case 0x25: // send
s_temp = xs->sp;
xs->sp -= ((opparams[0] >> 1) + restadjust); // Adjust stack
xs->sp[1].offset += restadjust;
xs_new = send_selector(s, s->r_acc, s->r_acc, s_temp, (int)(opparams[0] >> 1) + (uint16)restadjust, xs->sp);
if (xs_new && xs_new != xs)
s->_executionStackPosChanged = true;
restadjust = 0;
break;
case 0x28: // class
s->r_acc = get_class_address(s, (unsigned)opparams[0], SCRIPT_GET_LOCK, xs->addr.pc);
break;
case 0x2a: // self
s_temp = xs->sp;
xs->sp -= ((opparams[0] >> 1) + restadjust); // Adjust stack
xs->sp[1].offset += restadjust;
xs_new = send_selector(s, xs->objp, xs->objp, s_temp, (int)(opparams[0] >> 1) + (uint16)restadjust, xs->sp);
if (xs_new && xs_new != xs)
s->_executionStackPosChanged = true;
restadjust = 0;
break;
case 0x2b: // super
r_temp = get_class_address(s, opparams[0], SCRIPT_GET_LOAD, xs->addr.pc);
if (!r_temp.segment)
CORE_ERROR("VM", "Invalid superclass in object");
else {
s_temp = xs->sp;
xs->sp -= ((opparams[1] >> 1) + restadjust); // Adjust stack
xs->sp[1].offset += restadjust;
xs_new = send_selector(s, r_temp, xs->objp, s_temp, (int)(opparams[1] >> 1) + (uint16)restadjust, xs->sp);
if (xs_new && xs_new != xs)
s->_executionStackPosChanged = true;
restadjust = 0;
}
break;
case 0x2c: // &rest
temp = (uint16) opparams[0]; // First argument
restadjust = xs->argc - temp + 1; // +1 because temp counts the paramcount while argc doesn't
if (restadjust < 0)
restadjust = 0;
for (; temp <= xs->argc; temp++)
PUSH32(xs->variables_argp[temp]);
break;
case 0x2d: // lea
temp = (uint16) opparams[0] >> 1;
var_number = temp & 0x03; // Get variable type
// Get variable block offset
r_temp.segment = variables_seg[var_number];
r_temp.offset = variables[var_number] - variables_base[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 *= sizeof(reg_t);
// That's the immediate address now
s->r_acc = r_temp;
break;
case 0x2e: // selfID
s->r_acc = xs->objp;
break;
case 0x30: // pprev
PUSH32(s->r_prev);
break;
case 0x31: // pToa
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1));
break;
case 0x32: // aTop
OBJ_PROPERTY(obj, (opparams[0] >> 1)) = s->r_acc;
break;
case 0x33: // pTos
PUSH32(OBJ_PROPERTY(obj, opparams[0] >> 1));
break;
case 0x34: // sTop
OBJ_PROPERTY(obj, (opparams[0] >> 1)) = POP32();
break;
case 0x35: // ipToa
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1));
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(1 + /*acc*/);
break;
case 0x36: // dpToa
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1));
s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(-1 + /*acc*/);
break;
case 0x37: // ipTos
ASSERT_ARITHMETIC(OBJ_PROPERTY(obj, (opparams[0] >> 1)));
temp = ++OBJ_PROPERTY(obj, (opparams[0] >> 1)).offset;
PUSH(temp);
break;
case 0x38: // dpTos
ASSERT_ARITHMETIC(OBJ_PROPERTY(obj, (opparams[0] >> 1)));
temp = --OBJ_PROPERTY(obj, (opparams[0] >> 1)).offset;
PUSH(temp);
break;
case 0x39: // lofsa
s->r_acc.segment = xs->addr.pc.segment;
if (s->version >= SCI_VERSION(1, 001, 000))
s->r_acc.offset = opparams[0] + local_script->script_size;
else
if (s->version >= SCI_VERSION_FTU_LOFS_ABSOLUTE)
s->r_acc.offset = opparams[0];
else
s->r_acc.offset = xs->addr.pc.offset + opparams[0];
#ifndef DISABLE_VALIDATIONS
if (s->r_acc.offset >= code_buf_size) {
sciprintf("VM: lofsa operation overflowed: "PREG" beyond end"
" of script (at %04x)\n", PRINT_REG(s->r_acc), code_buf_size);
script_error_flag = script_debug_flag = 1;
}
#endif
break;
case 0x3a: // lofss
r_temp.segment = xs->addr.pc.segment;
if (s->version >= SCI_VERSION_FTU_LOFS_ABSOLUTE)
r_temp.offset = opparams[0];
else
r_temp.offset = xs->addr.pc.offset + opparams[0];
#ifndef DISABLE_VALIDATIONS
if (r_temp.offset >= code_buf_size) {
sciprintf("VM: lofss operation overflowed: "PREG" beyond end"
" of script (at %04x)\n", PRINT_REG(r_temp), code_buf_size);
script_error_flag = script_debug_flag = 1;
}
#endif
PUSH32(r_temp);
break;
case 0x3b: // push0
PUSH(0);
break;
case 0x3c: // push1
PUSH(1);
break;
case 0x3d: // push2
PUSH(2);
break;
case 0x3e: // pushSelf
PUSH32(xs->objp);
break;
case 0x40: // lag
case 0x41: // lal
case 0x42: // lat
case 0x43: // lap
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 0x44: // lsg
case 0x45: // lsl
case 0x46: // lst
case 0x47: // lsp
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 0x48: // lagi
case 0x49: // lali
case 0x4a: // lati
case 0x4b: // lapi
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 0x4c: // lsgi
case 0x4d: // lsli
case 0x4e: // lsti
case 0x4f: // lspi
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 0x50: // sag
case 0x51: // sal
case 0x52: // sat
case 0x53: // sap
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 0x54: // ssg
case 0x55: // ssl
case 0x56: // sst
case 0x57: // ssp
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 0x58: // sagi
case 0x59: // sali
case 0x5a: // sati
case 0x5b: // sapi
// 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 >> 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 0x5c: // ssgi
case 0x5d: // ssli
case 0x5e: // ssti
case 0x5f: // sspi
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 0x60: // +ag
case 0x61: // +al
case 0x62: // +at
case 0x63: // +ap
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
s->r_acc = make_reg(0, 1 + validate_arithmetic(READ_VAR(var_type, var_number, s->r_acc)));
WRITE_VAR(var_type, var_number, s->r_acc);
break;
case 0x64: // +sg
case 0x65: // +sl
case 0x66: // +st
case 0x67: // +sp
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
r_temp = make_reg(0, 1 + validate_arithmetic(READ_VAR(var_type, var_number, s->r_acc)));
PUSH32(r_temp);
WRITE_VAR(var_type, var_number, r_temp);
break;
case 0x68: // +agi
case 0x69: // +ali
case 0x6a: // +ati
case 0x6b: // +api
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 = make_reg(0, 1 + validate_arithmetic(READ_VAR(var_type, var_number, s->r_acc)));
WRITE_VAR(var_type, var_number, s->r_acc);
break;
case 0x6c: // +sgi
case 0x6d: // +sli
case 0x6e: // +sti
case 0x6f: // +spi
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 = make_reg(0, 1 + validate_arithmetic(READ_VAR(var_type, var_number, s->r_acc)));
PUSH32(r_temp);
WRITE_VAR(var_type, var_number, r_temp);
break;
case 0x70: // -ag
case 0x71: // -al
case 0x72: // -at
case 0x73: // -ap
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
s->r_acc = make_reg(0, -1 + validate_arithmetic(READ_VAR(var_type, var_number, s->r_acc)));
WRITE_VAR(var_type, var_number, s->r_acc);
break;
case 0x74: // -sg
case 0x75: // -sl
case 0x76: // -st
case 0x77: // -sp
var_type = (opcode >> 1) & 0x3; // Gets the variable type: g, l, t or p
var_number = opparams[0];
r_temp = make_reg(0, -1 + validate_arithmetic(READ_VAR(var_type, var_number, s->r_acc)));
PUSH32(r_temp);
WRITE_VAR(var_type, var_number, r_temp);
break;
case 0x78: // -agi
case 0x79: // -ali
case 0x7a: // -ati
case 0x7b: // -api
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 = make_reg(0, -1 + validate_arithmetic(READ_VAR(var_type, var_number, s->r_acc)));
WRITE_VAR(var_type, var_number, s->r_acc);
break;
case 0x7c: // -sgi
case 0x7d: // -sli
case 0x7e: // -sti
case 0x7f: // -spi
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 = make_reg(0, -1 + validate_arithmetic(READ_VAR(var_type, var_number, s->r_acc)));
PUSH32(r_temp);
WRITE_VAR(var_type, var_number, r_temp);
break;
default:
script_error(s, __FILE__, __LINE__, "Illegal opcode");
} // switch(opcode >> 1)
if (s->_executionStackPosChanged) // Force initialization
xs = xs_new;
#ifndef DISABLE_VALIDATIONS
if (xs != &(s->_executionStack[s->execution_stack_pos])) {
sciprintf("Error: xs is stale (%d vs %d); last command was %02x\n", (int)(xs - &s->_executionStack[0]), s->execution_stack_pos, opnumber);
}
#endif
if (script_error_flag) {
_debug_step_running = 0; // Stop multiple execution
_debug_seeking = 0; // Stop special seeks
xs->addr.pc.offset = old_pc_offset;
xs->sp = old_sp;
} else
++script_step_counter;
}
}
static int _obj_locate_varselector(EngineState *s, Object *obj, Selector slc) {
// Determines if obj explicitly defines slc as a varselector
// Returns -1 if not found
if (s->version < SCI_VERSION(1, 001, 000)) {
int varnum = obj->variable_names_nr;
int selector_name_offset = varnum * 2 + SCRIPT_SELECTOR_OFFSET;
int i;
byte *buf = obj->base_obj + selector_name_offset;
obj->base_vars = (uint16 *) buf;
for (i = 0; i < varnum; i++)
if (READ_LE_UINT16(buf + (i << 1)) == slc) // Found it?
return i; // report success
return -1; // Failed
} else {
byte *buf = (byte *) obj->base_vars;
int i;
int varnum = obj->_variables[1].offset;
if (!(obj->_variables[SCRIPT_INFO_SELECTOR].offset & SCRIPT_INFO_CLASS))
buf = ((byte *) obj_get(s, obj->_variables[SCRIPT_SUPERCLASS_SELECTOR])->base_vars);
for (i = 0; i < varnum; i++)
if (READ_LE_UINT16(buf + (i << 1)) == slc) // Found it?
return i; // report success
return -1; // Failed
}
}
static int _class_locate_funcselector(EngineState *s, Object *obj, Selector slc) {
// Determines if obj is a class and explicitly defines slc as a funcselector
// Does NOT say anything about obj's superclasses, i.e. failure may be
// returned even if one of the superclasses defines the funcselector.
int funcnum = obj->methods_nr;
int i;
for (i = 0; i < funcnum; i++)
if (VM_OBJECT_GET_FUNCSELECTOR(obj, i) == slc) // Found it?
return i; // report success
return -1; // Failed
}
static SelectorType _lookup_selector_function(EngineState *s, int seg_id, Object *obj, Selector selector_id, reg_t *fptr) {
int index;
// "recursive" lookup
while (obj) {
index = _class_locate_funcselector(s, obj, selector_id);
if (index >= 0) {
if (fptr) {
if (s->version < SCI_VERSION(1, 001, 000))
*fptr = make_reg(obj->pos.segment, READ_LE_UINT16((byte *)(obj->base_method + index + obj->methods_nr + 1)));
else
*fptr = make_reg(obj->pos.segment, READ_LE_UINT16((byte *)(obj->base_method + index * 2 + 2)));
}
return kSelectorMethod;
} else {
seg_id = obj->_variables[SCRIPT_SUPERCLASS_SELECTOR].segment;
obj = obj_get(s, obj->_variables[SCRIPT_SUPERCLASS_SELECTOR]);
}
}
return kSelectorNone;
}
SelectorType lookup_selector(EngineState *s, reg_t obj_location, Selector selector_id, reg_t **vptr, reg_t *fptr) {
Object *obj = obj_get(s, obj_location);
Object *species;
int index;
// Early SCI versions used the LSB in the selector ID as a read/write
// toggle, meaning that we must remove it for selector lookup.
if (s->version < SCI_VERSION_FTU_NEW_SCRIPT_HEADER)
selector_id &= ~1;
if (!obj) {
CORE_ERROR("SLC-LU", "Attempt to send to non-object or invalid script");
sciprintf("Address was "PREG"\n", PRINT_REG(obj_location));
return kSelectorNone;
}
if (IS_CLASS(obj))
species = obj;
else
species = obj_get(s, obj->_variables[SCRIPT_SPECIES_SELECTOR]);
if (!obj) {
CORE_ERROR("SLC-LU", "Error while looking up Species class");
sciprintf("Original address was "PREG"\n", PRINT_REG(obj_location));
sciprintf("Species address was "PREG"\n", PRINT_REG(obj->_variables[SCRIPT_SPECIES_SELECTOR]));
return kSelectorNone;
}
index = _obj_locate_varselector(s, obj, selector_id);
if (index >= 0) {
// Found it as a variable
if (vptr)
*vptr = &obj->_variables[index];
return kSelectorVariable;
}
return _lookup_selector_function(s, obj_location.segment, obj, selector_id, fptr);
}
SegmentId script_get_segment(EngineState *s, int script_nr, int load) {
SegmentId segment;
if ((load & SCRIPT_GET_LOAD) == SCRIPT_GET_LOAD)
script_instantiate(s, script_nr);
segment = s->seg_manager->segGet(script_nr);
if (segment > 0) {
if ((load & SCRIPT_GET_LOCK) == SCRIPT_GET_LOCK)
s->seg_manager->incrementLockers(segment, SEG_ID);
return segment;
} else
return 0;
}
reg_t script_lookup_export(EngineState *s, int script_nr, int export_index) {
SegmentId seg = script_get_segment(s, script_nr, SCRIPT_GET_DONT_LOAD);
MemObject *memobj;
Script *script = NULL;
#ifndef DISABLE_VALIDATIONS
if (!seg) {
CORE_ERROR("EXPORTS", "Script invalid or not loaded");
sciprintf("Script was script.%03d (0x%x)\n",
script_nr, script_nr);
return NULL_REG;
}
#endif
memobj = GET_SEGMENT(*s->seg_manager, seg, MEM_OBJ_SCRIPT);
if (memobj)
script = (Script *)memobj;
#ifndef DISABLE_VALIDATIONS
if (script
&& export_index < script->exports_nr
&& export_index >= 0)
#endif
return make_reg(seg, READ_LE_UINT16((byte *)(script->export_table + export_index)));
#ifndef DISABLE_VALIDATIONS
else {
CORE_ERROR("EXPORTS", "Export invalid or script missing ");
if (!script)
sciprintf("(script.%03d missing)\n", script_nr);
else
sciprintf("(script.%03d: Sought export %d/%d)\n",
script_nr, export_index, script->exports_nr);
return NULL_REG;
}
#endif
}
#define INST_LOOKUP_CLASS(id) ((id == 0xffff)? NULL_REG : get_class_address(s, id, SCRIPT_GET_LOCK, reg))
int script_instantiate_common(EngineState *s, int script_nr, Resource **script, Resource **heap, int *was_new) {
int seg;
int seg_id;
int marked_for_deletion;
reg_t reg;
*was_new = 1;
*script = s->resmgr->findResource(kResourceTypeScript, script_nr, 0);
if (s->version >= SCI_VERSION(1, 001, 000))
*heap = s->resmgr->findResource(kResourceTypeHeap, script_nr, 0);
if (!*script || (s->version >= SCI_VERSION(1, 001, 000) && !heap)) {
sciprintf("Script 0x%x requested but not found\n", script_nr);
//script_debug_flag = script_error_flag = 1;
if (s->version >= SCI_VERSION(1, 001, 000)) {
if (*heap)
sciprintf("Inconsistency: heap resource WAS found\n");
else if (*script)
sciprintf("Inconsistency: script resource WAS found\n");
}
return 0;
}
if (NULL == s) {
sciprintf("vm.c: script_instantiate(): NULL passed for \"s\"\n");
return 0;
}
Script *scr = 0;
seg = s->seg_manager->segGet(script_nr);
if (s->seg_manager->scriptIsLoaded(script_nr, SCRIPT_ID)) {
marked_for_deletion = s->seg_manager->scriptMarkedDeleted(script_nr);
if (!marked_for_deletion) {
s->seg_manager->incrementLockers(seg, SEG_ID);
return seg;
} else {
seg_id = seg;
scr = (Script *)s->seg_manager->_heap[seg];
assert(scr);
scr->freeScript();
}
} else if (!(scr = s->seg_manager->allocateScript(s, script_nr, &seg_id))) { // ALL YOUR SCRIPT BASE ARE BELONG TO US
sciprintf("Not enough heap space for script size 0x%x of script 0x%x, should this happen?`\n", (*script)->size, script_nr);
script_debug_flag = script_error_flag = 1;
return 0;
}
s->seg_manager->initialiseScript(*scr, s, script_nr);
reg.segment = seg_id;
reg.offset = 0;
// Set heap position (beyond the size word)
s->seg_manager->setLockers(1, reg.segment, SEG_ID);
s->seg_manager->setExportTableOffset(0, reg.segment, SEG_ID);
s->seg_manager->setSynonymsOffset(0, reg.segment, SEG_ID);
s->seg_manager->setSynonymsNr(0, reg.segment, SEG_ID);
*was_new = 0;
return seg_id;
}
int script_instantiate_sci0(EngineState *s, int script_nr) {
int objtype;
unsigned int objlength;
reg_t reg, reg_tmp;
int seg_id;
int relocation = -1;
int magic_pos_adder; // Usually 0; 2 for older SCI versions
Resource *script;
int was_new;
seg_id = script_instantiate_common(s, script_nr, &script, NULL, &was_new);
if (was_new)
return seg_id;
reg.segment = seg_id;
reg.offset = 0;
if (s->version < SCI_VERSION_FTU_NEW_SCRIPT_HEADER) {
//
int locals_nr = READ_LE_UINT16(script->data);
// Old script block
// There won't be a localvar block in this case
// Instead, the script starts with a 16 bit int specifying the
// number of locals we need; these are then allocated and zeroed.
s->seg_manager->mcpyInOut(0, script->data, script->size, reg.segment, SEG_ID);
magic_pos_adder = 2; // Step over the funny prefix
if (locals_nr)
s->seg_manager->scriptInitialiseLocalsZero(reg.segment, locals_nr);
} else {
s->seg_manager->mcpyInOut(0, script->data, script->size, reg.segment, SEG_ID);
magic_pos_adder = 0;
}
// Now do a first pass through the script objects to find the
// export table and local variable block
objlength = 0;
reg_tmp = reg;
reg.offset = magic_pos_adder;
do {
reg_t data_base;
reg_t addr;
reg.offset += objlength; // Step over the last checked object
objtype = SEG_GET_HEAP(s, reg);
if (!objtype) break;
objlength = SEG_GET_HEAP(s, make_reg(reg.segment, reg.offset + 2));
data_base = reg;
data_base.offset += 4;
addr = data_base;
switch (objtype) {
case sci_obj_exports: {
s->seg_manager->setExportTableOffset(data_base.offset, reg.segment, SEG_ID);
}
break;
case sci_obj_synonyms:
s->seg_manager->setSynonymsOffset(addr.offset, reg.segment, SEG_ID); // +4 is to step over the header
s->seg_manager->setSynonymsNr((objlength) / 4, reg.segment, SEG_ID);
break;
case sci_obj_localvars:
s->seg_manager->scriptInitialiseLocals(data_base);
break;
case sci_obj_class: {
int classpos = addr.offset - SCRIPT_OBJECT_MAGIC_OFFSET;
int species;
reg_tmp.offset = addr.offset - SCRIPT_OBJECT_MAGIC_OFFSET;
species = OBJ_SPECIES(s, reg_tmp);
if (species < 0 || species >= (int)s->_classtable.size()) {
sciprintf("Invalid species %d(0x%x) not in interval "
"[0,%d) while instantiating script %d\n",
species, species, s->_classtable.size(),
script_nr);
script_debug_flag = script_error_flag = 1;
return 1;
}
s->_classtable[species].script = script_nr;
s->_classtable[species].reg = addr;
s->_classtable[species].reg.offset = classpos;
// Set technical class position-- into the block allocated for it
}
break;
default:
break;
}
} while (objtype != 0);
// And now a second pass to adjust objects and class pointers, and the general pointers
objlength = 0;
reg.offset = magic_pos_adder; // Reset counter
do {
reg_t addr;
reg.offset += objlength; // Step over the last checked object
objtype = SEG_GET_HEAP(s, reg);
if (!objtype) break;
objlength = SEG_GET_HEAP(s, make_reg(reg.segment, reg.offset + 2));
reg.offset += 4; // Step over header
addr = reg;
switch (objtype) {
case sci_obj_code:
s->seg_manager->scriptAddCodeBlock(addr);
break;
case sci_obj_object:
case sci_obj_class: { // object or class?
Object *obj = s->seg_manager->scriptObjInit(s, addr);
Object *base_obj;
// Instantiate the superclass, if neccessary
obj->_variables[SCRIPT_SPECIES_SELECTOR] = INST_LOOKUP_CLASS(obj->_variables[SCRIPT_SPECIES_SELECTOR].offset);
base_obj = obj_get(s, obj->_variables[SCRIPT_SPECIES_SELECTOR]);
obj->variable_names_nr = base_obj->_variables.size();
obj->base_obj = base_obj->base_obj;
// Copy base from species class, as we need its selector IDs
obj->_variables[SCRIPT_SUPERCLASS_SELECTOR] = INST_LOOKUP_CLASS(obj->_variables[SCRIPT_SUPERCLASS_SELECTOR].offset);
} // if object or class
break;
case sci_obj_pointers: // A relocation table
relocation = addr.offset;
break;
default:
break;
}
reg.offset -= 4; // Step back on header
} while ((objtype != 0) && (((unsigned)reg.offset) < script->size - 2));
if (relocation >= 0)
s->seg_manager->scriptRelocate(make_reg(reg.segment, relocation));
return reg.segment; // instantiation successful
}
int script_instantiate_sci11(EngineState *s, int script_nr) {
Resource *script, *heap;
int seg_id;
int heap_start;
reg_t reg;
int was_new;
seg_id = script_instantiate_common(s, script_nr, &script, &heap, &was_new);
if (was_new)
return seg_id;
heap_start = script->size;
if (script->size & 2)
heap_start ++;
s->seg_manager->mcpyInOut(0, script->data, script->size, seg_id, SEG_ID);
s->seg_manager->mcpyInOut(heap_start, heap->data, heap->size, seg_id, SEG_ID);
if (READ_LE_UINT16(script->data + 6) > 0)
s->seg_manager->setExportTableOffset(6, seg_id, SEG_ID);
reg.segment = seg_id;
reg.offset = heap_start + 4;
s->seg_manager->scriptInitialiseLocals(reg);
s->seg_manager->scriptRelocateExportsSci11(seg_id);
s->seg_manager->scriptInitialiseObjectsSci11(s, seg_id);
reg.offset = READ_LE_UINT16(heap->data);
s->seg_manager->heapRelocate(s, reg);
return seg_id;
}
int script_instantiate(EngineState *s, int script_nr) {
if (s->version >= SCI_VERSION(1, 001, 000))
return script_instantiate_sci11(s, script_nr);
else
return script_instantiate_sci0(s, script_nr);
}
void script_uninstantiate_sci0(EngineState *s, int script_nr, SegmentId seg) {
reg_t reg = make_reg(seg, (s->version < SCI_VERSION_FTU_NEW_SCRIPT_HEADER) ? 2 : 0);
int objtype, objlength;
// Make a pass over the object in order uninstantiate all superclasses
objlength = 0;
do {
reg.offset += objlength; // Step over the last checked object
objtype = SEG_GET_HEAP(s, reg);
if (!objtype) break;
objlength = SEG_GET_HEAP(s, make_reg(reg.segment, reg.offset + 2)); // use SEG_UGET_HEAP ??
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 = OBJ_SUPERCLASS(s, reg); // Get superclass...
if (superclass >= 0) {
int superclass_script = s->_classtable[superclass].script;
if (superclass_script == script_nr) {
if (s->seg_manager->getLockers(reg.segment, SEG_ID))
s->seg_manager->decrementLockers(reg.segment, SEG_ID); // Decrease lockers if this is us ourselves
} else
script_uninstantiate(s, superclass_script);
// Recurse to assure that the superclass lockers number gets decreased
}
reg.offset += SCRIPT_OBJECT_MAGIC_OFFSET;
} // if object or class
reg.offset -= 4; // Step back on header
} while (objtype != 0);
}
void script_uninstantiate(EngineState *s, int script_nr) {
reg_t reg = make_reg(0, (s->version < SCI_VERSION_FTU_NEW_SCRIPT_HEADER) ? 2 : 0);
reg.segment = s->seg_manager->segGet(script_nr);
if (!s->seg_manager->scriptIsLoaded(script_nr, SCRIPT_ID) || reg.segment <= 0) { // Is it already loaded?
//warning("unloading script 0x%x requested although not loaded", script_nr);
// This is perfectly valid SCI behaviour
return;
}
s->seg_manager->decrementLockers(reg.segment, SEG_ID); // One less locker
if (s->seg_manager->getLockers(reg.segment, SEG_ID) > 0)
return;
// Free all classtable references to this script
for (uint i = 0; i < s->_classtable.size(); i++)
if (s->_classtable[i].reg.segment == reg.segment)
s->_classtable[i].reg = NULL_REG;
if (s->version < SCI_VERSION(1, 001, 000))
script_uninstantiate_sci0(s, script_nr, reg.segment);
else
sciprintf("FIXME: Add proper script uninstantiation for SCI 1.1\n");
if (s->seg_manager->getLockers(reg.segment, SEG_ID))
return; // if xxx.lockers > 0
// Otherwise unload it completely
// Explanation: I'm starting to believe that this work is done by SCI itself.
s->seg_manager->markScriptDeleted(script_nr);
if (script_checkloads_flag)
sciprintf("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;
}
EngineState *g_EngineState = 0;
static EngineState *_game_run(EngineState *s, int restoring) {
EngineState *successor = NULL;
int game_is_finished = 0;
g_EngineState = s;
do {
s->_executionStackPosChanged = false;
run_vm(s, (successor || restoring) ? 1 : 0);
if (s->restarting_flags & SCI_GAME_IS_RESTARTING_NOW) { // Restart was requested?
successor = NULL;
s->_executionStack.clear();
s->execution_stack_pos = -1;
s->_executionStackPosChanged = false;
game_exit(s);
script_free_engine(s);
script_init_engine(s, s->version);
game_init(s);
sfx_reset_player();
_init_stack_base_with_selector(s, s->selector_map.play);
send_selector(s, s->game_obj, s->game_obj, s->stack_base, 2, s->stack_base);
script_abort_flag = 0;
s->restarting_flags = SCI_GAME_WAS_RESTARTED | SCI_GAME_WAS_RESTARTED_AT_LEAST_ONCE;
} else {
successor = s->successor;
if (successor) {
game_exit(s);
script_free_vm_memory(s);
delete s;
s = successor;
g_EngineState = s;
if (script_abort_flag == SCRIPT_ABORT_WITH_REPLAY) {
sciprintf("Restarting with replay()\n");
s->execution_stack_pos = -1; // Restart with replay
_init_stack_base_with_selector(s, s->selector_map.replay);
send_selector(s, s->game_obj, s->game_obj, s->stack_base, 2, s->stack_base);
}
script_abort_flag = 0;
} else
game_is_finished = 1;
}
} while (!game_is_finished);
return s;
}
int objinfo(EngineState *s, reg_t pos);
int game_run(EngineState **_s) {
EngineState *s = *_s;
sciprintf(" Calling %s::play()\n", s->_gameName.c_str());
_init_stack_base_with_selector(s, s->selector_map.play); // Call the play selector
// Now: Register the first element on the execution stack-
if (!send_selector(s, s->game_obj, s->game_obj, s->stack_base, 2, s->stack_base) || script_error_flag) {
objinfo(s, s->game_obj);
sciprintf("Failed to run the game! Aborting...\n");
return 1;
}
// and ENGAGE!
*_s = s = _game_run(s, 0);
sciprintf(" Game::play() finished.\n");
return 0;
}
Object *obj_get(EngineState *s, reg_t offset) {
MemObject *memobj = GET_OBJECT_SEGMENT(*s->seg_manager, offset.segment);
Object *obj = NULL;
int idx;
if (memobj != NULL) {
if (memobj->getType() == MEM_OBJ_CLONES) {
CloneTable *ct = (CloneTable *)memobj;
if (ct->isValidEntry(offset.offset))
obj = &(ct->_table[offset.offset]);
} else if (memobj->getType() == MEM_OBJ_SCRIPT) {
Script *scr = (Script *)memobj;
if (offset.offset <= scr->buf_size && offset.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET
&& RAW_IS_OBJECT(scr->buf + offset.offset)) {
idx = RAW_GET_CLASS_INDEX(scr, offset);
if (idx >= 0 && (uint)idx < scr->_objects.size())
obj = &scr->_objects[idx];
}
}
}
return obj;
}
const char *obj_get_name(EngineState *s, reg_t pos) {
Object *obj = obj_get(s, pos);
if (!obj)
return "<no such object>";
return (const char *)(obj->base + obj->_variables[SCRIPT_NAME_SELECTOR].offset);
}
void quit_vm() {
script_abort_flag = 1; // Terminate VM
_debugstate_valid = 0;
_debug_seeking = 0;
_debug_step_running = 0;
}
} // End of namespace Sci