diff --git a/N64Recomp b/N64Recomp index 8781eb4..6e7a5bd 160000 --- a/N64Recomp +++ b/N64Recomp @@ -1 +1 @@ -Subproject commit 8781eb44acbf55cb6a109d2aa5529aadb95a419d +Subproject commit 6e7a5bdb2f1fff8b3d16122cc0b760d3c2541eae diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 5f1020a..3393f65 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -96,6 +96,7 @@ namespace recomp { }; std::string error_to_string(ModLoadError); + void unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg); enum class CodeModLoadError { Good, @@ -133,6 +134,19 @@ namespace recomp { String }; + enum class DependencyStatus { + // Do not change these values as they're exposed in the mod API! + + // The dependency was found and the version requirement was met. + Found = 0, + // The ID given is not a dependency of the mod in question. + InvalidDependency = 1, + // The dependency was not found. + NotFound = 2, + // The dependency was found, but the version requirement was not met. + WrongVersion = 3 + }; + struct ModFileHandle { virtual ~ModFileHandle() = default; virtual std::vector read_file(const std::string& filepath, bool& exists) const = 0; @@ -171,6 +185,7 @@ namespace recomp { struct Dependency { std::string mod_id; Version version; + bool optional; }; struct ConfigOptionEnum { @@ -363,6 +378,10 @@ namespace recomp { bool register_container_type(const std::string& extension, const std::vector& content_types, bool requires_manifest); ModContentTypeId get_code_content_type() const { return code_content_type_id; } bool is_content_runtime_toggleable(ModContentTypeId content_type) const; + std::string get_mod_display_name(size_t mod_index) const; + std::filesystem::path get_mod_path(size_t mod_index) const; + std::pair get_mod_import_info(size_t mod_index, size_t import_index) const; + DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id) const; private: ModOpenError open_mod_from_manifest(ModManifest &manifest, std::string &error_param, const std::vector &supported_content_types, bool requires_manifest); ModOpenError open_mod_from_path(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest); @@ -621,6 +640,10 @@ namespace recomp { size_t get_mod_order_index(size_t mod_index); 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); + std::string get_mod_display_name(size_t mod_index); + std::filesystem::path get_mod_path(size_t mod_index); + std::pair get_mod_import_info(size_t mod_index, size_t import_index); + DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id); void register_config_exports(); } diff --git a/librecomp/src/mod_config_api.cpp b/librecomp/src/mod_config_api.cpp index 14712a1..73e9169 100644 --- a/librecomp/src/mod_config_api.cpp +++ b/librecomp/src/mod_config_api.cpp @@ -96,6 +96,18 @@ void recomp_get_mod_folder_path(uint8_t* rdram, recomp_context* ctx) { return_string(rdram, ctx, std::filesystem::absolute(mod_folder_path).u8string()); } +void recomp_get_mod_file_path(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { + std::filesystem::path mod_file_path = recomp::mods::get_mod_path(mod_index); + + return_string(rdram, ctx, std::filesystem::absolute(mod_file_path).u8string()); +} + +void recomp_is_dependency_met(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { + std::string dependency_id = _arg_string<0>(rdram, ctx); + recomp::mods::DependencyStatus status = recomp::mods::is_dependency_met(mod_index, dependency_id); + _return(ctx, static_cast(status)); +} + void recomp::mods::register_config_exports() { recomp::overlays::register_ext_base_export("recomp_get_config_u32", recomp_get_config_u32); recomp::overlays::register_ext_base_export("recomp_get_config_double", recomp_get_config_double); @@ -105,4 +117,6 @@ void recomp::mods::register_config_exports() { recomp::overlays::register_ext_base_export("recomp_change_save_file", recomp_change_save_file); recomp::overlays::register_base_export("recomp_get_save_file_path", recomp_get_save_file_path); recomp::overlays::register_base_export("recomp_get_mod_folder_path", recomp_get_mod_folder_path); + recomp::overlays::register_ext_base_export("recomp_get_mod_file_path", recomp_get_mod_file_path); + recomp::overlays::register_ext_base_export("recomp_is_dependency_met", recomp_is_dependency_met); } diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 4bccf00..b41ec12 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -181,6 +181,7 @@ const std::string authors_key = "authors"; const std::string minimum_recomp_version_key = "minimum_recomp_version"; const std::string enabled_by_default_key = "enabled_by_default"; const std::string dependencies_key = "dependencies"; +const std::string optional_dependencies_key = "optional_dependencies"; const std::string native_libraries_key = "native_libraries"; const std::string config_schema_key = "config_schema"; @@ -602,6 +603,26 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const error_param = dep_string; return ModOpenError::InvalidDependencyString; } + cur_dep.optional = false; + + size_t dependency_index = ret.dependencies.size(); + ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index); + ret.dependencies.emplace_back(std::move(cur_dep)); + } + + // Optional dependencies (optional) + std::vector optional_dep_strings{}; + current_error = try_get_vec(optional_dep_strings, manifest_json, optional_dependencies_key, false, error_param); + if (current_error != ModOpenError::Good) { + return current_error; + } + for (const std::string& dep_string : optional_dep_strings) { + Dependency cur_dep; + if (!parse_dependency(dep_string, cur_dep)) { + error_param = dep_string; + return ModOpenError::InvalidDependencyString; + } + cur_dep.optional = true; size_t dependency_index = ret.dependencies.size(); ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index); diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 36b1373..7d9404f 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -975,6 +975,45 @@ bool recomp::mods::ModContext::register_container_type(const std::string& extens return true; } +std::string recomp::mods::ModContext::get_mod_display_name(size_t mod_index) const { + return opened_mods[mod_index].manifest.display_name; +} + +std::filesystem::path recomp::mods::ModContext::get_mod_path(size_t mod_index) const { + return opened_mods[mod_index].manifest.mod_root_path; +} + +std::pair recomp::mods::ModContext::get_mod_import_info(size_t mod_index, size_t import_index) const { + const ModHandle& mod = opened_mods[mod_index]; + const N64Recomp::ImportSymbol& imported_func = mod.recompiler_context->import_symbols[import_index]; + const std::string& dependency_id = mod.recompiler_context->dependencies[imported_func.dependency_index]; + + return std::make_pair(std::string{ dependency_id }, std::string{ imported_func.base.name }); +} + +recomp::mods::DependencyStatus recomp::mods::ModContext::is_dependency_met(size_t mod_index, const std::string& dependency_id) const { + const ModHandle& mod = opened_mods[mod_index]; + + auto find_dep = mod.manifest.dependencies_by_id.find(dependency_id); + if (find_dep == mod.manifest.dependencies_by_id.end()) { + return DependencyStatus::InvalidDependency; + } + + auto find_dep_mod = loaded_mods_by_id.find(dependency_id); + if (find_dep_mod == loaded_mods_by_id.end()) { + return DependencyStatus::NotFound; + } + + const Dependency& dep = mod.manifest.dependencies[find_dep->second]; + const ModHandle& dep_mod = opened_mods[find_dep_mod->second]; + + if (dep_mod.manifest.version < dep.version) { + return DependencyStatus::WrongVersion; + } + + return DependencyStatus::Found; +} + bool recomp::mods::ModContext::is_content_runtime_toggleable(ModContentTypeId content_type) const { assert(content_type.value < content_types.size()); @@ -1026,7 +1065,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable if (mod_from_stack_it != opened_mods_by_id.end()) { const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second]; for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) { - if (!auto_enabled_mods.contains(dependency.mod_id)) { + if (!dependency.optional && !auto_enabled_mods.contains(dependency.mod_id)) { auto_enabled_mods.emplace(dependency.mod_id); mod_stack.emplace_back(dependency.mod_id); @@ -1071,7 +1110,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable if (mod_from_stack_it != opened_mods_by_id.end()) { const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second]; for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) { - if (!new_auto_enabled_mods.contains(dependency.mod_id)) { + if (!dependency.optional && !new_auto_enabled_mods.contains(dependency.mod_id)) { new_auto_enabled_mods.emplace(dependency.mod_id); mod_stack.emplace_back(dependency.mod_id); } @@ -2038,25 +2077,28 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, mod.disable_runtime_toggle(); } for (const recomp::mods::Dependency& cur_dep : mod.manifest.dependencies) { - // Look for the dependency in the loaded mod mapping. - auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id); - if (find_loaded_dep_it == loaded_mods_by_id.end()) { - errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id); - continue; - } + if (!cur_dep.optional) { + // Look for the dependency in the loaded mod mapping. + auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id); + if (find_loaded_dep_it != loaded_mods_by_id.end()) { + ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second]; + if (cur_dep.version > dep_mod.manifest.version) + { + std::stringstream error_param_stream{}; + error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " << + (int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " << + (int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << ""; + errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str()); + } - ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second]; - if (cur_dep.version > dep_mod.manifest.version) - { - std::stringstream error_param_stream{}; - error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " << - (int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " << - (int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << ""; - errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str()); + // Prevent the dependency from being toggled at runtime, as it's required for this mod. + dep_mod.disable_runtime_toggle(); + } + // Add an error for this mod if the dependency isn't optional. + else { + errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id); + } } - - // Prevent the dependency from being toggled at runtime, as it's required for this mod. - dep_mod.disable_runtime_toggle(); } } @@ -2362,14 +2404,24 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci } else { auto find_mod_it = loaded_mods_by_id.find(dependency_id); - if (find_mod_it == loaded_mods_by_id.end()) { - error_param = "Failed to find import dependency while loading code: " + dependency_id; - // This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list - // is validated against the manifest's. - return CodeModLoadError::InternalError; + if (find_mod_it != loaded_mods_by_id.end()) { + const auto& dependency = opened_mods[find_mod_it->second]; + did_find_func = dependency.get_export_function(imported_func.base.name, func_handle); + } + else { + auto find_optional_it = mod.manifest.dependencies_by_id.find(dependency_id); + if (find_optional_it != mod.manifest.dependencies_by_id.end()) { + uintptr_t shim_argument = ((import_index & 0xFFFFFFFFu) << 32) | (mod_index & 0xFFFFFFFFu); + func_handle = shim_functions.emplace_back(std::make_unique(unmet_dependency_handler, shim_argument)).get()->get_func(); + did_find_func = true; + } + else { + error_param = "Failed to find import dependency while loading code: " + dependency_id; + // This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list + // is validated against the manifest's. + return CodeModLoadError::InternalError; + } } - const auto& dependency = opened_mods[find_mod_it->second]; - did_find_func = dependency.get_export_function(imported_func.base.name, func_handle); } if (!did_find_func) { @@ -2503,3 +2555,18 @@ void recomp::mods::ModContext::unload_mods() { num_events = recomp::overlays::num_base_events(); active_game = (size_t)-1; } + +void recomp::mods::unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg) { + size_t caller_mod_index = (arg >> 0) & uint64_t(0xFFFFFFFF); + size_t import_index = (arg >> 32) & uint64_t(0xFFFFFFFF); + + std::string mod_name = recomp::mods::get_mod_display_name(caller_mod_index); + std::pair import_info = recomp::mods::get_mod_import_info(caller_mod_index, import_index); + + ultramodern::error_handling::message_box( + ( + "Fatal error in mod \"" + mod_name + "\": Called function \"" + import_info.second + "\" in unmet optional dependency \"" + import_info.first + "\".\n" + ).c_str() + ); + ULTRAMODERN_QUICK_EXIT(); +} diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index ddf7a02..8a03dbd 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -129,6 +129,26 @@ bool recomp::mods::register_mod_container_type(const std::string& extension, con return mod_context->register_container_type(extension, content_types, requires_manifest); } +std::string recomp::mods::get_mod_display_name(size_t mod_index) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->get_mod_display_name(mod_index); +} + +std::filesystem::path recomp::mods::get_mod_path(size_t mod_index) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->get_mod_path(mod_index); +} + +std::pair recomp::mods::get_mod_import_info(size_t mod_index, size_t import_index) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->get_mod_import_info(mod_index, import_index); +} + +recomp::mods::DependencyStatus recomp::mods::is_dependency_met(size_t mod_index, const std::string& dependency_id) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->is_dependency_met(mod_index, dependency_id); +} + bool check_hash(const std::vector& rom_data, uint64_t expected_hash) { uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size()); return calculated_hash == expected_hash;