Mod Support (#499)

Integrates the modding functionality in N64ModernRuntime and adds several exported functions for mods to use. Also adds a ROM decompressor so that the runtime has access to the uncompressed code in the ROM for hooking purposes.
This commit is contained in:
Wiseguy
2025-02-14 18:38:10 -05:00
committed by GitHub
parent 0d0f64e32f
commit 91db87632c
42 changed files with 1172 additions and 187 deletions
+102 -16
View File
@@ -25,8 +25,16 @@
#include "zelda_config.h"
#include "zelda_sound.h"
#include "zelda_render.h"
#include "zelda_game.h"
#include "ovl_patches.hpp"
#include "librecomp/game.hpp"
#include "librecomp/mods.hpp"
#include "librecomp/helpers.hpp"
#include "../../patches/graphics.h"
#include "../../patches/input.h"
#include "../../patches/sound.h"
#include "../../patches/misc_funcs.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@@ -36,6 +44,8 @@
#include "../../lib/rt64/src/contrib/stb/stb_image.h"
const std::string version_string = "1.2.0-dev";
template<typename... Ts>
void exit_error(const char* str, Ts ...args) {
// TODO pop up an error
@@ -52,14 +62,12 @@ ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() {
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
#if defined(__linux__)
SDL_SetHint(SDL_HINT_VIDEODRIVER, "x11");
#endif
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) > 0) {
exit_error("Failed to initialize SDL2: %s\n", SDL_GetError());
}
fprintf(stdout, "SDL Video Driver: %s\n", SDL_GetCurrentVideoDriver());
return {};
}
@@ -115,7 +123,13 @@ bool SetImageAsIcon(const char* filename, SDL_Window* window)
SDL_Window* window;
ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) {
window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, SDL_WINDOW_RESIZABLE );
uint32_t flags = SDL_WINDOW_RESIZABLE;
#if defined(RT64_SDL_WINDOW_VULKAN)
flags |= SDL_WINDOW_VULKAN;
#endif
window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, flags);
#if defined(__linux__)
SetImageAsIcon("icons/512.png",window);
if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux
@@ -135,14 +149,8 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::
#if defined(_WIN32)
return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
#elif defined(__ANDROID__)
static_assert(false && "Unimplemented");
#elif defined(__linux__)
if (wmInfo.subsystem != SDL_SYSWM_X11) {
exit_error("Unsupported SDL2 video driver \"%s\". Only X11 is supported on Linux.\n", SDL_GetCurrentVideoDriver());
}
return ultramodern::renderer::WindowHandle{ wmInfo.info.x11.display, wmInfo.info.x11.window };
#elif defined(__linux__) || defined(__ANDROID__)
return ultramodern::renderer::WindowHandle{ window };
#else
static_assert(false && "Unimplemented");
#endif
@@ -327,7 +335,11 @@ std::vector<recomp::GameEntry> supported_games = {
.rom_hash = 0xEF18B4A9E2386169ULL,
.internal_name = "ZELDA MAJORA'S MASK",
.game_id = u8"mm.n64.us.1.0",
.is_enabled = true,
.mod_game_id = "mm",
.save_type = recomp::SaveType::Flashram,
.is_enabled = false,
.decompression_routine = zelda64::decompress_mm,
.has_compressed_code = true,
.entrypoint_address = get_entrypoint_address(),
.entrypoint = recomp_entrypoint,
},
@@ -524,7 +536,27 @@ void release_preload(PreloadContext& context) {
#endif
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
(void)context;
zelda64::renderer::enable_texture_pack(mod);
}
void disable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
(void)context;
zelda64::renderer::disable_texture_pack(mod);
}
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
int main(int argc, char** argv) {
(void)argc;
(void)argv;
recomp::Version project_version{};
if (!recomp::Version::from_string(version_string, project_version)) {
ultramodern::error_handling::message_box(("Invalid version string: " + version_string).c_str());
return EXIT_FAILURE;
}
// Map this executable into memory and lock it, which should keep it in physical memory. This ensures
// that there are no stutters from the OS having to load new pages of the executable whenever a new code page is run.
PreloadContext preload_context;
@@ -568,15 +600,29 @@ int main(int argc, char** argv) {
fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError());
}
recomp::register_config_path(zelda64::get_app_folder_path());
// Register supported games and patches
for (const auto& game : supported_games) {
recomp::register_game(game);
}
REGISTER_FUNC(recomp_get_window_resolution);
REGISTER_FUNC(recomp_get_target_aspect_ratio);
REGISTER_FUNC(recomp_get_target_framerate);
REGISTER_FUNC(recomp_get_autosave_enabled);
REGISTER_FUNC(recomp_get_analog_cam_enabled);
REGISTER_FUNC(recomp_get_camera_inputs);
REGISTER_FUNC(recomp_get_targeting_mode);
REGISTER_FUNC(recomp_get_bgm_volume);
REGISTER_FUNC(recomp_get_low_health_beeps_enabled);
REGISTER_FUNC(recomp_get_gyro_deltas);
REGISTER_FUNC(recomp_get_mouse_deltas);
REGISTER_FUNC(recomp_get_inverted_axes);
REGISTER_FUNC(recomp_get_analog_inverted_axes);
zelda64::register_overlays();
zelda64::register_patches();
recomp::register_config_path(zelda64::get_app_folder_path());
zelda64::load_config();
recomp::rsp::callbacks_t rsp_callbacks{
@@ -619,7 +665,47 @@ int main(int argc, char** argv) {
.get_game_thread_name = zelda64::get_game_thread_name,
};
// Register the texture pack content type with rt64.json as its content file.
recomp::mods::ModContentType texture_pack_content_type{
.content_filename = "rt64.json",
.allow_runtime_toggle = true,
.on_enabled = enable_texture_pack,
.on_disabled = disable_texture_pack,
};
auto texture_pack_content_type_id = recomp::mods::register_mod_content_type(texture_pack_content_type);
// Register the .rtz texture pack file format with the previous content type as its only allowed content type.
recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false);
recomp::mods::scan_mods();
printf("Found mods:\n");
for (const auto& mod : recomp::mods::get_mod_details("mm")) {
printf(" %s(%s)\n", mod.mod_id.c_str(), mod.version.to_string().c_str());
if (!mod.authors.empty()) {
printf(" Authors: %s", mod.authors[0].c_str());
for (size_t author_index = 1; author_index < mod.authors.size(); author_index++) {
const std::string& author = mod.authors[author_index];
printf(", %s", author.c_str());
}
printf("\n");
printf(" Runtime toggleable: %d\n", mod.runtime_toggleable);
}
if (!mod.dependencies.empty()) {
printf(" Dependencies: %s:%s", mod.dependencies[0].mod_id.c_str(), mod.dependencies[0].version.to_string().c_str());
for (size_t dep_index = 1; dep_index < mod.dependencies.size(); dep_index++) {
const recomp::mods::Dependency& dep = mod.dependencies[dep_index];
printf(", %s:%s", dep.mod_id.c_str(), dep.version.to_string().c_str());
}
printf("\n");
}
// TODO load all mods as a temporary solution to not having a UI yet.
recomp::mods::enable_mod(mod.mod_id, true);
}
printf("\n");
recomp::start(
project_version,
{},
rsp_callbacks,
renderer_callbacks,