aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine/vm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/engine/vm.cpp')
-rw-r--r--engines/sci/engine/vm.cpp178
1 files changed, 113 insertions, 65 deletions
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 66d9fee5fd..8e407a6ab9 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/debug.h"
#include "common/debug-channels.h"
@@ -124,32 +125,30 @@ static reg_t read_var(EngineState *s, int type, int index) {
case VAR_TEMP: {
// Uninitialized read on a temp
// We need to find correct replacements for each situation manually
- SciTrackOriginReply originReply;
+ SciCallOrigin originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply);
if (solution.type == WORKAROUND_NONE) {
#ifdef RELEASE_BUILD
// If we are running an official ScummVM release -> fake 0 in unknown cases
- warning("Uninitialized read for temp %d from method %s::%s (room %d, script %d, localCall %x)",
- index, originReply.objectName.c_str(), originReply.methodName.c_str(), s->currentRoomNumber(),
- originReply.scriptNr, originReply.localCallOffset);
+ warning("Uninitialized read for temp %d from %s", index, originReply.toString().c_str());
s->variables[type][index] = NULL_REG;
break;
#else
- error("Uninitialized read for temp %d from method %s::%s (room %d, script %d, localCall %x)",
- index, originReply.objectName.c_str(), originReply.methodName.c_str(), s->currentRoomNumber(),
- originReply.scriptNr, originReply.localCallOffset);
+ error("Uninitialized read for temp %d from %s", index, originReply.toString().c_str());
#endif
}
assert(solution.type == WORKAROUND_FAKE);
s->variables[type][index] = make_reg(0, solution.value);
break;
}
- case VAR_PARAM:
+ case VAR_PARAM: {
// Out-of-bounds read for a parameter that goes onto stack and hits an uninitialized temp
// We return 0 currently in that case
- debugC(kDebugLevelVM, "[VM] Read for a parameter goes out-of-bounds, onto the stack and gets uninitialized temp");
+ const SciCallOrigin origin = s->getCurrentCallOrigin();
+ warning("Uninitialized read for parameter %d from %s", index, origin.toString().c_str());
return NULL_REG;
+ }
default:
break;
}
@@ -179,7 +178,7 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
// stopGroop object, which points to ego, to the new ego object. If this is not
// done, ego's movement will not be updated properly, so the result is
// unpredictable (for example in LSL5, Patti spins around instead of walking).
- if (index == 0 && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) { // global 0 is ego
+ if (index == kGlobalVarEgo && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) {
reg_t stopGroopPos = s->_segMan->findObjectByName("stopGroop");
if (!stopGroopPos.isNull()) { // does the game have a stopGroop object?
// Find the "client" member variable of the stopGroop object, and update it
@@ -200,9 +199,26 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
s->variables[type][index] = value;
- if (type == VAR_GLOBAL && index == 90) {
+#ifdef ENABLE_SCI32
+ if (type == VAR_GLOBAL && getSciVersion() >= SCI_VERSION_2 && g_sci->getEngineState()->_syncedAudioOptions) {
+
+ switch (g_sci->getGameId()) {
+ case GID_LSL6HIRES:
+ if (index == kGlobalVarLSL6HiresTextSpeed) {
+ ConfMan.setInt("talkspeed", (14 - value.toSint16()) * 255 / 13);
+ }
+ break;
+ default:
+ if (index == kGlobalVarTextSpeed) {
+ ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8);
+ }
+ }
+ }
+#endif
+
+ if (type == VAR_GLOBAL && index == kGlobalVarMessageType) {
// The game is trying to change its speech/subtitle settings
- if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][4] == TRUE_REG) {
+ if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) {
// ScummVM audio options haven't been applied yet, so apply them.
// We also force the ScummVM audio options when loading a game from
// the launcher.
@@ -232,15 +248,16 @@ ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackP
scr = s->_segMan->getScript(seg);
}
+ // Check if a breakpoint is set on this method
+ g_sci->checkExportBreakpoint(script, pubfunct);
+
uint32 exportAddr = scr->validateExportFunc(pubfunct, false);
if (!exportAddr)
return NULL;
- // Check if a breakpoint is set on this method
- g_sci->checkExportBreakpoint(script, pubfunct);
-
+ assert(argp[0].toUint16() == argc); // The first argument is argc
ExecStack xstack(calling_obj, calling_obj, sp, argc, argp,
- seg, make_reg32(seg, exportAddr), -1, pubfunct, -1,
+ seg, make_reg32(seg, exportAddr), -1, -1, -1, pubfunct, -1,
s->_executionStack.size() - 1, EXEC_STACK_TYPE_CALL);
s->_executionStack.push_back(xstack);
return &(s->_executionStack.back());
@@ -312,8 +329,9 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
if (activeBreakpointTypes || DebugMan.isDebugChannelEnabled(kDebugLevelScripts))
debugSelectorCall(send_obj, selector, argc, argp, varp, funcp, s->_segMan, selectorType);
+ assert(argp[0].toUint16() == argc); // The first argument is argc
ExecStack xstack(work_obj, send_obj, curSP, argc, argp,
- 0xFFFF, curFP, selector, -1, -1,
+ 0xFFFF, curFP, selector, -1, -1, -1, -1,
origin, stackType);
if (selectorType == kSelectorVariable)
@@ -330,17 +348,20 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
argp += argc + 1;
} // while (framesize > 0)
+ // Perform all varselector actions at the top of the stack immediately.
+ // Note that there may be some behind method selector calls as well;
+ // those will get executed by op_ret later.
_exec_varselectors(s);
return s->_executionStack.empty() ? NULL : &(s->_executionStack.back());
}
-static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int argc, reg_t *argv) {
+static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int kernelSubCallNr, int argc, reg_t *argv) {
// Add stack frame to indicate we're executing a callk.
// This is useful in debugger backtraces if this
// kernel function calls a script itself.
ExecStack xstack(NULL_REG, NULL_REG, NULL, argc, argv - 1, 0xFFFF, make_reg32(0, 0),
- kernelCallNr, -1, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_KERNEL);
+ -1, kernelCallNr, kernelSubCallNr, -1, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_KERNEL);
s->_executionStack.push_back(xstack);
}
@@ -359,16 +380,13 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
if (kernelCall.signature
&& !kernel->signatureMatch(kernelCall.signature, argc, argv)) {
// signature mismatch, check if a workaround is available
- SciTrackOriginReply originReply;
+ SciCallOrigin originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply);
switch (solution.type) {
case WORKAROUND_NONE: {
Common::String signatureDetailsStr;
kernel->signatureDebug(signatureDetailsStr, kernelCall.signature, argc, argv);
- error("\n%s[VM] k%s[%x]: signature mismatch in method %s::%s (room %d, script %d, localCall 0x%x)",
- signatureDetailsStr.c_str(),
- kernelCall.name, kernelCallNr, originReply.objectName.c_str(), originReply.methodName.c_str(),
- s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
+ error("\n%s[VM] k%s[%x]: signature mismatch in %s", signatureDetailsStr.c_str(), kernelCall.name, kernelCallNr, originReply.toString().c_str());
break;
}
case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone
@@ -386,7 +404,8 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
// Call kernel function
if (!kernelCall.subFunctionCount) {
- addKernelCallToExecStack(s, kernelCallNr, argc, argv);
+ argv[-1] = make_reg(0, argc); // The first argument is argc
+ addKernelCallToExecStack(s, kernelCallNr, -1, argc, argv);
s->r_acc = kernelCall.function(s, argc, argv);
if (kernelCall.debugLogging)
@@ -402,6 +421,21 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
error("[VM] k%s[%x]: no subfunction ID parameter given", kernelCall.name, kernelCallNr);
if (argv[0].isPointer())
error("[VM] k%s[%x]: given subfunction ID is actually a pointer", kernelCall.name, kernelCallNr);
+
+#ifdef ENABLE_SCI32
+ // The Windows version of kShowMovie has subops, but the subop number
+ // is put in the second parameter in SCI2.1+, even though every other
+ // kcall with subops puts the subop in the first parameter. To allow use
+ // of the normal subops system, we swap the arguments so the subop
+ // number is in the usual place.
+ if (getSciVersion() > SCI_VERSION_2 &&
+ g_sci->getPlatform() == Common::kPlatformWindows &&
+ strcmp(kernelCall.name, "ShowMovie") == 0) {
+ assert(argc > 1);
+ SWAP(argv[0], argv[1]);
+ }
+#endif
+
const uint16 subId = argv[0].toUint16();
// Skip over subfunction-id
argc--;
@@ -411,7 +445,7 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
const KernelSubFunction &kernelSubCall = kernelCall.subFunctions[subId];
if (kernelSubCall.signature && !kernel->signatureMatch(kernelSubCall.signature, argc, argv)) {
// Signature mismatch
- SciTrackOriginReply originReply;
+ SciCallOrigin originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply);
switch (solution.type) {
case WORKAROUND_NONE: {
@@ -420,15 +454,13 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
int callNameLen = strlen(kernelCall.name);
if (strncmp(kernelCall.name, kernelSubCall.name, callNameLen) == 0) {
const char *subCallName = kernelSubCall.name + callNameLen;
- error("\n%s[VM] k%s(%s): signature mismatch in method %s::%s (room %d, script %d, localCall %x)",
- signatureDetailsStr.c_str(),
- kernelCall.name, subCallName, originReply.objectName.c_str(), originReply.methodName.c_str(),
- s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
+ error("\n%s[VM] k%s(%s): signature mismatch in %s",
+ signatureDetailsStr.c_str(), kernelCall.name, subCallName,
+ originReply.toString().c_str());
}
- error("\n%s[VM] k%s: signature mismatch in method %s::%s (room %d, script %d, localCall %x)",
- signatureDetailsStr.c_str(),
- kernelSubCall.name, originReply.objectName.c_str(), originReply.methodName.c_str(),
- s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
+ error("\n%s[VM] k%s: signature mismatch in %s",
+ signatureDetailsStr.c_str(), kernelSubCall.name,
+ originReply.toString().c_str());
break;
}
case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone
@@ -444,7 +476,8 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
}
if (!kernelSubCall.function)
error("[VM] k%s: subfunction ID %d requested, but not available", kernelCall.name, subId);
- addKernelCallToExecStack(s, kernelCallNr, argc, argv);
+ argv[-1] = make_reg(0, argc); // The first argument is argc
+ addKernelCallToExecStack(s, kernelCallNr, subId, argc, argv);
s->r_acc = kernelSubCall.function(s, argc, argv);
if (kernelSubCall.debugLogging)
@@ -545,6 +578,31 @@ int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4])
return offset;
}
+uint32 findOffset(const int16 relOffset, const Script *scr, const uint32 pcOffset) {
+ uint32 offset;
+
+ switch (g_sci->_features->detectLofsType()) {
+ case SCI_VERSION_0_EARLY:
+ offset = (uint16)pcOffset + relOffset;
+ break;
+ case SCI_VERSION_1_MIDDLE:
+ offset = relOffset;
+ break;
+ case SCI_VERSION_1_1:
+ offset = relOffset + scr->getScriptSize();
+ break;
+ case SCI_VERSION_3:
+ // In theory this can break if the variant with a one-byte argument is
+ // used. For now, assume it doesn't happen.
+ offset = scr->relocateOffsetSci3(pcOffset - 2);
+ break;
+ default:
+ error("Unknown lofs type");
+ }
+
+ return offset;
+}
+
void run_vm(EngineState *s) {
assert(s);
@@ -612,6 +670,8 @@ void run_vm(EngineState *s) {
if (s->abortScriptProcessing != kAbortNone)
return; // Stop processing
+ g_sci->checkAddressBreakpoint(s->xs->addr.pc);
+
// Debug if this has been requested:
// TODO: re-implement sci_debug_flags
if (g_sci->_debugState.debugging /* sci_debug_flags*/) {
@@ -834,14 +894,15 @@ void run_vm(EngineState *s) {
int argc = (opparams[1] >> 1) // Given as offset, but we need count
+ 1 + s->r_rest;
StackPtr call_base = s->xs->sp - argc;
- s->xs->sp[1].incOffset(s->r_rest);
uint32 localCallOffset = s->xs->addr.pc.getOffset() + opparams[0];
+ int final_argc = (call_base->requireUint16()) + s->r_rest;
+ call_base[0] = make_reg(0, final_argc); // The first argument is argc
ExecStack xstack(s->xs->objp, s->xs->objp, s->xs->sp,
- (call_base->requireUint16()) + s->r_rest, call_base,
+ final_argc, call_base,
s->xs->local_segment, make_reg32(s->xs->addr.pc.getSegment(), localCallOffset),
- NULL_SELECTOR, -1, localCallOffset, s->_executionStack.size() - 1,
+ NULL_SELECTOR, -1, -1, -1, localCallOffset, s->_executionStack.size() - 1,
EXEC_STACK_TYPE_CALL);
s->_executionStack.push_back(xstack);
@@ -938,9 +999,13 @@ void run_vm(EngineState *s) {
if (old_xs->type == EXEC_STACK_TYPE_VARSELECTOR) {
// varselector access?
reg_t *var = old_xs->getVarPointer(s->_segMan);
- if (old_xs->argc) // write?
+ if (old_xs->argc) { // write?
*var = old_xs->variables_argp[1];
- else // No, read
+
+#ifdef ENABLE_SCI32
+ updateInfoFlagViewVisible(s->_segMan->getObject(old_xs->addr.varp.obj), old_xs->addr.varp.varindex);
+#endif
+ } else // No, read
s->r_acc = *var;
}
@@ -1100,7 +1165,7 @@ void run_vm(EngineState *s) {
// Accumulator To Property
validate_property(s, obj, opparams[0]) = s->r_acc;
#ifdef ENABLE_SCI32
- updateInfoFlagViewVisible(obj, opparams[0]);
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
#endif
break;
@@ -1113,7 +1178,7 @@ void run_vm(EngineState *s) {
// Stack To Property
validate_property(s, obj, opparams[0]) = POP32();
#ifdef ENABLE_SCI32
- updateInfoFlagViewVisible(obj, opparams[0]);
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
#endif
break;
@@ -1130,7 +1195,7 @@ void run_vm(EngineState *s) {
else
opProperty -= 1;
#ifdef ENABLE_SCI32
- updateInfoFlagViewVisible(obj, opparams[0]);
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
#endif
if (opcode == op_ipToa || opcode == op_dpToa)
s->r_acc = opProperty;
@@ -1140,38 +1205,21 @@ void run_vm(EngineState *s) {
}
case op_lofsa: // 0x39 (57)
- case op_lofss: // 0x3a (58)
+ case op_lofss: { // 0x3a (58)
// Load offset to accumulator or push to stack
- r_temp.setSegment(s->xs->addr.pc.getSegment());
-
- switch (g_sci->_features->detectLofsType()) {
- case SCI_VERSION_0_EARLY:
- r_temp.setOffset((uint16)s->xs->addr.pc.getOffset() + opparams[0]);
- break;
- case SCI_VERSION_1_MIDDLE:
- r_temp.setOffset(opparams[0]);
- break;
- case SCI_VERSION_1_1:
- r_temp.setOffset(opparams[0] + local_script->getScriptSize());
- break;
- case SCI_VERSION_3:
- // In theory this can break if the variant with a one-byte argument is
- // used. For now, assume it doesn't happen.
- r_temp.setOffset(local_script->relocateOffsetSci3(s->xs->addr.pc.getOffset() - 2));
- break;
- default:
- error("Unknown lofs type");
- }
+ r_temp.setSegment(s->xs->addr.pc.getSegment());
+ r_temp.setOffset(findOffset(opparams[0], local_script, s->xs->addr.pc.getOffset()));
if (r_temp.getOffset() >= scr->getBufSize())
error("VM: lofsa/lofss operation overflowed: %04x:%04x beyond end"
- " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize());
+ " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize());
if (opcode == op_lofsa)
s->r_acc = r_temp;
else
PUSH32(r_temp);
break;
+ }
case op_push0: // 0x3b (59)
PUSH(0);