diff --git a/runtime/include/host_file_identity.h b/runtime/include/host_file_identity.h index 2cb5e193..9b190257 100644 --- a/runtime/include/host_file_identity.h +++ b/runtime/include/host_file_identity.h @@ -29,9 +29,24 @@ inline bool host_file_identity(const std::filesystem::path& path, std::string& i const bool is_remote = GetFileInformationByHandleEx(file, FileRemoteProtocolInfo, &remote, sizeof remote) != FALSE; const DWORD protocol_error = is_remote ? ERROR_SUCCESS : GetLastError(); + bool local_handle = false; + if (!is_remote) { + // A failed protocol query proves nothing about locality. Resolve the + // handle itself: mapped drives become UNC paths here and cannot take + // the local fallback when a remote provider omits protocol metadata. + WCHAR final_path[32768]{}; + const DWORD length = GetFinalPathNameByHandleW(file, final_path, 32768, FILE_NAME_NORMALIZED); + if (length >= 7 && length < 32768 && final_path[0] == L'\\' && + final_path[1] == L'\\' && final_path[2] == L'?' && final_path[3] == L'\\' && + final_path[5] == L':' && final_path[6] == L'\\') { + WCHAR drive_root[] = {final_path[4], L':', L'\\', L'\0'}; + const UINT drive_type = GetDriveTypeW(drive_root); + local_handle = drive_type == DRIVE_FIXED || drive_type == DRIVE_REMOVABLE; + } + } const bool reliable_protocol = is_remote ? remote.Protocol == 0x00020000 && remote.ProtocolMajorVersion >= 3 // WNNC_NET_LANMAN - : protocol_error == ERROR_INVALID_PARAMETER || protocol_error == ERROR_NOT_SUPPORTED; + : local_handle && (protocol_error == ERROR_INVALID_PARAMETER || protocol_error == ERROR_NOT_SUPPORTED); const bool reliable_fs = reliable_protocol && GetVolumeInformationByHandleW(file, nullptr, 0, nullptr, nullptr, nullptr, filesystem_name, 32) && diff --git a/runtime/src/mod_runtime.cpp b/runtime/src/mod_runtime.cpp index f9c1e433..ca21db8b 100644 --- a/runtime/src/mod_runtime.cpp +++ b/runtime/src/mod_runtime.cpp @@ -1873,14 +1873,21 @@ bool mod_runtime_prepare_for_launcher(const std::filesystem::path& disc_path, st bool mod_runtime_try_prepare_cached(const std::filesystem::path& disc_path) { #if defined(PSX_LAUNCHER_MOD_COMMIT_WORKER_SAFE) - auto& s = state(); - if (!s.initialized || s.main_applied || !session_plan_fp().empty()) return false; - auto probe = s.manager; - if (!probe.prepare_resources(s.game_id, disc_path, media_cache_root(), nullptr, true)) return false; - ScopedFlag flag(s.cached_prepare); - const bool ok = mod_runtime_commit(disc_path, nullptr, true); - if (ok && s.ticket_ready) psx_mod_counter_add("startup.preload_hit", 1); - return ok && s.ticket_ready; + try { + auto& s = state(); + if (!s.initialized || s.main_applied || !session_plan_fp().empty()) return false; + auto probe = s.manager; + if (!probe.prepare_resources(s.game_id, disc_path, media_cache_root(), nullptr, true)) return false; + ScopedFlag flag(s.cached_prepare); + const bool ok = mod_runtime_commit(disc_path, nullptr, true); + if (ok && s.ticket_ready) psx_mod_counter_add("startup.preload_hit", 1); + return ok && s.ticket_ready; + } catch (...) { + // Preload is best effort before the launcher exists. The ordinary + // provider path reports full-preparation errors once UI is available. + state().ticket_ready = false; + return false; + } #else (void)disc_path; return false; diff --git a/runtime/tests/test_mod_runtime.cpp b/runtime/tests/test_mod_runtime.cpp index 9304872b..20a5373b 100644 --- a/runtime/tests/test_mod_runtime.cpp +++ b/runtime/tests/test_mod_runtime.cpp @@ -301,7 +301,19 @@ static uint64_t startup_counter(const char* requested) { } int main() { +#if defined(PSX_LAUNCHER_MOD_COMMIT_WORKER_SAFE) + const fs::path root = fs::temp_directory_path() / "psxrecomp-mod-runtime-cache-test"; + const auto isolated_cache = root / "test-cache"; +#if defined(_WIN32) + check(SetEnvironmentVariableW(L"LOCALAPPDATA", isolated_cache.c_str()) != FALSE, + "isolate audited digest receipts from owner cache"); +#else + check(setenv("XDG_CACHE_HOME", isolated_cache.c_str(), 1) == 0, + "isolate audited digest receipts from owner cache"); +#endif +#else const fs::path root = fs::temp_directory_path() / "psxrecomp-mod-runtime-test"; +#endif std::error_code ec; fs::remove_all(root, ec); const std::vector stock(8 * 2352, 0); @@ -1453,11 +1465,9 @@ int main() { // A throw inside resolve must restore cached-only/provider modes. check(PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error), "invalidate ticket for resolver exception fixture"); throw_resolver = true; - bool threw = false; - try { PSXRecompV4::mod_runtime_try_prepare_cached(disc); } - catch (const std::runtime_error&) { threw = true; } + check(!PSXRecompV4::mod_runtime_try_prepare_cached(disc), + "best-effort preload contains resolver exceptions before launcher UI"); throw_resolver = false; - check(threw, "resolver exception reaches caller for unwind fixture"); fs::remove(output_path); const auto before_unwind = prepare_calls; check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && prepare_calls == before_unwind + 1,