Modules: Match start/stop results, state checks and QueryModuleInfo checks

All from modules/startresult and modules/queryinfo:

- A nonzero module_start result deletes the module. sceKernelStartModule
  returns 0 for 1 (no resident) and the value itself otherwise.
- A nonzero module_stop result leaves the module started, and
  sceKernelStopModule returns CAN_NOT_STOP for 1 and the value otherwise.
- Only a never-started module can be started (ERROR otherwise). Stopping one
  that was never started gives NOT_STARTED, and unloading a started module
  gives NOT_REMOVABLE.
- sceKernelQueryModuleInfo checks the pointer before the id, and with an SDK
  version past 2.07 accepts only info sizes 0x40 and 0x60.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-08 13:17:35 +01:00
1 parent 8c5e6cbd42
commit d09368e1cf
2 files changed
+59 -18

No files matched your search

+57 -18
View File
@@ -3138,6 +3138,11 @@ static const u32 SM_OPTION_ATTR_MASK = 0x00F06000;
// What sceKernelStartModule, sceKernelStopModule and the self-stop calls check in their option
// before anything else.
// Loaded modules have no status of their own here (it stays 0), so anything before STARTING is one.
static bool IsModuleLoadedNotStarted(const PSPModule *module) {
return module->nm.status < MODULE_STATUS_STARTING;
}
static int ValidateSMOption(const SceKernelSMOption *option) {
if ((u32)(sceKernelGetCompiledSdkVersion() & 0xFFFF0000) > 0x0207FFFF && option->size != sizeof(SceKernelSMOption)) {
return SCE_KERNEL_ERROR_ILLEGAL_SIZE;
@@ -3340,10 +3345,9 @@ u32 sceKernelStartModule(u32 moduleId, u32 argsize, u32 argAddr, u32 returnValue
if (returnValueAddr)
Memory::WriteOrException_U32(0, returnValueAddr);
return hleLogInfo(Log::sceModule, moduleId, "Faked module");
} else if (module->nm.status == MODULE_STATUS_STARTED) {
// TODO: Maybe should be SCE_KERNEL_ERROR_ALREADY_STARTED, but I get SCE_KERNEL_ERROR_ERROR.
// But I also get crashes...
return hleLogError(Log::sceModule, SCE_KERNEL_ERROR_ERROR);
} else if (!IsModuleLoadedNotStarted(module)) {
// Only a module that was never started can be, not a started or a stopped one (modules/startresult).
return hleLogError(Log::sceModule, SCE_KERNEL_ERROR_ERROR, "module status %d", module->nm.status);
} else {
bool needsWait;
int ret = __KernelStartModule(moduleId, argsize, argAddr, returnValueAddr, smoption.PtrOrNull(), &needsWait);
@@ -3383,7 +3387,16 @@ static u32 sceKernelStopModule(u32 moduleId, u32 argSize, u32 argAddr, u32 retur
return hleLogInfo(Log::sceModule, 0, "faking");
}
if (module->nm.status != MODULE_STATUS_STARTED) {
return hleLogError(Log::sceModule, SCE_KERNEL_ERROR_ALREADY_STOPPED, "already stopped");
// modules/startresult.
u32 stopError = SCE_KERNEL_ERROR_ERROR;
if (IsModuleLoadedNotStarted(module)) {
stopError = SCE_KERNEL_ERROR_NOT_STARTED;
} else if (module->nm.status == MODULE_STATUS_STOPPED) {
stopError = SCE_KERNEL_ERROR_ALREADY_STOPPED;
} else if (module->nm.status == MODULE_STATUS_STOPPING) {
stopError = SCE_KERNEL_ERROR_ALREADY_STOPPING;
}
return hleLogError(Log::sceModule, stopError, "module status %d", module->nm.status);
}
u32 stopFunc = module->nm.module_stop_func;
@@ -3419,6 +3432,10 @@ static u32 sceKernelUnloadModule(u32 moduleId) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module)
return hleDelayResult(hleLogError(Log::sceModule, error), "module unloaded", 150);
// A started module (or one starting or stopping) has to be stopped first (modules/startresult).
if (!module->isFake && !IsModuleLoadedNotStarted(module) && module->nm.status != MODULE_STATUS_STOPPED) {
return hleLogError(Log::sceModule, SCE_KERNEL_ERROR_NOT_REMOVABLE, "module status %d", module->nm.status);
}
module->Cleanup();
kernelObjects.Destroy<PSPModule>(moduleId);
@@ -3536,11 +3553,28 @@ void __KernelReturnFromModuleFunc(MIPSState *mips) {
return;
}
// We can't be starting and stopping at the same time, so no need to differentiate.
if (module->nm.status == MODULE_STATUS_STARTING)
module->nm.status = MODULE_STATUS_STARTED;
if (module->nm.status == MODULE_STATUS_STOPPING)
module->nm.status = MODULE_STATUS_STOPPED;
// What the start or stop call returns (modules/startresult). A nonzero module_start result
// gets rid of the module, and returns 0 for 1 (no resident) and the value itself otherwise.
// A nonzero module_stop result leaves the module started; 1 means it refused to stop.
u32 waitResult = 0;
bool deleteModule = false;
if (module->nm.status == MODULE_STATUS_STARTING) {
if (exitStatus == 0) {
module->nm.status = MODULE_STATUS_STARTED;
waitResult = leftModuleID;
} else {
module->nm.status = MODULE_STATUS_STOPPED;
waitResult = exitStatus == 1 ? 0 : exitStatus;
deleteModule = true;
}
} else if (module->nm.status == MODULE_STATUS_STOPPING) {
if (exitStatus == 0) {
module->nm.status = MODULE_STATUS_STOPPED;
} else {
module->nm.status = MODULE_STATUS_STARTED;
waitResult = exitStatus == 1 ? SCE_KERNEL_ERROR_CAN_NOT_STOP : exitStatus;
}
}
for (auto it = module->waitingThreads.begin(), end = module->waitingThreads.end(); it < end; ++it) {
// Still waiting?
if (HLEKernel::VerifyWait(it->threadID, WAITTYPE_MODULE, leftModuleID))
@@ -3551,7 +3585,7 @@ void __KernelReturnFromModuleFunc(MIPSState *mips) {
} else {
if (it->statusPtr != 0)
Memory::WriteOrException_U32(exitStatus, it->statusPtr);
__KernelResumeThreadFromWait(it->threadID, module->nm.status == MODULE_STATUS_STARTED ? leftModuleID : 0);
__KernelResumeThreadFromWait(it->threadID, waitResult);
}
}
}
@@ -3583,7 +3617,7 @@ void __KernelReturnFromModuleFunc(MIPSState *mips) {
}
}
if (module->nm.status == MODULE_STATUS_UNLOADING) {
if (module->nm.status == MODULE_STATUS_UNLOADING || deleteModule) {
// TODO: Delete the waiting thread?
module->Cleanup();
kernelObjects.Destroy<PSPModule>(leftModuleID);
@@ -3882,16 +3916,21 @@ SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmo
static u32 sceKernelQueryModuleInfo(u32 uid, u32 infoAddr)
{
DEBUG_LOG(Log::sceModule, "sceKernelQueryModuleInfo(%i, %08x)", uid, infoAddr);
// The pointer is checked before the id, and once an SDK version past 2.07 is set, only the
// two struct sizes are accepted (modules/queryinfo).
if (!Memory::IsValidRange(infoAddr, 0x40)) {
return hleLogError(Log::sceModule, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "bad infoAddr");
}
auto info = PSPPointer<ModuleInfo>::Create(infoAddr);
if ((u32)(sceKernelGetCompiledSdkVersion() & 0xFFFF0000) > 0x0207FFFF && info->size != 0x40 && info->size != 0x60) {
return hleLogError(Log::sceModule, SCE_KERNEL_ERROR_ILLEGAL_SIZE, "bad size %d", (int)info->size);
}
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(uid, error);
if (!module) {
return error;
return hleLogError(Log::sceModule, error);
}
if (!Memory::IsValidAddress(infoAddr)) {
return hleLogError(Log::sceModule, -1, "bad infoAddr");
}
auto info = PSPPointer<ModuleInfo>::Create(infoAddr);
memcpy(info->segmentaddr, module->nm.segmentaddr, sizeof(info->segmentaddr));
memcpy(info->segmentsize, module->nm.segmentsize, sizeof(info->segmentsize));
+2
View File
@@ -361,7 +361,9 @@ tests_good = [
"modules/idlist/idlist",
"modules/loadrace/loadrace",
"modules/nostart/nostart",
"modules/queryinfo/queryinfo",
"modules/startoptions/startoptions",
"modules/startresult/startresult",
"modules/unresolved/unresolved",
"net/inet/blocking",
"net/inet/addr",