// Copyright 2021 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "DiscIO/GameModDescriptor.h" #include #include #include #include #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_game_mod_descriptor = false; bool is_valid_version = false; for (const auto& [key, value] : json_root.get()) { if (key == "type" && value.is()) { is_game_mod_descriptor = value.get() == "dolphin-game-mod-descriptor"; } else 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_game_mod_descriptor || !is_valid_version) return std::nullopt; return descriptor; } } // namespace DiscIO