diff options
Diffstat (limited to 'src/common/fs')
| -rw-r--r-- | src/common/fs/fs_paths.h | 1 | ||||
| -rw-r--r-- | src/common/fs/path_util.cpp | 33 | ||||
| -rw-r--r-- | src/common/fs/path_util.h | 18 | ||||
| -rw-r--r-- | src/common/fs/ryujinx_compat.cpp | 93 | ||||
| -rw-r--r-- | src/common/fs/ryujinx_compat.h | 40 | ||||
| -rw-r--r-- | src/common/fs/symlink.cpp | 43 | ||||
| -rw-r--r-- | src/common/fs/symlink.h | 12 |
7 files changed, 221 insertions, 19 deletions
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index 5cdf9be39d..640a83c44b 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -33,6 +33,7 @@ #define SUDACHI_DIR "sudachi" #define YUZU_DIR "yuzu" #define SUYU_DIR "suyu" +#define RYUJINX_DIR "Ryujinx" // yuzu-specific files #define LOG_FILE "eden_log.txt" diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index a095e0c239..dc1071b9ca 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -84,7 +84,7 @@ public: return eden_paths.at(eden_path); } - [[nodiscard]] const fs::path& GetLegacyPathImpl(LegacyPath legacy_path) { + [[nodiscard]] const fs::path& GetLegacyPathImpl(EmuPath legacy_path) { return legacy_paths.at(legacy_path); } @@ -98,7 +98,7 @@ public: eden_paths.insert_or_assign(eden_path, new_path); } - void SetLegacyPathImpl(LegacyPath legacy_path, const fs::path& new_path) { + void SetLegacyPathImpl(EmuPath legacy_path, const fs::path& new_path) { legacy_paths.insert_or_assign(legacy_path, new_path); } @@ -118,9 +118,9 @@ public: } eden_path_cache = eden_path / CACHE_DIR; eden_path_config = eden_path / CONFIG_DIR; -#define LEGACY_PATH(titleName, upperName) GenerateLegacyPath(LegacyPath::titleName##Dir, GetAppDataRoamingDirectory() / upperName##_DIR); \ - GenerateLegacyPath(LegacyPath::titleName##ConfigDir, GetAppDataRoamingDirectory() / upperName##_DIR / CONFIG_DIR); \ - GenerateLegacyPath(LegacyPath::titleName##CacheDir, GetAppDataRoamingDirectory() / upperName##_DIR / CACHE_DIR); +#define LEGACY_PATH(titleName, upperName) GenerateLegacyPath(EmuPath::titleName##Dir, GetAppDataRoamingDirectory() / upperName##_DIR); \ + GenerateLegacyPath(EmuPath::titleName##ConfigDir, GetAppDataRoamingDirectory() / upperName##_DIR / CONFIG_DIR); \ + GenerateLegacyPath(EmuPath::titleName##CacheDir, GetAppDataRoamingDirectory() / upperName##_DIR / CACHE_DIR); LEGACY_PATH(Citron, CITRON) LEGACY_PATH(Sudachi, SUDACHI) LEGACY_PATH(Yuzu, YUZU) @@ -140,9 +140,9 @@ public: eden_path_cache = eden_path / CACHE_DIR; eden_path_config = eden_path / CONFIG_DIR; } -#define LEGACY_PATH(titleName, upperName) GenerateLegacyPath(LegacyPath::titleName##Dir, GetDataDirectory("XDG_DATA_HOME") / upperName##_DIR); \ - GenerateLegacyPath(LegacyPath::titleName##ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / upperName##_DIR); \ - GenerateLegacyPath(LegacyPath::titleName##CacheDir, GetDataDirectory("XDG_CACHE_HOME") / upperName##_DIR); +#define LEGACY_PATH(titleName, upperName) GenerateLegacyPath(EmuPath::titleName##Dir, GetDataDirectory("XDG_DATA_HOME") / upperName##_DIR); \ + GenerateLegacyPath(EmuPath::titleName##ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / upperName##_DIR); \ + GenerateLegacyPath(EmuPath::titleName##CacheDir, GetDataDirectory("XDG_CACHE_HOME") / upperName##_DIR); LEGACY_PATH(Citron, CITRON) LEGACY_PATH(Sudachi, SUDACHI) LEGACY_PATH(Yuzu, YUZU) @@ -165,6 +165,15 @@ public: GenerateEdenPath(EdenPath::ShaderDir, eden_path / SHADER_DIR); GenerateEdenPath(EdenPath::TASDir, eden_path / TAS_DIR); GenerateEdenPath(EdenPath::IconsDir, eden_path / ICONS_DIR); + +#ifdef _WIN32 + GenerateLegacyPath(EmuPath::RyujinxDir, GetAppDataRoamingDirectory() / RYUJINX_DIR); +#else + // In Ryujinx's infinite wisdom, it places EVERYTHING in the config directory on UNIX + // This is incredibly stupid and violates a million XDG standards, but whatever + GenerateLegacyPath(EmuPath::RyujinxDir, GetDataDirectory("XDG_CONFIG_HOME") / RYUJINX_DIR); +#endif + } private: @@ -179,12 +188,12 @@ private: SetEdenPathImpl(eden_path, new_path); } - void GenerateLegacyPath(LegacyPath legacy_path, const fs::path& new_path) { + void GenerateLegacyPath(EmuPath legacy_path, const fs::path& new_path) { SetLegacyPathImpl(legacy_path, new_path); } std::unordered_map<EdenPath, fs::path> eden_paths; - std::unordered_map<LegacyPath, fs::path> legacy_paths; + std::unordered_map<EmuPath, fs::path> legacy_paths; }; bool ValidatePath(const fs::path& path) { @@ -272,7 +281,7 @@ const fs::path& GetEdenPath(EdenPath eden_path) { return PathManagerImpl::GetInstance().GetEdenPathImpl(eden_path); } -const std::filesystem::path& GetLegacyPath(LegacyPath legacy_path) { +const std::filesystem::path& GetLegacyPath(EmuPath legacy_path) { return PathManagerImpl::GetInstance().GetLegacyPathImpl(legacy_path); } @@ -280,7 +289,7 @@ std::string GetEdenPathString(EdenPath eden_path) { return PathToUTF8String(GetEdenPath(eden_path)); } -std::string GetLegacyPathString(LegacyPath legacy_path) { +std::string GetLegacyPathString(EmuPath legacy_path) { return PathToUTF8String(GetLegacyPath(legacy_path)); } diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index b34efc472f..dc800b2892 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -32,22 +32,26 @@ enum class EdenPath { IconsDir, // Where Icons for Windows shortcuts are stored. }; -enum LegacyPath { - CitronDir, // Citron Directories for migration +// migration/compat dirs +enum EmuPath { + CitronDir, CitronConfigDir, CitronCacheDir, - SudachiDir, // Sudachi Directories for migration + SudachiDir, SudachiConfigDir, SudachiCacheDir, - YuzuDir, // Yuzu Directories for migration + YuzuDir, YuzuConfigDir, YuzuCacheDir, - SuyuDir, // Suyu Directories for migration + SuyuDir, SuyuConfigDir, SuyuCacheDir, + + // used exclusively for save data linking + RyujinxDir, }; /** @@ -229,7 +233,7 @@ void SetAppDirectory(const std::string& app_directory); * * @returns The filesystem path associated with the LegacyPath enum. */ -[[nodiscard]] const std::filesystem::path& GetLegacyPath(LegacyPath legacy_path); +[[nodiscard]] const std::filesystem::path& GetLegacyPath(EmuPath legacy_path); /** * Gets the filesystem path associated with the EdenPath enum as a UTF-8 encoded std::string. @@ -247,7 +251,7 @@ void SetAppDirectory(const std::string& app_directory); * * @returns The filesystem path associated with the LegacyPath enum as a UTF-8 encoded std::string. */ -[[nodiscard]] std::string GetLegacyPathString(LegacyPath legacy_path); +[[nodiscard]] std::string GetLegacyPathString(EmuPath legacy_path); /** * Sets a new filesystem path associated with the EdenPath enum. diff --git a/src/common/fs/ryujinx_compat.cpp b/src/common/fs/ryujinx_compat.cpp new file mode 100644 index 0000000000..610ba736a7 --- /dev/null +++ b/src/common/fs/ryujinx_compat.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ryujinx_compat.h" +#include "common/fs/path_util.h" +#include <cstddef> +#include <cstring> +#include <fmt/ranges.h> +#include <fstream> + +namespace Common::FS { + +namespace fs = std::filesystem; + +fs::path GetKvdbPath() +{ + return GetLegacyPath(EmuPath::RyujinxDir) / "bis" / "system" / "save" / "8000000000000000" / "0" + / "imkvdb.arc"; +} + +fs::path GetRyuSavePath(const u64 &save_id) +{ + std::string hex = fmt::format("{:016x}", save_id); + + // TODO: what's the difference between 0 and 1? + return GetLegacyPath(EmuPath::RyujinxDir) / "bis" / "user" / "save" / hex / "0"; +} + +IMENReadResult ReadKvdb(const fs::path &path, std::vector<IMEN> &imens) +{ + std::ifstream kvdb{path, std::ios::binary | std::ios::ate}; + + if (!kvdb) { + return IMENReadResult::Nonexistent; + } + + size_t file_size = kvdb.tellg(); + + // IMKV header + 8 bytes + if (file_size < 0xB) { + return IMENReadResult::NoHeader; + } + + // magic (not the wizard kind) + kvdb.seekg(0, std::ios::beg); + char header[12]; + kvdb.read(header, 12); + + if (std::memcmp(header, IMKV_MAGIC, 4) != 0) { + return IMENReadResult::InvalidMagic; + } + + // calculate num. of imens left + std::size_t remaining = (file_size - 12); + std::size_t num_imens = remaining / IMEN_SIZE; + + // File is misaligned and probably corrupt (rip) + if (remaining % IMEN_SIZE != 0) { + return IMENReadResult::Misaligned; + } + + // if there aren't any IMENs, it's empty and we can safely no-op out of here + if (num_imens == 0) { + return IMENReadResult::NoImens; + } + + imens.reserve(num_imens); + + // initially I wanted to do a struct, but imkvdb is 140 bytes + // while the compiler will murder you if you try to align u64 to 4 bytes + for (std::size_t i = 0; i < num_imens; ++i) { + char magic[4]; + u64 title_id = 0; + u64 save_id = 0; + + kvdb.read(magic, 4); + if (std::memcmp(magic, IMEN_MAGIC, 4) != 0) { + return IMENReadResult::InvalidMagic; + } + + kvdb.ignore(0x8); + kvdb.read(reinterpret_cast<char *>(&title_id), 8); + kvdb.ignore(0x38); + kvdb.read(reinterpret_cast<char *>(&save_id), 8); + kvdb.ignore(0x38); + + imens.emplace_back(IMEN{title_id, save_id}); + } + + return IMENReadResult::Success; +} + +} // namespace Common::FS diff --git a/src/common/fs/ryujinx_compat.h b/src/common/fs/ryujinx_compat.h new file mode 100644 index 0000000000..6273754074 --- /dev/null +++ b/src/common/fs/ryujinx_compat.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include <filesystem> +#include <vector> + +namespace fs = std::filesystem; + +namespace Common::FS { + +constexpr const char IMEN_MAGIC[4] = {0x49, 0x4d, 0x45, 0x4e}; +constexpr const char IMKV_MAGIC[4] = {0x49, 0x4d, 0x4b, 0x56}; +constexpr const u8 IMEN_SIZE = 0x8c; + +fs::path GetKvdbPath(); +fs::path GetRyuSavePath(const u64 &program_id); + +enum class IMENReadResult { + Nonexistent, // ryujinx not found + NoHeader, // file isn't big enough for header + InvalidMagic, // no IMKV or IMEN header + Misaligned, // file isn't aligned to expected IMEN boundaries + NoImens, // no-op, there are no IMENs + Success, // :) +}; + +struct IMEN +{ + u64 title_id; + u64 save_id; +}; + +static_assert(sizeof(IMEN) == 0x10, "IMEN has incorrect size."); + +IMENReadResult ReadKvdb(const fs::path &path, std::vector<IMEN> &imens); + +} // namespace Common::FS diff --git a/src/common/fs/symlink.cpp b/src/common/fs/symlink.cpp new file mode 100644 index 0000000000..9c9fe3d50b --- /dev/null +++ b/src/common/fs/symlink.cpp @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "symlink.h" + +#ifdef _WIN32 +#include <windows.h> +#include <fmt/format.h> +#endif + +namespace fs = std::filesystem; + +// The sole purpose of this file is to treat symlinks like symlinks on POSIX, +// or treat them as directory junctions on Windows. +// This is because, for some inexplicable reason, Microsoft has locked symbolic +// links behind a "security policy", whereas directory junctions--functionally identical +// for directories, by the way--are not. Why? I don't know. + +namespace Common::FS { + +bool CreateSymlink(const fs::path &from, const fs::path &to) +{ +#ifdef _WIN32 + const std::string command = fmt::format("mklink /J {} {}", to.string(), from.string()); + return system(command.c_str()) == 0; +#else + std::error_code ec; + fs::create_directory_symlink(from, to, ec); + return !ec; +#endif +} + +bool IsSymlink(const fs::path &path) +{ +#ifdef _WIN32 + auto attributes = GetFileAttributesW(path.wstring().c_str()); + return attributes & FILE_ATTRIBUTE_REPARSE_POINT; +#else + return fs::is_symlink(path); +#endif +} + +} // namespace Common::FS diff --git a/src/common/fs/symlink.h b/src/common/fs/symlink.h new file mode 100644 index 0000000000..3bfff95ebf --- /dev/null +++ b/src/common/fs/symlink.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <filesystem> +namespace Common::FS { + +bool CreateSymlink(const std::filesystem::path &from, const std::filesystem::path &to); +bool IsSymlink(const std::filesystem::path &path); + +} // namespace Common::FS |
