sceKernelLoadModule: Charge for reading the file and loading it

On hardware a load costs an open and a read of the file, plus about 1ms
and 30us per KB of loader work (pspautotests threads/scheduling/callcosts),
with the caller waiting throughout. It now charges sceIoOpen's and
sceIoRead's estimates for the file plus that, instead of a flat 500us.

Also notes why sceKernelLoadModuleByID fails from a game's own fd on ms0:
or host0: on hardware, which we don't emulate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-29 11:09:47 -06:00
1 parent 4ba981b5b3
commit f2eba8f711
3 files changed
+31 -17

No files matched your search

+10 -2
View File
@@ -2453,6 +2453,7 @@ u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) {
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)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.
@@ -2544,8 +2545,11 @@ u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) {
INFO_LOG(Log::sceModule,"%i=sceKernelLoadModule(name=%s,flag=%08x,(...))", module->GetUID(), name, flags);
}
// TODO: This is not the right timing and probably not the right wait type, just an approximation.
return hleDelayResult(hleNoLog(module->GetUID()), "module loaded", 500);
// 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);
}
static u32 sceKernelLoadModuleNpDrm(const char *name, u32 flags, u32 optionAddr) {
@@ -2984,6 +2988,10 @@ u32 sceKernelFindModuleByName(const char *name)
}
// The id in question here is a file handle.
// On hardware, from a game's own fd on ms0: or host0: this fails with
// SCE_KERNEL_ERROR_PROHIBIT_LOADMODULE_DEVICE: the file has to pass a kernel-only ioctl (0x00208001),
// which a user fd doesn't (it returns ILLEGAL_PERM). sceKernelLoadModule opens the file itself, so it
// passes. Whether a disc0:/umd0: fd passes is untested, so we let every device through.
static u32 sceKernelLoadModuleByID(u32 id, u32 flags, u32 lmoptionPtr) {
u32 error;
u32 handle = __IoGetFileHandleFromId(id, error);