Core: Implement Wii NAND path redirects for Riivolution savegame patches.

This commit is contained in:
Admiral H. Curtiss 2021-10-03 06:23:33 +02:00
parent 588c31acb6
commit fe242f79ee
No known key found for this signature in database
GPG Key ID: F051B4C4044F33FB
11 changed files with 160 additions and 35 deletions

View File

@ -73,6 +73,8 @@
#include "Core/MemoryWatcher.h" #include "Core/MemoryWatcher.h"
#endif #endif
#include "DiscIO/RiivolutionPatcher.h"
#include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/GCAdapter.h" #include "InputCommon/GCAdapter.h"
@ -603,6 +605,10 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
else else
cpuThreadFunc = CpuThread; cpuThreadFunc = CpuThread;
std::optional<DiscIO::Riivolution::SavegameRedirect> savegame_redirect = std::nullopt;
if (SConfig::GetInstance().bWii)
savegame_redirect = DiscIO::Riivolution::ExtractSavegameRedirect(boot->riivolution_patches);
if (!CBoot::BootUp(std::move(boot))) if (!CBoot::BootUp(std::move(boot)))
return; return;
@ -611,7 +617,7 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
// with the correct title context since save copying requires title directories to exist. // with the correct title context since save copying requires title directories to exist.
Common::ScopeGuard wiifs_guard{&Core::CleanUpWiiFileSystemContents}; Common::ScopeGuard wiifs_guard{&Core::CleanUpWiiFileSystemContents};
if (SConfig::GetInstance().bWii) if (SConfig::GetInstance().bWii)
Core::InitializeWiiFileSystemContents(); Core::InitializeWiiFileSystemContents(savegame_redirect);
else else
wiifs_guard.Dismiss(); wiifs_guard.Dismiss();

View File

@ -72,6 +72,15 @@ enum class SeekMode : u32
using FileAttribute = u8; 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 struct Modes
{ {
Mode owner, group, other; Mode owner, group, other;
@ -239,6 +248,8 @@ public:
virtual Result<NandStats> GetNandStats() = 0; virtual Result<NandStats> GetNandStats() = 0;
/// Get usage information about a directory (used cluster and inode counts). /// Get usage information about a directory (used cluster and inode counts).
virtual Result<DirectoryStats> GetDirectoryStats(const std::string& path) = 0; virtual Result<DirectoryStats> GetDirectoryStats(const std::string& path) = 0;
virtual void SetNandRedirects(std::vector<NandRedirect> nand_redirects) = 0;
}; };
template <typename T> template <typename T>
@ -269,7 +280,8 @@ enum class Location
Session, Session,
}; };
std::unique_ptr<FileSystem> MakeFileSystem(Location location = Location::Session); std::unique_ptr<FileSystem> MakeFileSystem(Location location = Location::Session,
std::vector<NandRedirect> nand_redirects = {});
/// Convert a FS result code to an IOS error code. /// Convert a FS result code to an IOS error code.
IOS::HLE::ReturnCode ConvertResult(ResultCode code); IOS::HLE::ReturnCode ConvertResult(ResultCode code);

View File

@ -28,11 +28,12 @@ SplitPathResult SplitPathAndBasename(std::string_view path)
std::string(path.substr(last_separator + 1))}; std::string(path.substr(last_separator + 1))};
} }
std::unique_ptr<FileSystem> MakeFileSystem(Location location) std::unique_ptr<FileSystem> MakeFileSystem(Location location,
std::vector<NandRedirect> nand_redirects)
{ {
const std::string nand_root = const std::string nand_root =
File::GetUserPath(location == Location::Session ? D_SESSION_WIIROOT_IDX : D_WIIROOT_IDX); File::GetUserPath(location == Location::Session ? D_SESSION_WIIROOT_IDX : D_WIIROOT_IDX);
return std::make_unique<HostFileSystem>(nand_root); return std::make_unique<HostFileSystem>(nand_root, std::move(nand_redirects));
} }
IOS::HLE::ReturnCode ConvertResult(ResultCode code) IOS::HLE::ReturnCode ConvertResult(ResultCode code)

View File

@ -22,13 +22,24 @@
namespace IOS::HLE::FS 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) 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); ASSERT(false);
return m_root_path; return HostFilename{m_root_path, false};
} }
// Get total filesize of contents of a directory (recursive) // 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); 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<NandRedirect> nand_redirects)
: m_root_path{root_path}, m_nand_redirects(std::move(nand_redirects))
{ {
File::CreateFullPath(m_root_path + "/"); File::CreateFullPath(m_root_path + "/");
ResetFst(); ResetFst();
@ -197,11 +210,12 @@ HostFileSystem::FstEntry* HostFileSystem::GetFstEntryForPath(const std::string&
if (!IsValidNonRootPath(path)) if (!IsValidNonRootPath(path))
return nullptr; 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()) if (!host_file_info.Exists())
return nullptr; return nullptr;
FstEntry* entry = &m_root_entry; FstEntry* entry = host_file.is_redirect ? &m_redirect_fst : &m_root_entry;
std::string complete_path = ""; std::string complete_path = "";
for (const std::string& component : SplitString(std::string(path.substr(1)), '/')) 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. // Fall back to dummy data to avoid breaking existing filesystems.
// This code path is also reached when creating a new file or directory; // This code path is also reached when creating a new file or directory;
// proper metadata is filled in later. // 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 = &entry->children.emplace_back();
entry->name = component; entry->name = component;
entry->data.modes = {Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite}; entry->data.modes = {Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite};
@ -241,7 +256,7 @@ void HostFileSystem::DoState(PointerWrap& p)
handle.host_file.reset(); handle.host_file.reset();
// handle /tmp // handle /tmp
std::string Path = BuildFilename("/tmp"); std::string Path = BuildFilename("/tmp").host_path;
if (p.GetMode() == PointerWrap::MODE_READ) if (p.GetMode() == PointerWrap::MODE_READ)
{ {
File::DeleteDirRecursively(Path); File::DeleteDirRecursively(Path);
@ -336,7 +351,7 @@ void HostFileSystem::DoState(PointerWrap& p)
p.Do(handle.wii_path); p.Do(handle.wii_path);
p.Do(handle.file_offset); p.Do(handle.file_offset);
if (handle.opened) 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; return ResultCode::AccessDenied;
if (m_root_path.empty()) if (m_root_path.empty())
return ResultCode::AccessDenied; return ResultCode::AccessDenied;
const std::string root = BuildFilename("/"); const std::string root = BuildFilename("/").host_path;
if (!File::DeleteDirRecursively(root) || !File::CreateDir(root)) if (!File::DeleteDirRecursively(root) || !File::CreateDir(root))
return ResultCode::UnknownError; return ResultCode::UnknownError;
ResetFst(); ResetFst();
@ -366,7 +381,7 @@ ResultCode HostFileSystem::CreateFileOrDirectory(Uid uid, Gid gid, const std::st
return ResultCode::TooManyPathComponents; return ResultCode::TooManyPathComponents;
const auto split_path = SplitPathAndBasename(path); 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); FstEntry* parent = GetFstEntryForPath(split_path.parent);
if (!parent) if (!parent)
@ -428,7 +443,7 @@ ResultCode HostFileSystem::Delete(Uid uid, Gid gid, const std::string& path)
if (!IsValidNonRootPath(path)) if (!IsValidNonRootPath(path))
return ResultCode::Invalid; 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); const auto split_path = SplitPathAndBasename(path);
FstEntry* parent = GetFstEntryForPath(split_path.parent); 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; return ResultCode::InUse;
} }
const std::string host_old_path = BuildFilename(old_path); const std::string host_old_path = BuildFilename(old_path).host_path;
const std::string host_new_path = BuildFilename(new_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 there is already something of the same type at the new path, delete it.
if (File::Exists(host_new_path)) if (File::Exists(host_new_path))
@ -544,7 +559,7 @@ Result<std::vector<std::string>> HostFileSystem::ReadDirectory(Uid uid, Gid gid,
if (entry->data.is_file) if (entry->data.is_file)
return ResultCode::Invalid; 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); File::FSTEntry host_entry = File::ScanDirectoryTree(host_path, false);
for (File::FSTEntry& child : host_entry.children) for (File::FSTEntry& child : host_entry.children)
{ {
@ -612,7 +627,7 @@ Result<Metadata> HostFileSystem::GetMetadata(Uid uid, Gid gid, const std::string
return ResultCode::NotFound; return ResultCode::NotFound;
Metadata metadata = entry->data; Metadata metadata = entry->data;
metadata.size = File::GetSize(BuildFilename(path)); metadata.size = File::GetSize(BuildFilename(path).host_path);
return metadata; return metadata;
} }
@ -631,7 +646,7 @@ ResultCode HostFileSystem::SetMetadata(Uid caller_uid, const std::string& path,
if (caller_uid != 0 && uid != entry->data.uid) if (caller_uid != 0 && uid != entry->data.uid)
return ResultCode::AccessDenied; 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) if (entry->data.uid != uid && entry->data.is_file && !is_empty)
return ResultCode::FileNotEmpty; return ResultCode::FileNotEmpty;
@ -667,7 +682,7 @@ Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_
return ResultCode::Invalid; return ResultCode::Invalid;
DirectoryStats stats{}; DirectoryStats stats{};
std::string path(BuildFilename(wii_path)); std::string path(BuildFilename(wii_path).host_path);
if (File::IsDirectory(path)) if (File::IsDirectory(path))
{ {
File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true); File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true);
@ -685,4 +700,8 @@ Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_
return stats; return stats;
} }
void HostFileSystem::SetNandRedirects(std::vector<NandRedirect> nand_redirects)
{
m_nand_redirects = std::move(nand_redirects);
}
} // namespace IOS::HLE::FS } // namespace IOS::HLE::FS

View File

@ -22,7 +22,7 @@ namespace IOS::HLE::FS
class HostFileSystem final : public FileSystem class HostFileSystem final : public FileSystem
{ {
public: public:
HostFileSystem(const std::string& root_path); HostFileSystem(const std::string& root_path, std::vector<NandRedirect> nand_redirects = {});
~HostFileSystem(); ~HostFileSystem();
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
@ -56,6 +56,8 @@ public:
Result<NandStats> GetNandStats() override; Result<NandStats> GetNandStats() override;
Result<DirectoryStats> GetDirectoryStats(const std::string& path) override; Result<DirectoryStats> GetDirectoryStats(const std::string& path) override;
void SetNandRedirects(std::vector<NandRedirect> nand_redirects) override;
private: private:
struct FstEntry struct FstEntry
{ {
@ -83,7 +85,12 @@ private:
Handle* GetHandleFromFd(Fd fd); Handle* GetHandleFromFd(Fd fd);
Fd ConvertHandleToFd(const Handle* handle) const; 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<File::IOFile> OpenHostFile(const std::string& host_path); std::shared_ptr<File::IOFile> OpenHostFile(const std::string& host_path);
ResultCode CreateFileOrDirectory(Uid uid, Gid gid, const std::string& path, ResultCode CreateFileOrDirectory(Uid uid, Gid gid, const std::string& path,
@ -112,6 +119,9 @@ private:
std::string m_root_path; std::string m_root_path;
std::map<std::string, std::weak_ptr<File::IOFile>> m_open_files; std::map<std::string, std::weak_ptr<File::IOFile>> m_open_files;
std::array<Handle, 16> m_handles{}; std::array<Handle, 16> m_handles{};
FstEntry m_redirect_fst{};
std::vector<NandRedirect> m_nand_redirects;
}; };
} // namespace IOS::HLE::FS } // namespace IOS::HLE::FS

View File

@ -80,7 +80,7 @@ Result<FileHandle> HostFileSystem::OpenFile(Uid, Gid, const std::string& path, M
if (!handle) if (!handle)
return ResultCode::NoFreeHandle; return ResultCode::NoFreeHandle;
const std::string host_path = BuildFilename(path); const std::string host_path = BuildFilename(path).host_path;
if (!File::IsFile(host_path)) if (!File::IsFile(host_path))
{ {
*handle = Handle{}; *handle = Handle{};

View File

@ -498,7 +498,7 @@ void Kernel::AddDevice(std::unique_ptr<Device> device)
void Kernel::AddCoreDevices() void Kernel::AddCoreDevices()
{ {
m_fs = FS::MakeFileSystem(); m_fs = FS::MakeFileSystem(IOS::HLE::FS::Location::Session, Core::GetActiveNandRedirects());
ASSERT(m_fs); ASSERT(m_fs);
std::lock_guard lock(m_device_map_mutex); std::lock_guard lock(m_device_map_mutex);

View File

@ -4,6 +4,7 @@
#include "Core/WiiRoot.h" #include "Core/WiiRoot.h"
#include <cinttypes> #include <cinttypes>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -34,6 +35,12 @@ namespace FS = IOS::HLE::FS;
static std::string s_temp_wii_root; static std::string s_temp_wii_root;
static bool s_wii_root_initialized = false; static bool s_wii_root_initialized = false;
static std::vector<IOS::HLE::FS::NandRedirect> s_nand_redirects;
const std::vector<IOS::HLE::FS::NandRedirect>& GetActiveNandRedirects()
{
return s_nand_redirects;
}
static bool CopyBackupFile(const std::string& path_from, const std::string& path_to) 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)); File::SetUserPath(D_SESSION_WIIROOT_IDX, File::GetUserPath(D_WIIROOT_IDX));
} }
s_nand_redirects.clear();
s_wii_root_initialized = true; s_wii_root_initialized = true;
} }
@ -213,6 +221,7 @@ void ShutdownWiiRoot()
s_temp_wii_root.clear(); s_temp_wii_root.clear();
} }
s_nand_redirects.clear();
s_wii_root_initialized = false; s_wii_root_initialized = false;
} }
@ -288,7 +297,8 @@ static bool CopySysmenuFilesToFS(FS::FileSystem* fs, const std::string& host_sou
return true; return true;
} }
void InitializeWiiFileSystemContents() void InitializeWiiFileSystemContents(
std::optional<DiscIO::Riivolution::SavegameRedirect> save_redirect)
{ {
const auto fs = IOS::HLE::GetIOS()->GetFS(); const auto fs = IOS::HLE::GetIOS()->GetFS();
@ -299,14 +309,31 @@ void InitializeWiiFileSystemContents()
if (!CopySysmenuFilesToFS(fs.get(), File::GetSysDirectory() + WII_USER_DIR, "")) if (!CopySysmenuFilesToFS(fs.get(), File::GetSysDirectory() + WII_USER_DIR, ""))
WARN_LOG_FMT(CORE, "Failed to copy initial System Menu files to the NAND"); WARN_LOG_FMT(CORE, "Failed to copy initial System Menu files to the NAND");
if (!WiiRootIsTemporary()) if (WiiRootIsTemporary())
return; {
// 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. InitializeDeterministicWiiSaves(fs.get());
SysConf sysconf{fs}; }
sysconf.Save(); else if (save_redirect)
{
InitializeDeterministicWiiSaves(fs.get()); 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() void CleanUpWiiFileSystemContents()

View File

@ -3,6 +3,16 @@
#pragma once #pragma once
#include <optional>
#include <vector>
#include "DiscIO/RiivolutionPatcher.h"
namespace IOS::HLE::FS
{
struct NandRedirect;
}
namespace Core namespace Core
{ {
enum class RestoreReason enum class RestoreReason
@ -21,6 +31,9 @@ void BackupWiiSettings();
void RestoreWiiSettings(RestoreReason reason); void RestoreWiiSettings(RestoreReason reason);
// Initialize or clean up the filesystem contents. // Initialize or clean up the filesystem contents.
void InitializeWiiFileSystemContents(); void InitializeWiiFileSystemContents(
std::optional<DiscIO::Riivolution::SavegameRedirect> save_redirect);
void CleanUpWiiFileSystemContents(); void CleanUpWiiFileSystemContents();
const std::vector<IOS::HLE::FS::NandRedirect>& GetActiveNandRedirects();
} // namespace Core } // namespace Core

View File

@ -14,6 +14,7 @@
#include "Common/IOFile.h" #include "Common/IOFile.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/PowerPC/MMU.h" #include "Core/PowerPC/MMU.h"
#include "DiscIO/DirectoryBlob.h" #include "DiscIO/DirectoryBlob.h"
#include "DiscIO/RiivolutionParser.h" #include "DiscIO/RiivolutionParser.h"
@ -174,6 +175,12 @@ FileDataLoaderHostFS::MakeContentSource(std::string_view external_relative_path,
ContentFile{std::move(*path), external_offset}}; ContentFile{std::move(*path), external_offset}};
} }
std::optional<std::string>
FileDataLoaderHostFS::ResolveSavegameRedirectPath(std::string_view external_relative_path)
{
return MakeAbsoluteFromRelative(external_relative_path);
}
// 'before' and 'after' should be two copies of the same source // '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 // '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) static void SplitAt(BuilderContentSource* before, BuilderContentSource* after, u64 split_at)
@ -588,4 +595,21 @@ void ApplyPatchesToMemory(const std::vector<Patch>& patches)
} }
} }
} }
std::optional<SavegameRedirect>
ExtractSavegameRedirect(const std::vector<Patch>& 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 } // namespace DiscIO::Riivolution

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@ -12,6 +13,12 @@
namespace DiscIO::Riivolution namespace DiscIO::Riivolution
{ {
struct SavegameRedirect
{
std::string m_target_path;
bool m_clone;
};
class FileDataLoader class FileDataLoader
{ {
public: public:
@ -28,6 +35,8 @@ public:
virtual BuilderContentSource MakeContentSource(std::string_view external_relative_path, virtual BuilderContentSource MakeContentSource(std::string_view external_relative_path,
u64 external_offset, u64 external_size, u64 external_offset, u64 external_size,
u64 disc_offset) = 0; u64 disc_offset) = 0;
virtual std::optional<std::string>
ResolveSavegameRedirectPath(std::string_view external_relative_path) = 0;
}; };
class FileDataLoaderHostFS : public FileDataLoader class FileDataLoaderHostFS : public FileDataLoader
@ -46,6 +55,8 @@ public:
BuilderContentSource MakeContentSource(std::string_view external_relative_path, BuilderContentSource MakeContentSource(std::string_view external_relative_path,
u64 external_offset, u64 external_size, u64 external_offset, u64 external_size,
u64 disc_offset) override; u64 disc_offset) override;
std::optional<std::string>
ResolveSavegameRedirectPath(std::string_view external_relative_path) override;
private: private:
std::optional<std::string> MakeAbsoluteFromRelative(std::string_view external_relative_path); std::optional<std::string> MakeAbsoluteFromRelative(std::string_view external_relative_path);
@ -58,4 +69,6 @@ void ApplyPatchesToFiles(const std::vector<Patch>& patches,
std::vector<DiscIO::FSTBuilderNode>* fst, std::vector<DiscIO::FSTBuilderNode>* fst,
DiscIO::FSTBuilderNode* dol_node); DiscIO::FSTBuilderNode* dol_node);
void ApplyPatchesToMemory(const std::vector<Patch>& patches); void ApplyPatchesToMemory(const std::vector<Patch>& patches);
std::optional<SavegameRedirect>
ExtractSavegameRedirect(const std::vector<Patch>& riivolution_patches);
} // namespace DiscIO::Riivolution } // namespace DiscIO::Riivolution