#include"Yuris.h" /******************************************************************************************** YU-RIS hook: Becomes common recently. I first encounter this game in Whirlpool games. Problem is name is repeated multiple times. Step out of function call to TextOuA, just before call to this function, there should be a piece of code to calculate the length of the name. This length is 2 for single character name and text, For a usual name this value is greater than 2. ********************************************************************************************/ //bool InsertWhirlpoolHook() // jichi: 12/27/2014: Renamed to YU-RIS static bool InsertYuris1Hook() { //IthBreak(); DWORD entry = Util::FindCallAndEntryBoth((DWORD)TextOutA, processStopAddress - processStartAddress, processStartAddress, 0xec83); //GROWL_DWORD(entry); if (!entry) { ConsoleOutput("YU-RIS: function entry does not exist"); return false; } entry = Util::FindCallAndEntryRel(entry - 4, processStopAddress - processStartAddress, processStartAddress, 0xec83); //GROWL_DWORD(entry); if (!entry) { ConsoleOutput("YU-RIS: function entry does not exist"); return false; } entry = Util::FindCallOrJmpRel(entry - 4,processStopAddress - processStartAddress - 0x10000, processStartAddress + 0x10000, false); DWORD i, t = 0; //GROWL_DWORD(entry); __try { // jichi 12/27/2014 for (i = entry - 4; i > entry - 0x100; i--) if (::IsBadReadPtr((LPCVOID)i, 4)) { // jichi 12/27/2014: might raise in new YU-RIS, 4 = sizeof(DWORD) ConsoleOutput("YU-RIS: do not have read permission"); return false; } else if (*(WORD *)i == 0xc085) { t = *(WORD *)(i + 2); if ((t & 0xff) == 0x76) { t = 4; break; } else if ((t & 0xffff) == 0x860f) { t = 8; break; } } } __except(EXCEPTION_EXECUTE_HANDLER) { ConsoleOutput("YU-RIS: illegal access exception"); return false; } if (i == entry - 0x100) { ConsoleOutput("YU-RIS: pattern not exist"); return false; } //GROWL_DWORD2(i,t); HookParam hp; hp.address = i + t; hp.offset=get_reg(regs::edi); hp.split = get_reg(regs::eax); hp.type = USING_STRING|USING_SPLIT; ConsoleOutput("INSERT YU-RIS"); //GROWL_DWORD(hp.address); return NewHook(hp, "YU-RIS"); } /** jichi 12/27/2014 * * Sample game: [Whirlpool] [150217] 鯨神�ヂ�アスヂ�ラ * Call site of TextOutA. * 00441811 90 nop * 00441812 90 nop * 00441813 90 nop * 00441814 8b4424 04 mov eax,dword ptr ss:[esp+0x4] * 00441818 8b5424 08 mov edx,dword ptr ss:[esp+0x8] * 0044181c 8b4c24 0c mov ecx,dword ptr ss:[esp+0xc] * 00441820 57 push edi * 00441821 56 push esi * 00441822 55 push ebp * 00441823 53 push ebx * 00441824 83ec 50 sub esp,0x50 * 00441827 8bf9 mov edi,ecx * 00441829 897c24 1c mov dword ptr ss:[esp+0x1c],edi * 0044182d 8bda mov ebx,edx * 0044182f 8be8 mov ebp,eax * 00441831 8b349d 603f7b00 mov esi,dword ptr ds:[ebx*4+0x7b3f60] * 00441838 807c24 74 01 cmp byte ptr ss:[esp+0x74],0x1 * 0044183d b9 00000000 mov ecx,0x0 * 00441842 0f94c1 sete cl * 00441845 8d041b lea eax,dword ptr ds:[ebx+ebx] * 00441848 03c3 add eax,ebx * 0044184a 0fafc1 imul eax,ecx * 0044184d 03c3 add eax,ebx * 0044184f 894424 0c mov dword ptr ss:[esp+0xc],eax * 00441853 897424 10 mov dword ptr ss:[esp+0x10],esi * 00441857 8bc3 mov eax,ebx * 00441859 8bd7 mov edx,edi * 0044185b 0fbe4c24 70 movsx ecx,byte ptr ss:[esp+0x70] * 00441860 e8 0c030000 call .00441b71 * 00441865 0fbec8 movsx ecx,al * 00441868 83f9 ff cmp ecx,-0x1 * 0044186b 0f84 db020000 je .00441b4c * 00441871 8bce mov ecx,esi * 00441873 0fafc9 imul ecx,ecx * 00441876 a1 64365d00 mov eax,dword ptr ds:[0x5d3664] * 0044187b 8bf9 mov edi,ecx * 0044187d c1ff 02 sar edi,0x2 * 00441880 c1ef 1d shr edi,0x1d * 00441883 03f9 add edi,ecx * 00441885 c1ff 03 sar edi,0x3 * 00441888 68 ff000000 push 0xff * 0044188d 57 push edi * 0044188e ff3485 70b48300 push dword ptr ds:[eax*4+0x83b470] * 00441895 ff15 a4355d00 call dword ptr ds:[0x5d35a4] ; .00401c88 * 0044189b 83c4 0c add esp,0xc * 0044189e 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664] * 004418a4 ff348d b4b48300 push dword ptr ds:[ecx*4+0x83b4b4] * 004418ab ff348d d4b48300 push dword ptr ds:[ecx*4+0x83b4d4] * 004418b2 ff15 54e05800 call dword ptr ds:[0x58e054] ; gdi32.selectobject * 004418b8 a3 b0b48300 mov dword ptr ds:[0x83b4b0],eax * 004418bd 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664] * 004418c3 ff348d 30b48300 push dword ptr ds:[ecx*4+0x83b430] * 004418ca ff348d d4b48300 push dword ptr ds:[ecx*4+0x83b4d4] * 004418d1 ff15 54e05800 call dword ptr ds:[0x58e054] ; gdi32.selectobject * 004418d7 a3 2cb48300 mov dword ptr ds:[0x83b42c],eax * 004418dc 8b3d 64365d00 mov edi,dword ptr ds:[0x5d3664] * 004418e2 33c9 xor ecx,ecx * 004418e4 880cbd f5b48300 mov byte ptr ds:[edi*4+0x83b4f5],cl * 004418eb 880cbd f6b48300 mov byte ptr ds:[edi*4+0x83b4f6],cl * 004418f2 0fb64d 00 movzx ecx,byte ptr ss:[ebp] * 004418f6 0fb689 a0645b00 movzx ecx,byte ptr ds:[ecx+0x5b64a0] * 004418fd 41 inc ecx * 004418fe 0fbec9 movsx ecx,cl * 00441901 51 push ecx * 00441902 55 push ebp * 00441903 33c9 xor ecx,ecx * 00441905 51 push ecx * 00441906 51 push ecx * 00441907 ff34bd d4b48300 push dword ptr ds:[edi*4+0x83b4d4] * 0044190e ff15 74e05800 call dword ptr ds:[0x58e074] ; gdi32.textouta, jichi: TextOutA here * 00441914 0fb67d 00 movzx edi,byte ptr ss:[ebp] * 00441918 0fb68f a0645b00 movzx ecx,byte ptr ds:[edi+0x5b64a0] * 0044191f 41 inc ecx * 00441920 0fbef9 movsx edi,cl * 00441923 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664] * 00441929 03c9 add ecx,ecx * 0044192b 8d8c09 f4b48300 lea ecx,dword ptr ds:[ecx+ecx+0x83b4f4] * * Runtime stack: The first dword after arguments on the stack seems to be good split value. */ static bool InsertYuris2Hook() { ULONG addr = MemDbg::findCallAddress((ULONG)::TextOutA, processStartAddress, processStopAddress); if (!addr) { ConsoleOutput("YU-RIS2: failed"); return false; } // BOOL TextOut( // _In_ HDC hdc, // _In_ int nXStart, // _In_ int nYStart, // _In_ LPCTSTR lpString, // _In_ int cchString // ); HookParam hp; hp.address = addr; hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; // disable context that will cause thread split hp.offset = get_stack(3); hp.split = get_stack(5); ConsoleOutput("INSERT YU-RIS 2"); return NewHook(hp, "YU-RIS2"); } bool InsertYuris4Hook() { /* * Sample games: * https://vndb.org/v6540 */ bool found = false; const BYTE pattern[] = { 0x52, // 52 push edx 0x68, 0x00, 0x42, 0x5C, 0x00, // 68 00425C00 push euphoria.exe+1C4200 0xFF, 0x15, 0x90, 0x44, 0x7E, 0x00, // FF 15 90447E00 call dword ptr [euphoria.exe+3E4490] 0x83, 0xC4, 0x0C, // 83 C4 0C add esp,0C 0xEB, 0x5F, // EB 5F jmp euphoria.exe+4F4C5 0xFF, 0x35, 0xA4, 0x19, 0x66, 0x00, // FF 35 A4196600 push [euphoria.exe+2619A4] 0x52 // 52 push edx }; enum { addr_offset = 12 }; // distance to the beginning of the function, which is 0x83, 0xC4, 0x0C (add esp,0C) for (auto addr : Util::SearchMemory(pattern, sizeof(pattern), PAGE_EXECUTE, processStartAddress, processStopAddress)) { HookParam hp; hp.address = addr+addr_offset; hp.offset=get_reg(regs::edx); hp.type = USING_STRING ; ConsoleOutput("INSERT YU-RIS 4"); found|=NewHook(hp, "YU-RIS4"); } if (!found) ConsoleOutput("YU-RIS 4: pattern not found"); return found; } bool InsertYuris5Hook() { /* * Sample games: * https://vndb.org/v4037 */ const BYTE bytes[] = { 0x33, 0xD2, // xor edx,edx 0x88, 0x14, 0x0F, // mov [edi+ecx],dl 0xA1, XX4, // mov eax,[exe+2DE630] 0x8B, 0x78, 0x3C, // mov edi,[eax+3C] 0x8B, 0x58, 0x5C, // mov ebx,[eax+5C] 0x88, 0x14, 0x3B // mov [ebx+edi],dl }; enum { addr_offset = 0 }; // distance to the beginning of the function, which is 0x55 (push ebp) 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 + addr_offset; hp.offset=get_reg(regs::ecx); hp.type = USING_STRING | NO_CONTEXT; ConsoleOutput("INSERT YU-RIS 5"); return NewHook(hp, "YU-RIS5"); } static bool Yuris6Filter(LPVOID data, size_t *size, HookParam *) { auto text = reinterpret_cast(data); auto len = reinterpret_cast(size); static std::string prevText; if (prevText.length()==*len && prevText.find(text, 0, *len) != std::string::npos) // Check if the string is present in the previous one return false; prevText.assign(text, *len); // ruby <手水舎/ちょうずや> if (cpp_strnstr(text, "\x81\x83", *len)) { // \x81\x83 -> '<' StringFilterBetween(text, len, "\x81\x5E", 2, "\x81\x84", 2); // \x81\x5E -> '/' , \x81\x84 -> '>' StringFilter(text, len, "\x81\x83", 2); // \x81\x83 -> '<' } // ruby ≪美桜/姉さん≫ else if (cpp_strnstr(text, "\x81\xE1", *len)) { // \x81\xE1 -> '≪' StringFilterBetween(text, len, "\x81\x5E", 2, "\x81\xE2", 2); // \x81\x5E -> '/' , \x81\xE2 -> '≫' StringFilter(text, len, "\x81\xE1", 2); // \x81\xE1 -> '≪' } CharReplacer(text, len, '=', '-'); StringCharReplacer(text, len, "\xEF\xF0", 2, ' '); StringFilter(text, len, "\xEF\xF2", 2); StringFilter(text, len, "\xEF\xF5", 2); StringFilter(text, len, "\x81\x98", 2); return true; } bool InsertYuris6Hook() { /* * work with Windows 11 * Sample games: * https://vndb.org/v40058 * https://vndb.org/v42883 * https://vndb.org/v44092 * https://vndb.org/v21171 * https://vndb.org/r46910 */ const BYTE bytes[] = { 0xE9, XX4, // jmp oshitona01.exe+1B629 << hook here 0xBF, XX4, // mov edi,oshitona01.exe+24EEA0 0x8A, 0x17, // mov dl,[edi] 0x47, // inc edi 0x88, 0x16, // mov [esi],dl 0x46, // inc esi 0x84, 0xD2 // test dl,dl }; enum { addr_offset = 0 }; 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.index = 0x38; hp.filter_fun = Yuris6Filter; hp.type = USING_STRING | NO_CONTEXT | DATA_INDIRECT; ConsoleOutput("INSERT YU-RIS 6"); return NewHook(hp, "YU-RIS6"); } bool yuris7(){ //猫忍えくすはーとSPIN! //夏空あすてりずむ const BYTE bytes[] = { 0x57,0x56,0x55,0x53,0x83,0xec,0x10, 0x8b,0x5c,0x24,0x24, 0x8b,0x15,XX4, 0x8b,0x0c,0x9a, 0xc6,0x41,0x01,0x03, 0x8b,0xc3, 0xe8 }; ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress); if (!addr) return false; HookParam hp; hp.address = addr; hp.offset=get_reg(regs::edx); hp.type = USING_STRING; hp.filter_fun=[](void* data, size_t* len, HookParam* hp){ static std::unordered_setfiltername; auto text=std::string((char*)data,*len); if(*len!=2)return false; // if(text.find("\x81\x45")!=text.npos)return false; // if(text.find("item")!=text.npos)return false; // if(text==std::string("\x81\x48\x81\x48\x81\x48"))return false; if(all_ascii((char*)data,*len))return false; // if(filtername.find(text)!=filtername.end())return false; // std::regex pattern("\x81\x79([^\x81\x7a]+)\x81\x7a"); // std::smatch match; // if(std::regex_search(text, match, pattern)) { // filtername.insert(match[1]); // } return true; }; return NewHook(hp,"yuris7"); } bool yuris8(){ //けもの道☆ガーリッシュスクエア LOVE+PLUS //https://vndb.org/v36773 //codepage 950 const BYTE bytes[] = { 0x8b,XX, 0x8b,0x94,0x24,XX,0,0,0, 0x8b,0x8c,0x24,XX,0,0,0, 0xe8,XX4, 0xeb,XX, 0x8b,XX, 0x8b,0x94,0x24,XX,0,0,0, 0x8b,0x8c,0x24,XX,0,0,0, 0xe8,XX4, }; ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress); if (!addr) return false; HookParam hp; hp.address = addr+sizeof(bytes)-5; hp.type = USING_STRING; hp.offset=get_reg(regs::ecx); hp.filter_fun=[](void* data, size_t* len, HookParam* hp){ auto text=std::string((char*)data,*len); if(std::all_of(text.begin(),text.end(),[](char c){return c=='1'||c=='2'||c=='E';}))return false; return true; }; return NewHook(hp,"yuris8"); } bool InsertYurisHook() { bool ok = InsertYuris1Hook(); ok = InsertYuris2Hook() || ok; ok = InsertYuris4Hook() || ok; ok = InsertYuris5Hook() || ok; ok = InsertYuris6Hook() || ok; ok=yuris7()||ok; ok=yuris8()||ok; return ok; } bool Yuris::attach_function() { return InsertYurisHook(); }