From 17659b03c278c475e214277e70711ba1f9c8afaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 7 Nov 2026 15:33:42 +0100 Subject: [PATCH 1/2] UMD swap: Own the loader ResolveFileLoaderTarget returns UmdMount gave g_loadedFile the new loader before ResolveFileLoaderTarget, which deletes it and returns another for a PBP directory or a PS1 disc image, and wraps it in a ZipFileLoader that owns it for a zip. After that, the next swap deleted the dangling loader again (a Play crash report shows it in UpdateLoadedFile), and for a zip the wrapper that was mounted had no owner. Reproduced in headless with game.umd.insert: inserting a directory with an EBOOT.PBP, then a disc image. Co-Authored-By: Claude Opus 5.5 --- Core/Loaders.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index f76ea3c6cc..bcf158c614 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -388,11 +388,12 @@ bool UmdMount(const Path &filepath, FileLoader **fileLoader, std::string &error) delete loadedFile; return false; } - UpdateLoadedFile(loadedFile); - std::string errorString; IdentifiedFileType fileType; + // Can delete loadedFile and return another, or return a ZipFileLoader that owns it, so what it + // returns is what g_loadedFile owns. loadedFile = ResolveFileLoaderTarget(loadedFile, &fileType, &errorString); + UpdateLoadedFile(loadedFile); *fileLoader = loadedFile; From 6cee44702e0f41e472c1f4fd1119b9ae484704a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 7 Nov 2026 15:59:42 +0100 Subject: [PATCH 2/2] UMD swap: Delete the old disc's loader only once the new one is mounted UmdMount deleted the old loader as soon as the new file was resolved, but the old disc's file systems stayed mounted until MountGameISO replaced them. A swap that then failed (an unsupported file, or an ISO ConstructBlockDevice rejects) left the game reading its old disc through a deleted loader, and even a successful swap could free it under a read still going on the IO thread. Now the new disc is mounted first, which takes pspFileSystem's lock that reads hold, and a failed swap deletes the new loader and leaves the old disc alone. Co-Authored-By: Claude Opus 5.5 --- Core/Loaders.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index bcf158c614..3197a6dff2 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -393,9 +393,6 @@ bool UmdMount(const Path &filepath, FileLoader **fileLoader, std::string &error) // Can delete loadedFile and return another, or return a ZipFileLoader that owns it, so what it // returns is what g_loadedFile owns. loadedFile = ResolveFileLoaderTarget(loadedFile, &fileType, &errorString); - UpdateLoadedFile(loadedFile); - - *fileLoader = loadedFile; switch (fileType) { case IdentifiedFileType::PSP_ISO: @@ -404,14 +401,21 @@ bool UmdMount(const Path &filepath, FileLoader **fileLoader, std::string &error) case IdentifiedFileType::PSP_UMD_VIDEO_ISO: if (!MountGameISO(loadedFile, &error)) { error = "mounting the ISO failed: " + error; + delete loadedFile; return false; } break; default: error = "Unsupported file type: " + std::string(IdentifiedFileTypeToString(fileType)) + " " + errorString; + delete loadedFile; return false; - break; } + + // Only now, with the old disc's file systems replaced, is the old loader unused: a swap that + // fails leaves the old disc mounted. Reads hold pspFileSystem's lock, which Mount takes, so none + // is still going on the IO thread either. + UpdateLoadedFile(loadedFile); + *fileLoader = loadedFile; return true; }