From 96c1bc12c3bcc994aaaef192e8e8d887de229c6c Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 26 Oct 2026 20:18:16 +0100 Subject: [PATCH 1/3] Reuse the effective disc reader within resident pack preparation --- runtime/CMakeLists.txt | 10 +++++ runtime/include/mod_disc_read_session.h | 43 ++++++++++++++++++ runtime/src/mod_resident.cpp | 2 + runtime/src/mod_runtime.cpp | 23 ++++++---- runtime/tests/test_mod_disc_read_session.cpp | 46 ++++++++++++++++++++ 5 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 runtime/include/mod_disc_read_session.h create mode 100644 runtime/tests/test_mod_disc_read_session.cpp diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 7264acdd..4ef969e6 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -2441,3 +2441,13 @@ if(BUILD_TESTING) target_link_libraries(${fixture} PRIVATE psx_texture_image_decode Threads::Threads) endforeach() endif() + +if(BUILD_TESTING) + add_executable(mod_disc_read_session_test tests/test_mod_disc_read_session.cpp + src/iso_reader.cpp src/cue_sheet.cpp) + target_include_directories(mod_disc_read_session_test PRIVATE include ../recompiler/include) + target_compile_features(mod_disc_read_session_test PRIVATE cxx_std_17) + target_link_libraries(mod_disc_read_session_test PRIVATE chdr-static) + add_test(NAME mod_disc_read_session_test COMMAND mod_disc_read_session_test + "${CMAKE_CURRENT_BINARY_DIR}/disc-read-session-fixture") +endif() diff --git a/runtime/include/mod_disc_read_session.h b/runtime/include/mod_disc_read_session.h new file mode 100644 index 00000000..f4593737 --- /dev/null +++ b/runtime/include/mod_disc_read_session.h @@ -0,0 +1,43 @@ +#pragma once +#include "iso_reader.h" +#include +#include + +namespace PSXRecompV4 { +/* Preparation-local host IO only. No file extents or patched sectors are cached. + * A fresh scope opens the effective disc again; nested scopes share its handle. + * Directory lookups still read the current effective sector overlay. */ +struct ModDiscReadSessionState { + unsigned depth = 0; + std::string mount; + std::unique_ptr reader; +}; +inline ModDiscReadSessionState& mod_disc_read_session_state() { + static thread_local ModDiscReadSessionState state; + return state; +} +class ModDiscReadSession { +public: + ModDiscReadSession() { ++mod_disc_read_session_state().depth; } + ~ModDiscReadSession() { + auto& s = mod_disc_read_session_state(); + if (!--s.depth) { s.reader.reset(); s.mount.clear(); } + } + ModDiscReadSession(const ModDiscReadSession&) = delete; + ModDiscReadSession& operator=(const ModDiscReadSession&) = delete; +}; +/* Outside the bounded prepare scope, retain the caller's original fresh reader. + * A changed mount invalidates the shared reader. Failed opens are never cached. */ +inline PS1::ISOReader* mod_disc_read_reader(const std::string& mount, + PS1::ISOReader& fresh) { + auto& s = mod_disc_read_session_state(); + if (!s.depth) return fresh.Open(mount) ? &fresh : nullptr; + if (!s.reader || s.mount != mount) { + s.reader.reset(); s.mount.clear(); + auto next = std::make_unique(); + if (!next->Open(mount)) return nullptr; + s.mount = mount; s.reader = std::move(next); + } + return s.reader.get(); +} +} // namespace PSXRecompV4 diff --git a/runtime/src/mod_resident.cpp b/runtime/src/mod_resident.cpp index d1aa3bde..ab70d7f2 100644 --- a/runtime/src/mod_resident.cpp +++ b/runtime/src/mod_resident.cpp @@ -11,6 +11,7 @@ #include "mod_resident.h" #include "mod_plugins.h" #include "mod_runtime.h" +#include "mod_disc_read_session.h" #include "psx_sha256.h" #include "host_sha256.h" @@ -397,6 +398,7 @@ extern "C" const PSXResidentPack* psx_resident_prepare(const PSXResidentSpec* sp psx_mod_counter_add("resident.invalid_spec", 1); return nullptr; } + PSXRecompV4::ModDiscReadSession disc_reads; Record& rec = record_for(spec); rec.pack.reset(); rec.error.clear(); diff --git a/runtime/src/mod_runtime.cpp b/runtime/src/mod_runtime.cpp index 29ef2bdd..386dfe3e 100644 --- a/runtime/src/mod_runtime.cpp +++ b/runtime/src/mod_runtime.cpp @@ -9,6 +9,7 @@ #include "func_override.h" #include "host_sha256.h" #include "iso_reader.h" +#include "mod_disc_read_session.h" #include "mod_packages.h" #include "mod_plugins.h" #include "mod_netplay.h" @@ -3063,11 +3064,13 @@ extern "C" int psx_mod_read_disc_file(const char* path, void* buffer, const auto& s = state(); const auto mount = mounted_disc_path(s); if (mount.empty()) return 0; - PS1::ISOReader reader; + PS1::ISOReader fresh; + PS1::ISOReader* shared = mod_disc_read_reader(mount.string(), fresh); + if (!shared) return 0; + PS1::ISOReader& reader = *shared; uint32_t lba = 0, bytes = 0; bool directory = false; - if (!reader.Open(mount.string()) || - !find_effective_file(reader, path, lba, bytes, directory) || + if (!find_effective_file(reader, path, lba, bytes, directory) || directory || !bytes || bytes > kMaxDiscFileBytes) return 0; if (!buffer) { *size = bytes; return 1; } @@ -3098,11 +3101,13 @@ extern "C" int psx_mod_disc_file_extent(const char* path, uint32_t* lba, const auto& s = state(); const auto mount = mounted_disc_path(s); if (mount.empty()) return 0; - PS1::ISOReader reader; + PS1::ISOReader fresh; + PS1::ISOReader* shared = mod_disc_read_reader(mount.string(), fresh); + if (!shared) return 0; + PS1::ISOReader& reader = *shared; uint32_t first = 0, bytes = 0; bool directory = false; - if (!reader.Open(mount.string()) || - !find_effective_file(reader, path, first, bytes, directory) || + if (!find_effective_file(reader, path, first, bytes, directory) || directory || !bytes || bytes > kMaxDiscFileBytes) return 0; *lba = first; @@ -3128,9 +3133,11 @@ bool mod_runtime_read_disc_file_sectors(const std::string& path, uint32_t max_by const auto& s = state(); const auto mount = mounted_disc_path(s); if (mount.empty()) return fail("no disc mounted"); - PS1::ISOReader reader; + PS1::ISOReader fresh; + PS1::ISOReader* shared = mod_disc_read_reader(mount.string(), fresh); + if (!shared) return fail("cannot open disc"); + PS1::ISOReader& reader = *shared; bool directory = false; - if (!reader.Open(mount.string())) return fail("cannot open disc"); if (!find_effective_file(reader, path, lba, size, directory) || directory) return fail("not found on the disc"); if (!size || size > kMaxDiscFileBytes || (max_bytes && size > max_bytes)) diff --git a/runtime/tests/test_mod_disc_read_session.cpp b/runtime/tests/test_mod_disc_read_session.cpp new file mode 100644 index 00000000..fa653f83 --- /dev/null +++ b/runtime/tests/test_mod_disc_read_session.cpp @@ -0,0 +1,46 @@ +/* Host-only scoped-reader regression; generated empty CUE/BIN fixture, no game data. */ +#ifdef NDEBUG +#undef NDEBUG +#endif +#include "mod_disc_read_session.h" +#include +#include +#include +#include +#include +#include +#include +using namespace PSXRecompV4; +int main(int argc,char**argv) { + assert(argc==2); + const auto dir=std::filesystem::path(argv[1])/std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count()); + assert(!std::filesystem::exists(dir));std::filesystem::create_directories(dir); + {std::ofstream out(dir/"data.bin",std::ios::binary);std::vector raw(2352*24,0);out.write(raw.data(),raw.size());} + for(const char* name:{"first.cue","second.cue"}){ + std::ofstream out(dir/name);out<<"FILE \"data.bin\" BINARY\n TRACK 01 MODE2/2352\n INDEX 01 00:00:00\n"; + } + const std::string first_path=(dir/"first.cue").string(),second_path=(dir/"second.cue").string(); + auto& state=mod_disc_read_session_state(); + PS1::ISOReader fresh; + assert(!state.depth && !state.reader); + assert(mod_disc_read_reader(first_path,fresh)==&fresh); + { + ModDiscReadSession outer; + PS1::ISOReader a,b,c; + auto* first=mod_disc_read_reader(first_path,a); + assert(first && first!=&a && state.depth==1); + { ModDiscReadSession inner; + assert(mod_disc_read_reader(first_path,b)==first && state.depth==2); } + assert(state.depth==1 && mod_disc_read_reader(first_path,c)==first); + assert(!mod_disc_read_reader("missing-disc-must-not-exist.cue",c)); + assert(!state.reader && state.mount.empty()); + assert(mod_disc_read_reader(second_path,b) && state.mount==second_path); + assert(mod_disc_read_reader(first_path,c) && state.mount==first_path); + } + assert(!state.depth && !state.reader && state.mount.empty()); + try { ModDiscReadSession scope; assert(mod_disc_read_reader(first_path,fresh)); throw std::runtime_error("test"); } + catch(const std::runtime_error&) {} + assert(!state.depth && !state.reader); + puts("PASS fresh reader outside scope, reuse, nested scopes, missing/change mount, RAII cleanup"); +} From b66b37b85516f0692ff156b63e3a2866c6d3740a Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 26 Oct 2026 21:03:57 +0100 Subject: [PATCH 2/3] Register the scoped-reader test beside the resident fixture --- runtime/CMakeLists.txt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 4ef969e6..0ea09eb2 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -1579,6 +1579,14 @@ if(BUILD_TESTING) endif() add_test(NAME mod_resident_test COMMAND mod_resident_test) + add_executable(mod_disc_read_session_test tests/test_mod_disc_read_session.cpp + src/iso_reader.cpp src/cue_sheet.cpp) + target_include_directories(mod_disc_read_session_test PRIVATE include ../recompiler/include) + target_compile_features(mod_disc_read_session_test PRIVATE cxx_std_17) + target_link_libraries(mod_disc_read_session_test PRIVATE chdr-static) + add_test(NAME mod_disc_read_session_test COMMAND mod_disc_read_session_test + "${CMAKE_CURRENT_BINARY_DIR}/disc-read-session-fixture") + if(WIN32) add_executable(autocompile_child_env_test tests/test_autocompile_child_env.c) target_include_directories(autocompile_child_env_test PRIVATE src) @@ -2441,13 +2449,3 @@ if(BUILD_TESTING) target_link_libraries(${fixture} PRIVATE psx_texture_image_decode Threads::Threads) endforeach() endif() - -if(BUILD_TESTING) - add_executable(mod_disc_read_session_test tests/test_mod_disc_read_session.cpp - src/iso_reader.cpp src/cue_sheet.cpp) - target_include_directories(mod_disc_read_session_test PRIVATE include ../recompiler/include) - target_compile_features(mod_disc_read_session_test PRIVATE cxx_std_17) - target_link_libraries(mod_disc_read_session_test PRIVATE chdr-static) - add_test(NAME mod_disc_read_session_test COMMAND mod_disc_read_session_test - "${CMAKE_CURRENT_BINARY_DIR}/disc-read-session-fixture") -endif() From 15e0f20988b4c4793735290ac17965491c64a579 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 26 Oct 2026 16:46:59 -0700 Subject: [PATCH 3/3] Remove scoped disc-reader fixtures after closing handles --- runtime/tests/test_mod_disc_read_session.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/runtime/tests/test_mod_disc_read_session.cpp b/runtime/tests/test_mod_disc_read_session.cpp index fa653f83..f36f2ca4 100644 --- a/runtime/tests/test_mod_disc_read_session.cpp +++ b/runtime/tests/test_mod_disc_read_session.cpp @@ -16,6 +16,16 @@ int main(int argc,char**argv) { const auto dir=std::filesystem::path(argv[1])/std::to_string( std::chrono::steady_clock::now().time_since_epoch().count()); assert(!std::filesystem::exists(dir));std::filesystem::create_directories(dir); + /* Declared before the readers, so their handles close before removal on + * Windows. Each successful run owns and removes only its unique fixture. */ + struct FixtureCleanup { + std::filesystem::path dir; + ~FixtureCleanup() { + std::error_code ec; + std::filesystem::remove_all(dir,ec); + assert(!ec); + } + } cleanup{dir}; {std::ofstream out(dir/"data.bin",std::ios::binary);std::vector raw(2352*24,0);out.write(raw.data(),raw.size());} for(const char* name:{"first.cue","second.cue"}){ std::ofstream out(dir/name);out<<"FILE \"data.bin\" BINARY\n TRACK 01 MODE2/2352\n INDEX 01 00:00:00\n";