#include "WillPlus.h" /** 1/18/2015 jichi Add new WillPlus * The old hook no longer works for new game. * Sample game: [150129] [honeybee] RE:BIRTHDAY SONG * * Note, WillPlus engine is migrating to UTF16 using GetGlyphOutlineW such as: * [141218] [Guily] 手�めにされる九人の堕女 * This engine does not work for GetGlyphOutlineW, which, however, does not need a H-code. * * See: http://sakuradite.com/topic/615 * * There WillPlus games have many hookable threads. * But it is kind of important to find the best one. * * By inserting hw point: * - There is a clean text thread with fixed memory address. * However, it cannot extract character name like GetGlyphOutlineA. * - This is a non-clean text thread, but it contains garbage such as %LC. * * By backtracking from GetGlyphOutlineA: * - GetGlyphOutlineA sometimes can extract all text, sometimes not. * - There are two GetGlyphOutlineA functions. * Both of them are called statically in the same function. * That function is hooked. * * Hooked function: * 0041820c cc int3 * 0041820d cc int3 * 0041820e cc int3 * 0041820f cc int3 * 00418210 81ec b4000000 sub esp,0xb4 * 00418216 8b8424 c4000000 mov eax,dword ptr ss:[esp+0xc4] * 0041821d 53 push ebx * 0041821e 8b9c24 d0000000 mov ebx,dword ptr ss:[esp+0xd0] * 00418225 55 push ebp * 00418226 33ed xor ebp,ebp * 00418228 56 push esi * 00418229 8bb424 dc000000 mov esi,dword ptr ss:[esp+0xdc] * 00418230 03c3 add eax,ebx * 00418232 57 push edi * 00418233 8bbc24 d8000000 mov edi,dword ptr ss:[esp+0xd8] * 0041823a 896c24 14 mov dword ptr ss:[esp+0x14],ebp * 0041823e 894424 4c mov dword ptr ss:[esp+0x4c],eax * 00418242 896c24 24 mov dword ptr ss:[esp+0x24],ebp * 00418246 39ac24 e8000000 cmp dword ptr ss:[esp+0xe8],ebp * 0041824d 75 29 jnz short .00418278 * 0041824f c74424 24 010000>mov dword ptr ss:[esp+0x24],0x1 * * ... * * 00418400 56 push esi * 00418401 52 push edx * 00418402 ff15 64c04b00 call dword ptr ds:[0x4bc064] ; gdi32.getglyphoutlinea * 00418408 8bf8 mov edi,eax * * The old WillPlus engine can also be inserted to the new games. * But it has no effects. * * A split value is used to get saving message out. * * Runtime stack for the scenario thread: * 0012d9ec 00417371 return to .00417371 from .00418210 * 0012d9f0 00000003 1 * 0012d9f4 00000000 2 * 0012d9f8 00000130 3 * 0012d9fc 0000001a 4 * 0012da00 0000000b 5 * 0012da04 00000016 6 * 0012da08 0092fc00 .0092fc00 ms gothic ; jichi: here's font * 0012da0c 00500aa0 .00500aa0 shun ; jichi: text is here in arg8 * 0012da10 0217dcc0 * * Runtime stack for name: * 0012d9ec 00417371 return to .00417371 from .00418210 * 0012d9f0 00000003 * 0012d9f4 00000000 * 0012d9f8 00000130 * 0012d9fc 0000001a * 0012da00 0000000b * 0012da04 00000016 * 0012da08 0092fc00 .0092fc00 * 0012da0c 00500aa0 .00500aa0 * 0012da10 0217dcc0 * 0012da14 00000000 * 0012da18 00000000 * * Runtime stack for non-dialog scenario text. * 0012e5bc 00438c1b return to .00438c1b from .00418210 * 0012e5c0 00000006 * 0012e5c4 00000000 * 0012e5c8 000001ae * 0012e5cc 000000c8 * 0012e5d0 0000000c * 0012e5d4 00000018 * 0012e5d8 0092fc00 .0092fc00 * 0012e5dc 0012e628 * 0012e5e0 0b0d0020 * 0012e5e4 004fda98 .004fda98 * * Runtime stack for saving message * 0012ed44 00426003 return to .00426003 from .00418210 * 0012ed48 000003c7 * 0012ed4c 00000000 * 0012ed50 000000d8 * 0012ed54 0000012f * 0012ed58 00000008 * 0012ed5c 00000010 * 0012ed60 0092fc00 .0092fc00 * 0012ed64 00951d88 ascii "2015/01/18" */ namespace { // unnamed void SpecialHookWillPlus(hook_stack *stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t *len) { // static DWORD detect_offset; // jichi 1/18/2015: this makes sure it only runs once // if (detect_offset) // return; DWORD i, l; union { DWORD retn; WORD *pw; BYTE *pb; }; retn = stack->retaddr; // jichi 1/18/2015: dynamically find function return address i = 0; while (*pw != 0xc483) { // add esp, $ l = ::disasm(pb); if (++i == 5) // ConsoleOutput("Fail to detect offset."); break; retn += l; } // jichi 2/11/2015: Check baddaddr which might crash the game on Windows XP. if (*pw == 0xc483 && !::IsBadReadPtr((LPCVOID)(pb + 2), 1) && !::IsBadReadPtr((LPCVOID)(*(pb + 2) - 8), 1)) { ConsoleOutput("WillPlus1 pattern found"); // jichi 1/18/2015: // By studying [honeybee] RE:BIRTHDAY SONG, it seems the scenario text is at fixed address // This offset might be used to find fixed address // However, this method cannot extract character name like GetGlyphOutlineA hp->offset = *(pb + 2) - 8; // Still extract the first text // hp->type ^= EXTERN_HOOK; char *str = *(char **)(stack->base + hp->offset); *data = (DWORD)str; *len = ::strlen(str); *split = 0; // 8/3/2014 jichi: use return address as split } else { // jichi 1/19/2015: Try willplus2 ConsoleOutput("WillPlus1 pattern not found, try WillPlus2 instead"); hp->offset = 4 * 8; // arg8, address of text hp->type = USING_STRING | NO_CONTEXT | USING_SPLIT; // merge different scenario threads hp->split = 4 * 1; // arg1 as split to get rid of saving message // The first text is skipped here // char *str = *(char **)(esp_base + hp->offset); //*data = (DWORD)str; //*len = ::strlen(str); } hp->text_fun = nullptr; // stop using text_fun any more // detect_offset = 1; } // Although the new hook also works for the old game, the old hook is still used by default for compatibility bool InsertOldWillPlusHook() { //__debugbreak(); enum { sub_esp = 0xec81 }; // jichi: caller pattern: sub esp = 0x81,0xec byte ULONG addr = MemDbg::findCallerAddress((ULONG)::GetGlyphOutlineA, sub_esp, processStartAddress, processStopAddress); if (!addr) { ConsoleOutput("WillPlus: function call not found"); return false; } HookParam hp; hp.address = addr; hp.text_fun = SpecialHookWillPlus; hp.type = USING_STRING; ConsoleOutput("INSERT WillPlus"); return NewHook(hp, "WillPlus"); } const char *_willplus_trim_a(const char *text, size_t *size) { int textSize = ::strlen(text); int prefix = 0; if (text[0] == '%') { while (prefix < textSize - 1 && text[prefix] == '%' && ::isupper(text[prefix + 1])) { prefix += 2; while (::isupper(text[prefix])) prefix++; } } { int pos = textSize; for (int i = textSize - 1; i >= prefix; i--) { char ch = text[i]; if (::isupper(ch)) ; else if (ch == '%') pos = i; else break; } int suffix = textSize - pos; if (size) *size = textSize - prefix - suffix; } return text + prefix; } const wchar_t *_willplus_trim_w(const wchar_t *text, size_t *size) { int textSize = ::wcslen(text); int prefix = 0; if (text[0] == '%') { while (prefix < textSize - 1 && text[prefix] == '%' && ::isupper(text[prefix + 1])) { prefix += 2; while (::isupper(text[prefix])) prefix++; } } { int pos = textSize; for (int i = textSize - 1; i >= prefix; i--) { wchar_t ch = text[i]; if (::isupper(ch)) ; else if (ch == '%') pos = i; else break; } int suffix = textSize - pos; if (size) *size = textSize - prefix - suffix; } return text + prefix; } void SpecialHookWillPlusA(hook_stack *stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t *len) { int index = 0; auto text = (LPCSTR)stack->eax; if (!text) return; if (index) // index == 1 is name text -= 1024; if (!*text) return; text = _willplus_trim_a(text, (size_t *)len); *data = (DWORD)text; *split = FIXED_SPLIT_VALUE << index; } bool WillPlus_extra_filter(void *data, size_t *size, HookParam *) { auto text = reinterpret_cast(data); StringFilter(text, size, L"%XS", 5); // remove %XS followed by 2 chars std::wstring str = text; str = str.substr(0, *size / 2); strReplace(str, L"\\n", L"\n"); std::wregex reg1(L"\\{(.*?):(.*?)\\}"); std::wstring result1 = std::regex_replace(str, reg1, L"$1"); std::wregex reg11(L"\\{(.*?);(.*?)\\}"); result1 = std::regex_replace(result1, reg11, L"$1"); std::wregex reg2(L"%[A-Z]+"); result1 = std::regex_replace(result1, reg2, L""); write_string_overwrite(data, size, result1); return true; }; bool InsertWillPlusAHook() { // by iov const BYTE bytes2[] = {0x8B, 0x00, 0xFF, 0x76, 0xFC, 0x8B, 0xCF, 0x50}; ULONG range2 = min(processStopAddress - processStartAddress, MAX_REL_ADDR); ULONG addr2 = MemDbg::findBytes(bytes2, sizeof(bytes2), processStartAddress, processStartAddress + range2); if (addr2) { HookParam myhp; myhp.address = addr2 + 2; myhp.type = CODEC_UTF16 | NO_CONTEXT | USING_STRING; myhp.offset = get_reg(regs::eax); myhp.filter_fun = WillPlus_extra_filter; char nameForUser[HOOK_NAME_SIZE] = "WillPlus3_memcpy"; ConsoleOutput("Insert: WillPlus3_memcpy Hook"); return NewHook(myhp, nameForUser); } const BYTE bytes[] = { 0x81, 0xec, 0x14, 0x08, 0x00, 0x00 // 0042B5E0 81EC 14080000 SUB ESP,0x814 ; jichi: text in eax, name in eax - 1024, able to copy }; DWORD addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress); if (!addr) { ConsoleOutput("WillPlusA: pattern not found"); return false; } HookParam hp; hp.address = addr; hp.text_fun = SpecialHookWillPlusA; hp.type = NO_CONTEXT; hp.filter_fun = NewLineStringFilterA; // remove two characters of "\\n" ConsoleOutput("INSERT WillPlusA"); return NewHook(hp, "WillPlusA"); } void SpecialHookWillPlusW(hook_stack *stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t *len) { auto text = (LPCWSTR)stack->ecx; if (!text || !*text) return; text = _willplus_trim_w(text, (size_t *)len); *len *= 2; *data = (DWORD)text; *split = FIXED_SPLIT_VALUE << hp->user_value; } bool InsertWillPlusWHook() { const BYTE bytes1[] = { // scenario 0x83, 0xc0, 0x20, // 00452b02 83c0 20 add eax,0x20 ; jichi: hook before here, text in ecx 0x33, 0xd2, // 00452b05 33d2 xor edx,edx 0x8b, 0xc1, // 00452b07 8bc1 mov eax,ecx 0xc7, 0x84, 0x24, 0xe0, 0x01, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00 // 00452b09 c78424 e0010000 07000000 mov dword ptr ss:[esp+0x1e0],0x7 // 00452b14 c78424 dc010000 00000000 mov dword ptr ss:[esp+0x1dc],0x0 }; const BYTE bytes2[] = { // name 0x33, 0xdb, // 00453521 33db xor ebx,ebx ; jichi: hook here, text in ecx 0x33, 0xd2, // 00453523 33d2 xor edx,edx 0x8b, 0xc1, // 00453525 8bc1 mov eax,ecx 0xc7, 0x84, 0x24, 0x88, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00 // 00453527 c78424 88000000 07000000 mov dword ptr ss:[esp+0x88],0x7 // 00453532 899c24 84000000 mov dword ptr ss:[esp+0x84],ebx }; const BYTE *bytes[] = {bytes1, bytes2}; const size_t sizes[] = {sizeof(bytes1), sizeof(bytes2)}; auto succ = false; for (int i = 0; i < 2; i++) { DWORD addr = MemDbg::findBytes(bytes[i], sizes[i], processStartAddress, processStopAddress); if (!addr) { ConsoleOutput("WillPlusW: pattern not found"); return false; } HookParam hp; hp.address = addr; hp.text_fun = SpecialHookWillPlusW; hp.type = NO_CONTEXT | CODEC_UTF16; hp.user_value = i; hp.filter_fun = NewLineStringFilterW; // remove two characters of "\\n" ConsoleOutput("INSERT WillPlusW"); succ |= NewHook(hp, "WillPlusW"); } return succ; } /* Artikash 9/29/2018: Updated WillPlus hook Sample games: https://vndb.org/r54549 https://vndb.org/v22705 Not too sure about the stability of this pattern, but it works for both of the above Hook code for first game: /HQ-8*0@43D620. This seems fairly stable: __thiscall calling convention and first member points to string Method to find hook code: trace call stack from GetGlyphOutlineW Disassembly from first game (damekoi). The first few instructions are actually a common function prologue: not enough to locate hook Hooking SysAllocString also seems to work, but has some garbage 0043D61D - C2 0800 - ret 0008 { 8 } 0043D620 - 55 - push ebp 0043D621 - 8B EC - mov ebp,esp 0043D623 - 6A FF - push -01 { 255 } 0043D625 - 68 6B6D5400 - push 00546D6B { [139] } 0043D62A - 64 A1 00000000 - mov eax,fs:[00000000] { 0 } 0043D630 - 50 - push eax 0043D631 - 81 EC 30010000 - sub esp,00000130 { 304 } 0043D637 - A1 08E05800 - mov eax,[0058E008] { [6A9138CD] } 0043D63C - 33 C5 - xor eax,ebp 0043D63E - 89 45 EC - mov [ebp-14],eax 0043D641 - 53 - push ebx 0043D642 - 56 - push esi 0043D643 - 57 - push edi 0043D644 - 50 - push eax 0043D645 - 8D 45 F4 - lea eax,[ebp-0C] 0043D648 - 64 A3 00000000 - mov fs:[00000000],eax { 0 } 0043D64E - 8B F9 - mov edi,ecx 0043D650 - 89 BD E8FEFFFF - mov [ebp-00000118],edi 0043D656 - 8B 45 08 - mov eax,[ebp+08] 0043D659 - 8B 4D 14 - mov ecx,[ebp+14] 0043D65C - F3 0F10 45 1C - movss xmm0,[ebp+1C] 0043D661 - 8B 5D 18 - mov ebx,[ebp+18] 0043D664 - 89 85 10FFFFFF - mov [ebp-000000F0],eax 0043D66A - 8B 45 10 - mov eax,[ebp+10] 0043D66D - 89 85 08FFFFFF - mov [ebp-000000F8],eax 0043D673 - 89 47 68 - mov [edi+68],eax 0043D676 - 8B 45 20 - mov eax,[ebp+20] 0043D679 - 51 - push ecx ... */ static bool InsertNewWillPlusHook() { bool found = false; const BYTE characteristicInstructions[] = { 0xc2, 0x08, 0, // ret 0008; Seems to always be ret 8 before the hookable function. not sure why, not sure if stable. 0x55, // push ebp; hook here 0x8b, 0xec, // mov ebp,esp 0x6a, 0xff, // push -01 0x68, XX4, // push ? 0x64, 0xa1, 0, 0, 0, 0, // mov eax,fs:[0] 0x50, // push eax 0x81, 0xec, XX4, // sub esp,? 0xa1, XX4, // mov eax,[?] 0x33, 0xc5, // xor eax,ebp // 0x89, 0x45, 0xec // mov [ebp-14],eax; not sure if 0x14 is stable }; for (auto addr : Util::SearchMemory(characteristicInstructions, sizeof(characteristicInstructions), PAGE_EXECUTE, processStartAddress, processStopAddress)) { HookParam hp; hp.address = addr + 3; hp.type = USING_STRING | CODEC_UTF16 | DATA_INDIRECT; hp.offset = get_reg(regs::ecx); hp.index = 0; found |= NewHook(hp, "WillPlus2"); } /* hook cmp reg,0x3000 Sample games: https://vndb.org/r54549 https://vndb.org/v22705 https://vndb.org/v24852 https://vndb.org/v25719 https://vndb.org/v27227 https://vndb.org/v27385 https://vndb.org/v34544 https://vndb.org/v35279 https://vndb.org/v36011 */ const BYTE pattern[] = { 0x81, XX, 0x00, 0x30, 0x00, 0x00 // 81FE 00300000 cmp esi,0x3000 // or 81FB 00300000 cmp ebx,0x3000 // or 81FF 00300000 cmp edi,0x3000 // je xx // 8b4D A8 mov ecx,dword ptr ss:[ebp-??] hook here // 85C9 test ecx,ecx }; for (auto addr : Util::SearchMemory(pattern, sizeof(pattern), PAGE_EXECUTE, processStartAddress, processStopAddress)) { if (*(WORD *)(addr + 0xb) != 0xC985) continue; BYTE byte = *(BYTE *)(addr + 1); regs offset = regs::invalid; switch (byte) { case 0xf9: offset = regs::ecx; break; case 0xfa: offset = regs::edx; break; case 0xfb: offset = regs::ebx; break; case 0xfc: offset = regs::esp; break; case 0xfd: offset = regs::ebp; break; case 0xfe: offset = regs::esi; break; case 0xff: offset = regs::edi; break; }; if (offset != regs::invalid) { HookParam hp; hp.address = addr + 8; hp.type = CODEC_UTF16; hp.offset = get_reg(offset); found |= NewHook(hp, "WillPlus3"); } } if (!found) ConsoleOutput("WillPlus: failed to find instructions"); return found; } } // unnamed namespace bool InsertWillPlusHook() { bool ok = InsertOldWillPlusHook(); ok = InsertWillPlusWHook() || InsertNewWillPlusHook() || InsertWillPlusAHook() || ok; return ok; } namespace will3 { int kp = 0; int lf = 0; int lc = 0; bool hookBefore(hook_stack *s, void *data, size_t *len, uintptr_t *role) { // DOUT(QString::fromUtf16((LPWSTR)s->stack[6]));//"MS UI Gothic" // DOUT(QString::fromUtf16((LPWSTR)s->stack[7]));//"���������ˤˤʤꤿ����%K%P" auto text = (LPWSTR)s->stack[7]; // text in arg1 if (!text || !*text) return false; auto split = s->stack[0]; // retaddr std::wstring str = ((LPWSTR)s->stack[7]); kp = 0; lf = 0; if (endWith(str, L"%K%P")) { kp = 1; str = str.substr(0, str.size() - 4); } if (startWith(str, L"%LF")) { lf = 1; str = str.substr(3); } if (startWith(str, L"%LC")) { lc = 1; str = str.substr(3); } std::wregex reg1(L"\\{(.*?):(.*?)\\}"); str = std::regex_replace(str, reg1, L"$1"); std::wregex reg11(L"\\{(.*?);(.*?)\\}"); str = std::regex_replace(str, reg11, L"$1"); write_string_overwrite(data, len, str); return true; } void hookafter(hook_stack *s, void *data, size_t len) { auto data_ = std::wstring((wchar_t *)data, len / 2); // EngineController::instance()->dispatchTextWSTD(innner, Engine::ScenarioRole, 0); if (kp) { data_.append(L"%K%P"); } if (lf) { data_ = L"%LF" + data_; } if (lc) { data_ = L"%LC" + data_; } s->stack[7] = (ULONG)(data_.c_str()); } } bool InsertWillPlus4Hook() { // 星の乙女と六華の姉妹 const BYTE bytes[] = { 0xc7, 0x45, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x33, 0xc9, 0xc7, 0x47, 0x78, 0x00, 0x00, 0x00, 0x00}; ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress); if (addr == 0) return false; addr = MemDbg::findEnclosingFunctionBeforeDword(0x83dc8b53, addr, MemDbg::MaximumFunctionSize, 1); if (addr == 0) return false; HookParam hp; hp.address = addr; hp.offset = get_stack(7); // hp.filter_fun = WillPlus_extra_filter; hp.type = USING_STRING | CODEC_UTF16 | EMBED_ABLE; hp.hook_before = will3::hookBefore; hp.newlineseperator = L"\\n"; hp.hook_after = will3::hookafter; return NewHook(hp, "EmbedWillplus3"); } bool InsertWillPlus5Hook() { // ensemble 29th Project『乙女の剣と秘めごとコンチェルト』オフィシャルサイト 体验版 const BYTE bytes[] = { 0x3d, XX2, 0x00, 0x00, 0x72, XX, 0x3d, XX2, 0x00, 0x00, 0x77}; /*if (v26 >= 0xE63E) { if (v26 <= 0xE757)*/ /*3D 3E E6 00 00 cmp eax, 0E63Eh .text:0040A24B 72 6C jb short loc_40A2B9 .text : 0040A24B .text : 0040A24D 3D 57 E7 00 00 cmp eax, 0E757h .text : 0040A252 77 71 ja short loc_40A2C5*/ bool ok = false; auto addrs = Util::SearchMemory(bytes, sizeof(bytes), PAGE_EXECUTE, processStartAddress, processStopAddress); for (auto addr : addrs) { HookParam hp; hp.address = addr; hp.offset = get_reg(regs::eax); hp.type = CODEC_UTF16; ConsoleOutput("INSERT WillPlus_extra2"); ok |= NewHook(hp, "WillPlus_extra2"); } return ok; } bool insertwillplus6() { /* 0x00492870 0: 50 push eax 1: b8 01 00 00 00 mov eax,0x1 6: 8d 74 24 18 lea esi,[esp+0x18] a: e8 f1 f5 f6 ff call 0xfff6f600 f: 6a 01 push 0x1 11: 68 7c 47 55 00 push 0x55477c 16: 33 c0 xor eax,eax 18: 8b d6 mov edx,esi 1a: e8 21 8c f7 ff call 0xfff78c40 //hook after call,但有的句子没有 1f: 83 f8 ff cmp eax,0xffffffff 22: 75 dc jne 0x0 //这里 24: 8d 44 24 14 lea eax,[esp+0x14] 28: 8b cd mov ecx,ebp 2a: e8 81 f3 04 00 call 0x4f3b0 2f: 83 7c 24 2c 08 cmp DWORD PTR [esp+0x2c],0x8 34: 8b f0 mov esi,eax 36: 72 0d jb 0x45 38: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18] 3c: 50 push eax 3d: e8 5e d6 09 00 call 0x9d6a0 42: 83 c4 04 add esp,0x4 45: 33 c9 xor ecx,ecx 47: c7 44 24 2c 07 00 00 mov DWORD PTR [esp+0x2c],0x7 */ // 想いを捧げる乙女のメロディー const BYTE bytes[] = { 0x6a, 0x01, 0x68, 0x7c, 0x47, 0x55, 0x00, 0x33, 0xc0, 0x8b, 0xd6, 0xe8, XX4, 0x83, 0xf8, 0xff, 0x75, 0xdc}; auto addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress); if (addr == 0) return false; addr += sizeof(bytes); ConsoleOutput("%p %p %p", addr, processStartAddress, processStopAddress); HookParam hp; hp.address = addr; hp.offset = get_stack(6); hp.type = CODEC_UTF16 | USING_STRING; ConsoleOutput("INSERT WillPlus6"); return NewHook(hp, "WillPlus6"); } bool willX() { // 世界でいちばんNGな恋 // .text:0040EAE9 81 FE 94 81 00 00 cmp esi, 8194h // .text:0040EAEF 74 2C jz short loc_40EB1D // .text:0040EAEF // .text:0040EAF1 81 FE 74 84 00 00 cmp esi, 8474h // .text:0040EAF7 74 24 jz short loc_40EB1D // .text:0040EAF7 // .text:0040EAF9 81 FE 97 81 00 00 cmp esi, 8197h // .text:0040EAFF 74 1C jz short loc_40EB1D // .text:0040EAFF // .text:0040EB01 81 FE 90 81 00 00 cmp esi, 8190h // .text:0040EB07 74 14 jz short loc_40EB1D // .text:0040EB07 // .text:0040EB09 81 FE 59 81 00 00 cmp esi, 8159h // .text:0040EB0F 74 0C jz short loc_40EB1D // .text:0040EB0F // .text:0040EB11 81 FE 96 81 00 00 cmp esi, 8196h // .text:0040EB17 0F 85 FF 00 00 00 jnz loc_40EC1C const BYTE bytes[] = { 0x81, 0xFE, 0x94, 0x81, 0x00, 0x00, 0x74, XX, 0x81, 0xFE, 0x74, 0x84, 0x00, 0x00, 0x74, XX, 0x81, 0xFE, 0x97, 0x81, 0x00, 0x00, 0x74, XX, 0x81, 0xFE, 0x90, 0x81, 0x00, 0x00, 0x74, XX, 0x81, 0xFE, 0x59, 0x81, 0x00, 0x00, 0x74, XX, 0x81, 0xFE, 0x96, 0x81, 0x00, 0x00}; auto addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress); if (addr == 0) return false; auto succ = false; { HookParam hp; hp.address = addr; hp.offset = get_reg(regs::esi); hp.type = NO_CONTEXT | CODEC_ANSI_BE; succ |= NewHook(hp, "willAN"); } addr = MemDbg::findEnclosingAlignedFunction(addr); if (addr) { HookParam hp; hp.address = addr; hp.offset = get_stack(7); hp.type = USING_STRING; succ |= NewHook(hp, "willS"); } return succ; } namespace { // unnamed // Sample prefix: %LF // Sample suffix: %L%P%W template strT trim(strT text, int *size) { int length = *size; if (text[0] == '%') { // handle prefix int pos = 0; while (pos < length - 1 && text[pos] == '%' && ::isupper(text[pos + 1])) { pos += 2; while (::isupper(text[pos])) pos++; } if (pos) { length -= pos; text += pos; } } { // handle suffix int pos = length; for (int i = length - 1; i >= 0; i--) { if (::isupper(text[i])) ; else if (text[i] == '%' && ::isupper(text[i + 1])) pos = i; else break; } length = pos; } *size = length; return text; } struct textinfo { std::wstring text_; int stackIndex_; int role_; }; std::unordered_map savetyperef; namespace TextHookW { // typedef TextHookW Self; template bool hookBefore(hook_stack *s, void *data, size_t *len, uintptr_t *role) { auto info = savetyperef.at(idx); enum { sig = 0 }; auto text = (LPCWSTR)s->stack[info->stackIndex_]; if (!text || !*text) return false; int size = ::wcslen(text), trimmedSize = size; auto trimmedText = trim(text, &trimmedSize); if (!trimmedSize || !*trimmedText) return false; std::wstring oldText = std::wstring(trimmedText, trimmedSize); write_string_overwrite(data, len, oldText); return true; } template void hookafter(hook_stack *s, void *data, size_t len) { auto newText = std::wstring((LPWSTR)data, len / 2); auto info = savetyperef.at(idx); enum { sig = 0 }; auto text = (LPCWSTR)s->stack[info->stackIndex_]; if (!text || !*text) return; int size = ::wcslen(text), trimmedSize = size; auto trimmedText = trim(text, &trimmedSize); if (!trimmedSize || !*trimmedText) return; std::wstring oldText = std::wstring(trimmedText, trimmedSize); if (newText == oldText) return; int prefixSize = trimmedText - text, suffixSize = size - prefixSize - trimmedSize; if (prefixSize) newText.insert(0, std::wstring(text, prefixSize)); if (suffixSize) newText.append(std::wstring(trimmedText + trimmedSize, suffixSize)); info->text_ = newText; s->stack[info->stackIndex_] = (ULONG)info->text_.c_str(); } // explicit TextHookW(int hookStackIndex, int role = Engine::UnknownRole) : stackIndex_(hookStackIndex), role_(role) {} template bool attach(const uint8_t *pattern, size_t patternSize, ULONG startAddress, ULONG stopAddress, int hookStackIndex, int role = Engine::UnknownRole) { ULONG addr = MemDbg::findBytes(pattern, patternSize, startAddress, stopAddress); if (addr == 0) return false; HookParam hp; hp.address = addr; auto _tinfo = new textinfo{}; _tinfo->role_ = role; _tinfo->stackIndex_ = hookStackIndex; savetyperef[_type] = _tinfo; hp.hook_before = hookBefore<_type>; hp.type = EMBED_ABLE | CODEC_UTF16; hp.newlineseperator = L"\\n"; hp.hook_after = hookafter<_type>; hp.hook_font = F_MultiByteToWideChar | F_GetGlyphOutlineW; char _[] = "EmbedWillplusW0"; _[sizeof(_) - 2] += _type; return NewHook(hp, _); } }; /** * Sample game: なついろレシピ * See: http://capita.tistory.com/m/post/251 * * Scenario: * 00452A8F 77 05 JA SHORT .00452A96 * 00452A91 E8 A25B0B00 CALL .00508638 ; JMP to msvcr90._invalid_parameter_noinfo * 00452A96 8B43 0C MOV EAX,DWORD PTR DS:[EBX+0xC] * 00452A99 8B48 18 MOV ECX,DWORD PTR DS:[EAX+0x18] * 00452A9C 83C0 10 ADD EAX,0x10 * 00452A9F 33D2 XOR EDX,EDX * 00452AA1 8BC1 MOV EAX,ECX * 00452AA3 C78424 C4010000 >MOV DWORD PTR SS:[ESP+0x1C4],0x7 * 00452AAE C78424 C0010000 >MOV DWORD PTR SS:[ESP+0x1C0],0x0 * 00452AB9 66:899424 B00100>MOV WORD PTR SS:[ESP+0x1B0],DX * 00452AC1 8D70 02 LEA ESI,DWORD PTR DS:[EAX+0x2] * 00452AC4 66:8B10 MOV DX,WORD PTR DS:[EAX] * 00452AC7 83C0 02 ADD EAX,0x2 * 00452ACA 66:85D2 TEST DX,DX * 00452ACD ^75 F5 JNZ SHORT .00452AC4 * 00452ACF 2BC6 SUB EAX,ESI * 00452AD1 D1F8 SAR EAX,1 * 00452AD3 50 PUSH EAX * 00452AD4 51 PUSH ECX * 00452AD5 8DB424 B4010000 LEA ESI,DWORD PTR SS:[ESP+0x1B4] * 00452ADC E8 DF4DFBFF CALL .004078C0 * 00452AE1 C68424 B8020000 >MOV BYTE PTR SS:[ESP+0x2B8],0x8 * 00452AE9 8B43 10 MOV EAX,DWORD PTR DS:[EBX+0x10] * 00452AEC 2B43 0C SUB EAX,DWORD PTR DS:[EBX+0xC] * 00452AEF C1F8 04 SAR EAX,0x4 * 00452AF2 83F8 02 CMP EAX,0x2 * 00452AF5 77 05 JA SHORT .00452AFC * 00452AF7 E8 3C5B0B00 CALL .00508638 ; JMP to msvcr90._invalid_parameter_noinfo * 00452AFC 8B43 0C MOV EAX,DWORD PTR DS:[EBX+0xC] * 00452AFF 8B48 28 MOV ECX,DWORD PTR DS:[EAX+0x28] * 00452B02 83C0 20 ADD EAX,0x20 ; jichi: hook before here, text in ecx * 00452B05 33D2 XOR EDX,EDX * 00452B07 8BC1 MOV EAX,ECX * 00452B09 C78424 E0010000 07000000 MOV DWORD PTR SS:[ESP+0x1E0],0x7 ; jichi: key pattern is here, text in eax * 00452B14 C78424 DC010000 00000000 MOV DWORD PTR SS:[ESP+0x1DC],0x0 * 00452B27 8D70 02 LEA ESI,DWORD PTR DS:[EAX+0x2] * 00452B2A 33DB XOR EBX,EBX * 00452B2C 8D6424 00 LEA ESP,DWORD PTR SS:[ESP] * 00452B30 66:8B10 MOV DX,WORD PTR DS:[EAX] * 00452B33 83C0 02 ADD EAX,0x2 * 00452B36 66:3BD3 CMP DX,BX * 00452B39 ^75 F5 JNZ SHORT .00452B30 * 00452B3B 2BC6 SUB EAX,ESI * 00452B3D D1F8 SAR EAX,1 * 00452B3F 50 PUSH EAX * 00452B40 51 PUSH ECX * 00452B41 8DB424 D0010000 LEA ESI,DWORD PTR SS:[ESP+0x1D0] * 00452B48 E8 734DFBFF CALL .004078C0 * 00452B4D C68424 B8020000 >MOV BYTE PTR SS:[ESP+0x2B8],0x9 * 00452B55 895C24 1C MOV DWORD PTR SS:[ESP+0x1C],EBX * 00452B59 395C24 14 CMP DWORD PTR SS:[ESP+0x14],EBX * 00452B5D 0F84 77080000 JE .004533DA * 00452B63 BE 07000000 MOV ESI,0x7 * 00452B68 33C0 XOR EAX,EAX * 00452B6A 895C24 20 MOV DWORD PTR SS:[ESP+0x20],EBX * 00452B6E 89B424 FC010000 MOV DWORD PTR SS:[ESP+0x1FC],ESI * 00452B75 899C24 F8010000 MOV DWORD PTR SS:[ESP+0x1F8],EBX * 00452B7C 66:898424 E80100>MOV WORD PTR SS:[ESP+0x1E8],AX * 00452B84 8D4C24 3C LEA ECX,DWORD PTR SS:[ESP+0x3C] * 00452B88 51 PUSH ECX * 00452B89 C68424 BC020000 >MOV BYTE PTR SS:[ESP+0x2BC],0xA * 00452B91 E8 7AACFCFF CALL .0041D810 * 00452B96 C68424 B8020000 >MOV BYTE PTR SS:[ESP+0x2B8],0xB * 00452B9E 399C24 C0010000 CMP DWORD PTR SS:[ESP+0x1C0],EBX * 00452BA5 0F84 BB020000 JE .00452E66 * 00452BAB 81C7 14010000 ADD EDI,0x114 */ bool attachScenarioHookW1(ULONG startAddress, ULONG stopAddress) { // ECX PTR: 83 C0 20 33 D2 8B C1 C7 84 24 E0 01 00 00 07 00 00 00 const uint8_t bytes[] = { 0x83, 0xc0, 0x20, // 00452b02 83c0 20 add eax,0x20 ; jichi: hook before here, text in ecx 0x33, 0xd2, // 00452b05 33d2 xor edx,edx 0x8b, 0xc1, // 00452b07 8bc1 mov eax,ecx 0xc7, 0x84, 0x24, 0xe0, 0x01, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00 // 00452b09 c78424 e0010000 07000000 mov dword ptr ss:[esp+0x1e0],0x7 // 00452b14 c78424 dc010000 00000000 mov dword ptr ss:[esp+0x1dc],0x0 }; int ecx = get_reg(regs::ecx) / 4; return TextHookW::attach<1>(bytes, sizeof(bytes), startAddress, stopAddress, ecx, Engine::ScenarioRole); } /** * 1/9/2016: 見上げてごらん、夜空の星を 体験版 * * 0045580D C68424 B8020000 08 MOV BYTE PTR SS:[ESP+0x2B8],0x8 * 00455815 8B47 10 MOV EAX,DWORD PTR DS:[EDI+0x10] * 00455818 2B47 0C SUB EAX,DWORD PTR DS:[EDI+0xC] * 0045581B C1F8 04 SAR EAX,0x4 * 0045581E 83F8 02 CMP EAX,0x2 * 00455821 77 05 JA SHORT .00455828 * 00455823 E8 A0F70B00 CALL .00514FC8 ; JMP to msvcr90._invalid_parameter_noinfo * 00455828 8B7F 0C MOV EDI,DWORD PTR DS:[EDI+0xC] * 0045582B 83C7 20 ADD EDI,0x20 * 0045582E 8B7F 08 MOV EDI,DWORD PTR DS:[EDI+0x8] * 00455831 33C9 XOR ECX,ECX * 00455833 8BC7 MOV EAX,EDI ; jichi: hook befoe here, text in eax assigned from edi * 00455835 C78424 E0010000 07000000 MOV DWORD PTR SS:[ESP+0x1E0],0x7 ; jichi: key pattern is here, text i eax * 00455840 899C24 DC010000 MOV DWORD PTR SS:[ESP+0x1DC],EBX * 00455847 66:898C24 CC010000 MOV WORD PTR SS:[ESP+0x1CC],CX * 0045584F 8D50 02 LEA EDX,DWORD PTR DS:[EAX+0x2] * 00455852 66:8B08 MOV CX,WORD PTR DS:[EAX] * 00455855 83C0 02 ADD EAX,0x2 * 00455858 66:3BCB CMP CX,BX * 0045585B ^75 F5 JNZ SHORT .00455852 * 0045585D 2BC2 SUB EAX,EDX * 0045585F D1F8 SAR EAX,1 * 00455861 50 PUSH EAX * 00455862 57 PUSH EDI * 00455863 8DB424 D0010000 LEA ESI,DWORD PTR SS:[ESP+0x1D0] * 0045586A E8 2120FBFF CALL .00407890 * 0045586F C68424 B8020000 09 MOV BYTE PTR SS:[ESP+0x2B8],0x9 * 00455877 895C24 30 MOV DWORD PTR SS:[ESP+0x30],EBX * 0045587B 395C24 18 CMP DWORD PTR SS:[ESP+0x18],EBX * 0045587F 0F84 D1080000 JE .00456156 * 00455885 33D2 XOR EDX,EDX * 00455887 895C24 24 MOV DWORD PTR SS:[ESP+0x24],EBX * 0045588B C78424 FC010000 07000000 MOV DWORD PTR SS:[ESP+0x1FC],0x7 * 00455896 899C24 F8010000 MOV DWORD PTR SS:[ESP+0x1F8],EBX * 0045589D 66:899424 E8010000 MOV WORD PTR SS:[ESP+0x1E8],DX * 004558A5 8D4424 3C LEA EAX,DWORD PTR SS:[ESP+0x3C] */ bool attachScenarioHookW2(ULONG startAddress, ULONG stopAddress) { // key pattern: C78424 E0010000 07000000 const uint8_t bytes[] = { 0x8b, 0xc7, // 00455833 8bc7 mov eax,edi ; jichi: text in eax assigned from edi 0xc7, 0x84, 0x24, 0xe0, 0x01, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00 // 00455835 c78424 e0010000 07000000 mov dword ptr ss:[esp+0x1e0],0x7 ; jichi: key pattern is here, text i eax }; int edi = get_reg(regs::edi) / 4; return TextHookW::attach<2>(bytes, sizeof(bytes), startAddress, stopAddress, edi, Engine::ScenarioRole); } /** * Sample game: なついろレシピ * See: http://capita.tistory.com/m/post/251 * * Name: * * 004534FA 64:A3 00000000 MOV DWORD PTR FS:[0],EAX * 00453500 8B75 14 MOV ESI,DWORD PTR SS:[EBP+0x14] * 00453503 8B46 10 MOV EAX,DWORD PTR DS:[ESI+0x10] * 00453506 2B46 0C SUB EAX,DWORD PTR DS:[ESI+0xC] * 00453509 8BF9 MOV EDI,ECX * 0045350B C1F8 04 SAR EAX,0x4 * 0045350E 897C24 14 MOV DWORD PTR SS:[ESP+0x14],EDI * 00453512 85C0 TEST EAX,EAX * 00453514 77 05 JA SHORT .0045351B * 00453516 E8 1D510B00 CALL .00508638 ; JMP to msvcr90._invalid_parameter_noinfo * 0045351B 8B76 0C MOV ESI,DWORD PTR DS:[ESI+0xC] * 0045351E 8B4E 08 MOV ECX,DWORD PTR DS:[ESI+0x8] * 00453521 33DB XOR EBX,EBX ; jichi: hook here, text in ecx * 00453523 33D2 XOR EDX,EDX * 00453525 8BC1 MOV EAX,ECX * 00453527 C78424 88000000 07000000 MOV DWORD PTR SS:[ESP+0x88],0x7 * 00453532 899C24 84000000 MOV DWORD PTR SS:[ESP+0x84],EBX * 00453539 66:895424 74 MOV WORD PTR SS:[ESP+0x74],DX * 0045353E 8D70 02 LEA ESI,DWORD PTR DS:[EAX+0x2] * 00453541 66:8B10 MOV DX,WORD PTR DS:[EAX] * 00453544 83C0 02 ADD EAX,0x2 * 00453547 66:3BD3 CMP DX,BX * 0045354A ^75 F5 JNZ SHORT .00453541 * 0045354C 2BC6 SUB EAX,ESI * 0045354E D1F8 SAR EAX,1 * 00453550 50 PUSH EAX * 00453551 51 PUSH ECX * 00453552 8D7424 78 LEA ESI,DWORD PTR SS:[ESP+0x78] * 00453556 E8 6543FBFF CALL .004078C0 * 0045355B 899C24 70010000 MOV DWORD PTR SS:[ESP+0x170],EBX * 00453562 A1 DCAA5500 MOV EAX,DWORD PTR DS:[0x55AADC] * 00453567 894424 1C MOV DWORD PTR SS:[ESP+0x1C],EAX * 0045356B B8 0F000000 MOV EAX,0xF * 00453570 894424 6C MOV DWORD PTR SS:[ESP+0x6C],EAX * 00453574 895C24 68 MOV DWORD PTR SS:[ESP+0x68],EBX * 00453578 885C24 58 MOV BYTE PTR SS:[ESP+0x58],BL * 0045357C 894424 50 MOV DWORD PTR SS:[ESP+0x50],EAX * 00453580 895C24 4C MOV DWORD PTR SS:[ESP+0x4C],EBX * 00453584 885C24 3C MOV BYTE PTR SS:[ESP+0x3C],BL * 00453588 C68424 70010000 02 MOV BYTE PTR SS:[ESP+0x170],0x2 * 00453590 8B8424 84000000 MOV EAX,DWORD PTR SS:[ESP+0x84] * 00453597 8BF0 MOV ESI,EAX * 00453599 3BC3 CMP EAX,EBX * 0045359B 74 3D JE SHORT .004535DA * 0045359D 83BC24 88000000 08 CMP DWORD PTR SS:[ESP+0x88],0x8 * 004535A5 8B5424 74 MOV EDX,DWORD PTR SS:[ESP+0x74] * 004535A9 73 04 JNB SHORT .004535AF * 004535AB 8D5424 74 LEA EDX,DWORD PTR SS:[ESP+0x74] */ bool attachNameHookW(ULONG startAddress, ULONG stopAddress) { // ECX PTR: 33 DB 33 D2 8B C1 C7 84 24 88 00 00 00 07 00 00 00 const uint8_t bytes[] = { 0x33, 0xdb, // 00453521 33db xor ebx,ebx ; jichi: hook here, text in ecx 0x33, 0xd2, // 00453523 33d2 xor edx,edx 0x8b, 0xc1, // 00453525 8bc1 mov eax,ecx 0xc7, 0x84, 0x24, 0x88, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00 // 00453527 c78424 88000000 07000000 mov dword ptr ss:[esp+0x88],0x7 // 00453532 899c24 84000000 mov dword ptr ss:[esp+0x84],ebx }; int ecx = get_reg(regs::ecx) / 4; return TextHookW::attach<3>(bytes, sizeof(bytes), startAddress, stopAddress, ecx, Engine::NameRole); } /** * Sample game: なついろレシピ * See: http://capita.tistory.com/m/post/251 * * Choice: * 00470D95 72 05 JB SHORT .00470D9C * 00470D97 E8 9C780900 CALL .00508638 ; JMP to msvcr90._invalid_parameter_noinfo * 00470D9C 8BB5 EC020000 MOV ESI,DWORD PTR SS:[EBP+0x2EC] * 00470DA2 037424 14 ADD ESI,DWORD PTR SS:[ESP+0x14] * 00470DA6 8B4E 10 MOV ECX,DWORD PTR DS:[ESI+0x10] * 00470DA9 2B4E 0C SUB ECX,DWORD PTR DS:[ESI+0xC] * 00470DAC C1F9 04 SAR ECX,0x4 * 00470DAF 83F9 01 CMP ECX,0x1 * 00470DB2 77 05 JA SHORT .00470DB9 * 00470DB4 E8 7F780900 CALL .00508638 ; JMP to msvcr90._invalid_parameter_noinfo * 00470DB9 8B46 0C MOV EAX,DWORD PTR DS:[ESI+0xC] * 00470DBC 8B50 18 MOV EDX,DWORD PTR DS:[EAX+0x18] * 00470DBF 83C0 10 ADD EAX,0x10 ; jichi: text in edx * 00470DC2 52 PUSH EDX * 00470DC3 8D8C24 7C040000 LEA ECX,DWORD PTR SS:[ESP+0x47C] * 00470DCA 8D7424 4C LEA ESI,DWORD PTR SS:[ESP+0x4C] * 00470DCE E8 EDA3F9FF CALL .0040B1C0 * 00470DD3 83C4 04 ADD ESP,0x4 * 00470DD6 6A FF PUSH -0x1 * 00470DD8 53 PUSH EBX * 00470DD9 50 PUSH EAX * 00470DDA 8D8424 84040000 LEA EAX,DWORD PTR SS:[ESP+0x484] * 00470DE1 C68424 B0040000 07 MOV BYTE PTR SS:[ESP+0x4B0],0x7 * 00470DE9 E8 1251F9FF CALL .00405F00 * 00470DEE BE 08000000 MOV ESI,0x8 * 00470DF3 C68424 A4040000 06 MOV BYTE PTR SS:[ESP+0x4A4],0x6 * 00470DFB 397424 60 CMP DWORD PTR SS:[ESP+0x60],ESI * 00470DFF 72 0D JB SHORT .00470E0E * 00470E01 8B4424 4C MOV EAX,DWORD PTR SS:[ESP+0x4C] * 00470E05 50 PUSH EAX * 00470E06 E8 65770900 CALL .00508570 ; JMP to msvcr90.??3@YAXPAX@Z * 00470E0B 83C4 04 ADD ESP,0x4 * 00470E0E 8B9424 7C040000 MOV EDX,DWORD PTR SS:[ESP+0x47C] * 00470E15 33C9 XOR ECX,ECX * 00470E17 C74424 60 07000000 MOV DWORD PTR SS:[ESP+0x60],0x7 * 00470E1F 895C24 5C MOV DWORD PTR SS:[ESP+0x5C],EBX * 00470E23 66:894C24 4C MOV WORD PTR SS:[ESP+0x4C],CX * 00470E28 39B424 90040000 CMP DWORD PTR SS:[ESP+0x490],ESI * 00470E2F 73 07 JNB SHORT .00470E38 * 00470E31 8D9424 7C040000 LEA EDX,DWORD PTR SS:[ESP+0x47C] * 00470E38 8B8424 44040000 MOV EAX,DWORD PTR SS:[ESP+0x444] * 00470E3F B9 10000000 MOV ECX,0x10 * 00470E44 398C24 58040000 CMP DWORD PTR SS:[ESP+0x458],ECX * 00470E4B 73 07 JNB SHORT .00470E54 * 00470E4D 8D8424 44040000 LEA EAX,DWORD PTR SS:[ESP+0x444] * 00470E54 398C24 74040000 CMP DWORD PTR SS:[ESP+0x474],ECX * 00470E5B 8B8C24 60040000 MOV ECX,DWORD PTR SS:[ESP+0x460] */ bool attachOtherHookW(ULONG startAddress, ULONG stopAddress) { // EDX PTR : 83 C0 10 52 8D 8C 24 7C 04 00 00 8D 74 24 4C const uint8_t bytes[] = { 0x83, 0xc0, 0x10, // 00470dbf 83c0 10 add eax,0x10 ; jichi: text in edx 0x52, // 00470dc2 52 push edx 0x8d, 0x8c, 0x24, 0x7c, 0x04, 0x00, 0x00, // 00470dc3 8d8c24 7c040000 lea ecx,dword ptr ss:[esp+0x47c] 0x8d, 0x74, 0x24, 0x4c // 00470dca 8d7424 4c lea esi,dword ptr ss:[esp+0x4c] }; int edx = get_reg(regs::edx) / 4; return TextHookW::attach<4>(bytes, sizeof(bytes), startAddress, stopAddress, edx, Engine::OtherRole); } namespace PatchA { namespace Private { // The second argument is always 0 and not used bool isLeadByteChar(int ch, int) { return dynsjis::isleadchar(ch); // return ::IsDBCSLeadByte(HIBYTE(testChar)); } } // namespace Private /** * Sample game: Re:BIRTHDAY SONG * * 0x8140 is found by tracing the call of the caller of GetGlyphOutlineA. * 00487F8D 25 FF7F0000 AND EAX,0x7FFF * 00487F92 C3 RETN * 00487F93 8BFF MOV EDI,EDI * 00487F95 55 PUSH EBP * 00487F96 8BEC MOV EBP,ESP * 00487F98 83EC 10 SUB ESP,0x10 * 00487F9B FF75 0C PUSH DWORD PTR SS:[EBP+0xC] * 00487F9E 8D4D F0 LEA ECX,DWORD PTR SS:[EBP-0x10] * 00487FA1 E8 02EEFFFF CALL .00486DA8 * 00487FA6 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] * 00487FA9 C1E8 08 SHR EAX,0x8 * 00487FAC 0FB6C8 MOVZX ECX,AL * 00487FAF 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-0xC] * 00487FB2 F64401 1D 04 TEST BYTE PTR DS:[ECX+EAX+0x1D],0x4 * 00487FB7 74 10 JE SHORT .00487FC9 * 00487FB9 0FB64D 08 MOVZX ECX,BYTE PTR SS:[EBP+0x8] * 00487FBD F64401 1D 08 TEST BYTE PTR DS:[ECX+EAX+0x1D],0x8 * 00487FC2 74 05 JE SHORT .00487FC9 * 00487FC4 33C0 XOR EAX,EAX * 00487FC6 40 INC EAX * 00487FC7 EB 02 JMP SHORT .00487FCB * 00487FC9 33C0 XOR EAX,EAX * 00487FCB 807D FC 00 CMP BYTE PTR SS:[EBP-0x4],0x0 * 00487FCF 74 07 JE SHORT .00487FD8 * 00487FD1 8B4D F8 MOV ECX,DWORD PTR SS:[EBP-0x8] * 00487FD4 8361 70 FD AND DWORD PTR DS:[ECX+0x70],0xFFFFFFFD * 00487FD8 C9 LEAVE * 00487FD9 C3 RETN * 00487FDA 8BFF MOV EDI,EDI ; jichi: called here, text in arg1 * 00487FDC 55 PUSH EBP * 00487FDD 8BEC MOV EBP,ESP * 00487FDF 6A 00 PUSH 0x0 * 00487FE1 FF75 08 PUSH DWORD PTR SS:[EBP+0x8] * 00487FE4 E8 AAFFFFFF CALL .00487F93 ; jichi: called here * 00487FE9 59 POP ECX * 00487FEA 59 POP ECX * 00487FEB 5D POP EBP * 00487FEC C3 RETN */ using ulong = ULONG; #define s1_call_ 0xe8 // near call, incomplete #define s1_nop 0x90 // nop bool csmemcpy(void *dst, const void *src, size_t size) { // return memcpy_(dst, src, size); DWORD oldProtect; if (!::VirtualProtect(dst, size, PAGE_EXECUTE_READWRITE, &oldProtect)) return false; // HANDLE hProc = OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE, FALSE, ::GetCurrentProcessId()); // VirtualProtectEx(hProc, dst, size, PAGE_EXECUTE_READWRITE, &oldProtect); memcpy(dst, src, size); DWORD newProtect; ::VirtualProtect(dst, size, oldProtect, &newProtect); // the error code is not checked for this function // hProc = OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE, FALSE, ::GetCurrentProcessId()); // VirtualProtectEx(hProc, dst, size, oldProtect, &newProtect); return true; } ulong replace_near_call(ulong addr, ulong val) { DWORD ret; switch (::disasm((LPCVOID)addr)) { case 5: // near call / short jmp: relative address ret = *(DWORD *)(addr + 1) + (addr + 5); val -= addr + 5; return csmemcpy((LPVOID)(addr + 1), &val, sizeof(val)) ? ret : 0; case 6: // far car / long jmp: absolute address { ret = *(DWORD *)(addr + 2); BYTE data[6]; data[0] = s1_call_; data[5] = s1_nop; *(DWORD *)(data + 1) = val - (addr + 5); return csmemcpy((LPVOID)addr, data, sizeof(data)) ? ret : 0; } default: return 0; } } ULONG patchEncoding(ULONG startAddress, ULONG stopAddress) { const uint8_t bytes[] = { 0x6a, 0x00, // 00487fdf 6a 00 push 0x0 0xff, 0x75, 0x08, // 00487fe1 ff75 08 push dword ptr ss:[ebp+0x8] 0xe8, 0xaa, 0xff, 0xff, 0xff // 00487fe4 e8 aaffffff call .00487f93 ; jichi: called here }; enum { addr_offset = 5 }; ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress); return addr; //&& replace_near_call(addr + addr_offset, (ULONG)Private::isLeadByteChar); } } // namespace PatchA namespace ScenarioHookA { namespace Private { /* void dispatch(LPSTR text, int role) { enum { sig = 0 }; if (!Engine::isAddressWritable(text) || !*text) // isAddressWritable is not needed for correct games return; int size = ::strlen(text), trimmedSize = size; auto trimmedText = trim(text, &trimmedSize); if (!trimmedSize || !*trimmedText) return; std::string oldData(trimmedText, trimmedSize), newData = EngineController::instance()->dispatchTextASTD(oldData, role, sig); if (newData == oldData) return; if (trimmedText[trimmedSize]) newData.append(trimmedText + trimmedSize); //, size - trimmedSize - (trimmedText - text)); ::strcpy(text, newData.c_str()); } */ bool hookBefore(hook_stack *s, void *data, size_t *len, uintptr_t *role) { auto text = (LPSTR)s->eax; if (!text) return false; // dispatch(text - 1024, Engine::NameRole); // dispatch(text, Engine::ScenarioRole); enum { sig = 0 }; if (!Engine::isAddressWritable(text) || !*text) // isAddressWritable is not needed for correct games return false; int size = ::strlen(text), trimmedSize = size; auto trimmedText = trim(text, &trimmedSize); if (!trimmedSize || !*trimmedText) return false; std::string oldData(trimmedText, trimmedSize); return write_string_overwrite(data, len, oldData); /*newData = EngineController::instance()->dispatchTextASTD(oldData, role, sig); if (newData == oldData) return; if (trimmedText[trimmedSize]) newData.append(trimmedText + trimmedSize); //, size - trimmedSize - (trimmedText - text)); ::strcpy(text, newData.c_str()); return true;*/ } void hookafter(hook_stack *s, void *data, size_t len) { auto newData = std::string((char *)data, len); auto text = (LPSTR)s->eax; int size = ::strlen(text), trimmedSize = size; auto trimmedText = trim(text, &trimmedSize); if (trimmedText[trimmedSize]) newData.append(trimmedText + trimmedSize); //, size - trimmedSize - (trimmedText - text)); ::strcpy(text, newData.c_str()); } } // namespace Private /** * Sample games * - [111028][PULLTOP] 神聖にして侵すべからず * - Re:BIRTHDAY SONG~恋を唄う死神~(体験版) * See: http://capita.tistory.com/m/post/84 * * ENCODEKOR,FORCEFONT(5),HOOK(0x0042B5E0,TRANS(0x004FFBF8,OVERWRITE(IGNORE)),RETNPOS(COPY),TRANS(0x004FF7F8,OVERWRITE(IGNORE))),HOOK(0x00413204,TRANS([ESP+0x1c],PTRCHEAT),RETNPOS(SOURCE)),HOOK(0x00424004,TRANS([ESP+0x1c],PTRCHEAT),RETNPOS(SOURCE)),HOOK(0x004242B9,TRANS([ESP+0x1c],PTRCHEAT),RETNPOS(SOURCE)),HOOK(0x00424109,TRANS([ESP+0x1c],PTRCHEAT),RETNPOS(SOURCE)) * * Scenario in eax * Name in (eax - 1024) * Memory can be directly overridden. * * 0042B5DE CC INT3 * 0042B5DF CC INT3 * 0042B5E0 81EC 14080000 SUB ESP,0x814 ; jichi: text in eax, name in eax - 1024, able to copy * 0042B5E6 53 PUSH EBX * 0042B5E7 55 PUSH EBP * 0042B5E8 56 PUSH ESI * 0042B5E9 33DB XOR EBX,EBX * 0042B5EB 57 PUSH EDI * 0042B5EC 8BF8 MOV EDI,EAX * 0042B5EE 399C24 28080000 CMP DWORD PTR SS:[ESP+0x828],EBX * 0042B5F5 75 13 JNZ SHORT .0042B60A * 0042B5F7 68 74030000 PUSH 0x374 * 0042B5FC 53 PUSH EBX * 0042B5FD 68 7CC44F00 PUSH .004FC47C * 0042B602 E8 09E60500 CALL .00489C10 * 0042B607 83C4 0C ADD ESP,0xC * 0042B60A 33F6 XOR ESI,ESI * 0042B60C 895C24 1C MOV DWORD PTR SS:[ESP+0x1C],EBX * 0042B610 895C24 10 MOV DWORD PTR SS:[ESP+0x10],EBX * 0042B614 381F CMP BYTE PTR DS:[EDI],BL * 0042B616 0F84 0D020000 JE .0042B829 * 0042B61C 8D6424 00 LEA ESP,DWORD PTR SS:[ESP] * 0042B620 8A4C37 01 MOV CL,BYTE PTR DS:[EDI+ESI+0x1] * 0042B624 84C9 TEST CL,CL * 0042B626 0F84 E6010000 JE .0042B812 * 0042B62C 66:0FB6043E MOVZX AX,BYTE PTR DS:[ESI+EDI] * 0042B631 8D2C3E LEA EBP,DWORD PTR DS:[ESI+EDI] * 0042B634 66:C1E0 08 SHL AX,0x8 * 0042B638 0FB7C0 MOVZX EAX,AX * 0042B63B 0FB6C9 MOVZX ECX,CL * 0042B63E 0BC1 OR EAX,ECX * 0042B640 50 PUSH EAX * 0042B641 E8 34B40500 CALL .00486A7A * 0042B646 83C4 04 ADD ESP,0x4 * 0042B649 85C0 TEST EAX,EAX * 0042B64B 74 14 JE SHORT .0042B661 * 0042B64D 66:8B55 00 MOV DX,WORD PTR SS:[EBP] * 0042B651 66:89541C 24 MOV WORD PTR SS:[ESP+EBX+0x24],DX * 0042B656 83C3 02 ADD EBX,0x2 * 0042B659 83C6 02 ADD ESI,0x2 * 0042B65C E9 BA010000 JMP .0042B81B * 0042B661 807D 00 7B CMP BYTE PTR SS:[EBP],0x7B * 0042B665 0F85 60010000 JNZ .0042B7CB * 0042B66B 8BC3 MOV EAX,EBX * 0042B66D 2B4424 1C SUB EAX,DWORD PTR SS:[ESP+0x1C] * 0042B671 46 INC ESI * 0042B672 33ED XOR EBP,EBP * 0042B674 894424 20 MOV DWORD PTR SS:[ESP+0x20],EAX * 0042B678 896C24 14 MOV DWORD PTR SS:[ESP+0x14],EBP * 0042B67C 8D6424 00 LEA ESP,DWORD PTR SS:[ESP] * 0042B680 8A0C3E MOV CL,BYTE PTR DS:[ESI+EDI] * 0042B683 84C9 TEST CL,CL * 0042B685 0F84 B5010000 JE .0042B840 * 0042B68B 0FB64437 01 MOVZX EAX,BYTE PTR DS:[EDI+ESI+0x1] * 0042B690 66:0FB6C9 MOVZX CX,CL * 0042B694 66:C1E1 08 SHL CX,0x8 * 0042B698 0FB7D1 MOVZX EDX,CX * 0042B69B 0BC2 OR EAX,EDX * 0042B69D 50 PUSH EAX * 0042B69E E8 D7B30500 CALL .00486A7A * 0042B6A3 83C4 04 ADD ESP,0x4 * 0042B6A6 85C0 TEST EAX,EAX * 0042B6A8 74 1A JE SHORT .0042B6C4 * 0042B6AA 66:8B043E MOV AX,WORD PTR DS:[ESI+EDI] * 0042B6AE 834424 14 02 ADD DWORD PTR SS:[ESP+0x14],0x2 * 0042B6B3 66:89441C 24 MOV WORD PTR SS:[ESP+EBX+0x24],AX * 0042B6B8 83C3 02 ADD EBX,0x2 * 0042B6BB 895C24 10 MOV DWORD PTR SS:[ESP+0x10],EBX * 0042B6BF 83C6 02 ADD ESI,0x2 * 0042B6C2 ^EB BC JMP SHORT .0042B680 * 0042B6C4 8A043E MOV AL,BYTE PTR DS:[ESI+EDI] * 0042B6C7 3C 3A CMP AL,0x3A * 0042B6C9 74 10 JE SHORT .0042B6DB * 0042B6CB FF4424 14 INC DWORD PTR SS:[ESP+0x14] * 0042B6CF 88441C 24 MOV BYTE PTR SS:[ESP+EBX+0x24],AL * 0042B6D3 43 INC EBX * 0042B6D4 895C24 10 MOV DWORD PTR SS:[ESP+0x10],EBX * 0042B6D8 46 INC ESI * 0042B6D9 ^EB A5 JMP SHORT .0042B680 * 0042B6DB 896C24 18 MOV DWORD PTR SS:[ESP+0x18],EBP * 0042B6DF 46 INC ESI * 0042B6E0 8A0C3E MOV CL,BYTE PTR DS:[ESI+EDI] * 0042B6E3 84C9 TEST CL,CL * 0042B6E5 0F84 55010000 JE .0042B840 * 0042B6EB 0FB64437 01 MOVZX EAX,BYTE PTR DS:[EDI+ESI+0x1] * 0042B6F0 66:0FB6C9 MOVZX CX,CL * 0042B6F4 66:C1E1 08 SHL CX,0x8 * 0042B6F8 0FB7D1 MOVZX EDX,CX * 0042B6FB 0BC2 OR EAX,EDX * 0042B6FD 50 PUSH EAX * 0042B6FE E8 77B30500 CALL .00486A7A * 0042B703 83C4 04 ADD ESP,0x4 * 0042B706 85C0 TEST EAX,EAX * 0042B708 74 18 JE SHORT .0042B722 * 0042B70A 66:8B043E MOV AX,WORD PTR DS:[ESI+EDI] * 0042B70E FF4424 18 INC DWORD PTR SS:[ESP+0x18] * 0042B712 66:89842C 240400>MOV WORD PTR SS:[ESP+EBP+0x424],AX * 0042B71A 83C5 02 ADD EBP,0x2 * 0042B71D 83C6 02 ADD ESI,0x2 * 0042B720 ^EB BE JMP SHORT .0042B6E0 * 0042B722 8A043E MOV AL,BYTE PTR DS:[ESI+EDI] * 0042B725 3C 7D CMP AL,0x7D * 0042B727 74 0E JE SHORT .0042B737 * 0042B729 FF4424 18 INC DWORD PTR SS:[ESP+0x18] * 0042B72D 88842C 24040000 MOV BYTE PTR SS:[ESP+EBP+0x424],AL * 0042B734 45 INC EBP * 0042B735 ^EB A8 JMP SHORT .0042B6DF * 0042B737 8D8424 24040000 LEA EAX,DWORD PTR SS:[ESP+0x424] * 0042B73E 46 INC ESI * 0042B73F C6842C 24040000 >MOV BYTE PTR SS:[ESP+EBP+0x424],0x0 * 0042B747 8D50 01 LEA EDX,DWORD PTR DS:[EAX+0x1] * 0042B74A 8D9B 00000000 LEA EBX,DWORD PTR DS:[EBX] * 0042B750 8A08 MOV CL,BYTE PTR DS:[EAX] * 0042B752 40 INC EAX * 0042B753 84C9 TEST CL,CL * 0042B755 ^75 F9 JNZ SHORT .0042B750 * 0042B757 2BC2 SUB EAX,EDX * 0042B759 83F8 1E CMP EAX,0x1E * 0042B75C 0F87 DE000000 JA .0042B840 * 0042B762 8B15 7CC44F00 MOV EDX,DWORD PTR DS:[0x4FC47C] * 0042B768 83FA 14 CMP EDX,0x14 * 0042B76B 0F8D AE000000 JGE .0042B81F * 0042B771 6BD2 2C IMUL EDX,EDX,0x2C * 0042B774 8D8C24 24040000 LEA ECX,DWORD PTR SS:[ESP+0x424] * 0042B77B 81C2 8CC44F00 ADD EDX,.004FC48C * 0042B781 8A01 MOV AL,BYTE PTR DS:[ECX] * 0042B783 8802 MOV BYTE PTR DS:[EDX],AL * 0042B785 41 INC ECX * 0042B786 42 INC EDX * 0042B787 84C0 TEST AL,AL * 0042B789 ^75 F6 JNZ SHORT .0042B781 * 0042B78B 8B0D 7CC44F00 MOV ECX,DWORD PTR DS:[0x4FC47C] * 0042B791 8B5424 14 MOV EDX,DWORD PTR SS:[ESP+0x14] * 0042B795 6BC9 2C IMUL ECX,ECX,0x2C * 0042B798 8991 88C44F00 MOV DWORD PTR DS:[ECX+0x4FC488],EDX * 0042B79E A1 7CC44F00 MOV EAX,DWORD PTR DS:[0x4FC47C] * 0042B7A3 8B4C24 20 MOV ECX,DWORD PTR SS:[ESP+0x20] * 0042B7A7 6BC0 2C IMUL EAX,EAX,0x2C * 0042B7AA 8988 80C44F00 MOV DWORD PTR DS:[EAX+0x4FC480],ECX * 0042B7B0 8B15 7CC44F00 MOV EDX,DWORD PTR DS:[0x4FC47C] * 0042B7B6 8B4424 18 MOV EAX,DWORD PTR SS:[ESP+0x18] * 0042B7BA 6BD2 2C IMUL EDX,EDX,0x2C * 0042B7BD 8982 84C44F00 MOV DWORD PTR DS:[EDX+0x4FC484],EAX * 0042B7C3 FF05 7CC44F00 INC DWORD PTR DS:[0x4FC47C] * 0042B7C9 EB 54 JMP SHORT .0042B81F * 0042B7CB 55 PUSH EBP * 0042B7CC E8 7F000000 CALL .0042B850 * 0042B7D1 8BD8 MOV EBX,EAX * 0042B7D3 83C4 04 ADD ESP,0x4 * 0042B7D6 85DB TEST EBX,EBX * 0042B7D8 74 23 JE SHORT .0042B7FD * 0042B7DA 53 PUSH EBX * 0042B7DB 55 PUSH EBP * 0042B7DC 8B6C24 18 MOV EBP,DWORD PTR SS:[ESP+0x18] * 0042B7E0 8D4C2C 2C LEA ECX,DWORD PTR SS:[ESP+EBP+0x2C] * 0042B7E4 51 PUSH ECX * 0042B7E5 E8 A6E40500 CALL .00489C90 * 0042B7EA 03EB ADD EBP,EBX * 0042B7EC 03F3 ADD ESI,EBX * 0042B7EE 83C4 0C ADD ESP,0xC * 0042B7F1 015C24 1C ADD DWORD PTR SS:[ESP+0x1C],EBX * 0042B7F5 896C24 10 MOV DWORD PTR SS:[ESP+0x10],EBP * 0042B7F9 8BDD MOV EBX,EBP * 0042B7FB EB 22 JMP SHORT .0042B81F * 0042B7FD 8B4424 10 MOV EAX,DWORD PTR SS:[ESP+0x10] * 0042B801 8A55 00 MOV DL,BYTE PTR SS:[EBP] * 0042B804 40 INC EAX * 0042B805 885404 23 MOV BYTE PTR SS:[ESP+EAX+0x23],DL * 0042B809 894424 10 MOV DWORD PTR SS:[ESP+0x10],EAX * 0042B80D 46 INC ESI * 0042B80E 8BD8 MOV EBX,EAX * 0042B810 EB 0D JMP SHORT .0042B81F * 0042B812 8A043E MOV AL,BYTE PTR DS:[ESI+EDI] * 0042B815 88441C 24 MOV BYTE PTR SS:[ESP+EBX+0x24],AL * 0042B819 43 INC EBX * 0042B81A 46 INC ESI * 0042B81B 895C24 10 MOV DWORD PTR SS:[ESP+0x10],EBX * 0042B81F 803C3E 00 CMP BYTE PTR DS:[ESI+EDI],0x0 * 0042B823 ^0F85 F7FDFFFF JNZ .0042B620 * 0042B829 8D4424 24 LEA EAX,DWORD PTR SS:[ESP+0x24] * 0042B82D 8BC8 MOV ECX,EAX * 0042B82F C6441C 24 00 MOV BYTE PTR SS:[ESP+EBX+0x24],0x0 * 0042B834 2BF9 SUB EDI,ECX * 0042B836 8A08 MOV CL,BYTE PTR DS:[EAX] * 0042B838 880C07 MOV BYTE PTR DS:[EDI+EAX],CL * 0042B83B 40 INC EAX * 0042B83C 84C9 TEST CL,CL * 0042B83E ^75 F6 JNZ SHORT .0042B836 * 0042B840 5F POP EDI * 0042B841 5E POP ESI * 0042B842 5D POP EBP * 0042B843 5B POP EBX * 0042B844 81C4 14080000 ADD ESP,0x814 * 0042B84A C3 RETN * 0042B84B CC INT3 * 0042B84C CC INT3 * 0042B84D CC INT3 * 0042B84E CC INT3 * * Skip scenario text: * 00438EF1 51 PUSH ECX * 00438EF2 56 PUSH ESI * 00438EF3 57 PUSH EDI * 00438EF4 52 PUSH EDX * 00438EF5 6A 03 PUSH 0x3 ; jichi: scenario arg1 is always 3 * 00438EF7 E8 14F3FDFF CALL .00418210 ; jichi: text called here * 00438EFC 894424 4C MOV DWORD PTR SS:[ESP+0x4C],EAX * 00438F00 8D4424 78 LEA EAX,DWORD PTR SS:[ESP+0x78] * 00438F04 83C4 30 ADD ESP,0x30 * 00438F07 897C24 34 MOV DWORD PTR SS:[ESP+0x34],EDI * 00438F0B 897424 38 MOV DWORD PTR SS:[ESP+0x38],ESI * 00438F0F 8D48 01 LEA ECX,DWORD PTR DS:[EAX+0x1] * 00438F12 8A10 MOV DL,BYTE PTR DS:[EAX] * 00438F14 40 INC EAX * 00438F15 84D2 TEST DL,DL */ bool attach(ULONG startAddress, ULONG stopAddress) { const uint8_t bytes[] = { 0x81, 0xec, 0x14, 0x08, 0x00, 0x00 // 0042B5E0 81EC 14080000 SUB ESP,0x814 ; jichi: text in eax, name in eax - 1024, able to copy }; ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress); if (addr == 0) return false; HookParam hp; hp.address = addr; hp.hook_before = Private::hookBefore; hp.type = EMBED_ABLE; hp.newlineseperator = L"\\n"; hp.hook_after = Private::hookafter; hp.hook_font = F_GetGlyphOutlineA | F_TextOutA; static ULONG paddr = (PatchA::patchEncoding(startAddress, stopAddress)); ConsoleOutput("%p", paddr); if (paddr) { hp.type |= EMBED_DYNA_SJIS; hp.hook_font = F_GetGlyphOutlineA | F_TextOutA; patch_fun = []() { PatchA::replace_near_call(paddr + 5, (ULONG)PatchA::Private::isLeadByteChar); }; } return NewHook(hp, "EmbedWillplusA"); } } // namespace ScenarioHookA namespace OtherHookA { namespace Private { bool hookBefore(hook_stack *s, void *data, size_t *len, uintptr_t *role) { static std::string data_; if (s->stack[1] == 3) // skip scenario hook where arg1 is 3 return false; auto text = (LPCSTR)s->stack[8]; // text in arg8 if (!Engine::isAddressReadable(text) || !*text || ::strlen(text) <= 2) // do not translate single character return false; *role = Engine::OtherRole; return write_string_overwrite(data, len, text); } } // namespace Private /** * Sample games: Re:BIRTHDAY SONG~恋を唄う死神~(体験版) * * There are two GetGlyphOutlineA, that are called in the same functions. * * Caller of GetGlyphOutlineA, text in arg8. */ bool attach(ULONG startAddress, ULONG stopAddress) { ULONG addr = MemDbg::findCallerAddressAfterInt3((ULONG)::GetGlyphOutlineA, startAddress, stopAddress); if (addr == 0) return false; HookParam hp; hp.address = addr; hp.hook_before = Private::hookBefore; hp.type = EMBED_ABLE | EMBED_DYNA_SJIS | EMBED_AFTER_OVERWRITE; hp.offset = get_stack(8); return NewHook(hp, "EmbedWillplus_other"); } } // namespace OtherHookA } // unnamed namespace /** Public class */ namespace WillPlusEngine { bool attach() { ULONG startAddress = processStartAddress, stopAddress = processStopAddress; if (::attachScenarioHookW1(startAddress, stopAddress) || ::attachScenarioHookW2(startAddress, stopAddress)) { (::attachNameHookW(startAddress, stopAddress)); (::attachOtherHookW(startAddress, stopAddress)); return true; } else if (ScenarioHookA::attach(startAddress, stopAddress)) { // try widechar pattern first, which is more unique (OtherHookA::attach(startAddress, stopAddress)); // HijackManager::instance()->attachFunction((ULONG)::GetGlyphOutlineA); // HijackManager::instance()->attachFunction((ULONG)::TextOutA); // not called. hijack in case it is used return true; } return false; } } namespace { static bool InsertWillPlus4() { // by Blu3train /* * Sample games: * https://vndb.org/r71235 */ const BYTE bytes[] = { 0x33, 0xC9, // xor ecx,ecx <-- hook 0x8B, 0xC7, // mov eax,edi 0xC7, 0x84, 0x24, XX4, XX4, // mov [esp+000001E0],00000007 0x89, 0x9C, 0x24, XX4 // mov [esp+000001DC],ebx }; ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR); ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range); if (!addr) { ConsoleOutput("WillPlus4: pattern not found"); return false; } HookParam hp = {}; hp.address = addr; hp.offset = get_reg(regs::edi); hp.type = CODEC_UTF16 | USING_STRING; hp.filter_fun = WillPlus_extra_filter; ConsoleOutput("INSERT WillPlus4"); NewHook(hp, "WillPlus4"); return true; } static bool InsertWillPlus5() { // by Blu3train /* * Sample games: * https://vndb.org/v29881 */ const BYTE bytes[] = { 0xE8, XX4, // call AdvHD.exe+38550 <-- hook here 0x8B, 0x4B, 0x08, // mov ecx,[ebx+08] 0x89, 0x8F, XX4, // mov [edi+0000014C],ecx 0x85, 0xC9, // test ecx,ecx 0x74, 0x04 // je AdvHD.exe+396C6 }; ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR); ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range); if (!addr) { ConsoleOutput("WillPlus5: pattern not found"); return false; } HookParam hp = {}; hp.address = addr; hp.offset = get_reg(regs::esi); hp.index = 0; hp.split = get_reg(regs::ebx); hp.split_index = 0; hp.type = CODEC_UTF16 | USING_STRING | NO_CONTEXT | USING_SPLIT; hp.filter_fun = WillPlus_extra_filter; ConsoleOutput("INSERT WillPlus5"); NewHook(hp, "WillPlus5"); return true; } bool _xxx() { bool ok = false; ok = InsertWillPlus4() || ok; ok = InsertWillPlus5() || ok; return ok; } } bool WillPlus::attach_function() { bool succ = WillPlusEngine::attach(); succ |= InsertWillPlusHook(); succ |= InsertWillPlus4Hook(); succ |= InsertWillPlus5Hook(); succ |= insertwillplus6(); succ |= willX(); succ |= _xxx(); return succ; } bool Willold::attach_function() { // https://vndb.org/v17755 // 凌辱鬼 auto addr = MemDbg::findLongJumpAddress((ULONG)TextOutA, processStartAddress, processStopAddress); if (addr == 0) return false; addr = MemDbg::findNearCallAddress(addr, processStartAddress, processStopAddress); if (addr == 0) return false; addr = findfuncstart(addr, 0x200); if (addr == 0) return false; HookParam hp; hp.address = addr; hp.type = USING_CHAR | CODEC_ANSI_BE; hp.offset = get_stack(1); return NewHook(hp, "will"); }