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 <tuple>
#include <unordered_set>
#include <unordered_map>
#include <array>
#include <cstddef>
#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<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);
// Internal functions, TODO move to an internal header.
struct PatchData {
std::array<std::byte, 16> 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<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);
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<ModHandle> opened_mods;
std::unordered_set<std::string> mod_ids;
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);
}
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);
}
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<recomp_func_t*, recomp::mods::PatchData>& patched_funcs) {
using namespace recomp::mods;
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);
// 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::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
std::vector<recomp::mods::ModLoadErrorDetails> 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::ModLoadErrorDetails> 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();
}

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