diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 6a019402b4..ba437b6e54 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -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 paths, return std::make_unique(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(boot_params->parameters)) + { + auto& disc = std::get(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* 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 {}; } diff --git a/Source/Core/DiscIO/CMakeLists.txt b/Source/Core/DiscIO/CMakeLists.txt index a36e32682e..cf13329f99 100644 --- a/Source/Core/DiscIO/CMakeLists.txt +++ b/Source/Core/DiscIO/CMakeLists.txt @@ -23,6 +23,8 @@ add_library(discio FileSystemGCWii.h Filesystem.cpp Filesystem.h + GameModDescriptor.cpp + GameModDescriptor.h LaggedFibonacciGenerator.cpp LaggedFibonacciGenerator.h MultithreadedCompressor.h diff --git a/Source/Core/DiscIO/GameModDescriptor.cpp b/Source/Core/DiscIO/GameModDescriptor.cpp new file mode 100644 index 0000000000..4a1c479fca --- /dev/null +++ b/Source/Core/DiscIO/GameModDescriptor.cpp @@ -0,0 +1,147 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "DiscIO/GameModDescriptor.h" + +#include + +#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 ParseGameModDescriptorFile(const std::string& filename) +{ + ::File::IOFile f(filename, "rb"); + if (!f) + return std::nullopt; + + std::vector 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 +ParseRiivolutionOptions(const picojson::array& array) +{ + std::vector options; + for (const auto& option_object : array) + { + if (!option_object.is()) + continue; + + auto& option = options.emplace_back(); + for (const auto& [key, value] : option_object.get()) + { + if (key == "section-name" && value.is()) + option.section_name = value.get(); + else if (key == "option-id" && value.is()) + option.option_id = value.get(); + else if (key == "option-name" && value.is()) + option.option_name = value.get(); + else if (key == "choice" && value.is()) + option.choice = MathUtil::SaturatingCast(value.get()); + } + } + 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()) + { + for (const auto& patch_object : element_value.get()) + { + if (!patch_object.is()) + continue; + + auto& patch = r.patches.emplace_back(); + for (const auto& [key, value] : patch_object.get()) + { + if (key == "xml" && value.is()) + patch.xml = MakeAbsolute(json_directory, value.get()); + else if (key == "root" && value.is()) + patch.root = MakeAbsolute(json_directory, value.get()); + else if (key == "options" && value.is()) + patch.options = ParseRiivolutionOptions(value.get()); + } + } + } + } + return r; +} + +std::optional 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()) + return std::nullopt; + + GameModDescriptor descriptor; + bool is_valid_version = false; + for (const auto& [key, value] : json_root.get()) + { + if (key == "version" && value.is()) + { + is_valid_version = value.get() == 1.0; + } + else if (key == "base-file" && value.is()) + { + descriptor.base_file = MakeAbsolute(json_directory, value.get()); + } + else if (key == "display-name" && value.is()) + { + descriptor.display_name = value.get(); + } + else if (key == "banner" && value.is()) + { + descriptor.banner = MakeAbsolute(json_directory, value.get()); + } + else if (key == "riivolution" && value.is()) + { + descriptor.riivolution = + ParseRiivolutionObject(json_directory, value.get()); + } + } + if (!is_valid_version) + return std::nullopt; + return descriptor; +} +} // namespace DiscIO diff --git a/Source/Core/DiscIO/GameModDescriptor.h b/Source/Core/DiscIO/GameModDescriptor.h new file mode 100644 index 0000000000..09a03ed2df --- /dev/null +++ b/Source/Core/DiscIO/GameModDescriptor.h @@ -0,0 +1,46 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#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 options; +}; + +struct GameModDescriptorRiivolution +{ + std::vector patches; +}; + +struct GameModDescriptor +{ + std::string base_file; + std::string display_name; + std::string banner; + std::optional riivolution = std::nullopt; +}; + +std::optional ParseGameModDescriptorFile(const std::string& filename); +std::optional ParseGameModDescriptorString(std::string_view json, + std::string_view json_path); +} // namespace DiscIO diff --git a/Source/Core/DiscIO/RiivolutionParser.cpp b/Source/Core/DiscIO/RiivolutionParser.cpp index 63a830d1c5..e8a284e07b 100644 --- a/Source/Core/DiscIO/RiivolutionParser.cpp +++ b/Source/Core/DiscIO/RiivolutionParser.cpp @@ -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 Disc::GeneratePatches(const std::string& game_id) const return active_patches; } + +std::vector GenerateRiivolutionPatchesFromGameModDescriptor( + const GameModDescriptorRiivolution& descriptor, const std::string& game_id, + std::optional revision, std::optional disc_number) +{ + std::vector 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( + patch_info.root, parsed->m_xml_path, p.m_root); + result.emplace_back(std::move(p)); + } + } + return result; +} } // namespace DiscIO::Riivolution diff --git a/Source/Core/DiscIO/RiivolutionParser.h b/Source/Core/DiscIO/RiivolutionParser.h index 3c3cb65455..d4415a397d 100644 --- a/Source/Core/DiscIO/RiivolutionParser.h +++ b/Source/Core/DiscIO/RiivolutionParser.h @@ -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 ParseFile(const std::string& filename); std::optional ParseString(std::string_view xml, std::string xml_path); +std::vector GenerateRiivolutionPatchesFromGameModDescriptor( + const GameModDescriptorRiivolution& descriptor, const std::string& game_id, + std::optional revision, std::optional disc_number); } // namespace DiscIO::Riivolution diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 4a3e8a9511..b7e9bf3b32 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -430,6 +430,7 @@ + @@ -1019,6 +1020,7 @@ +