mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #10127 from AdmiralCurtiss/riivolution
HLE Riivolution patch support
This commit is contained in:
@@ -47,6 +47,7 @@
|
||||
#define SCREENSHOTS_DIR "ScreenShots"
|
||||
#define LOAD_DIR "Load"
|
||||
#define HIRES_TEXTURES_DIR "Textures"
|
||||
#define RIIVOLUTION_DIR "Riivolution"
|
||||
#define DUMP_DIR "Dump"
|
||||
#define DUMP_TEXTURES_DIR "Textures"
|
||||
#define DUMP_FRAMES_DIR "Frames"
|
||||
|
||||
@@ -943,6 +943,7 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
||||
s_user_paths[D_SCREENSHOTS_IDX] = s_user_paths[D_USER_IDX] + SCREENSHOTS_DIR DIR_SEP;
|
||||
s_user_paths[D_LOAD_IDX] = s_user_paths[D_USER_IDX] + LOAD_DIR DIR_SEP;
|
||||
s_user_paths[D_HIRESTEXTURES_IDX] = s_user_paths[D_LOAD_IDX] + HIRES_TEXTURES_DIR DIR_SEP;
|
||||
s_user_paths[D_RIIVOLUTION_IDX] = s_user_paths[D_LOAD_IDX] + RIIVOLUTION_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMP_IDX] = s_user_paths[D_USER_IDX] + DUMP_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPFRAMES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_FRAMES_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPOBJECTS_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_OBJECTS_DIR DIR_SEP;
|
||||
@@ -1035,6 +1036,7 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
||||
|
||||
case D_LOAD_IDX:
|
||||
s_user_paths[D_HIRESTEXTURES_IDX] = s_user_paths[D_LOAD_IDX] + HIRES_TEXTURES_DIR DIR_SEP;
|
||||
s_user_paths[D_RIIVOLUTION_IDX] = s_user_paths[D_LOAD_IDX] + RIIVOLUTION_DIR DIR_SEP;
|
||||
s_user_paths[D_DYNAMICINPUT_IDX] = s_user_paths[D_LOAD_IDX] + DYNAMICINPUT_DIR DIR_SEP;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ enum
|
||||
D_STATESAVES_IDX,
|
||||
D_SCREENSHOTS_IDX,
|
||||
D_HIRESTEXTURES_IDX,
|
||||
D_RIIVOLUTION_IDX,
|
||||
D_DUMP_IDX,
|
||||
D_DUMPFRAMES_IDX,
|
||||
D_DUMPOBJECTS_IDX,
|
||||
|
||||
@@ -57,6 +57,9 @@ namespace fs = std::filesystem;
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/GameModDescriptor.h"
|
||||
#include "DiscIO/RiivolutionParser.h"
|
||||
#include "DiscIO/RiivolutionPatcher.h"
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
#include "DiscIO/VolumeWad.h"
|
||||
|
||||
@@ -216,6 +219,31 @@ BootParameters::GenerateFromFile(std::vector<std::string> paths,
|
||||
return std::make_unique<BootParameters>(std::move(*wad), savestate_path);
|
||||
}
|
||||
|
||||
if (extension == ".json")
|
||||
{
|
||||
auto descriptor = DiscIO::ParseGameModDescriptorFile(path);
|
||||
if (descriptor)
|
||||
{
|
||||
auto boot_params = GenerateFromFile(descriptor->base_file, savestate_path);
|
||||
if (!boot_params)
|
||||
{
|
||||
PanicAlertFmtT("Could not recognize file {0}", descriptor->base_file);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (descriptor->riivolution && std::holds_alternative<Disc>(boot_params->parameters))
|
||||
{
|
||||
const auto& volume = *std::get<Disc>(boot_params->parameters).volume;
|
||||
AddRiivolutionPatches(boot_params.get(),
|
||||
DiscIO::Riivolution::GenerateRiivolutionPatchesFromGameModDescriptor(
|
||||
*descriptor->riivolution, volume.GetGameID(),
|
||||
volume.GetRevision(), volume.GetDiscNumber()));
|
||||
}
|
||||
|
||||
return boot_params;
|
||||
}
|
||||
}
|
||||
|
||||
PanicAlertFmtT("Could not recognize file {0}", path);
|
||||
return {};
|
||||
}
|
||||
@@ -422,7 +450,10 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
||||
|
||||
struct BootTitle
|
||||
{
|
||||
BootTitle() : config(SConfig::GetInstance()) {}
|
||||
BootTitle(const std::vector<DiscIO::Riivolution::Patch>& patches)
|
||||
: config(SConfig::GetInstance()), riivolution_patches(patches)
|
||||
{
|
||||
}
|
||||
bool operator()(BootParameters::Disc& disc) const
|
||||
{
|
||||
NOTICE_LOG_FMT(BOOT, "Booting from disc: {}", disc.path);
|
||||
@@ -432,7 +463,7 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
||||
if (!volume)
|
||||
return false;
|
||||
|
||||
if (!EmulatedBS2(config.bWii, *volume))
|
||||
if (!EmulatedBS2(config.bWii, *volume, riivolution_patches))
|
||||
return false;
|
||||
|
||||
SConfig::OnNewTitleLoad();
|
||||
@@ -542,11 +573,14 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
||||
|
||||
private:
|
||||
const SConfig& config;
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches;
|
||||
};
|
||||
|
||||
if (!std::visit(BootTitle(), boot->parameters))
|
||||
if (!std::visit(BootTitle(boot->riivolution_patches), boot->parameters))
|
||||
return false;
|
||||
|
||||
DiscIO::Riivolution::ApplyGeneralMemoryPatches(boot->riivolution_patches);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -604,3 +638,20 @@ void CreateSystemMenuTitleDirs()
|
||||
const auto es = IOS::HLE::GetIOS()->GetES();
|
||||
es->CreateTitleDirectories(Titles::SYSTEM_MENU, IOS::SYSMENU_GID);
|
||||
}
|
||||
|
||||
void AddRiivolutionPatches(BootParameters* boot_params,
|
||||
std::vector<DiscIO::Riivolution::Patch> riivolution_patches)
|
||||
{
|
||||
if (riivolution_patches.empty())
|
||||
return;
|
||||
if (!std::holds_alternative<BootParameters::Disc>(boot_params->parameters))
|
||||
return;
|
||||
|
||||
auto& disc = std::get<BootParameters::Disc>(boot_params->parameters);
|
||||
disc.volume = DiscIO::CreateDisc(DiscIO::DirectoryBlobReader::Create(
|
||||
std::move(disc.volume),
|
||||
[&](std::vector<DiscIO::FSTBuilderNode>* fst, DiscIO::FSTBuilderNode* dol_node) {
|
||||
DiscIO::Riivolution::ApplyPatchesToFiles(riivolution_patches, fst, dol_node);
|
||||
}));
|
||||
boot_params->riivolution_patches = std::move(riivolution_patches);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "Core/IOS/IOSC.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/RiivolutionParser.h"
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
#include "DiscIO/VolumeWad.h"
|
||||
|
||||
@@ -78,6 +79,7 @@ struct BootParameters
|
||||
BootParameters(Parameters&& parameters_, const std::optional<std::string>& savestate_path_ = {});
|
||||
|
||||
Parameters parameters;
|
||||
std::vector<DiscIO::Riivolution::Patch> riivolution_patches;
|
||||
std::optional<std::string> savestate_path;
|
||||
bool delete_savestate = false;
|
||||
};
|
||||
@@ -113,10 +115,14 @@ private:
|
||||
|
||||
static void SetupMSR();
|
||||
static void SetupBAT(bool is_wii);
|
||||
static bool RunApploader(bool is_wii, const DiscIO::VolumeDisc& volume);
|
||||
static bool EmulatedBS2_GC(const DiscIO::VolumeDisc& volume);
|
||||
static bool EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume);
|
||||
static bool EmulatedBS2(bool is_wii, const DiscIO::VolumeDisc& volume);
|
||||
static bool RunApploader(bool is_wii, const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches);
|
||||
static bool EmulatedBS2_GC(const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches);
|
||||
static bool EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches);
|
||||
static bool EmulatedBS2(bool is_wii, const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches);
|
||||
static bool Load_BS2(const std::string& boot_rom_filename);
|
||||
|
||||
static void SetupGCMemory();
|
||||
@@ -161,3 +167,6 @@ void UpdateStateFlags(std::function<void(StateFlags*)> update_function);
|
||||
/// Normally, this is automatically done by ES when the System Menu is installed,
|
||||
/// but we cannot rely on this because we don't require any system titles to be installed.
|
||||
void CreateSystemMenuTitleDirs();
|
||||
|
||||
void AddRiivolutionPatches(BootParameters* boot_params,
|
||||
std::vector<DiscIO::Riivolution::Patch> riivolution_patches);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/RiivolutionPatcher.h"
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
|
||||
namespace
|
||||
@@ -87,7 +88,8 @@ void CBoot::SetupBAT(bool is_wii)
|
||||
PowerPC::IBATUpdated();
|
||||
}
|
||||
|
||||
bool CBoot::RunApploader(bool is_wii, const DiscIO::VolumeDisc& volume)
|
||||
bool CBoot::RunApploader(bool is_wii, const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches)
|
||||
{
|
||||
const DiscIO::Partition partition = volume.GetGamePartition();
|
||||
|
||||
@@ -148,6 +150,8 @@ bool CBoot::RunApploader(bool is_wii, const DiscIO::VolumeDisc& volume)
|
||||
ram_address, length);
|
||||
DVDRead(volume, dvd_offset, ram_address, length, partition);
|
||||
|
||||
DiscIO::Riivolution::ApplyApploaderMemoryPatches(riivolution_patches, ram_address, length);
|
||||
|
||||
PowerPC::ppcState.gpr[3] = 0x81300004;
|
||||
PowerPC::ppcState.gpr[4] = 0x81300008;
|
||||
PowerPC::ppcState.gpr[5] = 0x8130000c;
|
||||
@@ -203,7 +207,8 @@ void CBoot::SetupGCMemory()
|
||||
// GameCube Bootstrap 2 HLE:
|
||||
// copy the apploader to 0x81200000
|
||||
// execute the apploader, function by function, using the above utility.
|
||||
bool CBoot::EmulatedBS2_GC(const DiscIO::VolumeDisc& volume)
|
||||
bool CBoot::EmulatedBS2_GC(const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches)
|
||||
{
|
||||
INFO_LOG_FMT(BOOT, "Faking GC BS2...");
|
||||
|
||||
@@ -240,7 +245,7 @@ bool CBoot::EmulatedBS2_GC(const DiscIO::VolumeDisc& volume)
|
||||
// Global pointer to Small Data Area Base (Luigi's Mansion's apploader uses it)
|
||||
PowerPC::ppcState.gpr[13] = ntsc ? 0x81465320 : 0x814b4fc0;
|
||||
|
||||
return RunApploader(/*is_wii*/ false, volume);
|
||||
return RunApploader(/*is_wii*/ false, volume, riivolution_patches);
|
||||
}
|
||||
|
||||
static DiscIO::Region CodeRegion(char c)
|
||||
@@ -436,7 +441,8 @@ static void WriteEmptyPlayRecord()
|
||||
// Wii Bootstrap 2 HLE:
|
||||
// copy the apploader to 0x81200000
|
||||
// execute the apploader
|
||||
bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume)
|
||||
bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches)
|
||||
{
|
||||
INFO_LOG_FMT(BOOT, "Faking Wii BS2...");
|
||||
if (volume.GetVolumeType() != DiscIO::Platform::WiiDisc)
|
||||
@@ -493,7 +499,7 @@ bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume)
|
||||
|
||||
PowerPC::ppcState.gpr[1] = 0x816ffff0; // StackPointer
|
||||
|
||||
if (!RunApploader(/*is_wii*/ true, volume))
|
||||
if (!RunApploader(/*is_wii*/ true, volume, riivolution_patches))
|
||||
return false;
|
||||
|
||||
// The Apploader probably just overwrote values needed for RAM Override. Run this again!
|
||||
@@ -508,7 +514,9 @@ bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume)
|
||||
|
||||
// Returns true if apploader has run successfully. If is_wii is true, the disc
|
||||
// that volume refers to must currently be inserted into the emulated disc drive.
|
||||
bool CBoot::EmulatedBS2(bool is_wii, const DiscIO::VolumeDisc& volume)
|
||||
bool CBoot::EmulatedBS2(bool is_wii, const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches)
|
||||
{
|
||||
return is_wii ? EmulatedBS2_Wii(volume) : EmulatedBS2_GC(volume);
|
||||
return is_wii ? EmulatedBS2_Wii(volume, riivolution_patches) :
|
||||
EmulatedBS2_GC(volume, riivolution_patches);
|
||||
}
|
||||
|
||||
@@ -418,6 +418,10 @@ bool BootCore(std::unique_ptr<BootParameters> boot, const WindowSystemInfo& wsi)
|
||||
if (StartUp.bWii && DiscIO::IsNTSC(StartUp.m_region) && Config::Get(Config::SYSCONF_PAL60))
|
||||
Config::SetCurrent(Config::SYSCONF_PAL60, false);
|
||||
|
||||
// Disable loading time emulation for Riivolution-patched games until we have proper emulation.
|
||||
if (!boot->riivolution_patches.empty())
|
||||
StartUp.bFastDiscSpeed = true;
|
||||
|
||||
Core::UpdateWantDeterminism(/*initial*/ true);
|
||||
|
||||
if (StartUp.bWii)
|
||||
|
||||
@@ -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,10 @@ 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 auto host_old_info = BuildFilename(old_path);
|
||||
const auto host_new_info = BuildFilename(new_path);
|
||||
const std::string& host_old_path = host_old_info.host_path;
|
||||
const std::string& host_new_path = host_new_info.host_path;
|
||||
|
||||
// If there is already something of the same type at the new path, delete it.
|
||||
if (File::Exists(host_new_path))
|
||||
@@ -509,8 +526,27 @@ ResultCode HostFileSystem::Rename(Uid uid, Gid gid, const std::string& old_path,
|
||||
|
||||
if (!File::Rename(host_old_path, host_new_path))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_FS, "Rename {} to {} - failed", host_old_path, host_new_path);
|
||||
return ResultCode::NotFound;
|
||||
if (host_old_info.is_redirect || host_new_info.is_redirect)
|
||||
{
|
||||
// If either path is a redirect, the source and target may be on a different partition or
|
||||
// device, so a simple rename may not work. Fall back to Copy & Delete and see if that works.
|
||||
if (!File::Copy(host_old_path, host_new_path))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_FS, "Copying {} to {} in Rename fallback failed", host_old_path,
|
||||
host_new_path);
|
||||
return ResultCode::NotFound;
|
||||
}
|
||||
if (!File::Delete(host_old_path))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_FS, "Deleting {} in Rename fallback failed", host_old_path);
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_FS, "Rename {} to {} - failed", host_old_path, host_new_path);
|
||||
return ResultCode::NotFound;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, remove the child from the old parent and move it to the new parent.
|
||||
@@ -544,7 +580,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 +648,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 +667,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 +703,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 +721,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();
|
||||
|
||||
// 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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,11 +23,17 @@ add_library(discio
|
||||
FileSystemGCWii.h
|
||||
Filesystem.cpp
|
||||
Filesystem.h
|
||||
GameModDescriptor.cpp
|
||||
GameModDescriptor.h
|
||||
LaggedFibonacciGenerator.cpp
|
||||
LaggedFibonacciGenerator.h
|
||||
MultithreadedCompressor.h
|
||||
NANDImporter.cpp
|
||||
NANDImporter.h
|
||||
RiivolutionParser.cpp
|
||||
RiivolutionParser.h
|
||||
RiivolutionPatcher.cpp
|
||||
RiivolutionPatcher.h
|
||||
ScrubbedBlob.cpp
|
||||
ScrubbedBlob.h
|
||||
TGCBlob.cpp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -16,6 +17,7 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/WiiEncryptionCache.h"
|
||||
|
||||
namespace File
|
||||
@@ -29,22 +31,108 @@ namespace DiscIO
|
||||
enum class PartitionType : u32;
|
||||
|
||||
class DirectoryBlobReader;
|
||||
class VolumeDisc;
|
||||
|
||||
// Returns true if the path is inside a DirectoryBlob and doesn't represent the DirectoryBlob itself
|
||||
bool ShouldHideFromGameList(const std::string& volume_path);
|
||||
|
||||
// Content chunk that is loaded from a file in the host file system.
|
||||
struct ContentFile
|
||||
{
|
||||
// Path where the file can be found.
|
||||
std::string m_filename;
|
||||
|
||||
// Offset from the start of the file where the first byte of this content chunk is.
|
||||
u64 m_offset;
|
||||
};
|
||||
|
||||
// Content chunk that loads data from a DirectoryBlobReader.
|
||||
// Intented for representing a partition within a disc.
|
||||
struct ContentPartition
|
||||
{
|
||||
// The reader to read data from.
|
||||
DirectoryBlobReader* m_reader;
|
||||
|
||||
// Offset from the start of the partition for the first byte represented by this chunk.
|
||||
u64 m_offset;
|
||||
|
||||
// The value passed as partition_data_offset to EncryptPartitionData().
|
||||
u64 m_partition_data_offset;
|
||||
};
|
||||
|
||||
// Content chunk that loads data from a Volume.
|
||||
struct ContentVolume
|
||||
{
|
||||
// Offset from the start of the volume for the first byte represented by this chunk.
|
||||
u64 m_offset;
|
||||
|
||||
// The volume to read data from.
|
||||
const Volume* m_volume;
|
||||
|
||||
// The partition passed to the Volume's Read() method.
|
||||
Partition m_partition;
|
||||
};
|
||||
|
||||
// Content chunk representing a run of identical bytes.
|
||||
// Useful for padding between chunks within a file.
|
||||
struct ContentFixedByte
|
||||
{
|
||||
u8 m_byte;
|
||||
};
|
||||
|
||||
using ContentSource = std::variant<ContentFile, // File
|
||||
const u8*, // Memory
|
||||
ContentPartition, // Partition
|
||||
ContentVolume, // Volume
|
||||
ContentFixedByte // Fixed value padding
|
||||
>;
|
||||
|
||||
struct BuilderContentSource
|
||||
{
|
||||
u64 m_offset;
|
||||
u64 m_size;
|
||||
ContentSource m_source;
|
||||
};
|
||||
|
||||
struct FSTBuilderNode
|
||||
{
|
||||
std::string m_filename;
|
||||
u64 m_size;
|
||||
std::variant<std::vector<BuilderContentSource>, std::vector<FSTBuilderNode>> m_content;
|
||||
void* m_user_data = nullptr;
|
||||
|
||||
bool IsFile() const
|
||||
{
|
||||
return std::holds_alternative<std::vector<BuilderContentSource>>(m_content);
|
||||
}
|
||||
|
||||
std::vector<BuilderContentSource>& GetFileContent()
|
||||
{
|
||||
return std::get<std::vector<BuilderContentSource>>(m_content);
|
||||
}
|
||||
|
||||
const std::vector<BuilderContentSource>& GetFileContent() const
|
||||
{
|
||||
return std::get<std::vector<BuilderContentSource>>(m_content);
|
||||
}
|
||||
|
||||
bool IsFolder() const { return std::holds_alternative<std::vector<FSTBuilderNode>>(m_content); }
|
||||
|
||||
std::vector<FSTBuilderNode>& GetFolderContent()
|
||||
{
|
||||
return std::get<std::vector<FSTBuilderNode>>(m_content);
|
||||
}
|
||||
|
||||
const std::vector<FSTBuilderNode>& GetFolderContent() const
|
||||
{
|
||||
return std::get<std::vector<FSTBuilderNode>>(m_content);
|
||||
}
|
||||
};
|
||||
|
||||
class DiscContent
|
||||
{
|
||||
public:
|
||||
using ContentSource =
|
||||
std::variant<std::string, // File
|
||||
const u8*, // Memory
|
||||
DirectoryBlobReader* // Partition (which one it is is determined by m_offset)
|
||||
>;
|
||||
|
||||
DiscContent(u64 offset, u64 size, const std::string& path);
|
||||
DiscContent(u64 offset, u64 size, const u8* data);
|
||||
DiscContent(u64 offset, u64 size, DirectoryBlobReader* blob);
|
||||
DiscContent(u64 offset, u64 size, ContentSource source);
|
||||
|
||||
// Provided because it's convenient when searching for DiscContent in an std::set
|
||||
explicit DiscContent(u64 offset);
|
||||
@@ -62,8 +150,13 @@ public:
|
||||
bool operator>=(const DiscContent& other) const { return !(*this < other); }
|
||||
|
||||
private:
|
||||
// Position of this content chunk within its parent DiscContentContainer.
|
||||
u64 m_offset;
|
||||
|
||||
// Number of bytes this content chunk takes up.
|
||||
u64 m_size = 0;
|
||||
|
||||
// Where and how to find the data for this content chunk.
|
||||
ContentSource m_content_source;
|
||||
};
|
||||
|
||||
@@ -71,13 +164,11 @@ class DiscContentContainer
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
void Add(u64 offset, const std::vector<T>& vector)
|
||||
void AddReference(u64 offset, const std::vector<T>& vector)
|
||||
{
|
||||
return Add(offset, vector.size() * sizeof(T), reinterpret_cast<const u8*>(vector.data()));
|
||||
}
|
||||
void Add(u64 offset, u64 size, const std::string& path);
|
||||
void Add(u64 offset, u64 size, const u8* data);
|
||||
void Add(u64 offset, u64 size, DirectoryBlobReader* blob);
|
||||
void Add(u64 offset, u64 size, ContentSource source);
|
||||
u64 CheckSizeAndAdd(u64 offset, const std::string& path);
|
||||
u64 CheckSizeAndAdd(u64 offset, u64 max_size, const std::string& path);
|
||||
|
||||
@@ -92,6 +183,10 @@ class DirectoryBlobPartition
|
||||
public:
|
||||
DirectoryBlobPartition() = default;
|
||||
DirectoryBlobPartition(const std::string& root_directory, std::optional<bool> is_wii);
|
||||
DirectoryBlobPartition(DiscIO::VolumeDisc* volume, const DiscIO::Partition& partition,
|
||||
std::optional<bool> is_wii,
|
||||
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes,
|
||||
FSTBuilderNode* dol_node)>& fst_callback);
|
||||
|
||||
// We do not allow copying, because it might mess up the pointers inside DiscContents
|
||||
DirectoryBlobPartition(const DirectoryBlobPartition&) = delete;
|
||||
@@ -104,27 +199,38 @@ public:
|
||||
const std::string& GetRootDirectory() const { return m_root_directory; }
|
||||
const std::vector<u8>& GetHeader() const { return m_disc_header; }
|
||||
const DiscContentContainer& GetContents() const { return m_contents; }
|
||||
const std::optional<DiscIO::Partition>& GetWrappedPartition() const
|
||||
{
|
||||
return m_wrapped_partition;
|
||||
}
|
||||
|
||||
const std::array<u8, VolumeWii::AES_KEY_SIZE>& GetKey() const { return m_key; }
|
||||
void SetKey(std::array<u8, VolumeWii::AES_KEY_SIZE> key) { m_key = key; }
|
||||
|
||||
private:
|
||||
void SetDiscHeaderAndDiscType(std::optional<bool> is_wii);
|
||||
void SetBI2();
|
||||
void SetDiscHeaderFromFile(const std::string& boot_bin_path);
|
||||
void SetDiscHeader(std::vector<u8> boot_bin);
|
||||
void SetDiscType(std::optional<bool> is_wii);
|
||||
void SetBI2FromFile(const std::string& bi2_path);
|
||||
void SetBI2(std::vector<u8> bi2);
|
||||
|
||||
// Returns DOL address
|
||||
u64 SetApploader();
|
||||
u64 SetApploaderFromFile(const std::string& path);
|
||||
u64 SetApploader(std::vector<u8> apploader, const std::string& log_path);
|
||||
// Returns FST address
|
||||
u64 SetDOL(u64 dol_address);
|
||||
u64 SetDOLFromFile(const std::string& path, u64 dol_address);
|
||||
u64 SetDOL(FSTBuilderNode dol_node, u64 dol_address);
|
||||
|
||||
void BuildFST(u64 fst_address);
|
||||
void BuildFSTFromFolder(const std::string& fst_root_path, u64 fst_address);
|
||||
void BuildFST(std::vector<FSTBuilderNode> root_nodes, u64 fst_address);
|
||||
|
||||
// FST creation
|
||||
void WriteEntryData(u32* entry_offset, u8 type, u32 name_offset, u64 data_offset, u64 length,
|
||||
u32 address_shift);
|
||||
void WriteEntryName(u32* name_offset, const std::string& name, u64 name_table_offset);
|
||||
void WriteDirectory(const File::FSTEntry& parent_entry, u32* fst_offset, u32* name_offset,
|
||||
u64* data_offset, u32 parent_entry_index, u64 name_table_offset);
|
||||
void WriteDirectory(std::vector<FSTBuilderNode>* parent_entries, u32* fst_offset,
|
||||
u32* name_offset, u64* data_offset, u32 parent_entry_index,
|
||||
u64 name_table_offset);
|
||||
|
||||
DiscContentContainer m_contents;
|
||||
std::vector<u8> m_disc_header;
|
||||
@@ -140,6 +246,8 @@ private:
|
||||
u32 m_address_shift = 0;
|
||||
|
||||
u64 m_data_size = 0;
|
||||
|
||||
std::optional<DiscIO::Partition> m_wrapped_partition = std::nullopt;
|
||||
};
|
||||
|
||||
class DirectoryBlobReader : public BlobReader
|
||||
@@ -148,6 +256,10 @@ class DirectoryBlobReader : public BlobReader
|
||||
|
||||
public:
|
||||
static std::unique_ptr<DirectoryBlobReader> Create(const std::string& dol_path);
|
||||
static std::unique_ptr<DirectoryBlobReader> Create(
|
||||
std::unique_ptr<DiscIO::VolumeDisc> volume,
|
||||
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>&
|
||||
fst_callback);
|
||||
|
||||
// We do not allow copying, because it might mess up the pointers inside DiscContents
|
||||
DirectoryBlobReader(const DirectoryBlobReader&) = delete;
|
||||
@@ -183,15 +295,21 @@ private:
|
||||
|
||||
explicit DirectoryBlobReader(const std::string& game_partition_root,
|
||||
const std::string& true_root);
|
||||
explicit DirectoryBlobReader(std::unique_ptr<DiscIO::VolumeDisc> volume,
|
||||
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes,
|
||||
FSTBuilderNode* dol_node)>& fst_callback);
|
||||
|
||||
const DirectoryBlobPartition* GetPartition(u64 offset, u64 size, u64 partition_data_offset) const;
|
||||
|
||||
bool EncryptPartitionData(u64 offset, u64 size, u8* buffer, u64 partition_data_offset,
|
||||
u64 partition_data_decrypted_size);
|
||||
|
||||
void SetNonpartitionDiscHeaderFromFile(const std::vector<u8>& partition_header,
|
||||
const std::string& game_partition_root);
|
||||
void SetNonpartitionDiscHeader(const std::vector<u8>& partition_header,
|
||||
const std::string& game_partition_root);
|
||||
void SetWiiRegionData(const std::string& game_partition_root);
|
||||
std::vector<u8> header_bin);
|
||||
void SetWiiRegionDataFromFile(const std::string& game_partition_root);
|
||||
void SetWiiRegionData(const std::vector<u8>& wii_region_data, const std::string& log_path);
|
||||
void SetPartitions(std::vector<PartitionWithType>&& partitions);
|
||||
void SetPartitionHeader(DirectoryBlobPartition* partition, u64 partition_address);
|
||||
|
||||
@@ -209,9 +327,11 @@ private:
|
||||
std::vector<u8> m_disc_header_nonpartition;
|
||||
std::vector<u8> m_partition_table;
|
||||
std::vector<u8> m_wii_region_data;
|
||||
std::vector<std::vector<u8>> m_partition_headers;
|
||||
std::vector<std::vector<u8>> m_extra_data;
|
||||
|
||||
u64 m_data_size;
|
||||
|
||||
std::unique_ptr<DiscIO::VolumeDisc> m_wrapped_volume;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2021 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "DiscIO/GameModDescriptor.h"
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
static std::string MakeAbsolute(const std::string& directory, const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return PathToString(StringToPath(directory) / StringToPath(path));
|
||||
#else
|
||||
if (StringBeginsWith(path, "/"))
|
||||
return path;
|
||||
return directory + "/" + path;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<GameModDescriptor> ParseGameModDescriptorFile(const std::string& filename)
|
||||
{
|
||||
::File::IOFile f(filename, "rb");
|
||||
if (!f)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<char> data;
|
||||
data.resize(f.GetSize());
|
||||
if (!f.ReadBytes(data.data(), data.size()))
|
||||
return std::nullopt;
|
||||
|
||||
#ifdef _WIN32
|
||||
std::string path = ReplaceAll(filename, "\\", "/");
|
||||
#else
|
||||
const std::string& path = filename;
|
||||
#endif
|
||||
return ParseGameModDescriptorString(std::string_view(data.data(), data.size()), path);
|
||||
}
|
||||
|
||||
static std::vector<GameModDescriptorRiivolutionPatchOption>
|
||||
ParseRiivolutionOptions(const picojson::array& array)
|
||||
{
|
||||
std::vector<GameModDescriptorRiivolutionPatchOption> options;
|
||||
for (const auto& option_object : array)
|
||||
{
|
||||
if (!option_object.is<picojson::object>())
|
||||
continue;
|
||||
|
||||
auto& option = options.emplace_back();
|
||||
for (const auto& [key, value] : option_object.get<picojson::object>())
|
||||
{
|
||||
if (key == "section-name" && value.is<std::string>())
|
||||
option.section_name = value.get<std::string>();
|
||||
else if (key == "option-id" && value.is<std::string>())
|
||||
option.option_id = value.get<std::string>();
|
||||
else if (key == "option-name" && value.is<std::string>())
|
||||
option.option_name = value.get<std::string>();
|
||||
else if (key == "choice" && value.is<double>())
|
||||
option.choice = MathUtil::SaturatingCast<u32>(value.get<double>());
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
static GameModDescriptorRiivolution ParseRiivolutionObject(const std::string& json_directory,
|
||||
const picojson::object& object)
|
||||
{
|
||||
GameModDescriptorRiivolution r;
|
||||
for (const auto& [element_key, element_value] : object)
|
||||
{
|
||||
if (element_key == "patches" && element_value.is<picojson::array>())
|
||||
{
|
||||
for (const auto& patch_object : element_value.get<picojson::array>())
|
||||
{
|
||||
if (!patch_object.is<picojson::object>())
|
||||
continue;
|
||||
|
||||
auto& patch = r.patches.emplace_back();
|
||||
for (const auto& [key, value] : patch_object.get<picojson::object>())
|
||||
{
|
||||
if (key == "xml" && value.is<std::string>())
|
||||
patch.xml = MakeAbsolute(json_directory, value.get<std::string>());
|
||||
else if (key == "root" && value.is<std::string>())
|
||||
patch.root = MakeAbsolute(json_directory, value.get<std::string>());
|
||||
else if (key == "options" && value.is<picojson::array>())
|
||||
patch.options = ParseRiivolutionOptions(value.get<picojson::array>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
std::optional<GameModDescriptor> ParseGameModDescriptorString(std::string_view json,
|
||||
std::string_view json_path)
|
||||
{
|
||||
std::string json_directory;
|
||||
SplitPath(json_path, &json_directory, nullptr, nullptr);
|
||||
|
||||
picojson::value json_root;
|
||||
std::string err;
|
||||
picojson::parse(json_root, json.begin(), json.end(), &err);
|
||||
if (!err.empty())
|
||||
return std::nullopt;
|
||||
if (!json_root.is<picojson::object>())
|
||||
return std::nullopt;
|
||||
|
||||
GameModDescriptor descriptor;
|
||||
bool is_valid_version = false;
|
||||
for (const auto& [key, value] : json_root.get<picojson::object>())
|
||||
{
|
||||
if (key == "version" && value.is<double>())
|
||||
{
|
||||
is_valid_version = value.get<double>() == 1.0;
|
||||
}
|
||||
else if (key == "base-file" && value.is<std::string>())
|
||||
{
|
||||
descriptor.base_file = MakeAbsolute(json_directory, value.get<std::string>());
|
||||
}
|
||||
else if (key == "display-name" && value.is<std::string>())
|
||||
{
|
||||
descriptor.display_name = value.get<std::string>();
|
||||
}
|
||||
else if (key == "banner" && value.is<std::string>())
|
||||
{
|
||||
descriptor.banner = MakeAbsolute(json_directory, value.get<std::string>());
|
||||
}
|
||||
else if (key == "riivolution" && value.is<picojson::object>())
|
||||
{
|
||||
descriptor.riivolution =
|
||||
ParseRiivolutionObject(json_directory, value.get<picojson::object>());
|
||||
}
|
||||
}
|
||||
if (!is_valid_version)
|
||||
return std::nullopt;
|
||||
return descriptor;
|
||||
}
|
||||
} // namespace DiscIO
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user