Core: Add ability to specify and launch a riivolution-modded game via a .json file.
This commit is contained in:
parent
fe242f79ee
commit
6394960f54
|
@ -57,6 +57,8 @@ namespace fs = std::filesystem;
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
|
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
|
#include "DiscIO/GameModDescriptor.h"
|
||||||
|
#include "DiscIO/RiivolutionParser.h"
|
||||||
#include "DiscIO/RiivolutionPatcher.h"
|
#include "DiscIO/RiivolutionPatcher.h"
|
||||||
#include "DiscIO/VolumeDisc.h"
|
#include "DiscIO/VolumeDisc.h"
|
||||||
#include "DiscIO/VolumeWad.h"
|
#include "DiscIO/VolumeWad.h"
|
||||||
|
@ -217,6 +219,40 @@ BootParameters::GenerateFromFile(std::vector<std::string> paths,
|
||||||
return std::make_unique<BootParameters>(std::move(*wad), savestate_path);
|
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))
|
||||||
|
{
|
||||||
|
auto& disc = std::get<Disc>(boot_params->parameters);
|
||||||
|
const auto& volume = *disc.volume;
|
||||||
|
boot_params->riivolution_patches =
|
||||||
|
DiscIO::Riivolution::GenerateRiivolutionPatchesFromGameModDescriptor(
|
||||||
|
*descriptor->riivolution, volume.GetGameID(), volume.GetRevision(),
|
||||||
|
volume.GetDiscNumber());
|
||||||
|
if (!boot_params->riivolution_patches.empty())
|
||||||
|
{
|
||||||
|
disc.volume = DiscIO::CreateDisc(DiscIO::DirectoryBlobReader::Create(
|
||||||
|
std::move(disc.volume),
|
||||||
|
[&](std::vector<DiscIO::FSTBuilderNode>* fst, DiscIO::FSTBuilderNode* dol_node) {
|
||||||
|
DiscIO::Riivolution::ApplyPatchesToFiles(boot_params->riivolution_patches, fst,
|
||||||
|
dol_node);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boot_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PanicAlertFmtT("Could not recognize file {0}", path);
|
PanicAlertFmtT("Could not recognize file {0}", path);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ add_library(discio
|
||||||
FileSystemGCWii.h
|
FileSystemGCWii.h
|
||||||
Filesystem.cpp
|
Filesystem.cpp
|
||||||
Filesystem.h
|
Filesystem.h
|
||||||
|
GameModDescriptor.cpp
|
||||||
|
GameModDescriptor.h
|
||||||
LaggedFibonacciGenerator.cpp
|
LaggedFibonacciGenerator.cpp
|
||||||
LaggedFibonacciGenerator.h
|
LaggedFibonacciGenerator.h
|
||||||
MultithreadedCompressor.h
|
MultithreadedCompressor.h
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2021 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
namespace DiscIO
|
||||||
|
{
|
||||||
|
struct GameModDescriptorRiivolutionPatchOption
|
||||||
|
{
|
||||||
|
std::string section_name;
|
||||||
|
std::string option_id;
|
||||||
|
std::string option_name;
|
||||||
|
u32 choice = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameModDescriptorRiivolutionPatch
|
||||||
|
{
|
||||||
|
std::string xml;
|
||||||
|
std::string root;
|
||||||
|
std::vector<GameModDescriptorRiivolutionPatchOption> options;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameModDescriptorRiivolution
|
||||||
|
{
|
||||||
|
std::vector<GameModDescriptorRiivolutionPatch> patches;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameModDescriptor
|
||||||
|
{
|
||||||
|
std::string base_file;
|
||||||
|
std::string display_name;
|
||||||
|
std::string banner;
|
||||||
|
std::optional<GameModDescriptorRiivolution> riivolution = std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<GameModDescriptor> ParseGameModDescriptorFile(const std::string& filename);
|
||||||
|
std::optional<GameModDescriptor> ParseGameModDescriptorString(std::string_view json,
|
||||||
|
std::string_view json_path);
|
||||||
|
} // namespace DiscIO
|
|
@ -13,6 +13,7 @@
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
#include "Common/IOFile.h"
|
#include "Common/IOFile.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
#include "DiscIO/GameModDescriptor.h"
|
||||||
#include "DiscIO/RiivolutionPatcher.h"
|
#include "DiscIO/RiivolutionPatcher.h"
|
||||||
|
|
||||||
namespace DiscIO::Riivolution
|
namespace DiscIO::Riivolution
|
||||||
|
@ -336,4 +337,47 @@ std::vector<Patch> Disc::GeneratePatches(const std::string& game_id) const
|
||||||
|
|
||||||
return active_patches;
|
return active_patches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Patch> GenerateRiivolutionPatchesFromGameModDescriptor(
|
||||||
|
const GameModDescriptorRiivolution& descriptor, const std::string& game_id,
|
||||||
|
std::optional<u16> revision, std::optional<u8> disc_number)
|
||||||
|
{
|
||||||
|
std::vector<Patch> result;
|
||||||
|
for (const auto& patch_info : descriptor.patches)
|
||||||
|
{
|
||||||
|
auto parsed = ParseFile(patch_info.xml);
|
||||||
|
if (!parsed || !parsed->IsValidForGame(game_id, revision, disc_number))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (auto& section : parsed->m_sections)
|
||||||
|
{
|
||||||
|
for (auto& option : section.m_options)
|
||||||
|
{
|
||||||
|
const auto* info = [&]() -> const DiscIO::GameModDescriptorRiivolutionPatchOption* {
|
||||||
|
for (const auto& o : patch_info.options)
|
||||||
|
{
|
||||||
|
if (o.section_name == section.m_name)
|
||||||
|
{
|
||||||
|
if (!o.option_id.empty() && o.option_id == option.m_id)
|
||||||
|
return &o;
|
||||||
|
if (!o.option_name.empty() && o.option_name == option.m_name)
|
||||||
|
return &o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}();
|
||||||
|
if (info && info->choice <= option.m_choices.size())
|
||||||
|
option.m_selected_choice = info->choice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& p : parsed->GeneratePatches(game_id))
|
||||||
|
{
|
||||||
|
p.m_file_data_loader = std::make_shared<DiscIO::Riivolution::FileDataLoaderHostFS>(
|
||||||
|
patch_info.root, parsed->m_xml_path, p.m_root);
|
||||||
|
result.emplace_back(std::move(p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
} // namespace DiscIO::Riivolution
|
} // namespace DiscIO::Riivolution
|
||||||
|
|
|
@ -12,6 +12,11 @@
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
namespace DiscIO
|
||||||
|
{
|
||||||
|
struct GameModDescriptorRiivolution;
|
||||||
|
}
|
||||||
|
|
||||||
namespace DiscIO::Riivolution
|
namespace DiscIO::Riivolution
|
||||||
{
|
{
|
||||||
class FileDataLoader;
|
class FileDataLoader;
|
||||||
|
@ -196,4 +201,7 @@ struct Disc
|
||||||
|
|
||||||
std::optional<Disc> ParseFile(const std::string& filename);
|
std::optional<Disc> ParseFile(const std::string& filename);
|
||||||
std::optional<Disc> ParseString(std::string_view xml, std::string xml_path);
|
std::optional<Disc> ParseString(std::string_view xml, std::string xml_path);
|
||||||
|
std::vector<Patch> GenerateRiivolutionPatchesFromGameModDescriptor(
|
||||||
|
const GameModDescriptorRiivolution& descriptor, const std::string& game_id,
|
||||||
|
std::optional<u16> revision, std::optional<u8> disc_number);
|
||||||
} // namespace DiscIO::Riivolution
|
} // namespace DiscIO::Riivolution
|
||||||
|
|
|
@ -430,6 +430,7 @@
|
||||||
<ClInclude Include="DiscIO\FileBlob.h" />
|
<ClInclude Include="DiscIO\FileBlob.h" />
|
||||||
<ClInclude Include="DiscIO\Filesystem.h" />
|
<ClInclude Include="DiscIO\Filesystem.h" />
|
||||||
<ClInclude Include="DiscIO\FileSystemGCWii.h" />
|
<ClInclude Include="DiscIO\FileSystemGCWii.h" />
|
||||||
|
<ClInclude Include="DiscIO\GameModDescriptor.h" />
|
||||||
<ClInclude Include="DiscIO\LaggedFibonacciGenerator.h" />
|
<ClInclude Include="DiscIO\LaggedFibonacciGenerator.h" />
|
||||||
<ClInclude Include="DiscIO\MultithreadedCompressor.h" />
|
<ClInclude Include="DiscIO\MultithreadedCompressor.h" />
|
||||||
<ClInclude Include="DiscIO\NANDImporter.h" />
|
<ClInclude Include="DiscIO\NANDImporter.h" />
|
||||||
|
@ -1019,6 +1020,7 @@
|
||||||
<ClCompile Include="DiscIO\FileBlob.cpp" />
|
<ClCompile Include="DiscIO\FileBlob.cpp" />
|
||||||
<ClCompile Include="DiscIO\Filesystem.cpp" />
|
<ClCompile Include="DiscIO\Filesystem.cpp" />
|
||||||
<ClCompile Include="DiscIO\FileSystemGCWii.cpp" />
|
<ClCompile Include="DiscIO\FileSystemGCWii.cpp" />
|
||||||
|
<ClCompile Include="DiscIO\GameModDescriptor.cpp" />
|
||||||
<ClCompile Include="DiscIO\LaggedFibonacciGenerator.cpp" />
|
<ClCompile Include="DiscIO\LaggedFibonacciGenerator.cpp" />
|
||||||
<ClCompile Include="DiscIO\NANDImporter.cpp" />
|
<ClCompile Include="DiscIO\NANDImporter.cpp" />
|
||||||
<ClCompile Include="DiscIO\RiivolutionParser.cpp" />
|
<ClCompile Include="DiscIO\RiivolutionParser.cpp" />
|
||||||
|
|
Loading…
Reference in New Issue