Ren'py 2.x and 3.x engine hook

This commit is contained in:
Blu3train 2023-12-17 18:42:15 +01:00
parent f3fbe04409
commit f316813283
2 changed files with 539 additions and 25 deletions

View File

@ -16945,6 +16945,7 @@ bool InsertAdobeFlash10Hook()
*/ */
bool InsertRenpyHook() bool InsertRenpyHook()
{ {
bool ok = false;
wchar_t python[] = L"python2X.dll", libpython[] = L"libpython2.X.dll"; wchar_t python[] = L"python2X.dll", libpython[] = L"libpython2.X.dll";
for (wchar_t* name : { python, libpython }) for (wchar_t* name : { python, libpython })
{ {
@ -16957,32 +16958,77 @@ bool InsertRenpyHook()
wcscpy_s(spDefault.exportModule, name); wcscpy_s(spDefault.exportModule, name);
HookParam hp = {}; HookParam hp = {};
hp.address = (DWORD)GetProcAddress(module, "PyUnicodeUCS2_Format"); hp.address = (DWORD)GetProcAddress(module, "PyUnicodeUCS2_Format");
if (!hp.address) if (hp.address)
{ {
ConsoleOutput("Textractor: Ren'py failed: failed to find PyUnicodeUCS2_Format"); hp.offset = 4;
return false; hp.index = 0xc;
} hp.length_offset = 0;
hp.offset = 4; //hp.split = pusha_ebx_off - 4;
hp.index = 0xc; hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT/* | USING_SPLIT*/;
hp.length_offset = 0; hp.filter_fun = [](LPVOID data, DWORD *size, HookParam *, BYTE)
//hp.split = pusha_ebx_off - 4; {
hp.text_fun = [](auto, auto, auto, DWORD* data, DWORD* split, DWORD* count) static std::wstring prevText;
auto text = reinterpret_cast<LPWSTR>(data);
auto len = reinterpret_cast<size_t *>(size);
if (cpp_wcsnstr(text, L"%", *len/sizeof(wchar_t)))
return false;
if (cpp_wcsnstr(text, L"{", *len/sizeof(wchar_t))) {
WideStringCharReplacer(text, len, L"{i}", 3, L'\'');
WideStringCharReplacer(text, len, L"{/i}", 4, L'\'');
WideStringFilterBetween(text, len, L"{", 1, L"}", 1);
}
WideStringFilter(text, len, L"^", 2); // remove ^ followed by 1 char
WideCharReplacer(text, len, L'\n', L' ');
if (prevText.length()==*len/sizeof(wchar_t) && prevText.find(text, 0, *len/sizeof(wchar_t))!=std::string::npos) // Check if the string is the same as the previous one
return false;
prevText.assign(text, *len/sizeof(wchar_t));
return true;
};
NewHook(hp, "Ren'py UCS2Format");
ok = true;
}
hp.address = (DWORD)GetProcAddress(module, "PyUnicodeUCS2_Replace");
if (hp.address)
{ {
*data = *(DWORD*)(*data + 0xc); hp.offset = 3 * 4; //arg 3;
*count = wcslen((wchar_t*)*data) * sizeof(wchar_t); hp.index = 0xC;
*split = wcschr((wchar_t*)*data, L'%') == nullptr; hp.length_offset = 0;
}; hp.split = 2 * 4; //arg 2;
hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT/* | USING_SPLIT*/; hp.type = USING_STRING | USING_UNICODE | DATA_INDIRECT | USING_SPLIT;
//hp.filter_fun = [](void* str, auto, auto, auto) { return *(wchar_t*)str != L'%'; }; hp.filter_fun = [](LPVOID data, DWORD *size, HookParam *, BYTE)
NewHook(hp, "Ren'py"); {
return true; static std::wstring prevText;
auto text = reinterpret_cast<LPWSTR>(data);
auto len = reinterpret_cast<size_t *>(size);
if (cpp_wcsnstr(text, L"{fast}", *len/sizeof(wchar_t)))
return false;
if (cpp_wcsnstr(text, L"{", *len/sizeof(wchar_t))) {
WideStringCharReplacer(text, len, L"{i}", 3, L'\'');
WideStringCharReplacer(text, len, L"{/i}", 4, L'\'');
WideStringFilterBetween(text, len, L"{", 1, L"}", 1);
}
WideCharReplacer(text, len, L'\n', L' ');
if (prevText.length()==*len/sizeof(wchar_t) && prevText.find(text, 0, *len/sizeof(wchar_t))!=std::string::npos) // Check if the string is the same as the previous one
return false;
prevText.assign(text, *len/sizeof(wchar_t));
return true;
};
NewHook(hp, "Ren'py UCS2Replace");
ok = true;
}
} }
} }
} }
ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll"); if ( !ok )
return false; ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll");
return ok;
} }
void InsertMonoHook(HMODULE h) void InsertMonoHook(HMODULE h)
{ {
static HMODULE mono = h; static HMODULE mono = h;

View File

@ -6,9 +6,336 @@
#include "mono/funcinfo.h" #include "mono/funcinfo.h"
#include "engine.h" #include "engine.h"
#include "util.h" #include "util.h"
#include "cpputil/cppcstring.h"
// Warning: The offset in ITH has -4 offset comparing to pusha and AGTH
enum pusha_off
{
pusha_rax_off = -0xC,
pusha_rbx_off = -0x14,
pusha_rcx_off = -0x1c,
pusha_rdx_off = -0x24,
pusha_rsp_off = -0x2c,
pusha_rbp_off = -0x34,
pusha_rsi_off = -0x3c,
pusha_rdi_off = -0x44,
pusha_r8_off = -0x4c,
pusha_r9_off = -0x54,
pusha_r10_off = -0x5c,
pusha_r11_off = -0x64,
pusha_r12_off = -0x6c,
pusha_r13_off = -0x74,
pusha_r14_off = -0x7c,
pusha_r15_off = -0x84,
pusha_off = -0x8c // pushad offset
};
#define retof(rsp_base) *(uintptr_t *)(rsp_base) // return address
#define regof(name, rsp_base) *(uintptr_t *)((rsp_base) + pusha_##name##_off - 4)
#define argof(count, rsp_base) *(uintptr_t *)((rsp_base) + 4 * (count)) // starts from 1 instead of 0
enum { VNR_TEXT_CAPACITY = 1500 }; // estimated max number of bytes allowed in VNR, slightly larger than VNR's text limit (1000)
namespace { // unnamed helpers
#define XX2 XX,XX // WORD
#define XX4 XX2,XX2 // DWORD
#define XX8 XX4,XX4 // QWORD
// jichi 8/18/2013: Original maximum relative address in ITH
//enum { MAX_REL_ADDR = 0x200000 };
// jichi 10/1/2013: Increase relative address limit. Certain game engine like Artemis has larger code region
enum : DWORD { MAX_REL_ADDR = 0x00300000 };
static union {
char text_buffer[0x1000];
wchar_t wc_buffer[0x800];
struct { // CodeSection
DWORD base;
DWORD size;
} code_section[0x200];
};
DWORD text_buffer_length;
// 7/29/2014 jichi: I should move these functions to different files
// String utilities
// Return the address of the first non-zero address
LPCSTR reverse_search_begin(const char *s, int maxsize = VNR_TEXT_CAPACITY)
{
if (*s)
for (int i = 0; i < maxsize; i++, s--)
if (!*s)
return s + 1;
return nullptr;
}
bool all_ascii(const char *s, int maxsize = VNR_TEXT_CAPACITY)
{
if (s)
for (int i = 0; i < maxsize && *s; i++, s++)
if ((BYTE)*s > 127) // unsigned char
return false;
return true;
}
bool all_ascii(const wchar_t *s, int maxsize = VNR_TEXT_CAPACITY)
{
if (s)
for (int i = 0; i < maxsize && *s; i++, s++)
if (*s > 127) // unsigned char
return false;
return true;
}
// String filters
void CharReplacer(char *str, size_t *size, char fr, char to)
{
size_t len = *size;
for (size_t i = 0; i < len; i++)
if (str[i] == fr)
str[i] = to;
}
void WideCharReplacer(wchar_t *str, size_t *size, wchar_t fr, wchar_t to)
{
size_t len = *size / 2;
for (size_t i = 0; i < len; i++)
if (str[i] == fr)
str[i] = to;
}
void CharFilter(char *str, size_t *size, char ch)
{
size_t len = *size,
curlen;
for (char *cur = (char *)::memchr(str, ch, len);
(cur && --len && (curlen = len - (cur - str)));
cur = (char *)::memchr(cur, ch, curlen))
::memmove(cur, cur + 1, curlen);
*size = len;
}
void WideCharFilter(wchar_t *str, size_t *size, wchar_t ch)
{
size_t len = *size / 2,
curlen;
for (wchar_t *cur = cpp_wcsnchr(str, ch, len);
(cur && --len && (curlen = len - (cur - str)));
cur = cpp_wcsnchr(cur, ch, curlen))
::memmove(cur, cur + 1, 2 * curlen);
*size = len * 2;
}
void CharsFilter(char *str, size_t *size, const char *chars)
{
size_t len = *size,
curlen;
for (char *cur = cpp_strnpbrk(str, chars, len);
(cur && --len && (curlen = len - (cur - str)));
cur = cpp_strnpbrk(cur, chars, curlen))
::memmove(cur, cur + 1, curlen);
*size = len;
}
void WideCharsFilter(wchar_t *str, size_t *size, const wchar_t *chars)
{
size_t len = *size / 2,
curlen;
for (wchar_t *cur = cpp_wcsnpbrk(str, chars, len);
(cur && --len && (curlen = len - (cur - str)));
cur = cpp_wcsnpbrk(cur, chars, curlen))
::memmove(cur, cur + 1, 2 * curlen);
*size = len * 2;
}
void StringFilter(char *str, size_t *size, const char *remove, size_t removelen)
{
size_t len = *size,
curlen;
for (char *cur = cpp_strnstr(str, remove, len);
(cur && (len -= removelen) && (curlen = len - (cur - str)));
cur = cpp_strnstr(cur, remove, curlen))
::memmove(cur, cur + removelen, curlen);
*size = len;
}
void WideStringFilter(wchar_t *str, size_t *size, const wchar_t *remove, size_t removelen)
{
size_t len = *size / 2,
curlen;
for (wchar_t *cur = cpp_wcsnstr(str, remove, len);
(cur && (len -= removelen) && (curlen = len - (cur - str)));
cur = cpp_wcsnstr(cur, remove, curlen))
::memmove(cur, cur + removelen, 2 * curlen);
*size = len * 2;
}
void StringFilterBetween(char *str, size_t *size, const char *fr, size_t frlen, const char *to, size_t tolen)
{
size_t len = *size,
curlen;
for (char *cur = cpp_strnstr(str, fr, len);
cur;
cur = cpp_strnstr(cur, fr, curlen)) {
curlen = (len - frlen) - (cur - str);
auto end = cpp_strnstr(cur + frlen, to, curlen);
if (!end)
break;
curlen = len - (end - str) - tolen;
::memmove(cur, end + tolen, curlen);
len -= tolen + (end - cur);
}
*size = len;
}
void WideStringFilterBetween(wchar_t *str, size_t *size, const wchar_t *fr, size_t frlen, const wchar_t *to, size_t tolen)
{
size_t len = *size / 2,
curlen;
for (wchar_t *cur = cpp_wcsnstr(str, fr, len);
cur;
cur = cpp_wcsnstr(cur, fr, curlen)) {
curlen = (len - frlen) - (cur - str);
auto end = cpp_wcsnstr(cur + frlen, to, curlen);
if (!end)
break;
curlen = len - (end - str) - tolen;
::memmove(cur, end + tolen, 2 * curlen);
len -= tolen + (end - cur);
}
*size = len * 2;
}
void StringCharReplacer(char *str, size_t *size, const char *src, size_t srclen, char ch)
{
size_t len = *size,
curlen;
for (char *cur = cpp_strnstr(str, src, len);
cur && len;
cur = cpp_strnstr(cur, src, curlen)) {
*cur++ = ch;
len -= srclen - 1;
curlen = len - (cur - str);
if (curlen == 0)
break;
::memmove(cur, cur + srclen - 1, curlen);
}
*size = len;
}
void WideStringCharReplacer(wchar_t *str, size_t *size, const wchar_t *src, size_t srclen, wchar_t ch)
{
size_t len = *size / 2,
curlen;
for (wchar_t *cur = cpp_wcsnstr(str, src, len);
cur && len;
cur = cpp_wcsnstr(cur, src, curlen)) {
*cur++ = ch;
len -= srclen - 1;
curlen = len - (cur - str);
if (curlen == 0)
break;
::memmove(cur, cur + srclen -1, 2 * curlen);
}
*size = len * 2;
}
// NOTE: I assume srclen >= dstlen
void StringReplacer(char *str, size_t *size, const char *src, size_t srclen, const char *dst, size_t dstlen)
{
size_t len = *size,
curlen;
for (char *cur = cpp_strnstr(str, src, len);
cur && len;
cur = cpp_strnstr(cur, src, curlen)) {
::memcpy(cur, dst, dstlen);
cur += dstlen;
len -= srclen - dstlen;
curlen = len - (cur - str);
if (curlen == 0)
break;
if (srclen > dstlen)
::memmove(cur, cur + srclen - dstlen, curlen);
}
*size = len;
}
void WideStringReplacer(wchar_t *str, size_t *size, const wchar_t *src, size_t srclen, const wchar_t *dst, size_t dstlen)
{
size_t len = *size / 2,
curlen;
for (wchar_t *cur = cpp_wcsnstr(str, src, len);
cur && len;
cur = cpp_wcsnstr(cur, src, curlen)) {
::memcpy(cur, dst, 2 * dstlen);
cur += dstlen;
len -= srclen - dstlen;
curlen = len - (cur - str);
if (curlen == 0)
break;
if (srclen > dstlen)
::memmove(cur, cur + srclen - dstlen, 2 * curlen);
}
*size = len * 2;
}
bool NewLineCharFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
{
CharFilter(reinterpret_cast<LPSTR>(data), reinterpret_cast<size_t *>(size),
'\n');
return true;
}
bool NewLineWideCharFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
{
CharFilter(reinterpret_cast<LPSTR>(data), reinterpret_cast<size_t *>(size),
L'\n');
return true;
}
bool NewLineStringFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
{
StringFilter(reinterpret_cast<LPSTR>(data), reinterpret_cast<size_t *>(size),
"\\n", 2);
return true;
}
bool NewLineWideStringFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
{
WideStringFilter(reinterpret_cast<LPWSTR>(data), reinterpret_cast<size_t *>(size),
L"\\n", 2);
return true;
}
bool NewLineCharToSpaceFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
{
CharReplacer(reinterpret_cast<LPSTR>(data), reinterpret_cast<size_t *>(size), '\n', ' ');
return true;
}
bool NewLineWideCharToSpaceFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
{
WideCharReplacer(reinterpret_cast<LPWSTR>(data), reinterpret_cast<size_t *>(size), L'\n', L' ');
return true;
}
// Remove every characters <= 0x1f (i.e. before space ' ') except 0xa and 0xd.
bool IllegalCharsFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
{
CharsFilter(reinterpret_cast<LPSTR>(data), reinterpret_cast<size_t *>(size),
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x12\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f");
return true;
}
bool IllegalWideCharsFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
{
WideCharsFilter(reinterpret_cast<LPWSTR>(data), reinterpret_cast<size_t *>(size),
L"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x12\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f");
return true;
}
} // unnamed namespace
namespace Engine namespace Engine
{ {
enum : DWORD { X64_MAX_REL_ADDR = 0x00300000 };
/** Artikash 6/7/2019 /** Artikash 6/7/2019
* PPSSPP JIT code has pointers, but they are all added to an offset before being used. * PPSSPP JIT code has pointers, but they are all added to an offset before being used.
Find that offset so that hook searching works properly. Find that offset so that hook searching works properly.
@ -193,18 +520,40 @@ namespace Engine
{ {
wcscpy_s(spDefault.exportModule, name); wcscpy_s(spDefault.exportModule, name);
HookParam hp = {}; HookParam hp = {};
hp.address = (DWORD)GetProcAddress(module, "PyUnicodeUCS2_Format"); hp.address = (uintptr_t)GetProcAddress(module, "PyUnicodeUCS2_Format");
if (!hp.address) if (!hp.address)
{ {
ConsoleOutput("Textractor: Ren'py failed: failed to find PyUnicodeUCS2_Format"); ConsoleOutput("Textractor: Ren'py failed: failed to find PyUnicodeUCS2_Format");
return false; return false;
} }
hp.offset = -0x20; // rcx hp.offset = pusha_rcx_off -4; // rcx
hp.index = 0x18; hp.index = 0x18;
hp.length_offset = 0; hp.length_offset = 0;
//hp.split = pusha_ebx_off - 4; //hp.split = pusha_rsp_off -4;
hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT /* | USING_SPLIT*/; hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT /* | USING_SPLIT*/;
//hp.filter_fun = [](void* str, auto, auto, auto) { return *(wchar_t*)str != L'%'; }; hp.filter_fun = [](LPVOID data, DWORD *size, HookParam *, BYTE)
{
static std::wstring prevText;
auto text = reinterpret_cast<LPWSTR>(data);
auto len = reinterpret_cast<size_t *>(size);
if (cpp_wcsnstr(text, L"%", *len/sizeof(wchar_t)))
return false;
if (cpp_wcsnstr(text, L"{", *len/sizeof(wchar_t))) {
WideStringCharReplacer(text, len, L"{i}", 3, L'\'');
WideStringCharReplacer(text, len, L"{/i}", 4, L'\'');
WideStringFilterBetween(text, len, L"{", 1, L"}", 1);
}
WideStringFilter(text, len, L"^", 2); // remove ^ followed by 1 char
WideCharReplacer(text, len, L'\n', L' ');
if (prevText.length()==*len/sizeof(wchar_t) && prevText.find(text, 0, *len/sizeof(wchar_t))!=std::string::npos) // Check if the string is the same as the previous one
return false;
prevText.assign(text, *len/sizeof(wchar_t));
return true;
};
NewHook(hp, "Ren'py"); NewHook(hp, "Ren'py");
return true; return true;
} }
@ -213,7 +562,126 @@ namespace Engine
ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll"); ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll");
return false; return false;
} }
bool InsertRenpy3Hook()
{
//by Blu3train
/*
* Sample games:
* https://vndb.org/v45820
* https://vndb.org/v26318
* https://vndb.org/v39954
* https://vndb.org/r110220
* https://vndb.org/r114981
* https://vndb.org/v33647
* https://vndb.org/r73160
* https://vndb.org/v44518
* https://vndb.org/v31994
* https://vndb.org/r115756
*/
wchar_t python[] = L"python3X.dll", libpython[] = L"libpython3.X.dll";
for (wchar_t* name : { python, libpython })
{
wchar_t* pos = wcschr(name, L'X');
for (int pythonMinorVersion = 0; pythonMinorVersion <= 9; ++pythonMinorVersion)
{
*pos = L'0' + pythonMinorVersion;
if (HMODULE module = GetModuleHandleW(name))
{
wcscpy_s(spDefault.exportModule, name);
HookParam hp = {};
hp.address = (uintptr_t)GetProcAddress(module, "PyUnicode_Format");
if (!hp.address)
{
ConsoleOutput("Textractor: Ren'py 3 failed: failed to find PyUnicode_Format");
return false;
}
hp.offset = pusha_rcx_off -4; // rcx
hp.padding = 0x48;
hp.length_offset = 0;
hp.text_fun = [](DWORD rsp_base, HookParam *pHp, BYTE, DWORD* data, DWORD* split, DWORD* count)
{
uint64_t r10 = regof(r10, rsp_base);
uint64_t r11 = regof(r11, rsp_base);
if (r10==0x03FF || r11==0x03FF) {
uint64_t rcx = regof(rcx, rsp_base);
BYTE unicode = !(*(BYTE*)(rcx + 0x20) & 0x40); // [rcx+0x20) bit 0x40 == 0
if (unicode) {
*data += 0x48; //padding
*count = wcslen((wchar_t*)*data) * sizeof(wchar_t);
return;
}
}
*count = 0;
};
hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT;
hp.filter_fun = [](LPVOID data, DWORD *size, HookParam *, BYTE)
{
auto text = reinterpret_cast<LPWSTR>(data);
auto len = reinterpret_cast<size_t *>(size);
if (cpp_wcsnstr(text, L"%", *len/sizeof(wchar_t)))
return false;
if (cpp_wcsnstr(text, L"{", *len/sizeof(wchar_t))) {
WideStringCharReplacer(text, len, L"{i}", 3, L'\'');
WideStringCharReplacer(text, len, L"{/i}", 4, L'\'');
WideStringFilterBetween(text, len, L"{", 1, L"}", 1);
}
//CP_OEMCP -The current system OEM code page
WideCharToMultiByte(CP_OEMCP, 0, text, -1, text_buffer, 0x1000, NULL, NULL);
text_buffer_length = *len/sizeof(wchar_t); // saved for not unicode hook
return true;
};
NewHook(hp, "Ren'py 3 unicode");
hp.address += 6;
hp.padding = 0x30;
hp.text_fun = [](DWORD rsp_base, HookParam *pHp, BYTE, DWORD* data, DWORD* split, DWORD* count)
{
uint64_t r10 = regof(r10, rsp_base);
uint64_t r11 = regof(r11, rsp_base);
if (r10==0x03FF || r11==0x03FF) {
uint64_t rcx = regof(rcx, rsp_base);
BYTE unicode = !(*(BYTE*)(rcx + 0x20) & 0x40); // [rcx+0x20) bit 0x40 == 0
*data += unicode ? 0x48 : 0x30; //padding
*count = ::strlen((char*)*data);
if (!cpp_strnstr((char*)*data, "%", *count)) // not garbage
return;
}
*count = 0;
};
hp.type = USING_STRING | NO_CONTEXT;
hp.filter_fun = [](LPVOID data, DWORD *size, HookParam *, BYTE)
{
auto text = reinterpret_cast<LPSTR>(data);
auto len = reinterpret_cast<size_t *>(size);
if (text[0]!=0 && text[1]==0) {
// text from unicode hook
*len = text_buffer_length;
::memmove(text, text_buffer, *len);
}
if (cpp_strnstr(text, "%", *len))
return false;
if (cpp_strnstr(text, "{", *len)) {
StringCharReplacer(text, len, "{i}", 3, L'\'');
StringCharReplacer(text, len, "{/i}", 4, L'\'');
StringFilterBetween(text, len, "{", 1, "}", 1);
}
return true;
};
NewHook(hp, "Ren'py 3");
return true;
}
}
}
ConsoleOutput("Textractor: Ren'py 3 failed: failed to find python3X.dll");
return false;
}
bool UnsafeDetermineEngineType() bool UnsafeDetermineEngineType()
{ {
if (Util::CheckFile(L"PPSSPP*.exe") && FindPPSSPP()) return true; if (Util::CheckFile(L"PPSSPP*.exe") && FindPPSSPP()) return true;
@ -228,7 +696,7 @@ namespace Engine
return true; return true;
} }
if (Util::CheckFile(L"*.py") && InsertRenpyHook()) return true; if (Util::CheckFile(L"*.py") && InsertRenpyHook() || InsertRenpy3Hook()) return true;
for (const wchar_t* monoName : { L"mono.dll", L"mono-2.0-bdwgc.dll" }) if (HMODULE module = GetModuleHandleW(monoName)) if (InsertMonoHooks(module)) return true; for (const wchar_t* monoName : { L"mono.dll", L"mono-2.0-bdwgc.dll" }) if (HMODULE module = GetModuleHandleW(monoName)) if (InsertMonoHooks(module)) return true;