scummvm/engines/sci/engine/vm.h
sluicebox 30fad94e9a SCI: Track correct location and size of temp variables
The VM has been treating the entire area between the frame pointer and
the stack pointer as temp variables for the current function. There are
two problems with this:

1. The VM hasn't been updating the frame pointer correctly when multiple
   methods are called within the same send/self/super instruction.
2. The VM has been recalculating the number of temp variables on every
   instruction as the difference between the two pointers.
   This is incorrect, as this changes with almost every instruction in
   ways have nothing to do with the number of temp variables allocated
   by the link instruction. Meanwhile, the VM has not been recording
   the number of variables allocated by the link instruction.

The first discrepancy caused scripts to behave differently than in SSCI
when reading parameters out of bounds in certain situations.
It also prevented our uninitialized variable detection from detecting
certain reads. The second made the temp-count used for out of bounds
detection too large, made debugger output such as `stack` incorrect,
and made stepping through the link instruction in the debugger appear
to do nothing until stepping through the following instruction.

When multiple methods are called by a send/self/super instruction, each
method's link instruction increases the stack pointer further.
Method B's variables appear after method A's. The VM has been setting
the stack pointer correctly but it kept using the previous frame
pointer, so method B would re-use method A's stack area instead of the
area the VM had just allocated for B. If a script correctly initializes
variables before using them and doesn't use out of bounds parameters or
temp variables then this discrepancy doesn't make a difference.
But a lot of scripts do these bad things and accidentally rely on the
undefined values they read.

Now we update the frame pointer correctly when "carrying over" to
subsequent method calls from the same send/self/super instruction.
This matches SSCI behavior. We also now record the number of temp
variables that have been allocated by the link instruction and use
that instead of incorrectly recalculating on every instruction.

Fixes the KQ6 black widow lockup, and other KQ6 music bugs, where
scripts call Sound:fade without the required fourth parameter.
Sound:fade expects a fourth parameter, so reads it out of bounds,
and passes it as the stop-after-completion boolean to kDoSoundFade.
Scripts that called Sound:fade as the only method in a send got the same
results as in SSCI, but scripts that called Sound:play first didn't.

Fixes bugs #5625 #5653 #6120 #6210 #6252 #13944
2022-11-30 23:13:00 +02:00

436 lines
14 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCI_ENGINE_VM_H
#define SCI_ENGINE_VM_H
/* VM and kernel declarations */
#include "sci/engine/vm_types.h" // for reg_t
#include "sci/resource/resource.h" // for SciVersion
#include "common/util.h"
namespace Sci {
class SegManager;
struct EngineState;
class Object;
class ResourceManager;
class Script;
/** Number of bytes to be allocated for the stack */
#define VM_STACK_SIZE 0x1000
/** Magical object identifier */
#define SCRIPT_OBJECT_MAGIC_NUMBER 0x1234
/** Offset of this identifier */
#define SCRIPT_OBJECT_MAGIC_OFFSET (getSciVersion() < SCI_VERSION_1_1 ? -8 : 0)
/** Stack pointer value: Use predecessor's value */
#define CALL_SP_CARRY NULL
/** Types of selectors as returned by lookupSelector() below. */
enum SelectorType {
kSelectorNone = 0,
kSelectorVariable,
kSelectorMethod
};
struct Class {
int script; ///< number of the script the class is in, -1 for non-existing
reg_t reg; ///< offset; script-relative offset, segment: 0 if not instantiated
};
// A reference to an object's variable.
// The object is stored as a reg_t, the variable as an index into _variables
struct ObjVarRef {
reg_t obj;
int varindex;
reg_t* getPointer(SegManager *segMan) const;
};
enum ExecStackType {
EXEC_STACK_TYPE_CALL = 0,
EXEC_STACK_TYPE_KERNEL = 1,
EXEC_STACK_TYPE_VARSELECTOR = 2
};
struct ExecStack {
reg_t objp; ///< Pointer to the beginning of the current object
reg_t sendp; ///< Pointer to the object containing the invoked method
union {
ObjVarRef varp; // Variable pointer for r/w access
reg_t pc; // Pointer to the initial program counter. Not accurate for the TOS element
} addr;
StackPtr fp; // Frame pointer
StackPtr sp; // Stack pointer
int argc;
StackPtr variables_argp; // Argument pointer
int tempCount; // Number of temp variables allocated by link opcode
SegmentId local_segment; // local variables etc
Selector debugSelector; // The selector which was used to call or -1 if not applicable
int debugExportId; // The exportId which was called or -1 if not applicable
int debugLocalCallOffset; // Local call offset or -1 if not applicable
int debugOrigin; // The stack frame position the call was made from, or -1 if it was the initial call
int debugKernelFunction; // The kernel function called, or -1 if not applicable
int debugKernelSubFunction; // The kernel subfunction called, or -1 if not applicable
ExecStackType type;
reg_t* getVarPointer(SegManager *segMan) const;
ExecStack(reg_t objp_, reg_t sendp_, StackPtr sp_, int argc_, StackPtr argp_,
SegmentId localsSegment_, reg_t pc_, Selector debugSelector_,
int debugKernelFunction_, int debugKernelSubFunction_,
int debugExportId_, int debugLocalCallOffset_, int debugOrigin_,
ExecStackType type_) {
objp = objp_;
sendp = sendp_;
// varp is set separately for varselector calls
addr.pc = pc_;
fp = sp = sp_;
argc = argc_;
variables_argp = argp_;
tempCount = 0;
if (localsSegment_ != kUninitializedSegment)
local_segment = localsSegment_;
else
local_segment = pc_.getSegment();
debugSelector = debugSelector_;
debugKernelFunction = debugKernelFunction_;
debugKernelSubFunction = debugKernelSubFunction_;
debugExportId = debugExportId_;
debugLocalCallOffset = debugLocalCallOffset_;
debugOrigin = debugOrigin_;
type = type_;
}
};
enum {
VAR_GLOBAL = 0,
VAR_LOCAL = 1,
VAR_TEMP = 2,
VAR_PARAM = 3
};
enum GlobalVar {
kGlobalVarEgo = 0,
kGlobalVarGame = 1,
kGlobalVarCurrentRoom = 2,
kGlobalVarSpeed = 3, // SCI16
kGlobalVarQuit = 4,
kGlobalVarSounds = 8,
kGlobalVarPlanes = 10, // SCI32
kGlobalVarCurrentRoomNo = 11,
kGlobalVarPreviousRoomNo = 12,
kGlobalVarNewRoomNo = 13,
kGlobalVarScore = 15,
kGlobalVarVersionNew = 27, // version string or object in later games
kGlobalVarVersionOld = 28, // version string in earlier games
kGlobalVarGK2MusicVolume = 76, // 0 to 127
kGlobalVarPhant2SecondaryVolume = 76, // 0 to 127
kGlobalVarUser = 80,
kGlobalVarFastCast = 84, // SCI16
kGlobalVarMessageType = 90,
kGlobalVarTextSpeed = 94, // SCI32; 0 is fastest, 8 is slowest
kGlobalVarGK1Music1 = 102, // 0 to 127
kGlobalVarGK1Music2 = 103, // 0 to 127
kGlobalVarRamaCatalogFile = 130,
kGlobalVarLSL6HiresGameFlags = 137,
kGlobalVarKQ7UpscaleVideos = 160,
kGlobalVarGK1NarratorMode = 166, // 0 for text, 1 for speech
kGlobalVarRamaMusicVolume = 176, // 0 to 16
kGlobalVarPhant1MusicVolume = 187, // 0 to 15
kGlobalVarPhant1DACVolume = 188, // 0 to 127
kGlobalVarLSL6MusicVolume = 194, // 0 to 13
kGlobalVarGK1DAC1 = 207, // 0 to 127
kGlobalVarPhant2CensorshipFlag = 207,
kGlobalVarGK1DAC2 = 208, // 0 to 127
kGlobalVarLSL6HiresRestoreTextWindow = 210,
kGlobalVarGK1DAC3 = 211, // 0 to 127
kGlobalVarShiversFlags = 211,
kGlobalVarTorinMusicVolume = 227, // 0 to 100
kGlobalVarTorinSFXVolume = 228, // 0 to 100
kGlobalVarTorinSpeechVolume = 229, // 0 to 100
// Phant2 labels its volume slider as "music volume" but it is actually
// a master volume that affects both music *and* sound effects
kGlobalVarPhant2MasterVolume = 236, // 0 to 127
kGlobalVarPhant2ControlPanel = 250,
kGlobalVarShivers1Score = 349,
kGlobalVarQFG4Flags = 500,
kGlobalVarHoyle5MusicVolume = 897
};
/** Number of kernel calls in between gcs; should be < 50000 */
enum {
GC_INTERVAL = 0x8000
};
enum SciOpcodes {
op_bnot = 0x00, // 000
op_add = 0x01, // 001
op_sub = 0x02, // 002
op_mul = 0x03, // 003
op_div = 0x04, // 004
op_mod = 0x05, // 005
op_shr = 0x06, // 006
op_shl = 0x07, // 007
op_xor = 0x08, // 008
op_and = 0x09, // 009
op_or = 0x0a, // 010
op_neg = 0x0b, // 011
op_not = 0x0c, // 012
op_eq_ = 0x0d, // 013
op_ne_ = 0x0e, // 014
op_gt_ = 0x0f, // 015
op_ge_ = 0x10, // 016
op_lt_ = 0x11, // 017
op_le_ = 0x12, // 018
op_ugt_ = 0x13, // 019
op_uge_ = 0x14, // 020
op_ult_ = 0x15, // 021
op_ule_ = 0x16, // 022
op_bt = 0x17, // 023
op_bnt = 0x18, // 024
op_jmp = 0x19, // 025
op_ldi = 0x1a, // 026
op_push = 0x1b, // 027
op_pushi = 0x1c, // 028
op_toss = 0x1d, // 029
op_dup = 0x1e, // 030
op_link = 0x1f, // 031
op_call = 0x20, // 032
op_callk = 0x21, // 033
op_callb = 0x22, // 034
op_calle = 0x23, // 035
op_ret = 0x24, // 036
op_send = 0x25, // 037
op_info = 0x26, // 038
op_superP = 0x27, // 039
op_class = 0x28, // 040
// dummy 0x29, // 041
op_self = 0x2a, // 042
op_super = 0x2b, // 043
op_rest = 0x2c, // 044
op_lea = 0x2d, // 045
op_selfID = 0x2e, // 046
// dummy 0x2f // 047
op_pprev = 0x30, // 048
op_pToa = 0x31, // 049
op_aTop = 0x32, // 050
op_pTos = 0x33, // 051
op_sTop = 0x34, // 052
op_ipToa = 0x35, // 053
op_dpToa = 0x36, // 054
op_ipTos = 0x37, // 055
op_dpTos = 0x38, // 056
op_lofsa = 0x39, // 057
op_lofss = 0x3a, // 058
op_push0 = 0x3b, // 059
op_push1 = 0x3c, // 060
op_push2 = 0x3d, // 061
op_pushSelf = 0x3e, // 062
op_line = 0x3f, // 063
//
op_lag = 0x40, // 064
op_lal = 0x41, // 065
op_lat = 0x42, // 066
op_lap = 0x43, // 067
op_lsg = 0x44, // 068
op_lsl = 0x45, // 069
op_lst = 0x46, // 070
op_lsp = 0x47, // 071
op_lagi = 0x48, // 072
op_lali = 0x49, // 073
op_lati = 0x4a, // 074
op_lapi = 0x4b, // 075
op_lsgi = 0x4c, // 076
op_lsli = 0x4d, // 077
op_lsti = 0x4e, // 078
op_lspi = 0x4f, // 079
//
op_sag = 0x50, // 080
op_sal = 0x51, // 081
op_sat = 0x52, // 082
op_sap = 0x53, // 083
op_ssg = 0x54, // 084
op_ssl = 0x55, // 085
op_sst = 0x56, // 086
op_ssp = 0x57, // 087
op_sagi = 0x58, // 088
op_sali = 0x59, // 089
op_sati = 0x5a, // 090
op_sapi = 0x5b, // 091
op_ssgi = 0x5c, // 092
op_ssli = 0x5d, // 093
op_ssti = 0x5e, // 094
op_sspi = 0x5f, // 095
//
op_plusag = 0x60, // 096
op_plusal = 0x61, // 097
op_plusat = 0x62, // 098
op_plusap = 0x63, // 099
op_plussg = 0x64, // 100
op_plussl = 0x65, // 101
op_plusst = 0x66, // 102
op_plussp = 0x67, // 103
op_plusagi = 0x68, // 104
op_plusali = 0x69, // 105
op_plusati = 0x6a, // 106
op_plusapi = 0x6b, // 107
op_plussgi = 0x6c, // 108
op_plussli = 0x6d, // 109
op_plussti = 0x6e, // 110
op_plusspi = 0x6f, // 111
//
op_minusag = 0x70, // 112
op_minusal = 0x71, // 113
op_minusat = 0x72, // 114
op_minusap = 0x73, // 115
op_minussg = 0x74, // 116
op_minussl = 0x75, // 117
op_minusst = 0x76, // 118
op_minussp = 0x77, // 119
op_minusagi = 0x78, // 120
op_minusali = 0x79, // 121
op_minusati = 0x7a, // 122
op_minusapi = 0x7b, // 123
op_minussgi = 0x7c, // 124
op_minussli = 0x7d, // 125
op_minussti = 0x7e, // 126
op_minusspi = 0x7f // 127
};
void script_adjust_opcode_formats();
/**
* Executes function pubfunct of the specified script.
* @param[in] s The state which is to be executed with
* @param[in] script The script which is called
* @param[in] pubfunct The exported script function which is to
* be called
* @param[in] sp Stack pointer position
* @param[in] calling_obj The heap address of the object that
* executed the call
* @param[in] argc Number of arguments supplied
* @param[in] argp Pointer to the first supplied argument
* @return A pointer to the new exec stack TOS entry
*/
ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct,
StackPtr sp, reg_t calling_obj, uint16 argc, StackPtr argp);
/**
* Executes a "send" or related operation to a selector.
* @param[in] s The EngineState to operate on
* @param[in] send_obj Heap address of the object to send to
* @param[in] work_obj Heap address of the object initiating the send
* @param[in] sp Stack pointer position
* @param[in] framesize Size of the send as determined by the "send"
* operation
* @param[in] argp Pointer to the beginning of the heap block
* containing the data to be sent. This area is a
* succession of one or more sequences of
* [selector_number][argument_counter] and then
* "argument_counter" word entries with the
* parameter values.
* @return A pointer to the new execution stack TOS entry
*/
ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj,
StackPtr sp, int framesize, StackPtr argp);
/**
* This function executes SCI bytecode
* It executes the code on s->heap[pc] until it hits a 'ret' operation
* while (stack_base == stack_pos). Requires s to be set up correctly.
* @param[in] s The state to use
*/
void run_vm(EngineState *s);
/**
* Debugger functionality
* @param[in] s The state at which debugging should take place
*/
void script_debug(EngineState *s);
/**
* Looks up a selector and returns its type and value
* varindex is written to iff it is non-NULL and the selector indicates a property of the object.
* @param[in] segMan The Segment Manager
* @param[in] obj Address of the object to look the selector up in
* @param[in] selectorid The selector to look up
* @param[out] varp A reference to the selector, if it is a
* variable.
* @param[out] fptr A reference to the function described by that
* selector, if it is a valid function selector.
* fptr is written to iff it is non-NULL and the
* selector indicates a member function of that
* object.
* @return kSelectorNone if the selector was not found in
* the object or its superclasses.
* kSelectorVariable if the selector represents an
* object-relative variable.
* kSelectorMethod if the selector represents a
* method
*/
SelectorType lookupSelector(SegManager *segMan, reg_t obj, Selector selectorid,
ObjVarRef *varp, reg_t *fptr);
/**
* Read a PMachine instruction from a memory buffer and return its length.
*
* @param[in] src address from which to start parsing
* @param[out] extOpcode "extended" opcode of the parsed instruction
* @param[out] opparams parameter for the parsed instruction
* @return the length in bytes of the instruction
*
* @todo How about changing opparams from int16 to int / int32 to preserve
* unsigned 16bit words as read for Script_Word? In the past, this
* was irrelevant as only a debug opcode used Script_Word. But with
* SCI32 we are now using Script_Word for more opcodes. Maybe this is
* just a mistake and those opcodes should used Script_SWord -- but if
* not then we definitely should change this to int, else we might run
* into trouble if we encounter high value words. *If* those exist at all.
*/
int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]);
/**
* Finds the script-absolute offset of a relative object offset.
*
* @param[in] relOffset the relative object offset
* @param[in] scr the owner script object, used by SCI1.1+
* @param[in] pcOffset the offset of the program counter, used by SCI0early and
* SCI3
*/
uint32 findOffset(const int16 relOffset, const Script *scr, const uint32 pcOffset);
} // End of namespace Sci
#endif // SCI_ENGINE_VM_H