From 254246b814124f0f1dff5e1aebd9672c0d1bd492 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sat, 26 Feb 2022 00:19:38 -0600 Subject: [PATCH] VideoCommon: add logic to read a GraphicsMod from configuration --- Source/Core/DolphinLib.props | 10 + Source/Core/VideoCommon/CMakeLists.txt | 10 + .../GraphicsModSystem/Config/GraphicsMod.cpp | 291 ++++++++++++++++++ .../GraphicsModSystem/Config/GraphicsMod.h | 45 +++ .../Config/GraphicsModFeature.cpp | 48 +++ .../Config/GraphicsModFeature.h | 20 ++ .../Config/GraphicsModGroup.cpp | 191 ++++++++++++ .../Config/GraphicsModGroup.h | 46 +++ .../Config/GraphicsTarget.cpp | 254 +++++++++++++++ .../GraphicsModSystem/Config/GraphicsTarget.h | 56 ++++ .../Config/GraphicsTargetGroup.cpp | 88 ++++++ .../Config/GraphicsTargetGroup.h | 22 ++ 12 files changed, 1081 insertions(+) create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index b164c6dda1..d8b446bbc2 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -631,6 +631,11 @@ + + + + + @@ -1210,6 +1215,11 @@ + + + + + diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 51e51e672d..0b79a77519 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -39,6 +39,16 @@ add_library(videocommon GeometryShaderGen.h GeometryShaderManager.cpp GeometryShaderManager.h + GraphicsModSystem/Config/GraphicsMod.cpp + GraphicsModSystem/Config/GraphicsMod.h + GraphicsModSystem/Config/GraphicsModFeature.cpp + GraphicsModSystem/Config/GraphicsModFeature.h + GraphicsModSystem/Config/GraphicsModGroup.cpp + GraphicsModSystem/Config/GraphicsModGroup.h + GraphicsModSystem/Config/GraphicsTarget.cpp + GraphicsModSystem/Config/GraphicsTarget.h + GraphicsModSystem/Config/GraphicsTargetGroup.cpp + GraphicsModSystem/Config/GraphicsTargetGroup.h GraphicsModSystem/Constants.h HiresTextures.cpp HiresTextures.h diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp new file mode 100644 index 0000000000..c7dc5aab87 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp @@ -0,0 +1,291 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" + +#include + +#include "Common/CommonPaths.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +#include "VideoCommon/GraphicsModSystem/Constants.h" + +std::optional GraphicsModConfig::Create(const std::string& file_path, + Source source) +{ + std::string json_data; + if (!File::ReadFileToString(file_path, json_data)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod json file '{}'", file_path); + return std::nullopt; + } + + picojson::value root; + const auto error = picojson::parse(root, json_data); + + if (!error.empty()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod json file '{}' due to parse error: {}", + file_path, error); + return std::nullopt; + } + + GraphicsModConfig result; + if (!result.DeserializeFromConfig(root)) + { + return std::nullopt; + } + result.m_source = source; + if (source == Source::User) + { + const std::string base_path = File::GetUserPath(D_GRAPHICSMOD_IDX); + if (base_path.size() > file_path.size()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod json file '{}' due to it not matching the base path: {}", + file_path, base_path); + return std::nullopt; + } + result.m_relative_path = file_path.substr(base_path.size()); + } + else + { + const std::string base_path = File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR; + if (base_path.size() > file_path.size()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod json file '{}' due to it not matching the base path: {}", + file_path, base_path); + return std::nullopt; + } + result.m_relative_path = file_path.substr(base_path.size()); + } + + return result; +} + +std::optional GraphicsModConfig::Create(const picojson::object* obj) +{ + if (!obj) + return std::nullopt; + + const auto source_it = obj->find("source"); + if (source_it == obj->end()) + { + return std::nullopt; + } + const std::string source_str = source_it->second.to_str(); + + const auto path_it = obj->find("path"); + if (path_it == obj->end()) + { + return std::nullopt; + } + const std::string relative_path = path_it->second.to_str(); + + if (source_str == "system") + { + return Create(fmt::format("{}{}{}", File::GetSysDirectory(), DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, + relative_path), + Source::System); + } + else + { + return Create(File::GetUserPath(D_GRAPHICSMOD_IDX) + relative_path, Source::User); + } +} + +std::string GraphicsModConfig::GetAbsolutePath() const +{ + if (m_source == Source::System) + { + return WithUnifiedPathSeparators(fmt::format("{}{}{}", File::GetSysDirectory(), + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_relative_path)); + } + else + { + return WithUnifiedPathSeparators(File::GetUserPath(D_GRAPHICSMOD_IDX) + m_relative_path); + } +} + +bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) +{ + const auto& meta = value.get("meta"); + if (meta.is()) + { + const auto& title = meta.get("title"); + if (title.is()) + { + m_title = title.to_str(); + } + + const auto& author = meta.get("author"); + if (author.is()) + { + m_author = author.to_str(); + } + + const auto& description = meta.get("description"); + if (description.is()) + { + m_description = description.to_str(); + } + } + + const auto& groups = value.get("groups"); + if (groups.is()) + { + for (const auto& group_val : groups.get()) + { + if (!group_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified group is not a json object"); + return false; + } + GraphicsTargetGroupConfig group; + if (!group.DeserializeFromConfig(group_val.get())) + { + return false; + } + + m_groups.push_back(group); + } + } + + const auto& features = value.get("features"); + if (features.is()) + { + for (const auto& feature_val : features.get()) + { + if (!feature_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified feature is not a json object"); + return false; + } + GraphicsModFeatureConfig feature; + if (!feature.DeserializeFromConfig(feature_val.get())) + { + return false; + } + + m_features.push_back(feature); + } + } + + return true; +} + +void GraphicsModConfig::SerializeToProfile(picojson::object* obj) const +{ + if (!obj) + return; + + auto& json_obj = *obj; + switch (m_source) + { + case Source::User: + { + json_obj["source"] = picojson::value{"user"}; + } + break; + case Source::System: + { + json_obj["source"] = picojson::value{"system"}; + } + break; + }; + + json_obj["path"] = picojson::value{m_relative_path}; + + picojson::array serialized_groups; + for (const auto& group : m_groups) + { + picojson::object serialized_group; + group.SerializeToProfile(&serialized_group); + serialized_groups.push_back(picojson::value{serialized_group}); + } + json_obj["groups"] = picojson::value{serialized_groups}; + + picojson::array serialized_features; + for (const auto& feature : m_features) + { + picojson::object serialized_feature; + feature.SerializeToProfile(&serialized_feature); + serialized_features.push_back(picojson::value{serialized_feature}); + } + json_obj["features"] = picojson::value{serialized_features}; + + json_obj["enabled"] = picojson::value{m_enabled}; + + json_obj["weight"] = picojson::value{static_cast(m_weight)}; +} + +void GraphicsModConfig::DeserializeFromProfile(const picojson::object& obj) +{ + if (const auto it = obj.find("groups"); it != obj.end()) + { + if (it->second.is()) + { + auto serialized_groups = it->second.get(); + if (serialized_groups.size() != m_groups.size()) + return; + + for (std::size_t i = 0; i < serialized_groups.size(); i++) + { + const auto& serialized_group_val = serialized_groups[i]; + if (serialized_group_val.is()) + { + const auto& serialized_group = serialized_group_val.get(); + m_groups[i].DeserializeFromProfile(serialized_group); + } + } + } + } + + if (const auto it = obj.find("features"); it != obj.end()) + { + if (it->second.is()) + { + auto serialized_features = it->second.get(); + if (serialized_features.size() != m_features.size()) + return; + + for (std::size_t i = 0; i < serialized_features.size(); i++) + { + const auto& serialized_feature_val = serialized_features[i]; + if (serialized_feature_val.is()) + { + const auto& serialized_feature = serialized_feature_val.get(); + m_features[i].DeserializeFromProfile(serialized_feature); + } + } + } + } + + if (const auto it = obj.find("enabled"); it != obj.end()) + { + if (it->second.is()) + { + m_enabled = it->second.get(); + } + } + + if (const auto it = obj.find("weight"); it != obj.end()) + { + if (it->second.is()) + { + m_weight = static_cast(it->second.get()); + } + } +} + +bool GraphicsModConfig::operator<(const GraphicsModConfig& other) const +{ + return m_weight < other.m_weight; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h new file mode 100644 index 0000000000..f4f6859cb3 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h @@ -0,0 +1,45 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" + +struct GraphicsModConfig +{ + std::string m_title; + std::string m_author; + std::string m_description; + bool m_enabled = false; + u16 m_weight = 0; + std::string m_relative_path; + + enum class Source + { + User, + System + }; + Source m_source = Source::User; + + std::vector m_groups; + std::vector m_features; + + static std::optional Create(const std::string& file, Source source); + static std::optional Create(const picojson::object* obj); + + std::string GetAbsolutePath() const; + + bool DeserializeFromConfig(const picojson::value& value); + + void SerializeToProfile(picojson::object* value) const; + void DeserializeFromProfile(const picojson::object& value); + + bool operator<(const GraphicsModConfig& other) const; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp new file mode 100644 index 0000000000..3fa75eceba --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp @@ -0,0 +1,48 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" + +#include "Common/Logging/Log.h" + +bool GraphicsModFeatureConfig::DeserializeFromConfig(const picojson::object& obj) +{ + if (auto group_iter = obj.find("group"); group_iter != obj.end()) + { + if (!group_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, specified feature's group is not a string"); + return false; + } + m_group = group_iter->second.get(); + } + + if (auto action_iter = obj.find("action"); action_iter != obj.end()) + { + if (!action_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, specified feature's action is not a string"); + return false; + } + m_action = action_iter->second.get(); + } + + if (auto action_data_iter = obj.find("action_data"); action_data_iter != obj.end()) + { + m_action_data = action_data_iter->second; + } + + return true; +} + +void GraphicsModFeatureConfig::SerializeToProfile(picojson::object*) const +{ +} + +void GraphicsModFeatureConfig::DeserializeFromProfile(const picojson::object&) +{ +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h new file mode 100644 index 0000000000..af71bf35a7 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h @@ -0,0 +1,20 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +struct GraphicsModFeatureConfig +{ + std::string m_group; + std::string m_action; + picojson::value m_action_data; + + bool DeserializeFromConfig(const picojson::object& value); + + void SerializeToProfile(picojson::object* value) const; + void DeserializeFromProfile(const picojson::object& value); +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp new file mode 100644 index 0000000000..0d1ae30fb6 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp @@ -0,0 +1,191 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" + +#include +#include +#include + +#include "Common/CommonPaths.h" +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/ConfigManager.h" + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Constants.h" +#include "VideoCommon/HiresTextures.h" + +GraphicsModGroupConfig::GraphicsModGroupConfig(const std::string& game_id) : m_game_id(game_id) +{ +} + +GraphicsModGroupConfig::~GraphicsModGroupConfig() = default; + +GraphicsModGroupConfig::GraphicsModGroupConfig(const GraphicsModGroupConfig&) = default; + +GraphicsModGroupConfig::GraphicsModGroupConfig(GraphicsModGroupConfig&&) = default; + +GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(const GraphicsModGroupConfig&) = default; + +GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(GraphicsModGroupConfig&&) = default; + +void GraphicsModGroupConfig::Load() +{ + const std::string file_path = GetPath(); + + std::set known_paths; + if (File::Exists(file_path)) + { + std::string json_data; + if (!File::ReadFileToString(file_path, json_data)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod group json file '{}'", file_path); + return; + } + + picojson::value root; + const auto error = picojson::parse(root, json_data); + + if (!error.empty()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod group json file '{}' due to parse error: {}", + file_path, error); + return; + } + if (!root.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod group json file '{}' due to root not being an object!", + file_path); + return; + } + + const auto& mods = root.get("mods"); + if (mods.is()) + { + for (const auto& mod_json : mods.get()) + { + if (mod_json.is()) + { + const auto& mod_json_obj = mod_json.get(); + auto graphics_mod = GraphicsModConfig::Create(&mod_json_obj); + if (!graphics_mod) + { + continue; + } + graphics_mod->DeserializeFromProfile(mod_json_obj); + + auto mod_full_path = graphics_mod->GetAbsolutePath(); + known_paths.insert(std::move(mod_full_path)); + m_graphics_mods.push_back(*graphics_mod); + } + } + } + } + + const auto try_add_mod = [&known_paths, this](const std::string& dir, + GraphicsModConfig::Source source) { + auto file = dir + DIR_SEP + "metadata.json"; + UnifyPathSeparators(file); + if (known_paths.find(file) != known_paths.end()) + { + return; + } + const auto mod = GraphicsModConfig::Create(file, source); + if (mod) + { + m_graphics_mods.push_back(*mod); + } + }; + + const std::set graphics_mod_user_directories = + GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), m_game_id); + + for (const auto& graphics_mod_directory : graphics_mod_user_directories) + { + try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::User); + } + + const std::set graphics_mod_system_directories = GetTextureDirectoriesWithGameId( + File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_game_id); + + for (const auto& graphics_mod_directory : graphics_mod_system_directories) + { + try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::System); + } + + std::sort(m_graphics_mods.begin(), m_graphics_mods.end()); + for (auto& mod : m_graphics_mods) + { + m_path_to_graphics_mod[mod.GetAbsolutePath()] = &mod; + } + + m_change_count++; +} + +void GraphicsModGroupConfig::Save() const +{ + const std::string file_path = GetPath(); + std::ofstream json_stream; + File::OpenFStream(json_stream, file_path, std::ios_base::out); + if (!json_stream.is_open()) + { + ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path); + return; + } + + picojson::object serialized_root; + picojson::array serialized_mods; + for (const auto& mod : m_graphics_mods) + { + picojson::object serialized_mod; + mod.SerializeToProfile(&serialized_mod); + serialized_mods.push_back(picojson::value{serialized_mod}); + } + serialized_root["mods"] = picojson::value{serialized_mods}; + + const auto output = picojson::value{serialized_root}.serialize(true); + json_stream << output; +} + +void GraphicsModGroupConfig::SetChangeCount(u32 change_count) +{ + m_change_count = change_count; +} + +u32 GraphicsModGroupConfig::GetChangeCount() const +{ + return m_change_count; +} + +const std::vector& GraphicsModGroupConfig::GetMods() const +{ + return m_graphics_mods; +} + +GraphicsModConfig* GraphicsModGroupConfig::GetMod(const std::string& absolute_path) const +{ + if (const auto iter = m_path_to_graphics_mod.find(absolute_path); + iter != m_path_to_graphics_mod.end()) + { + return iter->second; + } + + return nullptr; +} + +const std::string& GraphicsModGroupConfig::GetGameID() const +{ + return m_game_id; +} + +std::string GraphicsModGroupConfig::GetPath() const +{ + const std::string game_mod_root = File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR; + return fmt::format("{}/{}.json", game_mod_root, m_game_id); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h new file mode 100644 index 0000000000..ace5127c58 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h @@ -0,0 +1,46 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" + +struct GraphicsModConfig; + +class GraphicsModGroupConfig +{ +public: + explicit GraphicsModGroupConfig(const std::string& game_id); + ~GraphicsModGroupConfig(); + + GraphicsModGroupConfig(const GraphicsModGroupConfig&); + GraphicsModGroupConfig(GraphicsModGroupConfig&&); + + GraphicsModGroupConfig& operator=(const GraphicsModGroupConfig&); + GraphicsModGroupConfig& operator=(GraphicsModGroupConfig&&); + + void Load(); + void Save() const; + + void SetChangeCount(u32 change_count); + u32 GetChangeCount() const; + + const std::vector& GetMods() const; + + GraphicsModConfig* GetMod(const std::string& absolute_path) const; + + const std::string& GetGameID() const; + +private: + std::string GetPath() const; + std::string m_game_id; + std::vector m_graphics_mods; + std::map m_path_to_graphics_mod; + u32 m_change_count = 0; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp new file mode 100644 index 0000000000..f55f184520 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp @@ -0,0 +1,254 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "VideoCommon/TextureCacheBase.h" + +namespace +{ +template , int> = 0> +std::optional DeserializeFBTargetFromConfig(const picojson::object& obj, std::string_view prefix) +{ + T fb; + const auto texture_filename_iter = obj.find("texture_filename"); + if (texture_filename_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'texture_filename' not found"); + return std::nullopt; + } + if (!texture_filename_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, option 'texture_filename' is not a string type"); + return std::nullopt; + } + const auto texture_filename = texture_filename_iter->second.get(); + const auto texture_filename_without_prefix = texture_filename.substr(prefix.size() + 1); + const auto split_str_values = SplitString(texture_filename_without_prefix, '_'); + if (split_str_values.size() == 1) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is not valid"); + return std::nullopt; + } + const auto split_width_height_values = SplitString(texture_filename_without_prefix, 'x'); + if (split_width_height_values.size() != 2) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, width and height separator found more matches than expected"); + return std::nullopt; + } + + const std::size_t width_underscore_pos = split_width_height_values[0].find_last_of('_'); + std::string width_str; + if (width_underscore_pos == std::string::npos) + { + width_str = split_width_height_values[0]; + } + else + { + width_str = split_width_height_values[0].substr(width_underscore_pos + 1); + } + if (!TryParse(width_str, &fb.m_width)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, width not a number"); + return std::nullopt; + } + + const std::size_t height_underscore_pos = split_width_height_values[1].find_first_of('_'); + if (height_underscore_pos == std::string::npos || + height_underscore_pos == split_width_height_values[1].size() - 1) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, underscore after height is missing or incomplete"); + return std::nullopt; + } + const std::string height_str = split_width_height_values[1].substr(0, height_underscore_pos); + if (!TryParse(height_str, &fb.m_height)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, height not a number"); + return std::nullopt; + } + + const std::size_t format_underscore_pos = + split_width_height_values[1].find_first_of('_', height_underscore_pos + 1); + + std::string format_str; + if (format_underscore_pos == std::string::npos) + { + format_str = split_width_height_values[1].substr(height_underscore_pos + 1); + } + else + { + format_str = split_width_height_values[1].substr( + height_underscore_pos + 1, (format_underscore_pos - height_underscore_pos) - 1); + } + u32 format; + if (!TryParse(format_str, &format)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, texture format is not a number"); + return std::nullopt; + } + if (!IsValidTextureFormat(static_cast(format))) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, texture format is not valid"); + return std::nullopt; + } + fb.m_texture_format = static_cast(format); + + return fb; +} +std::optional ExtractTextureFilenameForConfig(const picojson::object& obj) +{ + const auto texture_filename_iter = obj.find("texture_filename"); + if (texture_filename_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'texture_filename' not found"); + return std::nullopt; + } + if (!texture_filename_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, option 'texture_filename' is not a string type"); + return std::nullopt; + } + std::string texture_info = texture_filename_iter->second.get(); + if (texture_info.find(EFB_DUMP_PREFIX) != std::string::npos) + { + const auto letter_c_pos = texture_info.find_first_of('n'); + if (letter_c_pos == std::string::npos) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' " + "is an efb without a count"); + return std::nullopt; + } + texture_info = + texture_info.substr(letter_c_pos - 1, texture_info.find_first_of("_", letter_c_pos)); + } + else if (texture_info.find(XFB_DUMP_PREFIX) != std::string::npos) + { + const auto letter_c_pos = texture_info.find_first_of('n'); + if (letter_c_pos == std::string::npos) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' " + "is an xfb without a count"); + return std::nullopt; + } + texture_info = + texture_info.substr(letter_c_pos - 1, texture_info.find_first_of("_", letter_c_pos)); + } + return texture_info; +} +} // namespace + +std::optional DeserializeTargetFromConfig(const picojson::object& obj) +{ + const auto type_iter = obj.find("type"); + if (type_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'type' not found"); + return std::nullopt; + } + if (!type_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'type' is not a string type"); + return std::nullopt; + } + const std::string& type = type_iter->second.get(); + if (type == "draw_started") + { + std::optional texture_info = ExtractTextureFilenameForConfig(obj); + if (!texture_info.has_value()) + return std::nullopt; + + DrawStartedTextureTarget target; + target.m_texture_info_string = texture_info.value(); + return target; + } + else if (type == "load_texture") + { + std::optional texture_info = ExtractTextureFilenameForConfig(obj); + if (!texture_info.has_value()) + return std::nullopt; + + LoadTextureTarget target; + target.m_texture_info_string = texture_info.value(); + return target; + } + else if (type == "efb") + { + return DeserializeFBTargetFromConfig(obj, EFB_DUMP_PREFIX); + } + else if (type == "xfb") + { + return DeserializeFBTargetFromConfig(obj, EFB_DUMP_PREFIX); + } + else if (type == "projection") + { + ProjectionTarget target; + const auto texture_iter = obj.find("texture_filename"); + if (texture_iter != obj.end()) + { + std::optional texture_info = ExtractTextureFilenameForConfig(obj); + if (!texture_info.has_value()) + return std::nullopt; + target.m_texture_info_string = texture_info; + } + const auto value_iter = obj.find("value"); + if (value_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' not found"); + return std::nullopt; + } + if (!value_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'value' is not a string type"); + return std::nullopt; + } + const auto& value_str = value_iter->second.get(); + if (value_str == "2d") + { + target.m_projection_type = ProjectionType::Orthographic; + } + else if (value_str == "3d") + { + target.m_projection_type = ProjectionType::Perspective; + } + else + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' is not a valid " + "value, valid values are: 2d, 3d"); + return std::nullopt; + } + return target; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'type' is not a valid value"); + } + return std::nullopt; +} + +void SerializeTargetToProfile(picojson::object*, const GraphicsTargetConfig&) +{ + // Added for consistency, no functionality as of now +} + +void DeserializeTargetFromProfile(const picojson::object&, GraphicsTargetConfig*) +{ + // Added for consistency, no functionality as of now +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h new file mode 100644 index 0000000000..67a349e78f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h @@ -0,0 +1,56 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/TextureDecoder.h" +#include "VideoCommon/XFMemory.h" + +struct TextureTarget +{ + std::string m_texture_info_string; +}; + +struct DrawStartedTextureTarget final : public TextureTarget +{ +}; + +struct LoadTextureTarget final : public TextureTarget +{ +}; + +struct FBTarget +{ + u32 m_height = 0; + u32 m_width = 0; + TextureFormat m_texture_format = TextureFormat::I4; +}; + +struct EFBTarget final : public FBTarget +{ +}; + +struct XFBTarget final : public FBTarget +{ +}; + +struct ProjectionTarget +{ + std::optional m_texture_info_string; + ProjectionType m_projection_type = ProjectionType::Perspective; +}; + +using GraphicsTargetConfig = std::variant; + +std::optional DeserializeTargetFromConfig(const picojson::object& obj); + +void SerializeTargetToProfile(picojson::object* obj, const GraphicsTargetConfig& target); +void DeserializeTargetFromProfile(const picojson::object& obj, GraphicsTargetConfig* target); diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp new file mode 100644 index 0000000000..753f304cb5 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp @@ -0,0 +1,88 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" + +#include "Common/Logging/Log.h" + +bool GraphicsTargetGroupConfig::DeserializeFromConfig(const picojson::object& obj) +{ + if (auto name_iter = obj.find("name"); name_iter != obj.end()) + { + if (!name_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified group's name is not a string"); + return false; + } + m_name = name_iter->second.get(); + } + + if (auto targets_iter = obj.find("targets"); targets_iter != obj.end()) + { + if (!targets_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, specified group's targets is not an array"); + return false; + } + for (const auto& target_val : targets_iter->second.get()) + { + if (!target_val.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load shader configuration file, specified target is not a json object"); + return false; + } + const auto target = DeserializeTargetFromConfig(target_val.get()); + if (!target) + { + return false; + } + + m_targets.push_back(*target); + } + } + + return true; +} + +void GraphicsTargetGroupConfig::SerializeToProfile(picojson::object* obj) const +{ + if (!obj) + return; + auto& json_obj = *obj; + picojson::array serialized_targets; + for (const auto& target : m_targets) + { + picojson::object serialized_target; + SerializeTargetToProfile(&serialized_target, target); + serialized_targets.push_back(picojson::value{serialized_target}); + } + json_obj["targets"] = picojson::value{serialized_targets}; +} + +void GraphicsTargetGroupConfig::DeserializeFromProfile(const picojson::object& obj) +{ + if (const auto it = obj.find("targets"); it != obj.end()) + { + if (it->second.is()) + { + auto serialized_targets = it->second.get(); + if (serialized_targets.size() != m_targets.size()) + return; + + for (std::size_t i = 0; i < serialized_targets.size(); i++) + { + const auto& serialized_target_val = serialized_targets[i]; + if (serialized_target_val.is()) + { + const auto& serialized_target = serialized_target_val.get(); + DeserializeTargetFromProfile(serialized_target, &m_targets[i]); + } + } + } + } +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h new file mode 100644 index 0000000000..2db2517175 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h @@ -0,0 +1,22 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" + +struct GraphicsTargetGroupConfig +{ + std::string m_name; + std::vector m_targets; + + bool DeserializeFromConfig(const picojson::object& obj); + + void SerializeToProfile(picojson::object* obj) const; + void DeserializeFromProfile(const picojson::object& obj); +};