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) <[email protected]>
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

+18 -15
View File
@@ -1053,20 +1053,26 @@ static u32 npdrmRead(FileNode *f, u8 *data, int size) {
return size;
}
int __IoOpenDelayUs(const char *filename) {
// UMD: Speed varies from 1-6ms.
// Card: Path depth matters, but typically between 10-13ms on a standard Pro Duo.
return pspFileSystem.FlagsFromFilename(filename) & FileSystemFlags::UMD ? 4000 : 10000;
}
int __IoReadDelayUs(int size) {
int us;
if (PSP_CoreParameter().compat.flags().ForceUMDReadSpeed || g_Config.iIOTimingMethod == IOTIMING_UMDSLOWREALISTIC) {
us = size / 4.2;
} else {
us = size / 100;
}
return std::max(us, 100);
}
static bool __IoRead(int &result, int id, u32 data_addr, int size, int &us) {
PROFILE_THIS_SCOPE("io_rw");
// Low estimate, may be improved later from the ReadFile result.
if (PSP_CoreParameter().compat.flags().ForceUMDReadSpeed || g_Config.iIOTimingMethod == IOTIMING_UMDSLOWREALISTIC) {
us = size / 4.2;
}
else {
us = size / 100;
}
if (us < 100) {
us = 100;
}
us = __IoReadDelayUs(size);
if (id == PSP_STDIN) {
DEBUG_LOG(Log::sceIo, "sceIoRead STDIN");
@@ -1641,10 +1647,7 @@ static u32 sceIoOpen(const char *filename, int flags, int mode) {
// These are fast to open, no delay or even rescheduling happens.
return hleLogDebug(Log::sceIo, id);
}
// UMD: Speed varies from 1-6ms.
// Card: Path depth matters, but typically between 10-13ms on a standard Pro Duo.
int delay = pspFileSystem.FlagsFromFilename(filename) & FileSystemFlags::UMD ? 4000 : 10000;
return hleDelayResult(hleLogDebug(Log::sceIo, id), "file opened", delay);
return hleDelayResult(hleLogDebug(Log::sceIo, id), "file opened", __IoOpenDelayUs(filename));
}
}
+3
View File
@@ -38,6 +38,9 @@ u32 sceIoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 ou
int __IoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec);
u32 __IoGetFileHandleFromId(u32 id, u32 &outError);
// What sceIoOpen and sceIoRead charge, for loaders that read whole files without going through them.
int __IoOpenDelayUs(const char *filename);
int __IoReadDelayUs(int size);
void ConvertTmToPspDateTime(ScePspDateTime& date_out, const tm& date_in, int microSeconds);
KernelObject *__KernelFileNodeObject();
+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);