Added function conflict detection, recovery from failed mod loading and message box with load errors

This commit is contained in:
Mr-Wiseguy 2024-07-10 19:49:42 -04:00
parent b0d3e4610c
commit 7e063bfdae
4 changed files with 90 additions and 8 deletions

View file

@ -9,12 +9,17 @@
#include <memory> #include <memory>
#include <tuple> #include <tuple>
#include <unordered_set> #include <unordered_set>
#include <unordered_map>
#include <array>
#include <cstddef>
#define MINIZ_NO_DEFLATE_APIS #define MINIZ_NO_DEFLATE_APIS
#define MINIZ_NO_ARCHIVE_WRITING_APIS #define MINIZ_NO_ARCHIVE_WRITING_APIS
#include "miniz.h" #include "miniz.h"
#include "miniz_zip.h" #include "miniz_zip.h"
#include "librecomp/recomp.h"
namespace recomp { namespace recomp {
namespace mods { namespace mods {
enum class ModOpenError { enum class ModOpenError {
@ -41,8 +46,13 @@ namespace recomp {
FailedToLoadBinary, FailedToLoadBinary,
InvalidFunctionReplacement, InvalidFunctionReplacement,
FailedToFindReplacement, FailedToFindReplacement,
ReplacementConflict,
MissingDependencies,
ModConflict,
}; };
std::string error_to_string(ModLoadError);
struct ModFileHandle { struct ModFileHandle {
virtual ~ModFileHandle() = default; virtual ~ModFileHandle() = default;
virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0; virtual std::vector<char> 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); size_t num_opened_mods(const std::u8string& game_id);
// Internal functions, TODO move to an internal header. // Internal functions, TODO move to an internal header.
struct PatchData {
std::array<std::byte, 16> replaced_bytes;
std::string mod_id;
};
struct ModHandle; struct ModHandle;
class ModContext { class ModContext {
public: public:
@ -119,15 +134,16 @@ namespace recomp {
bool is_mod_enabled(const std::string& mod_id); bool is_mod_enabled(const std::string& mod_id);
size_t num_opened_mods(); size_t num_opened_mods();
std::vector<ModLoadErrorDetails> load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used); std::vector<ModLoadErrorDetails> 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); // const ModManifest& get_mod_manifest(size_t mod_index);
private: private:
ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param); 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); void add_opened_mod(ModManifest&& manifest);
std::vector<ModHandle> opened_mods; std::vector<ModHandle> opened_mods;
std::unordered_set<std::string> mod_ids; std::unordered_set<std::string> mod_ids;
std::unordered_set<std::string> enabled_mods; std::unordered_set<std::string> enabled_mods;
std::unordered_map<recomp_func_t*, PatchData> patched_funcs;
}; };
} }
}; };

View file

@ -416,3 +416,25 @@ std::string recomp::mods::error_to_string(ModOpenError error) {
} }
return "Unknown error " + std::to_string((int)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);
}

View file

@ -48,6 +48,13 @@ void patch_func(void* target_func, void* replacement_func) {
protect(target_func, old_flags); 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 recomp {
namespace mods { namespace mods {
struct ModHandle { struct ModHandle {
@ -66,7 +73,8 @@ void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest) {
opened_mods.emplace_back(std::move(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<recomp_func_t*, recomp::mods::PatchData>& patched_funcs) {
using namespace recomp::mods;
std::vector<int32_t> section_load_addresses{}; std::vector<int32_t> 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); 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); patch_func(to_replace, replacement_func);
} }
total_func_count += mod_section.replacements.size(); total_func_count += mod_section.replacements.size();
@ -208,12 +229,17 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
std::vector<recomp::mods::ModLoadErrorDetails> ret{}; std::vector<recomp::mods::ModLoadErrorDetails> ret{};
ram_used = 0; ram_used = 0;
if (!patched_funcs.empty()) {
printf("Mods already loaded!\n");
return {};
}
for (auto& mod : opened_mods) { for (auto& mod : opened_mods) {
if (enabled_mods.contains(mod.manifest.mod_id)) { if (enabled_mods.contains(mod.manifest.mod_id)) {
printf("Loading mod %s\n", mod.manifest.mod_id.c_str()); printf("Loading mod %s\n", mod.manifest.mod_id.c_str());
uint32_t cur_ram_used = 0; uint32_t cur_ram_used = 0;
std::string load_error_param; 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) { if (load_error != ModLoadError::Good) {
ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param); ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param);
@ -225,5 +251,17 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
} }
} }
if (!ret.empty()) {
printf("Mod loading failed, unpatching funcs\n");
unload_mods();
}
return ret; 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();
}

View file

@ -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); mod_open_errors = mod_context.scan_mod_folder(config_path / "mods" / entry.mod_subdirectory);
} }
for (const auto& cur_error : mod_open_errors) { 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::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()); auto find_it = game_roms.find(current_game.value());
const recomp::GameEntry& game_entry = find_it->second; 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()) { 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) { 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); game_status.store(GameStatus::None);
return false; return false;
} }
} }
ultramodern::init_saving(rdram);
ultramodern::load_shader_cache(game_entry.cache_data); ultramodern::load_shader_cache(game_entry.cache_data);
try { try {