mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-11 11:22:05 +00:00
Added function conflict detection, recovery from failed mod loading and message box with load errors
This commit is contained in:
parent
b0d3e4610c
commit
7e063bfdae
4 changed files with 90 additions and 8 deletions
|
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue