File: vm_hooks.cpp

package info (click to toggle)
scummvm 2.2.0%2Bdfsg1-4
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 227,768 kB
  • sloc: cpp: 2,525,134; ansic: 144,108; asm: 28,422; sh: 9,109; python: 8,774; xml: 6,003; perl: 3,523; java: 1,547; makefile: 948; yacc: 720; lex: 437; javascript: 336; objc: 81; sed: 22; php: 1
file content (228 lines) | stat: -rw-r--r-- 8,572 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/* 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