mirror of
				https://github.com/N64Recomp/N64ModernRuntime.git
				synced 2025-10-30 08:02:29 +00:00 
			
		
		
		
	Separate configuration into librecomp/config
This commit is contained in:
		
							parent
							
								
									283ebb9360
								
							
						
					
					
						commit
						3069d33446
					
				
					 10 changed files with 1293 additions and 297 deletions
				
			
		|  | @ -9,6 +9,8 @@ set(CMAKE_CXX_EXTENSIONS OFF) | ||||||
| add_library(librecomp STATIC | add_library(librecomp STATIC | ||||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/src/ai.cpp" |     "${CMAKE_CURRENT_SOURCE_DIR}/src/ai.cpp" | ||||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/src/cont.cpp" |     "${CMAKE_CURRENT_SOURCE_DIR}/src/cont.cpp" | ||||||
|  |     "${CMAKE_CURRENT_SOURCE_DIR}/src/config_option.cpp" | ||||||
|  |     "${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp" | ||||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/src/dp.cpp" |     "${CMAKE_CURRENT_SOURCE_DIR}/src/dp.cpp" | ||||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/src/eep.cpp" |     "${CMAKE_CURRENT_SOURCE_DIR}/src/eep.cpp" | ||||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/src/euc-jp.cpp" |     "${CMAKE_CURRENT_SOURCE_DIR}/src/euc-jp.cpp" | ||||||
|  |  | ||||||
							
								
								
									
										326
									
								
								librecomp/include/librecomp/config.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								librecomp/include/librecomp/config.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,326 @@ | ||||||
|  | #ifndef __RECOMP_CONFIG_HPP__ | ||||||
|  | #define __RECOMP_CONFIG_HPP__ | ||||||
|  | 
 | ||||||
|  | #include <filesystem> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  | #include <unordered_set> | ||||||
|  | #include <unordered_map> | ||||||
|  | #include <variant> | ||||||
|  | #include <functional> | ||||||
|  | 
 | ||||||
|  | #include <json/json.hpp> | ||||||
|  | 
 | ||||||
|  | #include "recomp.h" | ||||||
|  | 
 | ||||||
|  | namespace recomp { | ||||||
|  |     namespace config { | ||||||
|  |         enum class ConfigOptionType { | ||||||
|  |             None, | ||||||
|  |             Enum, | ||||||
|  |             Number, | ||||||
|  |             String, | ||||||
|  |             Bool | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct ConfigOptionEnumOption { | ||||||
|  |             uint32_t value; | ||||||
|  |             std::string key; | ||||||
|  |             std::string name; | ||||||
|  | 
 | ||||||
|  |             template <typename ENUM_TYPE = uint32_t> | ||||||
|  |             ConfigOptionEnumOption(ENUM_TYPE value, std::string key, std::string name) | ||||||
|  |                 : value(static_cast<uint32_t>(value)), key(key), name(name) {} | ||||||
|  | 
 | ||||||
|  |             template <typename ENUM_TYPE = uint32_t> | ||||||
|  |             ConfigOptionEnumOption(ENUM_TYPE value, std::string key) | ||||||
|  |                 : value(static_cast<uint32_t>(value)), key(key), name(key) {} | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct ConfigOptionEnum { | ||||||
|  |             std::vector<ConfigOptionEnumOption> options; | ||||||
|  |             uint32_t default_value = 0; | ||||||
|  | 
 | ||||||
|  |             // Case insensitive search for an option based on a key string. (Matches against options[n].key)
 | ||||||
|  |             std::vector<ConfigOptionEnumOption>::const_iterator find_option_from_string(const std::string& option_key) const; | ||||||
|  |             // Search for an option that has a specific value. (Matches against options[n].value)
 | ||||||
|  |             std::vector<ConfigOptionEnumOption>::const_iterator find_option_from_value(uint32_t value) const; | ||||||
|  |             // Verify an option has a unique key and a unique value
 | ||||||
|  |             bool can_add_option(const std::string& option_key, uint32_t option_value) const; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct ConfigOptionNumber { | ||||||
|  |             double min = 0.0; | ||||||
|  |             double max = 0.0; | ||||||
|  |             double step = 0.0; | ||||||
|  |             int precision = 0; | ||||||
|  |             bool percent = false; | ||||||
|  |             double default_value = 0.0; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct ConfigOptionString { | ||||||
|  |             std::string default_value; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct ConfigOptionBool { | ||||||
|  |             bool default_value; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         typedef std::variant<ConfigOptionEnum, ConfigOptionNumber, ConfigOptionString, ConfigOptionBool> ConfigOptionVariant; | ||||||
|  | 
 | ||||||
|  |         struct ConfigOption { | ||||||
|  |             std::string id; | ||||||
|  |             std::string name; | ||||||
|  |             std::string description; | ||||||
|  |             bool hidden = false; | ||||||
|  |             ConfigOptionType type; | ||||||
|  |             ConfigOptionVariant variant; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         typedef std::variant<std::monostate, uint32_t, double, std::string, bool> ConfigValueVariant; | ||||||
|  | 
 | ||||||
|  |         // Manages value dependencies between config options (e.g. option is hidden or disabled from other option being a certain value) .
 | ||||||
|  |         class ConfigOptionDependency { | ||||||
|  |         private: | ||||||
|  |             // Maps options to the options that are affected by their values
 | ||||||
|  |             std::unordered_map<size_t, std::unordered_set<size_t>> option_to_dependencies = {}; | ||||||
|  |             // Maps dependent options to the values that the source option can be
 | ||||||
|  |             std::unordered_map<size_t, std::vector<ConfigValueVariant>> dependency_to_values = {}; | ||||||
|  |         public: | ||||||
|  |             ConfigOptionDependency() = default; | ||||||
|  | 
 | ||||||
|  |             // Add dependency. When <source_option> is one of the <values>, <dependent_option> is affected.
 | ||||||
|  |             void add_option_dependency(size_t dependent_option_index, size_t source_option_index, std::vector<ConfigValueVariant> &values); | ||||||
|  | 
 | ||||||
|  |             // Check which dependent options are affected by the value of the source option.
 | ||||||
|  |             // Returns a map of dependent options and if they are a match
 | ||||||
|  |             std::unordered_map<size_t, bool> check_option_dependencies(size_t source_option_index, ConfigValueVariant value); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct ConfigSchema { | ||||||
|  |             std::vector<ConfigOption> options; | ||||||
|  |             std::unordered_map<std::string, size_t> options_by_id; | ||||||
|  |             ConfigOptionDependency disable_dependencies; | ||||||
|  |             ConfigOptionDependency hidden_dependencies; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct ConfigStorage { | ||||||
|  |             std::unordered_map<std::string, ConfigValueVariant> value_map; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         enum class ConfigOptionUpdateType { | ||||||
|  |             Disabled, | ||||||
|  |             Hidden, | ||||||
|  |             EnumDetails, | ||||||
|  |             EnumDisabled, | ||||||
|  |             Value, | ||||||
|  |             Description | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct ConfigOptionUpdateContext { | ||||||
|  |             size_t option_index; | ||||||
|  |             std::vector<ConfigOptionUpdateType> updates = {}; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         enum class OptionChangeContext { | ||||||
|  |             Load, | ||||||
|  |             Temporary, | ||||||
|  |             Permanent | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         using on_option_change_callback = std::function<void( | ||||||
|  |             ConfigValueVariant cur_value, | ||||||
|  |             ConfigValueVariant prev_value, | ||||||
|  |             OptionChangeContext change_context | ||||||
|  |         )>; | ||||||
|  | 
 | ||||||
|  |         using parse_option_func = std::function<ConfigValueVariant(const nlohmann::json&)>; | ||||||
|  |         using serialize_option_func = std::function<nlohmann::json(const ConfigValueVariant&)>; | ||||||
|  | 
 | ||||||
|  |         class Config { | ||||||
|  |         public: | ||||||
|  |             std::string name; | ||||||
|  |             // id is used for the file name (e.g. general.json) and storing keys
 | ||||||
|  |             std::string id; | ||||||
|  |             // If true, any configuration changes are temporarily stored until Apply is pressed.
 | ||||||
|  |             // Changing the tab will prompt the user to either apply or cancel changes.
 | ||||||
|  |             bool requires_confirmation = false; | ||||||
|  |              | ||||||
|  |             std::unordered_set<size_t> modified_options = {}; | ||||||
|  | 
 | ||||||
|  |             // For base game configs
 | ||||||
|  |             Config(std::string name, std::string id, bool requires_confirmation = false); | ||||||
|  |             // For mod configs
 | ||||||
|  |             Config(); | ||||||
|  | 
 | ||||||
|  |             void set_id(const std::string &id); | ||||||
|  |             void set_mod_version(const std::string &mod_version); | ||||||
|  | 
 | ||||||
|  |             void add_option(const ConfigOption& option); | ||||||
|  | 
 | ||||||
|  |             void add_enum_option( | ||||||
|  |                 const std::string &id, | ||||||
|  |                 const std::string &name, | ||||||
|  |                 const std::string &description, | ||||||
|  |                 const std::vector<ConfigOptionEnumOption> &options, | ||||||
|  |                 uint32_t default_value, | ||||||
|  |                 bool hidden = false | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             template <typename ENUM_TYPE> | ||||||
|  |             void add_enum_option( | ||||||
|  |                 const std::string &id, | ||||||
|  |                 const std::string &name, | ||||||
|  |                 const std::string &description, | ||||||
|  |                 const std::vector<ConfigOptionEnumOption> &options, | ||||||
|  |                 ENUM_TYPE default_value, | ||||||
|  |                 bool hidden = false | ||||||
|  |             ) { | ||||||
|  |                 add_enum_option(id, name, description, options, static_cast<uint32_t>(default_value), hidden); | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             void add_number_option( | ||||||
|  |                 const std::string &id, | ||||||
|  |                 const std::string &name, | ||||||
|  |                 const std::string &description, | ||||||
|  |                 double min = 0, | ||||||
|  |                 double max = 0, | ||||||
|  |                 double step = 1, | ||||||
|  |                 int precision = 0, | ||||||
|  |                 bool percent = false, | ||||||
|  |                 double default_value = 0, | ||||||
|  |                 bool hidden = false | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             void add_string_option( | ||||||
|  |                 const std::string &id, | ||||||
|  |                 const std::string &name, | ||||||
|  |                 const std::string &description, | ||||||
|  |                 const std::string &default_value, | ||||||
|  |                 bool hidden = false | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             void add_bool_option( | ||||||
|  |                 const std::string &id, | ||||||
|  |                 const std::string &name, | ||||||
|  |                 const std::string &description, | ||||||
|  |                 bool default_value = false, | ||||||
|  |                 bool hidden = false | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             const ConfigValueVariant get_option_value(const std::string& option_id) const; | ||||||
|  |             const ConfigValueVariant get_temp_option_value(const std::string& option_id) const; | ||||||
|  |             // This should only be used internally to recompui. Other changes to values should be done through update_option_value
 | ||||||
|  |             // so rendering can be updated with your new set value.
 | ||||||
|  |             void set_option_value(const std::string& option_id, ConfigValueVariant value); | ||||||
|  |             bool get_enum_option_disabled(size_t option_index, uint32_t enum_index); | ||||||
|  |             void add_option_change_callback(const std::string& option_id, on_option_change_callback callback); | ||||||
|  |             void set_apply_callback(std::function<void()> callback) { | ||||||
|  |                 apply_callback = callback; | ||||||
|  |             } | ||||||
|  |             void set_save_callback(std::function<void()> callback) { | ||||||
|  |                 save_callback = callback; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             void report_config_option_update(size_t option_index, ConfigOptionUpdateType update_type); | ||||||
|  |             void update_option_disabled(size_t option_index, bool disabled); | ||||||
|  |             void update_option_disabled(const std::string& option_id, bool disabled); | ||||||
|  |             void update_option_hidden(size_t option_index, bool hidden); | ||||||
|  |             void update_option_hidden(const std::string& option_id, bool hidden); | ||||||
|  |             void update_option_enum_details(const std::string& option_id, const std::string& enum_details); | ||||||
|  |             void update_option_value(const std::string& option_id, ConfigValueVariant value); | ||||||
|  |             void update_option_description(const std::string& option_id, const std::string& new_description); | ||||||
|  |             void update_enum_option_disabled(const std::string& option_id, uint32_t enum_index, bool disabled); | ||||||
|  | 
 | ||||||
|  |             // Makes the dependent option disabled when the source option is set to any of the specified values.
 | ||||||
|  |             void add_option_disable_dependency(const std::string& dependent_option_id, const std::string& source_option_id, std::vector<ConfigValueVariant> &values); | ||||||
|  |             template <typename... ENUM_TYPE> | ||||||
|  |             void add_option_disable_dependency(const std::string& dependent_option_id, const std::string& source_option_id, ENUM_TYPE... enum_values) { | ||||||
|  |                 std::vector<ConfigValueVariant> values; | ||||||
|  |                 for (const auto& value : {enum_values...}) { | ||||||
|  |                     values.push_back(static_cast<uint32_t>(value)); | ||||||
|  |                 } | ||||||
|  |                 add_option_disable_dependency(dependent_option_id, source_option_id, values); | ||||||
|  |             }; | ||||||
|  |             // Makes the dependent option hidden when the source option is set to any of the specified values.
 | ||||||
|  |             // Does not override the option's inherent hidden property if set.
 | ||||||
|  |             void add_option_hidden_dependency(const std::string& dependent_option_id, const std::string& source_option_id, std::vector<ConfigValueVariant> &values); | ||||||
|  |             template <typename... ENUM_TYPE> | ||||||
|  |             void add_option_hidden_dependency(const std::string& dependent_option_id, const std::string& source_option_id, ENUM_TYPE... enum_values) { | ||||||
|  |                 std::vector<ConfigValueVariant> values; | ||||||
|  |                 for (const auto& value : {enum_values...}) { | ||||||
|  |                     values.push_back(static_cast<uint32_t>(value)); | ||||||
|  |                 } | ||||||
|  |                 add_option_hidden_dependency(dependent_option_id, source_option_id, values); | ||||||
|  |             }; | ||||||
|  |             void add_option_hidden_dependency(const std::string& dependent_option_id, const std::string& source_option_id, bool bool_val) { | ||||||
|  |                 std::vector<ConfigValueVariant> values = { bool_val }; | ||||||
|  |                 add_option_hidden_dependency(dependent_option_id, source_option_id, values); | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             bool load_config(std::function<bool(nlohmann::json &)> validate_callback = nullptr); | ||||||
|  |             bool save_config(); | ||||||
|  |             bool save_config_json(nlohmann::json config_json) const; | ||||||
|  |             nlohmann::json get_json_config() const; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             void revert_temp_config(); | ||||||
|  |             bool is_dirty(); | ||||||
|  | 
 | ||||||
|  |             std::vector<ConfigOptionUpdateContext> get_config_option_updates() { return config_option_updates; } | ||||||
|  |             bool is_config_option_disabled(size_t option_index) { return disabled_options.contains(option_index); } | ||||||
|  |             bool is_config_option_hidden(size_t option_index); | ||||||
|  |             void clear_config_option_updates() { | ||||||
|  |                 config_option_updates.clear(); | ||||||
|  |             } | ||||||
|  |             std::string get_enum_option_details(size_t option_index); | ||||||
|  |             void on_json_parse_option(const std::string& option_id, parse_option_func callback) { | ||||||
|  |                 json_parse_option_map[option_id] = callback; | ||||||
|  |             } | ||||||
|  |             void on_json_serialize_option(const std::string& option_id, serialize_option_func callback) { | ||||||
|  |                 json_serialize_option_map[option_id] = callback; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const ConfigStorage& get_config_storage() const; | ||||||
|  |             const ConfigSchema& get_config_schema() const; | ||||||
|  | 
 | ||||||
|  |         private: | ||||||
|  |             bool loaded_config = false; | ||||||
|  |             bool is_mod_config = false; | ||||||
|  | 
 | ||||||
|  |             std::string config_file_name; | ||||||
|  |             std::string mod_version; // only used if mod
 | ||||||
|  | 
 | ||||||
|  |             ConfigSchema schema; | ||||||
|  |             ConfigStorage storage; | ||||||
|  |             ConfigStorage temp_storage; | ||||||
|  | 
 | ||||||
|  |             std::unordered_map<size_t, on_option_change_callback> option_change_callbacks = {}; | ||||||
|  |             std::function<void()> apply_callback = nullptr; | ||||||
|  |             std::function<void()> save_callback = nullptr; | ||||||
|  |             std::vector<ConfigOptionUpdateContext> config_option_updates = {}; | ||||||
|  |             std::unordered_set<size_t> disabled_options = {}; | ||||||
|  |             std::unordered_set<size_t> hidden_options = {}; | ||||||
|  |             std::unordered_map<size_t, std::string> enum_option_details = {}; | ||||||
|  |             std::unordered_map<size_t, std::unordered_set<size_t>> enum_options_disabled = {}; | ||||||
|  | 
 | ||||||
|  |             std::unordered_map<std::string, parse_option_func> json_parse_option_map = {}; | ||||||
|  |             std::unordered_map<std::string, serialize_option_func> json_serialize_option_map = {}; | ||||||
|  | 
 | ||||||
|  |             const ConfigValueVariant get_option_value_from_storage(const std::string& option_id, const ConfigStorage& src) const; | ||||||
|  | 
 | ||||||
|  |             void derive_all_config_option_dependencies(); | ||||||
|  |             void derive_option_dependencies(size_t option_index); | ||||||
|  |             void try_call_option_change_callback(const std::string& option_id, ConfigValueVariant value, ConfigValueVariant prev_value, OptionChangeContext change_context); | ||||||
|  |             const ConfigValueVariant get_option_default_value(const std::string& option_id) const; | ||||||
|  |             void determine_changed_option(const std::string& option_id); | ||||||
|  |             ConfigValueVariant parse_config_option_json_value(const nlohmann::json& json_value, const ConfigOption &option); | ||||||
|  | 
 | ||||||
|  |             // Return pointer to the root of where the config values should be stored in the json.
 | ||||||
|  |             nlohmann::json *get_config_storage_root(nlohmann::json* json); | ||||||
|  |             nlohmann::json get_storage_json() const; | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #endif // __RECOMP_CONFIG_HPP__
 | ||||||
|  | @ -70,6 +70,7 @@ namespace recomp { | ||||||
|         OtherError |         OtherError | ||||||
|     }; |     }; | ||||||
|     void register_config_path(std::filesystem::path path); |     void register_config_path(std::filesystem::path path); | ||||||
|  |     std::filesystem::path get_config_path(); | ||||||
|     bool register_game(const recomp::GameEntry& entry); |     bool register_game(const recomp::GameEntry& entry); | ||||||
|     void check_all_stored_roms(); |     void check_all_stored_roms(); | ||||||
|     bool load_stored_rom(std::u8string& game_id); |     bool load_stored_rom(std::u8string& game_id); | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ | ||||||
| #include "librecomp/game.hpp" | #include "librecomp/game.hpp" | ||||||
| #include "librecomp/sections.h" | #include "librecomp/sections.h" | ||||||
| #include "librecomp/overlays.hpp" | #include "librecomp/overlays.hpp" | ||||||
|  | #include "librecomp/config.hpp" | ||||||
| 
 | 
 | ||||||
| namespace N64Recomp { | namespace N64Recomp { | ||||||
|     class Context; |     class Context; | ||||||
|  | @ -81,7 +82,10 @@ namespace recomp { | ||||||
|             InvalidDependencyString, |             InvalidDependencyString, | ||||||
|             MissingManifestField, |             MissingManifestField, | ||||||
|             DuplicateMod, |             DuplicateMod, | ||||||
|             WrongGame |             WrongGame, | ||||||
|  |             InvalidDisableOptionDependency, | ||||||
|  |             InvalidHiddenOptionDependency, | ||||||
|  |             DuplicateEnumStrings, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         std::string error_to_string(ModOpenError); |         std::string error_to_string(ModOpenError); | ||||||
|  | @ -126,14 +130,6 @@ namespace recomp { | ||||||
| 
 | 
 | ||||||
|         std::string error_to_string(CodeModLoadError); |         std::string error_to_string(CodeModLoadError); | ||||||
| 
 | 
 | ||||||
|         enum class ConfigOptionType { |  | ||||||
|             None, |  | ||||||
|             Enum, |  | ||||||
|             Number, |  | ||||||
|             String, |  | ||||||
|             Bool |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         struct ModFileHandle { |         struct ModFileHandle { | ||||||
|             virtual ~ModFileHandle() = default; |             virtual ~ModFileHandle() = default; | ||||||
|             virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0; |             virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0; | ||||||
|  | @ -174,49 +170,6 @@ namespace recomp { | ||||||
|             Version version; |             Version version; | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         struct ConfigOptionEnum { |  | ||||||
|             std::vector<std::string> options; |  | ||||||
|             uint32_t default_value = 0; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         struct ConfigOptionNumber { |  | ||||||
|             double min = 0.0; |  | ||||||
|             double max = 0.0; |  | ||||||
|             double step = 0.0; |  | ||||||
|             int precision = 0; |  | ||||||
|             bool percent = false; |  | ||||||
|             double default_value = 0.0; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         struct ConfigOptionString { |  | ||||||
|             std::string default_value; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         struct ConfigOptionBool { |  | ||||||
|             bool default_value; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         typedef std::variant<ConfigOptionEnum, ConfigOptionNumber, ConfigOptionString, ConfigOptionBool> ConfigOptionVariant; |  | ||||||
| 
 |  | ||||||
|         struct ConfigOption { |  | ||||||
|             std::string id; |  | ||||||
|             std::string name; |  | ||||||
|             std::string description; |  | ||||||
|             ConfigOptionType type; |  | ||||||
|             ConfigOptionVariant variant; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         struct ConfigSchema { |  | ||||||
|             std::vector<ConfigOption> options; |  | ||||||
|             std::unordered_map<std::string, size_t> options_by_id; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         typedef std::variant<std::monostate, uint32_t, double, std::string, bool> ConfigValueVariant; |  | ||||||
| 
 |  | ||||||
|         struct ConfigStorage { |  | ||||||
|             std::unordered_map<std::string, ConfigValueVariant> value_map; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         struct ModDetails { |         struct ModDetails { | ||||||
|             std::string mod_id; |             std::string mod_id; | ||||||
|             std::string display_name; |             std::string display_name; | ||||||
|  | @ -240,7 +193,6 @@ namespace recomp { | ||||||
|             std::vector<std::string> authors; |             std::vector<std::string> authors; | ||||||
|             std::vector<Dependency> dependencies; |             std::vector<Dependency> dependencies; | ||||||
|             std::unordered_map<std::string, size_t> dependencies_by_id; |             std::unordered_map<std::string, size_t> dependencies_by_id; | ||||||
|             ConfigSchema config_schema; |  | ||||||
|             Version minimum_recomp_version; |             Version minimum_recomp_version; | ||||||
|             Version version; |             Version version; | ||||||
|             bool runtime_toggleable; |             bool runtime_toggleable; | ||||||
|  | @ -355,12 +307,12 @@ namespace recomp { | ||||||
|             recomp::Version get_mod_version(size_t mod_index); |             recomp::Version get_mod_version(size_t mod_index); | ||||||
|             std::string get_mod_id(size_t mod_index); |             std::string get_mod_id(size_t mod_index); | ||||||
|             void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index); |             void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index); | ||||||
|             const ConfigSchema &get_mod_config_schema(const std::string &mod_id) const; |             const config::ConfigSchema &get_mod_config_schema(const std::string &mod_id) const; | ||||||
|             const std::vector<char> &get_mod_thumbnail(const std::string &mod_id) const; |             const std::vector<char> &get_mod_thumbnail(const std::string &mod_id) const; | ||||||
|             void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value); |             void set_mod_config_value(size_t mod_index, const std::string &option_id, const config::ConfigValueVariant &value); | ||||||
|             void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value); |             void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const config::ConfigValueVariant &value); | ||||||
|             ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id) const; |             config::ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id) const; | ||||||
|             ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id) const; |             config::ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id) const; | ||||||
|             void set_mods_config_path(const std::filesystem::path &path); |             void set_mods_config_path(const std::filesystem::path &path); | ||||||
|             void set_mod_config_directory(const std::filesystem::path &path); |             void set_mod_config_directory(const std::filesystem::path &path); | ||||||
|             ModContentTypeId register_content_type(const ModContentType& type); |             ModContentTypeId register_content_type(const ModContentType& type); | ||||||
|  | @ -376,7 +328,7 @@ namespace recomp { | ||||||
|             CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param); |             CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param); | ||||||
|             CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param); |             CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param); | ||||||
|             CodeModLoadError resolve_code_dependencies(ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param); |             CodeModLoadError resolve_code_dependencies(ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param); | ||||||
|             void add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail); |             void add_opened_mod(ModManifest&& manifest, config::Config&& config, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail); | ||||||
|             std::vector<ModLoadErrorDetails> regenerate_with_hooks( |             std::vector<ModLoadErrorDetails> regenerate_with_hooks( | ||||||
|                 const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks, |                 const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks, | ||||||
|                 const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, |                 const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, | ||||||
|  | @ -418,7 +370,7 @@ namespace recomp { | ||||||
|             std::vector<bool> processed_hook_slots; |             std::vector<bool> processed_hook_slots; | ||||||
|             // Generated shim functions to use for implementing shim exports.
 |             // Generated shim functions to use for implementing shim exports.
 | ||||||
|             std::vector<std::unique_ptr<N64Recomp::ShimFunction>> shim_functions; |             std::vector<std::unique_ptr<N64Recomp::ShimFunction>> shim_functions; | ||||||
|             ConfigSchema empty_schema; |             config::ConfigSchema empty_schema; | ||||||
|             std::vector<char> empty_bytes; |             std::vector<char> empty_bytes; | ||||||
|             size_t num_events = 0; |             size_t num_events = 0; | ||||||
|             ModContentTypeId code_content_type_id; |             ModContentTypeId code_content_type_id; | ||||||
|  | @ -442,7 +394,7 @@ namespace recomp { | ||||||
|         public: |         public: | ||||||
|             // TODO make these private and expose methods for the functionality they're currently used in.
 |             // TODO make these private and expose methods for the functionality they're currently used in.
 | ||||||
|             ModManifest manifest; |             ModManifest manifest; | ||||||
|             ConfigStorage config_storage; |             config::Config config; | ||||||
|             std::unique_ptr<ModCodeHandle> code_handle; |             std::unique_ptr<ModCodeHandle> code_handle; | ||||||
|             std::unique_ptr<N64Recomp::Context> recompiler_context; |             std::unique_ptr<N64Recomp::Context> recompiler_context; | ||||||
|             std::vector<uint32_t> section_load_addresses; |             std::vector<uint32_t> section_load_addresses; | ||||||
|  | @ -450,7 +402,7 @@ namespace recomp { | ||||||
|             std::vector<ModContentTypeId> content_types; |             std::vector<ModContentTypeId> content_types; | ||||||
|             std::vector<char> thumbnail; |             std::vector<char> thumbnail; | ||||||
| 
 | 
 | ||||||
|             ModHandle(const ModContext& context, ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail); |             ModHandle(const ModContext& context, ModManifest&& manifest, config::Config&& config, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail); | ||||||
|             ModHandle(const ModHandle& rhs) = delete; |             ModHandle(const ModHandle& rhs) = delete; | ||||||
|             ModHandle& operator=(const ModHandle& rhs) = delete; |             ModHandle& operator=(const ModHandle& rhs) = delete; | ||||||
|             ModHandle(ModHandle&& rhs); |             ModHandle(ModHandle&& rhs); | ||||||
|  | @ -592,7 +544,7 @@ namespace recomp { | ||||||
|         void reset_hooks(); |         void reset_hooks(); | ||||||
|         void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index); |         void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index); | ||||||
| 
 | 
 | ||||||
|         ModOpenError parse_manifest(ModManifest &ret, const std::vector<char> &manifest_data, std::string &error_param); |         ModOpenError parse_manifest(ModManifest &ret, const std::vector<char> &manifest_data, std::string &error_param, recomp::config::Config *config); | ||||||
|         CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param); |         CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param); | ||||||
| 
 | 
 | ||||||
|         void initialize_mods(); |         void initialize_mods(); | ||||||
|  | @ -607,12 +559,12 @@ namespace recomp { | ||||||
|         void enable_mod(const std::string& mod_id, bool enabled); |         void enable_mod(const std::string& mod_id, bool enabled); | ||||||
|         bool is_mod_enabled(const std::string& mod_id); |         bool is_mod_enabled(const std::string& mod_id); | ||||||
|         bool is_mod_auto_enabled(const std::string& mod_id); |         bool is_mod_auto_enabled(const std::string& mod_id); | ||||||
|         const ConfigSchema &get_mod_config_schema(const std::string &mod_id); |         const config::ConfigSchema &get_mod_config_schema(const std::string &mod_id); | ||||||
|         const std::vector<char> &get_mod_thumbnail(const std::string &mod_id); |         const std::vector<char> &get_mod_thumbnail(const std::string &mod_id); | ||||||
|         void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value); |         void set_mod_config_value(size_t mod_index, const std::string &option_id, const config::ConfigValueVariant &value); | ||||||
|         void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value); |         void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const config::ConfigValueVariant &value); | ||||||
|         ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id); |         config::ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id); | ||||||
|         ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id); |         config::ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id); | ||||||
|         std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename); |         std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename); | ||||||
|         std::filesystem::path get_mod_filename(const std::string& mod_id); |         std::filesystem::path get_mod_filename(const std::string& mod_id); | ||||||
|         size_t get_mod_order_index(const std::string& mod_id); |         size_t get_mod_order_index(const std::string& mod_id); | ||||||
|  |  | ||||||
							
								
								
									
										638
									
								
								librecomp/src/config.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										638
									
								
								librecomp/src/config.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,638 @@ | ||||||
|  | #include <fstream> | ||||||
|  | #include "librecomp/files.hpp" | ||||||
|  | #include "librecomp/config.hpp" | ||||||
|  | #include "librecomp/game.hpp" | ||||||
|  | #include "librecomp/mods.hpp" | ||||||
|  | 
 | ||||||
|  | static bool read_json(std::ifstream input_file, nlohmann::json& json_out) { | ||||||
|  |     if (!input_file.good()) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         input_file >> json_out; | ||||||
|  |     } | ||||||
|  |     catch (nlohmann::json::parse_error&) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out) { | ||||||
|  |     // Try reading and parsing the base file.
 | ||||||
|  |     if (read_json(std::ifstream{path}, json_out)) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Try reading and parsing the backup file.
 | ||||||
|  |     if (read_json(recomp::open_input_backup_file(path), json_out)) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Both reads failed.
 | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data) { | ||||||
|  |     { | ||||||
|  |         std::ofstream output_file = recomp::open_output_file_with_backup(path); | ||||||
|  |         if (!output_file.good()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         output_file << std::setw(4) << json_data; | ||||||
|  |     } | ||||||
|  |     return recomp::finalize_output_file_with_backup(path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static std::filesystem::path get_path_to_config(bool is_mod_config) { | ||||||
|  |     if (is_mod_config) { | ||||||
|  |         return recomp::get_config_path() / recomp::mods::mod_config_directory; | ||||||
|  |     } | ||||||
|  |     return recomp::get_config_path(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace recomp::config { | ||||||
|  | Config::Config(std::string name, std::string id, bool requires_confirmation) { | ||||||
|  |     this->name = name; | ||||||
|  |     this->id = id; | ||||||
|  |     this->requires_confirmation = requires_confirmation; | ||||||
|  |     schema.options.clear(); | ||||||
|  |     schema.options_by_id.clear(); | ||||||
|  |     storage.value_map.clear(); | ||||||
|  |     temp_storage.value_map.clear(); | ||||||
|  | 
 | ||||||
|  |     config_file_name = this->id + ".json"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Config::Config() { | ||||||
|  |     is_mod_config = true; | ||||||
|  |     requires_confirmation = false; | ||||||
|  |     name = "Mod Config"; | ||||||
|  | 
 | ||||||
|  |     schema.options.clear(); | ||||||
|  |     schema.options_by_id.clear(); | ||||||
|  |     storage.value_map.clear(); | ||||||
|  |     temp_storage.value_map.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::set_id(const std::string &id) { | ||||||
|  |     this->id = id; | ||||||
|  |     config_file_name = this->id + ".json"; | ||||||
|  | } | ||||||
|  | void Config::set_mod_version(const std::string &mod_version) { | ||||||
|  |     this->mod_version = mod_version; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ConfigStorage& Config::get_config_storage() const { | ||||||
|  |     return storage; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ConfigSchema& Config::get_config_schema() const { | ||||||
|  |     return schema; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nlohmann::json *Config::get_config_storage_root(nlohmann::json* json) { | ||||||
|  |     if (is_mod_config) { | ||||||
|  |         return &(*json)["storage"]; | ||||||
|  |     } | ||||||
|  |     return json; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::add_option(const ConfigOption& option) { | ||||||
|  |     if (loaded_config) { | ||||||
|  |         assert(false && "Cannot add options after config has been loaded."); | ||||||
|  |     } | ||||||
|  |     schema.options.push_back(option); | ||||||
|  |     schema.options_by_id[option.id] = schema.options.size() - 1; | ||||||
|  | 
 | ||||||
|  |     ConfigValueVariant default_value = std::monostate(); | ||||||
|  |     switch (option.type) { | ||||||
|  |         case ConfigOptionType::None: | ||||||
|  |             assert(false && "Cannot add option with type None."); | ||||||
|  |             break; | ||||||
|  |         case ConfigOptionType::Enum: | ||||||
|  |             default_value = std::get<ConfigOptionEnum>(option.variant).default_value; | ||||||
|  |             break; | ||||||
|  |         case ConfigOptionType::Number: | ||||||
|  |             default_value = std::get<ConfigOptionNumber>(option.variant).default_value; | ||||||
|  |             break; | ||||||
|  |         case ConfigOptionType::String: | ||||||
|  |             default_value = std::get<ConfigOptionString>(option.variant).default_value; | ||||||
|  |             break; | ||||||
|  |         case ConfigOptionType::Bool: | ||||||
|  |             default_value = std::get<ConfigOptionBool>(option.variant).default_value; | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     storage.value_map[option.id] = default_value; | ||||||
|  |     if (requires_confirmation) { | ||||||
|  |         temp_storage.value_map[option.id] = default_value; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::add_enum_option( | ||||||
|  |     const std::string &id, | ||||||
|  |     const std::string &name, | ||||||
|  |     const std::string &description, | ||||||
|  |     const std::vector<ConfigOptionEnumOption> &options, | ||||||
|  |     uint32_t default_value, | ||||||
|  |     bool hidden | ||||||
|  | ) { | ||||||
|  |     ConfigOption option; | ||||||
|  |     option.id = id; | ||||||
|  |     option.name = name; | ||||||
|  |     option.description = description; | ||||||
|  |     option.type = ConfigOptionType::Enum; | ||||||
|  |     option.hidden = hidden; | ||||||
|  | 
 | ||||||
|  |     ConfigOptionEnum option_enum = {{}, default_value}; | ||||||
|  | 
 | ||||||
|  |     // Note: this is a bit too predictive since this calls add_option
 | ||||||
|  |     size_t option_index = schema.options.size(); | ||||||
|  | 
 | ||||||
|  |     for (const auto &option : options) { | ||||||
|  |         assert(option_enum.can_add_option(option.key, option.value) && "Duplicate enum option key or value."); | ||||||
|  |         option_enum.options.push_back(option); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (option_enum.find_option_from_value(default_value) == option_enum.options.end()) { | ||||||
|  |         assert(false && "Default value must match to an option."); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     option.variant = option_enum; | ||||||
|  | 
 | ||||||
|  |     add_option(option); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::add_number_option( | ||||||
|  |     const std::string &id, | ||||||
|  |     const std::string &name, | ||||||
|  |     const std::string &description, | ||||||
|  |     double min, | ||||||
|  |     double max, | ||||||
|  |     double step, | ||||||
|  |     int precision, | ||||||
|  |     bool percent, | ||||||
|  |     double default_value, | ||||||
|  |     bool hidden | ||||||
|  | ) { | ||||||
|  |     ConfigOption option; | ||||||
|  |     option.id = id; | ||||||
|  |     option.name = name; | ||||||
|  |     option.description = description; | ||||||
|  |     option.type = ConfigOptionType::Number; | ||||||
|  |     option.variant = ConfigOptionNumber{min, max, step, precision, percent, default_value}; | ||||||
|  |     option.hidden = hidden; | ||||||
|  | 
 | ||||||
|  |     add_option(option); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::add_string_option( | ||||||
|  |     const std::string &id, | ||||||
|  |     const std::string &name, | ||||||
|  |     const std::string &description, | ||||||
|  |     const std::string &default_value, | ||||||
|  |     bool hidden | ||||||
|  | ) { | ||||||
|  |     ConfigOption option; | ||||||
|  |     option.id = id; | ||||||
|  |     option.name = name; | ||||||
|  |     option.description = description; | ||||||
|  |     option.type = ConfigOptionType::String; | ||||||
|  |     option.variant = ConfigOptionString{default_value}; | ||||||
|  |     option.hidden = hidden; | ||||||
|  | 
 | ||||||
|  |     add_option(option); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::add_bool_option( | ||||||
|  |     const std::string &id, | ||||||
|  |     const std::string &name, | ||||||
|  |     const std::string &description, | ||||||
|  |     bool default_value, | ||||||
|  |     bool hidden | ||||||
|  | ) { | ||||||
|  |     ConfigOption option; | ||||||
|  |     option.id = id; | ||||||
|  |     option.name = name; | ||||||
|  |     option.description = description; | ||||||
|  |     option.type = ConfigOptionType::Bool; | ||||||
|  |     option.variant = ConfigOptionBool{default_value}; | ||||||
|  |     option.hidden = hidden; | ||||||
|  | 
 | ||||||
|  |     add_option(option); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ConfigValueVariant Config::get_option_default_value(const std::string& option_id) const { | ||||||
|  |     auto option_by_id_it = schema.options_by_id.find(option_id); | ||||||
|  |     if (option_by_id_it == schema.options_by_id.end()) { | ||||||
|  |         assert(false && "Option not found."); | ||||||
|  |         return std::monostate(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const ConfigOption &option = schema.options[option_by_id_it->second]; | ||||||
|  |     switch (option.type) { | ||||||
|  |     case ConfigOptionType::Enum: | ||||||
|  |         return std::get<ConfigOptionEnum>(option.variant).default_value; | ||||||
|  |     case ConfigOptionType::Number: | ||||||
|  |         return std::get<ConfigOptionNumber>(option.variant).default_value; | ||||||
|  |     case ConfigOptionType::String: | ||||||
|  |         return std::get<ConfigOptionString>(option.variant).default_value; | ||||||
|  |     case ConfigOptionType::Bool: | ||||||
|  |         return std::get<ConfigOptionBool>(option.variant).default_value; | ||||||
|  |     default: | ||||||
|  |         assert(false && "Unknown config option type."); | ||||||
|  |         return std::monostate(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ConfigValueVariant Config::get_option_value_from_storage(const std::string& option_id, const ConfigStorage& src) const { | ||||||
|  |     auto it = src.value_map.find(option_id); | ||||||
|  |     if (it != src.value_map.end()) { | ||||||
|  |         return it->second; | ||||||
|  |     } | ||||||
|  |     return get_option_default_value(option_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ConfigValueVariant Config::get_option_value(const std::string& option_id) const { | ||||||
|  |     return get_option_value_from_storage(option_id, storage); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ConfigValueVariant Config::get_temp_option_value(const std::string& option_id) const { | ||||||
|  |     return get_option_value_from_storage(option_id, temp_storage); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::determine_changed_option(const std::string& option_id) { | ||||||
|  |     if (get_option_value(option_id) != get_temp_option_value(option_id)) { | ||||||
|  |         modified_options.insert(schema.options_by_id[option_id]); | ||||||
|  |     } else { | ||||||
|  |         modified_options.erase(schema.options_by_id[option_id]); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::try_call_option_change_callback(const std::string& option_id, ConfigValueVariant value, ConfigValueVariant prev_value, OptionChangeContext change_context) { | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     auto callback_it = option_change_callbacks.find(option_index); | ||||||
|  |     bool is_load = (change_context == OptionChangeContext::Load); | ||||||
|  |     bool value_changed = (value != prev_value); | ||||||
|  |     if (callback_it != option_change_callbacks.end() && (is_load || value_changed)) { | ||||||
|  |         callback_it->second(value, prev_value, change_context); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::set_option_value(const std::string& option_id, ConfigValueVariant value) { | ||||||
|  |     ConfigStorage &storage = requires_confirmation ? temp_storage : storage; | ||||||
|  | 
 | ||||||
|  |     auto it = storage.value_map.find(option_id); | ||||||
|  |     if (it != storage.value_map.end()) { | ||||||
|  |         ConfigValueVariant prev_value = it->second; | ||||||
|  |         it->second = value; | ||||||
|  | 
 | ||||||
|  |         if (requires_confirmation) { | ||||||
|  |             determine_changed_option(option_id); | ||||||
|  |             try_call_option_change_callback(option_id, value, prev_value, OptionChangeContext::Temporary); | ||||||
|  |         } else { | ||||||
|  |             try_call_option_change_callback(option_id, value, prev_value, OptionChangeContext::Permanent); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         derive_option_dependencies(schema.options_by_id[option_id]); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Config::get_enum_option_disabled(size_t option_index, uint32_t enum_index) { | ||||||
|  |     auto enum_it = enum_options_disabled.find(option_index); | ||||||
|  |     if (enum_it != enum_options_disabled.end()) { | ||||||
|  |         return enum_it->second.contains(enum_index); | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nlohmann::json Config::get_json_config() const { | ||||||
|  |     if (is_mod_config) { | ||||||
|  |         nlohmann::json config_json; | ||||||
|  |         if (id.empty()) { | ||||||
|  |             assert(false && "Mod ID does not exist for this config."); | ||||||
|  |         } | ||||||
|  |         if (mod_version.empty()) { | ||||||
|  |             assert(false && "Mod version does not exist for this config."); | ||||||
|  |         } | ||||||
|  |         config_json["mod_id"] = id; | ||||||
|  |         config_json["mod_version"] = mod_version; | ||||||
|  |         config_json["recomp_version"] = recomp::get_project_version().to_string(); | ||||||
|  |         config_json["storage"] = get_storage_json(); | ||||||
|  |         return config_json; | ||||||
|  |     } | ||||||
|  |     return get_storage_json(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nlohmann::json Config::get_storage_json() const { | ||||||
|  |     nlohmann::json json; | ||||||
|  |     for (const auto& option : schema.options) { | ||||||
|  |         const ConfigValueVariant value = get_option_value(option.id); | ||||||
|  | 
 | ||||||
|  |         if (json_serialize_option_map.contains(option.id)) { | ||||||
|  |             auto cb = json_serialize_option_map.at(option.id); | ||||||
|  |             json[option.id] = cb(value); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         switch (option.type) { | ||||||
|  |         case ConfigOptionType::Enum: { | ||||||
|  |             auto &option_enum = std::get<ConfigOptionEnum>(option.variant); | ||||||
|  |             auto found_opt = option_enum.find_option_from_value(std::get<uint32_t>(value)); | ||||||
|  |             if (found_opt != option_enum.options.end()) { | ||||||
|  |                 json[option.id] = found_opt->key; | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case ConfigOptionType::Number: { | ||||||
|  |             auto &option_number = std::get<ConfigOptionNumber>(option.variant); | ||||||
|  |             if (option_number.precision == 0) { | ||||||
|  |                 json[option.id] = static_cast<int>(std::get<double>(value)); | ||||||
|  |             } else { | ||||||
|  |                 json[option.id] = std::get<double>(value); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case ConfigOptionType::String: | ||||||
|  |             json[option.id] = std::get<std::string>(value); | ||||||
|  |             break; | ||||||
|  |         case ConfigOptionType::Bool: | ||||||
|  |             json[option.id] = std::get<bool>(value); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return json; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Config::save_config_json(nlohmann::json config_json) const { | ||||||
|  |     std::filesystem::path file_path = get_path_to_config(is_mod_config) / config_file_name; | ||||||
|  | 
 | ||||||
|  |     bool result = save_json_with_backups(file_path, config_json); | ||||||
|  |     if (save_callback) { | ||||||
|  |         save_callback(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Config::save_config() { | ||||||
|  |     if (requires_confirmation) { | ||||||
|  |         for (const auto& option : schema.options) { | ||||||
|  |             ConfigValueVariant prev_value = get_option_value(option.id); | ||||||
|  |             ConfigValueVariant cur_value = get_temp_option_value(option.id); | ||||||
|  |             storage.value_map[option.id] = cur_value; | ||||||
|  |             try_call_option_change_callback(option.id, cur_value, prev_value, OptionChangeContext::Permanent); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (apply_callback && is_dirty()) { | ||||||
|  |             apply_callback(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         modified_options.clear(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return save_config_json(get_json_config()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::derive_option_dependencies(size_t option_index) { | ||||||
|  |     auto &option_id = schema.options[option_index].id; | ||||||
|  |     auto value = requires_confirmation ? get_temp_option_value(option_id) :  get_option_value(option_id); | ||||||
|  | 
 | ||||||
|  |     auto disable_result = schema.disable_dependencies.check_option_dependencies(option_index, value); | ||||||
|  |     for (auto &option_res : disable_result) { | ||||||
|  |         update_option_disabled(option_res.first, option_res.second); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto hidden_result = schema.hidden_dependencies.check_option_dependencies(option_index, value); | ||||||
|  |     for (auto &option_res : hidden_result) { | ||||||
|  |         update_option_hidden(option_res.first, option_res.second); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::derive_all_config_option_dependencies() { | ||||||
|  |     for (size_t option_index = 0; option_index < schema.options.size(); option_index++) { | ||||||
|  |         derive_option_dependencies(option_index); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ConfigValueVariant Config::parse_config_option_json_value(const nlohmann::json& json_value, const ConfigOption &option) { | ||||||
|  |     if (json_parse_option_map.contains(option.id)) { | ||||||
|  |         return json_parse_option_map[option.id](json_value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool is_null = json_value.is_null(); | ||||||
|  | 
 | ||||||
|  |     switch (option.type) { | ||||||
|  |         case ConfigOptionType::None: | ||||||
|  |         default: { | ||||||
|  |             return {}; | ||||||
|  |         } | ||||||
|  |         case ConfigOptionType::Enum: { | ||||||
|  |             if (is_null) { | ||||||
|  |                 return std::get<ConfigOptionEnum>(option.variant).default_value; | ||||||
|  |             } | ||||||
|  |             std::string enum_string_value = json_value.get<std::string>(); | ||||||
|  |             auto option_variant = std::get<ConfigOptionEnum>(option.variant); | ||||||
|  |             auto found_opt = option_variant.find_option_from_string(enum_string_value); | ||||||
|  |             if (found_opt != option_variant.options.end()) { | ||||||
|  |                 return found_opt->value; | ||||||
|  |             } else { | ||||||
|  |                 return std::get<ConfigOptionEnum>(option.variant).default_value; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         case ConfigOptionType::Number: | ||||||
|  |             if (is_null) { | ||||||
|  |                 return std::get<ConfigOptionNumber>(option.variant).default_value; | ||||||
|  |             } | ||||||
|  |             return json_value.get<double>(); | ||||||
|  |         case ConfigOptionType::String: | ||||||
|  |             if (is_null) { | ||||||
|  |                 return std::get<ConfigOptionString>(option.variant).default_value; | ||||||
|  |             } | ||||||
|  |             return json_value.get<std::string>(); | ||||||
|  |         case ConfigOptionType::Bool: | ||||||
|  |             if (is_null) { | ||||||
|  |                 return std::get<ConfigOptionBool>(option.variant).default_value; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             return json_value.get<bool>(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Config::load_config(std::function<bool(nlohmann::json &)> validate_callback) { | ||||||
|  |     std::filesystem::path file_path = get_path_to_config(is_mod_config) / config_file_name; | ||||||
|  |     nlohmann::json config_json{}; | ||||||
|  | 
 | ||||||
|  |     if (!read_json_with_backups(file_path, config_json)) { | ||||||
|  |         if (requires_confirmation) { | ||||||
|  |             revert_temp_config(); | ||||||
|  |         } | ||||||
|  |         save_config(); | ||||||
|  |         derive_all_config_option_dependencies(); | ||||||
|  |         clear_config_option_updates(); | ||||||
|  |         loaded_config = true; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (validate_callback != nullptr && !validate_callback(config_json)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     nlohmann::json *json_config_root = get_config_storage_root(&config_json); | ||||||
|  | 
 | ||||||
|  |     for (const auto& option : schema.options) { | ||||||
|  |         auto json_value = (*json_config_root)[option.id]; | ||||||
|  | 
 | ||||||
|  |         auto value = parse_config_option_json_value(json_value, option); | ||||||
|  |         storage.value_map[option.id] = value; | ||||||
|  | 
 | ||||||
|  |         if (requires_confirmation) { | ||||||
|  |             temp_storage.value_map[option.id] = value; | ||||||
|  |         } | ||||||
|  |         try_call_option_change_callback(option.id, value, value, OptionChangeContext::Load); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     derive_all_config_option_dependencies(); | ||||||
|  |     clear_config_option_updates(); | ||||||
|  | 
 | ||||||
|  |     loaded_config = true; | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::revert_temp_config() { | ||||||
|  |     if (!requires_confirmation) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     modified_options.clear(); | ||||||
|  | 
 | ||||||
|  |     for (const auto& option : schema.options) { | ||||||
|  |         temp_storage.value_map[option.id] = get_option_value(option.id); | ||||||
|  |     } | ||||||
|  |     derive_all_config_option_dependencies(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Config::is_dirty() { | ||||||
|  |     return !modified_options.empty(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::add_option_change_callback(const std::string& option_id, on_option_change_callback callback) { | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     option_change_callbacks[option_index] = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::report_config_option_update(size_t option_index, ConfigOptionUpdateType update_type) { | ||||||
|  |     ConfigOptionUpdateContext *update_context = nullptr; | ||||||
|  |     for (auto &context : config_option_updates) { | ||||||
|  |         if (context.option_index == option_index) { | ||||||
|  |             update_context = &context; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (update_context == nullptr) { | ||||||
|  |         config_option_updates.push_back({option_index, {}}); | ||||||
|  |         update_context = &config_option_updates.back(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     update_context->updates.push_back(update_type); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::update_option_disabled(size_t option_index, bool disabled) { | ||||||
|  |     bool was_disabled = is_config_option_disabled(option_index); | ||||||
|  |     if (was_disabled == disabled) return; | ||||||
|  | 
 | ||||||
|  |     if (disabled) { | ||||||
|  |         disabled_options.insert(option_index); | ||||||
|  |     } else { | ||||||
|  |         disabled_options.erase(option_index); | ||||||
|  |     } | ||||||
|  |     report_config_option_update(option_index, ConfigOptionUpdateType::Disabled); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void Config::update_option_disabled(const std::string& option_id, bool disabled) { | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     update_option_disabled(option_index, disabled); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void Config::update_option_hidden(size_t option_index, bool hidden) { | ||||||
|  |     if (schema.options[option_index].hidden) { | ||||||
|  |         // unchangeable - always hidden
 | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     bool was_hidden = is_config_option_hidden(option_index); | ||||||
|  |     if (was_hidden == hidden) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (hidden) { | ||||||
|  |         hidden_options.insert(option_index); | ||||||
|  |     } else { | ||||||
|  |         hidden_options.erase(option_index); | ||||||
|  |     } | ||||||
|  |     report_config_option_update(option_index, ConfigOptionUpdateType::Hidden); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void Config::update_option_hidden(const std::string& option_id, bool hidden) { | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     update_option_hidden(option_index, hidden); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void Config::update_option_enum_details(const std::string& option_id, const std::string& enum_details) { | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     enum_option_details[option_index] = enum_details; | ||||||
|  |     report_config_option_update(option_index, ConfigOptionUpdateType::EnumDetails); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void Config::update_option_value(const std::string& option_id, ConfigValueVariant value) { | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     // This could potentially cause an update loop due to set_option_value calling change callbacks, which could call this function.
 | ||||||
|  |     // It seems more important to call change callbacks AND respect requires_confirmation
 | ||||||
|  |     set_option_value(option_id, value); | ||||||
|  |     report_config_option_update(option_index, ConfigOptionUpdateType::Value); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void Config::update_option_description(const std::string& option_id, const std::string& new_description) { | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     schema.options[option_index].description = new_description; | ||||||
|  |     report_config_option_update(option_index, ConfigOptionUpdateType::Description); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::update_enum_option_disabled(const std::string& option_id, uint32_t enum_index, bool disabled) { | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     if (!enum_options_disabled.contains(option_index)) { | ||||||
|  |         enum_options_disabled[option_index] = {}; | ||||||
|  |     } | ||||||
|  |     if (disabled) { | ||||||
|  |         enum_options_disabled[option_index].insert(enum_index); | ||||||
|  |     } else { | ||||||
|  |         enum_options_disabled[option_index].erase(enum_index); | ||||||
|  |     } | ||||||
|  |     report_config_option_update(option_index, ConfigOptionUpdateType::EnumDisabled); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::add_option_disable_dependency(const std::string& dependent_option_id, const std::string& source_option_id, std::vector<ConfigValueVariant> &values) { | ||||||
|  |     size_t dependent_index = schema.options_by_id[dependent_option_id]; | ||||||
|  |     size_t source_index = schema.options_by_id[source_option_id]; | ||||||
|  |     schema.disable_dependencies.add_option_dependency(dependent_index, source_index, values); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Config::add_option_hidden_dependency(const std::string& dependent_option_id, const std::string& source_option_id, std::vector<ConfigValueVariant> &values) { | ||||||
|  |     size_t dependent_index = schema.options_by_id[dependent_option_id]; | ||||||
|  |     size_t source_index = schema.options_by_id[source_option_id]; | ||||||
|  |     schema.hidden_dependencies.add_option_dependency(dependent_index, source_index, values); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string Config::get_enum_option_details(size_t option_index) { | ||||||
|  |     if (!enum_option_details.contains(option_index)) { | ||||||
|  |         return std::string(); | ||||||
|  |     } | ||||||
|  |     return enum_option_details[option_index]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Config::is_config_option_hidden(size_t option_index) { | ||||||
|  |     return schema.options[option_index].hidden || hidden_options.contains(option_index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										73
									
								
								librecomp/src/config_option.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								librecomp/src/config_option.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | ||||||
|  | #include "librecomp/config.hpp" | ||||||
|  | 
 | ||||||
|  | static char make_char_upper(char c) { | ||||||
|  |     if (c >= 'a' && c <= 'z')  { | ||||||
|  |         c -= 'a' - 'A'; | ||||||
|  |     } | ||||||
|  |     return c; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool case_insensitive_compare(const std::string& a, const std::string& b) { | ||||||
|  |     if (a.size() != b.size()) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     for (size_t i = 0; i < a.size(); i++) { | ||||||
|  |         if (make_char_upper(a[i]) != make_char_upper(b[i])) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace recomp::config { | ||||||
|  | 
 | ||||||
|  |     // ConfigOptionEnum
 | ||||||
|  |     std::vector<ConfigOptionEnumOption>::const_iterator ConfigOptionEnum::find_option_from_string(const std::string& option_key) const { | ||||||
|  |         return std::find_if(options.begin(), options.end(), [option_key](const ConfigOptionEnumOption& opt) { | ||||||
|  |             return case_insensitive_compare(opt.key, option_key); | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     std::vector<ConfigOptionEnumOption>::const_iterator ConfigOptionEnum::find_option_from_value(uint32_t value) const { | ||||||
|  |         return std::find_if(options.begin(), options.end(), [value](const ConfigOptionEnumOption& opt) { | ||||||
|  |             return opt.value == value; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool ConfigOptionEnum::can_add_option(const std::string& option_key, uint32_t option_value) const { | ||||||
|  |         return options.size() == 0 || ( | ||||||
|  |             find_option_from_string(option_key) == options.end() && | ||||||
|  |             find_option_from_value(option_value) == options.end()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // ConfigOptionDependency
 | ||||||
|  |     void ConfigOptionDependency::add_option_dependency(size_t dependent_option_index, size_t source_option_index, std::vector<ConfigValueVariant> &values) { | ||||||
|  |         if (!option_to_dependencies.contains(source_option_index)) { | ||||||
|  |             option_to_dependencies[source_option_index] = {}; | ||||||
|  |         } | ||||||
|  |         option_to_dependencies[source_option_index].insert(dependent_option_index); | ||||||
|  |         dependency_to_values[dependent_option_index] = values; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::unordered_map<size_t, bool> ConfigOptionDependency::check_option_dependencies(size_t source_option_index, ConfigValueVariant value) { | ||||||
|  |         std::unordered_map<size_t, bool> result{}; | ||||||
|  |         if (!option_to_dependencies.contains(source_option_index)) { | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         std::unordered_set<size_t> &dependencies = option_to_dependencies[source_option_index]; | ||||||
|  |         for (auto &dep : dependencies) { | ||||||
|  |             bool is_match = false; | ||||||
|  |             for (auto &check_value : dependency_to_values[dep]) { | ||||||
|  |                 if (value == check_value) { | ||||||
|  |                     is_match = true; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             result[dep] = is_match; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| #include "librecomp/addresses.hpp" | #include "librecomp/addresses.hpp" | ||||||
| 
 | 
 | ||||||
| void recomp_get_config_u32(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { | void recomp_get_config_u32(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { | ||||||
|     recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); |     recomp::config::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); | ||||||
|     if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) { |     if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) { | ||||||
|         _return(ctx, *as_u32); |         _return(ctx, *as_u32); | ||||||
|     } |     } | ||||||
|  | @ -19,7 +19,7 @@ void recomp_get_config_u32(uint8_t* rdram, recomp_context* ctx, size_t mod_index | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void recomp_get_config_double(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { | void recomp_get_config_double(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { | ||||||
|     recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); |     recomp::config::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); | ||||||
|     if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) { |     if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) { | ||||||
|         ctx->f0.d = double(*as_u32); |         ctx->f0.d = double(*as_u32); | ||||||
|     } |     } | ||||||
|  | @ -52,7 +52,7 @@ void return_string(uint8_t* rdram, recomp_context* ctx, const StringType& str) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void recomp_get_config_string(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { | void recomp_get_config_string(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { | ||||||
|     recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); |     recomp::config::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); | ||||||
|     if (std::string* as_string = std::get_if<std::string>(&val)) { |     if (std::string* as_string = std::get_if<std::string>(&val)) { | ||||||
|         return_string(rdram, ctx, *as_string); |         return_string(rdram, ctx, *as_string); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -331,17 +331,20 @@ constexpr std::string_view config_schema_precision_key = "precision"; | ||||||
| constexpr std::string_view config_schema_percent_key = "percent"; | constexpr std::string_view config_schema_percent_key = "percent"; | ||||||
| constexpr std::string_view config_schema_options_key = "options"; | constexpr std::string_view config_schema_options_key = "options"; | ||||||
| constexpr std::string_view config_schema_default_key = "default"; | constexpr std::string_view config_schema_default_key = "default"; | ||||||
|  | constexpr std::string_view config_schema_matches_key = "matches"; | ||||||
|  | constexpr std::string_view config_schema_hidden_from_key = "hidden_from"; | ||||||
|  | constexpr std::string_view config_schema_disabled_from_key = "disabled_from"; | ||||||
| 
 | 
 | ||||||
| std::unordered_map<std::string, recomp::mods::ConfigOptionType> config_option_map{ | std::unordered_map<std::string, recomp::config::ConfigOptionType> config_option_map{ | ||||||
|     { "Enum",   recomp::mods::ConfigOptionType::Enum}, |     { "Enum",   recomp::config::ConfigOptionType::Enum}, | ||||||
|     { "Number", recomp::mods::ConfigOptionType::Number}, |     { "Number", recomp::config::ConfigOptionType::Number}, | ||||||
|     { "String", recomp::mods::ConfigOptionType::String}, |     { "String", recomp::config::ConfigOptionType::String}, | ||||||
|     { "Bool",   recomp::mods::ConfigOptionType::Bool}, |     { "Bool",   recomp::config::ConfigOptionType::Bool}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::json &config_schema_json, recomp::mods::ModManifest &ret, std::string &error_param) { | recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::json &config_schema_json, recomp::config::Config *ret, std::string &error_param) { | ||||||
|     using json = nlohmann::json; |     using json = nlohmann::json; | ||||||
|     recomp::mods::ConfigOption option; |     recomp::config::ConfigOption option; | ||||||
|     auto id = config_schema_json.find(config_schema_id_key); |     auto id = config_schema_json.find(config_schema_id_key); | ||||||
|     if (id != config_schema_json.end()) { |     if (id != config_schema_json.end()) { | ||||||
|         if (!get_to<json::string_t>(*id, option.id)) { |         if (!get_to<json::string_t>(*id, option.id)) { | ||||||
|  | @ -398,25 +401,34 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     switch (option.type) { |     switch (option.type) { | ||||||
|     case recomp::mods::ConfigOptionType::Enum: |     case recomp::config::ConfigOptionType::Enum: | ||||||
|         { |         { | ||||||
|             recomp::mods::ConfigOptionEnum option_enum; |             recomp::config::ConfigOptionEnum option_enum; | ||||||
| 
 | 
 | ||||||
|             auto options = config_schema_json.find(config_schema_options_key); |             auto options = config_schema_json.find(config_schema_options_key); | ||||||
|             if (options != config_schema_json.end()) { |             if (options != config_schema_json.end()) { | ||||||
|                 if (!get_to_vec<std::string>(*options, option_enum.options)) { |                 std::vector<std::string> option_key_list; | ||||||
|  |                 if (!get_to_vec<std::string>(*options, option_key_list)) { | ||||||
|                     error_param = config_schema_options_key; |                     error_param = config_schema_options_key; | ||||||
|                     return recomp::mods::ModOpenError::IncorrectConfigSchemaType; |                     return recomp::mods::ModOpenError::IncorrectConfigSchemaType; | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 for (uint32_t i = 0; i < static_cast<uint32_t>(option_key_list.size()); i++) { | ||||||
|  |                     if (!option_enum.can_add_option(option_key_list[i], i)) { | ||||||
|  |                         error_param = config_schema_options_key; | ||||||
|  |                         return recomp::mods::ModOpenError::DuplicateEnumStrings; | ||||||
|  |                     } | ||||||
|  |                     option_enum.options.push_back({i, option_key_list[i]}); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             auto default_value = config_schema_json.find(config_schema_default_key); |             auto default_value = config_schema_json.find(config_schema_default_key); | ||||||
|             if (default_value != config_schema_json.end()) { |             if (default_value != config_schema_json.end()) { | ||||||
|                 std::string default_value_string; |                 std::string default_value_string; | ||||||
|                 if (get_to<json::string_t>(*default_value, default_value_string)) { |                 if (get_to<json::string_t>(*default_value, default_value_string)) { | ||||||
|                     auto it = std::find(option_enum.options.begin(), option_enum.options.end(), default_value_string); |                     auto it = option_enum.find_option_from_string(default_value_string); | ||||||
|                     if (it != option_enum.options.end()) { |                     if (it != option_enum.options.end()) { | ||||||
|                         option_enum.default_value = uint32_t(it - option_enum.options.begin()); |                         option_enum.default_value = it->value; | ||||||
|                     } |                     } | ||||||
|                     else { |                     else { | ||||||
|                         error_param = config_schema_default_key; |                         error_param = config_schema_default_key; | ||||||
|  | @ -433,9 +445,9 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     case recomp::mods::ConfigOptionType::Number: |     case recomp::config::ConfigOptionType::Number: | ||||||
|         { |         { | ||||||
|             recomp::mods::ConfigOptionNumber option_number; |             recomp::config::ConfigOptionNumber option_number; | ||||||
| 
 | 
 | ||||||
|             auto min = config_schema_json.find(config_schema_min_key); |             auto min = config_schema_json.find(config_schema_min_key); | ||||||
|             if (min != config_schema_json.end()) { |             if (min != config_schema_json.end()) { | ||||||
|  | @ -496,9 +508,9 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j | ||||||
|             option.variant = option_number; |             option.variant = option_number; | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     case recomp::mods::ConfigOptionType::String: |     case recomp::config::ConfigOptionType::String: | ||||||
|         { |         { | ||||||
|             recomp::mods::ConfigOptionString option_string; |             recomp::config::ConfigOptionString option_string; | ||||||
| 
 | 
 | ||||||
|             auto default_value = config_schema_json.find(config_schema_default_key); |             auto default_value = config_schema_json.find(config_schema_default_key); | ||||||
|             if (default_value != config_schema_json.end()) { |             if (default_value != config_schema_json.end()) { | ||||||
|  | @ -511,9 +523,9 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j | ||||||
|             option.variant = option_string; |             option.variant = option_string; | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     case recomp::mods::ConfigOptionType::Bool: |     case recomp::config::ConfigOptionType::Bool: | ||||||
|         { |         { | ||||||
|             recomp::mods::ConfigOptionBool option_bool; |             recomp::config::ConfigOptionBool option_bool; | ||||||
| 
 | 
 | ||||||
|             auto default_value = config_schema_json.find(config_schema_default_key); |             auto default_value = config_schema_json.find(config_schema_default_key); | ||||||
|             if (default_value != config_schema_json.end()) { |             if (default_value != config_schema_json.end()) { | ||||||
|  | @ -530,13 +542,129 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ret.config_schema.options_by_id.emplace(option.id, ret.config_schema.options.size()); |     if (ret != nullptr) { | ||||||
|     ret.config_schema.options.emplace_back(option); |         ret->add_option(option); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     return recomp::mods::ModOpenError::Good; |     return recomp::mods::ModOpenError::Good; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const std::vector<char>& manifest_data, std::string& error_param) { | std::vector<recomp::config::ConfigValueVariant> parse_config_option_dependency_matches(const nlohmann::json &matches_object, const recomp::config::ConfigOption &source_option) { | ||||||
|  |     std::vector<recomp::config::ConfigValueVariant> matches = {}; | ||||||
|  | 
 | ||||||
|  |     switch (source_option.type) { | ||||||
|  |         case recomp::config::ConfigOptionType::None: | ||||||
|  |         default: | ||||||
|  |             //! ERROR: Source option type is invalid
 | ||||||
|  |             return matches; | ||||||
|  |         case recomp::config::ConfigOptionType::Enum: { | ||||||
|  |             std::vector<std::string> enum_values = {}; | ||||||
|  |             if (!get_to_vec<std::string>(matches_object, enum_values)) { | ||||||
|  |                 //! ERROR: failed to get array of strings in matches
 | ||||||
|  |                 return matches; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             auto &option_enum = get<recomp::config::ConfigOptionEnum>(source_option.variant); | ||||||
|  |             for (auto &value_str : enum_values) { | ||||||
|  |                 auto it = option_enum.find_option_from_string(value_str); | ||||||
|  |                 if (it == option_enum.options.end()) { | ||||||
|  |                     //! ERROR: one of the matches specified doesn't exist in the source enum
 | ||||||
|  |                     return matches; | ||||||
|  |                 } | ||||||
|  |                 matches.push_back(it->value); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case recomp::config::ConfigOptionType::String: | ||||||
|  |             //! ERROR: string dependencies are unsupported 
 | ||||||
|  |             return matches; | ||||||
|  |         case recomp::config::ConfigOptionType::Number: | ||||||
|  |             //! ERROR: numerical dependencies are unsupported 
 | ||||||
|  |             return matches; | ||||||
|  |         case recomp::config::ConfigOptionType::Bool: { | ||||||
|  |             bool bool_condition; | ||||||
|  |             if (!get_to<nlohmann::json::boolean_t>(matches_object, bool_condition)) { | ||||||
|  |                 //! ERROR: Failed to get boolean from dependency matches
 | ||||||
|  |                 return matches; | ||||||
|  |             } | ||||||
|  |             matches.push_back(bool_condition); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return matches; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | recomp::mods::ModOpenError parse_config_option_dependencies(const nlohmann::json &config_schema_json, recomp::config::Config &config) { | ||||||
|  |     using json = nlohmann::json; | ||||||
|  | 
 | ||||||
|  |     auto id = config_schema_json.find(config_schema_id_key); | ||||||
|  |     std::string option_id; | ||||||
|  |     if (id != config_schema_json.end()) { | ||||||
|  |         if (!get_to<json::string_t>(*id, option_id)) { | ||||||
|  |             return recomp::mods::ModOpenError::Good; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     recomp::config::ConfigSchema schema = config.get_config_schema(); | ||||||
|  | 
 | ||||||
|  |     size_t option_index = schema.options_by_id[option_id]; | ||||||
|  |     recomp::config::ConfigOption &option = schema.options[option_index]; | ||||||
|  | 
 | ||||||
|  |     auto conditional_add_dependency = [config_schema_json, schema, option, option_index](recomp::config::ConfigOptionDependency &dependency, const std::string_view &dep_key) -> bool { | ||||||
|  |         auto find_it = config_schema_json.find(dep_key); | ||||||
|  |         if (find_it == config_schema_json.end()) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         auto& dependency_json = *find_it; | ||||||
|  |         if (!dependency_json.is_object()) { | ||||||
|  |             //! ERROR: Dependency malformed
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         std::string source_dependency_id; | ||||||
|  |         auto find_dep_id = dependency_json.find(config_schema_id_key); | ||||||
|  |         if (find_dep_id == dependency_json.end()) { | ||||||
|  |             //! ERROR: Could not find source dependency id
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!get_to<json::string_t>(*find_dep_id, source_dependency_id)) { | ||||||
|  |             //! ERROR: Failed to get source dependency id
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         if (schema.options_by_id.contains(source_dependency_id) == false) { | ||||||
|  |             //! ERROR: Failed to find specified source dependency in schema
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         size_t source_dependency_index = schema.options_by_id.at(source_dependency_id); | ||||||
|  |         auto &source_option = schema.options.at(source_dependency_index); | ||||||
|  | 
 | ||||||
|  |         auto find_matches = dependency_json.find(config_schema_matches_key); | ||||||
|  |         if (find_matches == dependency_json.end()) { | ||||||
|  |             //! ERROR: Failed to find matches
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         auto matches = parse_config_option_dependency_matches(*find_matches, source_option); | ||||||
|  |         if (matches.empty()) { | ||||||
|  |             //! ERROR: Could not find valid data in matches
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         dependency.add_option_dependency(option_index, source_dependency_index, matches); | ||||||
|  |         return true; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     bool disable_success = conditional_add_dependency(schema.disable_dependencies, config_schema_disabled_from_key); | ||||||
|  |     if (!disable_success) return recomp::mods::ModOpenError::InvalidDisableOptionDependency; | ||||||
|  |     bool hidden_success = conditional_add_dependency(schema.hidden_dependencies, config_schema_hidden_from_key); | ||||||
|  |     if (!hidden_success) return recomp::mods::ModOpenError::InvalidHiddenOptionDependency; | ||||||
|  | 
 | ||||||
|  |     return recomp::mods::ModOpenError::Good; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const std::vector<char>& manifest_data, std::string& error_param, recomp::config::Config *config) { | ||||||
|     using json = nlohmann::json; |     using json = nlohmann::json; | ||||||
|     json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false); |     json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false); | ||||||
| 
 | 
 | ||||||
|  | @ -588,6 +716,11 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const | ||||||
|         return current_error; |         return current_error; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (config != nullptr) { | ||||||
|  |         config->set_id(ret.mod_id); | ||||||
|  |         config->set_mod_version(ret.version.to_string()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // Authors
 |     // Authors
 | ||||||
|     current_error = try_get_vec<json::string_t>(ret.authors, manifest_json, authors_key, true, error_param); |     current_error = try_get_vec<json::string_t>(ret.authors, manifest_json, authors_key, true, error_param); | ||||||
|     if (current_error != ModOpenError::Good) { |     if (current_error != ModOpenError::Good) { | ||||||
|  | @ -660,11 +793,23 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             for (const json &option : *options) { |             for (const json &option : *options) { | ||||||
|                 ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param); |                 ModOpenError open_error = parse_manifest_config_schema_option(option, config, error_param); | ||||||
|                 if (open_error != ModOpenError::Good) { |                 if (open_error != ModOpenError::Good) { | ||||||
|                     return open_error; |                     return open_error; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             // Parse option dependencies after all options have been added
 | ||||||
|  |             // Requires config to not be null
 | ||||||
|  |             if (config != nullptr) { | ||||||
|  |                 for (const json &option : *options) { | ||||||
|  |                     ModOpenError dep_error = parse_config_option_dependencies(option, *config); | ||||||
|  |                     if (dep_error != ModOpenError::Good) { | ||||||
|  |                         error_param = dep_error == ModOpenError::InvalidDisableOptionDependency ? config_schema_disabled_from_key : config_schema_hidden_from_key; | ||||||
|  |                         return dep_error; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             error_param = config_schema_options_key; |             error_param = config_schema_options_key; | ||||||
|  | @ -675,93 +820,44 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const | ||||||
|     return ModOpenError::Good; |     return ModOpenError::Good; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool parse_mod_config_storage(const std::filesystem::path &path, const std::string &expected_mod_id, recomp::mods::ConfigStorage &config_storage, const recomp::mods::ConfigSchema &config_schema) { | bool parse_mod_config_storage(const std::string &expected_mod_id, recomp::config::Config &config) { | ||||||
|     using json = nlohmann::json; |     return config.load_config([expected_mod_id](nlohmann::json &config_json) { | ||||||
|     json config_json; |         auto mod_id = config_json.find("mod_id"); | ||||||
|     if (!read_json_with_backups(path, config_json)) { |         if (mod_id != config_json.end()) { | ||||||
|         return false; |             std::string mod_id_str; | ||||||
|     } |             if (get_to<nlohmann::json::string_t>(*mod_id, mod_id_str)) { | ||||||
| 
 |                 if (*mod_id != expected_mod_id) { | ||||||
|     auto mod_id = config_json.find("mod_id"); |                     // The mod's ID doesn't match.
 | ||||||
|     if (mod_id != config_json.end()) { |                     return false; | ||||||
|         std::string mod_id_str; |                 } | ||||||
|         if (get_to<json::string_t>(*mod_id, mod_id_str)) { |             } | ||||||
|             if (*mod_id != expected_mod_id) { |             else { | ||||||
|                 // The mod's ID doesn't match.
 |                 // The mod ID is not a string.
 | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             // The mod ID is not a string.
 |             // The configuration file doesn't have a mod ID.
 | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|     } |      | ||||||
|     else { |         auto storage_json = config_json.find("storage"); | ||||||
|         // The configuration file doesn't have a mod ID.
 |         if (storage_json == config_json.end()) { | ||||||
|         return false; |             // The configuration file doesn't have a storage object.
 | ||||||
|     } |             return false; | ||||||
| 
 |         } | ||||||
|     auto storage_json = config_json.find("storage"); |      | ||||||
|     if (storage_json == config_json.end()) { |         if (!storage_json->is_object()) { | ||||||
|         // The configuration file doesn't have a storage object.
 |             // The storage key does not correspond to an object.
 | ||||||
|         return false; |             return false; | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!storage_json->is_object()) { |  | ||||||
|         // The storage key does not correspond to an object.
 |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Only parse the object for known option types based on the schema.
 |  | ||||||
|     std::string value_str; |  | ||||||
|     for (const recomp::mods::ConfigOption &option : config_schema.options) { |  | ||||||
|         auto option_json = storage_json->find(option.id); |  | ||||||
|         if (option_json == storage_json->end()) { |  | ||||||
|             // Option doesn't exist in storage.
 |  | ||||||
|             continue; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         switch (option.type) { |         return true; | ||||||
|         case recomp::mods::ConfigOptionType::Enum: |     }); | ||||||
|             if (get_to<json::string_t>(*option_json, value_str)) { |  | ||||||
|                 const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant); |  | ||||||
|                 auto option_it = std::find(option_enum.options.begin(), option_enum.options.end(), value_str); |  | ||||||
|                 if (option_it != option_enum.options.end()) { |  | ||||||
|                     config_storage.value_map[option.id] = uint32_t(option_it - option_enum.options.begin()); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         case recomp::mods::ConfigOptionType::Number: |  | ||||||
|             if (option_json->is_number()) { |  | ||||||
|                 config_storage.value_map[option.id] = option_json->template get<double>(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         case recomp::mods::ConfigOptionType::String: { |  | ||||||
|             if (get_to<json::string_t>(*option_json, value_str)) { |  | ||||||
|                 config_storage.value_map[option.id] = value_str; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         case recomp::mods::ConfigOptionType::Bool: { |  | ||||||
|             if (option_json->is_boolean()) { |  | ||||||
|                 config_storage.value_map[option.id] = option_json->get<bool>(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         default: |  | ||||||
|             assert(false && "Unknown option type."); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModManifest& manifest, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest) { | recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModManifest& manifest, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest) { | ||||||
|  |     recomp::config::Config mod_config; | ||||||
|     { |     { | ||||||
|         bool exists; |         bool exists; | ||||||
|         std::vector<char> manifest_data = manifest.file_handle->read_file("mod.json", exists); |         std::vector<char> manifest_data = manifest.file_handle->read_file("mod.json", exists); | ||||||
|  | @ -799,7 +895,7 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModM | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param); |             ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param, &mod_config); | ||||||
|             if (parse_error != ModOpenError::Good) { |             if (parse_error != ModOpenError::Good) { | ||||||
|                 return parse_error; |                 return parse_error; | ||||||
|             } |             } | ||||||
|  | @ -848,9 +944,7 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModM | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Read the mod config if it exists.
 |     // Read the mod config if it exists.
 | ||||||
|     ConfigStorage config_storage; |     parse_mod_config_storage(manifest.mod_id, mod_config); | ||||||
|     std::filesystem::path config_path = mod_config_directory / (manifest.mod_id + ".json"); |  | ||||||
|     parse_mod_config_storage(config_path, manifest.mod_id, config_storage, manifest.config_schema); |  | ||||||
| 
 | 
 | ||||||
|     // Read the mod thumbnail if it exists.
 |     // Read the mod thumbnail if it exists.
 | ||||||
|     static const std::string thumbnail_dds_name = "thumb.dds"; |     static const std::string thumbnail_dds_name = "thumb.dds"; | ||||||
|  | @ -862,7 +956,7 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModM | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Store the loaded mod manifest in a new mod handle.
 |     // Store the loaded mod manifest in a new mod handle.
 | ||||||
|     add_opened_mod(std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail_data)); |     add_opened_mod(std::move(manifest), std::move(mod_config), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail_data)); | ||||||
| 
 | 
 | ||||||
|     return ModOpenError::Good; |     return ModOpenError::Good; | ||||||
| } | } | ||||||
|  | @ -957,6 +1051,8 @@ std::string recomp::mods::error_to_string(ModOpenError error) { | ||||||
|             return "Duplicate mod found"; |             return "Duplicate mod found"; | ||||||
|         case ModOpenError::WrongGame: |         case ModOpenError::WrongGame: | ||||||
|             return "Mod is for a different game"; |             return "Mod is for a different game"; | ||||||
|  |         case ModOpenError::DuplicateEnumStrings: | ||||||
|  |             return "Duplicate enum strings found in mod.json (enum strings are case insensitive)"; | ||||||
|     } |     } | ||||||
|     return "Unknown mod opening error: " + std::to_string((int)error); |     return "Unknown mod opening error: " + std::to_string((int)error); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -266,9 +266,9 @@ recomp::mods::CodeModLoadError recomp::mods::validate_api_version(uint32_t api_v | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| recomp::mods::ModHandle::ModHandle(const ModContext& context, ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail) : | recomp::mods::ModHandle::ModHandle(const ModContext& context, ModManifest&& manifest, recomp::config::Config&& config, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail) : | ||||||
|     manifest(std::move(manifest)), |     manifest(std::move(manifest)), | ||||||
|     config_storage(std::move(config_storage)), |     config(std::move(config)), | ||||||
|     code_handle(), |     code_handle(), | ||||||
|     recompiler_context{std::make_unique<N64Recomp::Context>()}, |     recompiler_context{std::make_unique<N64Recomp::Context>()}, | ||||||
|     content_types{std::move(content_types)}, |     content_types{std::move(content_types)}, | ||||||
|  | @ -595,12 +595,12 @@ void unpatch_func(void* target_func, const recomp::mods::PatchData& data) { | ||||||
|     protect(target_func, old_flags); |     protect(target_func, old_flags); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail) { | void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, config::Config&& config, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail) { | ||||||
|     std::unique_lock lock(opened_mods_mutex); |     std::unique_lock lock(opened_mods_mutex); | ||||||
|     size_t mod_index = opened_mods.size(); |     size_t mod_index = opened_mods.size(); | ||||||
|     opened_mods_by_id.emplace(manifest.mod_id, mod_index); |     opened_mods_by_id.emplace(manifest.mod_id, mod_index); | ||||||
|     opened_mods_by_filename.emplace(manifest.mod_root_path.filename().native(), mod_index); |     opened_mods_by_filename.emplace(manifest.mod_root_path.filename().native(), mod_index); | ||||||
|     opened_mods.emplace_back(*this, std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail)); |     opened_mods.emplace_back(*this, std::move(manifest), std::move(config), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail)); | ||||||
|     opened_mods_order.emplace_back(mod_index); |     opened_mods_order.emplace_back(mod_index); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -643,49 +643,14 @@ void recomp::mods::ModContext::close_mods() { | ||||||
|     auto_enabled_mods.clear(); |     auto_enabled_mods.clear(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool save_mod_config_storage(const std::filesystem::path &path, const std::string &mod_id, const recomp::Version &mod_version, const recomp::mods::ConfigStorage &config_storage, const recomp::mods::ConfigSchema &config_schema) { | bool save_mod_config_storage(const std::string &mod_id, const recomp::Version &mod_version, const recomp::config::Config *config, nlohmann::json &storage_json) { | ||||||
|     using json = nlohmann::json; |     using json = nlohmann::json; | ||||||
|     json config_json; |     json config_json; | ||||||
|     config_json["mod_id"] = mod_id; |     config_json["mod_id"] = mod_id; | ||||||
|     config_json["mod_version"] = mod_version.to_string(); |     config_json["mod_version"] = mod_version.to_string(); | ||||||
|     config_json["recomp_version"] = recomp::get_project_version().to_string(); |     config_json["recomp_version"] = recomp::get_project_version().to_string(); | ||||||
| 
 |     config_json["storage"] = storage_json; | ||||||
|     json &storage_json = config_json["storage"]; |     return config->save_config_json(config_json); | ||||||
|     for (auto it : config_storage.value_map) { |  | ||||||
|         auto id_it = config_schema.options_by_id.find(it.first); |  | ||||||
|         if (id_it == config_schema.options_by_id.end()) { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const recomp::mods::ConfigOption &config_option = config_schema.options[id_it->second]; |  | ||||||
|         switch (config_option.type) { |  | ||||||
|         case recomp::mods::ConfigOptionType::Enum: |  | ||||||
|             storage_json[it.first] = std::get<recomp::mods::ConfigOptionEnum>(config_option.variant).options[std::get<uint32_t>(it.second)]; |  | ||||||
|             break; |  | ||||||
|         case recomp::mods::ConfigOptionType::Number: |  | ||||||
|             storage_json[it.first] = std::get<double>(it.second); |  | ||||||
|             break; |  | ||||||
|         case recomp::mods::ConfigOptionType::String: |  | ||||||
|             storage_json[it.first] = std::get<std::string>(it.second); |  | ||||||
|             break; |  | ||||||
|         case recomp::mods::ConfigOptionType::Bool: |  | ||||||
|             storage_json[it.first] = std::get<bool>(it.second); |  | ||||||
|             break; |  | ||||||
|         default: |  | ||||||
|             assert(false && "Unknown config type."); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::ofstream output_file = recomp::open_output_file_with_backup(path); |  | ||||||
|     if (!output_file.good()) { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     output_file << std::setw(4) << config_json; |  | ||||||
|     output_file.close(); |  | ||||||
| 
 |  | ||||||
|     return recomp::finalize_output_file_with_backup(path); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool parse_mods_config(const std::filesystem::path &path, std::unordered_set<std::string> &enabled_mods, std::vector<std::string> &mod_order) { | bool parse_mods_config(const std::filesystem::path &path, std::unordered_set<std::string> &enabled_mods, std::vector<std::string> &mod_order) { | ||||||
|  | @ -731,12 +696,13 @@ bool save_mods_config(const std::filesystem::path &path, const std::unordered_se | ||||||
| 
 | 
 | ||||||
| void recomp::mods::ModContext::dirty_mod_configuration_thread_process() { | void recomp::mods::ModContext::dirty_mod_configuration_thread_process() { | ||||||
|     using namespace std::chrono_literals; |     using namespace std::chrono_literals; | ||||||
|  |     using json = nlohmann::json; | ||||||
|  | 
 | ||||||
|     ModConfigQueueVariant variant; |     ModConfigQueueVariant variant; | ||||||
|     ModConfigQueueSaveMod save_mod; |     ModConfigQueueSaveMod save_mod; | ||||||
|     std::unordered_set<std::string> pending_mods; |     std::unordered_set<std::string> pending_mods; | ||||||
|     std::unordered_map<std::string, ConfigStorage> pending_mod_storage; |     std::unordered_map<std::string, const recomp::config::Config*> pending_mod_configs; | ||||||
|     std::unordered_map<std::string, ConfigSchema> pending_mod_schema; |     std::unordered_map<std::string, json> pending_mod_storage_json; | ||||||
|     std::unordered_map<std::string, Version> pending_mod_version; |  | ||||||
|     std::unordered_set<std::string> config_enabled_mods; |     std::unordered_set<std::string> config_enabled_mods; | ||||||
|     std::vector<std::string> config_mod_order; |     std::vector<std::string> config_mod_order; | ||||||
|     bool pending_config_save = false; |     bool pending_config_save = false; | ||||||
|  | @ -773,16 +739,14 @@ void recomp::mods::ModContext::dirty_mod_configuration_thread_process() { | ||||||
|                     if (it != opened_mods_by_id.end()) { |                     if (it != opened_mods_by_id.end()) { | ||||||
|                         const ModHandle &mod = opened_mods[it->second]; |                         const ModHandle &mod = opened_mods[it->second]; | ||||||
|                         std::unique_lock config_storage_lock(mod_config_storage_mutex); |                         std::unique_lock config_storage_lock(mod_config_storage_mutex); | ||||||
|                         pending_mod_storage[id] = mod.config_storage; |                         pending_mod_configs[id] = &mod.config; | ||||||
|                         pending_mod_schema[id] = mod.manifest.config_schema; |                         pending_mod_storage_json[id] = mod.config.get_json_config(); | ||||||
|                         pending_mod_version[id] = mod.manifest.version; |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             for (const std::string &id : pending_mods) { |             for (const std::string &id : pending_mods) { | ||||||
|                 config_path = mod_config_directory / std::string(id + ".json"); |                 pending_mod_configs[id]->save_config_json(pending_mod_storage_json[id]); | ||||||
|                 save_mod_config_storage(config_path, id, pending_mod_version[id], pending_mod_storage[id], pending_mod_schema[id]); |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             pending_mods.clear(); |             pending_mods.clear(); | ||||||
|  | @ -1360,7 +1324,7 @@ void recomp::mods::ModContext::set_mod_index(const std::string &mod_game_id, con | ||||||
|     mod_configuration_thread_queue.enqueue(ModConfigQueueSave()); |     mod_configuration_thread_queue.enqueue(ModConfigQueueSave()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const recomp::mods::ConfigSchema &recomp::mods::ModContext::get_mod_config_schema(const std::string &mod_id) const { | const recomp::config::ConfigSchema &recomp::mods::ModContext::get_mod_config_schema(const std::string &mod_id) const { | ||||||
|     // Check that the mod exists.
 |     // Check that the mod exists.
 | ||||||
|     auto find_it = opened_mods_by_id.find(mod_id); |     auto find_it = opened_mods_by_id.find(mod_id); | ||||||
|     if (find_it == opened_mods_by_id.end()) { |     if (find_it == opened_mods_by_id.end()) { | ||||||
|  | @ -1368,7 +1332,7 @@ const recomp::mods::ConfigSchema &recomp::mods::ModContext::get_mod_config_schem | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const ModHandle &mod = opened_mods[find_it->second]; |     const ModHandle &mod = opened_mods[find_it->second]; | ||||||
|     return mod.manifest.config_schema; |     return mod.config.get_config_schema(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const std::vector<char> &recomp::mods::ModContext::get_mod_thumbnail(const std::string &mod_id) const { | const std::vector<char> &recomp::mods::ModContext::get_mod_thumbnail(const std::string &mod_id) const { | ||||||
|  | @ -1382,7 +1346,7 @@ const std::vector<char> &recomp::mods::ModContext::get_mod_thumbnail(const std:: | ||||||
|     return mod.thumbnail; |     return mod.thumbnail; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void recomp::mods::ModContext::set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value) { | void recomp::mods::ModContext::set_mod_config_value(size_t mod_index, const std::string &option_id, const recomp::config::ConfigValueVariant &value) { | ||||||
|     // Check that the mod exists.
 |     // Check that the mod exists.
 | ||||||
|     if (mod_index >= opened_mods.size()) { |     if (mod_index >= opened_mods.size()) { | ||||||
|         return; |         return; | ||||||
|  | @ -1390,48 +1354,13 @@ void recomp::mods::ModContext::set_mod_config_value(size_t mod_index, const std: | ||||||
| 
 | 
 | ||||||
|     ModHandle &mod = opened_mods[mod_index]; |     ModHandle &mod = opened_mods[mod_index]; | ||||||
|     std::unique_lock lock(mod_config_storage_mutex); |     std::unique_lock lock(mod_config_storage_mutex); | ||||||
|     auto option_by_id_it = mod.manifest.config_schema.options_by_id.find(option_id); |     mod.config.set_option_value(option_id, value); | ||||||
|     if (option_by_id_it != mod.manifest.config_schema.options_by_id.end()) { |  | ||||||
|         // Only accept setting values if the value exists and the variant is the right type.
 |  | ||||||
|         const ConfigOption &option = mod.manifest.config_schema.options[option_by_id_it->second]; |  | ||||||
|         switch (option.type) { |  | ||||||
|         case ConfigOptionType::Enum: |  | ||||||
|             if (std::holds_alternative<uint32_t>(value)) { |  | ||||||
|                 if (std::get<uint32_t>(value) < std::get<ConfigOptionEnum>(option.variant).options.size()) { |  | ||||||
|                     mod.config_storage.value_map[option_id] = value; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         case ConfigOptionType::Number: |  | ||||||
|             if (std::holds_alternative<double>(value)) { |  | ||||||
|                 mod.config_storage.value_map[option_id] = value; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         case ConfigOptionType::String: |  | ||||||
|             if (std::holds_alternative<std::string>(value)) { |  | ||||||
|                 mod.config_storage.value_map[option_id] = value; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         case ConfigOptionType::Bool: |  | ||||||
|             if (std::holds_alternative<bool>(value)) { |  | ||||||
|                 mod.config_storage.value_map[option_id] = value; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         default: |  | ||||||
|             assert(false && "Unknown config option type."); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // Notify the asynchronous thread it should save the configuration for this mod.
 |     // Notify the asynchronous thread it should save the configuration for this mod.
 | ||||||
|     mod_configuration_thread_queue.enqueue(ModConfigQueueSaveMod{ mod.manifest.mod_id }); |     mod_configuration_thread_queue.enqueue(ModConfigQueueSaveMod{ mod.manifest.mod_id }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void recomp::mods::ModContext::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value) { | void recomp::mods::ModContext::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const recomp::config::ConfigValueVariant &value) { | ||||||
|     // Check that the mod exists.
 |     // Check that the mod exists.
 | ||||||
|     auto find_it = opened_mods_by_id.find(mod_id); |     auto find_it = opened_mods_by_id.find(mod_id); | ||||||
|     if (find_it == opened_mods_by_id.end()) { |     if (find_it == opened_mods_by_id.end()) { | ||||||
|  | @ -1441,7 +1370,7 @@ void recomp::mods::ModContext::set_mod_config_value(const std::string &mod_id, c | ||||||
|     set_mod_config_value(find_it->second, option_id, value); |     set_mod_config_value(find_it->second, option_id, value); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(size_t mod_index, const std::string &option_id) const { | recomp::config::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(size_t mod_index, const std::string &option_id) const { | ||||||
|     // Check that the mod exists.
 |     // Check that the mod exists.
 | ||||||
|     if (mod_index >= opened_mods.size()) { |     if (mod_index >= opened_mods.size()) { | ||||||
|         return std::monostate(); |         return std::monostate(); | ||||||
|  | @ -1449,35 +1378,10 @@ recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value( | ||||||
| 
 | 
 | ||||||
|     const ModHandle &mod = opened_mods[mod_index]; |     const ModHandle &mod = opened_mods[mod_index]; | ||||||
|     std::unique_lock lock(mod_config_storage_mutex); |     std::unique_lock lock(mod_config_storage_mutex); | ||||||
|     auto it = mod.config_storage.value_map.find(option_id); |     return mod.config.get_option_value(option_id); | ||||||
|     if (it != mod.config_storage.value_map.end()) { |  | ||||||
|         return it->second; |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         // Attempt to see if we can find a default value from the schema.
 |  | ||||||
|         auto option_by_id_it = mod.manifest.config_schema.options_by_id.find(option_id); |  | ||||||
|         if (option_by_id_it == mod.manifest.config_schema.options_by_id.end()) { |  | ||||||
|             return std::monostate(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const ConfigOption &option = mod.manifest.config_schema.options[option_by_id_it->second]; |  | ||||||
|         switch (option.type) { |  | ||||||
|         case ConfigOptionType::Enum: |  | ||||||
|             return std::get<ConfigOptionEnum>(option.variant).default_value; |  | ||||||
|         case ConfigOptionType::Number: |  | ||||||
|             return std::get<ConfigOptionNumber>(option.variant).default_value; |  | ||||||
|         case ConfigOptionType::String: |  | ||||||
|             return std::get<ConfigOptionString>(option.variant).default_value; |  | ||||||
|         case ConfigOptionType::Bool: |  | ||||||
|             return std::get<ConfigOptionBool>(option.variant).default_value; |  | ||||||
|         default: |  | ||||||
|             assert(false && "Unknown config option type."); |  | ||||||
|             return std::monostate(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(const std::string &mod_id, const std::string &option_id) const { | recomp::config::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(const std::string &mod_id, const std::string &option_id) const { | ||||||
|     // Check that the mod exists.
 |     // Check that the mod exists.
 | ||||||
|     auto find_it = opened_mods_by_id.find(mod_id); |     auto find_it = opened_mods_by_id.find(mod_id); | ||||||
|     if (find_it == opened_mods_by_id.end()) { |     if (find_it == opened_mods_by_id.end()) { | ||||||
|  |  | ||||||
|  | @ -68,6 +68,10 @@ void recomp::register_config_path(std::filesystem::path path) { | ||||||
|     config_path = path; |     config_path = path; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::filesystem::path recomp::get_config_path() { | ||||||
|  |     return config_path; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool recomp::register_game(const recomp::GameEntry& entry) { | bool recomp::register_game(const recomp::GameEntry& entry) { | ||||||
|     // TODO verify that there's no game with this ID already.
 |     // TODO verify that there's no game with this ID already.
 | ||||||
|     { |     { | ||||||
|  | @ -540,7 +544,7 @@ bool recomp::mods::is_mod_auto_enabled(const std::string& mod_id) { | ||||||
|     return mod_context->is_mod_auto_enabled(mod_id); |     return mod_context->is_mod_auto_enabled(mod_id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const recomp::mods::ConfigSchema &recomp::mods::get_mod_config_schema(const std::string &mod_id) { | const recomp::config::ConfigSchema &recomp::mods::get_mod_config_schema(const std::string &mod_id) { | ||||||
|     std::lock_guard lock{ mod_context_mutex }; |     std::lock_guard lock{ mod_context_mutex }; | ||||||
|     return mod_context->get_mod_config_schema(mod_id); |     return mod_context->get_mod_config_schema(mod_id); | ||||||
| } | } | ||||||
|  | @ -550,22 +554,22 @@ const std::vector<char> &recomp::mods::get_mod_thumbnail(const std::string &mod_ | ||||||
|     return mod_context->get_mod_thumbnail(mod_id); |     return mod_context->get_mod_thumbnail(mod_id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void recomp::mods::set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value) { | void recomp::mods::set_mod_config_value(size_t mod_index, const std::string &option_id, const recomp::config::ConfigValueVariant &value) { | ||||||
|     std::lock_guard lock{ mod_context_mutex }; |     std::lock_guard lock{ mod_context_mutex }; | ||||||
|     return mod_context->set_mod_config_value(mod_index, option_id, value); |     return mod_context->set_mod_config_value(mod_index, option_id, value); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void recomp::mods::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value) { | void recomp::mods::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const recomp::config::ConfigValueVariant &value) { | ||||||
|     std::lock_guard lock{ mod_context_mutex }; |     std::lock_guard lock{ mod_context_mutex }; | ||||||
|     return mod_context->set_mod_config_value(mod_id, option_id, value); |     return mod_context->set_mod_config_value(mod_id, option_id, value); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| recomp::mods::ConfigValueVariant recomp::mods::get_mod_config_value(size_t mod_index, const std::string &option_id) { | recomp::config::ConfigValueVariant recomp::mods::get_mod_config_value(size_t mod_index, const std::string &option_id) { | ||||||
|     std::lock_guard lock{ mod_context_mutex }; |     std::lock_guard lock{ mod_context_mutex }; | ||||||
|     return mod_context->get_mod_config_value(mod_index, option_id); |     return mod_context->get_mod_config_value(mod_index, option_id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| recomp::mods::ConfigValueVariant recomp::mods::get_mod_config_value(const std::string &mod_id, const std::string &option_id) { | recomp::config::ConfigValueVariant recomp::mods::get_mod_config_value(const std::string &mod_id, const std::string &option_id) { | ||||||
|     std::lock_guard lock{ mod_context_mutex }; |     std::lock_guard lock{ mod_context_mutex }; | ||||||
|     return mod_context->get_mod_config_value(mod_id, option_id); |     return mod_context->get_mod_config_value(mod_id, option_id); | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 thecozies
						thecozies