mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
We need to find the actual interpreter difference that causes this bug,
and not patch scenes in this manner, as this bug does not occur with
the original. There is still no explanation why this bug occurs in
ScummVM, so it's best to revert the inserted script code, as it caused
a regression in bug #11671.
This reverts commit 6216f855a8.
229 lines
8.4 KiB
C++
229 lines
8.4 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.
|
|
*
|
|
*/
|
|
|
|
#include "common/hashmap.h"
|
|
#include "common/array.h"
|
|
#include "common/language.h"
|
|
#include "sci/engine/vm_hooks.h"
|
|
#include "sci/engine/vm.h"
|
|
#include "sci/engine/state.h"
|
|
#include "sci/engine/kernel.h"
|
|
#include "sci/engine/scriptdebug.h"
|
|
|
|
namespace Sci {
|
|
|
|
/*****************************************************************************************************************
|
|
*
|
|
* This mechanism allows inserting new instructions, and not only replace existing code.
|
|
* Note that when using hooks, the regular PC is frozen, and doesn't advance.
|
|
* Therefore, 'jmp', 'bt' and 'bnt' are used to locally move around inside the patch.
|
|
*
|
|
******************************************************************************************************************/
|
|
|
|
|
|
// solves the issue described at #9646:
|
|
// "
|
|
// When in room 58, and type "run", the hero will fall and his HP will decrease by 1 point. This can be repeated, but will never cause the hero to die.
|
|
// When typing "run" the ego will be assigned with the script egoRuns.
|
|
// egoRuns::changeState calls proc0_36 in script 0 which is deducing damage from the hero's HP.
|
|
// This procedure returns TRUE if the hero is still alive, but the return value is never observed in egoRuns.
|
|
// "
|
|
// we solve that by calling the hook before executing the opcode following proc0_36 call
|
|
// and check the return value. if the hero should die, we kill him
|
|
|
|
static const byte qfg1_die_after_running_on_ice[] = {
|
|
// if shouldn't die, jump to end
|
|
0x2f, 22, // bt +22
|
|
|
|
// should die - done according to the code at main.sc, proc0_29:
|
|
// (proc0_1 0 59 80 {Death from Overwork} 82 800 1 4)
|
|
0x39, 0x08, // pushi 8 -- num of parameters
|
|
0x39, 0x00, // pushi 0
|
|
0x39, 59, // pushi 59
|
|
0x39, 0, // pushi 0 -- modified, not using {Death from Overwork}
|
|
0x36, // push
|
|
0x39, 82, // pushi 82
|
|
0x38, 32, 3, // push 800
|
|
0x39, 1, // pushi 1
|
|
0x39, 4, // pushi 4
|
|
0x47, 0x00, 0x01, 0x10 // calle proc0_1
|
|
};
|
|
|
|
|
|
// SCI0 Hebrew translations need to modify and relocate the "Enter input:" prompt
|
|
// currently SQ3 is the only Hebrew SCI0 game, but this patch (or similar) should work for the future games as well
|
|
|
|
static const byte sci0_hebrew_input_prompt[] = {
|
|
// in (procedure (Print ...
|
|
// ...
|
|
// (#edit
|
|
// (++ paramCnt)
|
|
// adding:
|
|
// (hDText moveTo: 104 4)
|
|
// (= hDText (DText new:))
|
|
// (hDText
|
|
// text: ''
|
|
// moveTo: 4 4
|
|
// font: gDefaultFont
|
|
// setSize:
|
|
// )
|
|
// (hDialog add: hDText)
|
|
0x38, 0x92, 0, // pushi #moveTo
|
|
0x39, 2, // pushi 2
|
|
0x38, 210, 0, // pushi 220 ;x
|
|
0x39, 4, // pushi 4 ;y
|
|
0x85, 1, // lat temp1
|
|
0x4a, 8, // send 8
|
|
0x38, 0x59, 0, // pushi #new
|
|
0x39, 0, // pushi 0
|
|
0x51, 0xd, // class DText
|
|
0x4a, 4, // send 4
|
|
0xa5, 1, // sat temp1
|
|
0x39, 0x1a, // pushi #text
|
|
0x39, 1, // pushi 1
|
|
0x75, 25, // lofss '' ; that location has '\0'
|
|
0x38, 146, 0, // pushi 146
|
|
0x39, 2, // pushi 2
|
|
0x39, 4, // pushi 4 ;x
|
|
0x39, 14, // pushi 12 ;y
|
|
0x39, 33, // pushi 33
|
|
0x39, 1, // pushi 1
|
|
0x89, 0x16, // lsg global22
|
|
0x38, 144, 0, // pushi 144
|
|
0x39, 0, // pushi 0
|
|
0x85, 1, // lat temp1
|
|
0x4a, 24, // send 24
|
|
0x38, 0x64, 0, // pushi #add
|
|
0x39, 1, // pushi 1
|
|
0x8d, 1, // lst temp1
|
|
0x85, 0, // lat temp0
|
|
0x4a, 6, // send 6
|
|
0x85, 5 // lat temp5
|
|
};
|
|
|
|
|
|
|
|
/** Write here all games hooks
|
|
* From this we'll build _hooksMap, which contains only relevant hooks to current game
|
|
* The match is performed according to PC, script number, opcode (only opcode name, as seen in ScummVM debugger),
|
|
* and either:
|
|
* - objName and selector (and then externID is -1)
|
|
* - external function ID (and then selector is "")
|
|
* = in that case, if objName == "" it will be ignored, otherwise, it will be also used to match
|
|
*/
|
|
|
|
static const GeneralHookEntry allGamesHooks[] = {
|
|
// GID, script, lang, PC.offset, objName, selector, externID, opcode, hook array
|
|
{GID_QFG1, Common::UNK_LANG, {58, 0x144d}, {"egoRuns", "changeState", -1 , "push0", HOOKARRAY(qfg1_die_after_running_on_ice)}},
|
|
{GID_SQ3, Common::HE_ISR, {255, 0x1103}, {"User", "", -1 , "pushi", HOOKARRAY(sci0_hebrew_input_prompt)}}
|
|
};
|
|
|
|
|
|
VmHooks::VmHooks() {
|
|
// build _hooksMap
|
|
for (uint i = 0; i < ARRAYSIZE(allGamesHooks); i++) {
|
|
if (allGamesHooks[i].gameId == g_sci->getGameId())
|
|
if (allGamesHooks[i].language == g_sci->getLanguage() || allGamesHooks[i].language == Common::UNK_LANG)
|
|
_hooksMap.setVal(allGamesHooks[i].key, allGamesHooks[i].entry);
|
|
}
|
|
|
|
_lastPc = NULL_REG;
|
|
_just_finished = false;
|
|
_location = 0;
|
|
}
|
|
|
|
uint64 HookHashKey::hash() {
|
|
return ((uint64)scriptNumber << 32) + offset;
|
|
}
|
|
|
|
//#ifndef REDUCE_MEMORY_USAGE
|
|
extern const char* opcodeNames[];
|
|
//#endif
|
|
|
|
// returns true if entry is matching to current state
|
|
bool hook_exec_match(Sci::EngineState *s, HookEntry entry) {
|
|
Script *scr = s->_segMan->getScript(s->xs->addr.pc.getSegment());
|
|
const char *objName = s->_segMan->getObjectName(s->xs->objp);
|
|
Common::String selector;
|
|
if (s->xs->debugSelector != -1)
|
|
selector = g_sci->getKernel()->getSelectorName(s->xs->debugSelector);
|
|
byte opcode = (scr->getBuf(s->xs->addr.pc.getOffset())[0]) >> 1;
|
|
|
|
bool objMatch;
|
|
if (entry.exportId != -1 && strcmp(entry.objName, "") == 0)
|
|
objMatch = true;
|
|
else
|
|
objMatch = strcmp(objName, entry.objName) == 0;
|
|
|
|
return objMatch && selector == entry.selector &&
|
|
s->xs->debugExportId == entry.exportId && strcmp(entry.opcodeName, opcodeNames[opcode]) == 0;
|
|
}
|
|
|
|
void VmHooks::vm_hook_before_exec(Sci::EngineState *s) {
|
|
if (_just_finished) {
|
|
_just_finished = false;
|
|
_lastPc = NULL_REG;
|
|
return;
|
|
}
|
|
Script *scr = s->_segMan->getScript(s->xs->addr.pc.getSegment());
|
|
int scriptNumber = scr->getScriptNumber();
|
|
HookHashKey key = { scriptNumber, s->xs->addr.pc.getOffset() };
|
|
if (_hookScriptData.empty() && _lastPc != s->xs->addr.pc && _hooksMap.contains(key)) {
|
|
_lastPc = s->xs->addr.pc;
|
|
HookEntry entry = _hooksMap[key];
|
|
if (hook_exec_match(s, entry)) {
|
|
debugC(kDebugLevelPatcher, "vm_hook: patching script: %d, PC: %04x:%04x, obj: %s, selector: %s, extern: %d, opcode: %s", scriptNumber, PRINT_REG(s->xs->addr.pc), entry.objName, entry.selector.c_str(), entry.exportId, entry.opcodeName);
|
|
Common::Array<byte> buffer(entry.patch, entry.patchSize);
|
|
|
|
_hookScriptData = buffer;
|
|
} else {
|
|
debugC(kDebugLevelPatcher, "vm_hook: failed to match! script: %d, PC: %04x:%04x, obj: %s, selector: %s, extern: %d, opcode: %s", scriptNumber, PRINT_REG(s->xs->addr.pc), entry.objName, entry.selector.c_str(), entry.exportId, entry.opcodeName);
|
|
}
|
|
}
|
|
}
|
|
|
|
byte *VmHooks::data() {
|
|
return _hookScriptData.data() + _location;
|
|
}
|
|
|
|
bool VmHooks::isActive(Sci::EngineState *s) {
|
|
// if PC has changed, then we're temporary inactive - went to some other call, or send, etc.
|
|
return !_hookScriptData.empty() && _lastPc == s->xs->addr.pc;
|
|
}
|
|
|
|
void VmHooks::advance(int offset) {
|
|
int newLocation = _location + offset;
|
|
if (newLocation < 0)
|
|
error("VmHooks: requested to change offset before start of patch");
|
|
else if ((uint)newLocation > _hookScriptData.size())
|
|
error("VmHooks: requested to change offset after end of patch");
|
|
else if ((uint)newLocation == _hookScriptData.size()) {
|
|
_hookScriptData.clear();
|
|
_just_finished = true;
|
|
_location = 0;
|
|
} else
|
|
_location = newLocation;
|
|
}
|
|
|
|
|
|
} // End of namespace Sci
|