mirror of
https://github.com/HIllya51/LunaHook.git
synced 2024-11-27 07:44:02 +08:00
1708 lines
67 KiB
C++
1708 lines
67 KiB
C++
#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<LPWSTR>(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]));//"<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ˤˤʤꤿ<CAA4><EAA4BF><EFBFBD><EFBFBD>%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 <typename strT>
|
||
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<int,textinfo*>savetyperef;
|
||
namespace TextHookW
|
||
{
|
||
|
||
// typedef TextHookW Self;
|
||
|
||
template<int idx>
|
||
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<int idx>
|
||
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< int _type>
|
||
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");
|
||
}
|