diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 518619a834..709c899e06 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -73,6 +73,8 @@ #include "Core/MemoryWatcher.h" #endif +#include "DiscIO/RiivolutionPatcher.h" + #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/GCAdapter.h" @@ -603,6 +605,10 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi else cpuThreadFunc = CpuThread; + std::optional savegame_redirect = std::nullopt; + if (SConfig::GetInstance().bWii) + savegame_redirect = DiscIO::Riivolution::ExtractSavegameRedirect(boot->riivolution_patches); + if (!CBoot::BootUp(std::move(boot))) return; @@ -611,7 +617,7 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi // with the correct title context since save copying requires title directories to exist. Common::ScopeGuard wiifs_guard{&Core::CleanUpWiiFileSystemContents}; if (SConfig::GetInstance().bWii) - Core::InitializeWiiFileSystemContents(); + Core::InitializeWiiFileSystemContents(savegame_redirect); else wiifs_guard.Dismiss(); diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h index dea78a26e9..6b1d7ddf78 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.h +++ b/Source/Core/Core/IOS/FS/FileSystem.h @@ -72,6 +72,15 @@ enum class SeekMode : u32 using FileAttribute = u8; +struct NandRedirect +{ + // A Wii FS path, eg. "/title/00010000/534d4e45/data". + std::string source_path; + + // An absolute host filesystem path the above should be redirected to. + std::string target_path; +}; + struct Modes { Mode owner, group, other; @@ -239,6 +248,8 @@ public: virtual Result GetNandStats() = 0; /// Get usage information about a directory (used cluster and inode counts). virtual Result GetDirectoryStats(const std::string& path) = 0; + + virtual void SetNandRedirects(std::vector nand_redirects) = 0; }; template @@ -269,7 +280,8 @@ enum class Location Session, }; -std::unique_ptr MakeFileSystem(Location location = Location::Session); +std::unique_ptr MakeFileSystem(Location location = Location::Session, + std::vector nand_redirects = {}); /// Convert a FS result code to an IOS error code. IOS::HLE::ReturnCode ConvertResult(ResultCode code); diff --git a/Source/Core/Core/IOS/FS/FileSystemCommon.cpp b/Source/Core/Core/IOS/FS/FileSystemCommon.cpp index bc904227ae..8d246157bb 100644 --- a/Source/Core/Core/IOS/FS/FileSystemCommon.cpp +++ b/Source/Core/Core/IOS/FS/FileSystemCommon.cpp @@ -28,11 +28,12 @@ SplitPathResult SplitPathAndBasename(std::string_view path) std::string(path.substr(last_separator + 1))}; } -std::unique_ptr MakeFileSystem(Location location) +std::unique_ptr MakeFileSystem(Location location, + std::vector nand_redirects) { const std::string nand_root = File::GetUserPath(location == Location::Session ? D_SESSION_WIIROOT_IDX : D_WIIROOT_IDX); - return std::make_unique(nand_root); + return std::make_unique(nand_root, std::move(nand_redirects)); } IOS::HLE::ReturnCode ConvertResult(ResultCode code) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index cdc8d2e7b2..8ca81558cd 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -22,13 +22,24 @@ namespace IOS::HLE::FS { -std::string HostFileSystem::BuildFilename(const std::string& wii_path) const +HostFileSystem::HostFilename HostFileSystem::BuildFilename(const std::string& wii_path) const { + for (const auto& redirect : m_nand_redirects) + { + if (StringBeginsWith(wii_path, redirect.source_path) && + (wii_path.size() == redirect.source_path.size() || + wii_path[redirect.source_path.size()] == '/')) + { + std::string relative_to_redirect = wii_path.substr(redirect.source_path.size()); + return HostFilename{redirect.target_path + Common::EscapePath(relative_to_redirect), true}; + } + } + if (wii_path.compare(0, 1, "/") == 0) - return m_root_path + Common::EscapePath(wii_path); + return HostFilename{m_root_path + Common::EscapePath(wii_path), false}; ASSERT(false); - return m_root_path; + return HostFilename{m_root_path, false}; } // Get total filesize of contents of a directory (recursive) @@ -101,7 +112,9 @@ bool HostFileSystem::FstEntry::CheckPermission(Uid caller_uid, Gid caller_gid, return (u8(requested_mode) & u8(file_mode)) == u8(requested_mode); } -HostFileSystem::HostFileSystem(const std::string& root_path) : m_root_path{root_path} +HostFileSystem::HostFileSystem(const std::string& root_path, + std::vector nand_redirects) + : m_root_path{root_path}, m_nand_redirects(std::move(nand_redirects)) { File::CreateFullPath(m_root_path + "/"); ResetFst(); @@ -197,11 +210,12 @@ HostFileSystem::FstEntry* HostFileSystem::GetFstEntryForPath(const std::string& if (!IsValidNonRootPath(path)) return nullptr; - const File::FileInfo host_file_info{BuildFilename(path)}; + auto host_file = BuildFilename(path); + const File::FileInfo host_file_info{host_file.host_path}; if (!host_file_info.Exists()) return nullptr; - FstEntry* entry = &m_root_entry; + FstEntry* entry = host_file.is_redirect ? &m_redirect_fst : &m_root_entry; std::string complete_path = ""; for (const std::string& component : SplitString(std::string(path.substr(1)), '/')) { @@ -217,7 +231,8 @@ HostFileSystem::FstEntry* HostFileSystem::GetFstEntryForPath(const std::string& // Fall back to dummy data to avoid breaking existing filesystems. // This code path is also reached when creating a new file or directory; // proper metadata is filled in later. - INFO_LOG_FMT(IOS_FS, "Creating a default entry for {}", complete_path); + INFO_LOG_FMT(IOS_FS, "Creating a default entry for {} ({})", complete_path, + host_file.is_redirect ? "redirect" : "NAND"); entry = &entry->children.emplace_back(); entry->name = component; entry->data.modes = {Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite}; @@ -241,7 +256,7 @@ void HostFileSystem::DoState(PointerWrap& p) handle.host_file.reset(); // handle /tmp - std::string Path = BuildFilename("/tmp"); + std::string Path = BuildFilename("/tmp").host_path; if (p.GetMode() == PointerWrap::MODE_READ) { File::DeleteDirRecursively(Path); @@ -336,7 +351,7 @@ void HostFileSystem::DoState(PointerWrap& p) p.Do(handle.wii_path); p.Do(handle.file_offset); if (handle.opened) - handle.host_file = OpenHostFile(BuildFilename(handle.wii_path)); + handle.host_file = OpenHostFile(BuildFilename(handle.wii_path).host_path); } } @@ -346,7 +361,7 @@ ResultCode HostFileSystem::Format(Uid uid) return ResultCode::AccessDenied; if (m_root_path.empty()) return ResultCode::AccessDenied; - const std::string root = BuildFilename("/"); + const std::string root = BuildFilename("/").host_path; if (!File::DeleteDirRecursively(root) || !File::CreateDir(root)) return ResultCode::UnknownError; ResetFst(); @@ -366,7 +381,7 @@ ResultCode HostFileSystem::CreateFileOrDirectory(Uid uid, Gid gid, const std::st return ResultCode::TooManyPathComponents; const auto split_path = SplitPathAndBasename(path); - const std::string host_path = BuildFilename(path); + const std::string host_path = BuildFilename(path).host_path; FstEntry* parent = GetFstEntryForPath(split_path.parent); if (!parent) @@ -428,7 +443,7 @@ ResultCode HostFileSystem::Delete(Uid uid, Gid gid, const std::string& path) if (!IsValidNonRootPath(path)) return ResultCode::Invalid; - const std::string host_path = BuildFilename(path); + const std::string host_path = BuildFilename(path).host_path; const auto split_path = SplitPathAndBasename(path); FstEntry* parent = GetFstEntryForPath(split_path.parent); @@ -491,8 +506,8 @@ ResultCode HostFileSystem::Rename(Uid uid, Gid gid, const std::string& old_path, return ResultCode::InUse; } - const std::string host_old_path = BuildFilename(old_path); - const std::string host_new_path = BuildFilename(new_path); + const std::string host_old_path = BuildFilename(old_path).host_path; + const std::string host_new_path = BuildFilename(new_path).host_path; // If there is already something of the same type at the new path, delete it. if (File::Exists(host_new_path)) @@ -544,7 +559,7 @@ Result> HostFileSystem::ReadDirectory(Uid uid, Gid gid, if (entry->data.is_file) return ResultCode::Invalid; - const std::string host_path = BuildFilename(path); + const std::string host_path = BuildFilename(path).host_path; File::FSTEntry host_entry = File::ScanDirectoryTree(host_path, false); for (File::FSTEntry& child : host_entry.children) { @@ -612,7 +627,7 @@ Result HostFileSystem::GetMetadata(Uid uid, Gid gid, const std::string return ResultCode::NotFound; Metadata metadata = entry->data; - metadata.size = File::GetSize(BuildFilename(path)); + metadata.size = File::GetSize(BuildFilename(path).host_path); return metadata; } @@ -631,7 +646,7 @@ ResultCode HostFileSystem::SetMetadata(Uid caller_uid, const std::string& path, if (caller_uid != 0 && uid != entry->data.uid) return ResultCode::AccessDenied; - const bool is_empty = File::GetSize(BuildFilename(path)) == 0; + const bool is_empty = File::GetSize(BuildFilename(path).host_path) == 0; if (entry->data.uid != uid && entry->data.is_file && !is_empty) return ResultCode::FileNotEmpty; @@ -667,7 +682,7 @@ Result HostFileSystem::GetDirectoryStats(const std::string& wii_ return ResultCode::Invalid; DirectoryStats stats{}; - std::string path(BuildFilename(wii_path)); + std::string path(BuildFilename(wii_path).host_path); if (File::IsDirectory(path)) { File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true); @@ -685,4 +700,8 @@ Result HostFileSystem::GetDirectoryStats(const std::string& wii_ return stats; } +void HostFileSystem::SetNandRedirects(std::vector nand_redirects) +{ + m_nand_redirects = std::move(nand_redirects); +} } // namespace IOS::HLE::FS diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.h b/Source/Core/Core/IOS/FS/HostBackend/FS.h index d9d53669a3..17ef9baf36 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.h +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.h @@ -22,7 +22,7 @@ namespace IOS::HLE::FS class HostFileSystem final : public FileSystem { public: - HostFileSystem(const std::string& root_path); + HostFileSystem(const std::string& root_path, std::vector nand_redirects = {}); ~HostFileSystem(); void DoState(PointerWrap& p) override; @@ -56,6 +56,8 @@ public: Result GetNandStats() override; Result GetDirectoryStats(const std::string& path) override; + void SetNandRedirects(std::vector nand_redirects) override; + private: struct FstEntry { @@ -83,7 +85,12 @@ private: Handle* GetHandleFromFd(Fd fd); Fd ConvertHandleToFd(const Handle* handle) const; - std::string BuildFilename(const std::string& wii_path) const; + struct HostFilename + { + std::string host_path; + bool is_redirect; + }; + HostFilename BuildFilename(const std::string& wii_path) const; std::shared_ptr OpenHostFile(const std::string& host_path); ResultCode CreateFileOrDirectory(Uid uid, Gid gid, const std::string& path, @@ -112,6 +119,9 @@ private: std::string m_root_path; std::map> m_open_files; std::array m_handles{}; + + FstEntry m_redirect_fst{}; + std::vector m_nand_redirects; }; } // namespace IOS::HLE::FS diff --git a/Source/Core/Core/IOS/FS/HostBackend/File.cpp b/Source/Core/Core/IOS/FS/HostBackend/File.cpp index c40f33dfaa..bdc4518310 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/File.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/File.cpp @@ -80,7 +80,7 @@ Result HostFileSystem::OpenFile(Uid, Gid, const std::string& path, M if (!handle) return ResultCode::NoFreeHandle; - const std::string host_path = BuildFilename(path); + const std::string host_path = BuildFilename(path).host_path; if (!File::IsFile(host_path)) { *handle = Handle{}; diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 868e0476b5..53c99ddaf9 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -498,7 +498,7 @@ void Kernel::AddDevice(std::unique_ptr device) void Kernel::AddCoreDevices() { - m_fs = FS::MakeFileSystem(); + m_fs = FS::MakeFileSystem(IOS::HLE::FS::Location::Session, Core::GetActiveNandRedirects()); ASSERT(m_fs); std::lock_guard lock(m_device_map_mutex); diff --git a/Source/Core/Core/WiiRoot.cpp b/Source/Core/Core/WiiRoot.cpp index ea795ac138..d57f194ddc 100644 --- a/Source/Core/Core/WiiRoot.cpp +++ b/Source/Core/Core/WiiRoot.cpp @@ -4,6 +4,7 @@ #include "Core/WiiRoot.h" #include +#include #include #include @@ -34,6 +35,12 @@ namespace FS = IOS::HLE::FS; static std::string s_temp_wii_root; static bool s_wii_root_initialized = false; +static std::vector s_nand_redirects; + +const std::vector& GetActiveNandRedirects() +{ + return s_nand_redirects; +} static bool CopyBackupFile(const std::string& path_from, const std::string& path_to) { @@ -202,6 +209,7 @@ void InitializeWiiRoot(bool use_temporary) File::SetUserPath(D_SESSION_WIIROOT_IDX, File::GetUserPath(D_WIIROOT_IDX)); } + s_nand_redirects.clear(); s_wii_root_initialized = true; } @@ -213,6 +221,7 @@ void ShutdownWiiRoot() s_temp_wii_root.clear(); } + s_nand_redirects.clear(); s_wii_root_initialized = false; } @@ -288,7 +297,8 @@ static bool CopySysmenuFilesToFS(FS::FileSystem* fs, const std::string& host_sou return true; } -void InitializeWiiFileSystemContents() +void InitializeWiiFileSystemContents( + std::optional save_redirect) { const auto fs = IOS::HLE::GetIOS()->GetFS(); @@ -299,14 +309,31 @@ void InitializeWiiFileSystemContents() if (!CopySysmenuFilesToFS(fs.get(), File::GetSysDirectory() + WII_USER_DIR, "")) WARN_LOG_FMT(CORE, "Failed to copy initial System Menu files to the NAND"); - if (!WiiRootIsTemporary()) - return; + if (WiiRootIsTemporary()) + { + // Generate a SYSCONF with default settings for the temporary Wii NAND. + SysConf sysconf{fs}; + sysconf.Save(); - // Generate a SYSCONF with default settings for the temporary Wii NAND. - SysConf sysconf{fs}; - sysconf.Save(); - - InitializeDeterministicWiiSaves(fs.get()); + InitializeDeterministicWiiSaves(fs.get()); + } + else if (save_redirect) + { + const u64 title_id = SConfig::GetInstance().GetTitleID(); + std::string source_path = Common::GetTitleDataPath(title_id); + if (!File::IsDirectory(save_redirect->m_target_path)) + { + File::CreateFullPath(save_redirect->m_target_path + "/"); + if (save_redirect->m_clone) + { + File::CopyDir(Common::GetTitleDataPath(title_id, Common::FROM_CONFIGURED_ROOT), + save_redirect->m_target_path); + } + } + s_nand_redirects.emplace_back(IOS::HLE::FS::NandRedirect{ + std::move(source_path), std::move(save_redirect->m_target_path)}); + fs->SetNandRedirects(s_nand_redirects); + } } void CleanUpWiiFileSystemContents() diff --git a/Source/Core/Core/WiiRoot.h b/Source/Core/Core/WiiRoot.h index 2875cfa0cb..0b17061b19 100644 --- a/Source/Core/Core/WiiRoot.h +++ b/Source/Core/Core/WiiRoot.h @@ -3,6 +3,16 @@ #pragma once +#include +#include + +#include "DiscIO/RiivolutionPatcher.h" + +namespace IOS::HLE::FS +{ +struct NandRedirect; +} + namespace Core { enum class RestoreReason @@ -21,6 +31,9 @@ void BackupWiiSettings(); void RestoreWiiSettings(RestoreReason reason); // Initialize or clean up the filesystem contents. -void InitializeWiiFileSystemContents(); +void InitializeWiiFileSystemContents( + std::optional save_redirect); void CleanUpWiiFileSystemContents(); + +const std::vector& GetActiveNandRedirects(); } // namespace Core diff --git a/Source/Core/DiscIO/RiivolutionPatcher.cpp b/Source/Core/DiscIO/RiivolutionPatcher.cpp index af8be05944..3f3a1f876f 100644 --- a/Source/Core/DiscIO/RiivolutionPatcher.cpp +++ b/Source/Core/DiscIO/RiivolutionPatcher.cpp @@ -14,6 +14,7 @@ #include "Common/IOFile.h" #include "Common/StringUtil.h" #include "Core/HW/Memmap.h" +#include "Core/IOS/FS/FileSystem.h" #include "Core/PowerPC/MMU.h" #include "DiscIO/DirectoryBlob.h" #include "DiscIO/RiivolutionParser.h" @@ -174,6 +175,12 @@ FileDataLoaderHostFS::MakeContentSource(std::string_view external_relative_path, ContentFile{std::move(*path), external_offset}}; } +std::optional +FileDataLoaderHostFS::ResolveSavegameRedirectPath(std::string_view external_relative_path) +{ + return MakeAbsoluteFromRelative(external_relative_path); +} + // 'before' and 'after' should be two copies of the same source // 'split_at' needs to be between the start and end of the source, may not match either boundary static void SplitAt(BuilderContentSource* before, BuilderContentSource* after, u64 split_at) @@ -588,4 +595,21 @@ void ApplyPatchesToMemory(const std::vector& patches) } } } + +std::optional +ExtractSavegameRedirect(const std::vector& riivolution_patches) +{ + for (const auto& patch : riivolution_patches) + { + if (!patch.m_savegame_patches.empty()) + { + const auto& save_patch = patch.m_savegame_patches[0]; + auto resolved = patch.m_file_data_loader->ResolveSavegameRedirectPath(save_patch.m_external); + if (resolved) + return SavegameRedirect{std::move(*resolved), save_patch.m_clone}; + return std::nullopt; + } + } + return std::nullopt; +} } // namespace DiscIO::Riivolution diff --git a/Source/Core/DiscIO/RiivolutionPatcher.h b/Source/Core/DiscIO/RiivolutionPatcher.h index 8ddd887cb5..e93ae5c00e 100644 --- a/Source/Core/DiscIO/RiivolutionPatcher.h +++ b/Source/Core/DiscIO/RiivolutionPatcher.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -12,6 +13,12 @@ namespace DiscIO::Riivolution { +struct SavegameRedirect +{ + std::string m_target_path; + bool m_clone; +}; + class FileDataLoader { public: @@ -28,6 +35,8 @@ public: virtual BuilderContentSource MakeContentSource(std::string_view external_relative_path, u64 external_offset, u64 external_size, u64 disc_offset) = 0; + virtual std::optional + ResolveSavegameRedirectPath(std::string_view external_relative_path) = 0; }; class FileDataLoaderHostFS : public FileDataLoader @@ -46,6 +55,8 @@ public: BuilderContentSource MakeContentSource(std::string_view external_relative_path, u64 external_offset, u64 external_size, u64 disc_offset) override; + std::optional + ResolveSavegameRedirectPath(std::string_view external_relative_path) override; private: std::optional MakeAbsoluteFromRelative(std::string_view external_relative_path); @@ -58,4 +69,6 @@ void ApplyPatchesToFiles(const std::vector& patches, std::vector* fst, DiscIO::FSTBuilderNode* dol_node); void ApplyPatchesToMemory(const std::vector& patches); +std::optional +ExtractSavegameRedirect(const std::vector& riivolution_patches); } // namespace DiscIO::Riivolution