Unload the firmware modules we swap in, when the game unloads the library

Tekken 6 never reaches gameplay with the real mpeg.prx: it plays its intro
movie, returns to the title screen, starts loading a demo match and loads
forever. With the sceMpeg HLE it plays fine.

The game unloads its video libraries before the level load and expects the
memory back. It gets most of it - scePsmf and scePsmfPlayer do go away - but
mpeg.prx stays resident, 33KB of it, sitting in the middle of the region the
loader then asks for:

  08c64000 - 09e24000  18.6MB  taken  UserSbrk
  09e24000 - 09ed4000   720KB  free
  09ed4000 - 09edc300  33.5KB  taken  ELF/sceMpeg_library
  09edc300 - 09f44000   425KB  free
  09f44000 - 09f4c000    32KB  taken  UtilityModule/302_av_atrac3plus

0x09f44000 - 0x09e24000 is 0x120000, which is exactly the allocation that
fails. Without mpeg.prx in the way that span is one free block and the level
loads.

sceUtility notifies the per-library hooks with state 1 when a utility module is
loaded and -1 when it is unloaded. The hooks that swap in a firmware module
only ever handled the load, so nothing ever took them back out. That affects
sceMpeg, sceMp3, sceMp4 and sceAtrac alike; Tekken is just the game whose
memory budget is tight enough to notice.

The unload has to take out what we put in and nothing else, so the loaded ids
are remembered rather than looked up by name: a game like Death Jr ships its
own mpeg.prx and loads it itself, and freeing that would be freeing the game's
memory. Verified that Death Jr still decodes its 1033 frames with our loader
never touching its module.

Savestates from before this have no record of what was swapped in, so they keep
the old behaviour of leaving the modules loaded rather than risk freeing
something the game owns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-09-21 14:37:10 -06:00
1 parent aeadfb3134
commit 1ad17e10d1
3 files changed
+69 -6

No files matched your search

+11
View File
@@ -2026,6 +2026,17 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
return module;
}
bool KernelUnloadModuleByID(SceUID moduleId) {
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module) {
return false;
}
module->Cleanup();
kernelObjects.Destroy<PSPModule>(moduleId);
return true;
}
bool KernelModuleIsLoaded(std::string_view name) {
u32 error;
for (SceUID moduleId : loadedModules) {