From 396b45d496b895e4ddb6adac4098f38b811e6f30 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sat, 23 Aug 2025 02:40:38 -0400 Subject: [PATCH] Integrate ROM patcher and add ROM patch content type --- librecomp/CMakeLists.txt | 1 + librecomp/include/librecomp/mods.hpp | 4 ++ librecomp/src/mod_manifest.cpp | 4 ++ librecomp/src/mods.cpp | 57 ++++++++++++++++++++++++++++ librecomp/src/recomp.cpp | 9 ++++- 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/librecomp/CMakeLists.txt b/librecomp/CMakeLists.txt index 9c4bf53..4db1505 100644 --- a/librecomp/CMakeLists.txt +++ b/librecomp/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(librecomp STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_config_api.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/patcher.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/print.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/recomp.cpp" diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 3393f65..d7a464e 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -93,6 +93,8 @@ namespace recomp { MissingDependency, WrongDependencyVersion, FailedToLoadCode, + RomPatchConflict, + FailedToLoadPatch, }; std::string error_to_string(ModLoadError); @@ -315,6 +317,7 @@ namespace recomp { // This is just a wrapper around a number for type safety purposes. struct ModContentTypeId { size_t value; + bool operator==(const ModContentTypeId& rhs) const = default; }; struct ModContainerType { @@ -439,6 +442,7 @@ namespace recomp { std::vector empty_bytes; size_t num_events = 0; ModContentTypeId code_content_type_id; + ModContentTypeId rom_patch_content_type_id; size_t active_game = (size_t)-1; }; diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index b41ec12..a75d253 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -973,6 +973,10 @@ std::string recomp::mods::error_to_string(ModLoadError error) { return "Wrong dependency version"; case ModLoadError::FailedToLoadCode: return "Failed to load mod code"; + case ModLoadError::RomPatchConflict: + return "ROM patch mod conflict"; + case ModLoadError::FailedToLoadPatch: + return "Invalid ROM patch"; } return "Unknown mod loading error " + std::to_string((int)error); } diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 13bed97..ff40212 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -7,6 +7,7 @@ #include "librecomp/mods.hpp" #include "librecomp/overlays.hpp" #include "librecomp/game.hpp" +#include "librecomp/patcher.hpp" #include "recompiler/context.h" #include "recompiler/live_recompiler.h" @@ -252,6 +253,7 @@ namespace modpaths { 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"; + constexpr std::string_view rom_patch_path = "patch.bps"; }; recomp::mods::CodeModLoadError recomp::mods::validate_api_version(uint32_t api_version, std::string& error_param) { @@ -916,6 +918,16 @@ recomp::mods::ModContext::ModContext() { .on_reordered = nullptr }; code_content_type_id = register_content_type(code_content_type); + + // Register the ROM patch content type. + ModContentType rom_patch_content_type { + .content_filename = std::string{modpaths::rom_patch_path}, + .allow_runtime_toggle = false, + .on_enabled = nullptr, + .on_disabled = nullptr, + .on_reordered = nullptr + }; + rom_patch_content_type_id = register_content_type(rom_patch_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); @@ -1610,6 +1622,51 @@ std::vector recomp::mods::ModContext::load_mo return ret; } + // Check for ROM patches. + size_t rom_patch_mod_index = (size_t)-1; + for (size_t mod_index : active_mods) { + auto& mod = opened_mods[mod_index]; + auto find_it = std::find(mod.content_types.begin(), mod.content_types.end(), rom_patch_content_type_id); + if (find_it != mod.content_types.end()) { + // If a mod has already provided a patch, mark the two as incompatible. + if (rom_patch_mod_index != (size_t)-1) { + ret.emplace_back(mod.manifest.mod_id, ModLoadError::RomPatchConflict, "conflicts with " + opened_mods[rom_patch_mod_index].manifest.display_name); + } + else { + rom_patch_mod_index = mod_index; + } + } + } + + // Exit early if errors were found. + if (!ret.empty()) { + unload_mods(); + return ret; + } + + // Apply a ROM patch if one was found. + if (rom_patch_mod_index != (size_t)-1) { + auto& mod = opened_mods[rom_patch_mod_index]; + + bool patch_exists; + std::vector patch_data = mod.manifest.file_handle->read_file(std::string{ modpaths::rom_patch_path }, patch_exists); + std::vector patched_rom; + + // This should never happen, as the content type's presence means the patch file exists. Catch it just in case regardless. + if (!patch_exists) { + ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadPatch, "Internal error"); + return ret; + } + + auto patch_result = recomp::patcher::patch_rom(recomp::get_rom(), std::span{ reinterpret_cast(patch_data.data()), patch_data.size() }, patched_rom); + if (patch_result != recomp::patcher::PatcherResult::Success) { + ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadPatch, std::string{}); + return ret; + } + + recomp::set_rom_contents(std::move(patched_rom)); + } + // Check that mod dependencies are met. for (size_t mod_index : active_mods) { auto& mod = opened_mods[mod_index]; diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 8a03dbd..78b33e1 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -666,7 +666,14 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { std::ostringstream mod_error_stream; mod_error_stream << "Error loading mods:\n\n"; for (const auto& cur_error : mod_load_errors) { - mod_error_stream << cur_error.mod_id.c_str() << ": " << recomp::mods::error_to_string(cur_error.error); + auto mod_details = recomp::mods::get_details_for_mod(cur_error.mod_id); + if (mod_details) { + mod_error_stream << mod_details->display_name; + } + else { + mod_error_stream << cur_error.mod_id.c_str(); + } + mod_error_stream << ": " << recomp::mods::error_to_string(cur_error.error); if (!cur_error.error_param.empty()) { mod_error_stream << " (" << cur_error.error_param.c_str() << ")"; }