diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index a5a7813..c967e32 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -9,12 +9,17 @@ #include #include #include +#include +#include +#include #define MINIZ_NO_DEFLATE_APIS #define MINIZ_NO_ARCHIVE_WRITING_APIS #include "miniz.h" #include "miniz_zip.h" +#include "librecomp/recomp.h" + namespace recomp { namespace mods { enum class ModOpenError { @@ -41,8 +46,13 @@ namespace recomp { FailedToLoadBinary, InvalidFunctionReplacement, FailedToFindReplacement, + ReplacementConflict, + MissingDependencies, + ModConflict, }; + std::string error_to_string(ModLoadError); + struct ModFileHandle { virtual ~ModFileHandle() = default; virtual std::vector read_file(const std::string& filepath, bool& exists) const = 0; @@ -108,6 +118,11 @@ namespace recomp { size_t num_opened_mods(const std::u8string& game_id); // Internal functions, TODO move to an internal header. + struct PatchData { + std::array replaced_bytes; + std::string mod_id; + }; + struct ModHandle; class ModContext { public: @@ -119,15 +134,16 @@ namespace recomp { 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); + 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, ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param); void add_opened_mod(ModManifest&& manifest); std::vector opened_mods; std::unordered_set mod_ids; std::unordered_set enabled_mods; + std::unordered_map patched_funcs; }; } }; diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 302e1c1..db1ffa4 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -416,3 +416,25 @@ std::string recomp::mods::error_to_string(ModOpenError error) { } return "Unknown error " + std::to_string((int)error); } + +std::string recomp::mods::error_to_string(ModLoadError error) { + switch (error) { + case ModLoadError::Good: + return "Good"; + case ModLoadError::FailedToLoadSyms: + return "Failed to load mod symbol file"; + case ModLoadError::FailedToLoadBinary: + return "Failed to load mod binary file"; + 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::MissingDependencies: + return "Missing dependencies"; + case ModLoadError::ModConflict: + return "Conflicts with other mod"; + } + return "Unknown error " + std::to_string((int)error); +} diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 15e40e5..06349ef 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -48,6 +48,13 @@ void patch_func(void* target_func, void* replacement_func) { protect(target_func, old_flags); } +void unpatch_func(void* target_func, const recomp::mods::PatchData& data) { + DWORD old_flags; + unprotect(target_func, &old_flags); + memcpy(target_func, data.replaced_bytes.data(), data.replaced_bytes.size()); + protect(target_func, old_flags); +} + namespace recomp { namespace mods { struct ModHandle { @@ -66,7 +73,8 @@ void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest) { opened_mods.emplace_back(std::move(manifest)); } -recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param) { +recomp::mods::ModLoadError load_mod(uint8_t* rdram, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param, std::unordered_map& patched_funcs) { + using namespace recomp::mods; std::vector section_load_addresses{}; { @@ -152,6 +160,19 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, Mo printf("found replacement func: 0x%016llX\n", (uintptr_t)to_replace); + // 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; + } + + // Copy the original bytes so they can be restored later after the mod is unloaded. + PatchData& cur_replacement_data = patched_funcs[to_replace]; + memcpy(cur_replacement_data.replaced_bytes.data(), to_replace, cur_replacement_data.replaced_bytes.size()); + cur_replacement_data.mod_id = handle.manifest.mod_id; + + // Patch the function to redirect it to the replacement. patch_func(to_replace, replacement_func); } total_func_count += mod_section.replacements.size(); @@ -208,12 +229,17 @@ std::vector recomp::mods::ModContext::load_mo std::vector ret{}; ram_used = 0; + if (!patched_funcs.empty()) { + printf("Mods already loaded!\n"); + return {}; + } + for (auto& mod : opened_mods) { if (enabled_mods.contains(mod.manifest.mod_id)) { 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, mod, load_address, cur_ram_used, load_error_param); + ModLoadError load_error = load_mod(rdram, mod, load_address, cur_ram_used, load_error_param, patched_funcs); if (load_error != ModLoadError::Good) { ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param); @@ -225,5 +251,17 @@ std::vector recomp::mods::ModContext::load_mo } } + if (!ret.empty()) { + printf("Mod loading failed, unpatching funcs\n"); + unload_mods(); + } + return ret; } + +void recomp::mods::ModContext::unload_mods() { + for (auto& [replacement_func, replacement_data] : patched_funcs) { + unpatch_func(replacement_func, replacement_data); + } + patched_funcs.clear(); +} diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 33f6c84..71774c5 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -78,7 +78,7 @@ bool recomp::register_game(const recomp::GameEntry& entry) { mod_open_errors = mod_context.scan_mod_folder(config_path / "mods" / entry.mod_subdirectory); } for (const auto& cur_error : mod_open_errors) { - printf("Error loading 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()); + 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()); } } @@ -449,8 +449,6 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { ultramodern::error_handling::message_box("Error opening stored ROM! Please restart this program."); } - ultramodern::init_saving(rdram); - auto find_it = game_roms.find(current_game.value()); const recomp::GameEntry& game_entry = find_it->second; @@ -469,14 +467,22 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { } if (!mod_load_errors.empty()) { + std::ostringstream mod_error_stream; + mod_error_stream << "Error loading mods:\n\n"; for (const auto& cur_error : mod_load_errors) { - printf("Mod %s failed to load with error %d (%s)\n", cur_error.mod_id.c_str(), (int)cur_error.error, cur_error.error_param.c_str()); + mod_error_stream << cur_error.mod_id.c_str() << ": " << recomp::mods::error_to_string(cur_error.error); + if (!cur_error.error_param.empty()) { + mod_error_stream << " (" << cur_error.error_param.c_str() << ")"; + } + mod_error_stream << "\n"; } + ultramodern::error_handling::message_box(mod_error_stream.str().c_str()); game_status.store(GameStatus::None); return false; } } - + + ultramodern::init_saving(rdram); ultramodern::load_shader_cache(game_entry.cache_data); try {