Integrate ROM patcher and add ROM patch content type

This commit is contained in:
Mr-Wiseguy 2025-08-23 02:40:38 -04:00
parent 8eb1ca7639
commit 396b45d496
5 changed files with 74 additions and 1 deletions

View file

@ -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"

View file

@ -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<char> 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;
};

View file

@ -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);
}

View file

@ -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) {
@ -917,6 +919,16 @@ recomp::mods::ModContext::ModContext() {
};
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::ModLoadErrorDetails> 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<char> patch_data = mod.manifest.file_handle->read_file(std::string{ modpaths::rom_patch_path }, patch_exists);
std::vector<uint8_t> 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<const uint8_t*>(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];

View file

@ -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() << ")";
}