Use UTF-8-safe filesystem paths end-to-end

This commit is contained in:
patchzyy
2026-08-28 19:01:14 +02:00
parent 4897e7e27d
commit b5e5858e1d
26 changed files with 334 additions and 306 deletions
+3 -1
View File
@@ -111,7 +111,9 @@ ECardResult CardGciFolder::createFile(const char* filename, size_t size, FileHan
}
gciFileHeader->swapEndian();
m_files.push_back({*gciFileHeader, fileSize, reinterpret_cast<const char8_t*>(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;
+1 -1
View File
@@ -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<const char8_t*>(aurora::g_config.userPath);
cardWorkingDir = fs_path_from_string(aurora::g_config.userPath);
else
cardWorkingDir = std::filesystem::current_path();
+14 -11
View File
@@ -1,3 +1,4 @@
#include "../../fs_helper.hpp"
#include "../../input.hpp"
#include "../../internal.hpp"
#include <dolphin/pad.h>
@@ -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);
+10 -2
View File
@@ -1,11 +1,19 @@
#pragma once
#include <filesystem>
#include <string>
#include <string_view>
/**
* 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<const char*>(u8str.c_str()) };
return { reinterpret_cast<const char*>(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()));
}
+2 -1
View File
@@ -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));
+2 -2
View File
@@ -506,8 +506,8 @@ void build_index() noexcept {
return;
}
auto userPath = std::filesystem::path{reinterpret_cast<const char8_t*>(g_config.userPath)};
auto cachePath = std::filesystem::path{reinterpret_cast<const char8_t*>(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";
+2 -1
View File
@@ -10,6 +10,7 @@
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_render.h>
#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 {
+1 -1
View File
@@ -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<const char8_t*>(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);
+2 -1
View File
@@ -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) {
+2 -2
View File
@@ -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;
+8 -20
View File
@@ -27,13 +27,14 @@ inline std::optional<std::filesystem::path> 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
+4 -1
View File
@@ -3,6 +3,7 @@
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
@@ -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<std::string>& DvdOverlayRoots();
const std::vector<std::filesystem::path>& 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
+16 -5
View File
@@ -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 {
+2 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <ostream>
#include <string>
@@ -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);
};
+2 -1
View File
@@ -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;
}
+4 -2
View File
@@ -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<std::streamoff>(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<uint8_t, kResamplingCoefficientCount * 2> bytes{};
if (!stream.read(reinterpret_cast<char*>(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<uint16_t>(bytes[i * 2]) << 8 |
+35 -37
View File
@@ -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<uint32_t>(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 "<dvd_root>/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<uint32_t>(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<uint32_t>(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<uint8_t> 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);
}
+22 -27
View File
@@ -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<int32_t>(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 -> <title home>/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;
+40 -32
View File
@@ -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;
}
+69 -57
View File
@@ -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;
}
+22 -18
View File
@@ -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
+22 -32
View File
@@ -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;
}
+20 -29
View File
@@ -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;
}
+18 -11
View File
@@ -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;
+6 -6
View File
@@ -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();
}
+5 -4
View File
@@ -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;
}