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"
#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();

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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{};

View File

@ -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);

View File

@ -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,15 +309,32 @@ 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()
{

View File

@ -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

View File

@ -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

View File

@ -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