From 5699906f34fcc82905303092d081ad92aa74f926 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 2 Sep 2024 01:19:08 -0400 Subject: [PATCH] Removed per-game mod subdirectories and added the mod's corresponding game id to the manifest --- librecomp/include/librecomp/game.hpp | 3 +- librecomp/include/librecomp/mods.hpp | 30 ++++++++---- librecomp/src/mod_manifest.cpp | 37 +++++++++++++- librecomp/src/mods.cpp | 25 +++++++--- librecomp/src/recomp.cpp | 72 +++++++++------------------- 5 files changed, 98 insertions(+), 69 deletions(-) diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index 54438d6..9168d6f 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -13,10 +13,9 @@ namespace recomp { uint64_t rom_hash; std::string internal_name; std::u8string game_id; - std::u8string mod_subdirectory; + std::string mod_game_id; std::span cache_data; bool is_enabled; - bool mods; gpr entrypoint_address; void (*entrypoint)(uint8_t* rdram, recomp_context* context); diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 705c8f0..8b785dc 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -41,13 +41,15 @@ namespace recomp { IncorrectManifestFieldType, MissingManifestField, InnerFileDoesNotExist, - DuplicateMod + DuplicateMod, + WrongGame }; std::string error_to_string(ModOpenError); enum class ModLoadError { Good, + InvalidGame, FailedToLoadSyms, FailedToLoadBinary, FailedToLoadNativeCode, @@ -106,6 +108,7 @@ namespace recomp { struct ModManifest { std::filesystem::path mod_root_path; + std::vector mod_game_ids; std::string mod_id; int major_version = -1; @@ -134,10 +137,9 @@ namespace recomp { std::string error_param; }; - std::vector scan_mod_folder(const std::u8string& game_id, const std::filesystem::path& mod_folder); - void enable_mod(const std::u8string& game_id, const std::string& mod_id, bool enabled); - bool is_mod_enabled(const std::u8string& game_id, const std::string& mod_id); - size_t num_opened_mods(const std::u8string& game_id); + void scan_mods(); + void enable_mod(const std::string& mod_id, bool enabled); + bool is_mod_enabled(const std::string& mod_id); // Internal functions, TODO move to an internal header. struct PatchData { @@ -153,22 +155,23 @@ namespace recomp { ModContext(); ~ModContext(); - void setup_sections(); + void register_game(const std::string& mod_game_id); std::vector scan_mod_folder(const std::filesystem::path& mod_folder); void enable_mod(const std::string& mod_id, bool enabled); bool is_mod_enabled(const std::string& mod_id); size_t num_opened_mods(); - std::vector load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used); + std::vector load_mods(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used); void unload_mods(); - // const ModManifest& get_mod_manifest(size_t mod_index); 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); 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); + void add_opened_mod(ModManifest&& manifest, std::vector&& game_indices); + // Maps game mod ID to the mod's internal integer ID. + std::unordered_map mod_game_ids; std::vector opened_mods; std::unordered_set mod_ids; std::unordered_set enabled_mods; @@ -202,7 +205,7 @@ namespace recomp { std::unique_ptr recompiler_context; std::vector section_load_addresses; - ModHandle(ModManifest&& manifest); + ModHandle(ModManifest&& manifest, std::vector&& game_indices); ModHandle(const ModHandle& rhs) = delete; ModHandle& operator=(const ModHandle& rhs) = delete; ModHandle(ModHandle&& rhs); @@ -217,6 +220,11 @@ namespace recomp { ModLoadError 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); + + 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(); + } private: // Mapping of export name to function index. std::unordered_map exports_by_name; @@ -226,6 +234,8 @@ namespace recomp { std::unordered_map events_by_name; // Loaded dynamic libraries. std::vector> native_libraries; // Vector of pointers so that implementation can be elsewhere. + // Games that this mod supports. + std::vector game_indices; }; class NativeCodeHandle : public ModCodeHandle { diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 013a37d..c43dd9f 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -131,6 +131,7 @@ bool recomp::mods::LooseModFileHandle::file_exists(const std::string& filepath) } enum class ManifestField { + GameModId, Id, MajorVersion, MinorVersion, @@ -142,6 +143,7 @@ enum class ManifestField { NativeLibraryPaths, }; +const std::string game_mod_id_key = "game_id"; const std::string mod_id_key = "id"; const std::string major_version_key = "major_version"; const std::string minor_version_key = "minor_version"; @@ -153,6 +155,7 @@ const std::string rom_patch_syms_path_key = "rom_patch_syms"; const std::string native_library_paths_key = "native_libraries"; std::unordered_map field_map { + { game_mod_id_key, ManifestField::GameModId }, { mod_id_key, ManifestField::Id }, { major_version_key, ManifestField::MajorVersion }, { minor_version_key, ManifestField::MinorVersion }, @@ -219,6 +222,17 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const ManifestField field = find_key_it->second; switch (field) { + case ManifestField::GameModId: + { + std::string mod_game_id; + if (!get_to(val, mod_game_id)) { + error_param = key; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; + } + ret.mod_game_ids.resize(1); + ret.mod_game_ids[0] = std::move(mod_game_id); + } + break; case ManifestField::Id: if (!get_to(val, ret.mod_id)) { error_param = key; @@ -306,6 +320,10 @@ recomp::mods::ModOpenError validate_manifest(const recomp::mods::ModManifest& ma using namespace recomp::mods; // Check for required fields. + if (manifest.mod_game_ids.empty()) { + error_param = game_mod_id_key; + return ModOpenError::MissingManifestField; + } if (manifest.mod_id.empty()) { error_param = mod_id_key; return ModOpenError::MissingManifestField; @@ -398,7 +416,7 @@ 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;; + return ModOpenError::NoManifest; } ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param); @@ -419,9 +437,20 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys return validate_error; } + // Check for this mod's game ids being valid. + std::vector game_indices; + for (const auto& mod_game_id : manifest.mod_game_ids) { + auto find_id_it = mod_game_ids.find(mod_game_id); + if (find_id_it == mod_game_ids.end()) { + error_param = mod_game_id; + return ModOpenError::WrongGame; + } + game_indices.emplace_back(find_id_it->second); + } + // Store the loaded mod manifest in a new mod handle. manifest.mod_root_path = mod_path; - add_opened_mod(std::move(manifest)); + add_opened_mod(std::move(manifest), std::move(game_indices)); return ModOpenError::Good; } @@ -454,6 +483,8 @@ std::string recomp::mods::error_to_string(ModOpenError error) { return "File inside mod does not exist"; case ModOpenError::DuplicateMod: return "Duplicate mod found"; + case ModOpenError::WrongGame: + return "Mod is for a different game"; } return "Unknown mod opening error: " + std::to_string((int)error); } @@ -462,6 +493,8 @@ std::string recomp::mods::error_to_string(ModLoadError error) { switch (error) { case ModLoadError::Good: return "Good"; + case ModLoadError::InvalidGame: + return "Invalid game"; case ModLoadError::FailedToLoadSyms: return "Failed to load mod symbol file"; case ModLoadError::FailedToLoadBinary: diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index ab81736..65d2e41 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -110,10 +110,11 @@ recomp::mods::ModLoadError recomp::mods::validate_api_version(uint32_t api_versi } } -recomp::mods::ModHandle::ModHandle(ModManifest&& manifest) : +recomp::mods::ModHandle::ModHandle(ModManifest&& manifest, std::vector&& game_indices) : manifest(std::move(manifest)), code_handle(), - recompiler_context{std::make_unique()} + recompiler_context{std::make_unique()}, + game_indices{std::move(game_indices)} { } @@ -306,8 +307,8 @@ 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) { - opened_mods.emplace_back(std::move(manifest)); +void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vector&& game_indices) { + opened_mods.emplace_back(std::move(manifest), std::move(game_indices)); } 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) { @@ -356,6 +357,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, co return ModLoadError::Good; } +void recomp::mods::ModContext::register_game(const std::string& mod_game_id) { + mod_game_ids.emplace(mod_game_id, mod_game_ids.size()); +} + std::vector recomp::mods::ModContext::scan_mod_folder(const std::filesystem::path& mod_folder) { std::vector ret{}; std::error_code ec; @@ -398,11 +403,19 @@ size_t recomp::mods::ModContext::num_opened_mods() { return opened_mods.size(); } -std::vector recomp::mods::ModContext::load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used) { +std::vector recomp::mods::ModContext::load_mods(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used) { std::vector ret{}; ram_used = 0; num_events = recomp::overlays::num_base_events(); + auto find_index_it = mod_game_ids.find(mod_game_id); + if (find_index_it == mod_game_ids.end()) { + ret.emplace_back(mod_game_id, ModLoadError::InvalidGame, std::string{}); + return ret; + } + + size_t mod_game_index = find_index_it->second; + if (!patched_funcs.empty()) { printf("Mods already loaded!\n"); return {}; @@ -415,7 +428,7 @@ std::vector recomp::mods::ModContext::load_mo // Find and load active mods. for (size_t mod_index = 0; mod_index < opened_mods.size(); mod_index++) { auto& mod = opened_mods[mod_index]; - if (enabled_mods.contains(mod.manifest.mod_id)) { + if (mod.is_for_game(mod_game_index) && enabled_mods.contains(mod.manifest.mod_id)) { active_mods.push_back(mod_index); loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index); diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 71774c5..806f221 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -51,8 +51,10 @@ std::mutex mod_context_mutex{}; // Global variables std::filesystem::path config_path; +// Maps game_id to the game's entry. std::unordered_map game_roms {}; -std::unordered_map mod_contexts {}; +// The global mod context. +std::unique_ptr mod_context = std::make_unique(); std::u8string recomp::GameEntry::stored_filename() const { return game_id + u8".z64"; @@ -68,23 +70,25 @@ bool recomp::register_game(const recomp::GameEntry& entry) { std::lock_guard lock(game_roms_mutex); game_roms.insert({ entry.game_id, entry }); } - - // Scan for mods in the main mod folder if enabled. - if (entry.mods) { - std::vector mod_open_errors; - { - std::lock_guard mod_lock{ mod_context_mutex }; - recomp::mods::ModContext& mod_context = mod_contexts[entry.game_id]; - mod_open_errors = mod_context.scan_mod_folder(config_path / "mods" / entry.mod_subdirectory); - } - for (const auto& cur_error : mod_open_errors) { - printf("Error opening mod " PATHFMT ": %s (%s)\n", cur_error.mod_path.c_str(), recomp::mods::error_to_string(cur_error.error).c_str(), cur_error.error_param.c_str()); - } + if (!entry.mod_game_id.empty()) { + std::lock_guard lock(mod_context_mutex); + mod_context->register_game(entry.mod_game_id); } return true; } +void recomp::mods::scan_mods() { + std::vector mod_open_errors; + { + std::lock_guard mod_lock{ mod_context_mutex }; + mod_open_errors = mod_context->scan_mod_folder(config_path / "mods"); + } + for (const auto& cur_error : mod_open_errors) { + printf("Error opening mod " PATHFMT ": %s (%s)\n", cur_error.mod_path.c_str(), recomp::mods::error_to_string(cur_error.error).c_str(), cur_error.error_param.c_str()); + } +} + 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; @@ -402,40 +406,14 @@ void ultramodern::quit() { current_game.reset(); } -std::vector recomp::mods::scan_mod_folder(const std::u8string& game_id, const std::filesystem::path& mod_folder) { +void recomp::mods::enable_mod(const std::string& mod_id, bool enabled) { std::lock_guard lock { mod_context_mutex }; - auto find_it = mod_contexts.find(game_id); - if (find_it == mod_contexts.end()) { - return {}; - } - return find_it->second.scan_mod_folder(mod_folder); + return mod_context->enable_mod(mod_id, enabled); } -void recomp::mods::enable_mod(const std::u8string& game_id, const std::string& mod_id, bool enabled) { +bool recomp::mods::is_mod_enabled(const std::string& mod_id) { std::lock_guard lock { mod_context_mutex }; - auto find_it = mod_contexts.find(game_id); - if (find_it == mod_contexts.end()) { - return; - } - return find_it->second.enable_mod(mod_id, enabled); -} - -bool recomp::mods::is_mod_enabled(const std::u8string& game_id, const std::string& mod_id) { - std::lock_guard lock { mod_context_mutex }; - auto find_it = mod_contexts.find(game_id); - if (find_it == mod_contexts.end()) { - return false; - } - return find_it->second.is_mod_enabled(mod_id); -} - -size_t recomp::mods::num_opened_mods(const std::u8string& game_id) { - std::lock_guard lock { mod_context_mutex }; - auto find_it = mod_contexts.find(game_id); - if (find_it == mod_contexts.end()) { - return 0; - } - return find_it->second.num_opened_mods(); + return mod_context->is_mod_enabled(mod_id); } bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { @@ -454,16 +432,12 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { init(rdram, context, game_entry.entrypoint_address); - if (game_entry.mods) { + if (!game_entry.mod_game_id.empty()) { uint32_t mod_ram_used = 0; std::vector mod_load_errors; { std::lock_guard lock { mod_context_mutex }; - auto find_it = mod_contexts.find(current_game.value()); - if (find_it == mod_contexts.end()) { - return false; - } - mod_load_errors = find_it->second.load_mods(rdram, recomp::mod_rdram_start, mod_ram_used); + mod_load_errors = mod_context->load_mods(game_entry.mod_game_id, rdram, recomp::mod_rdram_start, mod_ram_used); } if (!mod_load_errors.empty()) {