diff --git a/AGENTS.md b/AGENTS.md index d16382c510..396781f54e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -163,6 +163,11 @@ small examples to copy from). A module is a `const HLEFunction []` table o build-tested here, so double check them by hand against how an existing neighboring file (e.g. `sceVaudio.cpp`) is listed in each. Note: New files in the unittest project have to be updated in the unittest part in android/jni/Android.mk. +## Adding HLE functions + +Always add new functions at the *end* of an array of const HLEFunction. We store the function index in savestates +(in resolved syscall opcodes), so inserting a new function in the middle of an existing array will break things. + ## WebSocket debugger PPSSPP has a JSON/WebSocket debugger and automation API (connect, read/write memory, search memory for values or byte diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 85df5f83ef..e36c32b415 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -968,15 +968,92 @@ void Register_LoadExecForUser() RegisterHLEModule("LoadExecForUser", ARRAY_SIZE(LoadExecForUser), LoadExecForUser); } -const HLEFunction LoadExecForKernel[] = -{ +// sceKernelExitVSHVSH and sceKernelExitVSHKernel (two NIDs each, across firmware versions) are +// the VSH's own way of tearing itself down - when called from a regular game/homebrew context +// (which can happen, since these live in a kernel-mode module games can still reach) they have +// the same observable effect as sceKernelExitGame. See SceKernelLoadExecVSHParam in sceKernel.h. +static int sceKernelExitVSH(u32 paramPtr) { + if (Memory::IsValidRange(paramPtr, sizeof(SceKernelLoadExecVSHParam))) { + auto param = PSPPointer::Create(paramPtr); + INFO_LOG(Log::sceKernel, "sceKernelExitVSH: size=%d, args=%08x, argp=%08x, flags=%08x", param->size, param->args, param->argp, param->flags); + } + + INFO_LOG(Log::sceKernel, "sceKernelExitVSH"); + __KernelSwitchOffThread("VSH exited"); + Core_Stop(); + + g_OSD.Show(OSDType::MESSAGE_INFO, "sceKernelExitVSH()", 0.0f, "kernelexit"); + return hleNoLog(0); +} + +// The VSH's own version of sceKernelLoadExec, used for pushing a game to run over USB/WLAN +// (e.g. from a PC) rather than loading it from a file already on the memory stick/UMD. +static int sceKernelLoadExecBufferVSHUsbWlan(int bufferSize, u32 bufferAddr, u32 paramPtr) { + if (!Memory::IsValidRange(bufferAddr, bufferSize)) { + return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "invalid buffer"); + } + + const u8 *data = Memory::GetPointerUnchecked(bufferAddr); + std::string error_string; + if (!__KernelLoadExecFromBuffer(data, (size_t)bufferSize, paramPtr, &error_string)) { + Core_UpdateState(CORE_RUNTIME_ERROR); + return hleLogError(Log::sceKernel, -1, "failed: %s", error_string.c_str()); + } + if (gpu) { + gpu->Reinitialize(); + } + return hleLogInfo(Log::sceKernel, 0); +} + +const HLEFunction LoadExecForKernel[] = { {0x4AC57943, &WrapI_I, "sceKernelRegisterExitCallback", 'i', "i", HLE_KERNEL_SYSCALL }, - {0XA3D5E142, nullptr, "sceKernelExitVSHVSH", '?', "" }, + {0XA3D5E142, &WrapI_U, "sceKernelExitVSHVSH", 'i', "x", HLE_KERNEL_SYSCALL }, {0X28D0D249, &WrapI_CU, "sceKernelLoadExecVSHMs2", 'i', "sx" }, - {0x6D302D3D, &WrapV_V, "sceKernelExitVSHKernel", 'v', "x", HLE_KERNEL_SYSCALL },// when called in game mode it will have the same effect that sceKernelExitGame + {0x6D302D3D, &WrapI_U, "sceKernelExitVSHKernel", 'i', "x", HLE_KERNEL_SYSCALL },// when called in game mode it will have the same effect that sceKernelExitGame {0x05572A5F, &WrapV_V, "sceKernelExitGame", 'v', "", HLE_KERNEL_SYSCALL }, + {0X08F7166C, &WrapI_U, "sceKernelExitVSHVSH", 'i', "x", HLE_KERNEL_SYSCALL }, + {0XD940C83C, &WrapI_CU, "sceKernelLoadExecVSHMs2", 'i', "sx" }, + {0XF9CFCF2F, &WrapI_CU, "sceKernelLoadExec_F9CFCF2F", 'i', "sx" }, + {0XD8320A28, &WrapI_CU, "sceKernelLoadExecVSHDisc", 'i', "sx" }, + {0XC3474C2A, &WrapI_U, "sceKernelExitVSHKernel", 'i', "x", HLE_KERNEL_SYSCALL }, + {0XBEF585EC, &WrapI_IUU, "sceKernelLoadExecBufferVSHUsbWlan", 'i', "ixx" }, + // Everything below here is only known by NID - even JPCSP, which is further along in VSH + // support, only knows them by name/NID (or not even that) and stubs them all out. + {0X11412288, nullptr, "sceKernelLoadExec_11412288", '?', "" }, + {0XA5ECA6E3, nullptr, "sceKernelLoadExec_11412288", '?', "" }, + {0X00745486, nullptr, "sceKernelLoadExecVSHMs4", '?', "" }, + {0X4FB44D27, nullptr, "sceKernelLoadExecVSHMs1", '?', "" }, + {0XCC6A47D2, nullptr, "sceKernelLoadExecVSHMs3", '?', "" }, + {0X7CABED9B, nullptr, "sceKernelLoadExecVSHMs5", '?', "" }, + {0X1B305B09, nullptr, "sceKernelLoadExecVSHDiscDebug", '?', "" }, + {0XD4B49C4B, nullptr, "sceKernelLoadExecVSHDiscUpdater", '?', "" }, + {0X2B8813AF, nullptr, "sceKernelLoadExecBufferVSHUsbWlanDebug", '?', "" }, + {0X1F08547A, nullptr, "sceKernelInvokeExitCallback", '?', "" }, + {0X1F88A490, nullptr, "sceKernelRegisterExitCallback", '?', "" }, + {0X24114598, nullptr, "sceKernelUnregisterExitCallback", '?', "" }, + {0XB57D0DEC, nullptr, "sceKernelCheckExitCallback", '?', "" }, + {0X032A7938, nullptr, "LoadExecForKernel_032A7938", '?', "" }, + {0X077BA314, nullptr, "LoadExecForKernel_077BA314", '?', "" }, + {0X16A68007, nullptr, "LoadExecForKernel_16A68007", '?', "" }, + {0X1B8AB02E, nullptr, "LoadExecForKernel_1B8AB02E", '?', "" }, + {0X40564748, nullptr, "LoadExecForKernel_40564748", '?', "" }, + {0X47A5A49C, nullptr, "LoadExecForKernel_47A5A49C", '?', "" }, + {0X7CAFE77F, nullptr, "LoadExecForKernel_7CAFE77F", '?', "" }, + {0X87C3589C, nullptr, "LoadExecForKernel_87C3589C", '?', "" }, + {0X8C4679D3, nullptr, "LoadExecForKernel_8C4679D3", '?', "" }, + {0X9BD32619, nullptr, "LoadExecForKernel_9BD32619", '?', "" }, + {0XA6658F10, nullptr, "LoadExecForKernel_A6658F10", '?', "" }, + {0XB343FDAB, nullptr, "LoadExecForKernel_B343FDAB", '?', "" }, + {0XBC26BEEF, nullptr, "LoadExecForKernel_BC26BEEF", '?', "" }, + {0XC11E6DF1, nullptr, "LoadExecForKernel_C11E6DF1", '?', "" }, + {0XC540E3B3, nullptr, "LoadExecForKernel_C540E3B3", '?', "" }, + {0XC7C83B1E, nullptr, "LoadExecForKernel_C7C83B1E", '?', "" }, + {0XDBD0CF1B, nullptr, "LoadExecForKernel_DBD0CF1B", '?', "" }, + {0XE1972A24, nullptr, "LoadExecForKernel_E1972A24", '?', "" }, + {0XE704ECC3, nullptr, "LoadExecForKernel_E704ECC3", '?', "" }, + {0XAE9EFC0D, nullptr, "LoadExecForKernel_AE9EFC0D", '?', "" }, }; - + void Register_LoadExecForKernel() { RegisterHLEModule("LoadExecForKernel", ARRAY_SIZE(LoadExecForKernel), LoadExecForKernel); diff --git a/Core/HLE/sceKernel.h b/Core/HLE/sceKernel.h index b1d79f9476..c1a16c3ecb 100644 --- a/Core/HLE/sceKernel.h +++ b/Core/HLE/sceKernel.h @@ -82,6 +82,28 @@ struct SceKernelLoadExecParam u32_le keyp; // Encryption key? Not yet used }; +// Used by the VSH's own LoadExec variants (sceKernelLoadExecVSHMs2 and friends, see +// LoadExecForKernel in sceKernel.cpp) instead of SceKernelLoadExecParam above. The first four +// fields share the same layout as SceKernelLoadExecParam, which is why the plain +// sceKernelLoadExec happens to work fine even when handed one of these. Fields past "flags" +// are only present if size >= 0x30 - see JPCSP's SceKernelLoadExecVSHParam for the reference +// this was ported from. +struct SceKernelLoadExecVSHParam +{ + SceSize_le size; // Size of the structure + SceSize_le args; // Size of the arg string + u32_le argp; // Pointer to the arg string + u32_le keyp; // Pointer to the encryption key string + SceSize_le vshmainArgsSize; // Size of the vshmain-specific arg buffer + u32_le vshmainArgs; // Pointer to the vshmain-specific arg buffer + u32_le configFilep; // Pointer to a config file path string + u32_le unknownString; + u32_le flags; + u32_le extArgs; + u32_le extArgp; + u32_le opt11; +}; + void __KernelInit(); void __KernelShutdown(); void __KernelDoState(PointerWrap &p); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 3b0212eb64..4ad422b684 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1776,7 +1776,10 @@ void __KernelLoadReset() { __KernelInit(); } -bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_string) { +// Shared by __KernelLoadExec (loading from a file) and __KernelLoadExecFromBuffer (loading +// from a buffer already in RAM, as used by the VSH's USB/WLAN game-push feature) - everything +// past the point where we have the executable's bytes in hand. +bool __KernelLoadExecFromPtr(const u8 *data, size_t size, const char *filename, u32 paramPtr, std::string *error_string) { SceKernelLoadExecParam param{}; auto paramData = PSPPointer::Create(paramPtr); @@ -1801,18 +1804,7 @@ bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_str __KernelLoadReset(); - std::vector fileData; - if (pspFileSystem.ReadEntireFile(filename, fileData) < 0) { - ERROR_LOG(Log::Loader, "Failed to load executable %s - file doesn't exist", filename); - *error_string = StringFromFormat("Could not find executable %s", filename); - delete[] param_argp; - delete[] param_key; - __KernelShutdown(); - return false; - } - - size_t size = fileData.size(); - PSPModule *module = __KernelLoadModule(fileData.data(), size, 0, filename, error_string); + PSPModule *module = __KernelLoadModule((u8 *)data, size, 0, filename, error_string); if (!module || module->isFake) { if (module) { @@ -1872,6 +1864,22 @@ bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_str return true; } +bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_string) { + std::vector fileData; + if (pspFileSystem.ReadEntireFile(filename, fileData) < 0) { + ERROR_LOG(Log::Loader, "Failed to load executable %s - file doesn't exist", filename); + *error_string = StringFromFormat("Could not find executable %s", filename); + __KernelShutdown(); + return false; + } + + return __KernelLoadExecFromPtr(fileData.data(), fileData.size(), filename, paramPtr, error_string); +} + +bool __KernelLoadExecFromBuffer(const u8 *data, size_t size, u32 paramPtr, std::string *error_string) { + return __KernelLoadExecFromPtr(data, size, "vshbuffer", paramPtr, error_string); +} + bool __KernelLoadGEDump(std::string_view base_filename, std::string *error_string) { __KernelLoadReset(); @@ -2538,7 +2546,7 @@ static u32 sceKernelLoadModuleDNAS(const char *name, u32 flags) return hleNoLog(0); } -// Pretty sure this is a badly brute-forced function name... +// Suspecting this is a badly brute-forced function name... Although, it's not entirely implausible. Buffer makes sense. SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr) { if (flags != 0) { diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h index 2a2d39dd20..0cdcf97f31 100644 --- a/Core/HLE/sceKernelModule.h +++ b/Core/HLE/sceKernelModule.h @@ -235,6 +235,10 @@ u32 __KernelGetModuleGP(SceUID module); bool KernelModuleIsKernelMode(SceUID module); bool __KernelLoadGEDump(std::string_view base_filename, std::string *error_string); bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_string); +// Exposed so sceVshBridge can reuse it directly for vshKernelLoadModuleBufferVSH, matching JPCSP. +SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr); +bool __KernelLoadExecFromBuffer(const u8 *data, size_t size, u32 paramPtr, std::string *error_string); +// Exposed for HLE.cpp's "Unknown syscall" diagnostic - see the definition for details. bool KernelFindImportByStubAddr(u32 stubAddr, std::string *importModuleName, u32 *nid, std::string *importingModuleName); // Describes which loaded module (and section within it) an address falls in, e.g. "EBOOT.BIN.text+1234". // Returns an empty string if the address isn't inside any currently loaded module.