From 2be62d94fc659b64c7de1d53cd0ef9a09a375570 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 19 Oct 2021 23:30:02 +0200 Subject: [PATCH] RiivolutionParser: Add code to read and write the Riivolution config XMLs that remember the last selection. --- Source/Core/DiscIO/RiivolutionParser.cpp | 103 +++++++++++++++++++++++ Source/Core/DiscIO/RiivolutionParser.h | 24 ++++++ 2 files changed, 127 insertions(+) diff --git a/Source/Core/DiscIO/RiivolutionParser.cpp b/Source/Core/DiscIO/RiivolutionParser.cpp index e8a284e07b..92f37444a0 100644 --- a/Source/Core/DiscIO/RiivolutionParser.cpp +++ b/Source/Core/DiscIO/RiivolutionParser.cpp @@ -4,6 +4,7 @@ #include "DiscIO/RiivolutionParser.h" #include +#include #include #include #include @@ -380,4 +381,106 @@ std::vector GenerateRiivolutionPatchesFromGameModDescriptor( } return result; } + +std::optional ParseConfigFile(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; + + return ParseConfigString(std::string_view(data.data(), data.size())); +} + +std::optional ParseConfigString(std::string_view xml) +{ + pugi::xml_document doc; + const auto parse_result = doc.load_buffer(xml.data(), xml.size()); + if (!parse_result) + return std::nullopt; + + const auto riivolution = doc.child("riivolution"); + if (!riivolution) + return std::nullopt; + + Config config; + config.m_version = riivolution.attribute("version").as_int(-1); + if (config.m_version != 2) + return std::nullopt; + + const auto options = riivolution.children("option"); + for (const auto& option_node : options) + { + auto& option = config.m_options.emplace_back(); + option.m_id = option_node.attribute("id").as_string(); + option.m_default = option_node.attribute("default").as_uint(0); + } + + return config; +} + +std::string WriteConfigString(const Config& config) +{ + pugi::xml_document doc; + auto riivolution = doc.append_child("riivolution"); + riivolution.append_attribute("version").set_value(config.m_version); + for (const auto& option : config.m_options) + { + auto option_node = riivolution.append_child("option"); + option_node.append_attribute("id").set_value(option.m_id.c_str()); + option_node.append_attribute("default").set_value(option.m_default); + } + + std::stringstream ss; + doc.print(ss, " "); + return ss.str(); +} + +bool WriteConfigFile(const std::string& filename, const Config& config) +{ + auto xml = WriteConfigString(config); + if (xml.empty()) + return false; + + ::File::IOFile f(filename, "wb"); + if (!f) + return false; + + if (!f.WriteString(xml)) + return false; + + return true; +} + +void ApplyConfigDefaults(Disc* disc, const Config& config) +{ + for (const auto& config_option : config.m_options) + { + auto* matching_option = [&]() -> DiscIO::Riivolution::Option* { + for (auto& section : disc->m_sections) + { + for (auto& option : section.m_options) + { + if (option.m_id.empty()) + { + if ((section.m_name + option.m_name) == config_option.m_id) + return &option; + } + else + { + if (option.m_id == config_option.m_id) + return &option; + } + } + } + return nullptr; + }(); + if (matching_option) + matching_option->m_selected_choice = config_option.m_default; + } +} } // namespace DiscIO::Riivolution diff --git a/Source/Core/DiscIO/RiivolutionParser.h b/Source/Core/DiscIO/RiivolutionParser.h index d4415a397d..d826425e5a 100644 --- a/Source/Core/DiscIO/RiivolutionParser.h +++ b/Source/Core/DiscIO/RiivolutionParser.h @@ -199,9 +199,33 @@ struct Disc std::vector GeneratePatches(const std::string& game_id) const; }; +// Config format that remembers which patches are enabled/disabled for the next run. +// Some patches ship with pre-made config XMLs instead of baking their defaults into the actual +// patch XMLs, so it makes sense to support this format directly. +struct ConfigOption +{ + // The identifier for the referenced Option. + std::string m_id; + + // The selected Choice index. + u32 m_default; +}; + +struct Config +{ + // Config version. Riivolution itself only accepts '2' here. + int m_version = 2; + std::vector m_options; +}; + 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); +std::optional ParseConfigFile(const std::string& filename); +std::optional ParseConfigString(std::string_view xml); +std::string WriteConfigString(const Config& config); +bool WriteConfigFile(const std::string& filename, const Config& config); +void ApplyConfigDefaults(Disc* disc, const Config& config); } // namespace DiscIO::Riivolution