mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 23:10:33 +02:00
sceKernelLoadModule: Put the module in memory when the wait ends
We copied the module into memory at the start of the call, then made the caller wait for the read. On hardware the module's bytes only arrive at the end, and until then other threads can use that memory (pspautotests modules/loadrace). SOCOM: Fireteam Bravo 2 EU (UCES00543) relies on this. Its loader EBOOT loads SOCOMPSP.PRX on one thread while another builds a GU display list in a 0x40-byte buffer at the end of its .bss. That list overruns into the free memory right after it, which is where the game module goes. On hardware the module load later overwrites the overrun; for us, the overrun overwrote the module's first instructions, and the game crashed as soon as it started. The load now runs from a CoreTiming event at the end of the wait. Pending loads are saved in savestates. Fixes #18842 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
deca5f432d
commit
4fda53a62e
3 files changed
+182
-75
No files matched your search
+180
-74
@@ -16,6 +16,7 @@
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "zlib.h"
|
||||
@@ -23,6 +24,7 @@
|
||||
#include "Common/Data/Convert/SmallDataConvert.h"
|
||||
#include "Common/Serialize/Serializer.h"
|
||||
#include "Common/Serialize/SerializeFuncs.h"
|
||||
#include "Common/Serialize/SerializeMap.h"
|
||||
#include "Common/Serialize/SerializeSet.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
@@ -64,6 +66,7 @@
|
||||
#include "Core/HLE/sceKernel.h"
|
||||
#include "Core/HLE/sceKernelModule.h"
|
||||
#include "Core/HLE/sceKernelThread.h"
|
||||
#include "Core/HLE/sceKernelInterrupt.h"
|
||||
#include "Core/HLE/sceKernelMemory.h"
|
||||
#include "Core/HLE/sceMpeg.h"
|
||||
#include "Core/HLE/scePsmf.h"
|
||||
@@ -492,6 +495,30 @@ static std::set<SceUID> loadedModules;
|
||||
// Set once we've seen a PSP_MODULE_VSH_MODE module load (i.e. we're booting the VSH rather
|
||||
// than a game), and reset on the next __KernelLoadExec. See ShouldHLEModuleForLoad below.
|
||||
static bool g_runningVSH = false;
|
||||
|
||||
// A sceKernelLoadModule whose caller is waiting for the file to be read. The module is only put
|
||||
// in memory when the wait ends - see sceKernelLoadModule.
|
||||
struct PendingModuleLoad {
|
||||
std::string name;
|
||||
u32 flags = 0;
|
||||
bool hasOption = false;
|
||||
SceKernelLMOption option{};
|
||||
|
||||
void DoState(PointerWrap &p) {
|
||||
auto s = p.Section("PendingModuleLoad", 1);
|
||||
if (!s)
|
||||
return;
|
||||
Do(p, name);
|
||||
Do(p, flags);
|
||||
Do(p, hasOption);
|
||||
p.DoVoid(&option, sizeof(option));
|
||||
}
|
||||
};
|
||||
// By waiting thread.
|
||||
static std::map<SceUID, PendingModuleLoad> g_pendingModuleLoads;
|
||||
static int eventModuleLoadDone = -1;
|
||||
// The wait ID of a thread in sceKernelLoadModule, so hleDelayResult's event can't end that wait.
|
||||
static const SceUID LOAD_MODULE_WAIT_ID = 0x4C4D;
|
||||
// STATE END
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -500,12 +527,15 @@ static bool g_runningVSH = false;
|
||||
static std::vector<SceUID> g_vshModulesToStart;
|
||||
static SceUID g_vshModuleStarting = 0;
|
||||
|
||||
static void __KernelLoadModuleDone(u64 userdata, int cyclesLate);
|
||||
|
||||
static void __KernelModuleInit() {
|
||||
actionAfterModule = __KernelRegisterActionType(AfterModuleEntryCall::Create);
|
||||
eventModuleLoadDone = CoreTiming::RegisterEvent("LoadModuleDone", &__KernelLoadModuleDone);
|
||||
}
|
||||
|
||||
void __KernelModuleDoState(PointerWrap &p) {
|
||||
auto s = p.Section("sceKernelModule", 1, 4);
|
||||
auto s = p.Section("sceKernelModule", 1, 5);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
@@ -537,6 +567,15 @@ void __KernelModuleDoState(PointerWrap &p) {
|
||||
g_startModuleHandler = 0;
|
||||
}
|
||||
|
||||
if (s >= 5) {
|
||||
Do(p, g_pendingModuleLoads);
|
||||
Do(p, eventModuleLoadDone);
|
||||
} else if (p.mode == p.MODE_READ) {
|
||||
g_pendingModuleLoads.clear();
|
||||
eventModuleLoadDone = -1;
|
||||
}
|
||||
CoreTiming::RestoreRegisterEvent(eventModuleLoadDone, "LoadModuleDone", &__KernelLoadModuleDone);
|
||||
|
||||
if (p.mode == p.MODE_READ) {
|
||||
u32 error;
|
||||
// We process these late, since they depend on loadedModules for interlinking.
|
||||
@@ -559,6 +598,7 @@ void __KernelModuleDoState(PointerWrap &p) {
|
||||
|
||||
void __KernelModuleShutdown() {
|
||||
loadedModules.clear();
|
||||
g_pendingModuleLoads.clear();
|
||||
g_vshModulesToStart.clear();
|
||||
g_vshModuleStarting = 0;
|
||||
MIPSAnalyst::Reset();
|
||||
@@ -2516,48 +2556,20 @@ int sceKernelLoadExec(const char *filename, u32 paramPtr) {
|
||||
return hleLogDebug(Log::sceModule, 0);
|
||||
}
|
||||
|
||||
u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) {
|
||||
if (!name) {
|
||||
return hleLogError(Log::Loader, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "bad filename");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(lieAboutSuccessModules); i++) {
|
||||
if (!strcmp(name, lieAboutSuccessModules[i].path)) {
|
||||
PSPModule *module = new PSPModule();
|
||||
kernelObjects.Create(module);
|
||||
loadedModules.insert(module->GetUID());
|
||||
memset(&module->nm, 0, sizeof(module->nm));
|
||||
strcpy(module->nm.name, lieAboutSuccessModules[i].name);
|
||||
module->isFake = true;
|
||||
module->nm.entry_addr = -1;
|
||||
module->nm.gp_value = -1;
|
||||
|
||||
u32 moduleSize = sizeof(module->nm);
|
||||
char tag[32];
|
||||
snprintf(tag, sizeof(tag), "SceModule-%d", module->nm.modid);
|
||||
module->modulePtr.ptr = kernelMemory.Alloc(moduleSize, true, tag);
|
||||
|
||||
// Fill the struct.
|
||||
if (module->modulePtr.IsValid()) {
|
||||
*module->modulePtr = module->nm;
|
||||
module->modulePtr.NotifyWrite("KernelModule");
|
||||
}
|
||||
|
||||
// TODO: It would be more ideal to allocate memory for this module.
|
||||
return hleLogInfo(Log::Loader, module->GetUID(), "created fake module");
|
||||
}
|
||||
}
|
||||
|
||||
// What sceKernelLoadModule does once the file has been read: decrypt the module, place it in memory
|
||||
// and link it. Returns the module's UID or an error. Logs directly rather than through hleLog*,
|
||||
// since it also runs from the LoadModuleDone event. Sets *tryLoadExec for a BOOT.BIN we can't load
|
||||
// as a module.
|
||||
static u32 KernelLoadModuleFromFile(const char *name, u32 flags, const SceKernelLMOption *lmoption, bool *tryLoadExec) {
|
||||
std::vector<uint8_t> fileData;
|
||||
if (pspFileSystem.ReadEntireFile(name, fileData) < 0) {
|
||||
const u32 error = hleLogError(Log::Loader, SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file does not exist");
|
||||
return hleDelayResult(error, "module loaded", 500);
|
||||
ERROR_LOG(Log::Loader, "sceKernelLoadModule(%s): file does not exist", name);
|
||||
return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
||||
}
|
||||
if (fileData.empty()) {
|
||||
const u32 error = hleLogError(Log::Loader, SCE_KERNEL_ERROR_FILEERR, "module file size is 0");
|
||||
return hleDelayResult(error, "module loaded", 500);
|
||||
ERROR_LOG(Log::Loader, "sceKernelLoadModule(%s): module file size is 0", name);
|
||||
return SCE_KERNEL_ERROR_FILEERR;
|
||||
}
|
||||
const int fileSize = (int)fileData.size();
|
||||
|
||||
// A .sprx installed by a PKG game update comes wrapped in an NPDRM "\0PSPEDAT" container: a
|
||||
// 0x90-byte header naming the content ID, then the payload at the offset in its u16 at 0x0C.
|
||||
@@ -2593,6 +2605,82 @@ u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) {
|
||||
// We log before hand because ELF loading logs a bunch.
|
||||
DEBUG_LOG(Log::Loader, "sceKernelLoadModule(%s, %08x)", name, flags);
|
||||
|
||||
PSPModule *module = nullptr;
|
||||
u32 magic;
|
||||
u32 error;
|
||||
std::string error_string;
|
||||
module = __KernelLoadELFFromPtr(fileData.data(), fileData.size(), 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, name, error, havePrxSeed ? prxSeed : nullptr);
|
||||
|
||||
if (!module) {
|
||||
if (magic == 0x46535000) {
|
||||
// TODO: What's actually going on here? This is needed to keep Tekken 6 working, the "proper" error breaks it, when it tries to load PARAM.SFO as a module.
|
||||
ERROR_LOG(Log::Loader, "sceKernelLoadModule(%s): Game tried to load an SFO as a module. Go figure? Magic = %08x", name, magic);
|
||||
return (u32)-1;
|
||||
}
|
||||
|
||||
PSPFileInfo info = pspFileSystem.GetFileInfo(name);
|
||||
if (info.name == "BOOT.BIN") {
|
||||
NOTICE_LOG_REPORT(Log::Loader, "Module %s is blacklisted or undecryptable - we try __KernelLoadExec", name);
|
||||
*tryLoadExec = true;
|
||||
return 0;
|
||||
}
|
||||
ERROR_LOG(Log::Loader, "sceKernelLoadModule(%s): failed to load (%08x)", name, error);
|
||||
return error;
|
||||
}
|
||||
|
||||
if (lmoption) {
|
||||
INFO_LOG(Log::sceModule,"%i=sceKernelLoadModule(name=%s,flag=%08x,%08x,%08x,%08x,position = %08x)",
|
||||
module->GetUID(),name,flags,
|
||||
lmoption->size,lmoption->mpidtext,lmoption->mpiddata,lmoption->position);
|
||||
} else {
|
||||
INFO_LOG(Log::sceModule,"%i=sceKernelLoadModule(name=%s,flag=%08x,(...))", module->GetUID(), name, flags);
|
||||
}
|
||||
return module->GetUID();
|
||||
}
|
||||
|
||||
u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) {
|
||||
if (!name) {
|
||||
return hleLogError(Log::Loader, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "bad filename");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(lieAboutSuccessModules); i++) {
|
||||
if (!strcmp(name, lieAboutSuccessModules[i].path)) {
|
||||
PSPModule *module = new PSPModule();
|
||||
kernelObjects.Create(module);
|
||||
loadedModules.insert(module->GetUID());
|
||||
memset(&module->nm, 0, sizeof(module->nm));
|
||||
strcpy(module->nm.name, lieAboutSuccessModules[i].name);
|
||||
module->isFake = true;
|
||||
module->nm.entry_addr = -1;
|
||||
module->nm.gp_value = -1;
|
||||
|
||||
u32 moduleSize = sizeof(module->nm);
|
||||
char tag[32];
|
||||
snprintf(tag, sizeof(tag), "SceModule-%d", module->nm.modid);
|
||||
module->modulePtr.ptr = kernelMemory.Alloc(moduleSize, true, tag);
|
||||
|
||||
// Fill the struct.
|
||||
if (module->modulePtr.IsValid()) {
|
||||
*module->modulePtr = module->nm;
|
||||
module->modulePtr.NotifyWrite("KernelModule");
|
||||
}
|
||||
|
||||
// TODO: It would be more ideal to allocate memory for this module.
|
||||
return hleLogInfo(Log::Loader, module->GetUID(), "created fake module");
|
||||
}
|
||||
}
|
||||
|
||||
PSPFileInfo info = pspFileSystem.GetFileInfo(name);
|
||||
if (!info.exists) {
|
||||
const u32 error = hleLogError(Log::Loader, SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file does not exist");
|
||||
return hleDelayResult(error, "module loaded", 500);
|
||||
}
|
||||
if (info.size == 0) {
|
||||
const u32 error = hleLogError(Log::Loader, SCE_KERNEL_ERROR_FILEERR, "module file size is 0");
|
||||
return hleDelayResult(error, "module loaded", 500);
|
||||
}
|
||||
const int fileSize = (int)info.size;
|
||||
|
||||
if (flags != 0) {
|
||||
WARN_LOG_REPORT(Log::Loader, "sceKernelLoadModule: unsupported flags: %08x", flags);
|
||||
}
|
||||
@@ -2614,46 +2702,64 @@ u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) {
|
||||
WARN_LOG_REPORT(Log::Loader, "sceKernelLoadModule: unsupported options size=%08x, flags=%08x, pos=%d, access=%d, data=%d, text=%d", lmoption->size, lmoption->flags, lmoption->position, lmoption->access, lmoption->mpiddata, lmoption->mpidtext);
|
||||
}
|
||||
|
||||
PSPModule *module = nullptr;
|
||||
u32 magic;
|
||||
u32 error;
|
||||
std::string error_string;
|
||||
module = __KernelLoadELFFromPtr(fileData.data(), fileData.size(), 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, name, error, havePrxSeed ? prxSeed : nullptr);
|
||||
|
||||
if (!module) {
|
||||
if (magic == 0x46535000) {
|
||||
// TODO: What's actually going on here? This is needed to keep Tekken 6 working, the "proper" error breaks it, when it tries to load PARAM.SFO as a module.
|
||||
error = -1;
|
||||
return hleDelayResult(hleLogError(Log::Loader, error, "Game tried to load an SFO as a module. Go figure? Magic = %08x", magic), "module loaded", 500);
|
||||
}
|
||||
|
||||
PSPFileInfo info = pspFileSystem.GetFileInfo(name);
|
||||
if (info.name == "BOOT.BIN") {
|
||||
NOTICE_LOG_REPORT(Log::Loader, "Module %s is blacklisted or undecryptable - we try __KernelLoadExec", name);
|
||||
// Name might get deleted.
|
||||
const std::string safeName = name;
|
||||
if (gpu) {
|
||||
gpu->Reinitialize();
|
||||
}
|
||||
return __KernelLoadExec(currentMIPS, safeName.c_str(), 0, &error_string);
|
||||
} else {
|
||||
return hleDelayResult(hleLogError(Log::Loader, error, "failed to load"), "module loaded", 500);
|
||||
}
|
||||
}
|
||||
|
||||
if (lmoption) {
|
||||
INFO_LOG(Log::sceModule,"%i=sceKernelLoadModule(name=%s,flag=%08x,%08x,%08x,%08x,position = %08x)",
|
||||
module->GetUID(),name,flags,
|
||||
lmoption->size,lmoption->mpidtext,lmoption->mpiddata,lmoption->position);
|
||||
} else {
|
||||
INFO_LOG(Log::sceModule,"%i=sceKernelLoadModule(name=%s,flag=%08x,(...))", module->GetUID(), name, flags);
|
||||
}
|
||||
|
||||
// Opening and reading the file, then about 1ms plus 30us per KB of loader work, the caller waiting
|
||||
// throughout. The loader part is what a load took on hardware beyond an open and a read of the
|
||||
// same file (pspautotests threads/scheduling/callcosts, from an SD card in a memory stick adapter).
|
||||
const int loadUs = __IoOpenDelayUs(name) + __IoReadDelayUs(fileSize) + 1000 + fileSize / 34;
|
||||
return hleDelayResult(hleNoLog(module->GetUID()), "module loaded", loadUs);
|
||||
|
||||
// The module only reaches memory once the wait is over, and until then other threads may use
|
||||
// the memory it will occupy: SOCOM: Fireteam Bravo 2's EU loader overruns its display list into
|
||||
// it while the game module loads (pspautotests modules/loadrace). A BOOT.BIN may turn into a
|
||||
// LoadExec, which can't happen from an event, so that loads right away like a call that can't wait.
|
||||
if (info.name != "BOOT.BIN" && __KernelIsDispatchEnabled() && !__IsInInterrupt()) {
|
||||
const SceUID threadID = __KernelGetCurThread();
|
||||
PendingModuleLoad &load = g_pendingModuleLoads[threadID];
|
||||
load.name = name;
|
||||
load.flags = flags;
|
||||
load.hasOption = lmoption != nullptr;
|
||||
if (lmoption) {
|
||||
load.option = *lmoption;
|
||||
}
|
||||
CoreTiming::ScheduleEvent(usToCycles(loadUs), eventModuleLoadDone, threadID);
|
||||
__KernelWaitCurThread(WAITTYPE_HLEDELAY, LOAD_MODULE_WAIT_ID, 0, 0, false, "module loaded");
|
||||
return hleLogDebug(Log::Loader, 0, "loading, %d us", loadUs);
|
||||
}
|
||||
|
||||
bool tryLoadExec = false;
|
||||
const u32 result = KernelLoadModuleFromFile(name, flags, lmoption, &tryLoadExec);
|
||||
if (tryLoadExec) {
|
||||
std::string error_string;
|
||||
// Name might get deleted.
|
||||
const std::string safeName = name;
|
||||
if (gpu) {
|
||||
gpu->Reinitialize();
|
||||
}
|
||||
return __KernelLoadExec(currentMIPS, safeName.c_str(), 0, &error_string);
|
||||
}
|
||||
return hleDelayResult(hleNoLog(result), "module loaded", loadUs);
|
||||
}
|
||||
|
||||
static void __KernelLoadModuleDone(u64 userdata, int cyclesLate) {
|
||||
const SceUID threadID = (SceUID)userdata;
|
||||
auto it = g_pendingModuleLoads.find(threadID);
|
||||
if (it == g_pendingModuleLoads.end()) {
|
||||
ERROR_LOG(Log::Loader, "No pending module load for thread %d", threadID);
|
||||
return;
|
||||
}
|
||||
PendingModuleLoad load = std::move(it->second);
|
||||
g_pendingModuleLoads.erase(it);
|
||||
|
||||
u32 error;
|
||||
if (__KernelGetWaitID(threadID, WAITTYPE_HLEDELAY, error) != LOAD_MODULE_WAIT_ID || error != 0) {
|
||||
WARN_LOG(Log::Loader, "sceKernelLoadModule(%s): thread %d stopped waiting, not loading", load.name.c_str(), threadID);
|
||||
return;
|
||||
}
|
||||
|
||||
bool tryLoadExec = false;
|
||||
const u32 result = KernelLoadModuleFromFile(load.name.c_str(), load.flags, load.hasOption ? &load.option : nullptr, &tryLoadExec);
|
||||
_dbg_assert_(!tryLoadExec);
|
||||
__KernelResumeThreadFromWait(threadID, result);
|
||||
__KernelReSchedule("module loaded");
|
||||
}
|
||||
|
||||
static u32 sceKernelLoadModuleNpDrm(const char *name, u32 flags, u32 optionAddr) {
|
||||
|
||||
Reference in new issue
Block a user