LunaHook-mirror/LunaHook/engine32/Majiro.cpp

307 lines
12 KiB
C++
Raw Normal View History

2024-02-07 20:59:24 +08:00
#include"Majiro.h"
/** jichi 12/28/2014: new Majiro hook pattern
*
* Different function starts:
*
* Old Majiro:
* enum { sub_esp = 0xec81 }; // caller pattern: sub esp = 0x81,0xec byte
*
* New Majiro since [141128] [] <EFBFBD><EFBFBD><EFBFBD>
* 003e9230 55 push ebp
* 003e9231 8bec mov ebp,esp
* 003e9233 83ec 64 sub esp,0x64
*
* Also, function addresses are fixed in old majiro, but floating in new majiro.
* In the old Majiro game, caller's address could be used as split.
* In the new Majiro game, the hooked function is invoked by the same caller.
*
* Use a split instead.
* Sample stack values are as follows.
* - Old majiro: arg3 is text, arg1 is font name
* - New majiro: arg3 is text, arg4 is font name
*
* Name:
* 0038f164 003e8163 return to .003e8163 from .003e9230
* 0038f168 00000000
* 0038f16c 00000000
* 0038f170 08b04dbc ; jichi: arg3, text
* 0038f174 006709f0 ; jichi: arg4, font name
* 0038f178 006dace8
* 0038f17c 00000000
* 0038f180 00000013
* 0038f184 006fcba8
* 0038f188 00000078 ; jichi: 0x24, alternative split
* 0038f18c 00000078
* 0038f190 00000018
* 0038f194 00000002
* 0038f198 08b04dbc
* 0038f19c 006709f0
* 0038f1a0 00000000
* 0038f1a4 00000000
* 0038f1a8 00000078
* 0038f1ac 00000018
* 0038f1b0 08aa0130
* 0038f1b4 01b6b6c0
* 0038f1b8 beff26e4
* 0038f1bc 0038f1fc
* 0038f1c0 004154af return to .004154af from .00415400 ; jichi: 0x52, could be used as split
* 0038f1c4 0000000e
* 0038f1c8 000001ae
* 0038f1cc 00000158
* 0038f1d0 00000023
* 0038f1d4 beff2680
* 0038f1d8 0038f208
* 0038f1dc 003ecfda return to .003ecfda from .00415400
*
* Scenario:
* 0038e57c 003e8163 return to .003e8163 from .003e9230
* 0038e580 00000000
* 0038e584 00000000
* 0038e588 0038ee4c ; jichi: arg3, text
* 0038e58c 004d5400 .004d5400 ; jichi: arg4, font name
* 0038e590 006dace8
* 0038e594 0038ee6d
* 0038e598 004d7549 .004d7549
* 0038e59c 00000000
* 0038e5a0 00000180 ; jichi: 0x24, alternative hook
* 0038e5a4 00000180
* 0038e5a8 00000018
* 0038e5ac 00000002
* 0038e5b0 0038ee4c
* 0038e5b4 004d5400 .004d5400
* 0038e5b8 00000000
* 0038e5bc 00000000
* 0038e5c0 00000180
* 0038e5c4 00000018
* 0038e5c8 006a0180
* 0038e5cc 0038e5f8
* 0038e5d0 0041fc87 return to .0041fc87 from .0041fc99
* 0038e5d4 0038e5f8
* 0038e5d8 00418165 return to .00418165 from .0041fc81 ; jichi: used as split
* 0038e5dc 004d7549 .004d7549
* 0038e5e0 0038ee6d
* 0038e5e4 0038e608
* 0038e5e8 00419555 return to .00419555 from .0041814e
* 0038e5ec 00000000
* 0038e5f0 004d7549 .004d7549
* 0038e5f4 0038ee6d
*
* 12/4/2014: Add split for furigana.
* Sample game: [141128] [] <EFBFBD>
* Following are memory values after arg4 (font name)
*
* Surface: <EFBFBD> * 00EC5400 82 6C 82 72 20 82 6F 83 53 83 56 83 62 83 4E 00 <EFBFBD><EFBFBD> <EFBFBD><EFBFBD>.
* 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
* 00EC5420 01 00 00 00 00 00 00 00 1C 00 00 00 0D 00 00 00 ....... .......
* 00EC5430 (2D)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 -...<EFBFBD><EFBFBD><EFBFBD>.... .... ; jichi: first byte as split in parenthesis
* 00EC5440 00(00 00 00)60 F7 3F 00 F0 D8 FF FF 00 00 00 00 ....`. .... ; jichi: first word without first byte as split
*
* 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......<EFBFBD> ....
* 00EC5460 00 00 00 00 01 00 00 00 00 00 00 00 32 01 00 00 .... .......2 ..
* 00EC5470 14 00 00 00 01 00 00 00 82 6C 82 72 20 82 6F 83 ... ...<EFBFBD><EFBFBD> <EFBFBD> ; MS P Gothic
* 00EC5480 53 S
*
* Furigana: <EFBFBD>
* 00EC5400 82 6C 82 72 20 83 53 83 56 83 62 83 4E 00 4E 00 <EFBFBD><EFBFBD> <EFBFBD>.N.
* 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
* 00EC5420 01 00 00 00 00 00 00 00 0E 00 00 00 06 00 00 00 ....... ... ...
* 00EC5430 (16)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 ...<EFBFBD><EFBFBD><EFBFBD>.... ....
* 00EC5440 00(00 00 00)60 F7 3F 00 F0 D8 FF FF 00 00 00 00 ....`. ....
*
* 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......<EFBFBD> ....
* 00EC5460 00 00 00 00 00 00 00 00 00 00 00 00 32 01 00 00 ............2 ..
* 00EC5470 14 00 00 00 01 00 00 00 82 6C 82 72 20 82 6F 83 ... ...<EFBFBD><EFBFBD> <EFBFBD> ; MS P Gothic
* 00EC5480 53 S
*
* Furigana: <EFBFBD>
* 00EC5400 82 6C 82 72 20 82 6F 83 53 83 56 83 62 83 4E 00 <EFBFBD><EFBFBD> <EFBFBD><EFBFBD>.
* 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
* 00EC5420 01 00 00 00 00 00 00 00 0E 00 00 00 06 00 00 00 ....... ... ...
* 00EC5430 (2D)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 -...<EFBFBD><EFBFBD><EFBFBD>.... ....
* 00EC5440 00(00 00 00)60 F7 3F 00 2B 01 00 00 06 00 00 00 ....`.+ .. ...
*
* 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......<EFBFBD> ....
* 00EC5460 00 00 00 00 00 00 00 00 00 00 00 00 32 01 00 00 ............2 ..
* 00EC5470 14 00 00 00 01 00 00 00 82 6C 82 72 20 82 6F 83 ... ...<EFBFBD><EFBFBD> <EFBFBD> ; MS P Gothic
* 00EC5480 53 S
*
* ---- need to split the above and below case
*
* Text: <EFBFBD> * 00EC5400 82 6C 82 72 20 82 6F 83 53 83 56 83 62 83 4E 00 <EFBFBD><EFBFBD> <EFBFBD><EFBFBD>.
* 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
* 00EC5420 01 00 00 00 00 00 00 00 1C 00 00 00 0D 00 00 00 ....... .......
* 00EC5430 (2D)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 -...<EFBFBD><EFBFBD><EFBFBD>.... .... ; jichi: first byte as split in parenthesis
* 00EC5440 FF(FF FF FF)60 F7 3F 00 32 01 00 00 14 00 00 00 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>`.2 .. ... ; jichi: first word without first byte as split
*
* 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......<EFBFBD> ....
* 00EC5460 00 00 00 00 01 00 00 00 00 00 00 00 32 01 00 00 .... .......2 ..
* 00EC5470 14 00 00 00 00 00 00 00 82 6C 82 72 20 82 6F 83 .......<EFBFBD><EFBFBD> <EFBFBD> ; MS P Gothic
* 00EC5480 53 S
*
* Text: <EFBFBD> * 00EC5400 82 6C 82 72 20 82 6F 83 53 83 56 83 62 83 4E 00 <EFBFBD><EFBFBD> <EFBFBD><EFBFBD>.
* 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
* 00EC5420 01 00 00 00 00 00 00 00 1C 00 00 00 0D 00 00 00 ....... .......
* 00EC5430 (2D)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 -...<EFBFBD><EFBFBD><EFBFBD>.... ....
* 00EC5440 FF(FF FF FF)60 F7 3F 00 4D 01 00 00 14 00 00 00 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>`.M .. ...
*
* 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......<EFBFBD> ....
* 00EC5460 00 00 00 00 01 00 00 00 00 00 00 00 32 01 00 00 .... .......2 ..
* 00EC5470 14 00 00 00 00 00 00 00 82 6C 82 72 20 82 6F 83 .......<EFBFBD><EFBFBD> <EFBFBD> ; MS P Gothic
* 00EC5480 53 S
*/
namespace { // unnamed
// These values are the same as the assembly logic of ITH:
// ([eax+0x28] & 0xff) | (([eax+0x48] >> 1) & 0xffffff00)
// 0x28 = 10 * 4, 0x48 = 18 / 4
inline DWORD MajiroOldFontSplit(const DWORD *arg) // arg is supposed to be a string, though
{ return (arg[10] & 0xff) | ((arg[18] >> 1) & 0xffffff00); }
// Remove lower bytes use 0xffffff00, which are different for furigana
inline DWORD MajiroNewFontSplit(const DWORD *arg) // arg is supposed to be a string, though
{ return (arg[12] & 0xff) | (arg[16] & 0xffffff00); }
void SpecialHookMajiro(hook_stack* stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t*len)
{
DWORD arg3 = stack->stack[3]; // text
*data = arg3;
*len = ::strlen((LPCSTR)arg3);
// IsBadReadPtr is not needed for old Majiro game.
// I am not sure if it is needed by new Majiro game.
if (hp->user_value) { // new majiro
if (DWORD arg4 = stack->stack[4]) // old majiro
*split = MajiroNewFontSplit((LPDWORD)arg4);
else
*split = *(DWORD *)(stack->base + 0x5c); // = 4 * 23, caller's caller
} else if (DWORD arg1 = stack->stack[1]) // old majiro
*split = MajiroOldFontSplit((LPDWORD)arg1);
}
} // unnamed namespace
bool InsertMajiroHook()
{
// jichi 4/19/2014: There must be a function in Majiro game which contains 6 TextOutA.
// That function draws all texts.
//
// jichi 11/28/2014: Add new function signature
const DWORD funcs[] = { // caller patterns
0xec81, // sub esp = 0x81,0xec byte old majiro
0x83ec8b55, // mov ebp,esp, sub esp,* new majiro
0x5348ec83
// sub esp, 48h, push ebx
//MOON CHILDe
//https://vndb.org/v1568
};
enum { FunctionCount = sizeof(funcs) / sizeof(*funcs) };
ULONG addr = MemDbg::findMultiCallerAddress((ULONG)::TextOutA, funcs, FunctionCount, processStartAddress, processStopAddress);
//ULONG addr = MemDbg::findCallerAddress((ULONG)::TextOutA, 0x83ec8b55, processStartAddress, processStopAddress);
if (!addr) {
ConsoleOutput("Majiro: failed");
return false;
}
bool newMajiro = 0x55 == *(BYTE *)addr;
HookParam hp;
//hp.type|=USING_STRING|USING_SPLIT|SPLIT_INDIRECT;
hp.address = addr;
hp.text_fun = SpecialHookMajiro;
hp.user_value = newMajiro;
if (newMajiro) {
hp.type = NO_CONTEXT; // do not use return address for new majiro
ConsoleOutput("INSERT Majiro2");
return NewHook(hp, "Majiro2");
} else {
ConsoleOutput("INSERT Majiro");
return NewHook(hp, "Majiro");
}
//RegisterEngineType(ENGINE_MAJIRO);
}
bool InsertMajiroHook3x() {
const BYTE bytes[] = {
0x8b,0x08,
0x0f,0xbf,0x19,
0x83,0xc1,0x02,
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (addr == 0)return false;
HookParam hp;
hp.address = addr+8;
hp.offset=get_reg(regs::ecx);
hp.type = USING_STRING | NO_CONTEXT;//|EMBED_ABLE|EMBED_BEFORE_SIMPLE|EMBED_AFTER_OVERWRITE|EMBED_DYNA_SJIS;
//可以内嵌但是必须保持「」且DynamicEncoding编码的文字会被自动替换成引擎内的某的字符导致可读性低。
//hp.hook_font=F_TextOutA|F_GetTextExtentPoint32A;
//https://vndb.org/v17376
//私が好きなら「好き」って言って!
hp.text_fun= [](hook_stack* stack, HookParam* hp, uintptr_t* data, uintptr_t* split, size_t* len){
auto str=(BYTE*)(*data);
*len=strlen((char*)str);
if(((*len)>2)&&(str[0]==0x81)&&(str[1]==0x79))*split=0;
else *split=1;
};
return NewHook(hp, "majiro3");
}
bool InsertMajiro2Hookx() {
//Scarlettスカーレット
const BYTE bytes[] = {
0x83,0xE2,0x03,0x03,0xC2,0xC1,0xF8,0x02,0x81,0xF9,0x00,0x01,0x00,0x00
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (addr == 0)return false;
addr = MemDbg::findEnclosingAlignedFunction(addr);
if (addr == 0)return false;
HookParam hp;
hp.address = addr ;
hp.offset=get_stack(2);
hp.type = USING_STRING ;
ConsoleOutput("INSERT majiro4 %p",addr);
return NewHook(hp, "majiro4");
}
bool InsertMajiro3Hook()
{
/*
* Sample games:
* Narcissu 10th Anniversary Anthology Project
* https://vndb.org/v10
* https://vndb.org/v70
* https://vndb.org/v18738
* https://vndb.org/v18739
* https://vndb.org/v18736
*/
const BYTE bytes[] = {
0xC1, 0xE9, 0x02, // shr ecx,02 << hook here
0xF3, 0xA5, // repe movsd
0x8B, 0xCA, // mov ecx,edx
0x8D, 0x95, XX4 // lea edx,[ebp-00000404]
};
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("Majiro3: pattern not found");
return false;
}
HookParam hp;
hp.address = addr;
hp.offset=get_reg(regs::esi);
hp.type = USING_STRING;
ConsoleOutput("INSERT Majiro3");
ConsoleOutput("Majiro3: To separate the text between lines flag the \"Flush delay string spacing\" option");
return NewHook(hp, "Majiro3");
}
bool Majiro::attach_function() {
bool b1= InsertMajiroHook();
bool b2=InsertMajiroHook3x();
bool b3=InsertMajiro2Hookx();
bool b4=InsertMajiro3Hook();
return b1||b2||b3||b4;
}