LunaHook-mirror/LunaHook/engine32/Circus2.cpp
恍兮惚兮 6a43cd5b86 pch
2024-05-06 23:31:54 +08:00

395 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

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"Circus2.h"
namespace{
bool filter(void* data, size_t* len, HookParam* hp){
if (strstr((char*)data,"@i")||strstr((char*)data,"@y"))return false;
//{てんきゅう/天穹}
if(strstr((char*)data,"\x81\x6f")&&strstr((char*)data,"\x81\x5e")&&strstr((char*)data,"\x81\x70")){
StringFilter((char*)data, len, "\x81\x70", 2);
StringFilterBetween((char*)data,len, "\x81\x6f", 2, "\x81\x5e", 2);
}
return true;
};
}
/**
* jichi 6/5/2014: Sample function from DC3 at 0x4201d0
* 004201ce cc int3
* 004201cf cc int3
* 004201d0 /$ 8b4c24 08 mov ecx,dword ptr ss:[esp+0x8]
* 004201d4 |. 8a01 mov al,byte ptr ds:[ecx]
* 004201d6 |. 84c0 test al,al
* 004201d8 |. 74 1c je short dc3.004201f6
* 004201da |. 8b5424 04 mov edx,dword ptr ss:[esp+0x4]
* 004201de |. 8bff mov edi,edi
* 004201e0 |> 3c 24 /cmp al,0x24
* 004201e2 |. 75 05 |jnz short dc3.004201e9
* 004201e4 |. 83c1 02 |add ecx,0x2
* 004201e7 |. eb 04 |jmp short dc3.004201ed
* 004201e9 |> 8802 |mov byte ptr ds:[edx],al
* 004201eb |. 42 |inc edx
* 004201ec |. 41 |inc ecx
* 004201ed |> 8a01 |mov al,byte ptr ds:[ecx]
* 004201ef |. 84c0 |test al,al
* 004201f1 |.^75 ed \jnz short dc3.004201e0
* 004201f3 |. 8802 mov byte ptr ds:[edx],al
* 004201f5 |. c3 retn
* 004201f6 |> 8b4424 04 mov eax,dword ptr ss:[esp+0x4]
* 004201fa |. c600 00 mov byte ptr ds:[eax],0x0
* 004201fd \. c3 retn
*/
bool InsertCircusHook2() // jichi 10/2/2013: Change return type to bool
{
for (DWORD i = processStartAddress + 0x1000; i < processStopAddress -4; i++)
if ((*(DWORD *)i & 0xffffff) == 0x75243c) { // cmp al, 24; je
if (DWORD j = SafeFindEnclosingAlignedFunction(i, 0x80)) {
HookParam hp;
hp.address = j;
hp.offset=get_stack(2);
//hp.filter_fun = CharNewLineFilter; // \n\s* is used to remove new line
hp.type = USING_STRING;
//GROWL_DWORD(hp.address); // jichi 6/5/2014: 0x4201d0 for DC3
//RegisterEngineType(ENGINE_CIRCUS);
return NewHook(hp, "Circus");
}
break;
}
//ConsoleOutput("Unknown CIRCUS engine.");
ConsoleOutput("CIRCUS: failed");
return false;
}
namespace{
bool c2(){
//D.C.III Dream Daysダ・カーポIIIドリームデイズ
auto entry=Util::FindImportEntry(processStartAddress,(DWORD)GetGlyphOutlineA);
DWORD funcaddr=0;
if(entry==0)return false;
for (auto addr : Util::SearchMemory(&entry, 4, PAGE_EXECUTE, processStartAddress, processStopAddress) ) {
DWORD _=0xCCCCCCCC;
funcaddr=reverseFindBytes((BYTE*)&_,4,addr-0x1000,addr);
//funcaddr=MemDbg::findEnclosingAlignedFunction(addr,0x1000);ConsoleOutput("%p",funcaddr);
}
if(funcaddr==0)return false;
funcaddr+=4;
HookParam hp;
hp.address = funcaddr;
hp.offset=get_stack(2);
hp.type = USING_STRING;//|EMBED_ABLE|EMBED_BEFORE_SIMPLE|EMBED_AFTER_NEW|EMBED_DYNA_SJIS;
//hp.hook_font=F_GetGlyphOutlineA;
//it will split a long to many lines
hp.filter_fun=filter;
return NewHook(hp, "Circus2");
}
}
namespace { // unnamed
// Skip leading tags such as @K and @c5
template <typename strT>
strT ltrim(strT s)
{
if (s && *s == '@')
while ((signed char)*++s > 0);
return s;
}
namespace ScenarioHook {
namespace Private {
DWORD nameReturnAddress_,
scenarioReturnAddress_;
/**
* Sample game: DC3, function: 0x4201d0
*
* IDA: sub_4201D0 proc near
* - arg_0 = dword ptr 4
* - arg_4 = dword ptr 8
*
* Observations:
* - arg1: LPVOID, pointed to unknown object
* - arg2: LPCSTR, the actual text
*
* Example runtime stack:
* 0012F15C 0040C208 RETURN to .0040C208 from .00420460
* 0012F160 0012F7CC ; jichi: unknown stck
* 0012F164 0012F174 ; jichi: text
* 0012F168 0012F6CC
* 0012F16C 0012F7CC
* 0012F170 0012F7CC
*/
void hookafter(hook_stack*s,void* data, size_t len){
auto newData =std::string((char*)data,len);
LPCSTR text = (LPCSTR)s->stack[2], // arg2
trimmedText = ltrim(text);
if (trimmedText != text)
newData.insert(0,std::string(text, trimmedText - text));
write_string_new(&s->stack[2],0,newData);
}
bool hookBefore(hook_stack*s,void* data, size_t* len,uintptr_t*role)
{
LPCSTR text = (LPCSTR)s->stack[2], // arg2
trimmedText = ltrim(text);
if (!trimmedText || !*trimmedText)
return false;
auto retaddr = s->stack[0]; // retaddr
* role = retaddr == scenarioReturnAddress_ ? Engine::ScenarioRole :
retaddr == nameReturnAddress_ ? Engine::NameRole :
Engine::OtherRole;
//s->ebx? Engine::OtherRole : // other threads ebx is not zero
//// 004201e4 |. 83c1 02 |add ecx,0x2
//// 004201e7 |. eb 04 |jmp short dc3.004201ed
//*(BYTE *)(retaddr + 3) == 0xe9 // old name
//? Engine::NameRole : // retaddr+3 is jmp
//Engine::ScenarioRole;
return write_string_overwrite(data,len,trimmedText);
}
// Alternatively, using the following pattern bytes also works:
//
// 3c24750583c102eb0488024241
//
// 004201e0 |> 3c 24 /cmp al,0x24
// 004201e2 |. 75 05 |jnz short dc3.004201e9
// 004201e4 |. 83c1 02 |add ecx,0x2
// 004201e7 |. eb 04 |jmp short dc3.004201ed
// 004201e9 |> 8802 |mov byte ptr ds:[edx],al
// 004201eb |. 42 |inc edx
// 004201ec |. 41 |inc ecx
ULONG findFunctionAddress(ULONG startAddress, ULONG stopAddress) // find the function to hook
{
//return 0x4201d0; // DC3 function address
for (ULONG i = startAddress + 0x1000; i < stopAddress -4; i++)
// * 004201e0 |> 3c 24 /cmp al,0x24
// * 004201e2 |. 75 05 |jnz short dc3.004201e9
if ((*(ULONG *)i & 0xffffff) == 0x75243c) { // cmp al, 24; je
enum { range = 0x80 }; // the range is small, since it is a small function
if (ULONG addr = MemDbg::findEnclosingAlignedFunction(i, range))
return addr;
}
return 0;
}
} // namespace Private
/**
* jichi 6/5/2014: Sample function from DC3 at 0x4201d0
*
* Sample game: DC3PP
* 0042CE1E 68 E0F0B700 PUSH .00B7F0E0
* 0042CE23 A3 0C824800 MOV DWORD PTR DS:[0x48820C],EAX
* 0042CE28 E8 A352FFFF CALL .004220D0 ; jichi: name thread
* 0042CE2D C705 08024D00 01>MOV DWORD PTR DS:[0x4D0208],0x1
* 0042CE37 EB 52 JMP SHORT .0042CE8B
* 0042CE39 392D 08024D00 CMP DWORD PTR DS:[0x4D0208],EBP
* 0042CE3F 74 08 JE SHORT .0042CE49
* 0042CE41 392D 205BB900 CMP DWORD PTR DS:[0xB95B20],EBP
* 0042CE47 74 07 JE SHORT .0042CE50
* 0042CE49 C605 E0F0B700 00 MOV BYTE PTR DS:[0xB7F0E0],0x0
* 0042CE50 8D5424 40 LEA EDX,DWORD PTR SS:[ESP+0x40]
* 0042CE54 52 PUSH EDX
* 0042CE55 68 30B5BA00 PUSH .00BAB530
* 0042CE5A 892D 08024D00 MOV DWORD PTR DS:[0x4D0208],EBP
* 0042CE60 E8 6B52FFFF CALL .004220D0 ; jichi: scenario thread
* 0042CE65 C705 A0814800 FF>MOV DWORD PTR DS:[0x4881A0],-0x1
* 0042CE6F 892D 2C824800 MOV DWORD PTR DS:[0x48822C],EBP
*
* Sample game: 水夏弐律
*
* 004201ce cc int3
* 004201cf cc int3
* 004201d0 /$ 8b4c24 08 mov ecx,dword ptr ss:[esp+0x8]
* 004201d4 |. 8a01 mov al,byte ptr ds:[ecx]
* 004201d6 |. 84c0 test al,al
* 004201d8 |. 74 1c je short dc3.004201f6
* 004201da |. 8b5424 04 mov edx,dword ptr ss:[esp+0x4]
* 004201de |. 8bff mov edi,edi
* 004201e0 |> 3c 24 /cmp al,0x24
* 004201e2 |. 75 05 |jnz short dc3.004201e9
* 004201e4 |. 83c1 02 |add ecx,0x2
* 004201e7 |. eb 04 |jmp short dc3.004201ed
* 004201e9 |> 8802 |mov byte ptr ds:[edx],al
* 004201eb |. 42 |inc edx
* 004201ec |. 41 |inc ecx
* 004201ed |> 8a01 |mov al,byte ptr ds:[ecx]
* 004201ef |. 84c0 |test al,al
* 004201f1 |.^75 ed \jnz short dc3.004201e0
* 004201f3 |. 8802 mov byte ptr ds:[edx],al
* 004201f5 |. c3 retn
* 004201f6 |> 8b4424 04 mov eax,dword ptr ss:[esp+0x4]
* 004201fa |. c600 00 mov byte ptr ds:[eax],0x0
* 004201fd \. c3 retn
*
* Sample registers:
* EAX 0012F998
* ECX 000000DB
* EDX 00000059
* EBX 00000000 ; ebx is zero for name/scenario thread
* ESP 0012F96C
* EBP 00000003
* ESI 00000025
* EDI 000000DB
* EIP 022C0000
*
* EAX 0012F174
* ECX 0012F7CC
* EDX FDFBF80C
* EBX 0012F6CC
* ESP 0012F15C
* EBP 0012F5CC
* ESI 800000DB
* EDI 00000001
* EIP 00420460 .00420460
*
* EAX 0012F174
* ECX 0012F7CC
* EDX FDFBF7DF
* EBX 0012F6CC
* ESP 0012F15C
* EBP 0012F5CC
* ESI 00000108
* EDI 00000001
* EIP 00420460 .00420460
*
* 0042DC5D 52 PUSH EDX
* 0042DC5E 68 E038AC00 PUSH .00AC38E0 ; ASCII "Ami"
* 0042DC63 E8 F827FFFF CALL .00420460 ; jichi: name thread
* 0042DC68 83C4 08 ADD ESP,0x8
* 0042DC6B E9 48000000 JMP .0042DCB8
* 0042DC70 83FD 58 CMP EBP,0x58
* 0042DC73 74 07 JE SHORT .0042DC7C
* 0042DC75 C605 E038AC00 00 MOV BYTE PTR DS:[0xAC38E0],0x0
* 0042DC7C 8D4424 20 LEA EAX,DWORD PTR SS:[ESP+0x20]
* 0042DC80 50 PUSH EAX
* 0042DC81 68 0808AF00 PUSH .00AF0808
* 0042DC86 E8 D527FFFF CALL .00420460 ; jichi: scenario thread
* 0042DC8B 83C4 08 ADD ESP,0x8
* 0042DC8E 33C0 XOR EAX,EAX
* 0042DC90 C705 D0DF4700 FF>MOV DWORD PTR DS:[0x47DFD0],-0x1
* 0042DC9A A3 0CE04700 MOV DWORD PTR DS:[0x47E00C],EAX
* 0042DC9F A3 940EB200 MOV DWORD PTR DS:[0xB20E94],EAX
* 0042DCA4 A3 2C65AC00 MOV DWORD PTR DS:[0xAC652C],EAX
* 0042DCA9 C705 50F9AC00 59>MOV DWORD PTR DS:[0xACF950],0x59
* 0042DCB3 A3 3C70AE00 MOV DWORD PTR DS:[0xAE703C],EAX
*/
bool attach(ULONG startAddress, ULONG stopAddress)
{
ULONG addr = Private::findFunctionAddress(startAddress, stopAddress);
if (!addr)
return false;
// Find the nearest two callers (distance within 100)
ULONG lastCall = 0;
auto fun = [&lastCall](ULONG call) -> bool {
// scenario: 0x42b78c
// name: 0x42b754
if (call - lastCall < 100) {
Private::scenarioReturnAddress_ = call + 5;
Private::nameReturnAddress_ = lastCall + 5;
return false; // found target
}
lastCall = call;
return true; // replace all functions
};
MemDbg::iterNearCallAddress(fun, addr, startAddress, stopAddress);
if (!Private::scenarioReturnAddress_ && lastCall) {
Private::scenarioReturnAddress_ = lastCall + 5;
}
HookParam hp;
hp.address=addr;
hp.filter_fun=filter;
hp.hook_before=Private::hookBefore;
hp.hook_after=Private::hookafter;
hp.hook_font=F_GetGlyphOutlineA;
hp.type=USING_STRING|EMBED_ABLE|NO_CONTEXT|EMBED_DYNA_SJIS;
return NewHook(hp,"EmbedCircus");
}
} // namespace ScenarioHook
} // unnamed namespace
bool InsertCircusHook3()
{
/*
* Sample games:
* https://vndb.org/v20218
*/
const BYTE bytes[] = {
0xCC, // int 3
0x81, 0xEC, XX4, // sub esp,000004E0 << hook here
0xA1, XX4, // mov eax,[DSIF.EXE+AD288]
0x33, 0xC4, // xor eax,esp
0x89, 0x84, 0x24, XX4, // mov [esp+000004DC],eax
0x8B, 0x84, 0x24, XX4, // mov eax,[esp+000004E4]
0x53, // push ebx
0x55, // push ebp
0x56, // push esi
0x8B, 0xB4, 0x24, XX4 // mov esi,[esp+000004F4]
};
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
return false;
}
HookParam hp;
hp.address = addr + 1;
hp.offset=get_reg(regs::esi);
hp.split = get_reg(regs::ecx);
hp.type = USING_STRING | USING_SPLIT;
return NewHook(hp, "Circus3");
}
bool CircusFilter(LPVOID data, size_t *size, HookParam *)
{
auto text = reinterpret_cast<LPSTR>(data);
auto len = reinterpret_cast<size_t *>(size);
//ConsoleOutput("debug:Circus: -%.*s-", *len, text);
if (*len <= 1 || cpp_strnstr(text, "\\", *len) || (text[0] == '&' && text[1] == 'n'))
return false;
CharReplacer(text, len, '\n', ' ');
return true;
}
bool InsertCircusHook4()
{
/*
* Sample games:
* https://vndb.org/r46909
*/
const BYTE bytes[] = {
0x83, 0xF8, 0xFF, // cmp eax,-01 << hook here
0x0F, 0x84, XX4, // je DST.exe+1BCF0
0x8B, 0x0D, XX4 // mov ecx,[DST.exe+A41F0]
};
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
return false;
}
HookParam hp;
hp.address = addr;
hp.offset=get_reg(regs::edx);
hp.split =get_stack(4); //arg4
hp.padding = 0x40;
hp.type = USING_STRING | USING_SPLIT;
hp.filter_fun = CircusFilter;
return NewHook(hp, "Circus4");
}
bool Circus2::attach_function() {
bool ch2=InsertCircusHook2();
bool _1= ch2||c2();
bool _2=ch2|| InsertCircusHook3() || InsertCircusHook4();
bool embed=ScenarioHook::attach(processStartAddress,processStopAddress);
return _1||embed||_2;
}