From 25e8bcc1e1ac2ed98b4534628cc075e242bda45a Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:47:00 -0400 Subject: [PATCH] Added mechanism for recomps to declare custom mod content types and container formats (#64) Also adds a way to specify whether a given content type can be toggled at runtime. The built-in mod code content type has this setting disabled. --- librecomp/include/librecomp/mods.hpp | 105 +++++- librecomp/src/mod_manifest.cpp | 141 +++++--- librecomp/src/mods.cpp | 491 +++++++++++++++++---------- librecomp/src/recomp.cpp | 11 +- 4 files changed, 516 insertions(+), 232 deletions(-) diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 6e3ad46..00ec367 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -54,9 +54,20 @@ namespace recomp { Good, InvalidGame, MinimumRecompVersionNotMet, + MissingDependency, + WrongDependencyVersion, + FailedToLoadCode, + }; + + std::string error_to_string(ModLoadError); + + enum class CodeModLoadError { + Good, + InternalError, HasSymsButNoBinary, HasBinaryButNoSyms, FailedToParseSyms, + MissingDependencyInManifest, FailedToLoadNativeCode, FailedToLoadNativeLibrary, FailedToFindNativeExport, @@ -66,16 +77,13 @@ namespace recomp { InvalidFunctionReplacement, FailedToFindReplacement, ReplacementConflict, - MissingDependencyInManifest, - MissingDependency, - WrongDependencyVersion, ModConflict, DuplicateExport, NoSpecifiedApiVersion, UnsupportedApiVersion, }; - std::string error_to_string(ModLoadError); + std::string error_to_string(CodeModLoadError); struct ModFileHandle { virtual ~ModFileHandle() = default; @@ -121,6 +129,7 @@ namespace recomp { Version version; std::vector authors; std::vector dependencies; + bool runtime_toggleable; }; struct ModManifest { @@ -133,6 +142,7 @@ namespace recomp { std::unordered_map dependencies_by_id; Version minimum_recomp_version; Version version; + bool runtime_toggleable; std::vector native_libraries; std::unique_ptr file_handle; @@ -155,10 +165,7 @@ namespace recomp { ModLoadErrorDetails(const std::string& mod_id_, ModLoadError error_, const std::string& error_param_) : mod_id(mod_id_), error(error_), error_param(error_param_) {} }; - - void scan_mods(); - void enable_mod(const std::string& mod_id, bool enabled); - bool is_mod_enabled(const std::string& mod_id); + std::vector get_mod_details(const std::string& mod_game_id); // Internal functions, TODO move to an internal header. @@ -169,7 +176,38 @@ namespace recomp { using GenericFunction = std::variant; + class ModContext; class ModHandle; + using content_enabled_callback = void(ModContext&, const ModHandle&); + using content_disabled_callback = void(ModContext&, const ModHandle&); + + struct ModContentType { + // The file that's used to indicate that a mod contains this content type. + // If a mod contains this file, then it has this content type. + std::string content_filename; + // Whether or not this type of content can be toggled at runtime. + bool allow_runtime_toggle; + // Function to call when an instance of this content type is enabled. + content_enabled_callback* on_enabled; + // Function to call when an instance of this content type is disabled. + content_disabled_callback* on_disabled; + }; + + // Holds IDs for mod content types, which get assigned as they're registered. + // This is just a wrapper around a number for type safety purposes. + struct ModContentTypeId { + size_t value; + }; + + struct ModContainerType { + // The types of content that this container is allowed to have. + // Leaving this empty will allow the container to have any type of content. + std::vector supported_content_types; + // Whether or not this container requires a manifest to be treated as a valid mod. + // If no manifest is present, a default one will be created. + bool requires_manifest; + }; + class ModContext { public: ModContext(); @@ -183,22 +221,34 @@ namespace recomp { std::vector load_mods(const std::string& mod_game_id, 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); + ModContentTypeId register_content_type(const ModContentType& type); + 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; private: - ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param); - ModLoadError load_mod(uint8_t* rdram, const std::unordered_map& section_map, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param); + ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest); + ModLoadError load_mod(recomp::mods::ModHandle& mod, std::string& error_param); void check_dependencies(recomp::mods::ModHandle& mod, std::vector>& errors); - ModLoadError load_mod_code(recomp::mods::ModHandle& mod, std::string& error_param); - ModLoadError resolve_dependencies(recomp::mods::ModHandle& mod, std::string& error_param); - void add_opened_mod(ModManifest&& manifest, std::vector&& game_indices); + CodeModLoadError load_mod_code(uint8_t* rdram, const std::unordered_map& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param); + CodeModLoadError resolve_code_dependencies(recomp::mods::ModHandle& mod, std::string& error_param); + void add_opened_mod(ModManifest&& manifest, std::vector&& game_indices, std::vector&& detected_content_types); + static void on_code_mod_enabled(ModContext& context, const ModHandle& mod); + + std::vector content_types; + std::unordered_map container_types; // Maps game mod ID to the mod's internal integer ID. std::unordered_map mod_game_ids; std::vector opened_mods; + std::unordered_map opened_mods_by_id; std::unordered_set mod_ids; std::unordered_set enabled_mods; std::unordered_map patched_funcs; std::unordered_map loaded_mods_by_id; + std::vector loaded_code_mods; size_t num_events = 0; + ModContentTypeId code_content_type_id; + size_t active_game = (size_t)-1; }; class ModCodeHandle { @@ -229,8 +279,10 @@ namespace recomp { std::unique_ptr code_handle; std::unique_ptr recompiler_context; std::vector section_load_addresses; + // Content types present in this mod. + std::vector content_types; - ModHandle(ModManifest&& manifest, std::vector&& game_indices); + ModHandle(const ModContext& context, ModManifest&& manifest, std::vector&& game_indices, std::vector&& content_types); ModHandle(const ModHandle& rhs) = delete; ModHandle& operator=(const ModHandle& rhs) = delete; ModHandle(ModHandle&& rhs); @@ -240,16 +292,24 @@ namespace recomp { size_t num_exports() const; size_t num_events() const; - ModLoadError populate_exports(std::string& error_param); + CodeModLoadError populate_exports(std::string& error_param); bool get_export_function(const std::string& export_name, GenericFunction& out) const; - ModLoadError populate_events(size_t base_event_index, std::string& error_param); + CodeModLoadError populate_events(size_t base_event_index, std::string& error_param); bool get_global_event_index(const std::string& event_name, size_t& event_index_out) const; - ModLoadError load_native_library(const NativeLibraryManifest& lib_manifest, std::string& error_param); + CodeModLoadError load_native_library(const NativeLibraryManifest& lib_manifest, std::string& error_param); bool is_for_game(size_t game_index) const { auto find_it = std::find(game_indices.begin(), game_indices.end(), game_index); return find_it != game_indices.end(); } + + bool is_runtime_toggleable() const { + return runtime_toggleable; + } + + void disable_runtime_toggle() { + runtime_toggleable = false; + } private: // Mapping of export name to function index. std::unordered_map exports_by_name; @@ -261,6 +321,8 @@ namespace recomp { std::vector> native_libraries; // Vector of pointers so that implementation can be elsewhere. // Games that this mod supports. std::vector game_indices; + // Whether this mod can be toggled at runtime. + bool runtime_toggleable; }; class NativeCodeHandle : public ModCodeHandle { @@ -327,7 +389,14 @@ namespace recomp { void setup_events(size_t num_events); void register_event_callback(size_t event_index, GenericFunction callback); void reset_events(); - ModLoadError validate_api_version(uint32_t api_version, std::string& error_param); + CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param); + + + void scan_mods(); + void enable_mod(const std::string& mod_id, bool enabled); + bool is_mod_enabled(const std::string& mod_id); + 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); } }; diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 4207840..ba3073f 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -368,7 +368,7 @@ recomp::mods::ModOpenError validate_manifest(const recomp::mods::ModManifest& ma return ModOpenError::Good; } -recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesystem::path& mod_path, std::string& error_param) { +recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest) { ModManifest manifest{}; std::error_code ec; error_param = ""; @@ -408,12 +408,37 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys bool exists; std::vector manifest_data = manifest.file_handle->read_file("manifest.json", exists); if (!exists) { - return ModOpenError::NoManifest; - } + // If this container type requires a manifest then return an error. + if (requires_manifest) { + return ModOpenError::NoManifest; + } + // Otherwise, create a default manifest. + else { + // Take the file handle from the manifest before clearing it so that it can be reassigned afterwards. + std::unique_ptr file_handle = std::move(manifest.file_handle); + manifest = {}; + manifest.file_handle = std::move(file_handle); + + for (const auto& [key, val] : mod_game_ids) { + manifest.mod_game_ids.emplace_back(key); + } - ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param); - if (parse_error != ModOpenError::Good) { - return parse_error; + manifest.mod_id = mod_path.stem().string(); + manifest.authors = { "Unknown" }; + + manifest.minimum_recomp_version.major = 0; + manifest.minimum_recomp_version.minor = 0; + manifest.minimum_recomp_version.patch = 0; + manifest.version.major = 0; + manifest.version.minor = 0; + manifest.version.patch = 0; + } + } + else { + ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param); + if (parse_error != ModOpenError::Good) { + return parse_error; + } } } @@ -439,10 +464,33 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys } game_indices.emplace_back(find_id_it->second); } + + // Scan for content types present in this mod. + std::vector detected_content_types; + + auto scan_for_content_type = [&detected_content_types, &manifest](ModContentTypeId type_id, std::vector& content_types) { + const ModContentType& content_type = content_types[type_id.value]; + if (manifest.file_handle->file_exists(content_type.content_filename)) { + detected_content_types.emplace_back(type_id); + } + }; + + // If the mod has a list of specific content types, scan for only those. + if (!supported_content_types.empty()) { + for (ModContentTypeId content_type_id : supported_content_types) { + scan_for_content_type(content_type_id, content_types); + } + } + // Otherwise, scan for all content types. + else { + for (size_t content_type_index = 0; content_type_index < content_types.size(); content_type_index++) { + scan_for_content_type(ModContentTypeId{.value = content_type_index}, content_types); + } + } // Store the loaded mod manifest in a new mod handle. manifest.mod_root_path = mod_path; - add_opened_mod(std::move(manifest), std::move(game_indices)); + add_opened_mod(std::move(manifest), std::move(game_indices), std::move(detected_content_types)); return ModOpenError::Good; } @@ -493,44 +541,55 @@ std::string recomp::mods::error_to_string(ModLoadError error) { return "Invalid game"; case ModLoadError::MinimumRecompVersionNotMet: return "Mod requires a newer version of this project"; - case ModLoadError::HasSymsButNoBinary: - return "Mod has a symbol file but no binary file"; - case ModLoadError::HasBinaryButNoSyms: - return "Mod has a binary file but no symbol file"; - case ModLoadError::FailedToParseSyms: - return "Failed to parse mod symbol file"; - case ModLoadError::FailedToLoadNativeCode: - return "Failed to load offline mod library"; - case ModLoadError::FailedToLoadNativeLibrary: - return "Failed to load mod library"; - case ModLoadError::FailedToFindNativeExport: - return "Failed to find native export"; - case ModLoadError::InvalidReferenceSymbol: - return "Reference symbol does not exist"; - case ModLoadError::InvalidImport: - return "Imported function not found"; - case ModLoadError::InvalidCallbackEvent: - return "Event for callback not found"; - case ModLoadError::InvalidFunctionReplacement: - return "Function to be replaced does not exist"; - case ModLoadError::FailedToFindReplacement: - return "Failed to find replacement function"; - case ModLoadError::ReplacementConflict: - return "Attempted to replace a function that cannot be replaced"; - case ModLoadError::MissingDependencyInManifest: - return "Dependency is present in mod symbols but not in the manifest"; case ModLoadError::MissingDependency: return "Missing dependency"; case ModLoadError::WrongDependencyVersion: return "Wrong dependency version"; - case ModLoadError::ModConflict: - return "Conflicts with other mod"; - case ModLoadError::DuplicateExport: - return "Duplicate exports in mod"; - case ModLoadError::NoSpecifiedApiVersion: - return "Mod DLL does not specify an API version"; - case ModLoadError::UnsupportedApiVersion: - return "Mod DLL has an unsupported API version"; + case ModLoadError::FailedToLoadCode: + return "Failed to load mod code"; } return "Unknown mod loading error " + std::to_string((int)error); } + +std::string recomp::mods::error_to_string(CodeModLoadError error) { + switch (error) { + case CodeModLoadError::Good: + return "Good"; + case CodeModLoadError::InternalError: + return "Code mod loading internal error"; + case CodeModLoadError::HasSymsButNoBinary: + return "Mod has a symbol file but no binary file"; + case CodeModLoadError::HasBinaryButNoSyms: + return "Mod has a binary file but no symbol file"; + case CodeModLoadError::FailedToParseSyms: + return "Failed to parse mod symbol file"; + case CodeModLoadError::MissingDependencyInManifest: + return "Dependency is present in mod symbols but not in the manifest"; + case CodeModLoadError::FailedToLoadNativeCode: + return "Failed to load offline mod library"; + case CodeModLoadError::FailedToLoadNativeLibrary: + return "Failed to load mod library"; + case CodeModLoadError::FailedToFindNativeExport: + return "Failed to find native export"; + case CodeModLoadError::InvalidReferenceSymbol: + return "Reference symbol does not exist"; + case CodeModLoadError::InvalidImport: + return "Imported function not found"; + case CodeModLoadError::InvalidCallbackEvent: + return "Event for callback not found"; + case CodeModLoadError::InvalidFunctionReplacement: + return "Function to be replaced does not exist"; + case CodeModLoadError::FailedToFindReplacement: + return "Failed to find replacement function"; + case CodeModLoadError::ReplacementConflict: + return "Attempted to replace a function that cannot be replaced"; + case CodeModLoadError::ModConflict: + return "Conflicts with other mod"; + case CodeModLoadError::DuplicateExport: + return "Duplicate exports in mod"; + case CodeModLoadError::NoSpecifiedApiVersion: + return "Mod DLL does not specify an API version"; + case CodeModLoadError::UnsupportedApiVersion: + return "Mod DLL has an unsupported API version"; + } +} diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 6ab7592..6fada47 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "librecomp/mods.hpp" #include "librecomp/overlays.hpp" @@ -194,29 +195,37 @@ void protect(void* target_func, uint64_t old_flags) { #endif namespace modpaths { - const std::string binary_path = "mod_binary.bin"; - const std::string binary_syms_path = "mod_syms.bin"; + constexpr std::string_view default_mod_extension = "nrm"; + constexpr std::string_view binary_path = "mod_binary.bin"; + constexpr std::string_view binary_syms_path = "mod_syms.bin"; }; -recomp::mods::ModLoadError recomp::mods::validate_api_version(uint32_t api_version, std::string& error_param) { +recomp::mods::CodeModLoadError recomp::mods::validate_api_version(uint32_t api_version, std::string& error_param) { switch (api_version) { case 1: - return ModLoadError::Good; + return CodeModLoadError::Good; case (uint32_t)-1: - return ModLoadError::NoSpecifiedApiVersion; + return CodeModLoadError::NoSpecifiedApiVersion; default: error_param = std::to_string(api_version); - return ModLoadError::UnsupportedApiVersion; + return CodeModLoadError::UnsupportedApiVersion; } } -recomp::mods::ModHandle::ModHandle(ModManifest&& manifest, std::vector&& game_indices) : +recomp::mods::ModHandle::ModHandle(const ModContext& context, ModManifest&& manifest, std::vector&& game_indices, std::vector&& content_types) : manifest(std::move(manifest)), code_handle(), recompiler_context{std::make_unique()}, + content_types{std::move(content_types)}, game_indices{std::move(game_indices)} { - + runtime_toggleable = true; + for (ModContentTypeId type : this->content_types) { + if (!context.is_content_runtime_toggleable(type)) { + runtime_toggleable = false; + break; + } + } } recomp::mods::ModHandle::ModHandle(ModHandle&& rhs) = default; @@ -231,16 +240,16 @@ size_t recomp::mods::ModHandle::num_events() const { return recompiler_context->event_symbols.size(); } -recomp::mods::ModLoadError recomp::mods::ModHandle::populate_exports(std::string& error_param) { +recomp::mods::CodeModLoadError recomp::mods::ModHandle::populate_exports(std::string& error_param) { for (size_t func_index : recompiler_context->exported_funcs) { const auto& func_handle = recompiler_context->functions[func_index]; exports_by_name.emplace(func_handle.name, func_index); } - return ModLoadError::Good; + return CodeModLoadError::Good; } -recomp::mods::ModLoadError recomp::mods::ModHandle::load_native_library(const recomp::mods::NativeLibraryManifest& lib_manifest, std::string& error_param) { +recomp::mods::CodeModLoadError recomp::mods::ModHandle::load_native_library(const recomp::mods::NativeLibraryManifest& lib_manifest, std::string& error_param) { std::string lib_filename = lib_manifest.name + std::string{DynamicLibrary::PlatformExtension}; std::filesystem::path lib_path = manifest.mod_root_path.parent_path() / lib_filename; @@ -248,13 +257,13 @@ recomp::mods::ModLoadError recomp::mods::ModHandle::load_native_library(const re if (!lib->good()) { error_param = lib_filename; - return ModLoadError::FailedToLoadNativeLibrary; + return CodeModLoadError::FailedToLoadNativeLibrary; } std::string api_error_param; - ModLoadError api_error = validate_api_version(lib->get_api_version(), api_error_param); + CodeModLoadError api_error = validate_api_version(lib->get_api_version(), api_error_param); - if (api_error != ModLoadError::Good) { + if (api_error != CodeModLoadError::Good) { if (api_error_param.empty()) { error_param = lib_filename; } @@ -268,16 +277,16 @@ recomp::mods::ModLoadError recomp::mods::ModHandle::load_native_library(const re recomp_func_t* cur_func; if (native_library_exports.contains(export_name)) { error_param = export_name; - return ModLoadError::DuplicateExport; + return CodeModLoadError::DuplicateExport; } if (!lib->get_dll_symbol(cur_func, export_name.c_str())) { error_param = lib_manifest.name + ":" + export_name; - return ModLoadError::FailedToFindNativeExport; + return CodeModLoadError::FailedToFindNativeExport; } native_library_exports.emplace(export_name, cur_func); } - return ModLoadError::Good; + return CodeModLoadError::Good; } bool recomp::mods::ModHandle::get_export_function(const std::string& export_name, GenericFunction& out) const { @@ -300,14 +309,14 @@ bool recomp::mods::ModHandle::get_export_function(const std::string& export_name return false; } -recomp::mods::ModLoadError recomp::mods::ModHandle::populate_events(size_t base_event_index, std::string& error_param) { +recomp::mods::CodeModLoadError recomp::mods::ModHandle::populate_events(size_t base_event_index, std::string& error_param) { for (size_t event_index = 0; event_index < recompiler_context->event_symbols.size(); event_index++) { const N64Recomp::EventSymbol& event = recompiler_context->event_symbols[event_index]; events_by_name.emplace(event.base.name, event_index); } code_handle->set_base_event_index(base_event_index); - return ModLoadError::Good; + return CodeModLoadError::Good; } bool recomp::mods::ModHandle::get_global_event_index(const std::string& event_name, size_t& event_index_out) const { @@ -424,88 +433,26 @@ void unpatch_func(void* target_func, const recomp::mods::PatchData& data) { protect(target_func, old_flags); } -void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vector&& game_indices) { - opened_mods.emplace_back(std::move(manifest), std::move(game_indices)); +void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vector&& game_indices, std::vector&& detected_content_types) { + size_t mod_index = opened_mods.size(); + opened_mods_by_id.emplace(manifest.mod_id, mod_index); + opened_mods.emplace_back(*this, std::move(manifest), std::move(game_indices), std::move(detected_content_types)); } -recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, const std::unordered_map& section_vrom_map, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param) { +recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(recomp::mods::ModHandle& mod, std::string& error_param) { using namespace recomp::mods; - handle.section_load_addresses.clear(); + mod.section_load_addresses.clear(); // Check that the mod's minimum recomp version is met. - if (get_project_version() < handle.manifest.minimum_recomp_version) { - error_param = handle.manifest.minimum_recomp_version.to_string(); - return recomp::mods::ModLoadError::MinimumRecompVersionNotMet; + if (get_project_version() < mod.manifest.minimum_recomp_version) { + error_param = mod.manifest.minimum_recomp_version.to_string(); + return ModLoadError::MinimumRecompVersionNotMet; + } + + for (ModContentTypeId type_id : mod.content_types) { + content_types[type_id.value].on_enabled(*this, mod); } - // Load the mod symbol data from the file provided in the manifest. - bool binary_syms_exists = false; - std::vector syms_data = handle.manifest.file_handle->read_file(modpaths::binary_syms_path, binary_syms_exists); - - // Load the binary data from the file provided in the manifest. - bool binary_exists = false; - std::vector binary_data = handle.manifest.file_handle->read_file(modpaths::binary_path, binary_exists); - - if (binary_syms_exists && !binary_exists) { - return recomp::mods::ModLoadError::HasSymsButNoBinary; - } - - if (binary_exists && !binary_syms_exists) { - return recomp::mods::ModLoadError::HasBinaryButNoSyms; - } - - std::span binary_span {reinterpret_cast(binary_data.data()), binary_data.size() }; - - // Parse the symbol file into the recompiler context. - N64Recomp::ModSymbolsError symbol_load_error = N64Recomp::parse_mod_symbols(syms_data, binary_span, section_vrom_map, *handle.recompiler_context); - if (symbol_load_error != N64Recomp::ModSymbolsError::Good) { - return ModLoadError::FailedToParseSyms; - } - - const std::vector& mod_sections = handle.recompiler_context->sections; - handle.section_load_addresses.resize(mod_sections.size()); - - // Copy each section's binary into rdram, leaving room for the section's bss before the next one. - int32_t cur_section_addr = load_address; - for (size_t section_index = 0; section_index < mod_sections.size(); section_index++) { - const auto& section = mod_sections[section_index]; - for (size_t i = 0; i < section.size; i++) { - MEM_B(i, (gpr)cur_section_addr) = binary_data[section.rom_addr + i]; - } - handle.section_load_addresses[section_index] = cur_section_addr; - cur_section_addr += section.size + section.bss_size; - - } - - // Iterate over each section again after loading them to perform R_MIPS_32 relocations. - for (size_t section_index = 0; section_index < mod_sections.size(); section_index++) { - const auto& section = mod_sections[section_index]; - uint32_t cur_section_original_vram = section.ram_addr; - uint32_t cur_section_loaded_vram = handle.section_load_addresses[section_index]; - - // Perform mips32 relocations for this section. - for (const auto& reloc : section.relocs) { - if (reloc.type == N64Recomp::RelocType::R_MIPS_32 && !reloc.reference_symbol) { - if (reloc.target_section >= mod_sections.size()) { - return ModLoadError::FailedToParseSyms; - } - // Get the ram address of the word that's being relocated and read its original value. - int32_t reloc_word_addr = reloc.address - cur_section_original_vram + cur_section_loaded_vram; - uint32_t reloc_word = MEM_W(0, reloc_word_addr); - - // Determine the original and loaded addresses of the section that the relocation points to. - uint32_t target_section_original_vram = mod_sections[reloc.target_section].ram_addr; - uint32_t target_section_loaded_vram = handle.section_load_addresses[reloc.target_section]; - - // Recalculate the word and write it back into ram. - reloc_word += (target_section_loaded_vram - target_section_original_vram); - MEM_W(0, reloc_word_addr) = reloc_word; - } - } - } - - ram_used = cur_section_addr - load_address; - return ModLoadError::Good; } @@ -517,10 +464,25 @@ std::vector recomp::mods::ModContext::scan_mo std::vector ret{}; std::error_code ec; for (const auto& mod_path : std::filesystem::directory_iterator{mod_folder, std::filesystem::directory_options::skip_permission_denied, ec}) { - if ((mod_path.is_regular_file() && mod_path.path().extension() == ".nrm") || mod_path.is_directory()) { + bool is_mod = false; + bool requires_manifest = true; + static const std::vector empty_content_types{}; + std::reference_wrapper> supported_content_types = std::cref(empty_content_types); + if (mod_path.is_regular_file()) { + auto find_container_it = container_types.find(mod_path.path().extension().string()); + if (find_container_it != container_types.end()) { + is_mod = true; + supported_content_types = find_container_it->second.supported_content_types; + requires_manifest = find_container_it->second.requires_manifest; + } + } + else if (mod_path.is_directory()) { + is_mod = true; + } + if (is_mod) { printf("Opening mod " PATHFMT "\n", mod_path.path().stem().c_str()); std::string open_error_param; - ModOpenError open_error = open_mod(mod_path, open_error_param); + ModOpenError open_error = open_mod(mod_path, open_error_param, supported_content_types, requires_manifest); if (open_error != ModOpenError::Good) { ret.emplace_back(mod_path.path(), open_error, open_error_param); @@ -534,16 +496,114 @@ std::vector recomp::mods::ModContext::scan_mo return ret; } -// Nothing needed for these two, they just need to be explicitly declared outside the header to allow forward declaration of ModHandle. -recomp::mods::ModContext::ModContext() = default; +recomp::mods::ModContext::ModContext() { + // Register the code content type. + ModContentType code_content_type { + .content_filename = std::string{modpaths::binary_syms_path}, + .allow_runtime_toggle = false, + .on_enabled = ModContext::on_code_mod_enabled, + .on_disabled = nullptr + }; + code_content_type_id = register_content_type(code_content_type); + + // Register the default mod container type (.nrm) and allow it to have any content type by passing an empty vector. + register_container_type(std::string{ modpaths::default_mod_extension }, {}, true); +} + +void recomp::mods::ModContext::on_code_mod_enabled(ModContext& context, const ModHandle& mod) { + auto find_mod_it = context.loaded_mods_by_id.find(mod.manifest.mod_id); + if (find_mod_it == context.loaded_mods_by_id.end()) { + assert(false && "Failed to find enabled code mod"); + } + else { + context.loaded_code_mods.emplace_back(find_mod_it->second); + } +} + +// Nothing needed for this, it just need to be explicitly declared outside the header to allow forward declaration of ModHandle. recomp::mods::ModContext::~ModContext() = default; +recomp::mods::ModContentTypeId recomp::mods::ModContext::register_content_type(const ModContentType& type) { + size_t ret = content_types.size(); + content_types.emplace_back(type); + + return ModContentTypeId{.value = ret}; +} + +bool recomp::mods::ModContext::register_container_type(const std::string& extension, const std::vector& container_content_types, bool requires_manifest) { + // Validate the provided content type IDs. + for (ModContentTypeId id : container_content_types) { + if (id.value >= content_types.size()) { + return false; + } + } + + // Validate that the extension doesn't contain a dot. + if (extension.find('.') != std::string::npos) { + return false; + } + + // Prepend a dot to the extension to get the real extension that will be registered.. + std::string true_extension = "." + extension; + + // Validate that this extension hasn't been registered already. + if (container_types.contains(true_extension)) { + return false; + } + + // Register the container type. + container_types.emplace(true_extension, + ModContainerType { + .supported_content_types = container_content_types, + .requires_manifest = requires_manifest + }); + + return true; +} + +bool recomp::mods::ModContext::is_content_runtime_toggleable(ModContentTypeId content_type) const { + assert(content_type.value < content_types.size()); + + return content_types[content_type.value].allow_runtime_toggle; +} + void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enabled) { + // Check that the mod exists. + auto find_it = opened_mods_by_id.find(mod_id); + if (find_it == opened_mods_by_id.end()) { + return; + } + ModHandle& mod = opened_mods[find_it->second]; + + bool mods_loaded = active_game != (size_t)-1; + + // Do nothing if mods have already been loaded and this mod isn't runtime toggleable. + if (!mod.is_runtime_toggleable() && mods_loaded) { + return; + } + + // Do nothing if mods have already been loaded and this mod isn't for the active game. + if (mods_loaded && !mod.is_for_game(active_game)) { + return; + } + if (enabled) { - enabled_mods.emplace(mod_id); + bool was_enabled = enabled_mods.emplace(mod_id).second; + // If mods have been loaded and a mod was successfully enabled by this call, call the on_enabled handlers for its content types. + if (was_enabled && mods_loaded) { + for (ModContentTypeId type_id : mod.content_types) { + content_types[type_id.value].on_enabled(*this, mod); + } + } } else { - enabled_mods.erase(mod_id); + bool was_disabled = enabled_mods.erase(mod_id) != 0; + // If mods have been loaded and a mod was successfully disabled by this call, call the on_disabled handlers for its content types. + if (was_disabled && mods_loaded) { + for (ModContentTypeId type_id : mod.content_types) { + content_types[type_id.value].on_disabled(*this, mod); + } + } } } @@ -573,7 +633,8 @@ std::vector recomp::mods::ModContext::get_mod_details( .mod_id = mod.manifest.mod_id, .version = mod.manifest.version, .authors = mod.manifest.authors, - .dependencies = mod.manifest.dependencies + .dependencies = mod.manifest.dependencies, + .runtime_toggleable = mod.is_runtime_toggleable() }); } } @@ -611,17 +672,12 @@ std::vector recomp::mods::ModContext::load_mo loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index); printf("Loading mod %s\n", mod.manifest.mod_id.c_str()); - uint32_t cur_ram_used = 0; std::string load_error_param; - ModLoadError load_error = load_mod(rdram, section_vrom_map, mod, load_address, cur_ram_used, load_error_param); + ModLoadError load_error = load_mod(mod, load_error_param); if (load_error != ModLoadError::Good) { ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param); } - else { - load_address += cur_ram_used; - ram_used += cur_ram_used; - } } } @@ -651,12 +707,17 @@ std::vector recomp::mods::ModContext::load_mo } // Load the code and exports from all mods. - for (size_t mod_index : active_mods) { + for (size_t mod_index : loaded_code_mods) { + uint32_t cur_ram_used = 0; auto& mod = opened_mods[mod_index]; std::string cur_error_param; - ModLoadError cur_error = load_mod_code(mod, cur_error_param); - if (cur_error != ModLoadError::Good) { - ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param); + CodeModLoadError cur_error = load_mod_code(rdram, section_vrom_map, mod, load_address, cur_ram_used, cur_error_param); + if (cur_error != CodeModLoadError::Good) { + ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error) + ":" + cur_error_param); + } + else { + load_address += cur_ram_used; + ram_used += cur_ram_used; } } @@ -669,13 +730,13 @@ std::vector recomp::mods::ModContext::load_mo // Set up the event callbacks based on the number of events allocated. recomp::mods::setup_events(num_events); - // Resolve dependencies for all mods. - for (size_t mod_index : active_mods) { + // Resolve code dependencies for all mods. + for (size_t mod_index : loaded_code_mods) { auto& mod = opened_mods[mod_index]; std::string cur_error_param; - ModLoadError cur_error = resolve_dependencies(mod, cur_error_param); - if (cur_error != ModLoadError::Good) { - ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param); + CodeModLoadError cur_error = resolve_code_dependencies(mod, cur_error_param); + if (cur_error != CodeModLoadError::Good) { + ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error) + ":" + cur_error_param); } } @@ -685,11 +746,66 @@ std::vector recomp::mods::ModContext::load_mo return ret; } + active_game = mod_game_index; return ret; } void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector>& errors) { errors.clear(); + // Prevent mods with dependencies from being toggled at runtime. + // TODO make this possible. + if (!mod.manifest.dependencies.empty()) { + 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; + } + + 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(); + } +} + +recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* rdram, const std::unordered_map& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param) { + // Load the mod symbol data from the file provided in the manifest. + bool binary_syms_exists = false; + std::vector syms_data = mod.manifest.file_handle->read_file(std::string{ modpaths::binary_syms_path }, binary_syms_exists); + + // Load the binary data from the file provided in the manifest. + bool binary_exists = false; + std::vector binary_data = mod.manifest.file_handle->read_file(std::string{ modpaths::binary_path }, binary_exists); + + if (binary_syms_exists && !binary_exists) { + return CodeModLoadError::HasSymsButNoBinary; + } + + if (binary_exists && !binary_syms_exists) { + return CodeModLoadError::HasBinaryButNoSyms; + } + + std::span binary_span {reinterpret_cast(binary_data.data()), binary_data.size() }; + + // Parse the symbol file into the recompiler context. + N64Recomp::ModSymbolsError symbol_load_error = N64Recomp::parse_mod_symbols(syms_data, binary_span, section_vrom_map, *mod.recompiler_context); + if (symbol_load_error != N64Recomp::ModSymbolsError::Good) { + return CodeModLoadError::FailedToParseSyms; + } + + // Validate that the dependencies present in the symbol file are all present in the mod's manifest as well. for (const auto& [cur_dep_id, cur_dep_index] : mod.recompiler_context->dependencies_by_name) { // Handle special dependency names. if (cur_dep_id == N64Recomp::DependencyBaseRecomp || cur_dep_id == N64Recomp::DependencySelf) { @@ -699,59 +815,86 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, // Find the dependency in the mod manifest to get its version. auto find_manifest_dep_it = mod.manifest.dependencies_by_id.find(cur_dep_id); if (find_manifest_dep_it == mod.manifest.dependencies_by_id.end()) { - errors.emplace_back(ModLoadError::MissingDependencyInManifest, cur_dep_id); - continue; - } - const auto& cur_dep = mod.manifest.dependencies[find_manifest_dep_it->second]; - - // Look for the dependency in the loaded mod mapping. - auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep_id); - if (find_loaded_dep_it == loaded_mods_by_id.end()) { - errors.emplace_back(ModLoadError::MissingDependency, cur_dep_id); - continue; - } - - const 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()); + return CodeModLoadError::MissingDependencyInManifest; } } -} + + const std::vector& mod_sections = mod.recompiler_context->sections; + mod.section_load_addresses.resize(mod_sections.size()); + + // Copy each section's binary into rdram, leaving room for the section's bss before the next one. + int32_t cur_section_addr = load_address; + for (size_t section_index = 0; section_index < mod_sections.size(); section_index++) { + const auto& section = mod_sections[section_index]; + for (size_t i = 0; i < section.size; i++) { + MEM_B(i, (gpr)cur_section_addr) = binary_data[section.rom_addr + i]; + } + mod.section_load_addresses[section_index] = cur_section_addr; + cur_section_addr += section.size + section.bss_size; + + } + + // Iterate over each section again after loading them to perform R_MIPS_32 relocations. + for (size_t section_index = 0; section_index < mod_sections.size(); section_index++) { + const auto& section = mod_sections[section_index]; + uint32_t cur_section_original_vram = section.ram_addr; + uint32_t cur_section_loaded_vram = mod.section_load_addresses[section_index]; + + // Perform mips32 relocations for this section. + for (const auto& reloc : section.relocs) { + if (reloc.type == N64Recomp::RelocType::R_MIPS_32 && !reloc.reference_symbol) { + if (reloc.target_section >= mod_sections.size()) { + return CodeModLoadError::FailedToParseSyms; + } + // Get the ram address of the word that's being relocated and read its original value. + int32_t reloc_word_addr = reloc.address - cur_section_original_vram + cur_section_loaded_vram; + uint32_t reloc_word = MEM_W(0, reloc_word_addr); + + // Determine the original and loaded addresses of the section that the relocation points to. + uint32_t target_section_original_vram = mod_sections[reloc.target_section].ram_addr; + uint32_t target_section_loaded_vram = mod.section_load_addresses[reloc.target_section]; + + // Recalculate the word and write it back into ram. + reloc_word += (target_section_loaded_vram - target_section_original_vram); + MEM_W(0, reloc_word_addr) = reloc_word; + } + } + } + + ram_used = cur_section_addr - load_address; -recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods::ModHandle& mod, std::string& error_param) { // TODO implement LuaJIT recompilation and allow it instead of native code loading via a mod manifest flag. - std::filesystem::path dll_path = mod.manifest.mod_root_path; - dll_path.replace_extension(DynamicLibrary::PlatformExtension); - mod.code_handle = std::make_unique(dll_path, *mod.recompiler_context); - if (!mod.code_handle->good()) { - mod.code_handle.reset(); - error_param = dll_path.string(); - return ModLoadError::FailedToLoadNativeCode; - } - + std::string cur_error_param; - ModLoadError cur_error = validate_api_version(mod.code_handle->get_api_version(), cur_error_param); + CodeModLoadError cur_error; + if (1) { + std::filesystem::path dll_path = mod.manifest.mod_root_path; + dll_path.replace_extension(DynamicLibrary::PlatformExtension); + mod.code_handle = std::make_unique(dll_path, *mod.recompiler_context); + if (!mod.code_handle->good()) { + mod.code_handle.reset(); + error_param = dll_path.string(); + return CodeModLoadError::FailedToLoadNativeCode; + } - if (cur_error != ModLoadError::Good) { - if (cur_error_param.empty()) { - error_param = dll_path.filename().string(); + cur_error = validate_api_version(mod.code_handle->get_api_version(), cur_error_param); + + if (cur_error != CodeModLoadError::Good) { + if (cur_error_param.empty()) { + error_param = dll_path.filename().string(); + } + else { + error_param = dll_path.filename().string() + ":" + std::move(cur_error_param); + } + return cur_error; } - else { - error_param = dll_path.filename().string() + ":" + std::move(cur_error_param); - } - return cur_error; } // Populate the mod's export map. cur_error = mod.populate_exports(cur_error_param); - if (cur_error != ModLoadError::Good) { + if (cur_error != CodeModLoadError::Good) { error_param = std::move(cur_error_param); return cur_error; } @@ -760,7 +903,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods: std::filesystem::path parent_path = mod.manifest.mod_root_path.parent_path(); for (const recomp::mods::NativeLibraryManifest& cur_lib_manifest: mod.manifest.native_libraries) { cur_error = mod.load_native_library(cur_lib_manifest, cur_error_param); - if (cur_error != ModLoadError::Good) { + if (cur_error != CodeModLoadError::Good) { error_param = std::move(cur_error_param); return cur_error; } @@ -769,7 +912,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods: // Populate the mod's event map and set its base event index. cur_error = mod.populate_events(num_events, cur_error_param); - if (cur_error != ModLoadError::Good) { + if (cur_error != CodeModLoadError::Good) { error_param = std::move(cur_error_param); return cur_error; } @@ -778,11 +921,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods: num_events += mod.num_events(); // Add each function from the mod into the function lookup table. - const std::vector& mod_sections = mod.recompiler_context->sections; for (size_t func_index = 0; func_index < mod.recompiler_context->functions.size(); func_index++) { const auto& func = mod.recompiler_context->functions[func_index]; if (func.section_index >= mod_sections.size()) { - return ModLoadError::FailedToParseSyms; + return CodeModLoadError::FailedToParseSyms; } // Calculate the loaded address of this function. int32_t func_address = func.vram - mod_sections[func.section_index].ram_addr + mod.section_load_addresses[func.section_index]; @@ -796,10 +938,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods: }, func_handle); } - return ModLoadError::Good; + return CodeModLoadError::Good; } -recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp::mods::ModHandle& mod, std::string& error_param) { +recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, std::string& error_param) { // Reference symbols from the base recomp.1:1 with relocs for offline mods. // TODO this won't be needed for LuaJIT recompilation, so move this logic into the code handle. size_t reference_symbol_index = 0; @@ -813,7 +955,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp "section: " << reloc.target_section << " func offset: 0x" << reloc.target_section_offset; error_param = error_param_stream.str(); - return ModLoadError::InvalidReferenceSymbol; + return CodeModLoadError::InvalidReferenceSymbol; } mod.code_handle->set_reference_symbol_pointer(reference_symbol_index, cur_func); reference_symbol_index++; @@ -848,8 +990,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp else { auto find_mod_it = loaded_mods_by_id.find(dependency_id); if (find_mod_it == loaded_mods_by_id.end()) { - error_param = dependency_id; - return ModLoadError::MissingDependency; + 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); @@ -857,7 +1001,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp if (!did_find_func) { error_param = dependency_id + ":" + imported_func.base.name; - return ModLoadError::InvalidImport; + return CodeModLoadError::InvalidImport; } mod.code_handle->set_imported_function(import_index, func_handle); @@ -883,8 +1027,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp else { auto find_mod_it = loaded_mods_by_id.find(dependency_id); if (find_mod_it == loaded_mods_by_id.end()) { - error_param = dependency_id; - return ModLoadError::MissingDependency; + error_param = "Failed to find callback 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_mod = opened_mods[find_mod_it->second]; did_find_event = dependency_mod.get_global_event_index(dependency_event.event_name, event_index); @@ -892,7 +1038,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp if (!did_find_event) { error_param = dependency_id + ":" + dependency_event.event_name; - return ModLoadError::InvalidCallbackEvent; + return CodeModLoadError::InvalidCallbackEvent; } recomp::mods::register_event_callback(event_index, func); @@ -920,14 +1066,14 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp "section: 0x" << replacement.original_section_vrom << " func: 0x" << std::setfill('0') << std::setw(8) << replacement.original_vram; error_param = error_param_stream.str(); - return ModLoadError::InvalidFunctionReplacement; + return CodeModLoadError::InvalidFunctionReplacement; } // Check if this function has already been replaced. auto find_patch_it = patched_funcs.find(to_replace); if (find_patch_it != patched_funcs.end()) { error_param = find_patch_it->second.mod_id; - return ModLoadError::ModConflict; + return CodeModLoadError::ModConflict; } // Copy the original bytes so they can be restored later after the mod is unloaded. @@ -939,7 +1085,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp patch_func(to_replace, mod.code_handle->get_function_handle(replacement.func_index)); } - return ModLoadError::Good; + return CodeModLoadError::Good; } void recomp::mods::ModContext::unload_mods() { @@ -950,4 +1096,5 @@ void recomp::mods::ModContext::unload_mods() { loaded_mods_by_id.clear(); recomp::mods::reset_events(); num_events = recomp::overlays::num_base_events(); + active_game = (size_t)-1; } diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 82b6d52..d209b36 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -93,6 +93,16 @@ void recomp::mods::scan_mods() { } } +recomp::mods::ModContentTypeId recomp::mods::register_mod_content_type(const ModContentType& type) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->register_content_type(type); +} + +bool recomp::mods::register_mod_container_type(const std::string& extension, const std::vector& content_types, bool requires_manifest) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->register_container_type(extension, content_types, requires_manifest); +} + 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; @@ -177,7 +187,6 @@ const recomp::Version& recomp::get_project_version() { bool recomp::Version::from_string(const std::string& str, Version& out) { std::array period_indices; - size_t num_periods = 0; size_t cur_pos = 0; uint16_t major; uint16_t minor;