mirror of
https://github.com/HIllya51/LunaHook.git
synced 2024-12-27 13:44:11 +08:00
851 lines
31 KiB
C++
851 lines
31 KiB
C++
#include "CatSystem.h"
|
||
// jichi 5/10/2014
|
||
// See also: http://bbs.sumisora.org/read.php?tid=11044704&fpage=2
|
||
//
|
||
// Old engine: グリザイアの迷宮
|
||
// 0053cc4e cc int3
|
||
// 0053cc4f cc int3
|
||
// 0053cc50 6a ff push -0x1 ; jichi: hook here
|
||
// 0053cc52 68 6b486000 push .0060486b
|
||
// 0053cc57 64:a1 00000000 mov eax,dword ptr fs:[0]
|
||
// 0053cc5d 50 push eax
|
||
// 0053cc5e 81ec 24020000 sub esp,0x224
|
||
// 0053cc64 a1 f8647600 mov eax,dword ptr ds:[0x7664f8]
|
||
// 0053cc69 33c4 xor eax,esp
|
||
// 0053cc6b 898424 20020000 mov dword ptr ss:[esp+0x220],eax
|
||
// 0053cc72 53 push ebx
|
||
// 0053cc73 55 push ebp
|
||
// 0053cc74 56 push esi
|
||
// 0053cc75 57 push edi
|
||
//
|
||
// Stack:
|
||
// 0544e974 0053d593 return to .0053d593 from .0053cc50
|
||
// 0544e978 045cc820
|
||
// 0544e97c 00008dc5 : jichi: text
|
||
// 0544e980 00000016
|
||
// 0544e984 0452f2e4
|
||
// 0544e988 00000000
|
||
// 0544e98c 00000001
|
||
// 0544e990 0544ea94
|
||
// 0544e994 04513840
|
||
// 0544e998 0452f2b8
|
||
// 0544e99c 04577638
|
||
// 0544e9a0 04620450
|
||
// 0544e9a4 00000080
|
||
// 0544e9a8 00000080
|
||
// 0544e9ac 004914f3 return to .004914f3 from .0055c692
|
||
//
|
||
// Registers:
|
||
// edx 0
|
||
// ebx 00000016
|
||
//
|
||
//
|
||
// New engine: イノセントガール
|
||
// Stack:
|
||
// 051ae508 0054e9d1 return to .0054e9d1 from .0054e310
|
||
// 051ae50c 04361650
|
||
// 051ae510 00008ca9 ; jichi: text
|
||
// 051ae514 0000001a
|
||
// 051ae518 04343864
|
||
// 051ae51c 00000000
|
||
// 051ae520 00000001
|
||
// 051ae524 051ae62c
|
||
// 051ae528 041edc20
|
||
// 051ae52c 04343830
|
||
// 051ae530 0434a8b0
|
||
// 051ae534 0434a7f0
|
||
// 051ae538 00000080
|
||
// 051ae53c 00000080
|
||
// 051ae540 3f560000
|
||
// 051ae544 437f8000
|
||
// 051ae548 4433e000
|
||
// 051ae54c 16f60c00
|
||
// 051ae550 051ae650
|
||
// 051ae554 042c4c20
|
||
// 051ae558 0000002c
|
||
// 051ae55c 00439bc5 return to .00439bc5 from .0043af60
|
||
//
|
||
// Registers & stack:
|
||
// Scenario:
|
||
// eax 04361650
|
||
// ecx 04357640
|
||
// edx 04343864
|
||
// ebx 0000001a
|
||
// esp 051ae508
|
||
// ebp 00008169
|
||
// esi 04357640
|
||
// edi 051ae62c
|
||
// eip 0054e310 .0054e310
|
||
//
|
||
// 051ae508 0054e9d1 return to .0054e9d1 from .0054e310
|
||
// 051ae50c 04361650
|
||
// 051ae510 00008169
|
||
// 051ae514 0000001a
|
||
// 051ae518 04343864
|
||
// 051ae51c 00000000
|
||
// 051ae520 00000001
|
||
// 051ae524 051ae62c
|
||
// 051ae528 041edc20
|
||
// 051ae52c 04343830
|
||
// 051ae530 0434a8b0
|
||
// 051ae534 0434a7f0
|
||
// 051ae538 00000080
|
||
// 051ae53c 00000080
|
||
// 051ae540 3f560000
|
||
// 051ae544 437f8000
|
||
// 051ae548 4433e000
|
||
// 051ae54c 16f60c00
|
||
// 051ae550 051ae650
|
||
// 051ae554 042c4c20
|
||
// 051ae558 0000002c
|
||
//
|
||
// Name:
|
||
//
|
||
// eax 04362430
|
||
// ecx 17025230
|
||
// edx 0430b6e4
|
||
// ebx 0000001a
|
||
// esp 051ae508
|
||
// ebp 00008179
|
||
// esi 17025230
|
||
// edi 051ae62c
|
||
// eip 0054e310 .0054e310
|
||
//
|
||
// 051ae508 0054e9d1 return to .0054e9d1 from .0054e310
|
||
// 051ae50c 04362430
|
||
// 051ae510 00008179
|
||
// 051ae514 0000001a
|
||
// 051ae518 0430b6e4
|
||
// 051ae51c 00000000
|
||
// 051ae520 00000001
|
||
// 051ae524 051ae62c
|
||
// 051ae528 041edae0
|
||
// 051ae52c 0430b6b0
|
||
// 051ae530 0434a790
|
||
// 051ae534 0434a910
|
||
// 051ae538 00000080
|
||
// 051ae53c 00000080
|
||
// 051ae540 3efa0000
|
||
// 051ae544 4483f000
|
||
// 051ae548 44322000
|
||
// 051ae54c 16f60aa0
|
||
// 051ae550 051ae650
|
||
// 051ae554 042c4c20
|
||
// 051ae558 0000002c
|
||
|
||
static void SpecialHookCatSystem3(hook_stack *stack, HookParam *, uintptr_t *data, uintptr_t *split, size_t *len)
|
||
{
|
||
// DWORD ch = *data = *(DWORD *)(esp_base + hp->offset); // arg2
|
||
DWORD ch = *data = stack->stack[2];
|
||
*len = LeadByteTable[(ch >> 8) & 0xff]; // CODEC_ANSI_BE
|
||
*split = stack->edx >> 16;
|
||
}
|
||
|
||
bool InsertCatSystemHook()
|
||
{
|
||
// DWORD search=0x95EB60F;
|
||
// DWORD j,i=SearchPattern(processStartAddress,processStopAddress-processStartAddress,&search,4);
|
||
// if (i==0) return;
|
||
// i+=processStartAddress;
|
||
// for (j=i-0x100;i>j;i--)
|
||
// if (*(DWORD*)i==0xcccccccc) break;
|
||
// if (i==j) return;
|
||
// hp.address=i+4;
|
||
// hp.offset=get_reg(regs::eax);
|
||
// hp.index=4;
|
||
// hp.type =CODEC_ANSI_BE|DATA_INDIRECT|USING_SPLIT|SPLIT_INDIRECT;
|
||
// hp.length_offset=1;
|
||
|
||
enum
|
||
{
|
||
beg = 0xff6acccc
|
||
}; // jichi 7/12/2014: beginning of the function
|
||
enum
|
||
{
|
||
addr_offset = 2
|
||
}; // skip two leading 0xcc
|
||
ULONG addr = MemDbg::findCallerAddress((ULONG)::GetTextMetricsA, beg, processStartAddress, processStopAddress);
|
||
if (!addr)
|
||
{
|
||
ConsoleOutput("CatSystem2: pattern not exist");
|
||
return false;
|
||
}
|
||
|
||
HookParam hp;
|
||
hp.address = addr + addr_offset; // skip 1 push?
|
||
hp.offset = get_stack(2); // text character is in arg2
|
||
|
||
// jichi 12/23/2014: Modify split for new catsystem
|
||
bool newEngine = Util::CheckFile(L"cs2conf.dll");
|
||
if (newEngine)
|
||
{
|
||
// hp.text_fun = SpecialHookCatSystem3; // type not needed
|
||
// NewHook(hp, "CatSystem3");
|
||
// ConsoleOutput("INSERT CatSystem3");
|
||
hp.type = CODEC_ANSI_BE | USING_SPLIT;
|
||
hp.split = get_reg(regs::esi);
|
||
ConsoleOutput("INSERT CatSystem3new");
|
||
return NewHook(hp, "CatSystem3new");
|
||
}
|
||
else
|
||
{
|
||
hp.type = CODEC_ANSI_BE | USING_SPLIT;
|
||
hp.split = get_reg(regs::edx);
|
||
ConsoleOutput("INSERT CatSystem2");
|
||
return NewHook(hp, "CatSystem2");
|
||
}
|
||
}
|
||
bool InsertCatSystem2Hook()
|
||
{
|
||
|
||
/*
|
||
* Sample games:
|
||
* https://vndb.org/v26987
|
||
*/
|
||
const BYTE bytes[] = {
|
||
0x38, 0x08, // cmp [eax],cl
|
||
0x0F, 0x84, XX4, // je cs2.exe+23E490
|
||
0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, XX4, // nop word ptr [eax+eax+00000000]
|
||
0x4F, // dec edi
|
||
0xC7, 0x85, XX4, XX4, // mov [ebp-000005A0],00000000
|
||
0x33, 0xF6, // xor esi,esi
|
||
0xC7, 0x85, XX4, XX4, // mov [ebp-0000057C],00000000
|
||
0x85, 0xFF // test edi,edi
|
||
};
|
||
|
||
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::eax);
|
||
hp.type = USING_STRING | CODEC_UTF8;
|
||
hp.filter_fun = [](void *data, size_t *len, HookParam *hp)
|
||
{
|
||
static std::regex rx(R"(\[(.+?)/.+\])");
|
||
auto _ = std::regex_replace(std::string((char *)data, *len), rx, "$1");
|
||
return write_string_overwrite(data, len, _);
|
||
};
|
||
return NewHook(hp, "CatSystem2new");
|
||
}
|
||
namespace
|
||
{ // unnamed
|
||
namespace Patch
|
||
{
|
||
|
||
namespace Private
|
||
{
|
||
// String in ecx
|
||
// bool __fastcall isLeadByteChar(const char *s, DWORD edx)
|
||
// bool isLeadByteChar(hook_stack*s,void* data, size_t* len,uintptr_t*role)
|
||
// {
|
||
// auto pc=(CHAR*)s->ecx;
|
||
|
||
// s->eax=(bool)((pc)&&dynsjis::isleadbyte(*pc));
|
||
// return false;
|
||
|
||
// //return dynsjis::isleadstr(s); // no idea why this will cause Grisaia3 to hang
|
||
// //return ::IsDBCSLeadByte(HIBYTE(testChar));
|
||
// }
|
||
bool isLeadByteChar(char *s)
|
||
{
|
||
return s && dynsjis::isleadchar(*s);
|
||
|
||
// return dynsjis::isleadstr(s); // no idea why this will cause Grisaia3 to hang
|
||
// return ::IsDBCSLeadByte(HIBYTE(testChar));
|
||
}
|
||
|
||
} // namespace Private
|
||
|
||
/**
|
||
* Sample game: ゆきこいめると
|
||
*
|
||
* This function is found by searching the following instruction:
|
||
* 00511C8E 3C 81 CMP AL,0x81
|
||
*
|
||
* This function is very similar to that in LC-ScriptEngine.
|
||
*
|
||
* Return 1 if the first byte in arg1 is leading byte else 0.
|
||
*
|
||
* 00511C7C CC INT3
|
||
* 00511C7D CC INT3
|
||
* 00511C7E CC INT3
|
||
* 00511C7F CC INT3
|
||
* 00511C80 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+0x4]
|
||
* 00511C84 85C9 TEST ECX,ECX
|
||
* 00511C86 74 2F JE SHORT .00511CB7
|
||
* 00511C88 8A01 MOV AL,BYTE PTR DS:[ECX]
|
||
* 00511C8A 84C0 TEST AL,AL
|
||
* 00511C8C 74 29 JE SHORT .00511CB7
|
||
* 00511C8E 3C 81 CMP AL,0x81
|
||
* 00511C90 72 04 JB SHORT .00511C96
|
||
* 00511C92 3C 9F CMP AL,0x9F
|
||
* 00511C94 76 08 JBE SHORT .00511C9E
|
||
* 00511C96 3C E0 CMP AL,0xE0
|
||
* 00511C98 72 1D JB SHORT .00511CB7
|
||
* 00511C9A 3C EF CMP AL,0xEF
|
||
* 00511C9C 77 19 JA SHORT .00511CB7
|
||
* 00511C9E 8A41 01 MOV AL,BYTE PTR DS:[ECX+0x1]
|
||
* 00511CA1 3C 40 CMP AL,0x40
|
||
* 00511CA3 72 04 JB SHORT .00511CA9
|
||
* 00511CA5 3C 7E CMP AL,0x7E
|
||
* 00511CA7 76 08 JBE SHORT .00511CB1
|
||
* 00511CA9 3C 80 CMP AL,0x80
|
||
* 00511CAB 72 0A JB SHORT .00511CB7
|
||
* 00511CAD 3C FC CMP AL,0xFC
|
||
* 00511CAF 77 06 JA SHORT .00511CB7
|
||
* 00511CB1 B8 01000000 MOV EAX,0x1
|
||
* 00511CB6 C3 RETN
|
||
* 00511CB7 33C0 XOR EAX,EAX
|
||
* 00511CB9 C3 RETN
|
||
* 00511CBA CC INT3
|
||
* 00511CBB CC INT3
|
||
* 00511CBC CC INT3
|
||
* 00511CBD CC INT3
|
||
*
|
||
* Sample game: Grisaia3 グリザイアの楽園
|
||
* 0050747F CC INT3
|
||
* 00507480 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+0x4] ; jichi: text in arg1
|
||
* 00507484 85C9 TEST ECX,ECX
|
||
* 00507486 74 2F JE SHORT .005074B7
|
||
* 00507488 8A01 MOV AL,BYTE PTR DS:[ECX]
|
||
* 0050748A 84C0 TEST AL,AL
|
||
* 0050748C 74 29 JE SHORT .005074B7
|
||
* 0050748E 3C 81 CMP AL,0x81
|
||
* 00507490 72 04 JB SHORT .00507496
|
||
* 00507492 3C 9F CMP AL,0x9F
|
||
* 00507494 76 08 JBE SHORT .0050749E
|
||
* 00507496 3C E0 CMP AL,0xE0
|
||
* 00507498 72 1D JB SHORT .005074B7
|
||
* 0050749A 3C EF CMP AL,0xEF
|
||
* 0050749C 77 19 JA SHORT .005074B7
|
||
* 0050749E 8A41 01 MOV AL,BYTE PTR DS:[ECX+0x1]
|
||
* 005074A1 3C 40 CMP AL,0x40
|
||
* 005074A3 72 04 JB SHORT .005074A9
|
||
* 005074A5 3C 7E CMP AL,0x7E
|
||
* 005074A7 76 08 JBE SHORT .005074B1
|
||
* 005074A9 3C 80 CMP AL,0x80
|
||
* 005074AB 72 0A JB SHORT .005074B7
|
||
* 005074AD 3C FC CMP AL,0xFC
|
||
* 005074AF 77 06 JA SHORT .005074B7
|
||
* 005074B1 B8 01000000 MOV EAX,0x1
|
||
* 005074B6 C3 RETN
|
||
* 005074B7 33C0 XOR EAX,EAX
|
||
* 005074B9 C3 RETN
|
||
* 005074BA CC INT3
|
||
* 005074BB CC INT3
|
||
* 005074BC CC INT3
|
||
* 005074BD CC INT3
|
||
*
|
||
* Sample game: Grisaia1 グリザイアの果実
|
||
* 0041488A CC INT3
|
||
* 0041488B CC INT3
|
||
* 0041488C CC INT3
|
||
* 0041488D CC INT3
|
||
* 0041488E CC INT3
|
||
* 0041488F CC INT3
|
||
* 00414890 85C9 TEST ECX,ECX ; jichi: text in ecx
|
||
* 00414892 74 2F JE SHORT Grisaia.004148C3
|
||
* 00414894 8A01 MOV AL,BYTE PTR DS:[ECX]
|
||
* 00414896 84C0 TEST AL,AL
|
||
* 00414898 74 29 JE SHORT Grisaia.004148C3
|
||
* 0041489A 3C 81 CMP AL,0x81
|
||
* 0041489C 72 04 JB SHORT Grisaia.004148A2
|
||
* 0041489E 3C 9F CMP AL,0x9F
|
||
* 004148A0 76 08 JBE SHORT Grisaia.004148AA
|
||
* 004148A2 3C E0 CMP AL,0xE0
|
||
* 004148A4 72 1D JB SHORT Grisaia.004148C3
|
||
* 004148A6 3C EF CMP AL,0xEF
|
||
* 004148A8 77 19 JA SHORT Grisaia.004148C3
|
||
* 004148AA 8A41 01 MOV AL,BYTE PTR DS:[ECX+0x1]
|
||
* 004148AD 3C 40 CMP AL,0x40
|
||
* 004148AF 72 04 JB SHORT Grisaia.004148B5
|
||
* 004148B1 3C 7E CMP AL,0x7E
|
||
* 004148B3 76 08 JBE SHORT Grisaia.004148BD
|
||
* 004148B5 3C 80 CMP AL,0x80
|
||
* 004148B7 72 0A JB SHORT Grisaia.004148C3
|
||
* 004148B9 3C FC CMP AL,0xFC
|
||
* 004148BB 77 06 JA SHORT Grisaia.004148C3
|
||
* 004148BD B8 01000000 MOV EAX,0x1
|
||
* 004148C2 C3 RETN
|
||
* 004148C3 33C0 XOR EAX,EAX
|
||
* 004148C5 C3 RETN
|
||
* 004148C6 CC INT3
|
||
* 004148C7 CC INT3
|
||
* 004148C8 CC INT3
|
||
*/
|
||
|
||
ULONG patchEncoding(ULONG startAddress, ULONG stopAddress)
|
||
{
|
||
const uint8_t bytes[] = {
|
||
0x74, 0x29, // 00511c8c 74 29 je short .00511cb7
|
||
0x3c, 0x81 // 00511c8e 3c 81 cmp al,0x81
|
||
};
|
||
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress);
|
||
if (!addr)
|
||
return false;
|
||
addr = MemDbg::findEnclosingAlignedFunction(addr);
|
||
if (!addr)
|
||
return false;
|
||
for (auto p = addr; p - addr < 20; p += ::disasm((LPCVOID)p))
|
||
if (*(WORD *)p == 0xc985) // 00414890 85C9 TEST ECX,ECX ; jichi: text in ecx
|
||
return addr; // winhook::replace_fun(p, (ULONG)Private::isLeadByteChar);
|
||
return 0;
|
||
}
|
||
|
||
} // namespace Patch
|
||
|
||
/**
|
||
* Sample game: ゆきこいめると
|
||
*
|
||
* Example prefix to skip:
|
||
* 03751294 81 40 5C 70 63 81 75 83 7B 83 4E 82 CC 8E AF 82 \pc「ボクの識・
|
||
*
|
||
* 033CF370 5C 6E 81 40 5C 70 63 8C 4A 82 E8 95 D4 82 BB 82 \n \pc繰り返そ・
|
||
* 033CF380 A4 81 41 96 7B 93 96 82 C9 81 41 82 B1 82 CC 8B 、、本当に、この・
|
||
* 033CF390 47 90 DF 82 CD 81 41 83 8D 83 4E 82 C8 82 B1 82 G節は、ロクなこ・
|
||
* 033CF3A0 C6 82 AA 82 C8 82 A2 81 42 00 AA 82 C8 82 A2 81 ニがない。.ェない・
|
||
* 033CF3B0 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B...............
|
||
* 033CF3C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||
* 033CF3D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||
* 033CF3E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||
* 033CF3F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||
* 033CF400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||
*
|
||
* Sample choice texts:
|
||
*
|
||
* str 155 選択肢
|
||
*
|
||
* 0 op01 最初から始める
|
||
*
|
||
* 1 select_go_tar たるひ初キスシーンを見る
|
||
*/
|
||
template <typename strT>
|
||
strT ltrim(strT text)
|
||
{
|
||
strT lastText = nullptr;
|
||
while (*text && text != lastText)
|
||
{
|
||
lastText = text;
|
||
if (text[0] == 0x20)
|
||
text++;
|
||
if ((UINT8)text[0] == 0x81 && (UINT8)text[1] == 0x40) // skip space \u3000 (0x8140 in sjis)
|
||
text += 2;
|
||
if (text[0] == '\\')
|
||
{
|
||
text++;
|
||
while (::islower(text[0]) || text[0] == '@')
|
||
text++;
|
||
}
|
||
}
|
||
while ((signed char)text[0] > 0 && text[0] != '[') // skip all leading ascii characters except "[" needed for ruby
|
||
text++;
|
||
return text;
|
||
}
|
||
|
||
// Remove trailing '\@'
|
||
size_t rtrim(LPCSTR text)
|
||
{
|
||
size_t size = ::strlen(text);
|
||
while (size >= 2 && text[size - 2] == '\\' && (UINT8)text[size - 1] <= 127)
|
||
size -= 2;
|
||
return size;
|
||
}
|
||
|
||
namespace ScenarioHook
|
||
{
|
||
namespace Private
|
||
{
|
||
|
||
bool isOtherText(LPCSTR text)
|
||
{
|
||
/* Sample game: ゆきこいめると */
|
||
return ::strcmp(text, "\x91\x49\x91\xf0\x8e\x88") == 0; /* 選択肢 */
|
||
}
|
||
|
||
/**
|
||
* Sample game: 果つることなき未来ヨリ
|
||
*
|
||
* Sample ecx:
|
||
*
|
||
* 03283A88 24 00 CD 02 76 16 02 00 24 00 CD 02 58 00 CD 02 $.ヘv.$.ヘX.ヘ
|
||
* 03283A98 BD 2D 01 00 1C 1C 49 03 14 65 06 00 14 65 06 00 ス-.Ie.e.
|
||
* this is ID, this is the same ID: 0x066514
|
||
* 03283AA8 80 64 06 00 20 8C 06 00 24 00 6C 0D 00 00 10 00 d. ・.$.l....
|
||
* this is ID: 0x066480
|
||
* 03283AB8 C8 F1 C2 00 21 00 00 00 48 A9 75 00 E8 A9 96 00 ネ.!...Hゥu.隧・
|
||
* 03283AC8 00 00 00 00 48 80 4F 03 00 00 00 00 CC CC CC CC ....HO....フフフフ
|
||
* 03283AD8 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC フフフフフフフフフフフフフフフフ
|
||
*/
|
||
// struct ClassArgument // for ecx
|
||
//{
|
||
// DWORD unknown[7],
|
||
// split1, // 0x20 - 9
|
||
// split2; // 0x20
|
||
// // split1 - split2 is always 0x94
|
||
// DWORD split() const { return split1 - split2; } //
|
||
// };
|
||
|
||
static bool containsNamePunct_(const char *text)
|
||
{
|
||
static const char *puncts[] = {
|
||
"\x81\x41" /* 、 */
|
||
,
|
||
"\x81\x43" /* , */
|
||
,
|
||
"\x81\x42" /* 。 */
|
||
//, "\x81\x48" /* ? */
|
||
,
|
||
"\x81\x49" /* ! */
|
||
,
|
||
"\x81\x63" /* … */
|
||
,
|
||
"\x81\x64" /* ‥ */
|
||
|
||
//, "\x81\x79" /* 【 */
|
||
//, "\x81\x7a" /* 】 */
|
||
,
|
||
"\x81\x75" /* 「 */
|
||
,
|
||
"\x81\x76" /* 」 */
|
||
,
|
||
"\x81\x77" /* 『 */
|
||
,
|
||
"\x81\x78" /* 』 */
|
||
//, "\x81\x69" /* ( */
|
||
//, "\x81\x6a" /* ) */
|
||
//, "\x81\x6f" /* { */
|
||
//, "\x81\x70" /* } */
|
||
//, "\x81\x71" /* 〈 */
|
||
//, "\x81\x72" /* 〉 */
|
||
,
|
||
"\x81\x6d" /* [ */
|
||
,
|
||
"\x81\x6e" /* ] */
|
||
//, "\x81\x83", /* < */
|
||
//, "\x81\x84", /* > */
|
||
,
|
||
"\x81\x65" /* ‘ */
|
||
,
|
||
"\x81\x66" /* ’ */
|
||
,
|
||
"\x81\x67" /* “ */
|
||
,
|
||
"\x81\x68" /* ” */
|
||
};
|
||
for (size_t i = 0; i < sizeof(puncts) / sizeof(*puncts); i++)
|
||
if (::strstr(text, puncts[i]))
|
||
return true;
|
||
|
||
if (::strstr(text, "\x81\x48") /* ? */
|
||
&& !::strstr(text, "\x81\x48\x81\x48\x81\x48")) /* ??? */
|
||
return true;
|
||
return false;
|
||
}
|
||
bool guessIsNameText(const char *text, size_t size)
|
||
{
|
||
enum
|
||
{
|
||
MaximumNameSize = 0x10
|
||
};
|
||
if (!size)
|
||
size = ::strlen(text);
|
||
return size < MaximumNameSize && !containsNamePunct_(text);
|
||
}
|
||
LPSTR trimmedText;
|
||
size_t trimmedSize;
|
||
bool hookBefore(hook_stack *s, void *data, size_t *len, uintptr_t *role)
|
||
{
|
||
// static std::unordered_set<uint64_t> hashes_;
|
||
auto text = (LPSTR)s->eax; // arg1
|
||
if (!text || !*text || all_ascii(text))
|
||
return false;
|
||
// Alternatively, if do not skip ascii chars, edx is always 0x4ef74 for Japanese texts
|
||
// if (s->edx != 0x4ef74)
|
||
// return true;
|
||
trimmedText = ltrim(text);
|
||
if (!trimmedText || !*trimmedText)
|
||
return false;
|
||
trimmedSize = rtrim(trimmedText);
|
||
*role = Engine::OtherRole;
|
||
// DOUT(QString::fromLocal8Bit((LPCSTR)s->esi));
|
||
// auto splitText = (LPCSTR)s->esi;
|
||
// if (::strcmp(splitText, "MES_SETNAME")) // This is for scenario text with voice
|
||
// if (::strcmp(splitText, "MES_SETFACE"))
|
||
// if (::strcmp(splitText, "pcm")) // first scenario or history without text
|
||
// return true;
|
||
// auto retaddr = s->stack[1]; // caller
|
||
// auto retaddr = s->stack[13]; // parent caller
|
||
// auto split = *(DWORD *)s->esi;
|
||
// auto split = s->esi - s->eax;
|
||
// DOUT(split);
|
||
// auto self = (ClassArgument *)s->ecx;
|
||
// auto split = self->split();
|
||
// enum { sig = 0 };
|
||
auto self = s->ecx;
|
||
if (!Engine::isAddressWritable(self)) // old cs2 game such as Grisaia
|
||
self = s->stack[2]; // arg1
|
||
ULONG groupId = self;
|
||
if (Engine::isAddressWritable(self))
|
||
groupId = *(DWORD *)(self + 0x20);
|
||
{
|
||
static ULONG minimumGroupId_ = -1; // I assume scenario thread to have minimum groupId
|
||
|
||
// if (session_.addText(groupId, Engine::hashCharArray(text))) {
|
||
if (groupId <= minimumGroupId_)
|
||
{
|
||
minimumGroupId_ = groupId;
|
||
|
||
*role = Engine::ScenarioRole;
|
||
if (isOtherText(text))
|
||
*role = Engine::OtherRole;
|
||
else if (::isdigit(text[0]))
|
||
*role = Engine::ChoiceRole;
|
||
else if (trimmedText == text && !trimmedText[trimmedSize] // no prefix and suffix
|
||
&& guessIsNameText(trimmedText, trimmedSize))
|
||
*role = Engine::NameRole;
|
||
}
|
||
}
|
||
|
||
std::string oldData(trimmedText, trimmedSize);
|
||
|
||
return write_string_overwrite(data, len, oldData);
|
||
}
|
||
void hookafter(hook_stack *s, void *data, size_t len)
|
||
{
|
||
|
||
auto newData = std::string((char *)data, len);
|
||
if (trimmedText[trimmedSize])
|
||
newData.append(trimmedText + trimmedSize);
|
||
::strcpy(trimmedText, newData.c_str());
|
||
}
|
||
} // namespace Private
|
||
|
||
/**
|
||
* Sample game: 果つることなき未来ヨリ
|
||
*
|
||
* Debugging message:
|
||
* - Hook to GetGlyphOutlineA
|
||
* - Find "MES_SHOW" address on the stack
|
||
* Alternatively, find the address of "fes.int/flow.fes" immediately after the game is launched
|
||
* - Use hardware breakpoint to find out when "MES_SHOW" is overridden
|
||
* Only stop when text is written by valid scenario text.
|
||
*
|
||
* 00503ADE CC INT3
|
||
* 00503ADF CC INT3
|
||
* 00503AE0 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+0xC]
|
||
* 00503AE4 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+0x4]
|
||
* 00503AE8 56 PUSH ESI
|
||
* 00503AE9 FF30 PUSH DWORD PTR DS:[EAX]
|
||
* 00503AEB E8 102F1600 CALL Hatsumir.00666A00 ; jichi: text in eax after this call
|
||
* 00503AF0 BE 18058900 MOV ESI,Hatsumir.00890518 ; ASCII "fes.int/flow.fes"
|
||
* 00503AF5 8BC8 MOV ECX,EAX ; jichi: esi is the target location
|
||
* 00503AF7 2BF0 SUB ESI,EAX
|
||
* 00503AF9 8DA424 00000000 LEA ESP,DWORD PTR SS:[ESP]
|
||
* 00503B00 8A11 MOV DL,BYTE PTR DS:[ECX]
|
||
* 00503B02 8D49 01 LEA ECX,DWORD PTR DS:[ECX+0x1]
|
||
* 00503B05 88540E FF MOV BYTE PTR DS:[ESI+ECX-0x1],DL ; jichi: target location modified here
|
||
* 00503B09 84D2 TEST DL,DL
|
||
* 00503B0B ^75 F3 JNZ SHORT Hatsumir.00503B00
|
||
* 00503B0D 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+0xC]
|
||
* 00503B11 50 PUSH EAX
|
||
* 00503B12 68 18058900 PUSH Hatsumir.00890518 ; ASCII "fes.int/flow.fes"
|
||
* 00503B17 8B89 B4000000 MOV ECX,DWORD PTR DS:[ECX+0xB4]
|
||
* 00503B1D E8 EE030B00 CALL Hatsumir.005B3F10
|
||
* 00503B22 B8 02000000 MOV EAX,0x2
|
||
* 00503B27 5E POP ESI
|
||
* 00503B28 C2 1000 RETN 0x10
|
||
* 00503B2B CC INT3
|
||
* 00503B2C CC INT3
|
||
* 00503B2D CC INT3
|
||
* 00503B2E CC INT3
|
||
*
|
||
* EAX 0353B1A0 ; jichi: text here
|
||
* ECX 00D86D08
|
||
* EDX 0004EF74
|
||
* EBX 00012DB2
|
||
* ESP 0525EBAC
|
||
* EBP 0525ED6C
|
||
* ESI 00D86D08
|
||
* EDI 00000000
|
||
* EIP 00503AF0 Hatsumir.00503AF0
|
||
*
|
||
* 0525EBAC 00D86D08
|
||
* 0525EBB0 0066998E RETURN to Hatsumir.0066998E
|
||
* 0525EBB4 00D86D08
|
||
* 0525EBB8 00B16188
|
||
* 0525EBBC 035527D8
|
||
* 0525EBC0 0525EBE4
|
||
* 0525EBC4 00B16188
|
||
* 0525EBC8 00D86D08
|
||
* 0525EBCC 0525F62B ASCII "ript.kcs"
|
||
* 0525EBD0 00000004
|
||
* 0525EBD4 00000116
|
||
* 0525EBD8 00000003
|
||
* 0525EBDC 00000003
|
||
* 0525EBE0 00665C08 RETURN to Hatsumir.00665C08
|
||
* 0525EBE4 CCCCCCCC
|
||
* 0525EBE8 0525F620 ASCII "kcs.int/sscript.kcs"
|
||
* 0525EBEC 00694D94 Hatsumir.00694D94
|
||
* 0525EBF0 004B278F RETURN to Hatsumir.004B278F from Hatsumir.00666CA0
|
||
* 0525EBF4 B3307379
|
||
* 0525EBF8 0525ED04
|
||
* 0525EBFC 00B16188
|
||
* 0525EC00 0525ED04
|
||
* 0525EC04 00B16188
|
||
* 0525EC08 00CC5440
|
||
* 0525EC0C 02368938
|
||
* 0525EC10 0069448C ASCII "%s/%s"
|
||
* 0525EC14 00B45B18 ASCII "kcs.int"
|
||
* 0525EC18 00000001
|
||
* 0525EC1C 023741E0
|
||
* 0525EC20 0000000A
|
||
* 0525EC24 0049DBB3 RETURN to Hatsumir.0049DBB3 from Hatsumir.00605A84
|
||
* 0525EC28 72637373
|
||
* 0525EC2C 2E747069
|
||
* 0525EC30 0073636B Hatsumir.0073636B
|
||
* 0525EC34 0525ED04
|
||
* 0525EC38 0053ECDE RETURN to Hatsumir.0053ECDE from Hatsumir.004970C0
|
||
* 0525EC3C 0525EC80
|
||
* 0525EC40 023D9FB8
|
||
*
|
||
* Alternative ruby hook:
|
||
* It will hook to the beginning of the Ruby processing function, which is not better than the current approach.
|
||
* http://lab.aralgood.com/index.php?mid=board_lecture&search_target=title_content&search_keyword=CS&document_srl=1993027
|
||
*
|
||
* Sample game: Grisaia3 グリザイアの楽園
|
||
*
|
||
* 004B00CB CC INT3
|
||
* 004B00CC CC INT3
|
||
* 004B00CD CC INT3
|
||
* 004B00CE CC INT3
|
||
* 004B00CF CC INT3
|
||
* 004B00D0 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+0xC]
|
||
* 004B00D4 8B08 MOV ECX,DWORD PTR DS:[EAX]
|
||
* 004B00D6 56 PUSH ESI
|
||
* 004B00D7 51 PUSH ECX
|
||
* 004B00D8 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+0xC]
|
||
* 004B00DC E8 7F191300 CALL .005E1A60
|
||
* 004B00E1 BE D0E87B00 MOV ESI,.007BE8D0
|
||
* 004B00E6 8BC8 MOV ECX,EAX
|
||
* 004B00E8 2BF0 SUB ESI,EAX
|
||
* 004B00EA 8D9B 00000000 LEA EBX,DWORD PTR DS:[EBX]
|
||
* 004B00F0 8A11 MOV DL,BYTE PTR DS:[ECX]
|
||
* 004B00F2 88140E MOV BYTE PTR DS:[ESI+ECX],DL
|
||
* 004B00F5 41 INC ECX
|
||
* 004B00F6 84D2 TEST DL,DL
|
||
* 004B00F8 ^75 F6 JNZ SHORT .004B00F0
|
||
* 004B00FA 8B5424 0C MOV EDX,DWORD PTR SS:[ESP+0xC]
|
||
* 004B00FE 8B8A B4000000 MOV ECX,DWORD PTR DS:[EDX+0xB4]
|
||
* 004B0104 50 PUSH EAX
|
||
* 004B0105 68 D0E87B00 PUSH .007BE8D0
|
||
* 004B010A E8 818D0600 CALL .00518E90
|
||
* 004B010F B8 02000000 MOV EAX,0x2
|
||
* 004B0114 5E POP ESI
|
||
* 004B0115 C2 1000 RETN 0x10
|
||
* 004B0118 CC INT3
|
||
* 004B0119 CC INT3
|
||
* 004B011A CC INT3
|
||
* 004B011B CC INT3
|
||
* 004B011C CC INT3
|
||
*
|
||
* Sample game: Grisaia1 グリザイアの果実
|
||
* 00498579 CC INT3
|
||
* 0049857A CC INT3
|
||
* 0049857B CC INT3
|
||
* 0049857C CC INT3
|
||
* 0049857D CC INT3
|
||
* 0049857E CC INT3
|
||
* 0049857F CC INT3
|
||
* 00498580 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+0xC]
|
||
* 00498584 8B08 MOV ECX,DWORD PTR DS:[EAX] ; jichi: ecx is no longer a pointer
|
||
* 00498586 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+0x4]
|
||
* 0049858A 56 PUSH ESI
|
||
* 0049858B E8 10920500 CALL Grisaia.004F17A0
|
||
* 00498590 BE D89C7600 MOV ESI,Grisaia.00769CD8 ; ASCII "bgm01"
|
||
* 00498595 8BC8 MOV ECX,EAX
|
||
* 00498597 2BF0 SUB ESI,EAX
|
||
* 00498599 8DA424 00000000 LEA ESP,DWORD PTR SS:[ESP]
|
||
* 004985A0 8A11 MOV DL,BYTE PTR DS:[ECX]
|
||
* 004985A2 88140E MOV BYTE PTR DS:[ESI+ECX],DL
|
||
* 004985A5 41 INC ECX
|
||
* 004985A6 84D2 TEST DL,DL
|
||
* 004985A8 ^75 F6 JNZ SHORT Grisaia.004985A0
|
||
* 004985AA 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+0xC]
|
||
* 004985AE 8B91 B4000000 MOV EDX,DWORD PTR DS:[ECX+0xB4]
|
||
* 004985B4 50 PUSH EAX
|
||
* 004985B5 68 D89C7600 PUSH Grisaia.00769CD8 ; ASCII "bgm01"
|
||
* 004985BA 52 PUSH EDX
|
||
* 004985BB E8 701C0600 CALL Grisaia.004FA230
|
||
* 004985C0 B8 02000000 MOV EAX,0x2
|
||
* 004985C5 5E POP ESI
|
||
* 004985C6 C2 1000 RETN 0x10
|
||
* 004985C9 CC INT3
|
||
* 004985CA CC INT3
|
||
* 004985CB CC INT3
|
||
* 004985CC CC INT3
|
||
* 004985CD CC INT3
|
||
*/
|
||
bool attach(ULONG startAddress, ULONG stopAddress, HookParamType code)
|
||
{
|
||
const uint8_t bytes[] = {
|
||
0xe8, XX4, // 004b00dc e8 7f191300 call .005e1a60 ; jichi: hook after here
|
||
0xbe, XX4, // 004b00e1 be d0e87b00 mov esi,.007be8d0
|
||
0x8b, 0xc8, // 004b00e6 8bc8 mov ecx,eax
|
||
0x2b, 0xf0 // 004b00e8 2bf0 sub esi,eax
|
||
// XX2, XX, 0x00,0x00,0x00 // 004b00ea 8d9b 00000000 lea ebx,dword ptr ds:[ebx]
|
||
};
|
||
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress);
|
||
if (addr == 0)
|
||
return false;
|
||
HookParam hp;
|
||
hp.address = addr + 5;
|
||
hp.type = USING_STRING | EMBED_ABLE;
|
||
if (code)
|
||
hp.type |= code;
|
||
else
|
||
hp.type |= EMBED_DYNA_SJIS;
|
||
hp.hook_before = Private::hookBefore;
|
||
hp.hook_after = Private::hookafter;
|
||
hp.hook_font = F_GetGlyphOutlineA;
|
||
hp.filter_fun = [](void *data, size_t *len, HookParam *hp)
|
||
{
|
||
static std::regex rx(R"(\[(.+?)/.+\])");
|
||
auto _ = std::regex_replace(std::string((char *)data, *len), rx, "$1");
|
||
return write_string_overwrite(data, len, _);
|
||
};
|
||
|
||
static ULONG p;
|
||
p = Patch::patchEncoding(startAddress, stopAddress);
|
||
if (p)
|
||
{
|
||
hp.type |= EMBED_DYNA_SJIS;
|
||
hp.hook_font = F_GetGlyphOutlineA;
|
||
patch_fun = []()
|
||
{
|
||
ReplaceFunction((PVOID)p, (PVOID)(ULONG)Patch::Private::isLeadByteChar);
|
||
};
|
||
}
|
||
|
||
return NewHook(hp, "EmbedCS2");
|
||
}
|
||
}
|
||
} // namespace ScenarioHook
|
||
bool CatSystem::attach_function()
|
||
{
|
||
HookParamType code = CODEC_ANSI_LE;
|
||
auto b1 = InsertCatSystemHook();
|
||
if (!b1)
|
||
{
|
||
b1 |= InsertCatSystem2Hook();
|
||
code = CODEC_UTF8;
|
||
}
|
||
auto embed = ScenarioHook::attach(processStartAddress, processStopAddress, code);
|
||
|
||
b1 |= embed;
|
||
return b1;
|
||
} |