mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-13 20:33:05 +00:00
747 lines
28 KiB
C++
747 lines
28 KiB
C++
#include <span>
|
|
#include <fstream>
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
|
|
#include "librecomp/mods.hpp"
|
|
#include "librecomp/overlays.hpp"
|
|
#include "librecomp/game.hpp"
|
|
#include "n64recomp.h"
|
|
|
|
#if defined(_WIN32)
|
|
#define PATHFMT "%ls"
|
|
#else
|
|
#define PATHFMT "%s"
|
|
#endif
|
|
|
|
template<class... Ts>
|
|
struct overloaded : Ts... { using Ts::operator()...; };
|
|
template<class... Ts>
|
|
overloaded(Ts...) -> overloaded<Ts...>;
|
|
|
|
#if defined(_WIN32)
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# include "Windows.h"
|
|
|
|
class recomp::mods::DynamicLibrary {
|
|
public:
|
|
static constexpr std::string_view PlatformExtension = ".dll";
|
|
DynamicLibrary() = default;
|
|
DynamicLibrary(const std::filesystem::path& path) {
|
|
native_handle = LoadLibraryW(path.c_str());
|
|
|
|
if (good()) {
|
|
uint32_t* recomp_api_version;
|
|
if (get_dll_symbol(recomp_api_version, "recomp_api_version")) {
|
|
api_version = *recomp_api_version;
|
|
}
|
|
else {
|
|
api_version = (uint32_t)-1;
|
|
}
|
|
}
|
|
}
|
|
~DynamicLibrary() {
|
|
unload();
|
|
}
|
|
DynamicLibrary(const DynamicLibrary&) = delete;
|
|
DynamicLibrary& operator=(const DynamicLibrary&) = delete;
|
|
DynamicLibrary(DynamicLibrary&&) = delete;
|
|
DynamicLibrary& operator=(DynamicLibrary&&) = delete;
|
|
|
|
void unload() {
|
|
if (native_handle != nullptr) {
|
|
FreeLibrary(native_handle);
|
|
}
|
|
native_handle = nullptr;
|
|
}
|
|
|
|
bool good() const {
|
|
return native_handle != nullptr;
|
|
}
|
|
|
|
template <typename T>
|
|
bool get_dll_symbol(T& out, const char* name) const {
|
|
out = (T)GetProcAddress(native_handle, name);
|
|
if (out == nullptr) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
uint32_t get_api_version() {
|
|
return api_version;
|
|
}
|
|
private:
|
|
HMODULE native_handle;
|
|
uint32_t api_version;
|
|
};
|
|
|
|
void unprotect(void* target_func, uint64_t* old_flags) {
|
|
DWORD old_flags_dword;
|
|
BOOL result = VirtualProtect(target_func,
|
|
16,
|
|
PAGE_READWRITE,
|
|
&old_flags_dword);
|
|
*old_flags = old_flags_dword;
|
|
(void)result;
|
|
}
|
|
|
|
void protect(void* target_func, uint64_t old_flags) {
|
|
DWORD dummy_old_flags;
|
|
BOOL result = VirtualProtect(target_func,
|
|
16,
|
|
static_cast<DWORD>(old_flags),
|
|
&dummy_old_flags);
|
|
(void)result;
|
|
}
|
|
#else
|
|
# error "Mods not implemented yet on this platform"
|
|
#endif
|
|
|
|
recomp::mods::ModLoadError recomp::mods::validate_api_version(uint32_t api_version, std::string& error_param) {
|
|
switch (api_version) {
|
|
case 1:
|
|
return ModLoadError::Good;
|
|
case (size_t)-1:
|
|
return ModLoadError::NoSpecifiedApiVersion;
|
|
default:
|
|
error_param = std::to_string(api_version);
|
|
return ModLoadError::UnsupportedApiVersion;
|
|
}
|
|
}
|
|
|
|
recomp::mods::ModHandle::ModHandle(ModManifest&& manifest) :
|
|
manifest(std::move(manifest)),
|
|
code_handle(),
|
|
recompiler_context{std::make_unique<N64Recomp::Context>()}
|
|
{
|
|
|
|
}
|
|
|
|
recomp::mods::ModHandle::ModHandle(ModHandle&& rhs) = default;
|
|
recomp::mods::ModHandle& recomp::mods::ModHandle::operator=(ModHandle&& rhs) = default;
|
|
recomp::mods::ModHandle::~ModHandle() = default;
|
|
|
|
size_t recomp::mods::ModHandle::num_exports() const {
|
|
return recompiler_context->exported_funcs.size();
|
|
}
|
|
|
|
size_t recomp::mods::ModHandle::num_events() const {
|
|
return recompiler_context->event_symbols.size();
|
|
}
|
|
|
|
recomp::mods::ModLoadError recomp::mods::ModHandle::populate_exports(std::string& error_param) {
|
|
for (size_t func_index : recompiler_context->exported_funcs) {
|
|
const auto& func_handle = recompiler_context->functions[func_index];
|
|
exports_by_name.emplace(func_handle.name, func_index);
|
|
}
|
|
|
|
return ModLoadError::Good;
|
|
}
|
|
|
|
recomp::mods::ModLoadError recomp::mods::ModHandle::load_native_library(const recomp::mods::NativeLibraryManifest& lib_manifest, std::string& error_param) {
|
|
std::string lib_filename = lib_manifest.name + std::string{DynamicLibrary::PlatformExtension};
|
|
std::filesystem::path lib_path = manifest.mod_root_path.parent_path() / lib_filename;
|
|
|
|
std::unique_ptr<DynamicLibrary>& lib = native_libraries.emplace_back(std::make_unique<DynamicLibrary>(lib_path));
|
|
|
|
if (!lib->good()) {
|
|
error_param = lib_filename;
|
|
return ModLoadError::FailedToLoadNativeLibrary;
|
|
}
|
|
|
|
std::string api_error_param;
|
|
ModLoadError api_error = validate_api_version(lib->get_api_version(), api_error_param);
|
|
|
|
if (api_error != ModLoadError::Good) {
|
|
if (api_error_param.empty()) {
|
|
error_param = lib_filename;
|
|
}
|
|
else {
|
|
error_param = lib_filename + ":" + api_error_param;
|
|
}
|
|
return api_error;
|
|
}
|
|
|
|
for (const std::string& export_name : lib_manifest.exports) {
|
|
recomp_func_t* cur_func;
|
|
if (native_library_exports.contains(export_name)) {
|
|
error_param = export_name;
|
|
return ModLoadError::DuplicateExport;
|
|
}
|
|
if (!lib->get_dll_symbol(cur_func, export_name.c_str())) {
|
|
error_param = lib_manifest.name + ":" + export_name;
|
|
return ModLoadError::FailedToFindNativeExport;
|
|
}
|
|
native_library_exports.emplace(export_name, cur_func);
|
|
}
|
|
|
|
return ModLoadError::Good;
|
|
}
|
|
|
|
bool recomp::mods::ModHandle::get_export_function(const std::string& export_name, GenericFunction& out) const {
|
|
// First, check the code exports.
|
|
auto code_find_it = exports_by_name.find(export_name);
|
|
if (code_find_it != exports_by_name.end()) {
|
|
out = code_handle->get_function_handle(code_find_it->second);
|
|
return true;
|
|
}
|
|
|
|
// Next, check the native library exports.
|
|
auto native_find_it = native_library_exports.find(export_name);
|
|
if (native_find_it != native_library_exports.end()) {
|
|
out = native_find_it->second;
|
|
return true;
|
|
}
|
|
|
|
|
|
// Nothing found.
|
|
return false;
|
|
}
|
|
|
|
recomp::mods::ModLoadError recomp::mods::ModHandle::populate_events(size_t base_event_index, std::string& error_param) {
|
|
for (size_t event_index = 0; event_index < recompiler_context->event_symbols.size(); event_index++) {
|
|
const N64Recomp::EventSymbol& event = recompiler_context->event_symbols[event_index];
|
|
events_by_name.emplace(event.base.name, event_index);
|
|
}
|
|
|
|
code_handle->set_base_event_index(base_event_index);
|
|
return ModLoadError::Good;
|
|
}
|
|
|
|
bool recomp::mods::ModHandle::get_global_event_index(const std::string& event_name, size_t& event_index_out) const {
|
|
auto find_it = events_by_name.find(event_name);
|
|
if (find_it == events_by_name.end()) {
|
|
return false;
|
|
}
|
|
|
|
event_index_out = code_handle->get_base_event_index() + find_it->second;
|
|
return true;
|
|
}
|
|
|
|
recomp::mods::NativeCodeHandle::NativeCodeHandle(const std::filesystem::path& dll_path, const N64Recomp::Context& context) {
|
|
// Load the DLL.
|
|
dynamic_lib = std::make_unique<DynamicLibrary>(dll_path);
|
|
if (!dynamic_lib->good()) {
|
|
is_good = false;
|
|
return;
|
|
}
|
|
|
|
// Fill out the list of function pointers.
|
|
functions.resize(context.functions.size());
|
|
for (size_t i = 0; i < functions.size(); i++) {
|
|
if(!context.functions[i].name.empty()) {
|
|
is_good &= dynamic_lib->get_dll_symbol(functions[i], context.functions[i].name.c_str());
|
|
}
|
|
else {
|
|
std::string func_name = "mod_func_" + std::to_string(i);
|
|
is_good &= dynamic_lib->get_dll_symbol(functions[i], func_name.c_str());
|
|
}
|
|
if (!is_good) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get the standard exported symbols.
|
|
is_good = true;
|
|
is_good &= dynamic_lib->get_dll_symbol(imported_funcs, "imported_funcs");
|
|
is_good &= dynamic_lib->get_dll_symbol(reference_symbol_funcs, "reference_symbol_funcs");
|
|
is_good &= dynamic_lib->get_dll_symbol(base_event_index, "base_event_index");
|
|
is_good &= dynamic_lib->get_dll_symbol(recomp_trigger_event, "recomp_trigger_event");
|
|
is_good &= dynamic_lib->get_dll_symbol(get_function, "get_function");
|
|
is_good &= dynamic_lib->get_dll_symbol(reference_section_addresses, "reference_section_addresses");
|
|
is_good &= dynamic_lib->get_dll_symbol(section_addresses, "section_addresses");
|
|
}
|
|
|
|
bool recomp::mods::NativeCodeHandle::good() {
|
|
return dynamic_lib->good() && is_good;
|
|
}
|
|
|
|
uint32_t recomp::mods::NativeCodeHandle::get_api_version() {
|
|
return dynamic_lib->get_api_version();
|
|
}
|
|
|
|
void recomp::mods::NativeCodeHandle::set_bad() {
|
|
dynamic_lib.reset();
|
|
is_good = false;
|
|
}
|
|
|
|
void recomp::mods::NativeCodeHandle::set_imported_function(size_t import_index, GenericFunction func) {
|
|
std::visit(overloaded {
|
|
[this, import_index](recomp_func_t* native_func) {
|
|
imported_funcs[import_index] = native_func;
|
|
}
|
|
}, func);
|
|
}
|
|
|
|
void patch_func(recomp_func_t* target_func, recomp::mods::GenericFunction replacement_func) {
|
|
static const uint8_t movabs_rax[] = {0x48, 0xB8};
|
|
static const uint8_t jmp_rax[] = {0xFF, 0xE0};
|
|
uint8_t* target_func_u8 = reinterpret_cast<uint8_t*>(target_func);
|
|
size_t offset = 0;
|
|
|
|
auto write_bytes = [&](const void* bytes, size_t count) {
|
|
memcpy(target_func_u8 + offset, bytes, count);
|
|
offset += count;
|
|
};
|
|
|
|
uint64_t old_flags;
|
|
unprotect(target_func_u8, &old_flags);
|
|
|
|
std::visit(overloaded {
|
|
[&write_bytes](recomp_func_t* native_func) {
|
|
write_bytes(movabs_rax, sizeof(movabs_rax));
|
|
write_bytes(&native_func, sizeof(&native_func));
|
|
write_bytes(jmp_rax, sizeof(jmp_rax));
|
|
}
|
|
}, replacement_func);
|
|
|
|
protect(target_func_u8, old_flags);
|
|
}
|
|
|
|
void unpatch_func(void* target_func, const recomp::mods::PatchData& data) {
|
|
uint64_t old_flags;
|
|
unprotect(target_func, &old_flags);
|
|
memcpy(target_func, data.replaced_bytes.data(), data.replaced_bytes.size());
|
|
protect(target_func, old_flags);
|
|
}
|
|
|
|
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest) {
|
|
opened_mods.emplace_back(std::move(manifest));
|
|
}
|
|
|
|
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param) {
|
|
using namespace recomp::mods;
|
|
handle.section_load_addresses.clear();
|
|
|
|
// Load the mod symbol data from the file provided in the manifest.
|
|
bool binary_syms_exists = false;
|
|
std::vector<char> syms_data = handle.manifest.file_handle->read_file(handle.manifest.binary_syms_path, binary_syms_exists);
|
|
|
|
if (!binary_syms_exists) {
|
|
return recomp::mods::ModLoadError::FailedToLoadSyms;
|
|
}
|
|
|
|
// Load the binary data from the file provided in the manifest.
|
|
bool binary_exists = false;
|
|
std::vector<char> binary_data = handle.manifest.file_handle->read_file(handle.manifest.binary_path, binary_exists);
|
|
|
|
if (!binary_exists) {
|
|
return recomp::mods::ModLoadError::FailedToLoadBinary;
|
|
}
|
|
|
|
std::span<uint8_t> binary_span {reinterpret_cast<uint8_t*>(binary_data.data()), binary_data.size() };
|
|
|
|
// Parse the symbol file into the recompiler context.
|
|
N64Recomp::ModSymbolsError symbol_load_error = N64Recomp::parse_mod_symbols(syms_data, binary_span, section_vrom_map, *handle.recompiler_context);
|
|
if (symbol_load_error != N64Recomp::ModSymbolsError::Good) {
|
|
return ModLoadError::FailedToLoadSyms;
|
|
}
|
|
|
|
handle.section_load_addresses.resize(handle.recompiler_context->sections.size());
|
|
|
|
// Copy each section's binary into rdram, leaving room for the section's bss before the next one.
|
|
int32_t cur_section_addr = load_address;
|
|
for (size_t section_index = 0; section_index < handle.recompiler_context->sections.size(); section_index++) {
|
|
const auto& section = handle.recompiler_context->sections[section_index];
|
|
for (size_t i = 0; i < section.size; i++) {
|
|
MEM_B(i, (gpr)cur_section_addr) = binary_data[section.rom_addr + i];
|
|
}
|
|
handle.section_load_addresses[section_index] = cur_section_addr;
|
|
cur_section_addr += section.size + section.bss_size;
|
|
}
|
|
|
|
ram_used = cur_section_addr - load_address;
|
|
|
|
return ModLoadError::Good;
|
|
}
|
|
|
|
std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mod_folder(const std::filesystem::path& mod_folder) {
|
|
std::vector<recomp::mods::ModOpenErrorDetails> ret{};
|
|
std::error_code ec;
|
|
for (const auto& mod_path : std::filesystem::directory_iterator{mod_folder, std::filesystem::directory_options::skip_permission_denied, ec}) {
|
|
if ((mod_path.is_regular_file() && mod_path.path().extension() == ".zip") || mod_path.is_directory()) {
|
|
printf("Opening mod " PATHFMT "\n", mod_path.path().stem().c_str());
|
|
std::string open_error_param;
|
|
ModOpenError open_error = open_mod(mod_path, open_error_param);
|
|
|
|
if (open_error != ModOpenError::Good) {
|
|
ret.emplace_back(mod_path.path(), open_error, open_error_param);
|
|
}
|
|
}
|
|
else {
|
|
printf("Skipping non-mod " PATHFMT PATHFMT "\n", mod_path.path().stem().c_str(), mod_path.path().extension().c_str());
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Nothing needed for these two, they just need to be explicitly declared outside the header to allow forward declaration of ModHandle.
|
|
recomp::mods::ModContext::ModContext() = default;
|
|
recomp::mods::ModContext::~ModContext() = default;
|
|
|
|
void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enabled) {
|
|
if (enabled) {
|
|
enabled_mods.emplace(mod_id);
|
|
}
|
|
else {
|
|
enabled_mods.erase(mod_id);
|
|
}
|
|
}
|
|
|
|
bool recomp::mods::ModContext::is_mod_enabled(const std::string& mod_id) {
|
|
return enabled_mods.contains(mod_id);
|
|
}
|
|
|
|
size_t recomp::mods::ModContext::num_opened_mods() {
|
|
return opened_mods.size();
|
|
}
|
|
|
|
std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used) {
|
|
std::vector<recomp::mods::ModLoadErrorDetails> ret{};
|
|
ram_used = 0;
|
|
num_events = recomp::overlays::num_base_events();
|
|
|
|
if (!patched_funcs.empty()) {
|
|
printf("Mods already loaded!\n");
|
|
return {};
|
|
}
|
|
|
|
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map = recomp::overlays::get_vrom_to_section_map();
|
|
|
|
std::vector<size_t> active_mods{};
|
|
|
|
// Find and load active mods.
|
|
for (size_t mod_index = 0; mod_index < opened_mods.size(); mod_index++) {
|
|
auto& mod = opened_mods[mod_index];
|
|
if (enabled_mods.contains(mod.manifest.mod_id)) {
|
|
active_mods.push_back(mod_index);
|
|
loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index);
|
|
|
|
printf("Loading mod %s\n", mod.manifest.mod_id.c_str());
|
|
uint32_t cur_ram_used = 0;
|
|
std::string load_error_param;
|
|
ModLoadError load_error = load_mod(rdram, section_vrom_map, mod, load_address, cur_ram_used, load_error_param);
|
|
|
|
if (load_error != ModLoadError::Good) {
|
|
ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param);
|
|
}
|
|
else {
|
|
load_address += cur_ram_used;
|
|
ram_used += cur_ram_used;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exit early if errors were found.
|
|
if (!ret.empty()) {
|
|
unload_mods();
|
|
return ret;
|
|
}
|
|
|
|
// Check that mod dependencies are met.
|
|
for (size_t mod_index : active_mods) {
|
|
auto& mod = opened_mods[mod_index];
|
|
std::vector<std::pair<ModLoadError, std::string>> cur_errors;
|
|
check_dependencies(mod, cur_errors);
|
|
|
|
if (!cur_errors.empty()) {
|
|
for (auto const& [cur_error, cur_error_param] : cur_errors) {
|
|
ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exit early if errors were found.
|
|
if (!ret.empty()) {
|
|
unload_mods();
|
|
return ret;
|
|
}
|
|
|
|
// Load the code and exports from all mods.
|
|
for (size_t mod_index : active_mods) {
|
|
auto& mod = opened_mods[mod_index];
|
|
std::string cur_error_param;
|
|
ModLoadError cur_error = load_mod_code(mod, cur_error_param);
|
|
if (cur_error != ModLoadError::Good) {
|
|
ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param);
|
|
}
|
|
}
|
|
|
|
// Exit early if errors were found.
|
|
if (!ret.empty()) {
|
|
unload_mods();
|
|
return ret;
|
|
}
|
|
|
|
// Set up the event callbacks based on the number of events allocated.
|
|
recomp::mods::setup_events(num_events);
|
|
|
|
// Resolve dependencies for all mods.
|
|
for (size_t mod_index : active_mods) {
|
|
auto& mod = opened_mods[mod_index];
|
|
std::string cur_error_param;
|
|
ModLoadError cur_error = resolve_dependencies(mod, cur_error_param);
|
|
if (cur_error != ModLoadError::Good) {
|
|
ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param);
|
|
}
|
|
}
|
|
|
|
// Exit early if errors were found.
|
|
if (!ret.empty()) {
|
|
unload_mods();
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool dependency_version_met(uint8_t major, uint8_t minor, uint8_t patch, uint8_t major_target, uint8_t minor_target, uint8_t patch_target) {
|
|
if (major > major_target) {
|
|
return true;
|
|
}
|
|
else if (major < major_target) {
|
|
return false;
|
|
}
|
|
|
|
if (minor > minor_target) {
|
|
return true;
|
|
}
|
|
else if (minor < minor_target) {
|
|
return false;
|
|
}
|
|
|
|
if (patch >= patch_target) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors) {
|
|
errors.clear();
|
|
for (N64Recomp::Dependency& cur_dep : mod.recompiler_context->dependencies) {
|
|
// Handle special dependency names.
|
|
if (cur_dep.mod_id == N64Recomp::DependencyBaseRecomp || cur_dep.mod_id == N64Recomp::DependencySelf) {
|
|
continue;
|
|
}
|
|
|
|
// Look for the dependency in the loaded mod mapping.
|
|
auto find_it = loaded_mods_by_id.find(cur_dep.mod_id);
|
|
if (find_it == loaded_mods_by_id.end()) {
|
|
errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id);
|
|
continue;
|
|
}
|
|
|
|
const auto& mod = opened_mods[find_it->second];
|
|
if (!dependency_version_met(
|
|
mod.manifest.major_version, mod.manifest.minor_version, mod.manifest.patch_version,
|
|
cur_dep.major_version, cur_dep.minor_version, cur_dep.patch_version))
|
|
{
|
|
std::stringstream error_param_stream{};
|
|
error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " <<
|
|
(int)cur_dep.major_version << "." << (int)cur_dep.minor_version << "." << (int)cur_dep.patch_version << ", got " <<
|
|
(int)mod.manifest.major_version << "." << (int)mod.manifest.minor_version << "." << (int)mod.manifest.patch_version << "";
|
|
errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str());
|
|
}
|
|
}
|
|
}
|
|
|
|
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods::ModHandle& mod, std::string& error_param) {
|
|
// TODO implement LuaJIT recompilation and allow it instead of native code loading via a mod manifest flag.
|
|
std::filesystem::path dll_path = mod.manifest.mod_root_path;
|
|
dll_path.replace_extension(DynamicLibrary::PlatformExtension);
|
|
mod.code_handle = std::make_unique<NativeCodeHandle>(dll_path, *mod.recompiler_context);
|
|
if (!mod.code_handle->good()) {
|
|
mod.code_handle.reset();
|
|
error_param = dll_path.string();
|
|
return ModLoadError::FailedToLoadNativeCode;
|
|
}
|
|
|
|
std::string cur_error_param;
|
|
ModLoadError cur_error = validate_api_version(mod.code_handle->get_api_version(), cur_error_param);
|
|
|
|
if (cur_error != ModLoadError::Good) {
|
|
if (cur_error_param.empty()) {
|
|
error_param = dll_path.filename().string();
|
|
}
|
|
else {
|
|
error_param = dll_path.filename().string() + ":" + std::move(cur_error_param);
|
|
}
|
|
return cur_error;
|
|
}
|
|
|
|
// Populate the mod's export map.
|
|
cur_error = mod.populate_exports(cur_error_param);
|
|
|
|
if (cur_error != ModLoadError::Good) {
|
|
error_param = std::move(cur_error_param);
|
|
return cur_error;
|
|
}
|
|
|
|
// Load any native libraries specified by the mod and validate/register the expors.
|
|
std::filesystem::path parent_path = mod.manifest.mod_root_path.parent_path();
|
|
for (const recomp::mods::NativeLibraryManifest& cur_lib_manifest: mod.manifest.native_libraries) {
|
|
cur_error = mod.load_native_library(cur_lib_manifest, cur_error_param);
|
|
if (cur_error != ModLoadError::Good) {
|
|
error_param = std::move(cur_error_param);
|
|
return cur_error;
|
|
}
|
|
}
|
|
|
|
// Populate the mod's event map and set its base event index.
|
|
cur_error = mod.populate_events(num_events, cur_error_param);
|
|
|
|
if (cur_error != ModLoadError::Good) {
|
|
error_param = std::move(cur_error_param);
|
|
return cur_error;
|
|
}
|
|
|
|
// Allocate the event indices used by the mod.
|
|
num_events += mod.num_events();
|
|
|
|
return ModLoadError::Good;
|
|
}
|
|
|
|
recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp::mods::ModHandle& mod, std::string& error_param) {
|
|
// Reference symbols from the base recomp.
|
|
for (size_t reference_sym_index = 0; reference_sym_index < mod.recompiler_context->num_regular_reference_symbols(); reference_sym_index++) {
|
|
const N64Recomp::ReferenceSymbol& reference_sym = mod.recompiler_context->get_regular_reference_symbol(reference_sym_index);
|
|
uint32_t reference_section_vrom = mod.recompiler_context->get_reference_section_rom(reference_sym.section_index);
|
|
uint32_t reference_section_vram = mod.recompiler_context->get_reference_section_vram(reference_sym.section_index);
|
|
uint32_t reference_symbol_vram = reference_section_vram + reference_sym.section_offset;
|
|
|
|
recomp_func_t* found_func = recomp::overlays::get_func_by_section_ram(reference_section_vrom, reference_symbol_vram);
|
|
|
|
if (found_func == nullptr) {
|
|
std::stringstream error_param_stream{};
|
|
error_param_stream << std::hex <<
|
|
"section: 0x" << reference_section_vrom <<
|
|
" func: 0x" << std::setfill('0') << std::setw(8) << reference_symbol_vram;
|
|
error_param = error_param_stream.str();
|
|
return ModLoadError::InvalidReferenceSymbol;
|
|
}
|
|
|
|
mod.code_handle->set_reference_symbol_pointer(reference_sym_index, found_func);
|
|
}
|
|
|
|
// Imported symbols.
|
|
for (size_t import_index = 0; import_index < mod.recompiler_context->import_symbols.size(); import_index++) {
|
|
const N64Recomp::ImportSymbol& imported_func = mod.recompiler_context->import_symbols[import_index];
|
|
const N64Recomp::Dependency& dependency = mod.recompiler_context->dependencies[imported_func.dependency_index];
|
|
|
|
GenericFunction func_handle{};
|
|
bool did_find_func = false;
|
|
|
|
if (dependency.mod_id == N64Recomp::DependencyBaseRecomp) {
|
|
recomp_func_t* func_ptr = recomp::overlays::get_base_export(imported_func.base.name);
|
|
did_find_func = func_ptr != nullptr;
|
|
func_handle = func_ptr;
|
|
}
|
|
else if (dependency.mod_id == N64Recomp::DependencySelf) {
|
|
did_find_func = mod.get_export_function(imported_func.base.name, func_handle);
|
|
}
|
|
else {
|
|
auto find_mod_it = loaded_mods_by_id.find(dependency.mod_id);
|
|
if (find_mod_it == loaded_mods_by_id.end()) {
|
|
error_param = dependency.mod_id;
|
|
return ModLoadError::MissingDependency;
|
|
}
|
|
const auto& dependency = opened_mods[find_mod_it->second];
|
|
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
|
|
}
|
|
|
|
if (!did_find_func) {
|
|
error_param = dependency.mod_id + ":" + imported_func.base.name;
|
|
return ModLoadError::InvalidImport;
|
|
}
|
|
|
|
mod.code_handle->set_imported_function(import_index, func_handle);
|
|
}
|
|
|
|
// Register callbacks.
|
|
for (const N64Recomp::Callback& callback : mod.recompiler_context->callbacks) {
|
|
const N64Recomp::DependencyEvent& dependency_event = mod.recompiler_context->dependency_events[callback.dependency_event_index];
|
|
const N64Recomp::Dependency& dependency = mod.recompiler_context->dependencies[dependency_event.dependency_index];
|
|
GenericFunction func = mod.code_handle->get_function_handle(callback.function_index);
|
|
size_t event_index = 0;
|
|
bool did_find_event = false;
|
|
|
|
if (dependency.mod_id == N64Recomp::DependencyBaseRecomp) {
|
|
event_index = recomp::overlays::get_base_event_index(dependency_event.event_name);
|
|
if (event_index != (size_t)-1) {
|
|
did_find_event = true;
|
|
}
|
|
}
|
|
else if (dependency.mod_id == N64Recomp::DependencySelf) {
|
|
did_find_event = mod.get_global_event_index(dependency_event.event_name, event_index);
|
|
}
|
|
else {
|
|
auto find_mod_it = loaded_mods_by_id.find(dependency.mod_id);
|
|
if (find_mod_it == loaded_mods_by_id.end()) {
|
|
error_param = dependency.mod_id;
|
|
return ModLoadError::MissingDependency;
|
|
}
|
|
const auto& dependency_mod = opened_mods[find_mod_it->second];
|
|
did_find_event = dependency_mod.get_global_event_index(dependency_event.event_name, event_index);
|
|
}
|
|
|
|
if (!did_find_event) {
|
|
error_param = dependency.mod_id + ":" + dependency_event.event_name;
|
|
return ModLoadError::InvalidCallbackEvent;
|
|
}
|
|
|
|
recomp::mods::register_event_callback(event_index, func);
|
|
}
|
|
|
|
// Populate the mod's state fields.
|
|
mod.code_handle->set_recomp_trigger_event_pointer(recomp_trigger_event);
|
|
mod.code_handle->set_get_function_pointer(get_function);
|
|
mod.code_handle->set_reference_section_addresses_pointer(section_addresses);
|
|
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]);
|
|
}
|
|
|
|
// Apply all the function replacements in the mod.
|
|
for (const auto& replacement : mod.recompiler_context->replacements) {
|
|
recomp_func_t* to_replace = recomp::overlays::get_func_by_section_ram(replacement.original_section_vrom, replacement.original_vram);
|
|
|
|
if (to_replace == nullptr) {
|
|
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 ModLoadError::InvalidFunctionReplacement;
|
|
}
|
|
|
|
// 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 = mod.manifest.mod_id;
|
|
|
|
// Patch the function to redirect it to the replacement.
|
|
patch_func(to_replace, mod.code_handle->get_function_handle(replacement.func_index));
|
|
}
|
|
|
|
// TODO perform mips32 relocations
|
|
|
|
return ModLoadError::Good;
|
|
}
|
|
|
|
void recomp::mods::ModContext::unload_mods() {
|
|
for (auto& [replacement_func, replacement_data] : patched_funcs) {
|
|
unpatch_func(replacement_func, replacement_data);
|
|
}
|
|
patched_funcs.clear();
|
|
loaded_mods_by_id.clear();
|
|
recomp::mods::reset_events();
|
|
num_events = recomp::overlays::num_base_events();
|
|
}
|