Core: Add ability to specify and launch a riivolution-modded game via a .json file.

This commit is contained in:
Admiral H. Curtiss 2021-10-09 03:17:38 +02:00
parent fe242f79ee
commit 6394960f54
No known key found for this signature in database
GPG Key ID: F051B4C4044F33FB
7 changed files with 285 additions and 0 deletions

View File

@ -57,6 +57,8 @@ 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"
@ -217,6 +219,40 @@ 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))
{
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);
return {};
}

View File

@ -23,6 +23,8 @@ add_library(discio
FileSystemGCWii.h
Filesystem.cpp
Filesystem.h
GameModDescriptor.cpp
GameModDescriptor.h
LaggedFibonacciGenerator.cpp
LaggedFibonacciGenerator.h
MultithreadedCompressor.h

View File

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

View File

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

View File

@ -13,6 +13,7 @@
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Common/StringUtil.h"
#include "DiscIO/GameModDescriptor.h"
#include "DiscIO/RiivolutionPatcher.h"
namespace DiscIO::Riivolution
@ -336,4 +337,47 @@ std::vector<Patch> Disc::GeneratePatches(const std::string& game_id) const
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

View File

@ -12,6 +12,11 @@
#include "Common/CommonTypes.h"
namespace DiscIO
{
struct GameModDescriptorRiivolution;
}
namespace DiscIO::Riivolution
{
class FileDataLoader;
@ -196,4 +201,7 @@ struct Disc
std::optional<Disc> ParseFile(const std::string& filename);
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

View File

@ -430,6 +430,7 @@
<ClInclude Include="DiscIO\FileBlob.h" />
<ClInclude Include="DiscIO\Filesystem.h" />
<ClInclude Include="DiscIO\FileSystemGCWii.h" />
<ClInclude Include="DiscIO\GameModDescriptor.h" />
<ClInclude Include="DiscIO\LaggedFibonacciGenerator.h" />
<ClInclude Include="DiscIO\MultithreadedCompressor.h" />
<ClInclude Include="DiscIO\NANDImporter.h" />
@ -1019,6 +1020,7 @@
<ClCompile Include="DiscIO\FileBlob.cpp" />
<ClCompile Include="DiscIO\Filesystem.cpp" />
<ClCompile Include="DiscIO\FileSystemGCWii.cpp" />
<ClCompile Include="DiscIO\GameModDescriptor.cpp" />
<ClCompile Include="DiscIO\LaggedFibonacciGenerator.cpp" />
<ClCompile Include="DiscIO\NANDImporter.cpp" />
<ClCompile Include="DiscIO\RiivolutionParser.cpp" />