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 | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/src/ai.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/eep.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 | ||||
|     }; | ||||
|     void register_config_path(std::filesystem::path path); | ||||
|     std::filesystem::path get_config_path(); | ||||
|     bool register_game(const recomp::GameEntry& entry); | ||||
|     void check_all_stored_roms(); | ||||
|     bool load_stored_rom(std::u8string& game_id); | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ | |||
| #include "librecomp/game.hpp" | ||||
| #include "librecomp/sections.h" | ||||
| #include "librecomp/overlays.hpp" | ||||
| #include "librecomp/config.hpp" | ||||
| 
 | ||||
| namespace N64Recomp { | ||||
|     class Context; | ||||
|  | @ -81,7 +82,10 @@ namespace recomp { | |||
|             InvalidDependencyString, | ||||
|             MissingManifestField, | ||||
|             DuplicateMod, | ||||
|             WrongGame | ||||
|             WrongGame, | ||||
|             InvalidDisableOptionDependency, | ||||
|             InvalidHiddenOptionDependency, | ||||
|             DuplicateEnumStrings, | ||||
|         }; | ||||
| 
 | ||||
|         std::string error_to_string(ModOpenError); | ||||
|  | @ -126,14 +130,6 @@ namespace recomp { | |||
| 
 | ||||
|         std::string error_to_string(CodeModLoadError); | ||||
| 
 | ||||
|         enum class ConfigOptionType { | ||||
|             None, | ||||
|             Enum, | ||||
|             Number, | ||||
|             String, | ||||
|             Bool | ||||
|         }; | ||||
| 
 | ||||
|         struct ModFileHandle { | ||||
|             virtual ~ModFileHandle() = default; | ||||
|             virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0; | ||||
|  | @ -174,49 +170,6 @@ namespace recomp { | |||
|             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 { | ||||
|             std::string mod_id; | ||||
|             std::string display_name; | ||||
|  | @ -240,7 +193,6 @@ namespace recomp { | |||
|             std::vector<std::string> authors; | ||||
|             std::vector<Dependency> dependencies; | ||||
|             std::unordered_map<std::string, size_t> dependencies_by_id; | ||||
|             ConfigSchema config_schema; | ||||
|             Version minimum_recomp_version; | ||||
|             Version version; | ||||
|             bool runtime_toggleable; | ||||
|  | @ -355,12 +307,12 @@ namespace recomp { | |||
|             recomp::Version get_mod_version(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); | ||||
|             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; | ||||
|             void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value); | ||||
|             void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value); | ||||
|             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; | ||||
|             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 config::ConfigValueVariant &value); | ||||
|             config::ConfigValueVariant get_mod_config_value(size_t mod_index, 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_mod_config_directory(const std::filesystem::path &path); | ||||
|             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 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); | ||||
|             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( | ||||
|                 const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks, | ||||
|                 const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, | ||||
|  | @ -418,7 +370,7 @@ namespace recomp { | |||
|             std::vector<bool> processed_hook_slots; | ||||
|             // Generated shim functions to use for implementing shim exports.
 | ||||
|             std::vector<std::unique_ptr<N64Recomp::ShimFunction>> shim_functions; | ||||
|             ConfigSchema empty_schema; | ||||
|             config::ConfigSchema empty_schema; | ||||
|             std::vector<char> empty_bytes; | ||||
|             size_t num_events = 0; | ||||
|             ModContentTypeId code_content_type_id; | ||||
|  | @ -442,7 +394,7 @@ namespace recomp { | |||
|         public: | ||||
|             // TODO make these private and expose methods for the functionality they're currently used in.
 | ||||
|             ModManifest manifest; | ||||
|             ConfigStorage config_storage; | ||||
|             config::Config config; | ||||
|             std::unique_ptr<ModCodeHandle> code_handle; | ||||
|             std::unique_ptr<N64Recomp::Context> recompiler_context; | ||||
|             std::vector<uint32_t> section_load_addresses; | ||||
|  | @ -450,7 +402,7 @@ namespace recomp { | |||
|             std::vector<ModContentTypeId> content_types; | ||||
|             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& operator=(const ModHandle& rhs) = delete; | ||||
|             ModHandle(ModHandle&& rhs); | ||||
|  | @ -592,7 +544,7 @@ namespace recomp { | |||
|         void reset_hooks(); | ||||
|         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); | ||||
| 
 | ||||
|         void initialize_mods(); | ||||
|  | @ -607,12 +559,12 @@ namespace recomp { | |||
|         void enable_mod(const std::string& mod_id, bool enabled); | ||||
|         bool is_mod_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); | ||||
|         void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value); | ||||
|         void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value); | ||||
|         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); | ||||
|         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 config::ConfigValueVariant &value); | ||||
|         config::ConfigValueVariant get_mod_config_value(size_t mod_index, 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::filesystem::path get_mod_filename(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" | ||||
| 
 | ||||
| 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)) { | ||||
|         _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) { | ||||
|     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)) { | ||||
|         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) { | ||||
|     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)) { | ||||
|         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_options_key = "options"; | ||||
| 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{ | ||||
|     { "Enum",   recomp::mods::ConfigOptionType::Enum}, | ||||
|     { "Number", recomp::mods::ConfigOptionType::Number}, | ||||
|     { "String", recomp::mods::ConfigOptionType::String}, | ||||
|     { "Bool",   recomp::mods::ConfigOptionType::Bool}, | ||||
| std::unordered_map<std::string, recomp::config::ConfigOptionType> config_option_map{ | ||||
|     { "Enum",   recomp::config::ConfigOptionType::Enum}, | ||||
|     { "Number", recomp::config::ConfigOptionType::Number}, | ||||
|     { "String", recomp::config::ConfigOptionType::String}, | ||||
|     { "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; | ||||
|     recomp::mods::ConfigOption option; | ||||
|     recomp::config::ConfigOption option; | ||||
|     auto id = config_schema_json.find(config_schema_id_key); | ||||
|     if (id != config_schema_json.end()) { | ||||
|         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) { | ||||
|     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); | ||||
|             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; | ||||
|                     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); | ||||
|             if (default_value != config_schema_json.end()) { | ||||
|                 std::string 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()) { | ||||
|                         option_enum.default_value = uint32_t(it - option_enum.options.begin()); | ||||
|                         option_enum.default_value = it->value; | ||||
|                     } | ||||
|                     else { | ||||
|                         error_param = config_schema_default_key; | ||||
|  | @ -433,9 +445,9 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j | |||
| 
 | ||||
|         } | ||||
|         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); | ||||
|             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; | ||||
|         } | ||||
|         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); | ||||
|             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; | ||||
|         } | ||||
|         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); | ||||
|             if (default_value != config_schema_json.end()) { | ||||
|  | @ -530,13 +542,129 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j | |||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     ret.config_schema.options_by_id.emplace(option.id, ret.config_schema.options.size()); | ||||
|     ret.config_schema.options.emplace_back(option); | ||||
|     if (ret != nullptr) { | ||||
|         ret->add_option(option); | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     if (config != nullptr) { | ||||
|         config->set_id(ret.mod_id); | ||||
|         config->set_mod_version(ret.version.to_string()); | ||||
|     } | ||||
| 
 | ||||
|     // Authors
 | ||||
|     current_error = try_get_vec<json::string_t>(ret.authors, manifest_json, authors_key, true, error_param); | ||||
|     if (current_error != ModOpenError::Good) { | ||||
|  | @ -660,11 +793,23 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const | |||
|             } | ||||
| 
 | ||||
|             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) { | ||||
|                     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 { | ||||
|             error_param = config_schema_options_key; | ||||
|  | @ -675,93 +820,44 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const | |||
|     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) { | ||||
|     using json = nlohmann::json; | ||||
|     json config_json; | ||||
|     if (!read_json_with_backups(path, config_json)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     auto mod_id = config_json.find("mod_id"); | ||||
|     if (mod_id != config_json.end()) { | ||||
|         std::string mod_id_str; | ||||
|         if (get_to<json::string_t>(*mod_id, mod_id_str)) { | ||||
|             if (*mod_id != expected_mod_id) { | ||||
|                 // The mod's ID doesn't match.
 | ||||
| bool parse_mod_config_storage(const std::string &expected_mod_id, recomp::config::Config &config) { | ||||
|     return config.load_config([expected_mod_id](nlohmann::json &config_json) { | ||||
|         auto mod_id = config_json.find("mod_id"); | ||||
|         if (mod_id != config_json.end()) { | ||||
|             std::string mod_id_str; | ||||
|             if (get_to<nlohmann::json::string_t>(*mod_id, mod_id_str)) { | ||||
|                 if (*mod_id != expected_mod_id) { | ||||
|                     // The mod's ID doesn't match.
 | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 // The mod ID is not a string.
 | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             // The mod ID is not a string.
 | ||||
|             // The configuration file doesn't have a mod ID.
 | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         // The configuration file doesn't have a mod ID.
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     auto storage_json = config_json.find("storage"); | ||||
|     if (storage_json == config_json.end()) { | ||||
|         // The configuration file doesn't have a storage object.
 | ||||
|         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; | ||||
|      | ||||
|         auto storage_json = config_json.find("storage"); | ||||
|         if (storage_json == config_json.end()) { | ||||
|             // The configuration file doesn't have a storage object.
 | ||||
|             return false; | ||||
|         } | ||||
|      | ||||
|         if (!storage_json->is_object()) { | ||||
|             // The storage key does not correspond to an object.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         switch (option.type) { | ||||
|         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; | ||||
|         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::config::Config mod_config; | ||||
|     { | ||||
|         bool 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 { | ||||
|             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) { | ||||
|                 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.
 | ||||
|     ConfigStorage config_storage; | ||||
|     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); | ||||
|     parse_mod_config_storage(manifest.mod_id, mod_config); | ||||
| 
 | ||||
|     // Read the mod thumbnail if it exists.
 | ||||
|     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.
 | ||||
|     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; | ||||
| } | ||||
|  | @ -957,6 +1051,8 @@ std::string recomp::mods::error_to_string(ModOpenError error) { | |||
|             return "Duplicate mod found"; | ||||
|         case ModOpenError::WrongGame: | ||||
|             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); | ||||
| } | ||||
|  |  | |||
|  | @ -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)), | ||||
|     config_storage(std::move(config_storage)), | ||||
|     config(std::move(config)), | ||||
|     code_handle(), | ||||
|     recompiler_context{std::make_unique<N64Recomp::Context>()}, | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
|     size_t mod_index = opened_mods.size(); | ||||
|     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.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); | ||||
| } | ||||
| 
 | ||||
|  | @ -643,49 +643,14 @@ void recomp::mods::ModContext::close_mods() { | |||
|     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; | ||||
|     json config_json; | ||||
|     config_json["mod_id"] = mod_id; | ||||
|     config_json["mod_version"] = mod_version.to_string(); | ||||
|     config_json["recomp_version"] = recomp::get_project_version().to_string(); | ||||
| 
 | ||||
|     json &storage_json = config_json["storage"]; | ||||
|     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); | ||||
|     config_json["storage"] = storage_json; | ||||
|     return config->save_config_json(config_json); | ||||
| } | ||||
| 
 | ||||
| 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() { | ||||
|     using namespace std::chrono_literals; | ||||
|     using json = nlohmann::json; | ||||
| 
 | ||||
|     ModConfigQueueVariant variant; | ||||
|     ModConfigQueueSaveMod save_mod; | ||||
|     std::unordered_set<std::string> pending_mods; | ||||
|     std::unordered_map<std::string, ConfigStorage> pending_mod_storage; | ||||
|     std::unordered_map<std::string, ConfigSchema> pending_mod_schema; | ||||
|     std::unordered_map<std::string, Version> pending_mod_version; | ||||
|     std::unordered_map<std::string, const recomp::config::Config*> pending_mod_configs; | ||||
|     std::unordered_map<std::string, json> pending_mod_storage_json; | ||||
|     std::unordered_set<std::string> config_enabled_mods; | ||||
|     std::vector<std::string> config_mod_order; | ||||
|     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()) { | ||||
|                         const ModHandle &mod = opened_mods[it->second]; | ||||
|                         std::unique_lock config_storage_lock(mod_config_storage_mutex); | ||||
|                         pending_mod_storage[id] = mod.config_storage; | ||||
|                         pending_mod_schema[id] = mod.manifest.config_schema; | ||||
|                         pending_mod_version[id] = mod.manifest.version; | ||||
|                         pending_mod_configs[id] = &mod.config; | ||||
|                         pending_mod_storage_json[id] = mod.config.get_json_config(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             for (const std::string &id : pending_mods) { | ||||
|                 config_path = mod_config_directory / std::string(id + ".json"); | ||||
|                 save_mod_config_storage(config_path, id, pending_mod_version[id], pending_mod_storage[id], pending_mod_schema[id]); | ||||
|                 pending_mod_configs[id]->save_config_json(pending_mod_storage_json[id]); | ||||
|             } | ||||
| 
 | ||||
|             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()); | ||||
| } | ||||
| 
 | ||||
| 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.
 | ||||
|     auto find_it = opened_mods_by_id.find(mod_id); | ||||
|     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]; | ||||
|     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 { | ||||
|  | @ -1382,7 +1346,7 @@ const std::vector<char> &recomp::mods::ModContext::get_mod_thumbnail(const std:: | |||
|     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.
 | ||||
|     if (mod_index >= opened_mods.size()) { | ||||
|         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]; | ||||
|     std::unique_lock lock(mod_config_storage_mutex); | ||||
|     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()) { | ||||
|         // 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; | ||||
|         } | ||||
|     } | ||||
|     mod.config.set_option_value(option_id, value); | ||||
| 
 | ||||
|     // Notify the asynchronous thread it should save the configuration for this mod.
 | ||||
|     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.
 | ||||
|     auto find_it = opened_mods_by_id.find(mod_id); | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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.
 | ||||
|     if (mod_index >= opened_mods.size()) { | ||||
|         return std::monostate(); | ||||
|  | @ -1449,35 +1378,10 @@ recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value( | |||
| 
 | ||||
|     const ModHandle &mod = opened_mods[mod_index]; | ||||
|     std::unique_lock lock(mod_config_storage_mutex); | ||||
|     auto it = mod.config_storage.value_map.find(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(); | ||||
|         } | ||||
|     } | ||||
|     return mod.config.get_option_value(option_id); | ||||
| } | ||||
| 
 | ||||
| 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.
 | ||||
|     auto find_it = opened_mods_by_id.find(mod_id); | ||||
|     if (find_it == opened_mods_by_id.end()) { | ||||
|  |  | |||
|  | @ -68,6 +68,10 @@ void recomp::register_config_path(std::filesystem::path path) { | |||
|     config_path = path; | ||||
| } | ||||
| 
 | ||||
| std::filesystem::path recomp::get_config_path() { | ||||
|     return config_path; | ||||
| } | ||||
| 
 | ||||
| bool recomp::register_game(const recomp::GameEntry& entry) { | ||||
|     // 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); | ||||
| } | ||||
| 
 | ||||
| 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 }; | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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 }; | ||||
|     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 }; | ||||
|     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 }; | ||||
|     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 }; | ||||
|     return mod_context->get_mod_config_value(mod_id, option_id); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 thecozies
						thecozies