mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-14 21:37:52 -07:00
Core: Implement Wii NAND path redirects for Riivolution savegame patches.
This commit is contained in:
parent
588c31acb6
commit
fe242f79ee
@ -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<BootParameters> boot, WindowSystemInfo wsi
|
||||
else
|
||||
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)))
|
||||
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.
|
||||
Common::ScopeGuard wiifs_guard{&Core::CleanUpWiiFileSystemContents};
|
||||
if (SConfig::GetInstance().bWii)
|
||||
Core::InitializeWiiFileSystemContents();
|
||||
Core::InitializeWiiFileSystemContents(savegame_redirect);
|
||||
else
|
||||
wiifs_guard.Dismiss();
|
||||
|
||||
|
@ -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<NandStats> GetNandStats() = 0;
|
||||
/// Get usage information about a directory (used cluster and inode counts).
|
||||
virtual Result<DirectoryStats> GetDirectoryStats(const std::string& path) = 0;
|
||||
|
||||
virtual void SetNandRedirects(std::vector<NandRedirect> nand_redirects) = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@ -269,7 +280,8 @@ enum class Location
|
||||
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.
|
||||
IOS::HLE::ReturnCode ConvertResult(ResultCode code);
|
||||
|
@ -28,11 +28,12 @@ SplitPathResult SplitPathAndBasename(std::string_view path)
|
||||
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 =
|
||||
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)
|
||||
|
@ -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<NandRedirect> 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<std::vector<std::string>> 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<Metadata> 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<DirectoryStats> 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<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_
|
||||
return stats;
|
||||
}
|
||||
|
||||
void HostFileSystem::SetNandRedirects(std::vector<NandRedirect> nand_redirects)
|
||||
{
|
||||
m_nand_redirects = std::move(nand_redirects);
|
||||
}
|
||||
} // namespace IOS::HLE::FS
|
||||
|
@ -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<NandRedirect> nand_redirects = {});
|
||||
~HostFileSystem();
|
||||
|
||||
void DoState(PointerWrap& p) override;
|
||||
@ -56,6 +56,8 @@ public:
|
||||
Result<NandStats> GetNandStats() override;
|
||||
Result<DirectoryStats> GetDirectoryStats(const std::string& path) override;
|
||||
|
||||
void SetNandRedirects(std::vector<NandRedirect> 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<File::IOFile> 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<std::string, std::weak_ptr<File::IOFile>> m_open_files;
|
||||
std::array<Handle, 16> m_handles{};
|
||||
|
||||
FstEntry m_redirect_fst{};
|
||||
std::vector<NandRedirect> m_nand_redirects;
|
||||
};
|
||||
|
||||
} // namespace IOS::HLE::FS
|
||||
|
@ -80,7 +80,7 @@ Result<FileHandle> 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{};
|
||||
|
@ -498,7 +498,7 @@ void Kernel::AddDevice(std::unique_ptr<Device> 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);
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "Core/WiiRoot.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -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<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)
|
||||
{
|
||||
@ -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<DiscIO::Riivolution::SavegameRedirect> 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();
|
||||
|
||||
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()
|
||||
|
@ -3,6 +3,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#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<DiscIO::Riivolution::SavegameRedirect> save_redirect);
|
||||
void CleanUpWiiFileSystemContents();
|
||||
|
||||
const std::vector<IOS::HLE::FS::NandRedirect>& GetActiveNandRedirects();
|
||||
} // namespace Core
|
||||
|
@ -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<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
|
||||
// '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<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
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@ -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<std::string>
|
||||
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<std::string>
|
||||
ResolveSavegameRedirectPath(std::string_view external_relative_path) override;
|
||||
|
||||
private:
|
||||
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,
|
||||
DiscIO::FSTBuilderNode* dol_node);
|
||||
void ApplyPatchesToMemory(const std::vector<Patch>& patches);
|
||||
std::optional<SavegameRedirect>
|
||||
ExtractSavegameRedirect(const std::vector<Patch>& riivolution_patches);
|
||||
} // namespace DiscIO::Riivolution
|
||||
|
Loading…
Reference in New Issue
Block a user