LunaHook-mirror/LunaHook/engine32/QLIE.cpp
2024-04-26 04:53:45 +08:00

999 lines
35 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include"QLIE.h"
#include"embed_util.h"
/**
* jichi 8/18/2013: QLIE identified by GameData/data0.pack
*
* The old hook cannot recognize new games.
*/
namespace { // unnamed QLIE
/**
* Artikash 8/1/2018: new QLIE hook. old one misses on https://vndb.org/v22308 and https://vndb.org/v19182
* ExtTextOut hook misses characters because of font caching
* Method to find H-code: trace call stack from ExtTextOut until missing characters from default hook are found
* /HW-1C*0:-20@base address of pattern
* characterizing pattern:
kimimeza.exe+100D9C - 55 - push ebp
kimimeza.exe+100D9D - 8B EC - mov ebp,esp
kimimeza.exe+100D9F - 83 C4 E4 - add esp,-1C { 228 }
kimimeza.exe+100DA2 - 53 - push ebx
kimimeza.exe+100DA3 - 56 - push esi
kimimeza.exe+100DA4 - 57 - push edi
kimimeza.exe+100DA5 - 33 D2 - xor edx,edx
kimimeza.exe+100DA7 - 89 55 FC - mov [ebp-04],edx
*/
bool InsertQLIE3Hook()
{
const BYTE bytes[] =
{
0x55,
0x8b, 0xec,
0x83, 0xc4, 0xe4,
0x53,
0x56,
0x57,
0x33, 0xd2,
0x89, 0x55, 0xfc
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (!addr) {
ConsoleOutput("QLIE3: pattern not found");
//ConsoleOutput("Not QLIE2");
return false;
}
HookParam hp;
hp.type = CODEC_UTF16 | DATA_INDIRECT | USING_SPLIT;
hp.offset=get_reg(regs::esi);
hp.split=get_reg(regs::edi);
hp.address = addr;
ConsoleOutput("INSERT QLIE3");
return NewHook(hp, "QLiE3");
}
/**
* jichi 8/18/2013: new QLIE hook
* See: http://www.hongfire.com/forum/showthread.php/420362-QLIE-engine-Hcode
*
* Ins:
* 55 8B EC 53 8B 5D 1C
* - 55 push ebp ; hook here
* - 8bec mov ebp, esp
* - 53 push ebx
* - 8B5d 1c mov ebx, dword ptr ss:[ebp+1c]
*
* /HBN14*0@4CC2C4
* - addr: 5030596 (0x4cc2c4)
* - text_fun: 0x0
* - function: 0
* - hook_len: 0
* - ind: 0
* - length_offset: 1
* - module: 0
* - off: 20 (0x14)
* - recover_len: 0
* - split: 0
* - split_ind: 0
* - type: 1032 (0x408)
*/
bool InsertQLIE2Hook()
{
const BYTE bytes[] = { // size = 7
0x55, // 55 push ebp ; hook here
0x8b,0xec, // 8bec mov ebp, esp
0x53, // 53 push ebx
0x8b,0x5d, 0x1c // 8b5d 1c mov ebx, dword ptr ss:[ebp+1c]
};
//enum { addr_offset = 0 }; // current instruction is the first one
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("QLIE2: pattern not found");
//ConsoleOutput("Not QLIE2");
return false;
}
HookParam hp;
hp.type = DATA_INDIRECT|NO_CONTEXT; // 0x408
hp.offset=get_stack(5);
hp.address = addr;
ConsoleOutput("INSERT QLIE2");
return NewHook(hp, "QLiE2");
}
// jichi: 8/18/2013: Change return type to bool
bool InsertQLIE1Hook()
{
for (DWORD i = processStartAddress + 0x1000; i < processStopAddress - 4; i++)
if (*(DWORD *)i == 0x7ffe8347) { // inc edi, cmp esi,7f
DWORD t = 0;
for (DWORD j = i; j < i + 0x10; j++) {
if (*(DWORD *)j == 0xa0) { // cmp esi,a0
t = 1;
break;
}
}
if (t)
for (DWORD j = i; j > i - 0x100; j--)
if (*(DWORD *)j == 0x83ec8b55) { // push ebp, mov ebp,esp, sub esp,*
HookParam hp;
hp.address = j;
hp.offset =get_stack(6);
hp.split =get_reg(regs::esp);
hp.type = DATA_INDIRECT|USING_SPLIT;
ConsoleOutput("INSERT QLIE1");
return NewHook(hp, "QLiE");
}
}
ConsoleOutput("QLIE1: failed");
//ConsoleOutput("Unknown QLIE engine");
return false;
}
} // unnamed QLIE
namespace{
bool _4(){
//シスターシスター
//https://vndb.org/v653
const BYTE bytes[] = {
0x81,0xFB,0x80,0x00,0x00,0x00,
XX2,
0x81,0xFB,0xa0,0x00,0x00,0x00,
XX2,
0x81,0xFB,0xdf,0x00,0x00,0x00,
XX2,
0x81,0xFB,0xff,0x00,0x00,0x00,
XX2,
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (addr == 0)return false;
const BYTE funcstart[] = {
0x90,0x55,0x8b,0xec
};
addr = reverseFindBytes(funcstart, sizeof(funcstart), addr-0x100, addr);
if (addr == 0)return false;
HookParam hp;
hp.address = addr+1 ;
hp.offset = get_stack(6);
hp.type = USING_STRING ;
return NewHook(hp, "QLIE4");
}
bool _5(){
//おしかけおさなづま3(3乗
//School Festaスクールフェスタ
const BYTE bytes[] = {
0x83,0xFF,0x7F,
XX2,
0x81,0xFf,0xa0,0x00,0x00,0x00,
XX2,
0x81,0xFf,0xdf,0x00,0x00,0x00,
XX2,
0x81,0xFf,0xff,0x00,0x00,0x00,
XX2,
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (addr == 0)return false;
addr = findfuncstart(addr);
if (addr == 0)return false;
HookParam hp;
hp.address = addr;
hp.offset=get_reg(regs::ecx);
hp.type = USING_STRING ;
return NewHook(hp, "QLIE5");
}
}
namespace{
//(18禁ゲーム) [240426] [ωstar] 美少女万華鏡異聞 雪おんな
bool qlie4(){
BYTE bytes[]={
0x55,0x8b,0xec,
0x83,0xc4,0xe8,
0x53,0x56,0x57,
0x33,0xdb,
0x89,0x5d,0xe8,
0x89,0x5d,0xf8,
0x89,0x4d,0xf0,
0x89,0x55,0xfc,
0x8b,0x45,0xfc,
0xe8,XX4,
0x33,0xc0,
0x55,
0x68,XX4,
0x64,0xff,0x30,
0x64,0x89,0x20,
0x33,0xf6,
0x8d,0x45,0xf8,
0x33,0xd2,
0xe8,XX4,
0x8b,0x45,0xfc,
0x85,0xc0,
0x74,XX,
0x8b,0xd0,
0x83,0xea,0x0a,
0x66,0x83,0x3a,0x02,
0x74,XX,
0x8d,0x45,0xfc,
0x8b,0x55,0xfc
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (addr == 0)return false;
HookParam hp;
hp.address = addr;
hp.type = USING_STRING|CODEC_UTF16;
hp.text_fun=[](hook_stack* stack, HookParam* hp, uintptr_t* data, uintptr_t* split, size_t* len){
*data=stack->esi;
*len=wcslen((wchar_t*)*data)*2;
auto __s=std::wstring((wchar_t*)*data,*len/2);
if(startWith(__s,L"[f,3")){
*split=2;//history
}
else if(startWith(__s,L"[f,0")){
*split=1;//text
}
else if(startWith(__s,L"[f,1")){
*split=0;//name
}
else if(startWith(__s,L"[s,")){
*split=3;//[s,36,36]「ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ」
}
else if(startWith(__s,L"[pi,")||startWith(__s,L"[rp,")||startWith(__s,L"[rs,")||startWith(__s,L"[rpi,")){
*len=0;
}
};
hp.filter_fun=[](void* data, size_t* len, HookParam* hp){
//[f,1][rf,1][s,34,34][c,$FFFFFFFF][rc,$FFFFFFFF]雪之進
//[f,0][rf,0][s,36,36][c,$FFFFFFFF][rc,$FFFFFFFF]一瞬が勝負だ。私は模造刀に手をかけ、立て膝の状態(いわゆる『[rb,座業,すわりわざ]』)で待機していた。
auto s = std::wstring((wchar_t*)data,*len/2);
s = std::regex_replace(s, std::wregex(L"\\[rb,(.*),.*\\]"), L"$1");
s = std::regex_replace(s, std::wregex(L"\\[.*\\]"), L"");
return write_string_overwrite(data,len,s);
};
return NewHook(hp, "qlie4");
}
}
bool InsertQLIEHook()
{
bool _=_4()||_5();
return InsertQLIE1Hook() || InsertQLIE2Hook() || qlie4()||InsertQLIE3Hook()||_;
}
namespace { // unnamed
namespace ScenarioHook {
namespace Private {
template <typename strT>
strT trim(strT text, int *size)
{
//int length = ::strlen(text);
int length = *size;
if (text[0] == '[') {
if (all_ascii(text))
return nullptr;
if (text[length - 1] == ']' && ::CharPrevA(text, text + length) == text + length - 1) {
length--;
if (text[length - 1] == 'n' && text[length - 2] == '[')
length -= 2;
}
for (int i = 1; i < length; i++)
if ((signed char)text[i] <= 0) {
text += i;
length -= i - 1;
break;
}
length--; // skip the leading '['
}
*size = length;
return text;
}
/**
* Sample game: 月に寄りそう乙女の作法2
*
*
* Name:
*
* 019D7688 5B 66 2C 31 5D 5B 72 66 2C 31 5D 5B 73 2C 32 30 [f,1][rf,1][s,20
* 019D7698 2C 32 30 5D 5B 63 2C 24 46 46 46 46 46 46 46 46 ,20][c,$FFFFFFFF
* 019D76A8 5D 5B 72 63 2C 24 46 46 46 46 46 46 46 46 5D 81 ][rc,$FFFFFFFF]・
* 019D76B8 79 8D F7 8F AC 98 48 83 41 83 67 83 8C 81 7A 00 y桜小路アトレ】.
*
* 0012FBCC 0055553D RETURN to .0055553D from .00513234
* 0012FBD0 0012FDB8 Pointer to next SEH record
* 0012FBD4 005555A5 SE handler
* 0012FBD8 0012FD90
* 0012FBDC 0E9F72D0
* 0012FBE0 0E9F72D0
* 0012FBE4 0A24AA90
* 0012FBE8 00000000
* 0012FBEC 00000000
* 0012FBF0 0C7AE0C8 ASCII "st+cc+tt"
* 0012FBF4 00000000
* 0012FBF8 00000000
* 0012FBFC 00000000
* 0012FC00 00000000
* 0012FC04 00000000
* 0012FC08 00000000
*
* EAX 0E3885A0
* ECX 00000002
* EDX 019D7688
* EBX 0041D17C .0041D17C
* ESP 0012FBCC
* EBP 0012FD90
* ESI 0A24AA90
* EDI 0E9F72D0
* EIP 00513234 .00513234
*
*
* Dialog's arg4:
*
* 04A9BAD0 48 DB 51 00 B8 BA A9 04 F8 BA A9 04 07 02 00 00 HロQ.クコゥゥ..
* 04A9BAE0 B8 67 66 00 D0 AF A6 04 00 00 00 00 90 AC A9 04 クgf.ミッヲ....成ゥ
* 04A9BAF0 01 00 00 00 11 00 00 00 30 5F 64 69 61 6C 6F 67 ......0_dialog
* 04A9BB00 6D 65 73 73 61 67 65 2C 30 00 00 00 90 AC A9 04 message,0...成ゥ
*
* Scenario:
*
* 058DC708 5B 66 2C 30 5D 5B 72 66 2C 30 5D 5B 73 2C 32 34 [f,0][rf,0][s,24
* 058DC718 2C 32 34 5D 5B 63 2C 24 46 46 46 46 46 46 46 46 ,24][c,$FFFFFFFF
* 058DC728 5D 5B 72 63 2C 24 46 46 46 46 46 46 46 46 5D 81 ][rc,$FFFFFFFF]・
* 058DC738 75 82 CD 82 A2 81 41 82 B1 82 B1 82 CD 93 FA 96 uはい、ここは日・
* 058DC748 7B 82 C5 82 B7 81 42 8B F3 8D 60 82 CC 90 45 88 {です。空港の職・
* 058DC758 F5 82 E0 81 41 83 56 83 87 83 62 83 76 82 CC 93 焉Aショップの・
* 058DC768 58 88 F5 82 E0 81 41 83 8D 83 72 81 5B 82 C9 8D X員も、ロビーに・
* 058DC778 C0 82 E9 90 6C 82 E0 81 41 93 FA 96 7B 90 6C 82 タる人も、日本人・
* 058DC788 E7 82 B5 82 AB 90 6C 82 CE 82 A9 82 E8 82 C5 82 轤オき人ばかりで・
* 058DC798 B7 81 76 00 00 8E 8D 05 01 00 00 00 8C 00 00 00 キ」..詩...・..
* 058DC7A8 81 75 8D A1 93 FA 82 CD 90 E2 8D 44 82 CC 93 DC 「今日は絶好の曇
* 058DC7B8 82 E8 8B F3 82 BE 82 E6 81 41 82 C8 82 F1 82 C4 り空だよ、なんて
* 058DC7C8 91 66 93 47 82 C8 96 E9 8B F3 82 BE 82 EB 82 A4 素敵な夜空だろう
* 058DC7D8 81 49 81 40 96 6C 82 CC 8B 41 8D 91 82 C9 8D 87 ! 僕の帰国に合
* 058DC7E8 82 ED 82 B9 82 C4 91 BE 97 7A 82 F0 89 42 82 B5 わせて太陽を隠し
*
* 0012FBCC 0055553D RETURN to .0055553D from .00513234
* 0012FBD0 0012FDB8 Pointer to next SEH record
* 0012FBD4 005555A5 SE handler
* 0012FBD8 0012FD90
* 0012FBDC 0E9F7110
* 0012FBE0 0E9F7110
* 0012FBE4 0A24AA90
* 0012FBE8 00000000
* 0012FBEC 00000000
* 0012FBF0 0EA33460 ASCII "st+cc+tt"
* 0012FBF4 00000000
* 0012FBF8 00000000
* 0012FBFC 00000000
* 0012FC00 00000000
*
* EAX 0E9AD230
* ECX 00000002
* EDX 058DC708
* EBX 0041D17C .0041D17C
* ESP 0012FBCC
* EBP 0012FD90
* ESI 0A24AA90
* EDI 0E9F7110
* EIP 00513234 .00513234
*
* Backlog:
* FIXME: I don't have a way to distinguish Backlog out.
*
* 0A9775D8 5B 66 2C 32 5D 5B 63 2C 24 46 46 65 64 64 31 66 [f,2][c,$FFedd1f
* 0A9775E8 66 5D 5B 72 63 2C 24 46 46 65 64 64 31 66 66 5D f][rc,$FFedd1ff]
* 0A9775F8 81 75 82 CD 82 A2 81 41 82 B1 82 B1 82 CD 93 FA 「はい、ここは日
* 0A977608 96 7B 82 C5 82 B7 81 42 8B F3 8D 60 82 CC 90 45 本です。空港の職
* 0A977618 88 F5 82 E0 81 41 83 56 83 87 83 62 83 76 82 CC 員も、ショップの
* 0A977628 93 58 88 F5 82 E0 81 41 83 8D 83 72 81 5B 82 C9 店員も、ロビーに
*
* EAX 0FF32FE0
* ECX 00000002
* EDX 0A9775D8
* EBX 0041D17C .0041D17C
* ESP 0012FBCC
* EBP 0012FD90
* ESI 0A909350
* EDI 0B843690
* EIP 00513234 .00513234
*
* 0012FBCC 0055553D RETURN to .0055553D from .00513234
* 0012FBD0 0012FDB8 Pointer to next SEH record
* 0012FBD4 005555A5 SE handler
* 0012FBD8 0012FD90
* 0012FBDC 0B843690
* 0012FBE0 0B843690
* 0012FBE4 0A909350
* 0012FBE8 00000000
* 0012FBEC 00000000
* 0012FBF0 0FF25558 ASCII ""[f,2][c,$FFedd1ff][rc,$FFedd1ff]"+text"
* 0012FBF4 00000000
* 0012FBF8 00000000
* 0012FBFC 00000000
*
* Sample game ワルキューレロマンツェ more&more (QLiE2):
* Name:
* 0012FB84 00546877 RETURN to .00546877 from .00504AD0
* 0012FB88 0012FDBC Pointer to next SEH record
* 0012FB8C 00546B1B SE handler
* 0012FB90 0012FD94
* 0012FB94 11832DC0
* 0012FB98 11832DC0
* 0012FB9C 09278EA0
* 0012FBA0 00000000
* 0012FBA4 00000000
* 0012FBA8 00000000
* 0012FBAC 00000000
* 0012FBB0 00000000
* 0012FBB4 00000000
*
* 0A702400 5B 70 63 2C 94 FC 8D F7 5D 00 00 00 70 B6 6F 0A [pc,美桜]...pカo.
*
* EAX 0C2763E0 ASCII "HHP"
* ECX 00000003
* EDX 0A702400
* EBX 0041D168 .0041D168
* ESP 0012FB84 ASCII "whT"
* EBP 0012FD94
* ESI 09278EA0
* EDI 11832DC0
* EIP 00504AD0 .00504AD0
*
* Scenario:
* 09E0D7C8 5B 63 2C 24 46 46 46 46 46 46 44 44 5D 5B 72 63 [c,$FFFFFFDD][rc
* 09E0D7D8 2C 24 46 46 46 46 46 46 44 44 5D 81 75 82 A4 82 ,$FFFFFFDD]「う・
*
* 0012FB84 00546877 RETURN to .00546877 from .00504AD0
* 0012FB88 0012FDBC Pointer to next SEH record
* 0012FB8C 00546B1B SE handler
* 0012FB90 0012FD94
* 0012FB94 118314E0
* 0012FB98 118314E0
* 0012FB9C 09278EA0
* 0012FBA0 00000000
*
* EAX 0A72D820 ASCII "HHP"
* ECX 00000002
* EDX 09E0D7C8
* EBX 0041D168 .0041D168
* ESP 0012FB84 ASCII "whT"
* EBP 0012FD94
* ESI 09278EA0
* EDI 118314E0
* EIP 00504AD0 .00504AD0
*
* Sample game ワルキューレロマンツェ (QLiE1):
* Garbage:
* 0A5115D0 83 56 83 69 83 8A 83 49 5C 8B A4 92 CA 5C 6B 79 シナリオ\共通\ky
* 0A5115E0 6F 5F 30 30 31 5F 30 30 2E 73 00 00 50 FF 50 0A o_001_00.s..PP.
*
* Name:
* 0012FB84 00544913 RETURN to .00544913 from .004FFB04
* 0012FB88 0012FDBC Pointer to next SEH record
* 0012FB8C 00544BB1 SE handler
* 0012FB90 0012FD94
* 0012FB94 01A139A8
* 0012FB98 01A139A8
* 0012FB9C 07D35D00
* 0012FBA0 00000000
*
* EAX 0C303340
* ECX 00000003
* EDX 0ED8A620
* EBX 0041D6A8 .0041D6A8
* ESP 0012FB84
* EBP 0012FD94
* ESI 07D35D00
* EDI 01A139A8
* EIP 004FFB04 .004FFB04
*
* 01A139A8 60 27 52 00 00 00 00 00 00 00 00 00 00 00 80 3F `'R...........€?
* 01A139B8 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 80 3F ..€?..........€?
* 01A139C8 00 00 00 00 48 D9 14 0A 68 D9 14 0A 07 02 00 00 ....Hル.hル...
* 01A139D8 3C F1 07 00 93 9A 5C 00 1C 01 00 00 F4 01 00 00 <・.答\...・..
* 01A139E8 40 33 30 0C A0 D9 A0 01 C0 29 52 00 00 00 00 00 @30.ルタ)R.....
* 01A139F8 00 00 00 00 00 00 80 3F 00 00 80 3F 00 00 00 00 ......€?..€?....
*
* Scenario:
* 0012FB84 00544913 RETURN to .00544913 from .004FFB04
* 0012FB88 0012FDBC Pointer to next SEH record
* 0012FB8C 00544BB1 SE handler
* 0012FB90 0012FD94
* 0012FB94 01A13960 ; jichi: type string is saved here in edi and arg4/arg5
* 0012FB98 01A13960
* 0012FB9C 07D35D00
* 0012FBA0 00000000
*
* 0A14D7C8 30 5F 4D 65 73 73 61 67 65 54 65 78 74 2C 30 00 0_MessageText,0.
*
* EAX 0C308500
* ECX 00000006
* EDX 0B100590
* EBX 0041D6A8 .0041D6A8
* ESP 0012FB84
* EBP 0012FD94
* ESI 07D35D00
* EDI 01A13960
* EIP 004FFB04 .004FFB04
*
*
* 01A13960 60 27 52 00 00 00 00 00 00 00 00 00 00 00 80 3F `'R...........€?
* 01A13970 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 80 3F ..€?..........€?
* 01A13980 00 00 00 00 C8 D7 14 0A A8 D8 14 0A 07 02 00 00 ....ネラ.ィリ...
* 01A13990 34 90 3F 00 BE 0A 5B 00 D3 02 00 00 EC 01 00 00 4・.セ.[.モ..・..
* 01A139A0 00 85 30 0C A0 D9 A0 01 60 27 52 00 00 00 00 00 .・.ル`'R.....
* 01A139B0 00 00 00 00 00 00 80 3F 00 00 80 3F 00 00 00 00 ......€?..€?....
*
* 0A14D948 30 5F 4E 61 6D 65 54 65 78 74 2C 30 00 00 00 00 0_NameText,0....
*/
/**
* Known Type strings
* These strings seems to be different for different games
*
* ワルキューレロマンツェ(QLiE1)
* 七つのふしぎの終わるとき (QLiE1)
*
* 0_NameText,0
* 0_MessageText,0
* 0_Message,0
*
* ワルキューレロマンツェ More&More (QLiE2)
* 0_nametext,0
* 0_imo_message,0
*
* 月に寄りそう乙女の作法2 (QLiE2):
* 0_dialogmessage,0
* $windowapril
* fontsize:30:30
*
*/
struct TextArgument // root at [edx - 4]
{
DWORD size; // in [edx-4]
char text[1]; // in edx
bool isValid() const
{
return text && size
&& Engine::isAddressReadable(text, size)
&& ::strlen(text) == size;
}
};
struct TypeArgument
{
DWORD unknown[8]; // 0x20
DWORD textFlag; // +0x20, 0 for QLiE1, 1 for QLie2
LPCSTR textAddress; // for QLiE1
char textData[1]; // for QLiE2
LPCSTR text() const
{
if (textFlag == 0) // QLiE1
return Engine::isAddressReadable(textAddress) ? textAddress : nullptr;
else // QLiE2
return textData;
}
// Return UnknownRole(0) if not sure
Engine::TextRole role() const
{
if (textFlag > 0xff)
return Engine::OtherRole;
LPCSTR t = text();
if (!t || !*t)
return Engine::UnknownRole;
for (int i = 0; t[i]; i++) {
if (i > 0x40) // text too large
return Engine::OtherRole;
BYTE ch = t[0];
if (ch <= 32 || ch > 127) // non-printable or not ascii
return Engine::OtherRole;
}
// Convert to lower case
std::string s = stolower(std::string(t));
t = s.c_str();
if (::strchr(t, '_')) {
// QLiE2
if (::strstr(t, "_imo_message,"))
return Engine::ScenarioRole;
if (::strstr(t, "_dialogmessage,"))
return Engine::OtherRole;
// QLiE1
if (::strstr(t, "_messagetext,"))
return Engine::ScenarioRole;
if (::strstr(t, "_nametext,"))
return Engine::NameRole;
if (::strstr(t, "_message,") || // this is ambiguous and will overwrite imo_message
::strstr(t, "_statetext,") ||
//::strstr(t, "_databutton,") ||
//::strstr(t, "_selectbutton,") ||
::strstr(t, "button,"))
return Engine::OtherRole;
}
if (s.find_first_of(".[!@*\\") != std::string::npos)
return Engine::OtherRole;
//DOUT("unknown text type:" << t);
return Engine::UnknownRole;
}
};
int trimmedSize;char*trimmedText;
int endtype;
bool hookBefore(hook_stack*s,void* data1, size_t* len,uintptr_t*role)
{
auto arg = (TextArgument *)(s->edx - 4);
if (!arg->isValid())
return false;
trimmedSize = arg->size;
trimmedText = trim(arg->text, &trimmedSize);
if (trimmedSize <= 0 || !trimmedText || !*trimmedText)
return false;
if (::strstr(arg->text, "\x82\xa0\x82\xa0\x82\xa0\x82\xa0\x82\xa0")) /* Skip text containing あああああ */
return false;
if (all_ascii(trimmedText)) // This is optional, but I don't want to translate English
return false;
//role = Engine::OtherRole;
enum { sig = 0 };
* role = Engine::ScenarioRole;
enum : uint16_t {
w_name_open = 0x7981, /* 【 */
w_name_close = 0x7a81 /* 】 */
};
// if (trimmedText[trimmedSize]) // text ending withb ']' is other text
// *role = Engine::OtherRole;
{
std::string oldData(trimmedText, trimmedSize);
endtype=0;
if(oldData.size()>3&&oldData.substr(oldData.size()-3)=="[n]"){
endtype=1;trimmedSize-=3;
}else if(oldData.size()>3&&oldData.substr(oldData.size()-3)=="[c]"){
endtype=2;trimmedSize-=3;
}
}
if (trimmedSize > 4
&& w_name_open == *(uint16_t *)trimmedText
&& w_name_close == *(uint16_t *)(trimmedText + trimmedSize - 2)) {
trimmedText += 2;
trimmedSize -= 4;
if (*role == Engine::ScenarioRole)
*role = Engine::NameRole; // FIXME: This name recognition logic does not work for ワルキューレロマンツェ
}
// Skip sjis 名前 = 96bc914f
if (0 == ::strncmp(trimmedText, "\x96\xbc\x91\x4f", trimmedSize))
return false;
/*
if (s->stack[4] == s->stack[5]) { // && s->edi == s->stack[4]
auto t = (TypeArgument *)s->stack[4];
if (Engine::isAddressReadable(t)) {
//if (!t->isValid())
// return true;
if (auto r = t->role())
*role = r;
}
}
*/
//auto split = s->stack[0]; // retaddr is always the same anyway
std::string oldData(trimmedText, trimmedSize);
write_string_overwrite(data1,len,oldData);
return true;
}
void hookafter(hook_stack*s,void* data1, size_t len)
{
std::string newData=std::string((char*)data1,len);
auto arg = (TextArgument *)(s->edx - 4);
int prefixSize = trimmedText - arg->text,
suffixSize = arg->size - prefixSize - trimmedSize;
if (prefixSize)
newData.insert(0,std::string(arg->text, prefixSize));
if (suffixSize)
newData.append(trimmedText + trimmedSize, suffixSize);
if(endtype==1)
newData=newData+"[n]";
else if(endtype==2)
newData=newData+"[c]";
static std::string data_;
data_ = newData;
s->edx = (ULONG)data_.c_str(); // reset arg1
*(DWORD *)(s->edx - 4) = data_.size();
//arg->size = data_.size(); // no idea why this will crash ...
//*(DWORD *)(s->edx - 4) = newData.size() + trimmedText - text;
//::strcpy(trimmedText, newData.constData());
}
} // namespace Private
/**
* Sample game: 月に寄りそう乙女の作法2
* See: http://capita.tistory.com/m/post/236
*
* This function is not aligned.
* Text in edx. Length in [edx - 4]
*
* 00513234 55 PUSH EBP
* 00513235 8BEC MOV EBP,ESP
* 00513237 6A 00 PUSH 0x0
* 00513239 53 PUSH EBX
* 0051323A 56 PUSH ESI
* 0051323B 8BF2 MOV ESI,EDX
* 0051323D 8BD8 MOV EBX,EAX
* 0051323F 33C0 XOR EAX,EAX
* 00513241 55 PUSH EBP
* 00513242 68 AD325100 PUSH .005132AD
* 00513247 64:FF30 PUSH DWORD PTR FS:[EAX]
* 0051324A 64:8920 MOV DWORD PTR FS:[EAX],ESP
* 0051324D 80BB 0A160000 00 CMP BYTE PTR DS:[EBX+0x160A],0x0 ; jichi: can be used as pattern to distinguish QLiE1/2
* 00513254 74 07 JE SHORT .0051325D
* 00513256 8BC3 MOV EAX,EBX
* 00513258 8B10 MOV EDX,DWORD PTR DS:[EAX]
* 0051325A FF52 24 CALL DWORD PTR DS:[EDX+0x24]
* 0051325D 8BC3 MOV EAX,EBX
* 0051325F E8 98C1FFFF CALL .0050F3FC
* 00513264 84C0 TEST AL,AL
* 00513266 74 07 JE SHORT .0051326F
* 00513268 8BC3 MOV EAX,EBX
* 0051326A 8B10 MOV EDX,DWORD PTR DS:[EAX]
* 0051326C FF52 24 CALL DWORD PTR DS:[EDX+0x24]
* 0051326F 8D4D FC LEA ECX,DWORD PTR SS:[EBP-0x4]
* 00513272 8BD6 MOV EDX,ESI
* 00513274 8BC3 MOV EAX,EBX
* 00513276 E8 5D310000 CALL .005163D8
* 0051327B 8B55 FC MOV EDX,DWORD PTR SS:[EBP-0x4]
* 0051327E 8BC3 MOV EAX,EBX
* 00513280 E8 1B100000 CALL .005142A0
* 00513285 8BC3 MOV EAX,EBX
* 00513287 E8 5C300000 CALL .005162E8
* 0051328C 85C0 TEST EAX,EAX
* 0051328E 75 07 JNZ SHORT .00513297
* 00513290 8BC3 MOV EAX,EBX
* 00513292 E8 B1070000 CALL .00513A48
* 00513297 33C0 XOR EAX,EAX
* 00513299 5A POP EDX
* 0051329A 59 POP ECX
* 0051329B 59 POP ECX
* 0051329C 64:8910 MOV DWORD PTR FS:[EAX],EDX
* 0051329F 68 B4325100 PUSH .005132B4
* 005132A4 8D45 FC LEA EAX,DWORD PTR SS:[EBP-0x4]
* 005132A7 E8 F421EFFF CALL .004054A0
* 005132AC C3 RETN
* 005132AD ^E9 A21AEFFF JMP .00404D54
* 005132B2 ^EB F0 JMP SHORT .005132A4
* 005132B4 5E POP ESI
* 005132B5 5B POP EBX
* 005132B6 59 POP ECX
* 005132B7 5D POP EBP
* 005132B8 C3 RETN
* 005132B9 8D40 00 LEA EAX,DWORD PTR DS:[EAX]
* 005132BC 55 PUSH EBP
* 005132BD 8BEC MOV EBP,ESP
* 005132BF 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8]
* 005132C2 8B40 FC MOV EAX,DWORD PTR DS:[EAX-0x4]
* 005132C5 80B8 6F180000 00 CMP BYTE PTR DS:[EAX+0x186F],0x0
* 005132CC 74 23 JE SHORT .005132F1
* 005132CE A1 C8EA5700 MOV EAX,DWORD PTR DS:[0x57EAC8]
* 005132D3 8B80 FC020000 MOV EAX,DWORD PTR DS:[EAX+0x2FC]
* 005132D9 8B15 C8EA5700 MOV EDX,DWORD PTR DS:[0x57EAC8] ; .00586178
* 005132DF 8B92 E8020000 MOV EDX,DWORD PTR DS:[EDX+0x2E8]
* 005132E5 3BD0 CMP EDX,EAX
* 005132E7 7C 02 JL SHORT .005132EB
* 005132E9 8BC2 MOV EAX,EDX
* 005132EB 0105 B8E45700 ADD DWORD PTR DS:[0x57E4B8],EAX
* 005132F1 5D POP EBP
* 005132F2 C3 RETN
* 005132F3 90 NOP
* 005132F4 55 PUSH EBP
* 005132F5 8BEC MOV EBP,ESP
* 005132F7 53 PUSH EBX
* 005132F8 8B5D 08 MOV EBX,DWORD PTR SS:[EBP+0x8]
* ...
*
* {00528988(E9 73 FC 04 00 90),00578600(8D 45 FC 8B 4D FC 66 81 39 81 79 74 05 90 90 90 90 90 E9 77 03 FB FF)}
* {00528988(E9 73 FC 04 00 90),005785FE(EB 27 8D 45 FC 8B 4D FC 66 81 39 81 79 74 0A 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 68 8E 89 52 00 C3)}
*
* FORCEFONT(5),FONT(Gulim,-13),ENCODEKOR,HOOK(0x00513234,TRANS(EDX,LEN(-4),PTRCHEAT),RETNPOS(COPY)),HOOK(0x0057860D,TRANS(ECX,LEN(-4),PTRCHEAT),RETNPOS(SOURCE))
*
* Character handled here, which is not used:
* 00528969 74 28 JE SHORT .00528993
* 0052896B 3C 09 CMP AL,0x9
* 0052896D 74 24 JE SHORT .00528993
* 0052896F 3C 2F CMP AL,0x2F
* 00528971 74 20 JE SHORT .00528993
* 00528973 3C 40 CMP AL,0x40
* 00528975 74 1C JE SHORT .00528993
* 00528977 8D45 E8 LEA EAX,DWORD PTR SS:[EBP-0x18]
* 0052897A 8D93 49010000 LEA EDX,DWORD PTR DS:[EBX+0x149]
* 00528980 E8 7FCDEDFF CALL .00405704
* 00528985 8B55 E8 MOV EDX,DWORD PTR SS:[EBP-0x18]
* 00528988 8D45 FC LEA EAX,DWORD PTR SS:[EBP-0x4] ; jichi: 2-byte character in ecx
* 0052898B 8B4D FC MOV ECX,DWORD PTR SS:[EBP-0x4]
* 0052898E E8 25CEEDFF CALL .004057B8
* 00528993 8D83 4C020000 LEA EAX,DWORD PTR DS:[EBX+0x24C]
* 00528999 8B55 FC MOV EDX,DWORD PTR SS:[EBP-0x4]
* 0052899C E8 53CBEDFF CALL .004054F4
* 005289A1 8B83 4C020000 MOV EAX,DWORD PTR DS:[EBX+0x24C]
* 005289A7 85C0 TEST EAX,EAX
* 005289A9 74 05 JE SHORT .005289B0
* 005289AB 83E8 04 SUB EAX,0x4
* 005289AE 8B00 MOV EAX,DWORD PTR DS:[EAX]
* 005289B0 8983 50020000 MOV DWORD PTR DS:[EBX+0x250],EAX
* 005289B6 C645 F7 01 MOV BYTE PTR SS:[EBP-0x9],0x1
* 005289BA 33C0 XOR EAX,EAX
* 005289BC 5A POP EDX
* 005289BD 59 POP ECX
* 005289BE 59 POP ECX
* 005289BF 64:8910 MOV DWORD PTR FS:[EAX],EDX
* 005289C2 68 E4895200 PUSH .005289E4
* 005289C7 8D45 E8 LEA EAX,DWORD PTR SS:[EBP-0x18]
* 005289CA BA 03000000 MOV EDX,0x3
* 005289CF E8 F0CAEDFF CALL .004054C4
* 005289D4 8D45 FC LEA EAX,DWORD PTR SS:[EBP-0x4]
* 005289D7 E8 C4CAEDFF CALL .004054A0
* 005289DC C3 RETN
* 005289DD ^E9 72C3EDFF JMP .00404D54
* 005289E2 ^EB E3 JMP SHORT .005289C7
* 005289E4 0FB645 F7 MOVZX EAX,BYTE PTR SS:[EBP-0x9]
* 005289E8 5F POP EDI
* 005289E9 5E POP ESI
* 005289EA 5B POP EBX
* 005289EB 8BE5 MOV ESP,EBP
* 005289ED 5D POP EBP
* 005289EE C3 RETN
* 005289EF 90 NOP
* 005289F0 55 PUSH EBP
* 005289F1 8BEC MOV EBP,ESP
* 005289F3 83C4 F8 ADD ESP,-0x8
* 005289F6 53 PUSH EBX
*
* Sample game: ワルキューレロマンツェ (QLiE1)
*
* This function is found by looking all all matches of the following pattern
* And then lookup up for push ebp
* 005132E5 3BD0 CMP EDX,EAX
* 005132E7 7C 02 JL SHORT .005132EB
* 005132E9 8BC2 MOV EAX,EDX
*
* 004FFB04 55 PUSH EBP
* 004FFB05 8BEC MOV EBP,ESP
* 004FFB07 6A 00 PUSH 0x0
* 004FFB09 53 PUSH EBX
* 004FFB0A 56 PUSH ESI
* 004FFB0B 8BF2 MOV ESI,EDX
* 004FFB0D 8BD8 MOV EBX,EAX
* 004FFB0F 33C0 XOR EAX,EAX
* 004FFB11 55 PUSH EBP
* 004FFB12 68 7DFB4F00 PUSH .004FFB7D
* 004FFB17 64:FF30 PUSH DWORD PTR FS:[EAX]
* 004FFB1A 64:8920 MOV DWORD PTR FS:[EAX],ESP
* 004FFB1D 80BB FA150000 00 CMP BYTE PTR DS:[EBX+0x15FA],0x0
* 004FFB24 74 07 JE SHORT .004FFB2D
* 004FFB26 8BC3 MOV EAX,EBX
* 004FFB28 8B10 MOV EDX,DWORD PTR DS:[EAX]
* 004FFB2A FF52 1C CALL DWORD PTR DS:[EDX+0x1C]
* 004FFB2D 8BC3 MOV EAX,EBX
* 004FFB2F E8 04CFFFFF CALL .004FCA38
* 004FFB34 84C0 TEST AL,AL
* 004FFB36 74 07 JE SHORT .004FFB3F
* 004FFB38 8BC3 MOV EAX,EBX
* 004FFB3A 8B10 MOV EDX,DWORD PTR DS:[EAX]
* 004FFB3C FF52 1C CALL DWORD PTR DS:[EDX+0x1C]
* 004FFB3F 8D4D FC LEA ECX,DWORD PTR SS:[EBP-0x4]
* 004FFB42 8BD6 MOV EDX,ESI
* 004FFB44 8BC3 MOV EAX,EBX
* 004FFB46 E8 69320000 CALL .00502DB4
* 004FFB4B 8B55 FC MOV EDX,DWORD PTR SS:[EBP-0x4]
* 004FFB4E 8BC3 MOV EAX,EBX
* 004FFB50 E8 23120000 CALL .00500D78
* 004FFB55 8BC3 MOV EAX,EBX
* 004FFB57 E8 58310000 CALL .00502CB4
* 004FFB5C 85C0 TEST EAX,EAX
* 004FFB5E 75 07 JNZ SHORT .004FFB67
* 004FFB60 8BC3 MOV EAX,EBX
* 004FFB62 E8 5D070000 CALL .005002C4
* 004FFB67 33C0 XOR EAX,EAX
* 004FFB69 5A POP EDX
* 004FFB6A 59 POP ECX
* 004FFB6B 59 POP ECX
* 004FFB6C 64:8910 MOV DWORD PTR FS:[EAX],EDX
* 004FFB6F 68 84FB4F00 PUSH .004FFB84
* 004FFB74 8D45 FC LEA EAX,DWORD PTR SS:[EBP-0x4]
* 004FFB77 E8 5859F0FF CALL .004054D4
* 004FFB7C C3 RETN
* 004FFB7D ^E9 0652F0FF JMP .00404D88
* 004FFB82 ^EB F0 JMP SHORT .004FFB74
* 004FFB84 5E POP ESI
* 004FFB85 5B POP EBX
* 004FFB86 59 POP ECX
* 004FFB87 5D POP EBP
* 004FFB88 C3 RETN
* 004FFB89 8D40 00 LEA EAX,DWORD PTR DS:[EAX]
* 004FFB8C 55 PUSH EBP
* 004FFB8D 8BEC MOV EBP,ESP
* 004FFB8F 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8]
* 004FFB92 8B40 FC MOV EAX,DWORD PTR DS:[EAX-0x4]
* 004FFB95 80B8 4F180000 00 CMP BYTE PTR DS:[EAX+0x184F],0x0
* 004FFB9C 74 23 JE SHORT .004FFBC1
* 004FFB9E A1 E4CA5600 MOV EAX,DWORD PTR DS:[0x56CAE4]
* 004FFBA3 8B80 CC020000 MOV EAX,DWORD PTR DS:[EAX+0x2CC]
* 004FFBA9 8B15 E4CA5600 MOV EDX,DWORD PTR DS:[0x56CAE4] ; .005740E8
* 004FFBAF 8B92 B8020000 MOV EDX,DWORD PTR DS:[EDX+0x2B8]
* 004FFBB5 3BD0 CMP EDX,EAX
* 004FFBB7 7C 02 JL SHORT .004FFBBB
* 004FFBB9 8BC2 MOV EAX,EDX
* 004FFBBB 0105 64C45600 ADD DWORD PTR DS:[0x56C464],EAX
* 004FFBC1 5D POP EBP
* 004FFBC2 C3 RETN
* 004FFBC3 90 NOP
*/
bool attach(ULONG startAddress, ULONG stopAddress)
{
// QLiE1
// 004FFB1D 80BB FA150000 00 CMP BYTE PTR DS:[EBX+0x15FA],0x0
// QLiE2
// 0051324D 80BB 0A160000 00 CMP BYTE PTR DS:[EBX+0x160A],0x0 ; jichi: instruction used as pattern
const uint8_t bytes[] = { // i.e. 3BD0 7C 02 8BC2 0105
0x3B,0xD0, // 004FFBB5 3BD0 CMP EDX,EAX
0x7C, 0x02, // 004FFBB7 7C 02 JL SHORT .004FFBBB
0x8B,0xC2, // 004FFBB9 8BC2 MOV EAX,EDX
0x01,0x05 //64C45600 // 004FFBBB 0105 64C45600 ADD DWORD PTR DS:[0x56C464],EAX
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress);
if (!addr)
return false;
// 00513234 55 PUSH EBP ; jichi: hook here
// 00513235 8BEC MOV EBP,ESP
// 00513237 6A 00 PUSH 0x0
// 00513239 53 PUSH EBX
// 0051323A 56 PUSH ESI
enum : DWORD { sig = 0x6aec8b55 };
enum { AlignedStep = 1 }; // function not aligned
addr = MemDbg::findEnclosingFunctionBeforeDword(sig, addr, MemDbg::MaximumFunctionSize, AlignedStep);
if (!addr)
return false;
HookParam hp;
hp.address=addr;
hp.hook_before=Private::hookBefore;
hp.hook_after=Private::hookafter;
hp.newlineseperator=L"[n]";
hp.type=EMBED_ABLE|EMBED_DYNA_SJIS|USING_STRING;
hp.hook_font=F_ExtTextOutA|F_GetTextExtentPoint32A;
hp.filter_fun=[](void* data, size_t* len, HookParam* hp){
write_string_overwrite(data,len,std::regex_replace(std::string((char*)data,*len), std::regex("\\[rb,(.*?),.+\\]"), "$1"));
return true;
};
return NewHook(hp,"EmbedQLIE");
}
} // namespace ScenarioHook
} // unnamed namespace
bool QLIE::attach_function() {
auto embed=ScenarioHook::attach(processStartAddress, processStopAddress);
return InsertQLIEHook()||embed;
}