#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(); }