-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathHookEngine.d
500 lines (421 loc) · 18.6 KB
/
HookEngine.d
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
/***********************************\
HOOKENGINE
\***********************************/
//---------------------
// Register variables
//---------------------
var int EAX;
var int ECX;
var int EDX;
var int EBX;
var int ESP;
var int EBP;
var int ESI;
var int EDI;
//---------------------
// Overwrite instances
//---------------------
var int HookOverwriteInstances; // self, other, item
//========================================
// [intern] Hook controller
//========================================
func void _Hook(var int evtHAddr, // ESP-44
var int _edi, // ESP-40 // Function parameters in order of popad (reverse order of pushad)
var int _esi, // ESP-36
var int _ebp, // ESP-32
var int _esp, // ESP-28
var int _ebx, // ESP-24
var int _edx, // ESP-20
var int _ecx, // ESP-16
var int _eax) { // ESP-12
// Backup use-instance before anything else. Temporary variable for now, because it's done before locals()
var int _instBak_temp; _instBak_temp = MEM_GetUseInstance();
// Local backup for recursive hooks
locals();
// Secure register variables locally for recursive hooks
var int eaxBak; eaxBak = EAX;
var int ecxBak; ecxBak = ECX;
var int edxBak; edxBak = EDX;
var int ebxBak; ebxBak = EBX;
var int espBak; espBak = ESP;
var int ebpBak; ebpBak = EBP;
var int esiBak; esiBak = ESI;
var int ediBak; ediBak = EDI;
// Get address of yINSTANCE_HELP by symbol index 0
const int instHlpAddr = 0;
if (!instHlpAddr) {
instHlpAddr = MEM_GetSymbolByIndex(0)+zCParSymbol_offset_offset;
};
// Also secure global instances
var int selfBak; selfBak = _@(self);
var int otherBak; otherBak = _@(other);
var int itemBak; itemBak = _@(item);
var int iHlpBak; iHlpBak = MEM_ReadInt(instHlpAddr);
var int instBak; instBak = _instBak_temp;
// Update register variables
EAX = _eax;
ECX = _ecx;
EDX = _edx;
EBX = _ebx;
ESP = _esp;
EBP = _ebp;
ESI = _esi;
EDI = _edi;
// Check whether Ikarus is initialized for hooks that happen during level change
if (!_@(MEM_Parser)) {
MEM_InitLabels();
MEM_InitGlobalInst();
};
// Iterate over all registered event handler functions
var zCArray z; z = _^(evtHAddr);
var int l; l = z.numInArray;
var int a; a = MEM_Alloc(l<<2);
MEM_Copy(z.array, a, l); // Duplicate to be safe against manipulation during the loop
var int i; i = 0;
while(i < l); // Repeat does not work in combination with LeGo_Locals here!
// Clear data stack in-between function calls
MEM_Parser.datastack_sptr = 0;
// Do not overwrite the global instances by default
HookOverwriteInstances = FALSE;
// Obtain hooking function
var int funcID; funcID = MEM_ReadIntArray(a, i);
var zCPar_Symbol fncSymb; fncSymb = _^(MEM_GetSymbolByIndex(funcID));
// Supply function arguments if expected
var int stackOffset; stackOffset = 4;
var int j; j = 0;
while(j < (fncSymb.bitfield & zCPar_Symbol_bitfield_ele));
var zCPar_Symbol symb; symb = _^(MEM_GetSymbolByIndex(funcID+1+j));
var int stackValue; stackValue = MEM_ReadInt(ESP+stackOffset); stackOffset += 4;
if ((symb.bitfield & zCPar_Symbol_bitfield_type) == zPAR_TYPE_STRING) {
// Either zString or zString* on stack
var string str; str = "";
if (stackValue) {
if (stackValue == zString__vtbl) {
str = MEM_ReadString(ESP+stackOffset);
stackOffset += sizeof_zString-4;
} else if (MEM_ReadInt(stackValue) == zString__vtbl) {
str = MEM_ReadString(stackValue);
};
};
MEM_PushStringParam(str);
} else if ((symb.bitfield & zCPar_Symbol_bitfield_type) == zPAR_TYPE_INSTANCE) {
// Either symbol index or pointer on stack
if (stackValue > 0) && (stackValue < currSymbolTableLength) { // Exclude yINSTANCE_HELP
var zCPar_Symbol symb2; symb2 = _^(MEM_GetSymbolByIndex(stackValue));
if ((symb2.bitfield & zCPar_Symbol_bitfield_type) == zPAR_TYPE_INSTANCE) {
stackValue = symb2.offset;
};
};
symb.offset = stackValue;
MEM_PushInstParam(funcID+1+j);
} else {
// Otherwise push value directly (integer/float/...)
MEM_PushIntParam(stackValue);
};
j += 1;
end;
// Call the function
MEM_CallByID(funcID);
// Assign EAX from return value
if (fncSymb.offset) && (MEM_Parser.datastack_sptr > 0) {
if (fncSymb.offset == (zPAR_TYPE_INT >> 12)) || (fncSymb.offset == (zPAR_TYPE_FLOAT >> 12)) {
// Safety checks on stack integrity
if (MEM_Parser.datastack_sptr >= 2) {
var int sPtr; sPtr = MEM_Parser.datastack_sptr; // Stack pointer is constantly changing so copy it
var int tok; tok = contentParserAddress + zCParser_datastack_stack_offset + (sPtr-1)*4;
if (MEM_ReadInt(tok) == zPAR_TOK_PUSHINT) || (MEM_ReadInt(tok) == zPAR_TOK_PUSHVAR) {
// There is indeed a valid return value
EAX = MEM_PopIntResult();
};
};
} else {
// Strings are not supported, because we would need a unique string for each hook. Who frees the memory?
// Instances are not supported, because they are ambiguous: Return a pointer or a symbol ID?
// Since EAX is a 32-bit register, any non-simple data-type should be manually returned as a pointer
MEM_Error("HOOKENGINE: Only integer/float return values are supported. Return a pointer if necessary.");
// No need to clean up the stack here
};
};
// Restore global instances in between function calls
if (!HookOverwriteInstances) {
MEM_AssignInstSuppressNullWarning = TRUE;
self = _^(selfBak);
other = _^(otherBak);
item = _^(itemBak);
MEM_AssignInstSuppressNullWarning = FALSE;
};
MEM_WriteInt(instHlpAddr, iHlpBak);
MEM_SetUseInstance(instBak);
// Stack registers should be kept read-only in between function calls
ESP = _esp; // Stack pointer is read-only
i += 1;
end;
// Update modifiable registers on stack (ESP points to the position before pushad)
MEM_WriteInt(ESP-40, EDI);
MEM_WriteInt(ESP-36, ESI);
MEM_WriteInt(ESP-32, EBP);
MEM_WriteInt(ESP-24, EBX);
MEM_WriteInt(ESP-20, EDX);
MEM_WriteInt(ESP-16, ECX);
MEM_WriteInt(ESP-12, EAX);
// Restore register variables for recursive hooks
EDI = ediBak;
ESI = esiBak;
EBP = ebpBak;
ESP = espBak;
EBX = ebxBak;
EDX = edxBak;
ECX = ecxBak;
EAX = eaxBak;
// Just to be safe: restore again at the very end of the function
MEM_SetUseInstance(instBak);
};
//-------------------------
// Hash table of all hooks
//-------------------------
const int _Hook_htbl = 0;
//========================================
// Engine hook
//========================================
func void HookEngineI(var int address, var int oldInstr, var int function) {
var int SymbID; // Symbol index of 'function'
var int ptr; // Pointer to temporary memory of the old instruction
var int relAdr; // Relative address from 'address' to new assembly code
var int absAdr; // Absolute address
// ----- Safety checks -----
if (oldInstr < 5) {
PrintDebug("HOOKENGINE: oldInstr is too small. The minimum required length is 5 bytes.");
return;
};
SymbID = function;
if (SymbID == -1) {
PrintDebug("HOOKENGINE: The provided deadalus function was not found.");
return;
};
// ----- Find event handler in hash table -----
if (!_Hook_htbl) {
_Hook_htbl = _HT_Create();
};
// ----- Hook already present -----
if (_HT_Has(_Hook_htbl, address)) {
// Add deadalus function (new listener) to event handler once
MEM_PushIntParam(_HT_Get(_Hook_htbl, address));
MEM_PushIntParam(SymbID);
MEM_Call(EventPtr_AddOnceI); // EventPtr_AddOnceI(_HT_Get(_Hook_htbl, address), SymbID);
return;
};
// ----- Create event and add function as listener -----
MEM_Call(EventPtr_Create);
var int ev; ev = MEM_PopIntResult(); // var int ev; ev = Event_Create();
MEM_PushIntParam(ev);
MEM_PushIntParam(SymbID);
MEM_Call(EventPtr_AddI); // EventPtr_AddI(ev, SymbID);
_HT_Insert(_Hook_htbl, ev, address);
// ----- Backup old instruction -----
ptr = MEM_Alloc(oldInstr);
MEM_CopyBytes(address, ptr, oldInstr);
// ----- Allocate new stream for assembly code -----
ASM_Open(129 + oldInstr + 6 + 1); // Asm code + oldInstr + retn + 1
// ----- Treat possibly protected memory -----
MemoryProtectionOverride(address, oldInstr+3);
// ----- Add jump from engine function to new code -----
relAdr = ASMINT_CurrRun-address-5;
MEM_WriteByte(address + 0, ASMINT_OP_jmp);
MEM_WriteInt (address + 1, relAdr);
// ----- Write new assembly code -----
// Set up stack and backup general purpose registers
ASM_2(ASMINT_OP_subESPplus); ASM_1(8);
ASM_1(ASMINT_OP_pusha); // ESP -= 32 (8*4)
// Increase pushed ESP to correct it for use within Daedalus hook
ASM_2(ASMINT_OP_movESPtoEAX);
ASM_2(ASMINT_OP_addImToEAX); ASM_1(32+8); // 32 bytes popa, 8 bytes data stack backup
ASM_3(ASMINT_OP_movEAXtoESPplus); ASM_1(12); // Current stack position of pushed ESP
// Allocate memory for backing up Daedalus data stack for hooking external engine functions
ASM_1(ASMINT_OP_pushIm); ASM_4(MEMINT_SwitchG1G2(1024, 2048) * 4); // Data stack size
ASM_1(ASMINT_OP_call); ASM_4(malloc_adr-ASM_Here()-4);
ASM_2(ASMINT_OP_addImToESP); ASM_1(4); // Clean up 1 parameter from stack
ASM_3(ASMINT_OP_movEAXtoESPplus); ASM_1(32+4); // Save pointer on the stack
// Backup Daedalus data stack
ASM_1(ASMINT_OP_pushIm); ASM_4(MEMINT_SwitchG1G2(1024, 2048) * 4);
ASM_1(ASMINT_OP_pushIm); ASM_4(ContentParserAddress+zCParser_datastack_stack_offset);
ASM_1(ASMINT_OP_pushEAX);
ASM_1(ASMINT_OP_call); ASM_4(memcpy_adr-ASM_Here()-4);
ASM_2(ASMINT_OP_addImToESP); ASM_1(12); // Clean up 3 parameters from stack
// Backup Daedalus data stack "pointer"
ASM_1(ASMINT_OP_movMemToEAX); ASM_4(ContentParserAddress+zCParser_datastack_sptr_offset);
ASM_3(ASMINT_OP_movEAXtoESPplus); ASM_1(32); // Save pointer on the stack
// Call deadalus hook function
ASM_1(ASMINT_OP_pushIm); ASM_4(ev);
ASM_1(ASMINT_OP_pushIm); ASM_4(MEM_GetFuncID(_Hook));
ASM_1(ASMINT_OP_pushIm); ASM_4(parser);
ASM_1(ASMINT_OP_call); ASM_4(zParser__CallFunc-ASM_Here()-4);
ASM_2(ASMINT_OP_addImToESP); ASM_1(12); // 3*4: parser, _Hook, address
// Restore Daedalus stack "pointer"
ASM_3(ASMINT_OP_movESPplusToEAX); ASM_1(32);
ASM_2(ASMINT_OP_movEAXToMem); ASM_4(ContentParserAddress+zCParser_datastack_sptr_offset);
// Restore Daedalus stack
ASM_1(ASMINT_OP_pushIm); ASM_4(MEMINT_SwitchG1G2(1024, 2048) * 4);
ASM_3(ASMINT_OP_pushESPplus); ASM_1(32+4+4); // Current stack position of memory pointer
ASM_1(ASMINT_OP_pushIm); ASM_4(ContentParserAddress+zCParser_datastack_stack_offset);
ASM_1(ASMINT_OP_call); ASM_4(memcpy_adr-ASM_Here()-4);
ASM_2(ASMINT_OP_addImToESP); ASM_1(12); // Clean up 3 parameters from stack
// Free memory
ASM_3(ASMINT_OP_pushESPplus); ASM_1(32+4); // Current stack position of memory pointer
ASM_1(ASMINT_OP_call); ASM_4(free_adr-ASM_Here()-4);
ASM_2(ASMINT_OP_addImToESP); ASM_1(4); // Clean up 1 parameter from stack
// Clean up stack and pop altered registers
ASM_1(ASMINT_OP_popa);
ASM_2(ASMINT_OP_addImToESP); ASM_1(8);
// Resolve relative jump address of a possible third party hook at the same address
if (MEM_ReadByte(ptr) == ASMINT_OP_jmp) || (MEM_ReadByte(ptr) == ASMINT_OP_call) {
relAdr = MEM_ReadInt(ptr+1); // Relative jump from old address
absAdr = relAdr+5+address;
relAdr = absAdr-ASM_Here()-5; // Relative jump from new address
MEM_WriteInt(ptr+1, relAdr);
};
// Append original instruction
MEM_CopyBytes(ptr, ASMINT_Cursor, oldInstr);
MEM_Free(ptr);
ASMINT_Cursor += oldInstr;
// Return to engine function
ASM_1(ASMINT_OP_pushIm); ASM_4(address + oldInstr);
ASM_1(ASMINT_OP_retn);
var int i; i = ASM_Close();
};
func void HookEngineF(var int address, var int oldInstr, var func function) {
HookEngineI(address, oldInstr, MEM_GetFuncID(function));
};
func void HookEngine(var int address, var int oldInstr, var string function) {
HookEngineI(address, oldInstr, MEM_FindParserSymbol(STR_Upper(function)));
};
// Wrapper function for naming consistency
func void HookEngineS(var int address, var int oldInstr, var string function) {
HookEngine(address, oldInstr, function);
};
//========================================
// Check if address is hooked
//========================================
func int IsHooked(var int address) {
if (!_Hook_htbl) {
return FALSE;
};
return _HT_Has(_Hook_htbl, address);
};
//========================================
// Check if function hooks engine
//========================================
func int IsHookI(var int address, var int function) {
if (!IsHooked(address)) {
return FALSE;
};
var int ev; ev = _HT_Get(_Hook_htbl, address);
var int SymbID; SymbID = function;
// Check if listener exists
MEM_PushIntParam(ev);
MEM_PushIntParam(SymbID);
MEM_Call(EventPtr_HasI); // EventPtr_HasI(ev, SymbID)
return MEM_PopIntResult(); // This line is redundant (left here for readability)
};
func int IsHookF(var int address, var func function) {
return IsHookI(address, MEM_GetFuncID(function));
};
func int IsHook(var int address, var string function) {
return IsHookI(address, MEM_FindParserSymbol(STR_Upper(function)));
};
//========================================
// Remove listener (and engine hook)
//========================================
func void RemoveHookI(var int address, var int oldInstr, var int function) {
if (!IsHookI(address, function)) {
return;
};
var int ev; ev = _HT_Get(_Hook_htbl, address);
var int SymbID; SymbID = function;
// Remove listener
MEM_PushIntParam(ev);
MEM_PushIntParam(SymbID);
MEM_Call(EventPtr_RemoveI); // EventPtr_RemoveI(ev, SymbID)
// Is event empty now?
MEM_PushIntParam(ev);
MEM_Call(EventPtr_Empty); // EventPtr_Empty(ev);
if (MEM_PopIntResult()) && (oldInstr >= 5) {
/* If RemoveHookI is called with oldInstr == 0, the hook (ASM jump) remains.
* This is good for adding and removing a listener/hook frequently.
*/
// Remove hook from hash table
_HT_Remove(_Hook_htbl, address);
// Delete event
MEM_PushIntParam(ev);
MEM_Call(EventPtr_Delete); // EventPtr_Delete(ev);
// Check integrity of opcode at address (expecting jump)
if (MEM_ReadByte(address) != ASMINT_OP_jmp) {
MEM_Error("HOOKENGINE: Hook was invalidated by overwritten opcode");
return;
};
// Remove engine hook
var int newCodeAddr; newCodeAddr = MEM_ReadInt(address+1)+address+5;
var int rvtCodeAddr; rvtCodeAddr = newCodeAddr+129; // Original code
var int relAdr; var int absAdr;
// Revert relative jump address (see above)
if (MEM_ReadByte(rvtCodeAddr) == ASMINT_OP_jmp) || (MEM_ReadByte(rvtCodeAddr) == ASMINT_OP_call) {
relAdr = MEM_ReadInt(rvtCodeAddr+1); // Relative jump from new address
absAdr = relAdr+5+rvtCodeAddr;
relAdr = absAdr-address-5; // Relative jump from old address (reconstruct original jump)
MEM_WriteInt(rvtCodeAddr+1, relAdr);
};
// Replace jump with original instruction
MEM_CopyBytes(rvtCodeAddr, address, oldInstr);
// Free previously allocated space in memory (does this work as expected?)
MEM_Free(newCodeAddr);
};
};
func void RemoveHookF(var int address, var int oldInstr, var func function) {
RemoveHookI(address, oldInstr, MEM_GetFuncID(function));
};
func void RemoveHook(var int address, var int oldInstr, var string function) {
RemoveHookI(address, oldInstr, MEM_FindParserSymbol(STR_Upper(function)));
};
//========================================
// Replace Engine Function
//========================================
func void ReplaceEngineFuncI(var int funcAddr, var int thiscall_numParams, var int replaceFunc) {
// Check if already hooked
if (IsHooked(funcAddr)) {
if (!IsHookI(funcAddr, replaceFunc)) {
MEM_Error("Cannot replace/disable engine function: Address already hooked");
};
return;
};
// Write return at beginning of function
MemoryProtectionOverride(funcAddr, 3);
if (thiscall_numParams) {
MEM_WriteByte(funcAddr, /*C2*/ 194); // retn
MEM_WriteByte(funcAddr+1, thiscall_numParams*4);
MEM_WriteByte(funcAddr+2, 0);
} else {
MEM_WriteByte(funcAddr, ASMINT_OP_retn);
};
// Hook on top of return instruction
if (replaceFunc != /*NOFUNC*/ -1) {
HookEngineI(funcAddr, 5, replaceFunc);
};
};
func void ReplaceEngineFuncF(var int funcAddr, var int thiscall_numParams, var func replaceFunc) {
ReplaceEngineFuncI(funcAddr, thiscall_numParams, MEM_GetFuncID(replaceFunc));
};
func void ReplaceEngineFunc(var int funcAddr, var int thiscall_numParams, var string replaceFunc) {
ReplaceEngineFuncI(funcAddr, thiscall_numParams, MEM_FindParserSymbol(STR_Upper(replaceFunc)));
};
// Simple replace functions for return values
func void Hook_ReturnFalse() {
EAX = FALSE;
};
func void Hook_ReturnTrue() {
EAX = TRUE;
};
//========================================
// Disable Engine Function
//========================================
func void DisableEngineFunc(var int funcAddr, var int thiscall_numParams) {
ReplaceEngineFuncI(funcAddr, thiscall_numParams, -1);
};