LunaHook-mirror/LunaHook/engines/ppsspp/ppsspp.cpp
恍兮惚兮 edc5efec99 format
2024-11-02 15:49:09 +08:00

610 lines
22 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 "psputils.hpp"
#include "specialgames.hpp"
// See: https://github.com/hrydgard/ppsspp
// Core/HLE (High Level Emulator)
// - sceCcc
// #void sceCccSetTable(u32 jis2ucs, u32 ucs2jis)
// int sceCccUTF8toUTF16(u32 dstAddr, u32 dstSize, u32 srcAddr)
// int sceCccUTF8toSJIS(u32 dstAddr, u32 dstSize, u32 srcAddr)
// int sceCccUTF16toUTF8(u32 dstAddr, u32 dstSize, u32 srcAddr)
// int sceCccUTF16toSJIS(u32 dstAddr, u32 dstSize, u32 srcAddr)
// int sceCccSJIStoUTF8(u32 dstAddr, u32 dstSize, u32 srcAddr)
// int sceCccSJIStoUTF16(u32 dstAddr, u32 dstSize, u32 srcAddr)
// int sceCccStrlenUTF8(u32 strAddr)
// int sceCccStrlenUTF16(u32 strAddr)
// int sceCccStrlenSJIS(u32 strAddr)
// u32 sceCccEncodeUTF8(u32 dstAddrAddr, u32 ucs)
// void sceCccEncodeUTF16(u32 dstAddrAddr, u32 ucs)
// u32 sceCccEncodeSJIS(u32 dstAddrAddr, u32 jis)
// u32 sceCccDecodeUTF8(u32 dstAddrAddr)
// u32 sceCccDecodeUTF16(u32 dstAddrAddr)
// u32 sceCccDecodeSJIS(u32 dstAddrAddr)
// int sceCccIsValidUTF8(u32 c)
// int sceCccIsValidUTF16(u32 c)
// int sceCccIsValidSJIS(u32 c)
// int sceCccIsValidUCS2(u32 c)
// int sceCccIsValidUCS4(u32 c)
// int sceCccIsValidJIS(u32 c)
// int sceCccIsValidUnicode(u32 c)
// #u32 sceCccSetErrorCharUTF8(u32 c)
// #u32 sceCccSetErrorCharUTF16(u32 c)
// #u32 sceCccSetErrorCharSJIS(u32 c)
// u32 sceCccUCStoJIS(u32 c, u32 alt)
// u32 sceCccJIStoUCS(u32 c, u32 alt)
// - sceFont: search charCode
// int sceFontGetCharInfo(u32 fontHandle, u32 charCode, u32 charInfoPtr)
// int sceFontGetShadowInfo(u32 fontHandle, u32 charCode, u32 charInfoPtr)
// int sceFontGetCharImageRect(u32 fontHandle, u32 charCode, u32 charRectPtr)
// int sceFontGetShadowImageRect(u32 fontHandle, u32 charCode, u32 charRectPtr)
// int sceFontGetCharGlyphImage(u32 fontHandle, u32 charCode, u32 glyphImagePtr)
// int sceFontGetCharGlyphImage_Clip(u32 fontHandle, u32 charCode, u32 glyphImagePtr, int clipXPos, int clipYPos, int clipWidth, int clipHeight)
// #int sceFontSetAltCharacterCode(u32 fontLibHandle, u32 charCode)
// int sceFontGetShadowGlyphImage(u32 fontHandle, u32 charCode, u32 glyphImagePtr)
// int sceFontGetShadowGlyphImage_Clip(u32 fontHandle, u32 charCode, u32 glyphImagePtr, int clipXPos, int clipYPos, int clipWidth, int clipHeight)
// - sceKernelInterrupt
// u32 sysclib_strcat(u32 dst, u32 src)
// int sysclib_strcmp(u32 dst, u32 src)
// u32 sysclib_strcpy(u32 dst, u32 src)
// u32 sysclib_strlen(u32 src)
//
// Sample debug string:
// 006EFD8E PUSH PPSSPPWi.00832188 ASCII "sceCccEncodeSJIS(%08x, U+%04x)"
// Corresponding source code in sceCcc:
// ERROR_LOG(HLE, "sceCccEncodeSJIS(%08x, U+%04x): invalid pointer", dstAddrAddr, jis);
struct PPSSPPFunction
{
const char *hookName; // hook name
int argIndex; // argument index
uint64_t hookType; // hook parameter type
int hookSplit; // hook parameter split, positive: stack, negative: registers
const char *pattern; // debug string used within the function
};
namespace
{
uintptr_t findleapushaddr(uintptr_t addr)
{
#ifndef _WIN64
addr = MemDbg::findPushAddress(addr, processStartAddress, processStopAddress);
if (!addr)
return NULL;
addr = SafeFindEnclosingAlignedFunction(addr, 0x200);
#else
addr = MemDbg::findleaaddr(addr, processStartAddress, processStopAddress);
if (!addr)
return NULL;
BYTE sig1[] = {
0xCC,
0x48, 0x89, XX, 0x24, XX};
BYTE sig2[] = {
0xC3,
0x48, 0x89, XX, 0x24, XX};
BYTE sig3[] = {
0xCC,
0x89, XX, 0x24, XX4};
BYTE sig4[] = {
0xC3,
0x89, XX, 0x24, XX};
int idx = 0;
uintptr_t maxaddr = 0;
for (auto sig : {sig1, sig2, sig3, sig4})
{
idx += 1;
maxaddr = max(maxaddr, reverseFindBytes(sig, (idx > 2) ? 5 : 6, addr - 0x500, addr, 1, true));
}
maxaddr = max(maxaddr, MemDbg::findEnclosingAlignedFunction_strict(addr, 0x500));
addr = maxaddr;
#endif
return addr;
}
}
bool InsertPPSSPPHLEHooks()
{
auto functions = std::vector<PPSSPPFunction>{
// https://github.com/hrydgard/ppsspp/blob/master/Core/HLE/sceCcc.cpp
{"sceCccStrlenSJIS", GETARG1, USING_STRING, 0, "sceCccStrlenSJIS("},
{"sceCccStrlenUTF8", GETARG1, CODEC_UTF8 | USING_STRING, 0, "sceCccStrlenUTF8("},
{"sceCccStrlenUTF16", GETARG1, CODEC_UTF16 | USING_STRING, 0, "sceCccStrlenUTF16("},
{"sceCccSJIStoUTF8", GETARG3, USING_STRING, 0, "sceCccSJIStoUTF8("},
{"sceCccSJIStoUTF16", GETARG3, USING_STRING, 0, "sceCccSJIStoUTF16("},
{"sceCccUTF8toSJIS", GETARG3, CODEC_UTF8 | USING_STRING, 0, "sceCccUTF8toSJIS("},
{"sceCccUTF8toUTF16", GETARG3, CODEC_UTF8 | USING_STRING, 0, "sceCccUTF8toUTF16("},
{"sceCccUTF16toSJIS", GETARG3, CODEC_UTF16 | USING_STRING, 0, "sceCccUTF16toSJIS("},
{"sceCccUTF16toUTF8", GETARG3, CODEC_UTF16 | USING_STRING, 0, "sceCccUTF16toUTF8("},
// https://github.com/hrydgard/ppsspp/blob/master/Core/HLE/sceFont.cpp
{"sceFontGetCharInfo", GETARG2, CODEC_UTF16, GETARG1, "sceFontGetCharInfo("},
{"sceFontGetShadowInfo", GETARG2, CODEC_UTF16, GETARG1, "sceFontGetShadowInfo("},
{"sceFontGetCharImageRect", GETARG2, CODEC_UTF16, GETARG1, "sceFontGetCharImageRect("},
{"sceFontGetShadowImageRect", GETARG2, CODEC_UTF16, GETARG1, "sceFontGetShadowImageRect("},
{"sceFontGetCharGlyphImage", GETARG2, CODEC_UTF16, GETARG1, "sceFontGetCharGlyphImage("},
{"sceFontGetCharGlyphImage_Clip", GETARG2, CODEC_UTF16, GETARG1, "sceFontGetCharGlyphImage_Clip("},
{"sceFontGetShadowGlyphImage", GETARG2, CODEC_UTF16, GETARG1, "sceFontGetShadowGlyphImage("},
{"sceFontGetShadowGlyphImage_Clip", GETARG2, CODEC_UTF16, GETARG1, "sceFontGetShadowGlyphImage_Clip("},
// https://github.com/hrydgard/ppsspp/blob/master/Core/HLE/sceKernelInterrupt.cpp
{"sysclib_strcat", GETARG2, USING_STRING, 0, "Untested sysclib_strcat("},
{"sysclib_strcpy", GETARG2, USING_STRING, 0, "Untested sysclib_strcpy("},
{"sysclib_strlen", GETARG1, USING_STRING, 0, "Untested sysclib_strlen("}
// Disabled as I am not sure how to deal with the source string
//, { "sceCccEncodeSJIS", 2, USING_STRING, 0, "sceCccEncodeSJIS(" }
//, { "sceCccEncodeUTF8", 2, CODEC_UTF8, 0, "sceCccEncodeUTF8(" }
//, { "sceCccEncodeUTF16", 2, CODEC_UTF16, 0, "sceCccEncodeUTF16(" }
//, { "sysclib_strcmp", 2, USING_STRING, 0, "Untested sysclib_strcmp(" }
};
auto succ = false;
for (auto &&function : functions)
{
auto addr = MemDbg::findBytes(function.pattern, ::strlen(function.pattern), processStartAddress, processStopAddress);
if (!addr)
continue;
addr = findleapushaddr(addr);
if (!addr)
continue;
HookParam hp;
hp.address = addr;
hp.type = function.hookType;
hp.offset = function.argIndex;
hp.split = function.hookSplit;
if (hp.split)
hp.type |= USING_SPLIT;
succ |= NewHook(hp, function.hookName);
}
return succ;
}
#if 0
bool PPSSPPinithooksearch(){
bool found = false;
SYSTEM_INFO systemInfo;
GetNativeSystemInfo(&systemInfo);
for (BYTE* probe = NULL; probe < systemInfo.lpMaximumApplicationAddress;)
{
MEMORY_BASIC_INFORMATION info;
if (!VirtualQuery(probe, &info, sizeof(info)))
{
probe += systemInfo.dwPageSize;
}
else
{
if (info.RegionSize == 0x1f00000 && info.Protect == PAGE_READWRITE && info.Type == MEM_MAPPED)
{
found = true;
ConsoleOutput("PPSSPP memory found: searching for hooks should yield working hook codes");
#ifndef _WIN64
// PPSSPP 1.8.0 compiles jal to sub dword ptr [ebp+0x360],??
memcpy(spDefault.pattern, Array<BYTE>{ 0x83, 0xAD, 0x60, 0x03, 0x00, 0x00 }, spDefault.length = 6);
#else
// PPSSPP 1.8.0 compiles jal to sub dword ptr [r14+0x360],??
memcpy(spDefault.pattern, Array<BYTE>{ 0x41, 0x83, 0xae, 0x60, 0x03, 0x00, 0x00 }, spDefault.length = 7);
#endif
spDefault.offset = 0;
spDefault.minAddress = 0;
spDefault.maxAddress = -1ULL;
spDefault.padding = (uintptr_t)probe - 0x8000000;
spDefault.hookPostProcessor = [](HookParam& hp)
{
hp.type |= NO_CONTEXT | USING_SPLIT | SPLIT_INDIRECT;
#ifndef _WIN64
hp.split = get_reg(regs::ebp);
hp.split_index =get_reg(regs::eax); // this is where PPSSPP 1.8.0 stores its return address stack
#else
hp.split = get_reg(regs::r14);
hp.split_index = -8; // this is where PPSSPP 1.8.0 stores its return address stack
#endif
};
}
probe += info.RegionSize;
}
}
return found;
}
#endif
uintptr_t getDoJitAddress()
{
#ifndef _WIN64
auto string1 = "Jump target too far away, needs indirect register";
auto string2 = "Jump target too far away, needs force5Bytes = true";
auto addr1 = MemDbg::findBytes(string1, ::strlen(string1), processStartAddress, processStopAddress);
auto addr2 = MemDbg::findBytes(string2, ::strlen(string2), processStartAddress, processStopAddress);
if (addr1 == 0 || addr2 == 0)
return 0;
// 都是被push两次但是都是第一个
addr1 = MemDbg::findPushAddress(addr1, processStartAddress, processStopAddress);
addr2 = MemDbg::findPushAddress(addr2, processStartAddress, processStopAddress);
if (addr1 == 0 || addr2 == 0)
return 0;
addr1 = MemDbg::findEnclosingAlignedFunction_strict(addr1, 0x100);
addr2 = MemDbg::findEnclosingAlignedFunction_strict(addr2, 0x100);
if (addr1 == 0 || addr2 == 0 || addr1 != addr2)
return 0;
auto xrefs = findxref_reverse_checkcallop(addr1, processStartAddress, processStopAddress, 0xe8);
if (xrefs.size() < 28)
return 0;
addr1 = MemDbg::findEnclosingAlignedFunction_strict(xrefs[xrefs.size() - 1 - 3], 0x400);
addr2 = MemDbg::findEnclosingAlignedFunction_strict(xrefs[xrefs.size() - 1 - 4], 0x400);
if (addr1 == 0 || addr2 == 0 || addr1 != addr2)
return 0;
return addr1;
#else
auto DoJitSig1 = "C7 83 ?? 0? 00 00 11 00 00 00 F6 83 ?? 0? 00 00 01 C7 83 ?? 0? 00 00 E4 00 00 00";
auto first = find_pattern(DoJitSig1, processStartAddress, processStopAddress);
if (first)
{
auto beginSubSig1 = "55 41 ?? 41 ?? 41";
auto lookbackSize = 0x400;
auto address = first - lookbackSize;
auto subs = find_pattern(beginSubSig1, address, address + lookbackSize);
if (subs)
{
return subs;
}
}
else
{
auto DoJitSig2 = "C7 83 ?? 0? 00 00 11 00 00 00 F6 83 ?? 0? 00 00 01 ?? ?? ?? ?? ?? ?? ?? C7 83 ?? 0? 00 00 E4 00 00 00";
first = find_pattern(DoJitSig2, processStartAddress, processStopAddress);
if (first)
{
first = MemDbg::findEnclosingAlignedFunction_strict(first, 0x400);
return first;
}
}
#endif
return 0;
}
namespace ppsspp
{
bool checkiscurrentgame(const emfuncinfo &em)
{
auto wininfos = get_proc_windows();
for (auto &&info : wininfos)
{
if (info.title.find(acastw(em._id)) != info.title.npos)
return true;
}
return false;
}
std::unordered_set<uintptr_t> breakpoints;
inline bool IsValidAddress(const uintptr_t address)
{
if ((address & 0x3E000000) == 0x08000000)
{
return true;
}
else if ((address & 0x3F800000) == 0x04000000)
{
return true;
}
else if ((address & 0xBFFFC000) == 0x00010000)
{
return true;
}
else if ((address & 0x3F000000) >= 0x08000000)
{ // && (address & 0x3F000000) < 0x08000000 + g_MemorySize) {
return true;
}
else
{
return false;
}
}
void dohookemaddr(uintptr_t em_address, uintptr_t ret)
{
jitaddraddr(em_address, ret, JITTYPE::PPSSPP);
if (emfunctionhooks.find(em_address) == emfunctionhooks.end())
return;
if (!(checkiscurrentgame(emfunctionhooks.at(em_address))))
return;
auto op = emfunctionhooks.at(em_address);
ConsoleOutput("jit function addr %p", ret);
#ifndef _WIN64
BYTE sig[] = {
0x8b, XX2, // mov reg,[ebp-off]
0x8b, 0xc6, // mov eax,esi
0x25, 0xff, 0xff, 0xff, 0x3f, // and eax,0x3fffffff
0x89, XX, XX4, // mov [eax+base+off],reg
};
auto findbase = MemDbg::findBytes(sig, sizeof(sig), ret, ret + 0x20);
if (!findbase)
findbase = MemDbg::findBytes(sig, sizeof(sig), ret - 0x1000, ret + 0x1000);
if (!findbase)
ConsoleOutput("can't find emu_baseaddr");
PPSSPP::x86_baseaddr = (*(DWORD *)(findbase + 12)) & 0xffff0000;
ConsoleOutput("x86 base addr %p", PPSSPP::x86_baseaddr);
#endif
HookParam hpinternal;
hpinternal.address = ret;
hpinternal.emu_addr = em_address; // 用于生成hcode
hpinternal.type = USING_STRING | NO_CONTEXT | BREAK_POINT | op.type;
hpinternal.text_fun = (decltype(hpinternal.text_fun))op.hookfunc;
hpinternal.filter_fun = (decltype(hpinternal.filter_fun))op.filterfun;
hpinternal.argidx = op.argidx;
hpinternal.padding = op.padding;
hpinternal.jittype = JITTYPE::PPSSPP;
NewHook(hpinternal, op._id);
}
namespace
{
typedef DWORD u32;
typedef BYTE u8;
typedef WORD u16;
const int MAX_JIT_BLOCK_EXITS = 8;
namespace Memory
{
struct Opcode
{
Opcode()
{
}
explicit Opcode(u32 v) : encoding(v)
{
}
u32 operator&(const u32 &arg) const
{
return encoding & arg;
}
u32 operator>>(const u32 &arg) const
{
return encoding >> arg;
}
bool operator==(const u32 &arg) const
{
return encoding == arg;
}
bool operator!=(const u32 &arg) const
{
return encoding != arg;
}
u32 encoding;
};
}
typedef Memory::Opcode MIPSOpcode;
struct JitBlock
{
bool ContainsAddress(u32 em_address) const;
const u8 *checkedEntry; // const, we have to translate to writable.
const u8 *normalEntry;
u8 *exitPtrs[MAX_JIT_BLOCK_EXITS]; // to be able to rewrite the exit jump
u32 exitAddress[MAX_JIT_BLOCK_EXITS]; // 0xFFFFFFFF == unknown
u32 originalAddress;
MIPSOpcode originalFirstOpcode; // to be able to restore
uint64_t compiledHash;
u16 codeSize;
u16 originalSize;
u16 blockNum;
bool invalid;
bool linkStatus[MAX_JIT_BLOCK_EXITS];
#ifdef USE_VTUNE
char blockName[32];
#endif
// By having a pointer, we avoid a constructor/destructor being generated and dog slow
// performance in debug.
std::vector<u32> *proxyFor;
bool IsPureProxy() const
{
return originalFirstOpcode.encoding == 0x68FF0000;
}
void SetPureProxy()
{
// Magic number that won't be a real opcode.
originalFirstOpcode.encoding = 0x68FF0000;
}
};
}
void unsafeoncegetJitBlockCache(hook_stack *stack)
{
// class JitBlockCache : public JitBlockCacheDebugInterface {
//...
// JitBlock *blocks_ = nullptr;
// std::unordered_multimap<u32, int> proxyBlockMap_; ->64
// int num_blocks_ = 0;
#ifdef _WIN64
auto num_blocks_ = *(uint32_t *)(stack->rcx + 72 + 16 + 88);
auto blocks_ = (JitBlock *)*(uintptr_t *)(stack->rcx + 72 + 16 + 88 - 64 - 8);
#else
auto num_blocks_ = *(uint32_t *)(stack->ecx + 88);
auto blocks_ = (JitBlock *)*(uintptr_t *)(stack->ecx + 88 - 32 - 4);
#endif
int checkvalid = 0;
num_blocks_ -= 1; // last one is now dojiting
for (int i = 0; i < num_blocks_; i++)
{
if (IsValidAddress(blocks_[i].originalAddress) && blocks_[i].normalEntry)
checkvalid += 1;
}
if (checkvalid < num_blocks_ / 2)
return;
for (int i = 0; i < num_blocks_; i++)
{
if (IsValidAddress(blocks_[i].originalAddress) && blocks_[i].normalEntry)
{
dohookemaddr(blocks_[i].originalAddress, (uintptr_t)blocks_[i].normalEntry);
delayinsertNewHook(blocks_[i].originalAddress);
}
}
return;
}
bool oncegetJitBlockCache(hook_stack *stack)
{
// 在游戏中途hook获取已compiled jit
// 虽然只有在每次进行jit时才会触发不过测试后续触发的也挺频繁的。
__try
{
unsafeoncegetJitBlockCache(stack);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
return true;
}
bool hookPPSSPPDoJit()
{
auto DoJitPtr = getDoJitAddress();
if (DoJitPtr == 0)
return false;
spDefault.jittype = JITTYPE::PPSSPP;
spDefault.minAddress = 0;
spDefault.maxAddress = -1;
HookParam hp;
hp.address = DoJitPtr; // Jit::DoJit
ConsoleOutput("DoJitPtr %p", DoJitPtr);
hp.user_value = (uintptr_t) new uintptr_t;
hp.text_fun = [](hook_stack *stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t *len)
{
static auto once1 = oncegetJitBlockCache(stack);
auto em_address = stack->THISCALLARG1;
*(uintptr_t *)(hp->user_value) = em_address;
HookParam hpinternal;
hpinternal.user_value = hp->user_value;
hpinternal.address = stack->retaddr;
hpinternal.text_fun = [](hook_stack *stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t *len)
{
auto em_address = *(uintptr_t *)(hp->user_value);
if (!IsValidAddress(em_address))
return;
[&]()
{
auto ret = stack->LASTRETVAL;
if (breakpoints.find(ret) != breakpoints.end())
return;
breakpoints.insert(ret);
dohookemaddr(em_address, ret);
}();
delayinsertNewHook(em_address);
};
static auto once = NewHook(hpinternal, "DoJitPtrRet");
};
return NewHook(hp, "PPSSPPDoJit");
}
}
namespace
{
// ULJS00035 ULJS00149 流行り神
void *findGetPointer()
{
char GetPointer[] = "Unknown GetPointer %08x PC %08x LR %08x";
auto addr = MemDbg::findBytes(GetPointer, sizeof(GetPointer), processStartAddress, processStopAddress);
if (!addr)
return nullptr;
addr = findleapushaddr(addr);
return (void *)addr;
}
bool Replace_memcpy()
{
// static int Replace_memcpy() {
// u32 destPtr = PARAM(0);
// u32 srcPtr = PARAM(1);
// u32 bytes = PARAM(2);
static auto GetPointer = (uintptr_t(*)(uintptr_t))findGetPointer();
if (!GetPointer)
return false;
ConsoleOutput("GetPointer %p", GetPointer);
char ReplaceMemcpy_VideoDecodeRange[] = "ReplaceMemcpy/VideoDecodeRange";
auto addr = MemDbg::findBytes(ReplaceMemcpy_VideoDecodeRange, sizeof(ReplaceMemcpy_VideoDecodeRange), processStartAddress, processStopAddress);
if (!addr)
return false;
ConsoleOutput("ReplaceMemcpy/VideoDecodeRange %p", addr);
#ifndef _WIN64
BYTE sig[] = {0xb9, XX4};
*(uintptr_t *)(sig + 1) = addr;
bool succ = false;
for (auto addr : Util::SearchMemory(sig, sizeof(sig), PAGE_EXECUTE, processStartAddress, processStopAddress))
{
BYTE sig1[] = {
0x55, 0x8b, 0xec,
0x81, 0xec, XX4,
0x8b, 0x0d, XX4};
addr = reverseFindBytes(sig1, sizeof(sig1), addr - 0x200, addr);
if (!addr)
continue;
DWORD off_106D180 = *(DWORD *)(addr + sizeof(sig1) - 4);
HookParam hp;
hp.user_value = *(DWORD *)off_106D180;
#else
bool succ = false;
for (auto addr : MemDbg::findleaaddr_all(addr, processStartAddress, processStopAddress))
{
BYTE sig1[] = {
0x48, 0x89, XX, 0x24, 0x18,
0x48, 0x89, XX, 0x24, 0x20,
0x57,
0x48, 0x81, 0xec, XX4,
0x48, 0x8b, XX, XX4};
addr = reverseFindBytes(sig1, sizeof(sig1), addr - 0x200, addr);
if (!addr)
continue;
DWORD off_140F4C810 = *(DWORD *)(addr + sizeof(sig1) - 4);
HookParam hp;
hp.user_value = *(uintptr_t *)(off_140F4C810 + addr + sizeof(sig1));
#endif
hp.address = addr;
hp.text_fun = [](hook_stack *stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t *len)
{
auto bytes = *((DWORD *)hp->user_value + 6);
auto srcPtr = GetPointer(*((DWORD *)hp->user_value + 5));
if (!IsDBCSLeadByteEx(932, *(BYTE *)srcPtr))
return;
if (bytes != 2)
return;
if (bytes != strnlen((char *)srcPtr, TEXT_BUFFER_SIZE))
return;
*data = (uintptr_t)srcPtr;
*len = bytes;
};
succ |= NewHook(hp, "Replace_memcpy");
}
return succ;
}
}
bool InsertPPSSPPcommonhooks()
{
auto succ = ppsspp::hookPPSSPPDoJit();
succ |= InsertPPSSPPHLEHooks();
succ |= Replace_memcpy();
return succ;
}