summaryrefslogtreecommitdiff
path: root/Source/Core/DiscIO/RiivolutionParser.cpp
diff options
context:
space:
mode:
authorAdmiral H. Curtiss <pikachu025@gmail.com>2021-09-23 03:21:53 +0200
committerAdmiral H. Curtiss <pikachu025@gmail.com>2021-10-24 00:09:06 +0200
commite26b59bab39ee5117b8a01f63d739f9608009e1c (patch)
treebaaf9e9d6b24a0cd0da5039d23c972f819f36eb0 /Source/Core/DiscIO/RiivolutionParser.cpp
parenta4da56e5e6d5a056969e9d9ae6024a411a832ca4 (diff)
Core: Add RiivolutionParser to parse a Riivolution XML.
Diffstat (limited to 'Source/Core/DiscIO/RiivolutionParser.cpp')
-rw-r--r--Source/Core/DiscIO/RiivolutionParser.cpp336
1 files changed, 336 insertions, 0 deletions
diff --git a/Source/Core/DiscIO/RiivolutionParser.cpp b/Source/Core/DiscIO/RiivolutionParser.cpp
new file mode 100644
index 0000000000..3162c40d43
--- /dev/null
+++ b/Source/Core/DiscIO/RiivolutionParser.cpp
@@ -0,0 +1,336 @@
+// Copyright 2021 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "DiscIO/RiivolutionParser.h"
+
+#include <algorithm>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <pugixml.hpp>
+
+#include "Common/FileUtil.h"
+#include "Common/IOFile.h"
+#include "Common/StringUtil.h"
+
+namespace DiscIO::Riivolution
+{
+std::optional<Disc> ParseFile(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;
+
+ return ParseString(std::string_view(data.data(), data.size()), filename);
+}
+
+static std::map<std::string, std::string> ReadParams(const pugi::xml_node& node,
+ std::map<std::string, std::string> params = {})
+{
+ for (const auto& param_node : node.children("param"))
+ {
+ const std::string param_name = param_node.attribute("name").as_string();
+ const std::string param_value = param_node.attribute("value").as_string();
+ params[param_name] = param_value;
+ }
+ return params;
+}
+
+static std::vector<u8> ReadHexString(std::string_view sv)
+{
+ if ((sv.size() % 2) == 1)
+ return {};
+ if (StringBeginsWith(sv, "0x") || StringBeginsWith(sv, "0X"))
+ sv = sv.substr(2);
+
+ std::vector<u8> result;
+ result.reserve(sv.size() / 2);
+ while (!sv.empty())
+ {
+ u8 tmp;
+ if (!TryParse(std::string(sv.substr(0, 2)), &tmp, 16))
+ return {};
+ result.push_back(tmp);
+ sv = sv.substr(2);
+ }
+ return result;
+};
+
+std::optional<Disc> ParseString(std::string_view xml, std::string xml_path)
+{
+ pugi::xml_document doc;
+ const auto parse_result = doc.load_buffer(xml.data(), xml.size());
+ if (!parse_result)
+ return std::nullopt;
+
+ const auto wiidisc = doc.child("wiidisc");
+ if (!wiidisc)
+ return std::nullopt;
+
+ Disc disc;
+ disc.m_xml_path = std::move(xml_path);
+ disc.m_version = wiidisc.attribute("version").as_int(-1);
+ if (disc.m_version != 1)
+ return std::nullopt;
+ const std::string default_root = wiidisc.attribute("root").as_string();
+
+ const auto id = wiidisc.child("id");
+ if (id)
+ {
+ for (const auto& attribute : id.attributes())
+ {
+ const std::string_view attribute_name(attribute.name());
+ if (attribute_name == "game")
+ disc.m_game_filter.m_game = attribute.as_string();
+ else if (attribute_name == "developer")
+ disc.m_game_filter.m_developer = attribute.as_string();
+ else if (attribute_name == "disc")
+ disc.m_game_filter.m_disc = attribute.as_int(-1);
+ else if (attribute_name == "version")
+ disc.m_game_filter.m_version = attribute.as_int(-1);
+ }
+
+ auto xml_regions = id.children("region");
+ if (xml_regions.begin() != xml_regions.end())
+ {
+ std::vector<std::string> regions;
+ for (const auto& region : xml_regions)
+ regions.push_back(region.attribute("type").as_string());
+ disc.m_game_filter.m_regions = std::move(regions);
+ }
+ }
+
+ const auto options = wiidisc.child("options");
+ if (options)
+ {
+ for (const auto& section_node : options.children("section"))
+ {
+ Section& section = disc.m_sections.emplace_back();
+ section.m_name = section_node.attribute("name").as_string();
+ for (const auto& option_node : section_node.children("option"))
+ {
+ Option& option = section.m_options.emplace_back();
+ option.m_id = option_node.attribute("id").as_string();
+ option.m_name = option_node.attribute("name").as_string();
+ option.m_selected_choice = option_node.attribute("default").as_uint(0);
+ auto option_params = ReadParams(option_node);
+ for (const auto& choice_node : option_node.children("choice"))
+ {
+ Choice& choice = option.m_choices.emplace_back();
+ choice.m_name = choice_node.attribute("name").as_string();
+ auto choice_params = ReadParams(choice_node, option_params);
+ for (const auto& patchref_node : choice_node.children("patch"))
+ {
+ PatchReference& patchref = choice.m_patch_references.emplace_back();
+ patchref.m_id = patchref_node.attribute("id").as_string();
+ patchref.m_params = ReadParams(patchref_node, choice_params);
+ }
+ }
+ }
+ }
+ for (const auto& macro_node : options.children("macros"))
+ {
+ const std::string macro_id = macro_node.attribute("id").as_string();
+ for (auto& section : disc.m_sections)
+ {
+ auto option_to_clone = std::find_if(section.m_options.begin(), section.m_options.end(),
+ [&](const Option& o) { return o.m_id == macro_id; });
+ if (option_to_clone == section.m_options.end())
+ continue;
+
+ Option cloned_option = *option_to_clone;
+ cloned_option.m_name = macro_node.attribute("name").as_string();
+ for (auto& choice : cloned_option.m_choices)
+ for (auto& patch_ref : choice.m_patch_references)
+ patch_ref.m_params = ReadParams(macro_node, patch_ref.m_params);
+ }
+ }
+ }
+
+ const auto patches = wiidisc.children("patch");
+ for (const auto& patch_node : patches)
+ {
+ Patch& patch = disc.m_patches.emplace_back();
+ patch.m_id = patch_node.attribute("id").as_string();
+ patch.m_root = patch_node.attribute("root").as_string();
+ if (patch.m_root.empty())
+ patch.m_root = default_root;
+
+ for (const auto& patch_subnode : patch_node.children())
+ {
+ const std::string_view patch_name(patch_subnode.name());
+ if (patch_name == "file")
+ {
+ auto& file = patch.m_file_patches.emplace_back();
+ file.m_disc = patch_subnode.attribute("disc").as_string();
+ file.m_external = patch_subnode.attribute("external").as_string();
+ file.m_resize = patch_subnode.attribute("resize").as_bool(true);
+ file.m_create = patch_subnode.attribute("create").as_bool(false);
+ file.m_offset = patch_subnode.attribute("offset").as_uint(0);
+ file.m_fileoffset = patch_subnode.attribute("fileoffset").as_uint(0);
+ file.m_length = patch_subnode.attribute("length").as_uint(0);
+ }
+ else if (patch_name == "folder")
+ {
+ auto& folder = patch.m_folder_patches.emplace_back();
+ folder.m_disc = patch_subnode.attribute("disc").as_string();
+ folder.m_external = patch_subnode.attribute("external").as_string();
+ folder.m_resize = patch_subnode.attribute("resize").as_bool(true);
+ folder.m_create = patch_subnode.attribute("create").as_bool(false);
+ folder.m_recursive = patch_subnode.attribute("recursive").as_bool(true);
+ folder.m_length = patch_subnode.attribute("length").as_uint(0);
+ }
+ else if (patch_name == "savegame")
+ {
+ auto& savegame = patch.m_savegame_patches.emplace_back();
+ savegame.m_external = patch_subnode.attribute("external").as_string();
+ savegame.m_clone = patch_subnode.attribute("clone").as_bool(true);
+ }
+ else if (patch_name == "memory")
+ {
+ auto& memory = patch.m_memory_patches.emplace_back();
+ memory.m_offset = patch_subnode.attribute("offset").as_uint(0);
+ memory.m_value = ReadHexString(patch_subnode.attribute("value").as_string());
+ memory.m_valuefile = patch_subnode.attribute("valuefile").as_string();
+ memory.m_original = ReadHexString(patch_subnode.attribute("original").as_string());
+ memory.m_ocarina = patch_subnode.attribute("ocarina").as_bool(false);
+ memory.m_search = patch_subnode.attribute("search").as_bool(false);
+ memory.m_align = patch_subnode.attribute("align").as_uint(1);
+ }
+ }
+ }
+
+ return disc;
+}
+
+static bool CheckRegion(const std::vector<std::string>& xml_regions, std::string_view game_region)
+{
+ if (xml_regions.begin() == xml_regions.end())
+ return true;
+
+ for (const auto& region : xml_regions)
+ {
+ if (region == game_region)
+ return true;
+ }
+
+ return false;
+}
+
+bool Disc::IsValidForGame(const std::string& game_id, std::optional<u16> revision,
+ std::optional<u8> disc_number) const
+{
+ if (game_id.size() != 6)
+ return false;
+
+ const std::string_view game_id_full = std::string_view(game_id);
+ const std::string_view game_region = game_id_full.substr(3, 1);
+ const std::string_view game_developer = game_id_full.substr(4, 2);
+ const int disc_number_int = std::optional<int>(disc_number).value_or(-1);
+ const int revision_int = std::optional<int>(revision).value_or(-1);
+
+ if (m_game_filter.m_game && !StringBeginsWith(game_id_full, *m_game_filter.m_game))
+ return false;
+ if (m_game_filter.m_developer && game_developer != *m_game_filter.m_developer)
+ return false;
+ if (m_game_filter.m_disc && disc_number_int != *m_game_filter.m_disc)
+ return false;
+ if (m_game_filter.m_version && revision_int != *m_game_filter.m_version)
+ return false;
+ if (m_game_filter.m_regions && !CheckRegion(*m_game_filter.m_regions, game_region))
+ return false;
+
+ return true;
+}
+
+std::vector<Patch> Disc::GeneratePatches(const std::string& game_id) const
+{
+ const std::string_view game_id_full = std::string_view(game_id);
+ const std::string_view game_id_no_region = game_id_full.substr(0, 3);
+ const std::string_view game_region = game_id_full.substr(3, 1);
+ const std::string_view game_developer = game_id_full.substr(4, 2);
+
+ const auto replace_variables =
+ [&](std::string_view sv,
+ const std::vector<std::pair<std::string, std::string_view>>& replacements) {
+ std::string result;
+ result.reserve(sv.size());
+ while (!sv.empty())
+ {
+ bool replaced = false;
+ for (const auto& r : replacements)
+ {
+ if (StringBeginsWith(sv, r.first))
+ {
+ for (char c : r.second)
+ result.push_back(c);
+ sv = sv.substr(r.first.size());
+ replaced = true;
+ break;
+ }
+ }
+ if (replaced)
+ continue;
+ result.push_back(sv[0]);
+ sv = sv.substr(1);
+ }
+ return result;
+ };
+
+ // Take only selected patches, replace placeholders in all strings, and return them.
+ std::vector<Patch> active_patches;
+ for (const auto& section : m_sections)
+ {
+ for (const auto& option : section.m_options)
+ {
+ const u32 selected = option.m_selected_choice;
+ if (selected == 0 || selected > option.m_choices.size())
+ continue;
+ const Choice& choice = option.m_choices[selected - 1];
+ for (const auto& patch_ref : choice.m_patch_references)
+ {
+ const auto patch = std::find_if(m_patches.begin(), m_patches.end(),
+ [&](const Patch& p) { return patch_ref.m_id == p.m_id; });
+ if (patch == m_patches.end())
+ continue;
+
+ std::vector<std::pair<std::string, std::string_view>> replacements;
+ replacements.emplace_back(std::pair{"{$__gameid}", game_id_no_region});
+ replacements.emplace_back(std::pair{"{$__region}", game_region});
+ replacements.emplace_back(std::pair{"{$__maker}", game_developer});
+ for (const auto& param : patch_ref.m_params)
+ replacements.emplace_back(std::pair{"{$" + param.first + "}", param.second});
+
+ Patch& new_patch = active_patches.emplace_back(*patch);
+ new_patch.m_root = replace_variables(new_patch.m_root, replacements);
+ for (auto& file : new_patch.m_file_patches)
+ {
+ file.m_disc = replace_variables(file.m_disc, replacements);
+ file.m_external = replace_variables(file.m_external, replacements);
+ }
+ for (auto& folder : new_patch.m_folder_patches)
+ {
+ folder.m_disc = replace_variables(folder.m_disc, replacements);
+ folder.m_external = replace_variables(folder.m_external, replacements);
+ }
+ for (auto& savegame : new_patch.m_savegame_patches)
+ {
+ savegame.m_external = replace_variables(savegame.m_external, replacements);
+ }
+ for (auto& memory : new_patch.m_memory_patches)
+ {
+ memory.m_valuefile = replace_variables(memory.m_valuefile, replacements);
+ }
+ }
+ }
+ }
+
+ return active_patches;
+}
+} // namespace DiscIO::Riivolution