From db1b1a10822c6f7aa9d8bd4f930e820f11daed3e Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Sun, 6 Apr 2025 03:53:28 -0400 Subject: [PATCH] Expose functionality needed for runtime mod installation (#97) * DnD prototype. * Remaining changes needed for runtime mod installation * Change path unordered map to use strings as keys instead to fix MacOS compilation --------- Co-authored-by: Dario --- librecomp/include/librecomp/mods.hpp | 28 ++++++++++++--- librecomp/src/mod_manifest.cpp | 52 ++++++++++++++-------------- librecomp/src/mods.cpp | 35 +++++++++++++------ librecomp/src/recomp.cpp | 21 +++++++++-- 4 files changed, 93 insertions(+), 43 deletions(-) diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 20b7f80..eeb41bd 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "blockingconcurrentqueue.h" @@ -259,7 +260,9 @@ namespace recomp { mod_id(mod_id_), error(error_), error_param(error_param_) {} }; - std::vector get_mod_details(const std::string& mod_game_id); + std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename); + std::optional get_details_for_mod(const std::string& mod_id); + std::vector get_all_mod_details(const std::string& mod_game_id); void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index); // Internal functions, TODO move to an internal header. @@ -324,6 +327,7 @@ namespace recomp { void register_game(const std::string& mod_game_id); std::vector scan_mod_folder(const std::filesystem::path& mod_folder); + void close_mods(); void load_mods_config(); void enable_mod(const std::string& mod_id, bool enabled, bool trigger_save); bool is_mod_enabled(const std::string& mod_id); @@ -331,7 +335,9 @@ namespace recomp { size_t num_opened_mods(); std::vector load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used); void unload_mods(); - std::vector get_mod_details(const std::string& mod_game_id); + std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename) const; + std::optional get_details_for_mod(const std::string& mod_id) const; + std::vector get_all_mod_details(const std::string& mod_game_id); 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 std::vector &get_mod_thumbnail(const std::string &mod_id) const; @@ -353,7 +359,6 @@ namespace recomp { 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& base_patched_funcs, std::string& error_param); void add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector&& game_indices, std::vector&& detected_content_types, std::vector&& thumbnail); - void close_mods(); std::vector regenerate_with_hooks( const std::vector>& sorted_unprocessed_hooks, const std::unordered_map& section_vrom_map, @@ -369,6 +374,7 @@ namespace recomp { std::unordered_map mod_game_ids; std::vector opened_mods; std::unordered_map opened_mods_by_id; + std::unordered_map opened_mods_by_filename; std::vector opened_mods_order; std::mutex opened_mods_mutex; std::unordered_set mod_ids; @@ -453,6 +459,19 @@ namespace recomp { void disable_runtime_toggle() { runtime_toggleable = false; } + + ModDetails get_details() const { + return ModDetails { + .mod_id = manifest.mod_id, + .display_name = manifest.display_name, + .description = manifest.description, + .short_description = manifest.short_description, + .version = manifest.version, + .authors = manifest.authors, + .dependencies = manifest.dependencies, + .runtime_toggleable = is_runtime_toggleable() + }; + } private: // Mapping of export name to function index. std::unordered_map exports_by_name; @@ -553,10 +572,12 @@ 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 &manifest_data, std::string &error_param); CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param); void initialize_mods(); void scan_mods(); + void close_mods(); std::filesystem::path get_mods_directory(); void enable_mod(const std::string& mod_id, bool enabled); bool is_mod_enabled(const std::string& mod_id); @@ -570,7 +591,6 @@ namespace recomp { ModContentTypeId register_mod_content_type(const ModContentType& type); bool register_mod_container_type(const std::string& extension, const std::vector& content_types, bool requires_manifest); - void register_config_exports(); } }; diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 4ce7a59..4a03c90 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -509,81 +509,81 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j return recomp::mods::ModOpenError::Good; } -recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const std::vector& manifest_data, std::string& error_param) { +recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const std::vector& manifest_data, std::string& error_param) { using json = nlohmann::json; json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false); if (manifest_json.is_discarded()) { - return recomp::mods::ModOpenError::FailedToParseManifest; + return ModOpenError::FailedToParseManifest; } if (!manifest_json.is_object()) { - return recomp::mods::ModOpenError::InvalidManifestSchema; + return ModOpenError::InvalidManifestSchema; } - recomp::mods::ModOpenError current_error = recomp::mods::ModOpenError::Good; + ModOpenError current_error = ModOpenError::Good; // Mod Game ID std::string mod_game_id{}; current_error = try_get(mod_game_id, manifest_json, game_mod_id_key, true, error_param); - if (current_error != recomp::mods::ModOpenError::Good) { + if (current_error != ModOpenError::Good) { return current_error; } ret.mod_game_ids.emplace_back(std::move(mod_game_id)); // Mod ID current_error = try_get(ret.mod_id, manifest_json, mod_id_key, true, error_param); - if (current_error != recomp::mods::ModOpenError::Good) { + if (current_error != ModOpenError::Good) { return current_error; } // Display name current_error = try_get(ret.display_name, manifest_json, display_name_key, true, error_param); - if (current_error != recomp::mods::ModOpenError::Good) { + if (current_error != ModOpenError::Good) { return current_error; } // Description (optional) current_error = try_get(ret.description, manifest_json, description_key, false, error_param); - if (current_error != recomp::mods::ModOpenError::Good) { + if (current_error != ModOpenError::Good) { return current_error; } // Short Description (optional) current_error = try_get(ret.short_description, manifest_json, short_description_key, false, error_param); - if (current_error != recomp::mods::ModOpenError::Good) { + if (current_error != ModOpenError::Good) { return current_error; } // Version - current_error = try_get_version(ret.version, manifest_json, version_key, error_param, recomp::mods::ModOpenError::InvalidVersionString); - if (current_error != recomp::mods::ModOpenError::Good) { + current_error = try_get_version(ret.version, manifest_json, version_key, error_param, ModOpenError::InvalidVersionString); + if (current_error != ModOpenError::Good) { return current_error; } // Authors current_error = try_get_vec(ret.authors, manifest_json, authors_key, true, error_param); - if (current_error != recomp::mods::ModOpenError::Good) { + if (current_error != ModOpenError::Good) { return current_error; } // Minimum recomp version - current_error = try_get_version(ret.minimum_recomp_version, manifest_json, minimum_recomp_version_key, error_param, recomp::mods::ModOpenError::InvalidMinimumRecompVersionString); - if (current_error != recomp::mods::ModOpenError::Good) { + current_error = try_get_version(ret.minimum_recomp_version, manifest_json, minimum_recomp_version_key, error_param, ModOpenError::InvalidMinimumRecompVersionString); + if (current_error != ModOpenError::Good) { return current_error; } // Dependencies (optional) std::vector dep_strings{}; current_error = try_get_vec(dep_strings, manifest_json, dependencies_key, false, error_param); - if (current_error != recomp::mods::ModOpenError::Good) { + if (current_error != ModOpenError::Good) { return current_error; } for (const std::string& dep_string : dep_strings) { - recomp::mods::Dependency cur_dep; + Dependency cur_dep; if (!parse_dependency(dep_string, cur_dep)) { error_param = dep_string; - return recomp::mods::ModOpenError::InvalidDependencyString; + return ModOpenError::InvalidDependencyString; } size_t dependency_index = ret.dependencies.size(); @@ -597,15 +597,15 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const auto& val = *find_libs_it; if (!val.is_object()) { error_param = native_libraries_key; - return recomp::mods::ModOpenError::IncorrectManifestFieldType; + return ModOpenError::IncorrectManifestFieldType; } for (const auto& [lib_name, lib_exports] : val.items()) { - recomp::mods::NativeLibraryManifest& cur_lib = ret.native_libraries.emplace_back(); + NativeLibraryManifest& cur_lib = ret.native_libraries.emplace_back(); cur_lib.name = lib_name; if (!get_to_vec(lib_exports, cur_lib.exports)) { error_param = native_libraries_key; - return recomp::mods::ModOpenError::IncorrectManifestFieldType; + return ModOpenError::IncorrectManifestFieldType; } } } @@ -616,30 +616,30 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const auto& val = *find_config_schema_it; if (!val.is_object()) { error_param = config_schema_key; - return recomp::mods::ModOpenError::IncorrectManifestFieldType; + return ModOpenError::IncorrectManifestFieldType; } auto options = val.find(config_schema_options_key); if (options != val.end()) { if (!options->is_array()) { error_param = config_schema_options_key; - return recomp::mods::ModOpenError::IncorrectManifestFieldType; + return ModOpenError::IncorrectManifestFieldType; } for (const json &option : *options) { - recomp::mods::ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param); - if (open_error != recomp::mods::ModOpenError::Good) { + ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param); + if (open_error != ModOpenError::Good) { return open_error; } } } else { error_param = config_schema_options_key; - return recomp::mods::ModOpenError::MissingConfigSchemaField; + return ModOpenError::MissingConfigSchemaField; } } - return recomp::mods::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) { diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 4b2ea0d..b58a701 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -598,6 +598,7 @@ void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, ConfigStor 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_order.emplace_back(mod_index); } @@ -626,6 +627,7 @@ void recomp::mods::ModContext::register_game(const std::string& mod_game_id) { void recomp::mods::ModContext::close_mods() { std::unique_lock lock(opened_mods_mutex); opened_mods_by_id.clear(); + opened_mods_by_filename.clear(); opened_mods.clear(); opened_mods_order.clear(); mod_ids.clear(); @@ -1066,7 +1068,27 @@ size_t recomp::mods::ModContext::num_opened_mods() { return opened_mods.size(); } -std::vector recomp::mods::ModContext::get_mod_details(const std::string &mod_game_id) { +std::string recomp::mods::ModContext::get_mod_id_from_filename(const std::filesystem::path& filename) const { + auto find_it = opened_mods_by_filename.find(filename.native()); + if (find_it == opened_mods_by_filename.end()) { + return {}; + } + + return opened_mods[find_it->second].manifest.mod_id; +} + +std::optional recomp::mods::ModContext::get_details_for_mod(const std::string& mod_id) const { + auto find_it = opened_mods_by_id.find(mod_id); + if (find_it == opened_mods_by_id.end()) { + return {}; + } + + size_t mod_index = find_it->second; + const ModHandle &mod = opened_mods[mod_index]; + return mod.get_details(); +} + +std::vector recomp::mods::ModContext::get_all_mod_details(const std::string &mod_game_id) { std::vector ret{}; bool all_games = mod_game_id.empty(); size_t game_index = (size_t)-1; @@ -1081,16 +1103,7 @@ std::vector recomp::mods::ModContext::get_mod_details( if (all_games || mod.is_for_game(game_index)) { std::vector cur_dependencies{}; - ret.emplace_back(ModDetails{ - .mod_id = mod.manifest.mod_id, - .display_name = mod.manifest.display_name, - .description = mod.manifest.description, - .short_description = mod.manifest.short_description, - .version = mod.manifest.version, - .authors = mod.manifest.authors, - .dependencies = mod.manifest.dependencies, - .runtime_toggleable = mod.is_runtime_toggleable() - }); + ret.emplace_back(mod.get_details()); } } diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 2ac8d46..9cdcce6 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -103,6 +103,13 @@ void recomp::mods::scan_mods() { mod_context->load_mods_config(); } +void recomp::mods::close_mods() { + { + std::lock_guard mod_lock{ mod_context_mutex }; + mod_context->close_mods(); + } +} + std::filesystem::path recomp::mods::get_mods_directory() { return config_path / mods_directory; } @@ -550,9 +557,19 @@ recomp::mods::ConfigValueVariant recomp::mods::get_mod_config_value(const std::s return mod_context->get_mod_config_value(mod_id, option_id); } -std::vector recomp::mods::get_mod_details(const std::string& mod_game_id) { +std::string recomp::mods::get_mod_id_from_filename(const std::filesystem::path& mod_filename) { std::lock_guard lock { mod_context_mutex }; - return mod_context->get_mod_details(mod_game_id); + return mod_context->get_mod_id_from_filename(mod_filename); +} + +std::optional recomp::mods::get_details_for_mod(const std::string& mod_id) { + std::lock_guard lock { mod_context_mutex }; + return mod_context->get_details_for_mod(mod_id); +} + +std::vector recomp::mods::get_all_mod_details(const std::string& mod_game_id) { + std::lock_guard lock { mod_context_mutex }; + return mod_context->get_all_mod_details(mod_game_id); } void recomp::mods::set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index) {