From b5e5858e1d17da74e0060c933f822501f4b8e1ff Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:01:14 +0200 Subject: [PATCH] Use UTF-8-safe filesystem paths end-to-end --- aurora-main/lib/card/CardGciFolder.cpp | 4 +- aurora-main/lib/dolphin/card.cpp | 2 +- aurora-main/lib/dolphin/pad/pad.cpp | 25 ++-- aurora-main/lib/fs_helper.hpp | 12 +- aurora-main/lib/gfx/pipeline_cache.cpp | 3 +- aurora-main/lib/gfx/texture_replacement.cpp | 4 +- aurora-main/lib/imgui.cpp | 3 +- aurora-main/lib/webgpu/gpu_cache.cpp | 2 +- runtime/include/console_identity.h | 3 +- runtime/include/hle/dvd_contract.h | 4 +- runtime/include/nand_path.h | 28 ++--- runtime/include/recomp_mod_loader.h | 5 +- runtime/include/runtime_config.h | 21 +++- runtime/include/system_bridge.h | 3 +- runtime/src/controller_mapping_wizard.cpp | 3 +- runtime/src/hle/audio/ax_mix.cpp | 6 +- runtime/src/hle/storage/dvd.cpp | 72 ++++++----- runtime/src/hle/storage/nand_api.cpp | 49 ++++---- runtime/src/hle/storage/nand_async.cpp | 72 ++++++----- runtime/src/hle/storage/nand_fs.cpp | 126 +++++++++++--------- runtime/src/hle/storage/nand_internal.h | 40 ++++--- runtime/src/hle/storage/nand_isfs.cpp | 54 ++++----- runtime/src/hle/storage/riivolution.cpp | 49 ++++---- runtime/src/main.cpp | 29 +++-- runtime/src/recomp_mod_loader.cpp | 12 +- runtime/src/system_bridge.cpp | 9 +- 26 files changed, 334 insertions(+), 306 deletions(-) diff --git a/aurora-main/lib/card/CardGciFolder.cpp b/aurora-main/lib/card/CardGciFolder.cpp index 2b06e2e..57bd6a6 100644 --- a/aurora-main/lib/card/CardGciFolder.cpp +++ b/aurora-main/lib/card/CardGciFolder.cpp @@ -111,7 +111,9 @@ ECardResult CardGciFolder::createFile(const char* filename, size_t size, FileHan } gciFileHeader->swapEndian(); - m_files.push_back({*gciFileHeader, fileSize, reinterpret_cast(gciFilename.c_str()), false}); // push non-endian swapped header first + // push non-endian swapped header first + m_files.push_back({*gciFileHeader, fileSize, + std::u8string(gciFilename.begin(), gciFilename.end()), false}); handleOut = FileHandle(m_files.size() - 1, 0); return ECardResult::READY; diff --git a/aurora-main/lib/dolphin/card.cpp b/aurora-main/lib/dolphin/card.cpp index 626ce4f..81c450b 100644 --- a/aurora-main/lib/dolphin/card.cpp +++ b/aurora-main/lib/dolphin/card.cpp @@ -175,7 +175,7 @@ void CARDInit(const char* game, const char* maker) { std::filesystem::path cardWorkingDir; if (aurora::g_config.userPath != nullptr) - cardWorkingDir = reinterpret_cast(aurora::g_config.userPath); + cardWorkingDir = fs_path_from_string(aurora::g_config.userPath); else cardWorkingDir = std::filesystem::current_path(); diff --git a/aurora-main/lib/dolphin/pad/pad.cpp b/aurora-main/lib/dolphin/pad/pad.cpp index ed0cc8b..9cdea6c 100644 --- a/aurora-main/lib/dolphin/pad/pad.cpp +++ b/aurora-main/lib/dolphin/pad/pad.cpp @@ -1,3 +1,4 @@ +#include "../../fs_helper.hpp" #include "../../input.hpp" #include "../../internal.hpp" #include @@ -492,7 +493,7 @@ void __PADLoadMapping(aurora::input::GameController* controller) /* NOLINT(*-re return; } - std::string basePath{aurora::g_config.userPath}; + const std::filesystem::path basePath = fs_path_from_string(aurora::g_config.userPath); if (!controller->m_mappingLoaded) { __PADSetDefaultMapping(controller); controller->m_axisMapping = g_defaultAxes; @@ -500,8 +501,9 @@ void __PADLoadMapping(aurora::input::GameController* controller) /* NOLINT(*-re controller->m_mappingLoaded = true; - const auto path = fmt::format("{}/{}_{:04X}_{:04X}.controller", basePath, PADGetName(playerIndex), controller->m_vid, - controller->m_pid); + const auto path = fs_path_to_string( + basePath / fmt::format("{}_{:04X}_{:04X}.controller", PADGetName(playerIndex), controller->m_vid, + controller->m_pid)); SDL_IOStream* file = SDL_IOFromFile(path.c_str(), "rb"); if (file == nullptr) { return; @@ -1249,8 +1251,8 @@ constexpr uint32_t k_keyboardMagic = SBIG('KBND'); constexpr int32_t k_keyboardVersion = 3; static void load_keyboard_bindings() { - const auto filePath = std::filesystem::path{aurora::g_config.userPath} / "keyboard_bindings.dat"; - SDL_IOStream* file = SDL_IOFromFile(filePath.string().c_str(), "rb"); + const auto filePath = fs_path_from_string(aurora::g_config.userPath) / "keyboard_bindings.dat"; + SDL_IOStream* file = SDL_IOFromFile(fs_path_to_string(filePath).c_str(), "rb"); if (file == nullptr) { return; } @@ -1319,10 +1321,11 @@ static void load_keyboard_bindings() { } static void save_keyboard_bindings() { - const auto filePath = std::filesystem::path{aurora::g_config.userPath} / "keyboard_bindings.dat"; - SDL_IOStream* file = SDL_IOFromFile(filePath.string().c_str(), "wb"); + const auto filePath = fs_path_from_string(aurora::g_config.userPath) / "keyboard_bindings.dat"; + const auto filePathStr = fs_path_to_string(filePath); + SDL_IOStream* file = SDL_IOFromFile(filePathStr.c_str(), "wb"); if (file == nullptr) { - aurora::input::Log.warn("save_keyboard_bindings: failed to open {} for writing", filePath.string()); + aurora::input::Log.warn("save_keyboard_bindings: failed to open {} for writing", filePathStr); return; } @@ -1346,14 +1349,14 @@ void __PADWriteDeadZones(SDL_IOStream* file, // NOLINT(*-reserved-identifier) } void PADSerializeMappings() { - const std::filesystem::path basePath{aurora::g_config.userPath}; + const std::filesystem::path basePath = fs_path_from_string(aurora::g_config.userPath); for (auto& controller : aurora::input::g_GameControllers | std::views::values) { EnsureMappingLoaded(&controller); const auto filePath = basePath / fmt::format("{}_{:04X}_{:04X}.controller", aurora::input::controller_name(controller.m_index), controller.m_vid, controller.m_pid); - std::string filePathStr = filePath.string(); + std::string filePathStr = fs_path_to_string(filePath); // don't truncate the file if it already exists const char* openMode = std::filesystem::exists(filePath) ? "r+b" : "wb"; @@ -1372,7 +1375,7 @@ void PADSerializeMappings() { // start writing data at next 32-byte aligned offset const int64_t dataStart = SDL_TellIO(file) + 31 & ~31; if (dataStart == -1) { - aurora::input::Log.warn("Unable to seek in controller bindings! Path: \"{}\"", filePath.string()); + aurora::input::Log.warn("Unable to seek in controller bindings! Path: \"{}\"", filePathStr); return; } SDL_SeekIO(file, dataStart, SDL_IO_SEEK_SET); diff --git a/aurora-main/lib/fs_helper.hpp b/aurora-main/lib/fs_helper.hpp index ffb3ffa..0346642 100644 --- a/aurora-main/lib/fs_helper.hpp +++ b/aurora-main/lib/fs_helper.hpp @@ -1,11 +1,19 @@ #pragma once #include +#include +#include /** - * Converts a std::filesystem::path to a std::string, UTF-8, without exploding on Windows. + * Narrow path strings crossing the aurora boundary are UTF-8. path::string() and the + * char path constructor go through the ANSI codepage on Windows, so they must not be + * used for anything the host handed us or hands back to SDL, sqlite or ImGui. */ inline std::string fs_path_to_string(const std::filesystem::path& path) { const auto u8str = path.u8string(); - return { reinterpret_cast(u8str.c_str()) }; + return { reinterpret_cast(u8str.c_str()), u8str.size() }; +} + +inline std::filesystem::path fs_path_from_string(std::string_view utf8) { + return std::filesystem::path(std::u8string(utf8.begin(), utf8.end())); } diff --git a/aurora-main/lib/gfx/pipeline_cache.cpp b/aurora-main/lib/gfx/pipeline_cache.cpp index 812bbac..b0c94d4 100644 --- a/aurora-main/lib/gfx/pipeline_cache.cpp +++ b/aurora-main/lib/gfx/pipeline_cache.cpp @@ -2,6 +2,7 @@ #include "clear.hpp" #include "../gx/pipeline.hpp" +#include "../fs_helper.hpp" #include "../sqlite_utils.hpp" #include "../webgpu/gpu.hpp" @@ -715,7 +716,7 @@ static bool prepare_pipeline_cache_db() { return true; } - const auto path = (std::filesystem::path{g_config.pipelineCachePath} / "pipeline_cache.db").string(); + const auto path = fs_path_to_string(fs_path_from_string(g_config.pipelineCachePath) / "pipeline_cache.db"); auto ret = sqlite3_open(path.c_str(), &g_pipelineCacheDb); if (ret != SQLITE_OK) { Log.error("Failed to open pipeline cache database: {}", sqlite3_errmsg(g_pipelineCacheDb)); diff --git a/aurora-main/lib/gfx/texture_replacement.cpp b/aurora-main/lib/gfx/texture_replacement.cpp index 5307691..6791381 100644 --- a/aurora-main/lib/gfx/texture_replacement.cpp +++ b/aurora-main/lib/gfx/texture_replacement.cpp @@ -506,8 +506,8 @@ void build_index() noexcept { return; } - auto userPath = std::filesystem::path{reinterpret_cast(g_config.userPath)}; - auto cachePath = std::filesystem::path{reinterpret_cast(g_config.cachePath)}; + auto userPath = fs_path_from_string(g_config.userPath); + auto cachePath = fs_path_from_string(g_config.cachePath); s_replacementRoot = userPath / "texture_replacements"; s_dumpRoot = cachePath / "texture_dumps"; diff --git a/aurora-main/lib/imgui.cpp b/aurora-main/lib/imgui.cpp index 6129e37..15a1fe1 100644 --- a/aurora-main/lib/imgui.cpp +++ b/aurora-main/lib/imgui.cpp @@ -10,6 +10,7 @@ #include #include +#include "fs_helper.hpp" #include "internal.hpp" #include "webgpu/gpu.hpp" #include "window.hpp" @@ -37,7 +38,7 @@ void remove_legacy_ini_file(const char* basePath) noexcept { } std::error_code ec; - std::filesystem::remove(std::filesystem::path{basePath} / "imgui.ini", ec); + std::filesystem::remove(fs_path_from_string(basePath) / "imgui.ini", ec); } void create_context() noexcept { diff --git a/aurora-main/lib/webgpu/gpu_cache.cpp b/aurora-main/lib/webgpu/gpu_cache.cpp index b2a477b..8f98fe4 100644 --- a/aurora-main/lib/webgpu/gpu_cache.cpp +++ b/aurora-main/lib/webgpu/gpu_cache.cpp @@ -137,7 +137,7 @@ static void prune_stale_rows() { static bool cache_init_core() { Log.debug("SQLite version {}", sqlite3_libversion()); - const auto path = std::filesystem::path{reinterpret_cast(g_config.cachePath)} / "dawn_cache.db"; + const auto path = fs_path_from_string(g_config.cachePath) / "dawn_cache.db"; std::string file = fs_path_to_string(path); Log.debug("Using dawn cache at {}", file); auto ret = sqlite3_open(file.c_str(), &db); diff --git a/runtime/include/console_identity.h b/runtime/include/console_identity.h index 2d70745..ef46679 100644 --- a/runtime/include/console_identity.h +++ b/runtime/include/console_identity.h @@ -81,7 +81,8 @@ inline bool WriteSerial(const std::filesystem::path& path, const std::string& se return false; } - const std::filesystem::path temporary = path.string() + ".tmp"; + std::filesystem::path temporary = path; + temporary += ".tmp"; { std::ofstream output(temporary, std::ios::trunc); if (!output) { diff --git a/runtime/include/hle/dvd_contract.h b/runtime/include/hle/dvd_contract.h index f9832a8..9cb9576 100644 --- a/runtime/include/hle/dvd_contract.h +++ b/runtime/include/hle/dvd_contract.h @@ -20,14 +20,14 @@ namespace DvdFstContract { struct RegisteredFile { - std::string hostPath; + std::filesystem::path hostPath; std::string dvdPath; uint32_t size = 0; uint32_t discOffsetWords = 0; }; struct IndexedEntry { - std::string hostPath; + std::filesystem::path hostPath; std::string dvdPath; uint32_t size = 0; uint32_t discOffsetWords = 0; diff --git a/runtime/include/nand_path.h b/runtime/include/nand_path.h index 34c2858..709e4af 100644 --- a/runtime/include/nand_path.h +++ b/runtime/include/nand_path.h @@ -27,13 +27,14 @@ inline std::optional ExistingDirectory(const std::filesys if (path.empty()) { RT_LOGF(RT_TAG_NAND, "ERROR: %s\n", message); } else { - RT_LOGF(RT_TAG_NAND, "ERROR: %s: %s\n", message, path.string().c_str()); + RT_LOGF(RT_TAG_NAND, "ERROR: %s: %s\n", message, + RuntimeConfigFile::PathToUtf8(path).c_str()); } RT_LOGF(RT_TAG_NAND, "Set [paths] nand_root in Config.toml.\n"); std::string details = message ? message : "The configured NAND could not be initialized."; if (!path.empty()) { details += "\n\nPath: "; - details += path.string(); + details += RuntimeConfigFile::PathToUtf8(path); } details += "\n\nSet [paths] nand_root in Config.toml and try again."; // Same fatal idiom as the DVD and OS paths: crash artifacts first so the run @@ -51,18 +52,6 @@ inline std::filesystem::path ResolveConfiguredPath(const std::string& value) { return RuntimeConfigFile::ResolveRelativeToConfig(value); } -inline std::string PathStringWithoutTrailingSeparators(std::filesystem::path path) { - std::string text = path.string(); - while (!text.empty()) { - const char tail = text.back(); - if (tail != '\\' && tail != '/') { - break; - } - text.pop_back(); - } - return text; -} - inline std::filesystem::path ManagedNandRootPath() { return RuntimeConfigFile::ApplicationDataDirectory() / "NAND"; } @@ -134,7 +123,9 @@ inline bool SeedMissingBootstrapFiles(const std::filesystem::path& root) { const std::filesystem::path relativePath{std::string(file)}; ec.clear(); if (!CopyBootstrapFile(*payload, root, relativePath, ec)) { - RT_LOG(RT_TAG_NAND) << "could not create " << (root / relativePath).string() << std::endl; + RT_LOG(RT_TAG_NAND) << "could not create " + << RuntimeConfigFile::PathToUtf8(root / relativePath) + << std::endl; return false; } } @@ -167,7 +158,8 @@ inline std::filesystem::path CreateManagedNandRoot() { } } - RT_LOG(RT_TAG_NAND) << "using managed NAND root: " << root.string() << std::endl; + RT_LOG(RT_TAG_NAND) << "using managed NAND root: " << RuntimeConfigFile::PathToUtf8(root) + << std::endl; return root; } @@ -187,8 +179,4 @@ inline std::filesystem::path DiscoverNandRootPath() { return CreateManagedNandRoot(); } -inline std::string DiscoverNandRootString() { - return PathStringWithoutTrailingSeparators(DiscoverNandRootPath()); -} - } // namespace RuntimeNandPath diff --git a/runtime/include/recomp_mod_loader.h b/runtime/include/recomp_mod_loader.h index 5c47cb4..f527424 100644 --- a/runtime/include/recomp_mod_loader.h +++ b/runtime/include/recomp_mod_loader.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -67,8 +68,10 @@ void RunMemoryInitializers(); void RegisterPostRelInitializer(InitializerFn fn); void RunPostRelInitializers(); +// The generated call site passes a UTF-8 literal; it is decoded once here and +// stays a path from then on. void RegisterDvdOverlayRoot(std::string root); -const std::vector& DvdOverlayRoots(); +const std::vector& DvdOverlayRoots(); // Riivolution settings pinned by the distribution's recomp.yml. The XML path is // relative to the pack/overlay root; option selections use Riivolution's 1-based diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index 087c1e4..b86b723 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -67,6 +67,18 @@ struct RuntimeUserConfig { namespace RuntimeConfigFile { +// Narrow path strings are UTF-8 everywhere in the runtime; string() and the +// char path constructor would use the ANSI codepage on Windows, which drops +// characters the codepage cannot represent. +inline std::string PathToUtf8(const std::filesystem::path& path) { + const std::u8string text = path.u8string(); + return std::string(text.begin(), text.end()); +} + +inline std::filesystem::path PathFromUtf8(std::string_view text) { + return std::filesystem::path(std::u8string(text.begin(), text.end())); +} + inline constexpr const char* kConfigFileName = "Config.toml"; inline constexpr const char* kApplicationDirectoryName = "WiiCompiled"; @@ -419,7 +431,7 @@ inline RuntimeUserConfig ParseConfig(std::istream& input, std::string sourceName inline RuntimeUserConfig LoadConfigFile() { EnsureConfigFile(); std::ifstream file(ResolveConfigPath(), std::ios::binary); - return file ? ParseConfig(file, ResolveConfigPath().string()) : RuntimeUserConfig{}; + return file ? ParseConfig(file, PathToUtf8(ResolveConfigPath())) : RuntimeUserConfig{}; } inline const RuntimeUserConfig& Get() { @@ -510,7 +522,7 @@ inline bool WriteSetting(std::string_view section, std::string_view key, std::st } std::ofstream output(path, std::ios::trunc); if (!output) { - std::cerr << "[runtime-config] Unable to write " << path.string() << std::endl; + std::cerr << "[runtime-config] Unable to write " << PathToUtf8(path) << std::endl; return false; } for (const auto& outputLine : lines) { @@ -776,8 +788,7 @@ inline std::string DvdRoot(std::string fallback = "") { // never to the process working directory (docs/WHEELWIZARD_CONTRACT.md). inline std::filesystem::path ResolveRelativeTo(const std::filesystem::path& base, const std::string& value) { - // Config strings are UTF-8; the char overload would decode via the ANSI codepage. - std::filesystem::path path(std::u8string(value.begin(), value.end())); + std::filesystem::path path = PathFromUtf8(value); if (path.is_relative()) { path = base / path; } @@ -807,7 +818,7 @@ inline void LogLoadedConfig() { static const bool logged = [] { const auto& config = Get(); const auto configPath = ResolveConfigPath(); - std::cout << "[runtime-config] " << configPath.string(); + std::cout << "[runtime-config] " << PathToUtf8(configPath); if (!std::filesystem::exists(configPath)) { std::cout << " not found; using built-in defaults"; } else { diff --git a/runtime/include/system_bridge.h b/runtime/include/system_bridge.h index 18194e8..ec76421 100644 --- a/runtime/include/system_bridge.h +++ b/runtime/include/system_bridge.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -72,5 +73,5 @@ public: // `mem1Path` and MEM2 to `mem1Path + ".mem2"`, logging outcomes to `os`. static void DumpCrashHeuristics(std::ostream& os, const struct CpuContext* cpu, const uint32_t* missingGuestTarget); - static void WriteGuestMemorySnapshot(std::ostream& os, const char* mem1Path); + static void WriteGuestMemorySnapshot(std::ostream& os, const std::filesystem::path& mem1Path); }; diff --git a/runtime/src/controller_mapping_wizard.cpp b/runtime/src/controller_mapping_wizard.cpp index d88a7ec..fe1ba1d 100644 --- a/runtime/src/controller_mapping_wizard.cpp +++ b/runtime/src/controller_mapping_wizard.cpp @@ -186,7 +186,8 @@ void FinishWizard() { return; } if (!PersistMapping(guid, mapping)) { - g_wizard.status = "Failed to save mapping to " + MappingDbPath().string(); + g_wizard.status = + "Failed to save mapping to " + RuntimeConfigFile::PathToUtf8(MappingDbPath()); RT_LOG(RT_TAG_CONFIG) << "controller wizard: " << g_wizard.status << std::endl; return; } diff --git a/runtime/src/hle/audio/ax_mix.cpp b/runtime/src/hle/audio/ax_mix.cpp index 9b2ceb7..ba225e6 100644 --- a/runtime/src/hle/audio/ax_mix.cpp +++ b/runtime/src/hle/audio/ax_mix.cpp @@ -695,12 +695,14 @@ private: const auto path = FindDspCoefficientRom(); std::ifstream stream(path, std::ios::binary | std::ios::ate); if (!stream || stream.tellg() != static_cast(m_coeffs.size() * 2)) { - throw std::runtime_error("Bundled Wii DSP coefficient ROM has an invalid size: " + path.string()); + throw std::runtime_error("Bundled Wii DSP coefficient ROM has an invalid size: " + + RuntimeConfigFile::PathToUtf8(path)); } stream.seekg(0); std::array bytes{}; if (!stream.read(reinterpret_cast(bytes.data()), bytes.size())) { - throw std::runtime_error("Failed to read bundled Wii DSP coefficient ROM: " + path.string()); + throw std::runtime_error("Failed to read bundled Wii DSP coefficient ROM: " + + RuntimeConfigFile::PathToUtf8(path)); } for (size_t i = 0; i < m_coeffs.size(); ++i) { const uint16_t word = static_cast(bytes[i * 2]) << 8 | diff --git a/runtime/src/hle/storage/dvd.cpp b/runtime/src/hle/storage/dvd.cpp index a8af178..03c815c 100644 --- a/runtime/src/hle/storage/dvd.cpp +++ b/runtime/src/hle/storage/dvd.cpp @@ -40,7 +40,7 @@ namespace fs = std::filesystem; // The extracted ISO "DATA" folder. We map its "files" subfolder to the DVD // root "/" and its "sys" subfolder to "/sys/". The root is user-owned input: // it is never embedded into or copied by the public runtime. -static std::string g_dvdRoot; +static fs::path g_dvdRoot; static std::once_flag g_dvdRootOnce; static uint32_t CurrentDiscGameCode() { @@ -66,7 +66,7 @@ static uint32_t CurrentDiscGameCode() { #define DVD_FILEINFO_OFFSET_LEN 0x34 struct DVDFileEntry { - std::string hostPath; // Full Windows path + fs::path hostPath; std::string dvdPath; // Virtual Wii path (e.g., "/Race/Course.szs") uint32_t size; uint32_t discOffsetWords = 0; @@ -78,7 +78,7 @@ struct FstFileEntry { uint32_t end; uint32_t size; std::string dvdPath; - std::string hostPath; + fs::path hostPath; }; // Global State @@ -111,11 +111,9 @@ static void CopyToGuestAsDma(uint32_t dest, const uint8_t* data, size_t size) { GxNotifyGuestRamDmaWrite(dest, static_cast(size)); } -static std::string NormalizeDvdHostPath(std::string path) { - while (!path.empty() && (path.back() == '\\' || path.back() == '/')) { - path.pop_back(); - } - return path; +// Host path strings only ever leave this module as UTF-8 display text. +static std::string HostPathText(const fs::path& path) { + return RuntimeConfigFile::PathToUtf8(path); } static bool IsDvdDataRoot(const fs::path& path) { @@ -140,7 +138,7 @@ static bool IsDvdDataRoot(const fs::path& path) { [[noreturn]] static void FailDvdRoot(const char* source, const fs::path& path = {}) { RT_LOGF(RT_TAG_DVD, "ERROR: %s", source); if (!path.empty()) { - std::fprintf(stderr, ": %s", path.string().c_str()); + std::fprintf(stderr, ": %s", HostPathText(path).c_str()); } std::fprintf(stderr, "\n[dvd] Set [paths] dvd_root in Config.toml " @@ -148,14 +146,14 @@ static bool IsDvdDataRoot(const fs::path& path) { std::string details = source ? source : "The configured DVD root could not be opened."; if (!path.empty()) { details += "\n\nPath: "; - details += path.string(); + details += HostPathText(path); } details += "\n\nSet [paths] dvd_root in Config.toml to the extracted " "Mario Kart Wii DATA directory."; FailDvd("dvd_root", "DVD data is unavailable", details); } -static const std::string& GetDvdRoot() { +static const fs::path& GetDvdRoot() { std::call_once(g_dvdRootOnce, []() { const fs::path path = RuntimeConfigFile::ResolvedDvdRoot(); if (path.empty()) { @@ -164,14 +162,14 @@ static const std::string& GetDvdRoot() { if (!IsDvdDataRoot(path)) { FailDvdRoot("Configured DVD root is not an extracted DATA directory", path); } - g_dvdRoot = NormalizeDvdHostPath(path.string()); + g_dvdRoot = path; }); return g_dvdRoot; } static std::string NormalizePath(const std::string& path); -static std::string ResolveDvdMappedHostPath(const std::string& dvdPath, const std::string& fallbackHostPath); +static fs::path ResolveDvdMappedHostPath(const std::string& dvdPath, const fs::path& fallbackHostPath); static void InvokeDvdCallback(uint32_t callbackPtr, int32_t result, uint32_t fileInfoPtr) { if (callbackPtr == 0) { @@ -240,7 +238,7 @@ static void LoadFstIndex() { } g_fstLoaded = true; - fs::path fstPath = fs::path(GetDvdRoot()) / "sys" / "fst.bin"; + const fs::path fstPath = GetDvdRoot() / "sys" / "fst.bin"; std::ifstream fstFile(fstPath, std::ios::binary); if (!fstFile.is_open()) { return; @@ -333,7 +331,7 @@ static void LoadFstIndex() { entry.end = endBytes; entry.size = fileSize; entry.dvdPath = "/" + relPath; - const std::string baseHostPath = (fs::path(GetDvdRoot()) / "files" / fs::path(relPath)).string(); + const fs::path baseHostPath = GetDvdRoot() / "files" / fs::path(relPath); entry.hostPath = ResolveDvdMappedHostPath(entry.dvdPath, baseHostPath); g_fstFiles.push_back(std::move(entry)); } @@ -408,7 +406,7 @@ static void RegisterFileEntry(std::string dvdPath, const fs::path& hostPath, uin dvdPath = DvdFstContract::CanonicalizePath(dvdPath); DVDFileEntry fileEntry; - fileEntry.hostPath = hostPath.string(); + fileEntry.hostPath = hostPath; fileEntry.dvdPath = dvdPath; fileEntry.size = size; @@ -445,7 +443,7 @@ static void WalkDirectory(const fs::path& root, bool recursive, bool announceErr fs::recursive_directory_iterator it(root, fs::directory_options::skip_permission_denied, ec); if (ec) { if (announceErrors) { - RT_LOG(RT_TAG_DVD) << "WARNING: cannot enumerate " << root.string() << ": " + RT_LOG(RT_TAG_DVD) << "WARNING: cannot enumerate " << HostPathText(root) << ": " << ec.message() << std::endl; } return; @@ -458,7 +456,7 @@ static void WalkDirectory(const fs::path& root, bool recursive, bool announceErr it.increment(ec); if (ec) { if (announceErrors) { - RT_LOG(RT_TAG_DVD) << "WARNING: stopped enumerating " << root.string() << ": " + RT_LOG(RT_TAG_DVD) << "WARNING: stopped enumerating " << HostPathText(root) << ": " << ec.message() << std::endl; return; } @@ -488,7 +486,7 @@ static void ScanDirectory(const fs::path& root, const std::string& virtualPrefix const std::uintmax_t size = fs::file_size(entry.path(), entryEc); if (entryEc) { - RT_LOG(RT_TAG_DVD) << "WARNING: skipping " << entry.path().string() << ": " + RT_LOG(RT_TAG_DVD) << "WARNING: skipping " << HostPathText(entry.path()) << ": " << entryEc.message() << std::endl; return; } @@ -502,7 +500,7 @@ static void ScanDirectory(const fs::path& root, const std::string& virtualPrefix if (prefix.back() != '/' && prefix.back() != '\\') { prefix += "/"; } - std::string dvdPath = prefix + relative.string(); + std::string dvdPath = prefix + HostPathText(relative); if (!addNewFiles && !DvdEntryExists(dvdPath)) { return; } @@ -536,7 +534,7 @@ static void ApplyFolderByNameMapping(const RuntimeRiivolution::Mapping& mapping) if (!entry.is_regular_file(entryEc) || entryEc) { return; } - std::string name = entry.path().filename().string(); + std::string name = HostPathText(entry.path().filename()); RuntimeHle::LowerInPlace(name); const auto matches = discPathsByName.find(name); if (matches == discPathsByName.end()) { @@ -554,7 +552,7 @@ static void ApplyFolderByNameMapping(const RuntimeRiivolution::Mapping& mapping) WalkDirectory(mapping.hostPath, mapping.recursive, /*announceErrors=*/false, applyEntry); - RT_LOG(RT_TAG_DVD) << mapping.hostPath.string() << ": replaced " << replaced + RT_LOG(RT_TAG_DVD) << HostPathText(mapping.hostPath) << ": replaced " << replaced << " disc file(s) by filename" << std::endl; } @@ -562,7 +560,7 @@ static void ScanOverlayRoot(const RuntimeRiivolution::Overlay& overlay) { if (!overlay.patches) { // Fallback for mod roots that mirror the disc filesystem directly (not a // Riivolution pack, which wouldn't map anything useful this way). - RT_LOG(RT_TAG_DVD) << overlay.root.string() + RT_LOG(RT_TAG_DVD) << HostPathText(overlay.root) << ": no Riivolution XML found, treating the root as a disc-shaped overlay" << std::endl; ScanDirectory(overlay.root, "/"); @@ -592,7 +590,7 @@ static void ScanOverlayRoot(const RuntimeRiivolution::Overlay& overlay) { } } -static std::string ResolveDvdMappedHostPath(const std::string& dvdPath, const std::string& fallbackHostPath) { +static fs::path ResolveDvdMappedHostPath(const std::string& dvdPath, const fs::path& fallbackHostPath) { const std::string normalized = NormalizePath(dvdPath); const auto it = g_pathToEntry.find(normalized); if (it != g_pathToEntry.end() && it->second >= 0 && @@ -604,7 +602,7 @@ static std::string ResolveDvdMappedHostPath(const std::string& dvdPath, const st const fs::path candidate = overlay.root / fs::path(normalized.substr(1)); std::error_code ec; if (fs::is_regular_file(candidate, ec)) { - return candidate.string(); + return candidate; } } @@ -740,7 +738,7 @@ extern "C" const char* DVDResolveHostPathForTest(const char* dvdPath) return nullptr; } - resolved = g_fileEntries[it->second].hostPath; + resolved = HostPathText(g_fileEntries[it->second].hostPath); return resolved.c_str(); } @@ -808,7 +806,7 @@ extern "C" void DVDInit_8015EA1C() Memory::Write16(diskHeader + 0x04, 0x3031); // '01' (Maker) Memory::Write8(diskHeader + 0x06, 0x01); // Disk #1 // 4. Scan Files - fs::path rootPath(GetDvdRoot()); + const fs::path& rootPath = GetDvdRoot(); // Map "/files" -> "/" ScanDirectory(rootPath / "files", "/"); @@ -879,7 +877,7 @@ extern "C" int32_t DVDReadPrio_8015E834(uint32_t fileInfoPtr, uint32_t bufferPtr const DVDFileEntry& entry = g_fileEntries[extent->entryIndex]; if (offset < 0 || length < 0) { - return DvdReadFatal(fileInfoPtr, entry.hostPath, offset, + return DvdReadFatal(fileInfoPtr, HostPathText(entry.hostPath), offset, length > 0 ? static_cast(length) : 0, "negative DVD read offset or length"); } @@ -889,7 +887,7 @@ extern "C" int32_t DVDReadPrio_8015E834(uint32_t fileInfoPtr, uint32_t bufferPtr uint32_t uLength = (uint32_t)length; if (requestedOffset >= entry.size) { - return DvdReadFatal(fileInfoPtr, entry.hostPath, offset, uLength, + return DvdReadFatal(fileInfoPtr, HostPathText(entry.hostPath), offset, uLength, "read offset is outside the indexed DVD file"); } const uint32_t uOffset = static_cast(requestedOffset); @@ -899,14 +897,14 @@ extern "C" int32_t DVDReadPrio_8015E834(uint32_t fileInfoPtr, uint32_t bufferPtr } if (uLength != 0 && !Memory::Contains(bufferPtr, uLength)) { - return DvdReadFatal(fileInfoPtr, entry.hostPath, offset, uLength, + return DvdReadFatal(fileInfoPtr, HostPathText(entry.hostPath), offset, uLength, "DVD read destination is outside guest memory"); } std::vector tempBuf; DvdReadContract::HostReadFailure failure; if (!DvdReadContract::ReadExact(entry.hostPath, uOffset, uLength, tempBuf, failure)) { - return DvdReadFatal(fileInfoPtr, entry.hostPath, offset, uLength, + return DvdReadFatal(fileInfoPtr, HostPathText(entry.hostPath), offset, uLength, DvdReadContract::Describe(failure)); } @@ -962,11 +960,11 @@ extern "C" int32_t DVD__ReadAbsAsyncPrio_HLE_801628cc(uint32_t cmdBlockPtr, requestedLength, "absolute DVD read offset is not mapped to a host file"); } else if (requestedLength != 0 && readInfo.readLength != requestedLength) { - bytesRead = DvdReadFatal(cmdBlockPtr, readInfo.entry->hostPath, + bytesRead = DvdReadFatal(cmdBlockPtr, HostPathText(readInfo.entry->hostPath), readInfo.fileOffset, requestedLength, "requested range extends beyond the indexed DVD file"); } else if (requestedLength != 0 && !Memory::Contains(bufferPtr, requestedLength)) { - bytesRead = DvdReadFatal(cmdBlockPtr, readInfo.entry->hostPath, + bytesRead = DvdReadFatal(cmdBlockPtr, HostPathText(readInfo.entry->hostPath), readInfo.fileOffset, requestedLength, "DVD read destination is outside guest memory"); } else { @@ -977,7 +975,7 @@ extern "C" int32_t DVD__ReadAbsAsyncPrio_HLE_801628cc(uint32_t cmdBlockPtr, readInfo.readLength, tempBuf, failure)) { - bytesRead = DvdReadFatal(cmdBlockPtr, readInfo.entry->hostPath, + bytesRead = DvdReadFatal(cmdBlockPtr, HostPathText(readInfo.entry->hostPath), readInfo.fileOffset, readInfo.readLength, DvdReadContract::Describe(failure)); } else { @@ -1076,12 +1074,12 @@ extern "C" int32_t DVDLowRead_80166330(uint32_t buffer, uint32_t length, uint32_ return finish(false); } if (readInfo.readLength != length) { - ReportDvdReadError(readInfo.entry->hostPath, readInfo.fileOffset, length, + ReportDvdReadError(HostPathText(readInfo.entry->hostPath), readInfo.fileOffset, length, "requested range extends beyond the indexed DVD file"); return finish(false); } if (!Memory::Contains(buffer, length)) { - ReportDvdReadError(readInfo.entry->hostPath, readInfo.fileOffset, length, + ReportDvdReadError(HostPathText(readInfo.entry->hostPath), readInfo.fileOffset, length, "DVD read destination is outside guest memory"); return finish(false); } @@ -1093,7 +1091,7 @@ extern "C" int32_t DVDLowRead_80166330(uint32_t buffer, uint32_t length, uint32_ readInfo.readLength, tempBuf, failure)) { - ReportDvdReadError(readInfo.entry->hostPath, readInfo.fileOffset, + ReportDvdReadError(HostPathText(readInfo.entry->hostPath), readInfo.fileOffset, readInfo.readLength, DvdReadContract::Describe(failure)); return finish(false); } diff --git a/runtime/src/hle/storage/nand_api.cpp b/runtime/src/hle/storage/nand_api.cpp index abd3605..723569c 100644 --- a/runtime/src/hle/storage/nand_api.cpp +++ b/runtime/src/hle/storage/nand_api.cpp @@ -91,7 +91,7 @@ extern "C" int32_t NANDOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint32_t return NAND_RESULT_INVALID; } - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); // Existing-file write opens go through a shadow copy seeded from the original, so a // crash between NANDWrite and NANDClose cannot leave a torn file (the game patches @@ -101,23 +101,23 @@ extern "C" int32_t NANDOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint32_t // Another live handle already refers to this file. A shadow would hide the // writes from that handle, so stay in place for this open. LogNandWarning("NANDOpen", "WARNING: '%s' already has a live handle, writing in place", - hostPath.c_str()); + HostPathText(hostPath).c_str()); } else { - const std::string tempPath = SafeTempPathFor(hostPath); + const std::filesystem::path tempPath = SafeTempPathFor(hostPath); if (DiscardStaleSafeTemp(tempPath)) { std::error_code ec; std::filesystem::copy_file(hostPath, tempPath, std::filesystem::copy_options::overwrite_existing, ec); if (ec) { LogNandWarning("NANDOpen", "WARNING: could not seed shadow '%s' (%s), writing in place", - tempPath.c_str(), ec.message().c_str()); - std::remove(tempPath.c_str()); + HostPathText(tempPath).c_str(), ec.message().c_str()); + NandRemove(tempPath); } else { - FILE* shadow = std::fopen(tempPath.c_str(), "r+b"); + FILE* shadow = NandFopen(tempPath, "r+b"); if (!shadow) { LogNandWarning("NANDOpen", "WARNING: could not open shadow '%s', writing in place", - tempPath.c_str()); - std::remove(tempPath.c_str()); + HostPathText(tempPath).c_str()); + NandRemove(tempPath); } else { const int32_t shadowFd = AllocateFd(tempPath, shadow, static_cast(mode)); { @@ -141,20 +141,20 @@ extern "C" int32_t NANDOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint32_t else if (mode == 2) fopenMode = "r+b"; else if (mode == 3) fopenMode = "r+b"; - FILE* file = std::fopen(hostPath.c_str(), fopenMode); + FILE* file = NandFopen(hostPath, fopenMode); if (!file && mode >= 2) { // Try creating for write modes - file = std::fopen(hostPath.c_str(), "w+b"); + file = NandFopen(hostPath, "w+b"); } // Create parent directories and retry if (!file && CreateParentDirectories(hostPath)) { - file = std::fopen(hostPath.c_str(), mode >= 2 ? "w+b" : "rb"); + file = NandFopen(hostPath, mode >= 2 ? "w+b" : "rb"); } if (!file) { if (IsFaceLibResourcePath(path) && SeedFaceLibResource(hostPath)) { - file = std::fopen(hostPath.c_str(), fopenMode); + file = NandFopen(hostPath, fopenMode); } if (!file) { LogNandError("NANDOpen", "FAILED to open"); @@ -282,7 +282,7 @@ extern "C" int32_t NANDCreate_HLE(uint32_t pathPtr, uint32_t perm, uint32_t attr return NAND_RESULT_INVALID; } - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); CreateParentDirectories(hostPath); // Check if file already exists @@ -291,7 +291,7 @@ extern "C" int32_t NANDCreate_HLE(uint32_t pathPtr, uint32_t perm, uint32_t attr } // Create empty file - FILE* f = std::fopen(hostPath.c_str(), "wb"); + FILE* f = NandFopen(hostPath, "wb"); if (!f) { return NAND_RESULT_UNKNOWN; } @@ -307,13 +307,13 @@ extern "C" int32_t NANDDelete_HLE(uint32_t pathPtr) { return NAND_RESULT_INVALID; } - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); if (!PathExists(hostPath)) { return NAND_RESULT_NOEXISTS; } - if (std::remove(hostPath.c_str()) == 0) { + if (NandRemove(hostPath)) { return NAND_RESULT_OK; } @@ -327,7 +327,7 @@ extern "C" int32_t NANDCreateDir_HLE(uint32_t pathPtr, uint32_t perm, uint32_t a return NAND_RESULT_INVALID; } - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); if (PathExists(hostPath)) { if (IsDirectory(hostPath)) { @@ -337,11 +337,6 @@ extern "C" int32_t NANDCreateDir_HLE(uint32_t pathPtr, uint32_t perm, uint32_t a } if (CreateDirectoryPath(hostPath)) { -#ifdef _WIN32 - _mkdir(hostPath.c_str()); -#else - mkdir(hostPath.c_str(), 0755); -#endif return NAND_RESULT_OK; } @@ -369,13 +364,13 @@ extern "C" int32_t NANDMove_HLE(uint32_t srcPathPtr, uint32_t dstPathPtr) { // filename (for example /tmp/banner.bin -> /banner.bin). const std::filesystem::path dstHost = dstDirectoryHost / srcName; - if (!PathExists(srcHost.string())) { + if (!PathExists(srcHost)) { return NAND_RESULT_NOEXISTS; } - if (!IsDirectory(dstDirectoryHost.string())) { + if (!IsDirectory(dstDirectoryHost)) { return NAND_RESULT_NOEXISTS; } - if (PathExists(dstHost.string())) { + if (PathExists(dstHost)) { return NAND_RESULT_EXISTS; } @@ -396,7 +391,7 @@ extern "C" int32_t NANDGetStatus_HLE(uint32_t pathPtr, uint32_t outStatusPtr) { return NAND_RESULT_INVALID; } - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); if (!PathExists(hostPath)) { return NAND_RESULT_NOEXISTS; @@ -417,7 +412,7 @@ extern "C" int32_t NANDGetType_HLE(uint32_t pathPtr, uint32_t outTypePtr) { return NAND_RESULT_INVALID; } - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); if (!PathExists(hostPath)) { return NAND_RESULT_NOEXISTS; diff --git a/runtime/src/hle/storage/nand_async.cpp b/runtime/src/hle/storage/nand_async.cpp index 8ad225d..ae9f3d9 100644 --- a/runtime/src/hle/storage/nand_async.cpp +++ b/runtime/src/hle/storage/nand_async.cpp @@ -234,8 +234,10 @@ PPC_NATIVE_OVERRIDE(8019E7B4, NANDPrivateGetTypeAsync_HLE, int32_t, static const char kNandSafeTempSuffix[] = ".nandsafe.tmp"; -std::string SafeTempPathFor(const std::string& hostPath) { - return hostPath + kNandSafeTempSuffix; +std::filesystem::path SafeTempPathFor(const std::filesystem::path& hostPath) { + std::filesystem::path tempPath = hostPath; + tempPath += kNandSafeTempSuffix; + return tempPath; } // Push the CRT buffer out and then force the OS to put it on the platter, so the data is @@ -264,24 +266,25 @@ static bool FlushFileToDisk(FILE* file) { // Replace `targetPath` with `tempPath` in one step. Either the old or the new contents // survive a crash; there is no window where the target is truncated or partial. -static bool AtomicReplaceHostFile(const char* who, const std::string& tempPath, - const std::string& targetPath) { +static bool AtomicReplaceHostFile(const char* who, const std::filesystem::path& tempPath, + const std::filesystem::path& targetPath) { #ifdef _WIN32 - if (MoveFileExA(tempPath.c_str(), targetPath.c_str(), + if (MoveFileExW(tempPath.c_str(), targetPath.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { return true; } LogNandError(who, "ERROR: MoveFileEx('%s' -> '%s') failed (err=%lu)", - tempPath.c_str(), targetPath.c_str(), static_cast<unsigned long>(GetLastError())); + HostPathText(tempPath).c_str(), HostPathText(targetPath).c_str(), + static_cast<unsigned long>(GetLastError())); return false; #else - if (std::rename(tempPath.c_str(), targetPath.c_str()) != 0) { + if (!NandRename(tempPath, targetPath)) { LogNandError(who, "ERROR: rename('%s' -> '%s') failed", - tempPath.c_str(), targetPath.c_str()); + HostPathText(tempPath).c_str(), HostPathText(targetPath).c_str()); return false; } // Durably record the directory entry so the rename itself survives a crash. - const std::string directory = std::filesystem::path(targetPath).parent_path().string(); + const std::string directory = targetPath.parent_path().string(); const int dirFd = open(directory.c_str(), O_RDONLY); if (dirFd >= 0) { fsync(dirFd); @@ -293,23 +296,24 @@ static bool AtomicReplaceHostFile(const char* who, const std::string& tempPath, // Remove a scratch file left behind by a previous run that died between safe open and // safe close. Its contents are worthless: the original was never replaced. -bool DiscardStaleSafeTemp(const std::string& tempPath) { +bool DiscardStaleSafeTemp(const std::filesystem::path& tempPath) { if (!PathExists(tempPath)) { return true; } LogNandWarning("nand-shadow", "WARNING: discarding stale scratch file '%s' from a previous run", - tempPath.c_str()); - if (std::remove(tempPath.c_str()) == 0) { + HostPathText(tempPath).c_str()); + if (NandRemove(tempPath)) { return true; } - LogNandError("nand-shadow", "FAILED to remove stale scratch file '%s'", tempPath.c_str()); + LogNandError("nand-shadow", "FAILED to remove stale scratch file '%s'", + HostPathText(tempPath).c_str()); return false; } // True when any live handle already refers to `hostPath`, either directly or as the // commit target of a shadow. Used to keep shadow writes from hiding data behind a second // handle on the same file. -bool IsHostPathOpen(const std::string& hostPath) { +bool IsHostPathOpen(const std::filesystem::path& hostPath) { std::lock_guard<std::mutex> lock(g_fdMutex); for (const auto& entry : g_fileHandles) { if (entry.second.path == hostPath || entry.second.safeCommitPath == hostPath) { @@ -324,8 +328,8 @@ bool IsHostPathOpen(const std::string& hostPath) { // dropped and the original is left exactly as it was, and the error is returned so the // guest's close call fails instead of silently reporting success. int32_t CommitAndCloseFd(const char* who, int32_t fd, bool missingFdIsError) { - std::string tempPath; - std::string commitPath; + std::filesystem::path tempPath; + std::filesystem::path commitPath; FILE* file = nullptr; int32_t mode = 0; @@ -358,7 +362,7 @@ int32_t CommitAndCloseFd(const char* who, int32_t fd, bool missingFdIsError) { if (!needsCommit) { if (!flushed) { - LogNandError(who, "ERROR: flush of '%s' failed", tempPath.c_str()); + LogNandError(who, "ERROR: flush of '%s' failed", HostPathText(tempPath).c_str()); return NAND_RESULT_UNKNOWN; } return NAND_RESULT_OK; @@ -366,13 +370,13 @@ int32_t CommitAndCloseFd(const char* who, int32_t fd, bool missingFdIsError) { if (!flushed) { LogNandError(who, "ERROR: flush of '%s' failed, discarding it and leaving '%s' untouched", - tempPath.c_str(), commitPath.c_str()); - std::remove(tempPath.c_str()); + HostPathText(tempPath).c_str(), HostPathText(commitPath).c_str()); + NandRemove(tempPath); return NAND_RESULT_UNKNOWN; } if (!AtomicReplaceHostFile(who, tempPath, commitPath)) { - std::remove(tempPath.c_str()); + NandRemove(tempPath); return NAND_RESULT_UNKNOWN; } @@ -394,7 +398,7 @@ extern "C" int32_t NANDSafeOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint return NAND_RESULT_INVALID; } - const std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); if (hostPath.empty()) { LogNandError("NANDSafeOpen", "FAILED to translate path '%s'", path); return NAND_RESULT_INVALID; @@ -407,12 +411,13 @@ extern "C" int32_t NANDSafeOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint if (mode == 1) { // Read-only safe open reads the original in place; the library builds no scratch // copy for this case. - FILE* file = std::fopen(hostPath.c_str(), "rb"); + FILE* file = NandFopen(hostPath, "rb"); if (!file && IsFaceLibResourcePath(path) && SeedFaceLibResource(hostPath)) { - file = std::fopen(hostPath.c_str(), "rb"); + file = NandFopen(hostPath, "rb"); } if (!file) { - LogNandError("NANDSafeOpen", "FAILED to open '%s' for reading", hostPath.c_str()); + LogNandError("NANDSafeOpen", "FAILED to open '%s' for reading", + HostPathText(hostPath).c_str()); return NAND_RESULT_NOEXISTS; } @@ -425,15 +430,16 @@ extern "C" int32_t NANDSafeOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint // Write modes. The library queries the attributes of the original first, so a safe // open of a file that does not exist fails instead of creating one. if (!PathExists(hostPath)) { - LogNandError("NANDSafeOpen", "FAILED: '%s' does not exist, safe open never creates it", hostPath.c_str()); + LogNandError("NANDSafeOpen", "FAILED: '%s' does not exist, safe open never creates it", + HostPathText(hostPath).c_str()); return NAND_RESULT_NOEXISTS; } if (IsDirectory(hostPath)) { - LogNandError("NANDSafeOpen", "FAILED: '%s' is a directory", hostPath.c_str()); + LogNandError("NANDSafeOpen", "FAILED: '%s' is a directory", HostPathText(hostPath).c_str()); return NAND_RESULT_INVALID; } - const std::string tempPath = SafeTempPathFor(hostPath); + const std::filesystem::path tempPath = SafeTempPathFor(hostPath); if (!DiscardStaleSafeTemp(tempPath)) { return NAND_RESULT_ACCESS; } @@ -445,15 +451,17 @@ extern "C" int32_t NANDSafeOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint std::filesystem::copy_options::overwrite_existing, ec); if (ec) { LogNandError("NANDSafeOpen", "FAILED to seed scratch file '%s' from '%s': %s", - tempPath.c_str(), hostPath.c_str(), ec.message().c_str()); - std::remove(tempPath.c_str()); + HostPathText(tempPath).c_str(), HostPathText(hostPath).c_str(), + ec.message().c_str()); + NandRemove(tempPath); return NAND_RESULT_UNKNOWN; } - FILE* file = std::fopen(tempPath.c_str(), "r+b"); + FILE* file = NandFopen(tempPath, "r+b"); if (!file) { - LogNandError("NANDSafeOpen", "FAILED to open scratch file '%s'", tempPath.c_str()); - std::remove(tempPath.c_str()); + LogNandError("NANDSafeOpen", "FAILED to open scratch file '%s'", + HostPathText(tempPath).c_str()); + NandRemove(tempPath); return NAND_RESULT_UNKNOWN; } diff --git a/runtime/src/hle/storage/nand_fs.cpp b/runtime/src/hle/storage/nand_fs.cpp index 6b31c03..5b35e16 100644 --- a/runtime/src/hle/storage/nand_fs.cpp +++ b/runtime/src/hle/storage/nand_fs.cpp @@ -12,7 +12,7 @@ // ============================================================================ // Base path for the host Wii NAND directory (resolved at runtime). -static std::string g_dolphinWiiBase; +static std::filesystem::path g_dolphinWiiBase; static std::once_flag g_dolphinWiiBaseOnce; // ============================================================================ @@ -47,7 +47,7 @@ std::map<int32_t, FileHandle> g_fileHandles; static int32_t g_nextFd = 100; // Start at 100 to avoid confusion with stdio fds std::mutex g_fdMutex; -int32_t AllocateFd(const std::string& path, FILE* file, int32_t mode) { +int32_t AllocateFd(const std::filesystem::path& path, FILE* file, int32_t mode) { std::lock_guard<std::mutex> lock(g_fdMutex); int32_t fd = g_nextFd++; g_fileHandles[fd] = {file, path, mode, 0}; @@ -88,29 +88,50 @@ std::string CurrentNandDataDir() { return path; } -const std::string& GetNandBasePath() { +const std::filesystem::path& GetNandBasePath() { std::call_once(g_dolphinWiiBaseOnce, []() { - g_dolphinWiiBase = RuntimeNandPath::DiscoverNandRootString(); + g_dolphinWiiBase = RuntimeNandPath::DiscoverNandRootPath(); }); return g_dolphinWiiBase; } -static std::string BuildHostNandPath(std::string wiiPathStr) { - std::string hostPath = GetNandBasePath(); - for (char& c : wiiPathStr) { - if (c == '/') { -#ifdef _WIN32 - c = '\\'; -#endif - } - } +std::string HostPathText(const std::filesystem::path& path) { + return RuntimeConfigFile::PathToUtf8(path); +} - if (!wiiPathStr.empty() && (wiiPathStr[0] == '\\' || wiiPathStr[0] == '/')) { - hostPath += wiiPathStr; - } else { - hostPath += "\\"; - hostPath += wiiPathStr; +FILE* NandFopen(const std::filesystem::path& path, const char* mode) { +#ifdef _WIN32 + const std::wstring wideMode(mode, mode + std::strlen(mode)); + return _wfopen(path.c_str(), wideMode.c_str()); +#else + return std::fopen(path.c_str(), mode); +#endif +} + +bool NandRemove(const std::filesystem::path& path) { + std::error_code ec; + return std::filesystem::remove(path, ec) && !ec; +} + +bool NandRename(const std::filesystem::path& from, const std::filesystem::path& to) { + std::error_code ec; + std::filesystem::rename(from, to, ec); + return !ec; +} + +// Guest paths are absolute and already lexically resolved against the NAND root, so +// they are appended as relative components instead of replacing the root. +static std::filesystem::path BuildHostNandPath(const std::string& wiiPathStr) { + std::filesystem::path hostPath = GetNandBasePath(); + size_t cursor = 0; + while (cursor < wiiPathStr.size()) { + const size_t slash = wiiPathStr.find('/', cursor); + const size_t end = slash == std::string::npos ? wiiPathStr.size() : slash; + if (end != cursor) { + hostPath /= wiiPathStr.substr(cursor, end - cursor); + } + cursor = end + 1; } return hostPath; } @@ -169,7 +190,7 @@ static std::string NormalizeAbsoluteWiiPath(const char* wiiPath) { struct RiivolutionSaveRedirect { bool enabled = false; bool clone = false; - std::string hostDirectory; + std::filesystem::path hostDirectory; }; static std::once_flag g_riivolutionSaveRedirectOnce; @@ -187,7 +208,7 @@ static const RiivolutionSaveRedirect& GetRiivolutionSaveRedirect() { } g_riivolutionSaveRedirect.enabled = true; g_riivolutionSaveRedirect.clone = redirect->clone; - g_riivolutionSaveRedirect.hostDirectory = redirect->hostDirectory.string(); + g_riivolutionSaveRedirect.hostDirectory = redirect->hostDirectory; // Riivolution creates the redirect folder if it does not exist. CreateDirectoryPath(g_riivolutionSaveRedirect.hostDirectory); }); @@ -195,8 +216,8 @@ static const RiivolutionSaveRedirect& GetRiivolutionSaveRedirect() { return g_riivolutionSaveRedirect; } -static void CloneRiivolutionSaveIfNeeded(const std::string& sourceHostPath, - const std::string& redirectedHostPath, +static void CloneRiivolutionSaveIfNeeded(const std::filesystem::path& sourceHostPath, + const std::filesystem::path& redirectedHostPath, const RiivolutionSaveRedirect& redirect) { if (!redirect.clone || PathExists(redirectedHostPath) || !PathExists(sourceHostPath)) { return; @@ -209,11 +230,13 @@ static void CloneRiivolutionSaveIfNeeded(const std::string& sourceHostPath, std::filesystem::copy_options::skip_existing, ec); if (ec) { LogNandWarning("RiivolutionSave", "WARNING: failed to clone '%s' -> '%s': %s", - sourceHostPath.c_str(), redirectedHostPath.c_str(), ec.message().c_str()); + HostPathText(sourceHostPath).c_str(), + HostPathText(redirectedHostPath).c_str(), ec.message().c_str()); } } -static bool ResolveRiivolutionSaveHostPath(const std::string& absoluteWiiPath, std::string& outHostPath) { +static bool ResolveRiivolutionSaveHostPath(const std::string& absoluteWiiPath, + std::filesystem::path& outHostPath) { const RiivolutionSaveRedirect& redirect = GetRiivolutionSaveRedirect(); if (!redirect.enabled) { return false; @@ -232,23 +255,23 @@ static bool ResolveRiivolutionSaveHostPath(const std::string& absoluteWiiPath, s relative = absoluteWiiPath.substr(dataDir.size() + 1); } - std::filesystem::path redirected(redirect.hostDirectory); + std::filesystem::path redirected = redirect.hostDirectory; if (!relative.empty()) { redirected /= std::filesystem::path(relative); } - outHostPath = redirected.string(); + outHostPath = redirected; CloneRiivolutionSaveIfNeeded(BuildHostNandPath(absoluteWiiPath), outHostPath, redirect); return true; } -std::string TranslateNandPath(const char* wiiPath) { +std::filesystem::path TranslateNandPath(const char* wiiPath) { std::string wiiPathStr = NormalizeAbsoluteWiiPath(wiiPath); if (wiiPathStr.empty()) { - return ""; + return {}; } - std::string redirectedHostPath; + std::filesystem::path redirectedHostPath; if (ResolveRiivolutionSaveHostPath(wiiPathStr, redirectedHostPath)) { return redirectedHostPath; } @@ -266,7 +289,8 @@ struct U8Node { uint32_t size; }; -static bool ExtractFromU8(const std::string& archivePath, const char* targetName, std::vector<uint8_t>& outData) { +static bool ExtractFromU8(const std::filesystem::path& archivePath, const char* targetName, + std::vector<uint8_t>& outData) { std::ifstream file(archivePath, std::ios::binary); if (!file) { return false; @@ -352,13 +376,13 @@ static bool ExtractFromU8(const std::string& archivePath, const char* targetName // as an argument and the path predicate below hard-codes the same name. static constexpr char kFaceLibResourceName[] = "RFL_Res.dat"; -bool SeedFaceLibResource(const std::string& hostPath) { +bool SeedFaceLibResource(const std::filesystem::path& hostPath) { std::vector<uint8_t> payload; if (const auto dvdRoot = RuntimeConfigFile::ResolvedDvdRoot(); !dvdRoot.empty()) { const auto arcPath = dvdRoot / "files" / "contents" / "RFLRes01.arc"; std::error_code ec; if (std::filesystem::exists(arcPath, ec)) { - ExtractFromU8(arcPath.string(), kFaceLibResourceName, payload); + ExtractFromU8(arcPath, kFaceLibResourceName, payload); } } @@ -371,12 +395,12 @@ bool SeedFaceLibResource(const std::string& hostPath) { std::ofstream out(hostPath, std::ios::binary); if (!out) { - LogNandError("FaceLibSeed", "Failed to create %s", hostPath.c_str()); + LogNandError("FaceLibSeed", "Failed to create %s", HostPathText(hostPath).c_str()); return false; } out.write(reinterpret_cast<const char*>(payload.data()), static_cast<std::streamsize>(payload.size())); if (!out) { - LogNandError("FaceLibSeed", "Failed to write %s", hostPath.c_str()); + LogNandError("FaceLibSeed", "Failed to write %s", HostPathText(hostPath).c_str()); return false; } @@ -388,45 +412,33 @@ bool IsFaceLibResourcePath(const char* path) { } // Create directories recursively -bool CreateDirectoryPath(const std::string& path) { +bool CreateDirectoryPath(const std::filesystem::path& path) { if (path.empty()) { return true; } std::error_code ec; std::filesystem::create_directories(path, ec); - return !ec || std::filesystem::is_directory(path); + return !ec || std::filesystem::is_directory(path, ec); } // Check if a path exists -bool PathExists(const std::string& path) { -#ifdef _WIN32 - return GetFileAttributesA(path.c_str()) != INVALID_FILE_ATTRIBUTES; -#else - return access(path.c_str(), F_OK) == 0; -#endif +bool PathExists(const std::filesystem::path& path) { + std::error_code ec; + return std::filesystem::exists(path, ec) && !ec; } // Check if path is a directory -bool IsDirectory(const std::string& path) { -#ifdef _WIN32 - DWORD attrs = GetFileAttributesA(path.c_str()); - return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY); -#else - struct stat st; - return stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode); -#endif +bool IsDirectory(const std::filesystem::path& path) { + std::error_code ec; + return std::filesystem::is_directory(path, ec) && !ec; } -bool CreateParentDirectories(const std::string& path) { - size_t lastSlash = path.rfind('\\'); - if (lastSlash == std::string::npos) { - lastSlash = path.rfind('/'); - } - if (lastSlash == std::string::npos) { +bool CreateParentDirectories(const std::filesystem::path& path) { + if (!path.has_parent_path()) { return false; } - CreateDirectoryPath(path.substr(0, lastSlash)); + CreateDirectoryPath(path.parent_path()); return true; } diff --git a/runtime/src/hle/storage/nand_internal.h b/runtime/src/hle/storage/nand_internal.h index 37e4e8d..f45c617 100644 --- a/runtime/src/hle/storage/nand_internal.h +++ b/runtime/src/hle/storage/nand_internal.h @@ -29,15 +29,10 @@ #include <vector> #include <filesystem> #include <string> -#include <sys/stat.h> #ifdef _WIN32 -#include <direct.h> #include <io.h> #include <windows.h> -#define mkdir(path, mode) _mkdir(path) -#define access _access -#define F_OK 0 #else #include <fcntl.h> #include <sys/types.h> @@ -67,19 +62,19 @@ void LogNandWarning(const char* func, const char* fmt, ...); struct FileHandle { FILE* file = nullptr; - std::string path; + std::filesystem::path path; int32_t mode = 0; // 1=read, 2=write, 3=read/write uint32_t position = 0; // Non-empty only for write-mode NANDSafeOpen handles. `path` then points at the // sibling scratch file the guest is writing into, and this is the original file it // atomically replaces on NANDSafeClose. - std::string safeCommitPath; + std::filesystem::path safeCommitPath; }; extern std::map<int32_t, FileHandle> g_fileHandles; extern std::mutex g_fdMutex; -int32_t AllocateFd(const std::string& path, FILE* file, int32_t mode); +int32_t AllocateFd(const std::filesystem::path& path, FILE* file, int32_t mode); FileHandle* GetHandle(int32_t fd); void CloseFd(int32_t fd); @@ -89,18 +84,27 @@ void CloseFd(int32_t fd); uint32_t CurrentMkwTitleIdLo(); std::string CurrentNandDataDir(); -const std::string& GetNandBasePath(); -std::string TranslateNandPath(const char* wiiPath); +const std::filesystem::path& GetNandBasePath(); +std::filesystem::path TranslateNandPath(const char* wiiPath); -bool CreateDirectoryPath(const std::string& path); -bool PathExists(const std::string& path); -bool IsDirectory(const std::string& path); +bool CreateDirectoryPath(const std::filesystem::path& path); +bool PathExists(const std::filesystem::path& path); +bool IsDirectory(const std::filesystem::path& path); // Create the directory that contains `path`. False when `path` has no directory // component, i.e. there was nothing to create. -bool CreateParentDirectories(const std::string& path); +bool CreateParentDirectories(const std::filesystem::path& path); -bool SeedFaceLibResource(const std::string& hostPath); +// Host paths keep their native encoding end to end; these are the only places a NAND +// path is narrowed, and they narrow to UTF-8 for display. +std::string HostPathText(const std::filesystem::path& path); + +// fopen takes an ANSI-codepage name on Windows, which cannot express every path. +FILE* NandFopen(const std::filesystem::path& path, const char* mode); +bool NandRemove(const std::filesystem::path& path); +bool NandRename(const std::filesystem::path& from, const std::filesystem::path& to); + +bool SeedFaceLibResource(const std::filesystem::path& hostPath); bool IsFaceLibResourcePath(const char* path); // ============================================================================ @@ -178,9 +182,9 @@ enum ISFSResult { int32_t ISFS_OpenLib_Initialize(CpuContext* ctx); // Shadow-write machinery, defined with the NANDSafeOpen/NANDSafeClose section below. -std::string SafeTempPathFor(const std::string& hostPath); -bool DiscardStaleSafeTemp(const std::string& tempPath); -bool IsHostPathOpen(const std::string& hostPath); +std::filesystem::path SafeTempPathFor(const std::filesystem::path& hostPath); +bool DiscardStaleSafeTemp(const std::filesystem::path& tempPath); +bool IsHostPathOpen(const std::filesystem::path& hostPath); int32_t CommitAndCloseFd(const char* who, int32_t fd, bool missingFdIsError); // Synchronous NAND library entry points (defined in nand_api.cpp); the async diff --git a/runtime/src/hle/storage/nand_isfs.cpp b/runtime/src/hle/storage/nand_isfs.cpp index accf2c3..d8a0887 100644 --- a/runtime/src/hle/storage/nand_isfs.cpp +++ b/runtime/src/hle/storage/nand_isfs.cpp @@ -329,7 +329,7 @@ extern "C" int32_t NAND_IOS_Open_HLE(uint32_t pathPtr, uint32_t mode) { } // It's a NAND file path - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); // Seed FaceLib resources before the existence check so every open mode can // still find them on a fresh managed NAND. @@ -346,16 +346,16 @@ extern "C" int32_t NAND_IOS_Open_HLE(uint32_t pathPtr, uint32_t mode) { if (mode == 2 || mode == 3) { if (!PathExists(hostPath)) { LogNandWarning("IOS_Open", "'%s' does not exist; open mode %u never creates it", - hostPath.c_str(), mode); + HostPathText(hostPath).c_str(), mode); return ISFS_ENOENT; } fopenMode = "r+b"; // Write-only opens still need read for seeks } - FILE* file = std::fopen(hostPath.c_str(), fopenMode); + FILE* file = NandFopen(hostPath, fopenMode); if (!file) { - LogNandError("IOS_Open", "FAILED to open '%s'", hostPath.c_str()); + LogNandError("IOS_Open", "FAILED to open '%s'", HostPathText(hostPath).c_str()); return ISFS_ENOENT; } @@ -516,14 +516,9 @@ extern "C" int32_t NAND_IOS_Ioctl_HLE( return ISFS_EINVAL; } const char* path = (const char*)Memory::GetPointer(inBufPtr + 6); - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); if (CreateDirectoryPath(hostPath)) { -#ifdef _WIN32 - _mkdir(hostPath.c_str()); -#else - mkdir(hostPath.c_str(), 0755); -#endif return ISFS_OK; } return ISFS_EIO; @@ -534,16 +529,11 @@ extern "C" int32_t NAND_IOS_Ioctl_HLE( return ISFS_EINVAL; } const char* path = (const char*)Memory::GetPointer(inBufPtr); - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); - if (IsDirectory(hostPath)) { -#ifdef _WIN32 - if (RemoveDirectoryA(hostPath.c_str())) return ISFS_OK; -#else - if (rmdir(hostPath.c_str()) == 0) return ISFS_OK; -#endif - } else { - if (std::remove(hostPath.c_str()) == 0) return ISFS_OK; + // fs::remove refuses a non-empty directory, matching rmdir. + if (NandRemove(hostPath)) { + return ISFS_OK; } return ISFS_ENOENT; } @@ -553,7 +543,7 @@ extern "C" int32_t NAND_IOS_Ioctl_HLE( return ISFS_EINVAL; } const char* path = (const char*)Memory::GetPointer(inBufPtr); - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); if (!PathExists(hostPath)) { return ISFS_ENOENT; @@ -582,11 +572,11 @@ extern "C" int32_t NAND_IOS_Ioctl_HLE( return ISFS_EINVAL; } const char* path = (const char*)Memory::GetPointer(inBufPtr + 6); - std::string hostPath = TranslateNandPath(path); + const std::filesystem::path hostPath = TranslateNandPath(path); CreateParentDirectories(hostPath); // Create empty file - FILE* f = std::fopen(hostPath.c_str(), "wb"); + FILE* f = NandFopen(hostPath, "wb"); if (f) { std::fclose(f); return ISFS_OK; @@ -606,10 +596,10 @@ extern "C" int32_t NAND_IOS_Ioctl_HLE( } const char* srcPath = (const char*)Memory::GetPointer(inBufPtr); const char* dstPath = (const char*)Memory::GetPointer(inBufPtr + 0x40); - std::string srcHost = TranslateNandPath(srcPath); - std::string dstHost = TranslateNandPath(dstPath); + const std::filesystem::path srcHost = TranslateNandPath(srcPath); + const std::filesystem::path dstHost = TranslateNandPath(dstPath); - if (std::rename(srcHost.c_str(), dstHost.c_str()) == 0) { + if (NandRename(srcHost, dstHost)) { return ISFS_OK; } return ISFS_EIO; @@ -805,11 +795,11 @@ int32_t ISFS_OpenLib_Initialize(CpuContext* ctx) { g_isfsInitialized = true; // Create the title data directory if it doesn't exist - char titlePath[256]; - const std::string& base = GetNandBasePath(); - std::snprintf(titlePath, sizeof(titlePath), "%s\\title\\%08x\\%08x\\data", - base.c_str(), kNandTitleIdHi, CurrentMkwTitleIdLo()); - CreateDirectoryPath(titlePath); + char titleId[32]; + std::snprintf(titleId, sizeof(titleId), "%08x", kNandTitleIdHi); + char gameId[32]; + std::snprintf(gameId, sizeof(gameId), "%08x", CurrentMkwTitleIdLo()); + CreateDirectoryPath(GetNandBasePath() / "title" / titleId / gameId / "data"); if (!ctx) { return ISFS_OK; @@ -912,7 +902,7 @@ static int32_t HandleIsfsReadDir(uint32_t numIn, uint32_t numOut, uint32_t vecto if (wiiPath.empty()) { return ISFS_EINVAL; } - const std::string hostPath = TranslateNandPath(wiiPath.c_str()); + const std::filesystem::path hostPath = TranslateNandPath(wiiPath.c_str()); if (!IsDirectory(hostPath)) { return ISFS_ENOENT; } @@ -923,7 +913,7 @@ static int32_t HandleIsfsReadDir(uint32_t numIn, uint32_t numOut, uint32_t vecto std::vector<std::string> names; std::error_code ec; for (const auto& entry : std::filesystem::directory_iterator(hostPath, ec)) { - std::string name = entry.path().filename().string(); + std::string name = HostPathText(entry.path().filename()); if (name.empty() || name.size() > kMaxNandNameLength) { continue; } diff --git a/runtime/src/hle/storage/riivolution.cpp b/runtime/src/hle/storage/riivolution.cpp index 047a565..7fddf83 100644 --- a/runtime/src/hle/storage/riivolution.cpp +++ b/runtime/src/hle/storage/riivolution.cpp @@ -74,23 +74,14 @@ std::string RiivoGameId() { return id; } -// Every narrow path string in this file is UTF-8: string()/generic_string() -// would use the ANSI codepage on Windows, and the XML-supplied halves the -// resolved paths are built from are UTF-8 already. -std::string RiivoUtf8(const std::u8string& text) { - return std::string(reinterpret_cast<const char*>(text.c_str()), text.size()); -} - -std::string RiivoPathText(const fs::path& path) { - return RiivoUtf8(path.u8string()); -} +// Every narrow path string here is UTF-8, including the ones the XML halves of +// resolved paths are concatenated with. +using RuntimeConfigFile::PathFromUtf8; +using RuntimeConfigFile::PathToUtf8; std::string RiivoGenericText(const fs::path& path) { - return RiivoUtf8(path.generic_u8string()); -} - -fs::path RiivoPathFromUtf8(const std::string& text) { - return fs::path(std::u8string(text.begin(), text.end())); + const std::u8string text = path.generic_u8string(); + return std::string(text.begin(), text.end()); } std::string RiivoComparablePath(const fs::path& path) { @@ -106,7 +97,7 @@ void RiivoAddRoot(std::vector<RuntimeRiivolution::Overlay>& overlays, fs::path r std::error_code ec; if (!fs::is_directory(root, ec)) { RT_LOG(RT_TAG_RIIVOLUTION) << "rejected overlay root (" << (source ? source : "unknown") - << "): " << RiivoPathText(root) << " is not a reachable directory" << std::endl; + << "): " << PathToUtf8(root) << " is not a reachable directory" << std::endl; return; } @@ -125,7 +116,7 @@ void RiivoAddRoot(std::vector<RuntimeRiivolution::Overlay>& overlays, fs::path r } RT_LOG(RT_TAG_RIIVOLUTION) << "overlay root (" << (source ? source : "unknown") - << "): " << RiivoPathText(normalized) << std::endl; + << "): " << PathToUtf8(normalized) << std::endl; overlays.push_back({std::move(normalized), std::nullopt}); } @@ -154,7 +145,7 @@ std::vector<RuntimeRiivolution::Overlay> RiivoDiscoverRoots() { } for (const auto& root : RecompMod::DvdOverlayRoots()) { - RiivoAddRoot(overlays, fs::path(root), "recomp mod manifest"); + RiivoAddRoot(overlays, root, "recomp mod manifest"); } return overlays; @@ -175,7 +166,7 @@ std::optional<RiivoXmlSet> RiivoFindXmls(const fs::path& overlayRoot) { // <sd>/RetroRewind6), so externals resolve against the root's parent. const std::string& configured = RecompMod::RiivolutionXml(); if (!configured.empty()) { - const fs::path configuredXml = overlayRoot / RiivoPathFromUtf8(configured); + const fs::path configuredXml = overlayRoot / PathFromUtf8(configured); if (fs::is_regular_file(configuredXml, ec)) { return RiivoXmlSet{overlayRoot.parent_path(), {configuredXml}}; } @@ -232,7 +223,7 @@ void RiivoCollectMappings(const RiivolutionContract::Patch& patch, const std::st ++set.skippedExternals; continue; } - const fs::path hostFile = RiivoPathFromUtf8(*resolved); + const fs::path hostFile = PathFromUtf8(*resolved); if (!fs::is_regular_file(hostFile, ec)) { ++set.skippedExternals; continue; @@ -248,7 +239,7 @@ void RiivoCollectMappings(const RiivolutionContract::Patch& patch, const std::st ++set.skippedExternals; continue; } - const fs::path hostFolder = RiivoPathFromUtf8(*resolved); + const fs::path hostFolder = PathFromUtf8(*resolved); if (!fs::is_directory(hostFolder, ec)) { ++set.skippedExternals; continue; @@ -282,20 +273,20 @@ std::optional<RuntimeRiivolution::PatchSet> RiivoLoadPatchSet(const fs::path& ov for (const fs::path& xmlFile : xmlSet->xmlFiles) { const auto text = RiivoReadFile(xmlFile); if (!text) { - RT_LOG(RT_TAG_RIIVOLUTION) << "WARNING: cannot read " << RiivoPathText(xmlFile) + RT_LOG(RT_TAG_RIIVOLUTION) << "WARNING: cannot read " << PathToUtf8(xmlFile) << std::endl; continue; } auto disc = RiivolutionContract::ParseString(*text); if (!disc) { - RT_LOG(RT_TAG_RIIVOLUTION) << "WARNING: " << RiivoPathText(xmlFile) + RT_LOG(RT_TAG_RIIVOLUTION) << "WARNING: " << PathToUtf8(xmlFile) << " is not a valid Riivolution XML (version 1 wiidisc); ignoring it" << std::endl; continue; } if (!disc->IsValidForGame(gameId, std::nullopt, std::nullopt)) { - RT_LOG(RT_TAG_RIIVOLUTION) << RiivoPathText(xmlFile) << ": not valid for " << gameId + RT_LOG(RT_TAG_RIIVOLUTION) << PathToUtf8(xmlFile) << ": not valid for " << gameId << ", skipped" << std::endl; continue; } @@ -319,10 +310,10 @@ std::optional<RuntimeRiivolution::PatchSet> RiivoLoadPatchSet(const fs::path& ov if (const auto resolvedSave = RiivolutionContract::MakeAbsoluteFromRelative( sdRootGeneric, xmlDirGeneric, savegame->external)) { state.saveRedirect = - RuntimeRiivolution::SaveRedirect{RiivoPathFromUtf8(*resolvedSave), + RuntimeRiivolution::SaveRedirect{PathFromUtf8(*resolvedSave), savegame->clone}; RT_LOG(RT_TAG_RIIVOLUTION) << "savegame redirect: " - << RiivoPathText(state.saveRedirect->hostDirectory) + << PathToUtf8(state.saveRedirect->hostDirectory) << (savegame->clone ? " (clone)" : "") << std::endl; } } @@ -331,11 +322,11 @@ std::optional<RuntimeRiivolution::PatchSet> RiivoLoadPatchSet(const fs::path& ov // A pack whose XML parses but activates nothing is the most confusing // failure this layer has: the game boots, plays, and quietly shows // vanilla content. Always say what happened. - RT_LOG(RT_TAG_RIIVOLUTION) << RiivoPathText(xmlFile) << ": " << activePatches.size() + RT_LOG(RT_TAG_RIIVOLUTION) << PathToUtf8(xmlFile) << ": " << activePatches.size() << " active patch(es), " << (set.mappings.size() - before) << " mapping(s)" << std::endl; if (activePatches.empty()) { - RT_LOG(RT_TAG_RIIVOLUTION) << "WARNING: " << RiivoPathText(xmlFile) + RT_LOG(RT_TAG_RIIVOLUTION) << "WARNING: " << PathToUtf8(xmlFile) << " has no enabled options for " << gameId << "; check the riivolution option selections (recomp.yml) or " << sdRootGeneric << "/riivolution/config/" << gameId.substr(0, 4) << ".xml" @@ -344,7 +335,7 @@ std::optional<RuntimeRiivolution::PatchSet> RiivoLoadPatchSet(const fs::path& ov } if (set.skippedExternals != 0) { - RT_LOG(RT_TAG_RIIVOLUTION) << RiivoPathText(overlayRoot) << ": skipped " + RT_LOG(RT_TAG_RIIVOLUTION) << PathToUtf8(overlayRoot) << ": skipped " << set.skippedExternals << " mapping(s) whose external path does not exist" << std::endl; } diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 0c254dc..077310a 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -395,7 +395,7 @@ void InitializeProcessTranscript(int argc, char** argv) { } const std::filesystem::path path = GetRunLogDirectory() / "console.log"; - state.file.open(path.string(), std::ios::out | std::ios::trunc | std::ios::binary); + state.file.open(path, std::ios::out | std::ios::trunc | std::ios::binary); if (!state.file) { return; } @@ -571,14 +571,19 @@ std::string FormatHostStackTrace(unsigned framesToSkip) { for (USHORT i = 0; i < captured; ++i) { const DWORD64 addr = reinterpret_cast<DWORD64>(frames[i]); HMODULE module = nullptr; - char modulePath[MAX_PATH] = "?"; + std::string modulePath = "?"; DWORD64 moduleBase = 0; - if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - reinterpret_cast<LPCSTR>(frames[i]), + if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCWSTR>(frames[i]), &module) != 0 && module != nullptr) { moduleBase = reinterpret_cast<DWORD64>(module); - (void)GetModuleFileNameA(module, modulePath, MAX_PATH); + wchar_t modulePathBuffer[MAX_PATH] = L""; + const DWORD length = GetModuleFileNameW(module, modulePathBuffer, MAX_PATH); + if (length != 0) { + modulePath = RuntimeConfigFile::PathToUtf8( + std::filesystem::path(std::wstring(modulePathBuffer, length))); + } } const char* symbolName = "?"; @@ -642,7 +647,7 @@ void WriteFatalLogImpl(std::string_view reason, std::string_view extraDetails = std::string fileName = "crash_"; fileName.append(reason); fileName.append(".txt"); - std::ofstream out((runDirectory / fileName).string(), std::ios::out | std::ios::trunc); + std::ofstream out(runDirectory / fileName, std::ios::out | std::ios::trunc); if (!out) { return; } @@ -681,11 +686,12 @@ void WriteFatalLogImpl(std::string_view reason, std::string_view extraDetails = // are large. static std::atomic_bool s_memorySnapshotWritten{false}; if (!s_memorySnapshotWritten.exchange(true, std::memory_order_acq_rel)) { - SystemBridge::WriteGuestMemorySnapshot(out, (runDirectory / "mem1.bin").string().c_str()); + SystemBridge::WriteGuestMemorySnapshot(out, runDirectory / "mem1.bin"); } out.flush(); - RT_LOG(RT_TAG_RUNTIME) << "crash artifacts written to " << runDirectory.string() << std::endl; + RT_LOG(RT_TAG_RUNTIME) << "crash artifacts written to " + << RuntimeConfigFile::PathToUtf8(runDirectory) << std::endl; } void SetRuntimeExitCodeImpl(int code) { @@ -1184,10 +1190,11 @@ int RuntimeMain(int argc, char** argv) { std::filesystem::create_directories(rendererCacheDirectory, rendererPathError); if (rendererPathError) { RT_LOG(RT_TAG_RUNTIME) << "Unable to create renderer cache directory " - << rendererCacheDirectory << ": " << rendererPathError.message() << std::endl; + << RuntimeConfigFile::PathToUtf8(rendererCacheDirectory) << ": " + << rendererPathError.message() << std::endl; } - const std::string auroraUserPath = applicationDataDirectory.string(); - const std::string auroraCachePath = rendererCacheDirectory.string(); + const std::string auroraUserPath = RuntimeConfigFile::PathToUtf8(applicationDataDirectory); + const std::string auroraCachePath = RuntimeConfigFile::PathToUtf8(rendererCacheDirectory); auroraConfig.userPath = auroraUserPath.c_str(); auroraConfig.cachePath = auroraCachePath.c_str(); auroraConfig.logCallback = &RuntimeAuroraLogCallback; diff --git a/runtime/src/recomp_mod_loader.cpp b/runtime/src/recomp_mod_loader.cpp index bb7e037..104ff0d 100644 --- a/runtime/src/recomp_mod_loader.cpp +++ b/runtime/src/recomp_mod_loader.cpp @@ -33,8 +33,8 @@ std::vector<RecompMod::InitializerFn>& PostRelInitializers() { return initializers; } -std::vector<std::string>& OverlayRoots() { - static std::vector<std::string> roots; +std::vector<std::filesystem::path>& OverlayRoots() { + static std::vector<std::filesystem::path> roots; return roots; } @@ -279,17 +279,17 @@ void RegisterDvdOverlayRoot(std::string root) { // against the executable directory instead. const std::filesystem::path base = RuntimeConfigFile::ExecutableDirectory().value_or(std::filesystem::current_path()); - root = RuntimeConfigFile::ResolveRelativeTo(base, root).string(); + std::filesystem::path resolved = RuntimeConfigFile::ResolveRelativeTo(base, root); std::lock_guard<std::mutex> lock(ModMutex()); auto& roots = OverlayRoots(); - const auto it = std::find(roots.begin(), roots.end(), root); + const auto it = std::find(roots.begin(), roots.end(), resolved); if (it == roots.end()) { - roots.push_back(std::move(root)); + roots.push_back(std::move(resolved)); } } -const std::vector<std::string>& DvdOverlayRoots() { +const std::vector<std::filesystem::path>& DvdOverlayRoots() { return OverlayRoots(); } diff --git a/runtime/src/system_bridge.cpp b/runtime/src/system_bridge.cpp index 644156c..0cf5a17 100644 --- a/runtime/src/system_bridge.cpp +++ b/runtime/src/system_bridge.cpp @@ -409,14 +409,14 @@ void SystemBridge::Initialize() { RT_LOG(RT_TAG_RUNTIME) << "Executed " << count << " static constructors." << std::endl; } -void SystemBridge::WriteGuestMemorySnapshot(std::ostream& os, const char* mem1Path) { +void SystemBridge::WriteGuestMemorySnapshot(std::ostream& os, const std::filesystem::path& mem1Path) { constexpr uint32_t kMem1Base = 0x80000000u; constexpr uint32_t kMem1Size = 0x01800000u; if (Memory::Contains(kMem1Base, kMem1Size)) { std::ofstream dump(mem1Path, std::ios::binary | std::ios::trunc); dump.write(reinterpret_cast<const char*>(Memory::GetPointer(kMem1Base, kMem1Size)), kMem1Size); - os << "[runtime] MEM1 snapshot written to " << mem1Path + os << "[runtime] MEM1 snapshot written to " << RuntimeConfigFile::PathToUtf8(mem1Path) << (dump.good() ? "" : " (write failed)") << std::endl; } constexpr uint32_t kMem2Base = 0x90000000u; @@ -424,11 +424,12 @@ void SystemBridge::WriteGuestMemorySnapshot(std::ostream& os, const char* mem1Pa if (!Memory::Contains(kMem2Base, mem2Size)) { continue; } - const std::string mem2Path = std::string(mem1Path) + ".mem2"; + std::filesystem::path mem2Path = mem1Path; + mem2Path += ".mem2"; std::ofstream dump(mem2Path, std::ios::binary | std::ios::trunc); dump.write(reinterpret_cast<const char*>(Memory::GetPointer(kMem2Base, mem2Size)), mem2Size); - os << "[runtime] MEM2 snapshot written to " << mem2Path + os << "[runtime] MEM2 snapshot written to " << RuntimeConfigFile::PathToUtf8(mem2Path) << (dump.good() ? "" : " (write failed)") << std::endl; break; }