104 lines
3.6 KiB
C++
Raw Normal View History

2024-02-07 20:59:24 +08:00
#include"Alice.h"
/********************************************************************************************
System40 hook:
System40 is a game engine developed by Alicesoft.
Afaik, there are 2 very different types of System40. Each requires a particular hook.
Pattern 1: Either SACTDX.dll or SACT2.dll exports SP_TextDraw.
The first relative call in this function draw text to some surface.
Text pointer is return by last absolute indirect call before that.
Split parameter is a little tricky. The first register pushed onto stack at the begining
usually is used as font size later. According to instruction opcode map, push
eax -- 50, ecx -- 51, edx -- 52, ebx --53, esp -- 54, ebp -- 55, esi -- 56, edi -- 57
Split parameter value:
eax - -8, ecx - -C, edx - -10, ebx - -14, esp - -18, ebp - -1C, esi - -20, edi - -24
Just extract the low 4 bit and shift left 2 bit, then minus by -8,
will give us the split parameter. e.g. push ebx 53->3 *4->C, -8-C=-14.
Sometimes if split function is enabled, ITH will split text spoke by different
character into different thread. Just open hook dialog and uncheck split parameter.
Then click modify hook.
Pattern 2: *engine.dll exports SP_SetTextSprite.
At the entry point, EAX should be a pointer to some structure, character at +0x8.
Before calling this function, the caller put EAX onto stack, we can also find this
value on stack. But seems parameter order varies from game release. If a future
game breaks the EAX rule then we need to disassemble the caller code to determine
data offset dynamically.
********************************************************************************************/
static bool InsertAliceHook1(DWORD addr)
{
if (!addr) {
ConsoleOutput("AliceHook1: failed");
return false;
}
for (DWORD i = addr, s = addr; i < s + 0x100; i++)
if (*(BYTE *)i == 0xe8) { // Find the first relative call.
DWORD j = i + 5 + *(DWORD *)(i + 1);
while (true) { // Find the first register push onto stack.
DWORD c = ::disasm((BYTE *)s);
if (c == 1)
break;
s += c;
}
DWORD c = *(BYTE *)s;
HookParam hp;
hp.address = j;
hp.offset=get_reg(regs::eax);
hp.split = -8 -((c & 0xf) << 2);
hp.type = USING_STRING|USING_SPLIT;
//if (s>j) hp.type^=USING_SPLIT;
ConsoleOutput("INSERT AliceHook1");
//RegisterEngineType(ENGINE_SYS40);
return NewHook(hp, "System40");
}
ConsoleOutput("AliceHook1: failed");
return false;
}
static bool InsertAliceHook2(DWORD addr)
{
if (!addr) {
ConsoleOutput("AliceHook2: failed");
return false;
}
HookParam hp;
hp.address = addr;
hp.offset=get_reg(regs::eax);
hp.index = 0x8;
hp.type = DATA_INDIRECT;
ConsoleOutput("INSERT AliceHook2");
return NewHook(hp, "System40");
//RegisterEngineType(ENGINE_SYS40);
}
// jichi 8/23/2013 Move here from engine.cc
// Do not work for the latest Alice games
// jichi 5/13/2015: Looking for function entries in StoatSpriteEngine.dll
bool InsertAliceHook()
{
bool ok=false;
if (auto addr = Util::FindFunction("SP_TextDraw")) {
ok|= InsertAliceHook1(addr);
}
//if (GetFunctionAddr("SP_SetTextSprite", &addr, &low, &high, 0) && addr) {
// InsertAliceHook2(addr);
// return true;
//}
if (auto addr = Util::FindFunction("SP_SetTextSprite")) { // Artikash 6/27/2018 not sure if this works
ok|= InsertAliceHook2(addr);
}
//ConsoleOutput("AliceHook: failed");
return ok;
}
bool Alice::attach_function() {
return InsertAliceHook();
}