Separate configuration into librecomp/config

This commit is contained in:
thecozies 2025-09-01 13:30:48 -05:00
parent 283ebb9360
commit 3069d33446
10 changed files with 1293 additions and 297 deletions

View file

@ -9,6 +9,8 @@ set(CMAKE_CXX_EXTENSIONS OFF)
add_library(librecomp STATIC add_library(librecomp STATIC
"${CMAKE_CURRENT_SOURCE_DIR}/src/ai.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/ai.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/cont.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/cont.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/config_option.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/dp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/dp.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/eep.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/eep.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/euc-jp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/euc-jp.cpp"

View 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__

View file

@ -70,6 +70,7 @@ namespace recomp {
OtherError OtherError
}; };
void register_config_path(std::filesystem::path path); void register_config_path(std::filesystem::path path);
std::filesystem::path get_config_path();
bool register_game(const recomp::GameEntry& entry); bool register_game(const recomp::GameEntry& entry);
void check_all_stored_roms(); void check_all_stored_roms();
bool load_stored_rom(std::u8string& game_id); bool load_stored_rom(std::u8string& game_id);

View file

@ -27,6 +27,7 @@
#include "librecomp/game.hpp" #include "librecomp/game.hpp"
#include "librecomp/sections.h" #include "librecomp/sections.h"
#include "librecomp/overlays.hpp" #include "librecomp/overlays.hpp"
#include "librecomp/config.hpp"
namespace N64Recomp { namespace N64Recomp {
class Context; class Context;
@ -81,7 +82,10 @@ namespace recomp {
InvalidDependencyString, InvalidDependencyString,
MissingManifestField, MissingManifestField,
DuplicateMod, DuplicateMod,
WrongGame WrongGame,
InvalidDisableOptionDependency,
InvalidHiddenOptionDependency,
DuplicateEnumStrings,
}; };
std::string error_to_string(ModOpenError); std::string error_to_string(ModOpenError);
@ -126,14 +130,6 @@ namespace recomp {
std::string error_to_string(CodeModLoadError); std::string error_to_string(CodeModLoadError);
enum class ConfigOptionType {
None,
Enum,
Number,
String,
Bool
};
struct ModFileHandle { struct ModFileHandle {
virtual ~ModFileHandle() = default; virtual ~ModFileHandle() = default;
virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0; virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0;
@ -174,49 +170,6 @@ namespace recomp {
Version version; Version version;
}; };
struct ConfigOptionEnum {
std::vector<std::string> options;
uint32_t default_value = 0;
};
struct ConfigOptionNumber {
double min = 0.0;
double max = 0.0;
double step = 0.0;
int precision = 0;
bool percent = false;
double default_value = 0.0;
};
struct ConfigOptionString {
std::string default_value;
};
struct ConfigOptionBool {
bool default_value;
};
typedef std::variant<ConfigOptionEnum, ConfigOptionNumber, ConfigOptionString, ConfigOptionBool> ConfigOptionVariant;
struct ConfigOption {
std::string id;
std::string name;
std::string description;
ConfigOptionType type;
ConfigOptionVariant variant;
};
struct ConfigSchema {
std::vector<ConfigOption> options;
std::unordered_map<std::string, size_t> options_by_id;
};
typedef std::variant<std::monostate, uint32_t, double, std::string, bool> ConfigValueVariant;
struct ConfigStorage {
std::unordered_map<std::string, ConfigValueVariant> value_map;
};
struct ModDetails { struct ModDetails {
std::string mod_id; std::string mod_id;
std::string display_name; std::string display_name;
@ -240,7 +193,6 @@ namespace recomp {
std::vector<std::string> authors; std::vector<std::string> authors;
std::vector<Dependency> dependencies; std::vector<Dependency> dependencies;
std::unordered_map<std::string, size_t> dependencies_by_id; std::unordered_map<std::string, size_t> dependencies_by_id;
ConfigSchema config_schema;
Version minimum_recomp_version; Version minimum_recomp_version;
Version version; Version version;
bool runtime_toggleable; bool runtime_toggleable;
@ -355,12 +307,12 @@ namespace recomp {
recomp::Version get_mod_version(size_t mod_index); recomp::Version get_mod_version(size_t mod_index);
std::string get_mod_id(size_t mod_index); std::string get_mod_id(size_t mod_index);
void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index); void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index);
const ConfigSchema &get_mod_config_schema(const std::string &mod_id) const; const config::ConfigSchema &get_mod_config_schema(const std::string &mod_id) const;
const std::vector<char> &get_mod_thumbnail(const std::string &mod_id) const; const std::vector<char> &get_mod_thumbnail(const std::string &mod_id) const;
void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value); void set_mod_config_value(size_t mod_index, const std::string &option_id, const config::ConfigValueVariant &value);
void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value); void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const config::ConfigValueVariant &value);
ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id) const; config::ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id) const;
ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id) const; config::ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id) const;
void set_mods_config_path(const std::filesystem::path &path); void set_mods_config_path(const std::filesystem::path &path);
void set_mod_config_directory(const std::filesystem::path &path); void set_mod_config_directory(const std::filesystem::path &path);
ModContentTypeId register_content_type(const ModContentType& type); ModContentTypeId register_content_type(const ModContentType& type);
@ -376,7 +328,7 @@ namespace recomp {
CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param); CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param);
CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param); CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param);
CodeModLoadError resolve_code_dependencies(ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param); CodeModLoadError resolve_code_dependencies(ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param);
void add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail); void add_opened_mod(ModManifest&& manifest, config::Config&& config, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail);
std::vector<ModLoadErrorDetails> regenerate_with_hooks( std::vector<ModLoadErrorDetails> regenerate_with_hooks(
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks, const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
@ -418,7 +370,7 @@ namespace recomp {
std::vector<bool> processed_hook_slots; std::vector<bool> processed_hook_slots;
// Generated shim functions to use for implementing shim exports. // Generated shim functions to use for implementing shim exports.
std::vector<std::unique_ptr<N64Recomp::ShimFunction>> shim_functions; std::vector<std::unique_ptr<N64Recomp::ShimFunction>> shim_functions;
ConfigSchema empty_schema; config::ConfigSchema empty_schema;
std::vector<char> empty_bytes; std::vector<char> empty_bytes;
size_t num_events = 0; size_t num_events = 0;
ModContentTypeId code_content_type_id; ModContentTypeId code_content_type_id;
@ -442,7 +394,7 @@ namespace recomp {
public: public:
// TODO make these private and expose methods for the functionality they're currently used in. // TODO make these private and expose methods for the functionality they're currently used in.
ModManifest manifest; ModManifest manifest;
ConfigStorage config_storage; config::Config config;
std::unique_ptr<ModCodeHandle> code_handle; std::unique_ptr<ModCodeHandle> code_handle;
std::unique_ptr<N64Recomp::Context> recompiler_context; std::unique_ptr<N64Recomp::Context> recompiler_context;
std::vector<uint32_t> section_load_addresses; std::vector<uint32_t> section_load_addresses;
@ -450,7 +402,7 @@ namespace recomp {
std::vector<ModContentTypeId> content_types; std::vector<ModContentTypeId> content_types;
std::vector<char> thumbnail; std::vector<char> thumbnail;
ModHandle(const ModContext& context, ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail); ModHandle(const ModContext& context, ModManifest&& manifest, config::Config&& config, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail);
ModHandle(const ModHandle& rhs) = delete; ModHandle(const ModHandle& rhs) = delete;
ModHandle& operator=(const ModHandle& rhs) = delete; ModHandle& operator=(const ModHandle& rhs) = delete;
ModHandle(ModHandle&& rhs); ModHandle(ModHandle&& rhs);
@ -592,7 +544,7 @@ namespace recomp {
void reset_hooks(); void reset_hooks();
void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index); void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index);
ModOpenError parse_manifest(ModManifest &ret, const std::vector<char> &manifest_data, std::string &error_param); ModOpenError parse_manifest(ModManifest &ret, const std::vector<char> &manifest_data, std::string &error_param, recomp::config::Config *config);
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param); CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
void initialize_mods(); void initialize_mods();
@ -607,12 +559,12 @@ namespace recomp {
void enable_mod(const std::string& mod_id, bool enabled); void enable_mod(const std::string& mod_id, bool enabled);
bool is_mod_enabled(const std::string& mod_id); bool is_mod_enabled(const std::string& mod_id);
bool is_mod_auto_enabled(const std::string& mod_id); bool is_mod_auto_enabled(const std::string& mod_id);
const ConfigSchema &get_mod_config_schema(const std::string &mod_id); const config::ConfigSchema &get_mod_config_schema(const std::string &mod_id);
const std::vector<char> &get_mod_thumbnail(const std::string &mod_id); const std::vector<char> &get_mod_thumbnail(const std::string &mod_id);
void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value); void set_mod_config_value(size_t mod_index, const std::string &option_id, const config::ConfigValueVariant &value);
void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value); void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const config::ConfigValueVariant &value);
ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id); config::ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id);
ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id); config::ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id);
std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename); std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename);
std::filesystem::path get_mod_filename(const std::string& mod_id); std::filesystem::path get_mod_filename(const std::string& mod_id);
size_t get_mod_order_index(const std::string& mod_id); size_t get_mod_order_index(const std::string& mod_id);

638
librecomp/src/config.cpp Normal file
View 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);
}
}

View 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;
}
}

View file

@ -3,7 +3,7 @@
#include "librecomp/addresses.hpp" #include "librecomp/addresses.hpp"
void recomp_get_config_u32(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { void recomp_get_config_u32(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); recomp::config::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) { if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) {
_return(ctx, *as_u32); _return(ctx, *as_u32);
} }
@ -19,7 +19,7 @@ void recomp_get_config_u32(uint8_t* rdram, recomp_context* ctx, size_t mod_index
} }
void recomp_get_config_double(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { void recomp_get_config_double(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); recomp::config::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) { if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) {
ctx->f0.d = double(*as_u32); ctx->f0.d = double(*as_u32);
} }
@ -52,7 +52,7 @@ void return_string(uint8_t* rdram, recomp_context* ctx, const StringType& str) {
} }
void recomp_get_config_string(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { void recomp_get_config_string(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx)); recomp::config::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
if (std::string* as_string = std::get_if<std::string>(&val)) { if (std::string* as_string = std::get_if<std::string>(&val)) {
return_string(rdram, ctx, *as_string); return_string(rdram, ctx, *as_string);
} }

View file

@ -331,17 +331,20 @@ constexpr std::string_view config_schema_precision_key = "precision";
constexpr std::string_view config_schema_percent_key = "percent"; constexpr std::string_view config_schema_percent_key = "percent";
constexpr std::string_view config_schema_options_key = "options"; constexpr std::string_view config_schema_options_key = "options";
constexpr std::string_view config_schema_default_key = "default"; constexpr std::string_view config_schema_default_key = "default";
constexpr std::string_view config_schema_matches_key = "matches";
constexpr std::string_view config_schema_hidden_from_key = "hidden_from";
constexpr std::string_view config_schema_disabled_from_key = "disabled_from";
std::unordered_map<std::string, recomp::mods::ConfigOptionType> config_option_map{ std::unordered_map<std::string, recomp::config::ConfigOptionType> config_option_map{
{ "Enum", recomp::mods::ConfigOptionType::Enum}, { "Enum", recomp::config::ConfigOptionType::Enum},
{ "Number", recomp::mods::ConfigOptionType::Number}, { "Number", recomp::config::ConfigOptionType::Number},
{ "String", recomp::mods::ConfigOptionType::String}, { "String", recomp::config::ConfigOptionType::String},
{ "Bool", recomp::mods::ConfigOptionType::Bool}, { "Bool", recomp::config::ConfigOptionType::Bool},
}; };
recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::json &config_schema_json, recomp::mods::ModManifest &ret, std::string &error_param) { recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::json &config_schema_json, recomp::config::Config *ret, std::string &error_param) {
using json = nlohmann::json; using json = nlohmann::json;
recomp::mods::ConfigOption option; recomp::config::ConfigOption option;
auto id = config_schema_json.find(config_schema_id_key); auto id = config_schema_json.find(config_schema_id_key);
if (id != config_schema_json.end()) { if (id != config_schema_json.end()) {
if (!get_to<json::string_t>(*id, option.id)) { if (!get_to<json::string_t>(*id, option.id)) {
@ -398,25 +401,34 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j
} }
switch (option.type) { switch (option.type) {
case recomp::mods::ConfigOptionType::Enum: case recomp::config::ConfigOptionType::Enum:
{ {
recomp::mods::ConfigOptionEnum option_enum; recomp::config::ConfigOptionEnum option_enum;
auto options = config_schema_json.find(config_schema_options_key); auto options = config_schema_json.find(config_schema_options_key);
if (options != config_schema_json.end()) { if (options != config_schema_json.end()) {
if (!get_to_vec<std::string>(*options, option_enum.options)) { std::vector<std::string> option_key_list;
if (!get_to_vec<std::string>(*options, option_key_list)) {
error_param = config_schema_options_key; error_param = config_schema_options_key;
return recomp::mods::ModOpenError::IncorrectConfigSchemaType; return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
} }
for (uint32_t i = 0; i < static_cast<uint32_t>(option_key_list.size()); i++) {
if (!option_enum.can_add_option(option_key_list[i], i)) {
error_param = config_schema_options_key;
return recomp::mods::ModOpenError::DuplicateEnumStrings;
}
option_enum.options.push_back({i, option_key_list[i]});
}
} }
auto default_value = config_schema_json.find(config_schema_default_key); auto default_value = config_schema_json.find(config_schema_default_key);
if (default_value != config_schema_json.end()) { if (default_value != config_schema_json.end()) {
std::string default_value_string; std::string default_value_string;
if (get_to<json::string_t>(*default_value, default_value_string)) { if (get_to<json::string_t>(*default_value, default_value_string)) {
auto it = std::find(option_enum.options.begin(), option_enum.options.end(), default_value_string); auto it = option_enum.find_option_from_string(default_value_string);
if (it != option_enum.options.end()) { if (it != option_enum.options.end()) {
option_enum.default_value = uint32_t(it - option_enum.options.begin()); option_enum.default_value = it->value;
} }
else { else {
error_param = config_schema_default_key; error_param = config_schema_default_key;
@ -433,9 +445,9 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j
} }
break; break;
case recomp::mods::ConfigOptionType::Number: case recomp::config::ConfigOptionType::Number:
{ {
recomp::mods::ConfigOptionNumber option_number; recomp::config::ConfigOptionNumber option_number;
auto min = config_schema_json.find(config_schema_min_key); auto min = config_schema_json.find(config_schema_min_key);
if (min != config_schema_json.end()) { if (min != config_schema_json.end()) {
@ -496,9 +508,9 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j
option.variant = option_number; option.variant = option_number;
} }
break; break;
case recomp::mods::ConfigOptionType::String: case recomp::config::ConfigOptionType::String:
{ {
recomp::mods::ConfigOptionString option_string; recomp::config::ConfigOptionString option_string;
auto default_value = config_schema_json.find(config_schema_default_key); auto default_value = config_schema_json.find(config_schema_default_key);
if (default_value != config_schema_json.end()) { if (default_value != config_schema_json.end()) {
@ -511,9 +523,9 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j
option.variant = option_string; option.variant = option_string;
} }
break; break;
case recomp::mods::ConfigOptionType::Bool: case recomp::config::ConfigOptionType::Bool:
{ {
recomp::mods::ConfigOptionBool option_bool; recomp::config::ConfigOptionBool option_bool;
auto default_value = config_schema_json.find(config_schema_default_key); auto default_value = config_schema_json.find(config_schema_default_key);
if (default_value != config_schema_json.end()) { if (default_value != config_schema_json.end()) {
@ -530,13 +542,129 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j
break; break;
} }
ret.config_schema.options_by_id.emplace(option.id, ret.config_schema.options.size()); if (ret != nullptr) {
ret.config_schema.options.emplace_back(option); ret->add_option(option);
}
return recomp::mods::ModOpenError::Good; return recomp::mods::ModOpenError::Good;
} }
recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const std::vector<char>& manifest_data, std::string& error_param) { std::vector<recomp::config::ConfigValueVariant> parse_config_option_dependency_matches(const nlohmann::json &matches_object, const recomp::config::ConfigOption &source_option) {
std::vector<recomp::config::ConfigValueVariant> matches = {};
switch (source_option.type) {
case recomp::config::ConfigOptionType::None:
default:
//! ERROR: Source option type is invalid
return matches;
case recomp::config::ConfigOptionType::Enum: {
std::vector<std::string> enum_values = {};
if (!get_to_vec<std::string>(matches_object, enum_values)) {
//! ERROR: failed to get array of strings in matches
return matches;
}
auto &option_enum = get<recomp::config::ConfigOptionEnum>(source_option.variant);
for (auto &value_str : enum_values) {
auto it = option_enum.find_option_from_string(value_str);
if (it == option_enum.options.end()) {
//! ERROR: one of the matches specified doesn't exist in the source enum
return matches;
}
matches.push_back(it->value);
}
break;
}
case recomp::config::ConfigOptionType::String:
//! ERROR: string dependencies are unsupported
return matches;
case recomp::config::ConfigOptionType::Number:
//! ERROR: numerical dependencies are unsupported
return matches;
case recomp::config::ConfigOptionType::Bool: {
bool bool_condition;
if (!get_to<nlohmann::json::boolean_t>(matches_object, bool_condition)) {
//! ERROR: Failed to get boolean from dependency matches
return matches;
}
matches.push_back(bool_condition);
break;
}
}
return matches;
}
recomp::mods::ModOpenError parse_config_option_dependencies(const nlohmann::json &config_schema_json, recomp::config::Config &config) {
using json = nlohmann::json;
auto id = config_schema_json.find(config_schema_id_key);
std::string option_id;
if (id != config_schema_json.end()) {
if (!get_to<json::string_t>(*id, option_id)) {
return recomp::mods::ModOpenError::Good;
}
}
recomp::config::ConfigSchema schema = config.get_config_schema();
size_t option_index = schema.options_by_id[option_id];
recomp::config::ConfigOption &option = schema.options[option_index];
auto conditional_add_dependency = [config_schema_json, schema, option, option_index](recomp::config::ConfigOptionDependency &dependency, const std::string_view &dep_key) -> bool {
auto find_it = config_schema_json.find(dep_key);
if (find_it == config_schema_json.end()) {
return true;
}
auto& dependency_json = *find_it;
if (!dependency_json.is_object()) {
//! ERROR: Dependency malformed
return false;
}
std::string source_dependency_id;
auto find_dep_id = dependency_json.find(config_schema_id_key);
if (find_dep_id == dependency_json.end()) {
//! ERROR: Could not find source dependency id
return false;
}
if (!get_to<json::string_t>(*find_dep_id, source_dependency_id)) {
//! ERROR: Failed to get source dependency id
return false;
}
if (schema.options_by_id.contains(source_dependency_id) == false) {
//! ERROR: Failed to find specified source dependency in schema
return false;
}
size_t source_dependency_index = schema.options_by_id.at(source_dependency_id);
auto &source_option = schema.options.at(source_dependency_index);
auto find_matches = dependency_json.find(config_schema_matches_key);
if (find_matches == dependency_json.end()) {
//! ERROR: Failed to find matches
return false;
}
auto matches = parse_config_option_dependency_matches(*find_matches, source_option);
if (matches.empty()) {
//! ERROR: Could not find valid data in matches
return false;
}
dependency.add_option_dependency(option_index, source_dependency_index, matches);
return true;
};
bool disable_success = conditional_add_dependency(schema.disable_dependencies, config_schema_disabled_from_key);
if (!disable_success) return recomp::mods::ModOpenError::InvalidDisableOptionDependency;
bool hidden_success = conditional_add_dependency(schema.hidden_dependencies, config_schema_hidden_from_key);
if (!hidden_success) return recomp::mods::ModOpenError::InvalidHiddenOptionDependency;
return recomp::mods::ModOpenError::Good;
}
recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const std::vector<char>& manifest_data, std::string& error_param, recomp::config::Config *config) {
using json = nlohmann::json; using json = nlohmann::json;
json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false); json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false);
@ -588,6 +716,11 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const
return current_error; return current_error;
} }
if (config != nullptr) {
config->set_id(ret.mod_id);
config->set_mod_version(ret.version.to_string());
}
// Authors // Authors
current_error = try_get_vec<json::string_t>(ret.authors, manifest_json, authors_key, true, error_param); current_error = try_get_vec<json::string_t>(ret.authors, manifest_json, authors_key, true, error_param);
if (current_error != ModOpenError::Good) { if (current_error != ModOpenError::Good) {
@ -660,11 +793,23 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const
} }
for (const json &option : *options) { for (const json &option : *options) {
ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param); ModOpenError open_error = parse_manifest_config_schema_option(option, config, error_param);
if (open_error != ModOpenError::Good) { if (open_error != ModOpenError::Good) {
return open_error; return open_error;
} }
} }
// Parse option dependencies after all options have been added
// Requires config to not be null
if (config != nullptr) {
for (const json &option : *options) {
ModOpenError dep_error = parse_config_option_dependencies(option, *config);
if (dep_error != ModOpenError::Good) {
error_param = dep_error == ModOpenError::InvalidDisableOptionDependency ? config_schema_disabled_from_key : config_schema_hidden_from_key;
return dep_error;
}
}
}
} }
else { else {
error_param = config_schema_options_key; error_param = config_schema_options_key;
@ -675,93 +820,44 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const
return ModOpenError::Good; return ModOpenError::Good;
} }
bool parse_mod_config_storage(const std::filesystem::path &path, const std::string &expected_mod_id, recomp::mods::ConfigStorage &config_storage, const recomp::mods::ConfigSchema &config_schema) { bool parse_mod_config_storage(const std::string &expected_mod_id, recomp::config::Config &config) {
using json = nlohmann::json; return config.load_config([expected_mod_id](nlohmann::json &config_json) {
json config_json; auto mod_id = config_json.find("mod_id");
if (!read_json_with_backups(path, config_json)) { if (mod_id != config_json.end()) {
return false; std::string mod_id_str;
} if (get_to<nlohmann::json::string_t>(*mod_id, mod_id_str)) {
if (*mod_id != expected_mod_id) {
auto mod_id = config_json.find("mod_id"); // The mod's ID doesn't match.
if (mod_id != config_json.end()) { return false;
std::string mod_id_str; }
if (get_to<json::string_t>(*mod_id, mod_id_str)) { }
if (*mod_id != expected_mod_id) { else {
// The mod's ID doesn't match. // The mod ID is not a string.
return false; return false;
} }
} }
else { else {
// The mod ID is not a string. // The configuration file doesn't have a mod ID.
return false; return false;
} }
}
else { auto storage_json = config_json.find("storage");
// The configuration file doesn't have a mod ID. if (storage_json == config_json.end()) {
return false; // The configuration file doesn't have a storage object.
} return false;
}
auto storage_json = config_json.find("storage");
if (storage_json == config_json.end()) { if (!storage_json->is_object()) {
// The configuration file doesn't have a storage object. // The storage key does not correspond to an object.
return false; return false;
}
if (!storage_json->is_object()) {
// The storage key does not correspond to an object.
return false;
}
// Only parse the object for known option types based on the schema.
std::string value_str;
for (const recomp::mods::ConfigOption &option : config_schema.options) {
auto option_json = storage_json->find(option.id);
if (option_json == storage_json->end()) {
// Option doesn't exist in storage.
continue;
} }
switch (option.type) { return true;
case recomp::mods::ConfigOptionType::Enum: });
if (get_to<json::string_t>(*option_json, value_str)) {
const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant);
auto option_it = std::find(option_enum.options.begin(), option_enum.options.end(), value_str);
if (option_it != option_enum.options.end()) {
config_storage.value_map[option.id] = uint32_t(option_it - option_enum.options.begin());
}
}
break;
case recomp::mods::ConfigOptionType::Number:
if (option_json->is_number()) {
config_storage.value_map[option.id] = option_json->template get<double>();
}
break;
case recomp::mods::ConfigOptionType::String: {
if (get_to<json::string_t>(*option_json, value_str)) {
config_storage.value_map[option.id] = value_str;
}
break;
}
case recomp::mods::ConfigOptionType::Bool: {
if (option_json->is_boolean()) {
config_storage.value_map[option.id] = option_json->get<bool>();
}
break;
}
default:
assert(false && "Unknown option type.");
break;
}
}
return true;
} }
recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModManifest& manifest, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest) { recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModManifest& manifest, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest) {
recomp::config::Config mod_config;
{ {
bool exists; bool exists;
std::vector<char> manifest_data = manifest.file_handle->read_file("mod.json", exists); std::vector<char> manifest_data = manifest.file_handle->read_file("mod.json", exists);
@ -799,7 +895,7 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModM
} }
} }
else { else {
ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param); ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param, &mod_config);
if (parse_error != ModOpenError::Good) { if (parse_error != ModOpenError::Good) {
return parse_error; return parse_error;
} }
@ -848,9 +944,7 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModM
} }
// Read the mod config if it exists. // Read the mod config if it exists.
ConfigStorage config_storage; parse_mod_config_storage(manifest.mod_id, mod_config);
std::filesystem::path config_path = mod_config_directory / (manifest.mod_id + ".json");
parse_mod_config_storage(config_path, manifest.mod_id, config_storage, manifest.config_schema);
// Read the mod thumbnail if it exists. // Read the mod thumbnail if it exists.
static const std::string thumbnail_dds_name = "thumb.dds"; static const std::string thumbnail_dds_name = "thumb.dds";
@ -862,7 +956,7 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModM
} }
// Store the loaded mod manifest in a new mod handle. // Store the loaded mod manifest in a new mod handle.
add_opened_mod(std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail_data)); add_opened_mod(std::move(manifest), std::move(mod_config), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail_data));
return ModOpenError::Good; return ModOpenError::Good;
} }
@ -957,6 +1051,8 @@ std::string recomp::mods::error_to_string(ModOpenError error) {
return "Duplicate mod found"; return "Duplicate mod found";
case ModOpenError::WrongGame: case ModOpenError::WrongGame:
return "Mod is for a different game"; return "Mod is for a different game";
case ModOpenError::DuplicateEnumStrings:
return "Duplicate enum strings found in mod.json (enum strings are case insensitive)";
} }
return "Unknown mod opening error: " + std::to_string((int)error); return "Unknown mod opening error: " + std::to_string((int)error);
} }

View file

@ -266,9 +266,9 @@ recomp::mods::CodeModLoadError recomp::mods::validate_api_version(uint32_t api_v
} }
} }
recomp::mods::ModHandle::ModHandle(const ModContext& context, ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail) : recomp::mods::ModHandle::ModHandle(const ModContext& context, ModManifest&& manifest, recomp::config::Config&& config, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail) :
manifest(std::move(manifest)), manifest(std::move(manifest)),
config_storage(std::move(config_storage)), config(std::move(config)),
code_handle(), code_handle(),
recompiler_context{std::make_unique<N64Recomp::Context>()}, recompiler_context{std::make_unique<N64Recomp::Context>()},
content_types{std::move(content_types)}, content_types{std::move(content_types)},
@ -595,12 +595,12 @@ void unpatch_func(void* target_func, const recomp::mods::PatchData& data) {
protect(target_func, old_flags); protect(target_func, old_flags);
} }
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail) { void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, config::Config&& config, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail) {
std::unique_lock lock(opened_mods_mutex); std::unique_lock lock(opened_mods_mutex);
size_t mod_index = opened_mods.size(); size_t mod_index = opened_mods.size();
opened_mods_by_id.emplace(manifest.mod_id, mod_index); opened_mods_by_id.emplace(manifest.mod_id, mod_index);
opened_mods_by_filename.emplace(manifest.mod_root_path.filename().native(), mod_index); opened_mods_by_filename.emplace(manifest.mod_root_path.filename().native(), mod_index);
opened_mods.emplace_back(*this, std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail)); opened_mods.emplace_back(*this, std::move(manifest), std::move(config), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail));
opened_mods_order.emplace_back(mod_index); opened_mods_order.emplace_back(mod_index);
} }
@ -643,49 +643,14 @@ void recomp::mods::ModContext::close_mods() {
auto_enabled_mods.clear(); auto_enabled_mods.clear();
} }
bool save_mod_config_storage(const std::filesystem::path &path, const std::string &mod_id, const recomp::Version &mod_version, const recomp::mods::ConfigStorage &config_storage, const recomp::mods::ConfigSchema &config_schema) { bool save_mod_config_storage(const std::string &mod_id, const recomp::Version &mod_version, const recomp::config::Config *config, nlohmann::json &storage_json) {
using json = nlohmann::json; using json = nlohmann::json;
json config_json; json config_json;
config_json["mod_id"] = mod_id; config_json["mod_id"] = mod_id;
config_json["mod_version"] = mod_version.to_string(); config_json["mod_version"] = mod_version.to_string();
config_json["recomp_version"] = recomp::get_project_version().to_string(); config_json["recomp_version"] = recomp::get_project_version().to_string();
config_json["storage"] = storage_json;
json &storage_json = config_json["storage"]; return config->save_config_json(config_json);
for (auto it : config_storage.value_map) {
auto id_it = config_schema.options_by_id.find(it.first);
if (id_it == config_schema.options_by_id.end()) {
continue;
}
const recomp::mods::ConfigOption &config_option = config_schema.options[id_it->second];
switch (config_option.type) {
case recomp::mods::ConfigOptionType::Enum:
storage_json[it.first] = std::get<recomp::mods::ConfigOptionEnum>(config_option.variant).options[std::get<uint32_t>(it.second)];
break;
case recomp::mods::ConfigOptionType::Number:
storage_json[it.first] = std::get<double>(it.second);
break;
case recomp::mods::ConfigOptionType::String:
storage_json[it.first] = std::get<std::string>(it.second);
break;
case recomp::mods::ConfigOptionType::Bool:
storage_json[it.first] = std::get<bool>(it.second);
break;
default:
assert(false && "Unknown config type.");
break;
}
}
std::ofstream output_file = recomp::open_output_file_with_backup(path);
if (!output_file.good()) {
return false;
}
output_file << std::setw(4) << config_json;
output_file.close();
return recomp::finalize_output_file_with_backup(path);
} }
bool parse_mods_config(const std::filesystem::path &path, std::unordered_set<std::string> &enabled_mods, std::vector<std::string> &mod_order) { bool parse_mods_config(const std::filesystem::path &path, std::unordered_set<std::string> &enabled_mods, std::vector<std::string> &mod_order) {
@ -731,12 +696,13 @@ bool save_mods_config(const std::filesystem::path &path, const std::unordered_se
void recomp::mods::ModContext::dirty_mod_configuration_thread_process() { void recomp::mods::ModContext::dirty_mod_configuration_thread_process() {
using namespace std::chrono_literals; using namespace std::chrono_literals;
using json = nlohmann::json;
ModConfigQueueVariant variant; ModConfigQueueVariant variant;
ModConfigQueueSaveMod save_mod; ModConfigQueueSaveMod save_mod;
std::unordered_set<std::string> pending_mods; std::unordered_set<std::string> pending_mods;
std::unordered_map<std::string, ConfigStorage> pending_mod_storage; std::unordered_map<std::string, const recomp::config::Config*> pending_mod_configs;
std::unordered_map<std::string, ConfigSchema> pending_mod_schema; std::unordered_map<std::string, json> pending_mod_storage_json;
std::unordered_map<std::string, Version> pending_mod_version;
std::unordered_set<std::string> config_enabled_mods; std::unordered_set<std::string> config_enabled_mods;
std::vector<std::string> config_mod_order; std::vector<std::string> config_mod_order;
bool pending_config_save = false; bool pending_config_save = false;
@ -773,16 +739,14 @@ void recomp::mods::ModContext::dirty_mod_configuration_thread_process() {
if (it != opened_mods_by_id.end()) { if (it != opened_mods_by_id.end()) {
const ModHandle &mod = opened_mods[it->second]; const ModHandle &mod = opened_mods[it->second];
std::unique_lock config_storage_lock(mod_config_storage_mutex); std::unique_lock config_storage_lock(mod_config_storage_mutex);
pending_mod_storage[id] = mod.config_storage; pending_mod_configs[id] = &mod.config;
pending_mod_schema[id] = mod.manifest.config_schema; pending_mod_storage_json[id] = mod.config.get_json_config();
pending_mod_version[id] = mod.manifest.version;
} }
} }
} }
for (const std::string &id : pending_mods) { for (const std::string &id : pending_mods) {
config_path = mod_config_directory / std::string(id + ".json"); pending_mod_configs[id]->save_config_json(pending_mod_storage_json[id]);
save_mod_config_storage(config_path, id, pending_mod_version[id], pending_mod_storage[id], pending_mod_schema[id]);
} }
pending_mods.clear(); pending_mods.clear();
@ -1360,7 +1324,7 @@ void recomp::mods::ModContext::set_mod_index(const std::string &mod_game_id, con
mod_configuration_thread_queue.enqueue(ModConfigQueueSave()); mod_configuration_thread_queue.enqueue(ModConfigQueueSave());
} }
const recomp::mods::ConfigSchema &recomp::mods::ModContext::get_mod_config_schema(const std::string &mod_id) const { const recomp::config::ConfigSchema &recomp::mods::ModContext::get_mod_config_schema(const std::string &mod_id) const {
// Check that the mod exists. // Check that the mod exists.
auto find_it = opened_mods_by_id.find(mod_id); auto find_it = opened_mods_by_id.find(mod_id);
if (find_it == opened_mods_by_id.end()) { if (find_it == opened_mods_by_id.end()) {
@ -1368,7 +1332,7 @@ const recomp::mods::ConfigSchema &recomp::mods::ModContext::get_mod_config_schem
} }
const ModHandle &mod = opened_mods[find_it->second]; const ModHandle &mod = opened_mods[find_it->second];
return mod.manifest.config_schema; return mod.config.get_config_schema();
} }
const std::vector<char> &recomp::mods::ModContext::get_mod_thumbnail(const std::string &mod_id) const { const std::vector<char> &recomp::mods::ModContext::get_mod_thumbnail(const std::string &mod_id) const {
@ -1382,7 +1346,7 @@ const std::vector<char> &recomp::mods::ModContext::get_mod_thumbnail(const std::
return mod.thumbnail; return mod.thumbnail;
} }
void recomp::mods::ModContext::set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value) { void recomp::mods::ModContext::set_mod_config_value(size_t mod_index, const std::string &option_id, const recomp::config::ConfigValueVariant &value) {
// Check that the mod exists. // Check that the mod exists.
if (mod_index >= opened_mods.size()) { if (mod_index >= opened_mods.size()) {
return; return;
@ -1390,48 +1354,13 @@ void recomp::mods::ModContext::set_mod_config_value(size_t mod_index, const std:
ModHandle &mod = opened_mods[mod_index]; ModHandle &mod = opened_mods[mod_index];
std::unique_lock lock(mod_config_storage_mutex); std::unique_lock lock(mod_config_storage_mutex);
auto option_by_id_it = mod.manifest.config_schema.options_by_id.find(option_id); mod.config.set_option_value(option_id, value);
if (option_by_id_it != mod.manifest.config_schema.options_by_id.end()) {
// Only accept setting values if the value exists and the variant is the right type.
const ConfigOption &option = mod.manifest.config_schema.options[option_by_id_it->second];
switch (option.type) {
case ConfigOptionType::Enum:
if (std::holds_alternative<uint32_t>(value)) {
if (std::get<uint32_t>(value) < std::get<ConfigOptionEnum>(option.variant).options.size()) {
mod.config_storage.value_map[option_id] = value;
}
}
break;
case ConfigOptionType::Number:
if (std::holds_alternative<double>(value)) {
mod.config_storage.value_map[option_id] = value;
}
break;
case ConfigOptionType::String:
if (std::holds_alternative<std::string>(value)) {
mod.config_storage.value_map[option_id] = value;
}
break;
case ConfigOptionType::Bool:
if (std::holds_alternative<bool>(value)) {
mod.config_storage.value_map[option_id] = value;
}
break;
default:
assert(false && "Unknown config option type.");
return;
}
}
// Notify the asynchronous thread it should save the configuration for this mod. // Notify the asynchronous thread it should save the configuration for this mod.
mod_configuration_thread_queue.enqueue(ModConfigQueueSaveMod{ mod.manifest.mod_id }); mod_configuration_thread_queue.enqueue(ModConfigQueueSaveMod{ mod.manifest.mod_id });
} }
void recomp::mods::ModContext::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value) { void recomp::mods::ModContext::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const recomp::config::ConfigValueVariant &value) {
// Check that the mod exists. // Check that the mod exists.
auto find_it = opened_mods_by_id.find(mod_id); auto find_it = opened_mods_by_id.find(mod_id);
if (find_it == opened_mods_by_id.end()) { if (find_it == opened_mods_by_id.end()) {
@ -1441,7 +1370,7 @@ void recomp::mods::ModContext::set_mod_config_value(const std::string &mod_id, c
set_mod_config_value(find_it->second, option_id, value); set_mod_config_value(find_it->second, option_id, value);
} }
recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(size_t mod_index, const std::string &option_id) const { recomp::config::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(size_t mod_index, const std::string &option_id) const {
// Check that the mod exists. // Check that the mod exists.
if (mod_index >= opened_mods.size()) { if (mod_index >= opened_mods.size()) {
return std::monostate(); return std::monostate();
@ -1449,35 +1378,10 @@ recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(
const ModHandle &mod = opened_mods[mod_index]; const ModHandle &mod = opened_mods[mod_index];
std::unique_lock lock(mod_config_storage_mutex); std::unique_lock lock(mod_config_storage_mutex);
auto it = mod.config_storage.value_map.find(option_id); return mod.config.get_option_value(option_id);
if (it != mod.config_storage.value_map.end()) {
return it->second;
}
else {
// Attempt to see if we can find a default value from the schema.
auto option_by_id_it = mod.manifest.config_schema.options_by_id.find(option_id);
if (option_by_id_it == mod.manifest.config_schema.options_by_id.end()) {
return std::monostate();
}
const ConfigOption &option = mod.manifest.config_schema.options[option_by_id_it->second];
switch (option.type) {
case ConfigOptionType::Enum:
return std::get<ConfigOptionEnum>(option.variant).default_value;
case ConfigOptionType::Number:
return std::get<ConfigOptionNumber>(option.variant).default_value;
case ConfigOptionType::String:
return std::get<ConfigOptionString>(option.variant).default_value;
case ConfigOptionType::Bool:
return std::get<ConfigOptionBool>(option.variant).default_value;
default:
assert(false && "Unknown config option type.");
return std::monostate();
}
}
} }
recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(const std::string &mod_id, const std::string &option_id) const { recomp::config::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(const std::string &mod_id, const std::string &option_id) const {
// Check that the mod exists. // Check that the mod exists.
auto find_it = opened_mods_by_id.find(mod_id); auto find_it = opened_mods_by_id.find(mod_id);
if (find_it == opened_mods_by_id.end()) { if (find_it == opened_mods_by_id.end()) {

View file

@ -68,6 +68,10 @@ void recomp::register_config_path(std::filesystem::path path) {
config_path = path; config_path = path;
} }
std::filesystem::path recomp::get_config_path() {
return config_path;
}
bool recomp::register_game(const recomp::GameEntry& entry) { bool recomp::register_game(const recomp::GameEntry& entry) {
// TODO verify that there's no game with this ID already. // TODO verify that there's no game with this ID already.
{ {
@ -540,7 +544,7 @@ bool recomp::mods::is_mod_auto_enabled(const std::string& mod_id) {
return mod_context->is_mod_auto_enabled(mod_id); return mod_context->is_mod_auto_enabled(mod_id);
} }
const recomp::mods::ConfigSchema &recomp::mods::get_mod_config_schema(const std::string &mod_id) { const recomp::config::ConfigSchema &recomp::mods::get_mod_config_schema(const std::string &mod_id) {
std::lock_guard lock{ mod_context_mutex }; std::lock_guard lock{ mod_context_mutex };
return mod_context->get_mod_config_schema(mod_id); return mod_context->get_mod_config_schema(mod_id);
} }
@ -550,22 +554,22 @@ const std::vector<char> &recomp::mods::get_mod_thumbnail(const std::string &mod_
return mod_context->get_mod_thumbnail(mod_id); return mod_context->get_mod_thumbnail(mod_id);
} }
void recomp::mods::set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value) { void recomp::mods::set_mod_config_value(size_t mod_index, const std::string &option_id, const recomp::config::ConfigValueVariant &value) {
std::lock_guard lock{ mod_context_mutex }; std::lock_guard lock{ mod_context_mutex };
return mod_context->set_mod_config_value(mod_index, option_id, value); return mod_context->set_mod_config_value(mod_index, option_id, value);
} }
void recomp::mods::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value) { void recomp::mods::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const recomp::config::ConfigValueVariant &value) {
std::lock_guard lock{ mod_context_mutex }; std::lock_guard lock{ mod_context_mutex };
return mod_context->set_mod_config_value(mod_id, option_id, value); return mod_context->set_mod_config_value(mod_id, option_id, value);
} }
recomp::mods::ConfigValueVariant recomp::mods::get_mod_config_value(size_t mod_index, const std::string &option_id) { recomp::config::ConfigValueVariant recomp::mods::get_mod_config_value(size_t mod_index, const std::string &option_id) {
std::lock_guard lock{ mod_context_mutex }; std::lock_guard lock{ mod_context_mutex };
return mod_context->get_mod_config_value(mod_index, option_id); return mod_context->get_mod_config_value(mod_index, option_id);
} }
recomp::mods::ConfigValueVariant recomp::mods::get_mod_config_value(const std::string &mod_id, const std::string &option_id) { recomp::config::ConfigValueVariant recomp::mods::get_mod_config_value(const std::string &mod_id, const std::string &option_id) {
std::lock_guard lock{ mod_context_mutex }; std::lock_guard lock{ mod_context_mutex };
return mod_context->get_mod_config_value(mod_id, option_id); return mod_context->get_mod_config_value(mod_id, option_id);
} }