mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2025-10-30 08:02:29 +00:00
Mod function hooking (#80)
* Prevent mods from replacing functions patched by the base recomp unless they're marked as Force * Implement function hooking for functions replaced by mods * Add support for hooking non-relocated functions that aren't replaced by a mod * Only create the regenerated code handle if any functions need to be regenerated * Implement relocs for function regeneration in hooking * Implement hooking of functions patched by the base recomp * Fix base event index tracking when loading mods * Update to N64Recomp main branch after merge
This commit is contained in:
parent
7eb4bc3459
commit
cdfe416809
13 changed files with 975 additions and 40 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit fc696046da3e703450559154d9370ca74c197f8b
|
Subproject commit 38df8e3ddcd64ce056af1fe6156580291c4273ae
|
||||||
|
|
@ -18,6 +18,7 @@ add_library(librecomp STATIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mods.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mods.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_events.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_events.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_hooks.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ namespace recomp {
|
||||||
std::string internal_name;
|
std::string internal_name;
|
||||||
std::u8string game_id;
|
std::u8string game_id;
|
||||||
std::string mod_game_id;
|
std::string mod_game_id;
|
||||||
std::span<const char> cache_data;
|
|
||||||
SaveType save_type = SaveType::None;
|
SaveType save_type = SaveType::None;
|
||||||
bool is_enabled;
|
bool is_enabled;
|
||||||
|
// Only needed for mod function hooking support, not needed if `has_compressed_code` is false.
|
||||||
|
std::vector<uint8_t> (*decompression_routine)(std::span<const uint8_t> compressed_rom) = nullptr;
|
||||||
|
bool has_compressed_code = false;
|
||||||
|
|
||||||
gpr entrypoint_address;
|
gpr entrypoint_address;
|
||||||
void (*entrypoint)(uint8_t* rdram, recomp_context* context);
|
void (*entrypoint)(uint8_t* rdram, recomp_context* context);
|
||||||
|
|
@ -73,6 +75,7 @@ namespace recomp {
|
||||||
bool is_rom_valid(std::u8string& game_id);
|
bool is_rom_valid(std::u8string& game_id);
|
||||||
bool is_rom_loaded();
|
bool is_rom_loaded();
|
||||||
void set_rom_contents(std::vector<uint8_t>&& new_rom);
|
void set_rom_contents(std::vector<uint8_t>&& new_rom);
|
||||||
|
std::span<const uint8_t> get_rom();
|
||||||
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes);
|
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes);
|
||||||
void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr);
|
void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr);
|
||||||
const Version& get_project_version();
|
const Version& get_project_version();
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,37 @@
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
#include "librecomp/game.hpp"
|
#include "librecomp/game.hpp"
|
||||||
#include "librecomp/sections.h"
|
#include "librecomp/sections.h"
|
||||||
|
#include "librecomp/overlays.hpp"
|
||||||
|
|
||||||
namespace N64Recomp {
|
namespace N64Recomp {
|
||||||
class Context;
|
class Context;
|
||||||
struct LiveGeneratorOutput;
|
struct LiveGeneratorOutput;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace recomp {
|
||||||
|
namespace mods {
|
||||||
|
struct HookDefinition {
|
||||||
|
uint32_t section_rom;
|
||||||
|
uint32_t function_vram;
|
||||||
|
bool at_return;
|
||||||
|
bool operator==(const HookDefinition& rhs) const = default;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct std::hash<recomp::mods::HookDefinition>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const recomp::mods::HookDefinition& def) const {
|
||||||
|
// This hash packing only works if the resulting value is 64 bits.
|
||||||
|
static_assert(sizeof(std::size_t) == 8);
|
||||||
|
// Combine the three values into a single 64-bit value.
|
||||||
|
// The lower 2 bits of a function address will always be zero, so pack
|
||||||
|
// the value of at_return into the lowest bit.
|
||||||
|
return (size_t(def.section_rom) << 32) | size_t(def.function_vram) | size_t(def.at_return ? 1 : 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
namespace recomp {
|
namespace recomp {
|
||||||
namespace mods {
|
namespace mods {
|
||||||
enum class ModOpenError {
|
enum class ModOpenError {
|
||||||
|
|
@ -77,10 +102,14 @@ namespace recomp {
|
||||||
InvalidImport,
|
InvalidImport,
|
||||||
InvalidCallbackEvent,
|
InvalidCallbackEvent,
|
||||||
InvalidFunctionReplacement,
|
InvalidFunctionReplacement,
|
||||||
|
HooksUnavailable,
|
||||||
|
InvalidHook,
|
||||||
|
CannotBeHooked,
|
||||||
FailedToFindReplacement,
|
FailedToFindReplacement,
|
||||||
ReplacementConflict,
|
BaseRecompConflict,
|
||||||
ModConflict,
|
ModConflict,
|
||||||
DuplicateExport,
|
DuplicateExport,
|
||||||
|
OfflineModHooked,
|
||||||
NoSpecifiedApiVersion,
|
NoSpecifiedApiVersion,
|
||||||
UnsupportedApiVersion,
|
UnsupportedApiVersion,
|
||||||
};
|
};
|
||||||
|
|
@ -210,6 +239,7 @@ namespace recomp {
|
||||||
bool requires_manifest;
|
bool requires_manifest;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class LiveRecompilerCodeHandle;
|
||||||
class ModContext {
|
class ModContext {
|
||||||
public:
|
public:
|
||||||
ModContext();
|
ModContext();
|
||||||
|
|
@ -220,7 +250,7 @@ namespace recomp {
|
||||||
void enable_mod(const std::string& mod_id, bool enabled);
|
void enable_mod(const std::string& mod_id, bool enabled);
|
||||||
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(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
|
std::vector<ModLoadErrorDetails> load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
|
||||||
void unload_mods();
|
void unload_mods();
|
||||||
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
|
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
|
||||||
ModContentTypeId register_content_type(const ModContentType& type);
|
ModContentTypeId register_content_type(const ModContentType& type);
|
||||||
|
|
@ -229,12 +259,18 @@ namespace recomp {
|
||||||
bool is_content_runtime_toggleable(ModContentTypeId content_type) const;
|
bool is_content_runtime_toggleable(ModContentTypeId content_type) const;
|
||||||
private:
|
private:
|
||||||
ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest);
|
ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest);
|
||||||
ModLoadError load_mod(recomp::mods::ModHandle& mod, std::string& error_param);
|
ModLoadError load_mod(ModHandle& mod, std::string& error_param);
|
||||||
void check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors);
|
void check_dependencies(ModHandle& mod, std::vector<std::pair<ModLoadError, std::string>>& errors);
|
||||||
CodeModLoadError load_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param);
|
CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param);
|
||||||
CodeModLoadError resolve_code_dependencies(recomp::mods::ModHandle& mod, std::string& error_param);
|
CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param);
|
||||||
|
CodeModLoadError resolve_code_dependencies(ModHandle& mod, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param);
|
||||||
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types);
|
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types);
|
||||||
void close_mods();
|
void close_mods();
|
||||||
|
std::vector<ModLoadErrorDetails> regenerate_with_hooks(
|
||||||
|
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
|
||||||
|
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
|
||||||
|
const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs,
|
||||||
|
std::span<const uint8_t> decompressed_rom);
|
||||||
|
|
||||||
static void on_code_mod_enabled(ModContext& context, const ModHandle& mod);
|
static void on_code_mod_enabled(ModContext& context, const ModHandle& mod);
|
||||||
|
|
||||||
|
|
@ -249,6 +285,15 @@ namespace recomp {
|
||||||
std::unordered_map<recomp_func_t*, PatchData> patched_funcs;
|
std::unordered_map<recomp_func_t*, PatchData> patched_funcs;
|
||||||
std::unordered_map<std::string, size_t> loaded_mods_by_id;
|
std::unordered_map<std::string, size_t> loaded_mods_by_id;
|
||||||
std::vector<size_t> loaded_code_mods;
|
std::vector<size_t> loaded_code_mods;
|
||||||
|
// Code handle for vanilla code that was regenerated to add hooks.
|
||||||
|
std::unique_ptr<LiveRecompilerCodeHandle> regenerated_code_handle;
|
||||||
|
// Code handle for base patched code that was regenerated to add hooks.
|
||||||
|
std::unique_ptr<LiveRecompilerCodeHandle> base_patched_code_handle;
|
||||||
|
// Map of hook definition to the entry hook slot's index.
|
||||||
|
std::unordered_map<HookDefinition, size_t> hook_slots;
|
||||||
|
// Tracks which hook slots have already been processed. Used to regenerate vanilla functions as needed
|
||||||
|
// to add hooks to any functions that weren't already replaced by a mod.
|
||||||
|
std::vector<bool> processed_hook_slots;
|
||||||
size_t num_events = 0;
|
size_t num_events = 0;
|
||||||
ModContentTypeId code_content_type_id;
|
ModContentTypeId code_content_type_id;
|
||||||
size_t active_game = (size_t)-1;
|
size_t active_game = (size_t)-1;
|
||||||
|
|
@ -368,7 +413,8 @@ namespace recomp {
|
||||||
|
|
||||||
class LiveRecompilerCodeHandle : public ModCodeHandle {
|
class LiveRecompilerCodeHandle : public ModCodeHandle {
|
||||||
public:
|
public:
|
||||||
LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs);
|
LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs,
|
||||||
|
std::unordered_map<size_t, size_t>&& entry_func_hooks, std::unordered_map<size_t, size_t>&& return_func_hooks);
|
||||||
|
|
||||||
~LiveRecompilerCodeHandle() = default;
|
~LiveRecompilerCodeHandle() = default;
|
||||||
|
|
||||||
|
|
@ -398,6 +444,12 @@ namespace recomp {
|
||||||
void setup_events(size_t num_events);
|
void setup_events(size_t num_events);
|
||||||
void register_event_callback(size_t event_index, GenericFunction callback);
|
void register_event_callback(size_t event_index, GenericFunction callback);
|
||||||
void reset_events();
|
void reset_events();
|
||||||
|
|
||||||
|
void setup_hooks(size_t num_hook_slots);
|
||||||
|
void register_hook(size_t hook_slot_index, GenericFunction callback);
|
||||||
|
void reset_hooks();
|
||||||
|
void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index);
|
||||||
|
|
||||||
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
|
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
|
||||||
|
|
||||||
void initialize_mod_recompiler();
|
void initialize_mod_recompiler();
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <span>
|
||||||
#include "sections.h"
|
#include "sections.h"
|
||||||
|
|
||||||
namespace recomp {
|
namespace recomp {
|
||||||
|
|
@ -26,17 +27,35 @@ namespace recomp {
|
||||||
void register_base_export(const std::string& name, recomp_func_t* func);
|
void register_base_export(const std::string& name, recomp_func_t* func);
|
||||||
void register_base_exports(const FunctionExport* exports);
|
void register_base_exports(const FunctionExport* exports);
|
||||||
void register_base_events(char const* const* event_names);
|
void register_base_events(char const* const* event_names);
|
||||||
|
void register_manual_patch_symbols(const ManualPatchSymbol* manual_patch_symbols);
|
||||||
void read_patch_data(uint8_t* rdram, gpr patch_data_address);
|
void read_patch_data(uint8_t* rdram, gpr patch_data_address);
|
||||||
|
|
||||||
void init_overlays();
|
void init_overlays();
|
||||||
const std::unordered_map<uint32_t, uint16_t>& get_vrom_to_section_map();
|
const std::unordered_map<uint32_t, uint16_t>& get_vrom_to_section_map();
|
||||||
|
uint32_t get_section_ram_addr(uint16_t code_section_index);
|
||||||
|
std::span<const RelocEntry> get_section_relocs(uint16_t code_section_index);
|
||||||
recomp_func_t* get_func_by_section_rom_function_vram(uint32_t section_rom, uint32_t function_vram);
|
recomp_func_t* get_func_by_section_rom_function_vram(uint32_t section_rom, uint32_t function_vram);
|
||||||
|
bool get_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out);
|
||||||
recomp_func_t* get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset);
|
recomp_func_t* get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset);
|
||||||
recomp_func_t* get_base_export(const std::string& export_name);
|
recomp_func_t* get_base_export(const std::string& export_name);
|
||||||
size_t get_base_event_index(const std::string& event_name);
|
size_t get_base_event_index(const std::string& event_name);
|
||||||
size_t num_base_events();
|
size_t num_base_events();
|
||||||
|
|
||||||
void add_loaded_function(int32_t ram_addr, recomp_func_t* func);
|
void add_loaded_function(int32_t ram_addr, recomp_func_t* func);
|
||||||
|
|
||||||
|
struct BasePatchedFunction {
|
||||||
|
size_t patch_section;
|
||||||
|
size_t function_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<recomp_func_t*, BasePatchedFunction> get_base_patched_funcs();
|
||||||
|
const std::unordered_map<uint32_t, uint16_t>& get_patch_vrom_to_section_map();
|
||||||
|
uint32_t get_patch_section_ram_addr(uint16_t patch_code_section_index);
|
||||||
|
uint32_t get_patch_section_rom_addr(uint16_t patch_code_section_index);
|
||||||
|
const FuncEntry* get_patch_function_entry(uint16_t patch_code_section_index, size_t function_index);
|
||||||
|
bool get_patch_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out);
|
||||||
|
std::span<const RelocEntry> get_patch_section_relocs(uint16_t patch_code_section_index);
|
||||||
|
std::span<const uint8_t> get_patch_binary();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,39 @@
|
||||||
typedef struct {
|
typedef struct {
|
||||||
recomp_func_t* func;
|
recomp_func_t* func;
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
|
uint32_t rom_size;
|
||||||
} FuncEntry;
|
} FuncEntry;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
R_MIPS_NONE = 0,
|
||||||
|
R_MIPS_16,
|
||||||
|
R_MIPS_32,
|
||||||
|
R_MIPS_REL32,
|
||||||
|
R_MIPS_26,
|
||||||
|
R_MIPS_HI16,
|
||||||
|
R_MIPS_LO16,
|
||||||
|
R_MIPS_GPREL16,
|
||||||
|
} RelocEntryType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Offset into the section of the word to relocate.
|
||||||
|
uint32_t offset;
|
||||||
|
// Reloc addend from the target section's address.
|
||||||
|
uint32_t target_section_offset;
|
||||||
|
// Index of the target section (indexes into `section_addresses`).
|
||||||
|
uint16_t target_section;
|
||||||
|
// Relocation type.
|
||||||
|
RelocEntryType type;
|
||||||
|
} RelocEntry;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t rom_addr;
|
uint32_t rom_addr;
|
||||||
uint32_t ram_addr;
|
uint32_t ram_addr;
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
FuncEntry *funcs;
|
FuncEntry *funcs;
|
||||||
size_t num_funcs;
|
size_t num_funcs;
|
||||||
|
RelocEntry* relocs;
|
||||||
|
size_t num_relocs;
|
||||||
size_t index;
|
size_t index;
|
||||||
} SectionTableEntry;
|
} SectionTableEntry;
|
||||||
|
|
||||||
|
|
@ -25,4 +50,9 @@ typedef struct {
|
||||||
uint32_t ram_addr;
|
uint32_t ram_addr;
|
||||||
} FunctionExport;
|
} FunctionExport;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t ram_addr;
|
||||||
|
recomp_func_t* func;
|
||||||
|
} ManualPatchSymbol;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
51
librecomp/src/mod_hooks.cpp
Normal file
51
librecomp/src/mod_hooks.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#include <vector>
|
||||||
|
#include "librecomp/mods.hpp"
|
||||||
|
#include "librecomp/overlays.hpp"
|
||||||
|
#include "ultramodern/error_handling.hpp"
|
||||||
|
|
||||||
|
template<class... Ts>
|
||||||
|
struct overloaded : Ts... { using Ts::operator()...; };
|
||||||
|
template<class... Ts>
|
||||||
|
overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
|
|
||||||
|
// Vector of individual hooks for each hook slot.
|
||||||
|
std::vector<std::vector<recomp::mods::GenericFunction>> hook_table{};
|
||||||
|
|
||||||
|
void recomp::mods::run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index) {
|
||||||
|
// Sanity check the hook slot index.
|
||||||
|
if (hook_slot_index >= hook_table.size()) {
|
||||||
|
printf("Hook slot %zu triggered, but only %zu hook slots have been registered!\n", hook_slot_index, hook_table.size());
|
||||||
|
assert(false);
|
||||||
|
ultramodern::error_handling::message_box("Encountered an error with loaded mods: hook slot out of bounds");
|
||||||
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the initial context state to restore it after running each callback.
|
||||||
|
recomp_context initial_context = *ctx;
|
||||||
|
|
||||||
|
// Call every hook attached to the hook slot.
|
||||||
|
const std::vector<recomp::mods::GenericFunction>& hooks = hook_table[hook_slot_index];
|
||||||
|
for (recomp::mods::GenericFunction func : hooks) {
|
||||||
|
// Run the hook.
|
||||||
|
std::visit(overloaded {
|
||||||
|
[rdram, ctx](recomp_func_t* native_func) {
|
||||||
|
native_func(rdram, ctx);
|
||||||
|
},
|
||||||
|
}, func);
|
||||||
|
|
||||||
|
// Restore the original context.
|
||||||
|
*ctx = initial_context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::setup_hooks(size_t num_hook_slots) {
|
||||||
|
hook_table.resize(num_hook_slots);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::register_hook(size_t hook_slot_index, GenericFunction callback) {
|
||||||
|
hook_table[hook_slot_index].emplace_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::reset_hooks() {
|
||||||
|
hook_table.clear();
|
||||||
|
}
|
||||||
|
|
@ -581,14 +581,24 @@ std::string recomp::mods::error_to_string(CodeModLoadError error) {
|
||||||
return "Event for callback not found";
|
return "Event for callback not found";
|
||||||
case CodeModLoadError::InvalidFunctionReplacement:
|
case CodeModLoadError::InvalidFunctionReplacement:
|
||||||
return "Function to be replaced does not exist";
|
return "Function to be replaced does not exist";
|
||||||
|
case CodeModLoadError::HooksUnavailable:
|
||||||
|
// This error will occur if the ROM's GameEntry is set as having compressed code, but no
|
||||||
|
// ROM decompression routine has been provided.
|
||||||
|
return "Function hooks are currently unavailable in this project";
|
||||||
|
case CodeModLoadError::InvalidHook:
|
||||||
|
return "Function to be hooked does not exist";
|
||||||
|
case CodeModLoadError::CannotBeHooked:
|
||||||
|
return "Function is not hookable";
|
||||||
case CodeModLoadError::FailedToFindReplacement:
|
case CodeModLoadError::FailedToFindReplacement:
|
||||||
return "Failed to find replacement function";
|
return "Failed to find replacement function";
|
||||||
case CodeModLoadError::ReplacementConflict:
|
case CodeModLoadError::BaseRecompConflict:
|
||||||
return "Attempted to replace a function that cannot be replaced";
|
return "Attempted to replace a function that's been patched by the base recomp";
|
||||||
case CodeModLoadError::ModConflict:
|
case CodeModLoadError::ModConflict:
|
||||||
return "Conflicts with other mod";
|
return "Conflicts with other mod";
|
||||||
case CodeModLoadError::DuplicateExport:
|
case CodeModLoadError::DuplicateExport:
|
||||||
return "Duplicate exports in mod";
|
return "Duplicate exports in mod";
|
||||||
|
case CodeModLoadError::OfflineModHooked:
|
||||||
|
return "Offline recompiled mod has a function hooked by another mod";
|
||||||
case CodeModLoadError::NoSpecifiedApiVersion:
|
case CodeModLoadError::NoSpecifiedApiVersion:
|
||||||
return "Mod DLL does not specify an API version";
|
return "Mod DLL does not specify an API version";
|
||||||
case CodeModLoadError::UnsupportedApiVersion:
|
case CodeModLoadError::UnsupportedApiVersion:
|
||||||
|
|
|
||||||
|
|
@ -418,7 +418,10 @@ recomp::mods::CodeModLoadError recomp::mods::DynamicLibraryCodeHandle::populate_
|
||||||
return CodeModLoadError::Good;
|
return CodeModLoadError::Good;
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs) {
|
recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(
|
||||||
|
const N64Recomp::Context& context, const ModCodeHandleInputs& inputs,
|
||||||
|
std::unordered_map<size_t, size_t>&& entry_func_hooks, std::unordered_map<size_t, size_t>&& return_func_hooks)
|
||||||
|
{
|
||||||
section_addresses = std::make_unique<int32_t[]>(context.sections.size());
|
section_addresses = std::make_unique<int32_t[]>(context.sections.size());
|
||||||
base_event_index = inputs.base_event_index;
|
base_event_index = inputs.base_event_index;
|
||||||
|
|
||||||
|
|
@ -434,6 +437,9 @@ recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(const N64Recomp
|
||||||
.trigger_event = inputs.recomp_trigger_event,
|
.trigger_event = inputs.recomp_trigger_event,
|
||||||
.reference_section_addresses = inputs.reference_section_addresses,
|
.reference_section_addresses = inputs.reference_section_addresses,
|
||||||
.local_section_addresses = section_addresses.get(),
|
.local_section_addresses = section_addresses.get(),
|
||||||
|
.run_hook = run_hook,
|
||||||
|
.entry_func_hooks = std::move(entry_func_hooks),
|
||||||
|
.return_func_hooks = std::move(return_func_hooks)
|
||||||
};
|
};
|
||||||
|
|
||||||
N64Recomp::LiveGenerator generator{ context.functions.size(), recompiler_inputs };
|
N64Recomp::LiveGenerator generator{ context.functions.size(), recompiler_inputs };
|
||||||
|
|
@ -747,15 +753,152 @@ std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_mod_details(
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mods(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used) {
|
struct RegeneratedSection {
|
||||||
|
uint32_t rom_addr;
|
||||||
|
uint32_t ram_addr;
|
||||||
|
size_t first_func_index;
|
||||||
|
size_t first_reloc_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RegeneratedFunction {
|
||||||
|
uint32_t section_offset;
|
||||||
|
uint32_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RegeneratedReloc {
|
||||||
|
uint32_t section_offset;
|
||||||
|
uint32_t target_section;
|
||||||
|
uint32_t target_section_offset;
|
||||||
|
RelocEntryType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RegeneratedList {
|
||||||
|
std::vector<RegeneratedSection> sections;
|
||||||
|
std::vector<RegeneratedFunction> functions;
|
||||||
|
std::vector<RegeneratedReloc> relocs;
|
||||||
|
|
||||||
|
// The native function pointers to be used for patching.
|
||||||
|
std::vector<recomp_func_t*> func_ptrs;
|
||||||
|
// Mappings of function index within context to hook slot index.
|
||||||
|
std::unordered_map<size_t, size_t> entry_func_hooks;
|
||||||
|
std::unordered_map<size_t, size_t> return_func_hooks;
|
||||||
|
|
||||||
|
// Regeneration list for the patches.
|
||||||
|
std::vector<std::pair<recomp::overlays::BasePatchedFunction, std::pair<recomp::mods::HookDefinition, size_t>>> patched_hooks;
|
||||||
|
};
|
||||||
|
|
||||||
|
N64Recomp::Context context_from_regenerated_list(const RegeneratedList& regenlist, std::span<const uint8_t> rom) {
|
||||||
|
N64Recomp::Context ret{};
|
||||||
|
|
||||||
|
// TODO avoid copying the whole ROM into the context somehow.
|
||||||
|
ret.rom.assign(rom.begin(), rom.end());
|
||||||
|
|
||||||
|
ret.sections.resize(regenlist.sections.size());
|
||||||
|
ret.section_functions.resize(regenlist.sections.size());
|
||||||
|
ret.functions.resize(regenlist.functions.size());
|
||||||
|
|
||||||
|
for (size_t section_index = 0; section_index < regenlist.sections.size(); section_index++) {
|
||||||
|
const RegeneratedSection& section_in = regenlist.sections[section_index];
|
||||||
|
N64Recomp::Section& section_out = ret.sections[section_index];
|
||||||
|
|
||||||
|
size_t cur_num_funcs;
|
||||||
|
size_t cur_num_relocs;
|
||||||
|
if (section_index == regenlist.sections.size() - 1) {
|
||||||
|
cur_num_funcs = regenlist.functions.size() - section_in.first_func_index;
|
||||||
|
cur_num_relocs = regenlist.relocs.size() - section_in.first_reloc_index;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cur_num_funcs = regenlist.sections[section_index + 1].first_func_index - section_in.first_func_index;
|
||||||
|
cur_num_relocs = regenlist.sections[section_index + 1].first_reloc_index - section_in.first_reloc_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
section_out.rom_addr = section_in.rom_addr;
|
||||||
|
section_out.ram_addr = section_in.ram_addr;
|
||||||
|
section_out.size = 0;
|
||||||
|
section_out.bss_size = 0;
|
||||||
|
section_out.function_addrs.resize(cur_num_funcs);
|
||||||
|
section_out.relocs.resize(cur_num_relocs);
|
||||||
|
section_out.name = "patch_section_" + std::to_string(section_index);
|
||||||
|
section_out.bss_section_index = 0;
|
||||||
|
section_out.executable = true;
|
||||||
|
section_out.relocatable = false;
|
||||||
|
section_out.has_mips32_relocs = false;
|
||||||
|
|
||||||
|
std::vector<size_t>& section_funcs_out = ret.section_functions[section_index];
|
||||||
|
section_funcs_out.resize(cur_num_funcs);
|
||||||
|
|
||||||
|
for (size_t section_function_index = 0; section_function_index < cur_num_funcs; section_function_index++) {
|
||||||
|
// Get the global index of the function within the context.
|
||||||
|
size_t function_index = section_in.first_func_index + section_function_index;
|
||||||
|
section_funcs_out[section_function_index] = function_index;
|
||||||
|
|
||||||
|
// Populate the fields of the function.
|
||||||
|
const RegeneratedFunction& function_in = regenlist.functions[function_index];
|
||||||
|
N64Recomp::Function& function_out = ret.functions[function_index];
|
||||||
|
function_out.vram = section_out.ram_addr + function_in.section_offset;
|
||||||
|
function_out.rom = section_out.rom_addr + function_in.section_offset;
|
||||||
|
function_out.words.resize(function_in.size / sizeof(uint32_t));
|
||||||
|
function_out.name = "patch_function_" + std::to_string(function_index);
|
||||||
|
function_out.section_index = section_index;
|
||||||
|
function_out.ignored = false;
|
||||||
|
function_out.reimplemented = false;
|
||||||
|
function_out.stubbed = false;
|
||||||
|
function_out.function_hooks.clear();
|
||||||
|
|
||||||
|
// Copy the function's words.
|
||||||
|
const uint32_t* func_words = reinterpret_cast<const uint32_t*>(rom.data() + function_out.rom);
|
||||||
|
function_out.words.assign(func_words, func_words + function_in.size / sizeof(uint32_t));
|
||||||
|
|
||||||
|
// Add the function to the lookup table.
|
||||||
|
ret.functions_by_vram[function_out.vram].push_back(function_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t section_reloc_index = 0; section_reloc_index < cur_num_relocs; section_reloc_index++) {
|
||||||
|
// Get the global index of the reloc within the regenlist.
|
||||||
|
size_t reloc_index = section_in.first_reloc_index + section_reloc_index;
|
||||||
|
|
||||||
|
const RegeneratedReloc& reloc_in = regenlist.relocs[reloc_index];
|
||||||
|
N64Recomp::Reloc& reloc_out = section_out.relocs[section_reloc_index];
|
||||||
|
|
||||||
|
reloc_out.address = reloc_in.section_offset + section_out.ram_addr;
|
||||||
|
reloc_out.target_section_offset = reloc_in.target_section_offset;
|
||||||
|
reloc_out.symbol_index = 0; // Unused for live recompilation.
|
||||||
|
reloc_out.target_section = reloc_in.target_section;
|
||||||
|
reloc_out.type = static_cast<N64Recomp::RelocType>(reloc_in.type);
|
||||||
|
reloc_out.reference_symbol = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used) {
|
||||||
std::vector<recomp::mods::ModLoadErrorDetails> ret{};
|
std::vector<recomp::mods::ModLoadErrorDetails> ret{};
|
||||||
ram_used = 0;
|
ram_used = 0;
|
||||||
num_events = recomp::overlays::num_base_events();
|
num_events = recomp::overlays::num_base_events();
|
||||||
loaded_code_mods.clear();
|
loaded_code_mods.clear();
|
||||||
|
|
||||||
auto find_index_it = mod_game_ids.find(mod_game_id);
|
std::span<const uint8_t> decompressed_rom{};
|
||||||
|
|
||||||
|
// Decompress the rom if needed.
|
||||||
|
std::vector<uint8_t> decompressed_rom_data{};
|
||||||
|
if (game_entry.has_compressed_code) {
|
||||||
|
if (game_entry.decompression_routine != nullptr) {
|
||||||
|
decompressed_rom_data = game_entry.decompression_routine(recomp::get_rom());
|
||||||
|
}
|
||||||
|
decompressed_rom = std::span{decompressed_rom_data};
|
||||||
|
}
|
||||||
|
// Otherwise, assign the regular rom as the decompressed rom since no decompression is needed.
|
||||||
|
else {
|
||||||
|
decompressed_rom = recomp::get_rom();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the set of functions patched by the base recomp.
|
||||||
|
std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction> base_patched_funcs = recomp::overlays::get_base_patched_funcs();
|
||||||
|
|
||||||
|
auto find_index_it = mod_game_ids.find(game_entry.mod_game_id);
|
||||||
if (find_index_it == mod_game_ids.end()) {
|
if (find_index_it == mod_game_ids.end()) {
|
||||||
ret.emplace_back(mod_game_id, ModLoadError::InvalidGame, std::string{});
|
ret.emplace_back(game_entry.mod_game_id, ModLoadError::InvalidGame, std::string{});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -812,12 +955,16 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the code and exports from all mods.
|
std::vector<uint32_t> base_event_indices;
|
||||||
|
base_event_indices.resize(opened_mods.size());
|
||||||
|
|
||||||
|
// Parse the code mods and load their binary data.
|
||||||
for (size_t mod_index : loaded_code_mods) {
|
for (size_t mod_index : loaded_code_mods) {
|
||||||
uint32_t cur_ram_used = 0;
|
uint32_t cur_ram_used = 0;
|
||||||
auto& mod = opened_mods[mod_index];
|
auto& mod = opened_mods[mod_index];
|
||||||
std::string cur_error_param;
|
std::string cur_error_param;
|
||||||
CodeModLoadError cur_error = load_mod_code(rdram, section_vrom_map, mod, load_address, cur_ram_used, cur_error_param);
|
size_t base_event_index = num_events;
|
||||||
|
CodeModLoadError cur_error = init_mod_code(rdram, section_vrom_map, mod, load_address, !decompressed_rom.empty(), cur_ram_used, cur_error_param);
|
||||||
if (cur_error != CodeModLoadError::Good) {
|
if (cur_error != CodeModLoadError::Good) {
|
||||||
if (cur_error_param.empty()) {
|
if (cur_error_param.empty()) {
|
||||||
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
||||||
|
|
@ -829,6 +976,7 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
||||||
else {
|
else {
|
||||||
load_address += cur_ram_used;
|
load_address += cur_ram_used;
|
||||||
ram_used += cur_ram_used;
|
ram_used += cur_ram_used;
|
||||||
|
base_event_indices[mod_index] = static_cast<uint32_t>(base_event_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -841,11 +989,21 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
||||||
// Set up the event callbacks based on the number of events allocated.
|
// Set up the event callbacks based on the number of events allocated.
|
||||||
recomp::mods::setup_events(num_events);
|
recomp::mods::setup_events(num_events);
|
||||||
|
|
||||||
// Resolve code dependencies for all mods.
|
// TODO if any hooks have been made but the decompressed rom isn't available,
|
||||||
|
// present an error and stop loading mods.
|
||||||
|
|
||||||
|
// Set up the hook slots based on the number of unique hooks.
|
||||||
|
recomp::mods::setup_hooks(hook_slots.size());
|
||||||
|
|
||||||
|
// Allocate room for tracking the processed hook slots.
|
||||||
|
processed_hook_slots.clear();
|
||||||
|
processed_hook_slots.resize(hook_slots.size());
|
||||||
|
|
||||||
|
// Load the code and exports from all mods.
|
||||||
for (size_t mod_index : loaded_code_mods) {
|
for (size_t mod_index : loaded_code_mods) {
|
||||||
auto& mod = opened_mods[mod_index];
|
auto& mod = opened_mods[mod_index];
|
||||||
std::string cur_error_param;
|
std::string cur_error_param;
|
||||||
CodeModLoadError cur_error = resolve_code_dependencies(mod, cur_error_param);
|
CodeModLoadError cur_error = load_mod_code(rdram, mod, base_event_indices[mod_index], cur_error_param);
|
||||||
if (cur_error != CodeModLoadError::Good) {
|
if (cur_error != CodeModLoadError::Good) {
|
||||||
if (cur_error_param.empty()) {
|
if (cur_error_param.empty()) {
|
||||||
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
||||||
|
|
@ -862,10 +1020,364 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve code dependencies for all mods.
|
||||||
|
for (size_t mod_index : loaded_code_mods) {
|
||||||
|
auto& mod = opened_mods[mod_index];
|
||||||
|
std::string cur_error_param;
|
||||||
|
CodeModLoadError cur_error = resolve_code_dependencies(mod, base_patched_funcs, cur_error_param);
|
||||||
|
if (cur_error != CodeModLoadError::Good) {
|
||||||
|
if (cur_error_param.empty()) {
|
||||||
|
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error) + ":" + cur_error_param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit early if errors were found.
|
||||||
|
if (!ret.empty()) {
|
||||||
|
unload_mods();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regenerate any remaining hook slots that weren't handled during mod recompilation.
|
||||||
|
|
||||||
|
// List of unprocessed hooks and their hook index.
|
||||||
|
std::vector<std::pair<recomp::mods::HookDefinition, size_t>> unprocessed_hooks;
|
||||||
|
for (const auto& [def, index] : hook_slots) {
|
||||||
|
if (!processed_hook_slots[index]) {
|
||||||
|
unprocessed_hooks.emplace_back(std::make_pair(def, index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unprocessed_hooks.empty()) {
|
||||||
|
|
||||||
|
// Sort the unprocessed hooks by section and vram.
|
||||||
|
std::sort(unprocessed_hooks.begin(), unprocessed_hooks.end(),
|
||||||
|
[](const std::pair<recomp::mods::HookDefinition, size_t>& lhs, const std::pair<recomp::mods::HookDefinition, size_t>& rhs) {
|
||||||
|
if (lhs.first.section_rom == rhs.first.section_rom) {
|
||||||
|
return lhs.first.function_vram < rhs.first.function_vram;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return lhs.first.section_rom < rhs.first.section_rom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ret = regenerate_with_hooks(unprocessed_hooks, section_vrom_map, base_patched_funcs, decompressed_rom);
|
||||||
|
// Exit early if errors were found.
|
||||||
|
if (!ret.empty()) {
|
||||||
|
unload_mods();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
active_game = mod_game_index;
|
active_game = mod_game_index;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool patched_regenlist>
|
||||||
|
std::vector<recomp::mods::ModLoadErrorDetails> build_regen_list(
|
||||||
|
const std::vector<std::pair<recomp::mods::HookDefinition, size_t>>& sorted_unprocessed_hooks,
|
||||||
|
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
|
||||||
|
const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs,
|
||||||
|
RegeneratedList& regenlist
|
||||||
|
) {
|
||||||
|
using namespace recomp;
|
||||||
|
using namespace recomp::mods;
|
||||||
|
|
||||||
|
std::vector<ModLoadErrorDetails> ret{};
|
||||||
|
uint32_t cur_section_rom = 0xFFFFFFFF;
|
||||||
|
uint32_t cur_section_vram = 0xFFFFFFFF;
|
||||||
|
uint16_t cur_section_index = 0xFFFF;
|
||||||
|
uint32_t cur_function_vram = 0xFFFFFFFF;
|
||||||
|
std::span<const RelocEntry> cur_section_relocs = {};
|
||||||
|
size_t cur_section_reloc_index = 0;
|
||||||
|
|
||||||
|
// Collect the unprocessed hooks into a patch list.
|
||||||
|
// Hooks have been sorted by their section address and function address at this point so they
|
||||||
|
// can be gathered by section into the patch list.
|
||||||
|
for (size_t hook_index = 0; hook_index < sorted_unprocessed_hooks.size(); hook_index++) {
|
||||||
|
const auto& cur_hook = sorted_unprocessed_hooks[hook_index];
|
||||||
|
const auto& cur_hook_def = cur_hook.first;
|
||||||
|
size_t cur_hook_slot_index = cur_hook.second;
|
||||||
|
|
||||||
|
if (cur_hook_def.section_rom != cur_section_rom) {
|
||||||
|
// Get the index of the section.
|
||||||
|
auto find_section_it = section_vrom_map.find(cur_hook_def.section_rom);
|
||||||
|
if (find_section_it == section_vrom_map.end()) {
|
||||||
|
std::stringstream error_param_stream{};
|
||||||
|
error_param_stream << std::hex <<
|
||||||
|
"section: 0x" << cur_hook_def.section_rom <<
|
||||||
|
" func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram;
|
||||||
|
ret.emplace_back(ModLoadErrorDetails{
|
||||||
|
"", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InvalidHook) + ":" + error_param_stream.str()
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t section_index = find_section_it->second;
|
||||||
|
uint32_t section_ram_addr;
|
||||||
|
|
||||||
|
if constexpr (patched_regenlist) {
|
||||||
|
section_ram_addr = recomp::overlays::get_patch_section_ram_addr(section_index);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
section_ram_addr = recomp::overlays::get_section_ram_addr(section_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a new section.
|
||||||
|
auto& section_out = regenlist.sections.emplace_back(RegeneratedSection{
|
||||||
|
.rom_addr = cur_hook_def.section_rom,
|
||||||
|
.ram_addr = section_ram_addr,
|
||||||
|
.first_func_index = regenlist.functions.size(),
|
||||||
|
.first_reloc_index = regenlist.relocs.size()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the tracked section fields.
|
||||||
|
cur_section_rom = section_out.rom_addr;
|
||||||
|
cur_section_vram = section_out.ram_addr;
|
||||||
|
cur_section_index = section_index;
|
||||||
|
cur_section_reloc_index = 0;
|
||||||
|
|
||||||
|
if constexpr (patched_regenlist) {
|
||||||
|
cur_section_relocs = recomp::overlays::get_patch_section_relocs(cur_section_index);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cur_section_relocs = recomp::overlays::get_section_relocs(cur_section_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the tracked function vram to prevent issues when two functions have the same vram in different sections.
|
||||||
|
cur_function_vram = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cur_hook_def.function_vram != cur_function_vram) {
|
||||||
|
uint32_t function_section_offset = cur_hook_def.function_vram - cur_section_vram;
|
||||||
|
FuncEntry func_entry{};
|
||||||
|
bool found_func;
|
||||||
|
|
||||||
|
if constexpr (patched_regenlist) {
|
||||||
|
found_func = recomp::overlays::get_patch_func_entry_by_section_index_function_offset(cur_section_index, function_section_offset, func_entry);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
found_func = recomp::overlays::get_func_entry_by_section_index_function_offset(cur_section_index, function_section_offset, func_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found_func) {
|
||||||
|
std::stringstream error_param_stream{};
|
||||||
|
error_param_stream << std::hex <<
|
||||||
|
"section: 0x" << cur_hook_def.section_rom <<
|
||||||
|
" func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram;
|
||||||
|
ret.emplace_back(ModLoadErrorDetails{
|
||||||
|
"", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InvalidHook) + ":" + error_param_stream.str()
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t function_rom_size = func_entry.rom_size;
|
||||||
|
|
||||||
|
// A size of 0 means the function can't be hooked (e.g. it's a native reimplemented function).
|
||||||
|
if (function_rom_size == 0) {
|
||||||
|
std::stringstream error_param_stream{};
|
||||||
|
error_param_stream << std::hex <<
|
||||||
|
"section: 0x" << cur_hook_def.section_rom <<
|
||||||
|
" func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram;
|
||||||
|
ret.emplace_back(ModLoadErrorDetails{
|
||||||
|
"", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::CannotBeHooked) + ":" + error_param_stream.str()
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this function has been patched by the base recomp.
|
||||||
|
bool skip_func = false;
|
||||||
|
if constexpr (!patched_regenlist) {
|
||||||
|
auto find_patched_it = base_patched_funcs.find(func_entry.func);
|
||||||
|
if (find_patched_it != base_patched_funcs.end()) {
|
||||||
|
regenlist.patched_hooks.emplace_back(std::make_pair(find_patched_it->second, cur_hook));
|
||||||
|
skip_func = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip_func) {
|
||||||
|
// Allocate a new function.
|
||||||
|
regenlist.functions.emplace_back(RegeneratedFunction{
|
||||||
|
.section_offset = function_section_offset,
|
||||||
|
.size = function_rom_size
|
||||||
|
});
|
||||||
|
regenlist.func_ptrs.push_back(func_entry.func);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the tracked function address.
|
||||||
|
cur_function_vram = cur_hook_def.function_vram;
|
||||||
|
|
||||||
|
// Advance forward in the section's reloc list until reaching this function's offset or the end of the list.
|
||||||
|
size_t cur_function_offset = cur_function_vram - cur_section_vram;
|
||||||
|
size_t cur_function_end_offset = cur_function_offset + function_rom_size;
|
||||||
|
while (true) {
|
||||||
|
if (cur_section_reloc_index >= cur_section_relocs.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto& reloc_in = cur_section_relocs[cur_section_reloc_index];
|
||||||
|
if (reloc_in.offset >= cur_function_offset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_section_reloc_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all relocs until the end of this function or the end of the reloc list.
|
||||||
|
while (true) {
|
||||||
|
if (cur_section_reloc_index >= cur_section_relocs.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& reloc_in = cur_section_relocs[cur_section_reloc_index];
|
||||||
|
if (reloc_in.offset >= cur_function_end_offset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
regenlist.relocs.emplace_back(RegeneratedReloc {
|
||||||
|
.section_offset = reloc_in.offset,
|
||||||
|
.target_section = reloc_in.target_section,
|
||||||
|
.target_section_offset = reloc_in.target_section_offset,
|
||||||
|
.type = reloc_in.type
|
||||||
|
});
|
||||||
|
|
||||||
|
cur_section_reloc_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the hooks in the function to hook mapping.
|
||||||
|
size_t func_index = regenlist.functions.size() - 1;
|
||||||
|
if (cur_hook_def.at_return) {
|
||||||
|
regenlist.return_func_hooks[func_index] = cur_hook_slot_index;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
regenlist.entry_func_hooks[func_index] = cur_hook_slot_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<recomp::mods::LiveRecompilerCodeHandle> apply_regenlist(RegeneratedList& regenlist, std::span<const uint8_t> rom) {
|
||||||
|
using namespace recomp::mods;
|
||||||
|
|
||||||
|
std::unique_ptr<LiveRecompilerCodeHandle> regenerated_code_handle{};
|
||||||
|
|
||||||
|
// Generate the recompiler context.
|
||||||
|
N64Recomp::Context hook_context = context_from_regenerated_list(regenlist, rom);
|
||||||
|
hook_context.set_all_reference_sections_relocatable();
|
||||||
|
hook_context.use_lookup_for_all_function_calls = true;
|
||||||
|
|
||||||
|
// Regenerate the functions using the live recompiler.
|
||||||
|
ModCodeHandleInputs handle_inputs{
|
||||||
|
.base_event_index = 0, // No events in vanilla functions, so this doesn't matter.
|
||||||
|
.recomp_trigger_event = recomp_trigger_event,
|
||||||
|
.get_function = get_function,
|
||||||
|
.cop0_status_write = cop0_status_write,
|
||||||
|
.cop0_status_read = cop0_status_read,
|
||||||
|
.switch_error = switch_error,
|
||||||
|
.do_break = do_break,
|
||||||
|
.reference_section_addresses = section_addresses,
|
||||||
|
};
|
||||||
|
regenerated_code_handle = std::make_unique<LiveRecompilerCodeHandle>(hook_context, handle_inputs, std::move(regenlist.entry_func_hooks), std::move(regenlist.return_func_hooks));
|
||||||
|
|
||||||
|
if (!regenerated_code_handle->good()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string reference_syms_error_param{};
|
||||||
|
CodeModLoadError reference_syms_error = regenerated_code_handle->populate_reference_symbols(hook_context, reference_syms_error_param);
|
||||||
|
if (reference_syms_error != CodeModLoadError::Good) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch the functions that were regenerated.
|
||||||
|
for (size_t patched_func_index = 0; patched_func_index < regenlist.func_ptrs.size(); patched_func_index++) {
|
||||||
|
patch_func(regenlist.func_ptrs[patched_func_index], regenerated_code_handle->get_function_handle(patched_func_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
return regenerated_code_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::regenerate_with_hooks(
|
||||||
|
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
|
||||||
|
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
|
||||||
|
const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs,
|
||||||
|
std::span<const uint8_t> decompressed_rom
|
||||||
|
) {
|
||||||
|
// The output regenerated function list.
|
||||||
|
RegeneratedList regenlist{};
|
||||||
|
|
||||||
|
std::vector<ModLoadErrorDetails> ret = build_regen_list<false>(sorted_unprocessed_hooks, section_vrom_map, base_patched_funcs, regenlist);
|
||||||
|
if (!ret.empty()) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the regenlist.
|
||||||
|
regenerated_code_handle = apply_regenlist(regenlist, decompressed_rom);
|
||||||
|
if (!regenerated_code_handle || !regenerated_code_handle->good()) {
|
||||||
|
regenerated_code_handle.reset();
|
||||||
|
ret.emplace_back(ModLoadErrorDetails{
|
||||||
|
"", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InternalError)
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!regenlist.patched_hooks.empty()) {
|
||||||
|
// Create new hook definitions based on the actual addresses in the patch binary.
|
||||||
|
std::vector<std::pair<HookDefinition, size_t>> base_patched_hooks{};
|
||||||
|
base_patched_hooks.resize(regenlist.patched_hooks.size());
|
||||||
|
for (size_t i = 0; i < regenlist.patched_hooks.size(); i++) {
|
||||||
|
const auto& regenlist_entry = regenlist.patched_hooks[i];
|
||||||
|
uint16_t patch_section_index = static_cast<uint16_t>(regenlist_entry.first.patch_section);
|
||||||
|
uint32_t patch_section_ram_addr = overlays::get_patch_section_ram_addr(patch_section_index);
|
||||||
|
const FuncEntry* func_entry = overlays::get_patch_function_entry(patch_section_index, regenlist_entry.first.function_index);
|
||||||
|
base_patched_hooks[i].first = HookDefinition {
|
||||||
|
.section_rom = overlays::get_patch_section_rom_addr(patch_section_index),
|
||||||
|
.function_vram = patch_section_ram_addr + func_entry->offset,
|
||||||
|
.at_return = regenlist_entry.second.first.at_return
|
||||||
|
};
|
||||||
|
base_patched_hooks[i].second = regenlist_entry.second.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the hook definitions by rom and ram.
|
||||||
|
std::sort(base_patched_hooks.begin(), base_patched_hooks.end(),
|
||||||
|
[](const std::pair<recomp::mods::HookDefinition, size_t>& lhs, const std::pair<recomp::mods::HookDefinition, size_t>& rhs) {
|
||||||
|
if (lhs.first.section_rom == rhs.first.section_rom) {
|
||||||
|
return lhs.first.function_vram < rhs.first.function_vram;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return lhs.first.section_rom < rhs.first.section_rom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the regenerated list for the base patched functions.
|
||||||
|
std::unordered_map<uint32_t, uint16_t> patch_section_vrom_map = overlays::get_patch_vrom_to_section_map();
|
||||||
|
RegeneratedList patch_regenlist{};
|
||||||
|
std::vector<ModLoadErrorDetails> ret = build_regen_list<true>(base_patched_hooks, patch_section_vrom_map, {}, patch_regenlist);
|
||||||
|
if (!ret.empty()) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the patched function regenlist.
|
||||||
|
base_patched_code_handle = apply_regenlist(patch_regenlist, overlays::get_patch_binary());
|
||||||
|
if (!base_patched_code_handle || !base_patched_code_handle->good()) {
|
||||||
|
regenerated_code_handle.reset();
|
||||||
|
base_patched_code_handle.reset();
|
||||||
|
ret.emplace_back(ModLoadErrorDetails{
|
||||||
|
"", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InternalError)
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors) {
|
void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors) {
|
||||||
errors.clear();
|
errors.clear();
|
||||||
// Prevent mods with dependencies from being toggled at runtime.
|
// Prevent mods with dependencies from being toggled at runtime.
|
||||||
|
|
@ -896,7 +1408,7 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param) {
|
recomp::mods::CodeModLoadError recomp::mods::ModContext::init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param) {
|
||||||
// Load the mod symbol data from the file provided in the manifest.
|
// Load the mod symbol data from the file provided in the manifest.
|
||||||
bool binary_syms_exists = false;
|
bool binary_syms_exists = false;
|
||||||
std::vector<char> syms_data = mod.manifest.file_handle->read_file(std::string{ modpaths::binary_syms_path }, binary_syms_exists);
|
std::vector<char> syms_data = mod.manifest.file_handle->read_file(std::string{ modpaths::binary_syms_path }, binary_syms_exists);
|
||||||
|
|
@ -921,6 +1433,11 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
||||||
return CodeModLoadError::FailedToParseSyms;
|
return CodeModLoadError::FailedToParseSyms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent loading the mod if hooks aren't available and it has any hooks.
|
||||||
|
if (!hooks_available && !mod.recompiler_context->hooks.empty()) {
|
||||||
|
return CodeModLoadError::HooksUnavailable;
|
||||||
|
}
|
||||||
|
|
||||||
// Set all reference sections as relocatable, since the only relocations present in a mod's context
|
// Set all reference sections as relocatable, since the only relocations present in a mod's context
|
||||||
// are ones that target relocatable sections.
|
// are ones that target relocatable sections.
|
||||||
mod.recompiler_context->set_all_reference_sections_relocatable();
|
mod.recompiler_context->set_all_reference_sections_relocatable();
|
||||||
|
|
@ -995,10 +1512,69 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
||||||
|
|
||||||
ram_used = cur_section_addr - load_address;
|
ram_used = cur_section_addr - load_address;
|
||||||
|
|
||||||
|
// Allocate the event indices used by the mod.
|
||||||
|
num_events += mod.num_events();
|
||||||
|
|
||||||
|
// Read the mod's hooks and allocate hook slots as needed.
|
||||||
|
for (const N64Recomp::FunctionHook& hook : mod.recompiler_context->hooks) {
|
||||||
|
// Get the definition of this hook.
|
||||||
|
HookDefinition def {
|
||||||
|
.section_rom = hook.original_section_vrom,
|
||||||
|
.function_vram = hook.original_vram,
|
||||||
|
.at_return = (hook.flags & N64Recomp::HookFlags::AtReturn) == N64Recomp::HookFlags::AtReturn
|
||||||
|
};
|
||||||
|
// Check if the hook definition already exists in the hook slots.
|
||||||
|
auto find_it = hook_slots.find(def);
|
||||||
|
if (find_it == hook_slots.end()) {
|
||||||
|
// The hook definition is new, so assign a hook slot index and add it to the slots.
|
||||||
|
hook_slots.emplace(def, hook_slots.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the mod's binary into the recompiler context so it can be analyzed during code loading.
|
||||||
|
// TODO move it instead, right now the move can't be done because of a signedness difference in the types.
|
||||||
|
mod.recompiler_context->rom.assign(binary_span.begin(), binary_span.end());
|
||||||
|
|
||||||
|
return CodeModLoadError::Good;
|
||||||
|
}
|
||||||
|
|
||||||
|
recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* rdram, recomp::mods::ModHandle& mod, uint32_t base_event_index, std::string& error_param) {
|
||||||
|
// Build the hook list for this mod. Maps function index within mod to hook slot index.
|
||||||
|
std::unordered_map<size_t, size_t> entry_func_hooks{};
|
||||||
|
std::unordered_map<size_t, size_t> return_func_hooks{};
|
||||||
|
|
||||||
|
// Scan the replacements and check for any
|
||||||
|
for (const auto& replacement : mod.recompiler_context->replacements) {
|
||||||
|
// Check if there's a hook slot for the entry of this function.
|
||||||
|
HookDefinition entry_def {
|
||||||
|
.section_rom = replacement.original_section_vrom,
|
||||||
|
.function_vram = replacement.original_vram,
|
||||||
|
.at_return = false
|
||||||
|
};
|
||||||
|
auto find_entry_it = hook_slots.find(entry_def);
|
||||||
|
if (find_entry_it != hook_slots.end()) {
|
||||||
|
entry_func_hooks.emplace(replacement.func_index, find_entry_it->second);
|
||||||
|
processed_hook_slots[find_entry_it->second] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's a hook slot for the return of this function.
|
||||||
|
HookDefinition return_def {
|
||||||
|
.section_rom = replacement.original_section_vrom,
|
||||||
|
.function_vram = replacement.original_vram,
|
||||||
|
.at_return = true
|
||||||
|
};
|
||||||
|
auto find_return_it = hook_slots.find(return_def);
|
||||||
|
if (find_return_it != hook_slots.end()) {
|
||||||
|
return_func_hooks.emplace(replacement.func_index, find_return_it->second);
|
||||||
|
processed_hook_slots[find_return_it->second] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the inputs for the mod code handle.
|
||||||
std::string cur_error_param;
|
std::string cur_error_param;
|
||||||
CodeModLoadError cur_error;
|
CodeModLoadError cur_error;
|
||||||
ModCodeHandleInputs handle_inputs{
|
ModCodeHandleInputs handle_inputs{
|
||||||
.base_event_index = static_cast<uint32_t>(num_events),
|
.base_event_index = base_event_index,
|
||||||
.recomp_trigger_event = recomp_trigger_event,
|
.recomp_trigger_event = recomp_trigger_event,
|
||||||
.get_function = get_function,
|
.get_function = get_function,
|
||||||
.cop0_status_write = cop0_status_write,
|
.cop0_status_write = cop0_status_write,
|
||||||
|
|
@ -1008,17 +1584,15 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
||||||
.reference_section_addresses = section_addresses,
|
.reference_section_addresses = section_addresses,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allocate the event indices used by the mod.
|
|
||||||
num_events += mod.num_events();
|
|
||||||
|
|
||||||
// Copy the mod's binary into the recompiler context so it can be analyzed during code loading.
|
|
||||||
// TODO move it instead, right now the move can't be done because of a signedness difference in the types.
|
|
||||||
mod.recompiler_context->rom.assign(binary_span.begin(), binary_span.end());
|
|
||||||
|
|
||||||
// Use a dynamic library code handle. This feature isn't meant to be used by end users, but provides a more debuggable
|
// Use a dynamic library code handle. This feature isn't meant to be used by end users, but provides a more debuggable
|
||||||
// experience than the live recompiler for mod developers.
|
// experience than the live recompiler for mod developers.
|
||||||
// Enabled if the mod's filename ends with ".offline.dll".
|
// Enabled if the mod's filename ends with ".offline.nrm".
|
||||||
if (mod.manifest.mod_root_path.filename().string().ends_with(".offline.nrm")) {
|
if (mod.manifest.mod_root_path.filename().string().ends_with(".offline.nrm")) {
|
||||||
|
// Hooks can't be generated for native mods, so return an error if any of the functions this mod replaces are also hooked by another mod.
|
||||||
|
if (!entry_func_hooks.empty() || !return_func_hooks.empty()) {
|
||||||
|
return CodeModLoadError::OfflineModHooked;
|
||||||
|
}
|
||||||
|
|
||||||
std::filesystem::path dll_path = mod.manifest.mod_root_path;
|
std::filesystem::path dll_path = mod.manifest.mod_root_path;
|
||||||
dll_path.replace_extension(DynamicLibrary::PlatformExtension);
|
dll_path.replace_extension(DynamicLibrary::PlatformExtension);
|
||||||
mod.code_handle = std::make_unique<DynamicLibraryCodeHandle>(dll_path, *mod.recompiler_context, handle_inputs);
|
mod.code_handle = std::make_unique<DynamicLibraryCodeHandle>(dll_path, *mod.recompiler_context, handle_inputs);
|
||||||
|
|
@ -1042,7 +1616,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
||||||
}
|
}
|
||||||
// Live recompiler code handle.
|
// Live recompiler code handle.
|
||||||
else {
|
else {
|
||||||
mod.code_handle = std::make_unique<LiveRecompilerCodeHandle>(*mod.recompiler_context, handle_inputs);
|
mod.code_handle = std::make_unique<LiveRecompilerCodeHandle>(*mod.recompiler_context, handle_inputs, std::move(entry_func_hooks), std::move(return_func_hooks));
|
||||||
|
|
||||||
if (!mod.code_handle->good()) {
|
if (!mod.code_handle->good()) {
|
||||||
mod.code_handle.reset();
|
mod.code_handle.reset();
|
||||||
|
|
@ -1061,6 +1635,8 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<N64Recomp::Section>& mod_sections = mod.recompiler_context->sections;
|
||||||
|
|
||||||
// Add each function from the mod into the function lookup table.
|
// Add each function from the mod into the function lookup table.
|
||||||
for (size_t func_index = 0; func_index < mod.recompiler_context->functions.size(); func_index++) {
|
for (size_t func_index = 0; func_index < mod.recompiler_context->functions.size(); func_index++) {
|
||||||
const auto& func = mod.recompiler_context->functions[func_index];
|
const auto& func = mod.recompiler_context->functions[func_index];
|
||||||
|
|
@ -1082,7 +1658,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
||||||
return CodeModLoadError::Good;
|
return CodeModLoadError::Good;
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, std::string& error_param) {
|
recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param) {
|
||||||
// Reference symbols.
|
// Reference symbols.
|
||||||
std::string reference_syms_error_param{};
|
std::string reference_syms_error_param{};
|
||||||
CodeModLoadError reference_syms_error = mod.code_handle->populate_reference_symbols(*mod.recompiler_context, reference_syms_error_param);
|
CodeModLoadError reference_syms_error = mod.code_handle->populate_reference_symbols(*mod.recompiler_context, reference_syms_error_param);
|
||||||
|
|
@ -1173,6 +1749,28 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
|
||||||
recomp::mods::register_event_callback(event_index, func);
|
recomp::mods::register_event_callback(event_index, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register hooks.
|
||||||
|
for (const auto& cur_hook : mod.recompiler_context->hooks) {
|
||||||
|
// Get the definition of this hook.
|
||||||
|
HookDefinition def {
|
||||||
|
.section_rom = cur_hook.original_section_vrom,
|
||||||
|
.function_vram = cur_hook.original_vram,
|
||||||
|
.at_return = (cur_hook.flags & N64Recomp::HookFlags::AtReturn) == N64Recomp::HookFlags::AtReturn
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the hook's slot from the definition.
|
||||||
|
auto find_it = hook_slots.find(def);
|
||||||
|
if (find_it == hook_slots.end()) {
|
||||||
|
error_param = "Failed to register hook";
|
||||||
|
// This should never happen, as hooks are scanned earlier to generate hook_slots.
|
||||||
|
return CodeModLoadError::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the function handle for this hook slot.
|
||||||
|
GenericFunction func = mod.code_handle->get_function_handle(cur_hook.func_index);
|
||||||
|
recomp::mods::register_hook(find_it->second, func);
|
||||||
|
}
|
||||||
|
|
||||||
// Populate the relocated section addresses for the mod.
|
// Populate the relocated section addresses for the mod.
|
||||||
for (size_t section_index = 0; section_index < mod.section_load_addresses.size(); section_index++) {
|
for (size_t section_index = 0; section_index < mod.section_load_addresses.size(); section_index++) {
|
||||||
mod.code_handle->set_local_section_address(section_index, mod.section_load_addresses[section_index]);
|
mod.code_handle->set_local_section_address(section_index, mod.section_load_addresses[section_index]);
|
||||||
|
|
@ -1191,6 +1789,19 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
|
||||||
return CodeModLoadError::InvalidFunctionReplacement;
|
return CodeModLoadError::InvalidFunctionReplacement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this function has already been patched by the base recomp, but allow it if this is a force patch.
|
||||||
|
if ((replacement.flags & N64Recomp::ReplacementFlags::Force) == N64Recomp::ReplacementFlags(0)) {
|
||||||
|
auto find_it = base_patched_funcs.find(to_replace);
|
||||||
|
if (find_it != base_patched_funcs.end()) {
|
||||||
|
std::stringstream error_param_stream{};
|
||||||
|
error_param_stream << std::hex <<
|
||||||
|
"section: 0x" << replacement.original_section_vrom <<
|
||||||
|
" func: 0x" << std::setfill('0') << std::setw(8) << replacement.original_vram;
|
||||||
|
error_param = error_param_stream.str();
|
||||||
|
return CodeModLoadError::BaseRecompConflict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this function has already been replaced.
|
// Check if this function has already been replaced.
|
||||||
auto find_patch_it = patched_funcs.find(to_replace);
|
auto find_patch_it = patched_funcs.find(to_replace);
|
||||||
if (find_patch_it != patched_funcs.end()) {
|
if (find_patch_it != patched_funcs.end()) {
|
||||||
|
|
@ -1216,7 +1827,10 @@ void recomp::mods::ModContext::unload_mods() {
|
||||||
}
|
}
|
||||||
patched_funcs.clear();
|
patched_funcs.clear();
|
||||||
loaded_mods_by_id.clear();
|
loaded_mods_by_id.clear();
|
||||||
|
hook_slots.clear();
|
||||||
|
processed_hook_slots.clear();
|
||||||
recomp::mods::reset_events();
|
recomp::mods::reset_events();
|
||||||
|
recomp::mods::reset_hooks();
|
||||||
num_events = recomp::overlays::num_base_events();
|
num_events = recomp::overlays::num_base_events();
|
||||||
active_game = (size_t)-1;
|
active_game = (size_t)-1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
|
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
|
#include "recompiler/context.h"
|
||||||
#include "overlays.hpp"
|
#include "overlays.hpp"
|
||||||
#include "sections.h"
|
#include "sections.h"
|
||||||
|
|
||||||
|
|
@ -33,10 +34,12 @@ struct LoadedSection {
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<uint32_t, uint16_t> code_sections_by_rom{};
|
static std::unordered_map<uint32_t, uint16_t> code_sections_by_rom{};
|
||||||
|
static std::unordered_map<uint32_t, uint16_t> patch_code_sections_by_rom{};
|
||||||
static std::vector<LoadedSection> loaded_sections{};
|
static std::vector<LoadedSection> loaded_sections{};
|
||||||
static std::unordered_map<int32_t, recomp_func_t*> func_map{};
|
static std::unordered_map<int32_t, recomp_func_t*> func_map{};
|
||||||
static std::unordered_map<std::string, recomp_func_t*> base_exports{};
|
static std::unordered_map<std::string, recomp_func_t*> base_exports{};
|
||||||
static std::unordered_map<std::string, size_t> base_events;
|
static std::unordered_map<std::string, size_t> base_events;
|
||||||
|
static std::unordered_map<uint32_t, recomp_func_t*> manual_patch_symbols_by_vram;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
int32_t* section_addresses = nullptr;
|
int32_t* section_addresses = nullptr;
|
||||||
|
|
@ -53,6 +56,11 @@ void recomp::overlays::register_patches(const char* patch, std::size_t size, Sec
|
||||||
|
|
||||||
patch_data.resize(size);
|
patch_data.resize(size);
|
||||||
std::memcpy(patch_data.data(), patch, size);
|
std::memcpy(patch_data.data(), patch, size);
|
||||||
|
|
||||||
|
patch_code_sections_by_rom.reserve(num_patch_code_sections);
|
||||||
|
for (size_t i = 0; i < num_patch_code_sections; i++) {
|
||||||
|
patch_code_sections_by_rom.emplace(patch_code_sections[i].rom_addr, i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::overlays::register_base_export(const std::string& name, recomp_func_t* func) {
|
void recomp::overlays::register_base_export(const std::string& name, recomp_func_t* func) {
|
||||||
|
|
@ -112,6 +120,19 @@ const std::unordered_map<uint32_t, uint16_t>& recomp::overlays::get_vrom_to_sect
|
||||||
return code_sections_by_rom;
|
return code_sections_by_rom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t recomp::overlays::get_section_ram_addr(uint16_t code_section_index) {
|
||||||
|
return sections_info.code_sections[code_section_index].ram_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const RelocEntry> recomp::overlays::get_section_relocs(uint16_t code_section_index) {
|
||||||
|
if (code_section_index < sections_info.num_code_sections) {
|
||||||
|
const auto& section = sections_info.code_sections[code_section_index];
|
||||||
|
return std::span{ section.relocs, section.num_relocs };
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void recomp::overlays::add_loaded_function(int32_t ram, recomp_func_t* func) {
|
void recomp::overlays::add_loaded_function(int32_t ram, recomp_func_t* func) {
|
||||||
func_map[ram] = func;
|
func_map[ram] = func;
|
||||||
}
|
}
|
||||||
|
|
@ -259,19 +280,55 @@ void recomp::overlays::init_overlays() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds a function given a section's index and the function's offset into the section.
|
// Finds a function given a section's index and the function's offset into the section.
|
||||||
recomp_func_t* recomp::overlays::get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset) {
|
bool recomp::overlays::get_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out) {
|
||||||
if (code_section_index >= sections_info.num_code_sections) {
|
if (code_section_index >= sections_info.num_code_sections) {
|
||||||
return nullptr;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionTableEntry* section = §ions_info.code_sections[code_section_index];
|
SectionTableEntry* section = §ions_info.code_sections[code_section_index];
|
||||||
if (function_offset >= section->size) {
|
if (function_offset >= section->size) {
|
||||||
return nullptr;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO avoid a linear lookup here.
|
||||||
for (size_t func_index = 0; func_index < section->num_funcs; func_index++) {
|
for (size_t func_index = 0; func_index < section->num_funcs; func_index++) {
|
||||||
if (section->funcs[func_index].offset == function_offset) {
|
if (section->funcs[func_index].offset == function_offset) {
|
||||||
return section->funcs[func_index].func;
|
func_out = section->funcs[func_index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::overlays::register_manual_patch_symbols(const ManualPatchSymbol* manual_patch_symbols) {
|
||||||
|
for (size_t i = 0; manual_patch_symbols[i].func != nullptr; i++) {
|
||||||
|
if (!manual_patch_symbols_by_vram.emplace(manual_patch_symbols[i].ram_addr, manual_patch_symbols[i].func).second) {
|
||||||
|
printf("Duplicate manual patch symbol address: %08X\n", manual_patch_symbols[i].ram_addr);
|
||||||
|
ultramodern::error_handling::message_box("Duplicate manual patch symbol address (syms.ld)!");
|
||||||
|
assert(false && "Duplicate manual patch symbol address (syms.ld)!");
|
||||||
|
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use N64Recomp::is_manual_patch_symbol instead after updating submodule.
|
||||||
|
bool is_manual_patch_symbol(uint32_t vram) {
|
||||||
|
return vram >= 0x8F000000 && vram < 0x90000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds a function given a section's index and the function's offset into the section and returns its native pointer.
|
||||||
|
recomp_func_t* recomp::overlays::get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset) {
|
||||||
|
FuncEntry entry;
|
||||||
|
|
||||||
|
if (get_func_entry_by_section_index_function_offset(code_section_index, function_offset, entry)) {
|
||||||
|
return entry.func;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code_section_index == N64Recomp::SectionAbsolute && is_manual_patch_symbol(function_offset)) {
|
||||||
|
auto find_it = manual_patch_symbols_by_vram.find(function_offset);
|
||||||
|
if (find_it != manual_patch_symbols_by_vram.end()) {
|
||||||
|
return find_it->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,3 +358,97 @@ extern "C" recomp_func_t * get_function(int32_t addr) {
|
||||||
return func_find->second;
|
return func_find->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction> recomp::overlays::get_base_patched_funcs() {
|
||||||
|
std::unordered_map<recomp_func_t*, BasePatchedFunction> ret{};
|
||||||
|
|
||||||
|
// Collect the set of all functions in the patches.
|
||||||
|
std::unordered_map<recomp_func_t*, BasePatchedFunction> all_patch_funcs{};
|
||||||
|
for (size_t patch_section_index = 0; patch_section_index < num_patch_code_sections; patch_section_index++) {
|
||||||
|
const auto& patch_section = patch_code_sections[patch_section_index];
|
||||||
|
for (size_t func_index = 0; func_index < patch_section.num_funcs; func_index++) {
|
||||||
|
all_patch_funcs.emplace(patch_section.funcs[func_index].func, BasePatchedFunction{ .patch_section = patch_section_index, .function_index = func_index });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check every vanilla function against the full patch function set.
|
||||||
|
// Any functions in both are patched.
|
||||||
|
for (size_t code_section_index = 0; code_section_index < sections_info.num_code_sections; code_section_index++) {
|
||||||
|
const auto& code_section = sections_info.code_sections[code_section_index];
|
||||||
|
for (size_t func_index = 0; func_index < code_section.num_funcs; func_index++) {
|
||||||
|
recomp_func_t* cur_func = code_section.funcs[func_index].func;
|
||||||
|
// If this function also exists in the patches function set then it's a vanilla function that was patched.
|
||||||
|
auto find_it = all_patch_funcs.find(cur_func);
|
||||||
|
if (find_it != all_patch_funcs.end()) {
|
||||||
|
ret.emplace(cur_func, find_it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<uint32_t, uint16_t>& recomp::overlays::get_patch_vrom_to_section_map() {
|
||||||
|
return patch_code_sections_by_rom;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t recomp::overlays::get_patch_section_ram_addr(uint16_t patch_code_section_index) {
|
||||||
|
if (patch_code_section_index < num_patch_code_sections) {
|
||||||
|
return patch_code_sections[patch_code_section_index].ram_addr;
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t recomp::overlays::get_patch_section_rom_addr(uint16_t patch_code_section_index) {
|
||||||
|
if (patch_code_section_index < num_patch_code_sections) {
|
||||||
|
return patch_code_sections[patch_code_section_index].rom_addr;
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FuncEntry* recomp::overlays::get_patch_function_entry(uint16_t patch_code_section_index, size_t function_index) {
|
||||||
|
if (patch_code_section_index < num_patch_code_sections) {
|
||||||
|
const auto& section = patch_code_sections[patch_code_section_index];
|
||||||
|
if (function_index < section.num_funcs) {
|
||||||
|
return §ion.funcs[function_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds a base patched function given a patch section's index and the function's offset into the section.
|
||||||
|
bool recomp::overlays::get_patch_func_entry_by_section_index_function_offset(uint16_t patch_code_section_index, uint32_t function_offset, FuncEntry& func_out) {
|
||||||
|
if (patch_code_section_index >= num_patch_code_sections) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionTableEntry* section = &patch_code_sections[patch_code_section_index];
|
||||||
|
if (function_offset >= section->size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO avoid a linear lookup here.
|
||||||
|
for (size_t func_index = 0; func_index < section->num_funcs; func_index++) {
|
||||||
|
if (section->funcs[func_index].offset == function_offset) {
|
||||||
|
func_out = section->funcs[func_index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const RelocEntry> recomp::overlays::get_patch_section_relocs(uint16_t patch_code_section_index) {
|
||||||
|
if (patch_code_section_index < num_patch_code_sections) {
|
||||||
|
const auto& section = patch_code_sections[patch_code_section_index];
|
||||||
|
return std::span{ section.relocs, section.num_relocs };
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const uint8_t> recomp::overlays::get_patch_binary() {
|
||||||
|
return std::span{ reinterpret_cast<const uint8_t*>(patch_data.data()), patch_data.size() };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ void recomp::set_rom_contents(std::vector<uint8_t>&& new_rom) {
|
||||||
rom = std::move(new_rom);
|
rom = std::move(new_rom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::span<const uint8_t> recomp::get_rom() {
|
||||||
|
return rom;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr uint32_t k1_to_phys(uint32_t addr) {
|
constexpr uint32_t k1_to_phys(uint32_t addr) {
|
||||||
return addr & 0x1FFFFFFF;
|
return addr & 0x1FFFFFFF;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -460,13 +460,13 @@ void init(uint8_t* rdram, recomp_context* ctx, gpr entrypoint) {
|
||||||
|
|
||||||
// Initialize variables normally set by IPL3
|
// Initialize variables normally set by IPL3
|
||||||
constexpr int32_t osTvType = 0x80000300;
|
constexpr int32_t osTvType = 0x80000300;
|
||||||
constexpr int32_t osRomType = 0x80000304;
|
//constexpr int32_t osRomType = 0x80000304;
|
||||||
constexpr int32_t osRomBase = 0x80000308;
|
constexpr int32_t osRomBase = 0x80000308;
|
||||||
constexpr int32_t osResetType = 0x8000030c;
|
constexpr int32_t osResetType = 0x8000030c;
|
||||||
constexpr int32_t osCicId = 0x80000310;
|
//constexpr int32_t osCicId = 0x80000310;
|
||||||
constexpr int32_t osVersion = 0x80000314;
|
//constexpr int32_t osVersion = 0x80000314;
|
||||||
constexpr int32_t osMemSize = 0x80000318;
|
constexpr int32_t osMemSize = 0x80000318;
|
||||||
constexpr int32_t osAppNMIBuffer = 0x8000031c;
|
//constexpr int32_t osAppNMIBuffer = 0x8000031c;
|
||||||
MEM_W(osTvType, 0) = 1; // NTSC
|
MEM_W(osTvType, 0) = 1; // NTSC
|
||||||
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
|
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
|
||||||
MEM_W(osResetType, 0) = 0; // cold reset
|
MEM_W(osResetType, 0) = 0; // cold reset
|
||||||
|
|
@ -536,7 +536,7 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) {
|
||||||
std::vector<recomp::mods::ModLoadErrorDetails> mod_load_errors;
|
std::vector<recomp::mods::ModLoadErrorDetails> mod_load_errors;
|
||||||
{
|
{
|
||||||
std::lock_guard lock { mod_context_mutex };
|
std::lock_guard lock { mod_context_mutex };
|
||||||
mod_load_errors = mod_context->load_mods(game_entry.mod_game_id, rdram, recomp::mod_rdram_start, mod_ram_used);
|
mod_load_errors = mod_context->load_mods(game_entry, rdram, recomp::mod_rdram_start, mod_ram_used);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mod_load_errors.empty()) {
|
if (!mod_load_errors.empty()) {
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ namespace ultramodern {
|
||||||
/**
|
/**
|
||||||
* This callback is optional. If not provided a library default will be used.
|
* This callback is optional. If not provided a library default will be used.
|
||||||
*/
|
*/
|
||||||
get_graphics_api_name_t *get_graphics_api_name;
|
get_graphics_api_name_t *get_graphics_api_name = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
void set_callbacks(const callbacks_t& callbacks);
|
void set_callbacks(const callbacks_t& callbacks);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue