From 670bd610673432beead61b1b8d41c78f316afde1 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Fri, 5 Jul 2024 21:24:43 -0400 Subject: [PATCH 01/29] Updated RT64 and enabled RT64 extended RDRAM mode --- patches/ui_patches.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/patches/ui_patches.c b/patches/ui_patches.c index a057655..0177611 100644 --- a/patches/ui_patches.c +++ b/patches/ui_patches.c @@ -56,9 +56,10 @@ RECOMP_PATCH void Graph_SetNextGfxPool(GraphicsContext* gfxCtx) { gSPEndDisplayList(&gGfxMasterDL->disps[4]); gSPBranchList(&gGfxMasterDL->debugDisp[0], bigger_pool->debugBuffer); - // @recomp Enable RT64 extended GBI mode and set the current framerate + // @recomp Enable RT64 extended GBI mode and extended rdram. OPEN_DISPS(gfxCtx); gEXEnable(POLY_OPA_DISP++); + gEXSetRDRAMExtended(POLY_OPA_DISP++, 1); CLOSE_DISPS(gfxCtx); } From cf1943fe6af3655eff0e9c80170c4a311dfb8b1e Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 11 Aug 2024 12:51:26 -0400 Subject: [PATCH 02/29] Removed shader cache --- CMakeLists.txt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 62bce6f..d4ef3ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,16 +138,6 @@ add_custom_target(DownloadGameControllerDB add_executable(Zelda64Recompiled) add_dependencies(Zelda64Recompiled DownloadGameControllerDB) -# Generate mm_shader_cache.c from the MM shader cache if it exists -if (EXISTS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin) - set(HAS_MM_SHADER_CACHE TRUE) - target_compile_definitions(Zelda64Recompiled PRIVATE HAS_MM_SHADER_CACHE) - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.h - COMMAND file_to_c ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin mm_shader_cache_bytes ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.h - DEPENDS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin - ) -endif() - set (SOURCES ${CMAKE_SOURCE_DIR}/src/main/main.cpp ${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp @@ -174,10 +164,6 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp ) -if (HAS_MM_SHADER_CACHE) - list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c) -endif() - target_include_directories(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/lib/concurrentqueue From ba39a73dca67898b2afb32f2ceb2c9d53b74e513 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 2 Sep 2024 12:01:22 -0400 Subject: [PATCH 03/29] Update modern runtime for mod support, add some exports and events in the patches --- lib/N64ModernRuntime | 2 +- patches.toml | 2 ++ patches/autosaving.c | 28 ++++++++++++++-------------- patches/patches.h | 5 +++++ patches/patches.ld | 5 +++-- patches/play_patches.c | 7 +++++++ patches/print.c | 2 +- src/main/main.cpp | 9 +++++++-- src/main/register_patches.cpp | 2 ++ src/ui/ui_renderer.cpp | 6 ++++++ us.rev1.toml | 1 + 11 files changed, 49 insertions(+), 20 deletions(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 0a53855..5699906 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 0a538553330ea5fdb1673708704bb92a854241b9 +Subproject commit 5699906f34fcc82905303092d081ad92aa74f926 diff --git a/patches.toml b/patches.toml index 6d928be..2206f23 100644 --- a/patches.toml +++ b/patches.toml @@ -14,3 +14,5 @@ data_reference_syms_files = [ "Zelda64RecompSyms/mm.us.rev1.datasyms.toml", "Zel output_binary_path = "patches/patches.bin" # Do not emit warnings for unpaired LO16 values, as clang produces many of them. unpaired_lo16_warnings = false +# Allow exporting functions and events for mods to use. +allow_exports = true diff --git a/patches/autosaving.c b/patches/autosaving.c index 46f1e8a..809c98d 100644 --- a/patches/autosaving.c +++ b/patches/autosaving.c @@ -67,10 +67,10 @@ RECOMP_PATCH void KaleidoSetup_Update(PlayState* play) { void Sram_SyncWriteToFlash(SramContext* sramCtx, s32 curPage, s32 numPages); -void autosave_reset_timer(); -void autosave_reset_timer_slow(); +void recomp_reset_autosave_timer(); +void recomp_reset_autosave_timer_slow(); -void do_autosave(PlayState* play) { +RECOMP_EXPORT void recomp_do_autosave(PlayState* play) { // Transfer the scene flags into the cycle flags. Play_SaveCycleSceneFlags(&play->state); // Transfer the cycle flags into the save buffer. Logic copied from func_8014546C. @@ -176,7 +176,7 @@ RECOMP_PATCH void func_8014546C(SramContext* sramCtx) { // @recomp Delete the owl save. delete_owl_save(sramCtx, gSaveContext.fileNum); // @recomp Reset the autosave timer. - autosave_reset_timer(); + recomp_reset_autosave_timer(); for (i = 0; i < ARRAY_COUNT(gSaveContext.cycleSceneFlags); i++) { gSaveContext.save.saveInfo.permanentSceneFlags[i].chest = gSaveContext.cycleSceneFlags[i].chest; gSaveContext.save.saveInfo.permanentSceneFlags[i].switch0 = gSaveContext.cycleSceneFlags[i].switch0; @@ -344,11 +344,11 @@ void draw_autosave_icon(PlayState* play) { CLOSE_DISPS(play->state.gfxCtx); } -void show_autosave_icon() { +RECOMP_EXPORT void recomp_show_autosave_icon() { autosave_icon_counter = AUTOSAVE_ICON_TOTAL_FRAMES; } -u32 recomp_autosave_interval() { +RECOMP_EXPORT u32 recomp_autosave_interval() { return 2 * 60 * 1000; } @@ -367,12 +367,12 @@ bool reached_final_three_hours() { return false; } -void autosave_reset_timer() { +RECOMP_EXPORT void recomp_reset_autosave_timer() { last_autosave_time = osGetTime(); extra_autosave_delay_milliseconds = 0; } -void autosave_reset_timer_slow() { +RECOMP_EXPORT void recomp_reset_autosave_timer_slow() { // Set the most recent autosave time in the future to give extra time before an autosave triggers. last_autosave_time = osGetTime(); extra_autosave_delay_milliseconds = 2 * 60 * 1000; @@ -425,20 +425,20 @@ void autosave_post_play_update(PlayState* play) { frames_since_autosave_ready >= MIN_FRAMES_SINCE_READY && time_now - last_autosave_time > (OS_USEC_TO_CYCLES(1000 * (recomp_autosave_interval() + extra_autosave_delay_milliseconds))) ) { - do_autosave(play); - show_autosave_icon(); - autosave_reset_timer(); + recomp_do_autosave(play); + recomp_show_autosave_icon(); + recomp_reset_autosave_timer(); } } else { // Update the last autosave time to the current time to prevent autosaving immediately if autosaves are turned back on. - autosave_reset_timer(); + recomp_reset_autosave_timer(); } gCanPause = false; } void autosave_init() { - autosave_reset_timer_slow(); + recomp_reset_autosave_timer_slow(); Lib_MemCpy(&prev_save_ctx, &gSaveContext, offsetof(SaveContext, fileNum)); } @@ -705,7 +705,7 @@ RECOMP_PATCH void Sram_ResetSaveFromMoonCrash(SramContext* sramCtx) { gSaveContext.jinxTimer = 0; // @recomp Use the slow autosave timer to give the player extra time to respond to the moon crashing to decide if they want to reload their autosave. - autosave_reset_timer_slow(); + recomp_reset_autosave_timer_slow(); } diff --git a/patches/patches.h b/patches/patches.h index f0f2b5e..a93a6fd 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -4,6 +4,11 @@ #define RECOMP_EXPORT __attribute__((section(".recomp_export"))) #define RECOMP_PATCH __attribute__((section(".recomp_patch"))) #define RECOMP_FORCE_PATCH __attribute__((section(".recomp_force_patch"))) +#define RECOMP_DECLARE_EVENT(func) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") \ + __attribute__((noinline, weak, used, section(".recomp_event"))) void func {} \ + _Pragma("GCC diagnostic pop") // TODO fix renaming symbols in patch recompilation #define osCreateMesgQueue osCreateMesgQueue_recomp diff --git a/patches/patches.ld b/patches/patches.ld index b0a3b7f..fad9148 100644 --- a/patches/patches.ld +++ b/patches/patches.ld @@ -1,5 +1,5 @@ RAMBASE = 0x80801000; /* Used to hold any new symbols */ -EXTRA_RAM_SIZE = 0x01000000; /* Amount of extra ram allocated by recomp */ +PATCH_RAM_END = 0x81000000; /* Amount of extra ram allocated by recomp */ MEMORY { extram : ORIGIN = RAMBASE, LENGTH = 64M @@ -12,10 +12,11 @@ SECTIONS { .text : { *(.text*) } >extram AT >rom .recomp_patch : { *(.recomp_patch*) *(.recomp_force_patch*) } >extram AT >rom .recomp_export : { *(.recomp_export*) } >extram AT >rom + .recomp_event : { *(.recomp_event*) } >extram AT >rom .rodata : { *(.rodata*) } >extram AT >rom .data : { *(.data*) } >extram AT >rom .bss (NOLOAD) : { *(.bss*) *(COMMON) } >extram - ASSERT(. < RAMBASE + EXTRA_RAM_SIZE, "Maxed out recomp extra ram") + ASSERT(. <= PATCH_RAM_END, "Maxed out recomp extra ram") .reloc 0 : { *(.reloc*) } .symtab 0 : { *(.symtab) } diff --git a/patches/play_patches.c b/patches/play_patches.c index e3d7c84..eedda5d 100644 --- a/patches/play_patches.c +++ b/patches/play_patches.c @@ -4,6 +4,10 @@ extern Input D_801F6C18; +RECOMP_DECLARE_EVENT(recomp_on_play_main(PlayState* play)); +RECOMP_DECLARE_EVENT(recomp_before_play_update(PlayState* play)); +RECOMP_DECLARE_EVENT(recomp_after_play_update(PlayState* play)); + void controls_play_update(PlayState* play) { gSaveContext.options.zTargetSetting = recomp_get_targeting_mode(); } @@ -12,6 +16,7 @@ void controls_play_update(PlayState* play) { RECOMP_PATCH void Play_Main(GameState* thisx) { static Input* prevInput = NULL; PlayState* this = (PlayState*)thisx; + recomp_on_play_main(this); // @recomp debug_play_update(this); @@ -32,7 +37,9 @@ RECOMP_PATCH void Play_Main(GameState* thisx) { this->state.gfxCtx = NULL; } camera_pre_play_update(this); + recomp_before_play_update(this); Play_Update(this); + recomp_after_play_update(this); camera_post_play_update(this); analog_cam_post_play_update(this); autosave_post_play_update(this); diff --git a/patches/print.c b/patches/print.c index 0fec414..d5216b3 100644 --- a/patches/print.c +++ b/patches/print.c @@ -6,7 +6,7 @@ void* proutPrintf(void* dst, const char* fmt, size_t size) { return (void*)1; } -int recomp_printf(const char* fmt, ...) { +RECOMP_EXPORT int recomp_printf(const char* fmt, ...) { va_list args; va_start(args, fmt); diff --git a/src/main/main.cpp b/src/main/main.cpp index d350b6d..0fbf6f6 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -27,6 +27,7 @@ #include "zelda_render.h" #include "ovl_patches.hpp" #include "librecomp/game.hpp" +#include "librecomp/mods.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -327,6 +328,7 @@ std::vector supported_games = { .rom_hash = 0xEF18B4A9E2386169ULL, .internal_name = "ZELDA MAJORA'S MASK", .game_id = u8"mm.n64.us.1.0", + .mod_game_id = "mm", .is_enabled = true, .entrypoint_address = get_entrypoint_address(), .entrypoint = recomp_entrypoint, @@ -568,6 +570,8 @@ 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); @@ -575,8 +579,6 @@ int main(int argc, char** argv) { 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 +621,10 @@ int main(int argc, char** argv) { .get_game_thread_name = zelda64::get_game_thread_name, }; + recomp::mods::scan_mods(); + recomp::start( + 64 * 1024 * 1024, // 64MB to have plenty of room for loading mods {}, rsp_callbacks, renderer_callbacks, diff --git a/src/main/register_patches.cpp b/src/main/register_patches.cpp index 12c5725..8725f81 100644 --- a/src/main/register_patches.cpp +++ b/src/main/register_patches.cpp @@ -7,4 +7,6 @@ void zelda64::register_patches() { recomp::overlays::register_patches(mm_patches_bin, sizeof(mm_patches_bin), section_table, ARRLEN(section_table)); + recomp::overlays::register_base_exports(export_table); + recomp::overlays::register_base_events(event_name_table); } diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index 8fb93d4..a234d7c 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -1287,6 +1287,12 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s static recompui::Menu prev_menu = recompui::Menu::None; recompui::Menu cur_menu = open_menu.load(); + // Return to the launcher if no menu is open and the game isn't started. + if (cur_menu == recompui::Menu::None && !ultramodern::is_game_started()) { + cur_menu = recompui::Menu::Launcher; + recompui::set_current_menu(cur_menu); + } + if (reload_sheets) { ui_context->rml.load_documents(); prev_menu = recompui::Menu::None; diff --git a/us.rev1.toml b/us.rev1.toml index 7e776f4..7257691 100644 --- a/us.rev1.toml +++ b/us.rev1.toml @@ -5,6 +5,7 @@ entrypoint = 0x80080000 # Paths are relative to the location of this config file. output_func_path = "RecompiledFuncs" relocatable_sections_path = "overlays.us.rev1.txt" +# elf_path = "mm.us.rev1.rom_uncompressed.elf" symbols_file_path = "Zelda64RecompSyms/mm.us.rev1.syms.toml" rom_file_path = "mm.us.rev1.rom_uncompressed.z64" From 2c23d5f29b78c6ed3713dceef87ceb71f4f3bcf5 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 2 Sep 2024 12:04:14 -0400 Subject: [PATCH 04/29] Update N64Recomp commit for CI --- .github/workflows/validate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 4440cc2..442d031 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -9,7 +9,7 @@ on: N64RECOMP_COMMIT: type: string required: false - default: '5b17bf8bb556d2544c6161487232a455eae8f188' + default: 'd33d38161798167929b114c2b0fd445f9670e10a' DXC_CHECKSUM: type: string required: false From be70a2a8f175e185c22403938c5e4ceb02e98916 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 2 Sep 2024 20:00:47 -0400 Subject: [PATCH 05/29] Parse version number from a string and provide it to the runtime --- assets/launcher.rml | 2 +- lib/N64ModernRuntime | 2 +- src/main/main.cpp | 9 +++++++++ src/ui/ui_launcher.cpp | 6 ++++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/assets/launcher.rml b/assets/launcher.rml index d4a77d1..6496117 100644 --- a/assets/launcher.rml +++ b/assets/launcher.rml @@ -58,7 +58,7 @@
- +
diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 5699906..09f5759 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 5699906f34fcc82905303092d081ad92aa74f926 +Subproject commit 09f5759d3a68437d407d54922e3fddf0c52f4dad diff --git a/src/main/main.cpp b/src/main/main.cpp index 0fbf6f6..cc34fa8 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -37,6 +37,8 @@ #include "../../lib/rt64/src/contrib/stb/stb_image.h" +const std::string version_string = "1.2.0-dev"; + template void exit_error(const char* str, Ts ...args) { // TODO pop up an error @@ -527,6 +529,12 @@ void release_preload(PreloadContext& context) { #endif int main(int argc, char** 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; @@ -625,6 +633,7 @@ int main(int argc, char** argv) { recomp::start( 64 * 1024 * 1024, // 64MB to have plenty of room for loading mods + project_version, {}, rsp_callbacks, renderer_callbacks, diff --git a/src/ui/ui_launcher.cpp b/src/ui/ui_launcher.cpp index b4b6312..f699eb7 100644 --- a/src/ui/ui_launcher.cpp +++ b/src/ui/ui_launcher.cpp @@ -6,7 +6,7 @@ #include "nfd.h" #include -std::string version_number = "v1.1.1"; +static std::string version_string; Rml::DataModelHandle model_handle; bool mm_rom_valid = false; @@ -103,7 +103,9 @@ public: Rml::DataModelConstructor constructor = context->CreateDataModel("launcher_model"); constructor.Bind("mm_rom_valid", &mm_rom_valid); - constructor.Bind("version_number", &version_number); + + version_string = recomp::get_project_version().to_string(); + constructor.Bind("version_number", &version_string); model_handle = constructor.GetModelHandle(); } From e6892248c73849024c75782baf457698b5927629 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 2 Sep 2024 23:46:38 -0400 Subject: [PATCH 06/29] Update runtime for new mod manifest schema --- lib/N64ModernRuntime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 09f5759..3718758 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 09f5759d3a68437d407d54922e3fddf0c52f4dad +Subproject commit 3718758cd529c779018a880ab645cec9d9888803 From ea0cc6e6be76e40d189e9e39ec077dae8efdf9f4 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Tue, 3 Sep 2024 00:04:17 -0400 Subject: [PATCH 07/29] Update runtime to have mod list functionality --- lib/N64ModernRuntime | 2 +- src/main/main.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 3718758..986881e 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 3718758cd529c779018a880ab645cec9d9888803 +Subproject commit 986881e4e1b19d39ad0efd5a301be7ed439434b1 diff --git a/src/main/main.cpp b/src/main/main.cpp index cc34fa8..579071a 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -631,6 +631,12 @@ int main(int argc, char** argv) { 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()); + } + printf("\n"); + recomp::start( 64 * 1024 * 1024, // 64MB to have plenty of room for loading mods project_version, From 60b8d58e734a47d2bcf94b792aef78881e7493c4 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Wed, 4 Sep 2024 02:29:01 -0400 Subject: [PATCH 08/29] Update runtime and print mod authors and dependencies --- lib/N64ModernRuntime | 2 +- src/main/main.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 986881e..b9592c6 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 986881e4e1b19d39ad0efd5a301be7ed439434b1 +Subproject commit b9592c625d87ab45ddd12d9ad30c886ee77a5d68 diff --git a/src/main/main.cpp b/src/main/main.cpp index 579071a..910f545 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -634,6 +634,22 @@ int main(int argc, char** argv) { 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"); + } + 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"); + } } printf("\n"); From 359f8a9279a7b429e1314d1e6be872277cc1f431 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 8 Sep 2024 22:45:37 -0400 Subject: [PATCH 09/29] Update runtime for mod loading on posix systems, enable all mods temporarily --- include/recomp_input.h | 5 +++-- lib/N64ModernRuntime | 2 +- src/main/main.cpp | 2 ++ src/main/register_patches.cpp | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/recomp_input.h b/include/recomp_input.h index db9b160..8599628 100644 --- a/include/recomp_input.h +++ b/include/recomp_input.h @@ -120,7 +120,8 @@ namespace recomp { std::vector apply_menu; }; - constexpr const std::vector& get_default_mapping_for_input(const DefaultN64Mappings& defaults, const GameInput input) { + inline const std::vector& get_default_mapping_for_input(const DefaultN64Mappings& defaults, const GameInput input) { + static const std::vector empty_input_field{}; switch (input) { case GameInput::A: return defaults.a; case GameInput::B: return defaults.b; @@ -143,7 +144,7 @@ namespace recomp { case GameInput::TOGGLE_MENU: return defaults.toggle_menu; case GameInput::ACCEPT_MENU: return defaults.accept_menu; case GameInput::APPLY_MENU: return defaults.apply_menu; - default: return std::vector(); + default: return empty_input_field; } } diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index b9592c6..a7e5a77 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit b9592c625d87ab45ddd12d9ad30c886ee77a5d68 +Subproject commit a7e5a7770f2344aa504725af6465e2db4565292c diff --git a/src/main/main.cpp b/src/main/main.cpp index 910f545..6007ffa 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -650,6 +650,8 @@ int main(int argc, char** argv) { } 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"); diff --git a/src/main/register_patches.cpp b/src/main/register_patches.cpp index 8725f81..ef800fd 100644 --- a/src/main/register_patches.cpp +++ b/src/main/register_patches.cpp @@ -8,5 +8,5 @@ void zelda64::register_patches() { recomp::overlays::register_patches(mm_patches_bin, sizeof(mm_patches_bin), section_table, ARRLEN(section_table)); recomp::overlays::register_base_exports(export_table); - recomp::overlays::register_base_events(event_name_table); + recomp::overlays::register_base_events(event_names); } From 2463fe4613a855d899d672fb992e33327cf5efc1 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 9 Sep 2024 02:02:47 -0400 Subject: [PATCH 10/29] Update N64ModernRuntime for mod mips32 relocs and function lookups --- lib/N64ModernRuntime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index a7e5a77..26a0543 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit a7e5a7770f2344aa504725af6465e2db4565292c +Subproject commit 26a05439c2215ea9e1c0b0d572b46b3584a90a4f From 765d1d32f0acd9b920ca99b210df2c38f6460416 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 9 Sep 2024 23:23:57 -0400 Subject: [PATCH 11/29] Update runtime after merge --- lib/N64ModernRuntime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 26a0543..45e9f7a 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 26a05439c2215ea9e1c0b0d572b46b3584a90a4f +Subproject commit 45e9f7a6cb46b743cb4ada142be3b398445a8b20 From eda1979bf0883f9da148f39df4999c330c2e0dfc Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Thu, 12 Sep 2024 00:10:29 -0400 Subject: [PATCH 12/29] Update runtime to fix reference symbols --- lib/N64ModernRuntime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 45e9f7a..81ce18d 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 45e9f7a6cb46b743cb4ada142be3b398445a8b20 +Subproject commit 81ce18d0d3448308d29ca1cc1a835344571126c9 From 33806821e287d898f2e48423b990728c139fac6d Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Thu, 12 Sep 2024 00:55:35 -0400 Subject: [PATCH 13/29] Update runtime for populating missing runtime pointers in offline compiled mods --- lib/N64ModernRuntime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 81ce18d..c4ee705 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 81ce18d0d3448308d29ca1cc1a835344571126c9 +Subproject commit c4ee705250e53af3d84678bcccafeb50e4655e06 From a568261064f1490440f2c40372f70da4c71ab4b8 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Thu, 12 Sep 2024 19:24:04 -0400 Subject: [PATCH 14/29] Update runtime after merge --- lib/N64ModernRuntime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index c4ee705..356b9f9 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit c4ee705250e53af3d84678bcccafeb50e4655e06 +Subproject commit 356b9f901e65dcde3b7368116f5a7e84087b07ef From 5dc260f35aef040253bd20f9d1cdece2630e5c34 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Fri, 27 Sep 2024 00:57:49 -0400 Subject: [PATCH 15/29] Update runtime to add DLL search path, enable strict mode in patch recompilation --- lib/N64ModernRuntime | 2 +- patches.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 356b9f9..46797d6 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 356b9f901e65dcde3b7368116f5a7e84087b07ef +Subproject commit 46797d6cadca6ff78377e7065d073a176d988025 diff --git a/patches.toml b/patches.toml index 2206f23..38910ee 100644 --- a/patches.toml +++ b/patches.toml @@ -16,3 +16,5 @@ output_binary_path = "patches/patches.bin" unpaired_lo16_warnings = false # Allow exporting functions and events for mods to use. allow_exports = true +# # Enable strict patch mode, validates that patched symbols exist and that non-patch functions aren't symbols. +strict_patch_mode = true From 5a8a1cb2ba6936a10a3d4a78df1e8e6f0919740a Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sat, 28 Sep 2024 16:50:58 -0400 Subject: [PATCH 16/29] Update runtime for custom mod content types and add RT64 texture pack support --- include/zelda_render.h | 8 +++++ lib/N64ModernRuntime | 2 +- src/game/config.cpp | 1 - src/main/main.cpp | 23 ++++++++++++++ src/main/rt64_render_context.cpp | 54 ++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 2 deletions(-) diff --git a/include/zelda_render.h b/include/zelda_render.h index 684672b..2d909e4 100644 --- a/include/zelda_render.h +++ b/include/zelda_render.h @@ -1,8 +1,12 @@ #ifndef __ZELDA_RENDER_H__ #define __ZELDA_RENDER_H__ +#include +#include + #include "common/rt64_user_configuration.h" #include "ultramodern/renderer_context.hpp" +#include "librecomp/mods.hpp" namespace RT64 { struct Application; @@ -29,6 +33,7 @@ namespace zelda64 { protected: std::unique_ptr app; + std::unordered_set enabled_texture_packs; }; std::unique_ptr create_render_context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode); @@ -36,6 +41,9 @@ namespace zelda64 { RT64::UserConfiguration::Antialiasing RT64MaxMSAA(); bool RT64SamplePositionsSupported(); bool RT64HighPrecisionFBEnabled(); + + void enable_texture_pack(const recomp::mods::ModHandle& mod); + void disable_texture_pack(const recomp::mods::ModHandle& mod); } } diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 46797d6..a4f5ee1 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 46797d6cadca6ff78377e7065d073a176d988025 +Subproject commit a4f5ee10c8303474d9028468e84845b192e9297d diff --git a/src/game/config.cpp b/src/game/config.cpp index ac8ebff..ab24e0a 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -19,7 +19,6 @@ constexpr std::u8string_view general_filename = u8"general.json"; constexpr std::u8string_view graphics_filename = u8"graphics.json"; constexpr std::u8string_view controls_filename = u8"controls.json"; constexpr std::u8string_view sound_filename = u8"sound.json"; -constexpr std::u8string_view program_id = u8"Zelda64Recompiled"; constexpr auto res_default = ultramodern::renderer::Resolution::Auto; constexpr auto hr_default = ultramodern::renderer::HUDRatioMode::Clamp16x9; diff --git a/src/main/main.cpp b/src/main/main.cpp index 6007ffa..cbeb63a 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -528,6 +528,16 @@ 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); +} + int main(int argc, char** argv) { recomp::Version project_version{}; if (!recomp::Version::from_string(version_string, project_version)) { @@ -629,6 +639,18 @@ 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"); @@ -641,6 +663,7 @@ int main(int argc, char** argv) { 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()); diff --git a/src/main/rt64_render_context.cpp b/src/main/rt64_render_context.cpp index 497e504..8d5c735 100644 --- a/src/main/rt64_render_context.cpp +++ b/src/main/rt64_render_context.cpp @@ -1,5 +1,6 @@ #include #include +#include #define HLSL_CPU #include "hle/rt64_application.h" @@ -10,6 +11,13 @@ #include "zelda_render.h" #include "recomp_ui.h" +#include "concurrentqueue.h" + +// Helper class for variant visiting. +template +struct overloaded : Ts... { using Ts::operator()...; }; +template +overloaded(Ts...) -> overloaded; static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None; static bool sample_positions_supported = false; @@ -18,6 +26,18 @@ static bool high_precision_fb_enabled = false; static uint8_t DMEM[0x1000]; static uint8_t IMEM[0x1000]; +struct TexturePackEnableAction { + std::filesystem::path path; +}; + +struct TexturePackDisableAction { + std::filesystem::path path; +}; + +using TexturePackAction = std::variant; + +static moodycamel::ConcurrentQueue texture_pack_action_queue; + unsigned int MI_INTR_REG = 0; unsigned int DPC_START_REG = 0; @@ -286,6 +306,32 @@ zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::rendere zelda64::renderer::RT64Context::~RT64Context() = default; void zelda64::renderer::RT64Context::send_dl(const OSTask* task) { + bool packs_disabled = false; + TexturePackAction cur_action; + while (texture_pack_action_queue.try_dequeue(cur_action)) { + std::visit(overloaded{ + [&](TexturePackDisableAction& to_disable) { + enabled_texture_packs.erase(to_disable.path); + packs_disabled = true; + }, + [&](TexturePackEnableAction& to_enable) { + enabled_texture_packs.insert(to_enable.path); + // Load the pack now if no packs have been disabled. + if (!packs_disabled) { + app->textureCache->loadReplacementDirectory(to_enable.path); + } + } + }, cur_action); + } + + // If any packs were disabled, unload all packs and load all the active ones. + if (packs_disabled) { + app->textureCache->clearReplacementDirectories(); + for (const std::filesystem::path& cur_pack_path : enabled_texture_packs) { + app->textureCache->loadReplacementDirectory(cur_pack_path); + } + } + app->state->rsp->reset(); app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true); app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true); @@ -376,3 +422,11 @@ bool zelda64::renderer::RT64SamplePositionsSupported() { bool zelda64::renderer::RT64HighPrecisionFBEnabled() { return high_precision_fb_enabled; } + +void zelda64::renderer::enable_texture_pack(const recomp::mods::ModHandle& mod) { + texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_root_path}); +} + +void zelda64::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) { + texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_root_path}); +} From 24c436a572be8934d44e66362a977783302b5f04 Mon Sep 17 00:00:00 2001 From: LittleCube Date: Fri, 4 Oct 2024 00:01:23 -0400 Subject: [PATCH 17/29] add exports and events for moon crash save and owl save (#487) * add exports and events for moon crash save and owl save * fix recomp_on_owl_save event, add exports and callbacks for save loading * add more flexible owl events * add init event * fix init event to not be terrible * rename a couple events * use deletes instead of resets * use better names, add better annotations * use full signature for event annotations --- patches/autosaving.c | 104 ++++++++++++++++++++++++++++++------- patches/play_patches.c | 12 ++++- patches/required_patches.c | 5 ++ 3 files changed, 100 insertions(+), 21 deletions(-) diff --git a/patches/autosaving.c b/patches/autosaving.c index 809c98d..bcf5c3c 100644 --- a/patches/autosaving.c +++ b/patches/autosaving.c @@ -99,13 +99,22 @@ RECOMP_EXPORT void recomp_do_autosave(PlayState* play) { gSaveContext.save.isOwlSave = false; } -// @recomp Do not clear the save if the save was an autosave. +bool loading_deletes_owl_save = true; + +// @recomp_export void recomp_set_loading_deletes_owl_save(bool new_val): Set whether loading an owl save should also delete it. +RECOMP_EXPORT void recomp_set_loading_deletes_owl_save(bool new_val) +{ + loading_deletes_owl_save = new_val; +} + +// @recomp Do not clear the save if the save was an autosave, or if mods have disabled save deletion. RECOMP_PATCH void func_80147314(SramContext* sramCtx, s32 fileNum) { s32 save_type = gSaveContext.save.isOwlSave; gSaveContext.save.isOwlSave = false; - // @recomp Prevent owl save/autosave deletion if autosaving is enabled. - if (!recomp_autosave_enabled()) { + // @recomp Prevent owl save/autosave deletion if autosaving is enabled, and... + // @recomp_use_export_var loading_deletes_owl_save: Prevent owl save deletion if mods disable it. + if (!recomp_autosave_enabled() && loading_deletes_owl_save) { gSaveContext.save.saveInfo.playerData.newf[0] = '\0'; gSaveContext.save.saveInfo.playerData.newf[1] = '\0'; gSaveContext.save.saveInfo.playerData.newf[2] = '\0'; @@ -522,6 +531,9 @@ s32 spawn_entrance_from_autosave_entrance(s16 autosave_entrance) { } } +RECOMP_DECLARE_EVENT(recomp_on_load_save(FileSelectState* fileSelect, SramContext* sramCtx)); +RECOMP_DECLARE_EVENT(recomp_after_load_save(FileSelectState* fileSelect, SramContext* sramCtx)); + // @recomp Patched to change the entrance for autosaves and initialize autosaves. RECOMP_PATCH void Sram_OpenSave(FileSelectState* fileSelect, SramContext* sramCtx) { s32 i; @@ -530,6 +542,9 @@ RECOMP_PATCH void Sram_OpenSave(FileSelectState* fileSelect, SramContext* sramCt s32 pad1; s32 fileNum; + // @recomp_event recomp_on_load_save(FileSelectState* fileSelect, SramContext* sramCtx): A save-file was just chosen. + recomp_on_load_save(fileSelect, sramCtx); + if (gSaveContext.flashSaveAvailable) { bzero(sramCtx->saveBuf, SAVE_BUFFER_SIZE); @@ -653,29 +668,49 @@ RECOMP_PATCH void Sram_OpenSave(FileSelectState* fileSelect, SramContext* sramCt // @recomp Initialize the autosave state tracking. autosave_init(); + + // @recomp_event recomp_after_load_save(FileSelectState* fileSelect, SramContext* sramCtx): The save has finished loading. + recomp_after_load_save(fileSelect, sramCtx); +} + +bool moon_crash_resets_save = true; + +// @recomp_export void recomp_set_moon_crash_resets_save(bool new_val): Set whether a moon crash should revert the player's save data. +RECOMP_EXPORT void recomp_set_moon_crash_resets_save(bool new_val) +{ + moon_crash_resets_save = new_val; } extern s32 Actor_ProcessTalkRequest(Actor* actor, GameState* gameState); +RECOMP_DECLARE_EVENT(recomp_on_moon_crash(SramContext* sramCtx)); +RECOMP_DECLARE_EVENT(recomp_after_moon_crash(SramContext* sramCtx)); + // @recomp Reset the autosave timer when the moon crashes. RECOMP_PATCH void Sram_ResetSaveFromMoonCrash(SramContext* sramCtx) { s32 i; s32 cutsceneIndex = gSaveContext.save.cutsceneIndex; - bzero(sramCtx->saveBuf, SAVE_BUFFER_SIZE); + // @recomp_event recomp_on_moon_crash(SramContext* sramCtx): A moon crash has just been triggered. + recomp_on_moon_crash(sramCtx); - if (SysFlashrom_ReadData(sramCtx->saveBuf, gFlashSaveStartPages[gSaveContext.fileNum * 2], - gFlashSaveNumPages[gSaveContext.fileNum * 2]) != 0) { - SysFlashrom_ReadData(sramCtx->saveBuf, gFlashSaveStartPages[gSaveContext.fileNum * 2 + 1], - gFlashSaveNumPages[gSaveContext.fileNum * 2 + 1]); + if (moon_crash_resets_save) + { + bzero(sramCtx->saveBuf, SAVE_BUFFER_SIZE); + + if (SysFlashrom_ReadData(sramCtx->saveBuf, gFlashSaveStartPages[gSaveContext.fileNum * 2], + gFlashSaveNumPages[gSaveContext.fileNum * 2]) != 0) { + SysFlashrom_ReadData(sramCtx->saveBuf, gFlashSaveStartPages[gSaveContext.fileNum * 2 + 1], + gFlashSaveNumPages[gSaveContext.fileNum * 2 + 1]); + } + Lib_MemCpy(&gSaveContext.save, sramCtx->saveBuf, sizeof(Save)); + if (CHECK_NEWF(gSaveContext.save.saveInfo.playerData.newf)) { + SysFlashrom_ReadData(sramCtx->saveBuf, gFlashSaveStartPages[gSaveContext.fileNum * 2 + 1], + gFlashSaveNumPages[gSaveContext.fileNum * 2 + 1]); + Lib_MemCpy(&gSaveContext, sramCtx->saveBuf, sizeof(Save)); + } + gSaveContext.save.cutsceneIndex = cutsceneIndex; } - Lib_MemCpy(&gSaveContext.save, sramCtx->saveBuf, sizeof(Save)); - if (CHECK_NEWF(gSaveContext.save.saveInfo.playerData.newf)) { - SysFlashrom_ReadData(sramCtx->saveBuf, gFlashSaveStartPages[gSaveContext.fileNum * 2 + 1], - gFlashSaveNumPages[gSaveContext.fileNum * 2 + 1]); - Lib_MemCpy(&gSaveContext, sramCtx->saveBuf, sizeof(Save)); - } - gSaveContext.save.cutsceneIndex = cutsceneIndex; for (i = 0; i < ARRAY_COUNT(gSaveContext.eventInf); i++) { gSaveContext.eventInf[i] = 0; @@ -706,24 +741,55 @@ RECOMP_PATCH void Sram_ResetSaveFromMoonCrash(SramContext* sramCtx) { // @recomp Use the slow autosave timer to give the player extra time to respond to the moon crashing to decide if they want to reload their autosave. recomp_reset_autosave_timer_slow(); + + // @recomp_event recomp_after_moon_crash(SramContext* sramCtx): The effects of moon crash have been written. + recomp_after_moon_crash(sramCtx); } -// @recomp If autosave is enabled, skip the part of the owl statue dialog that talks about the file being deleted on load, since it's not true. +bool owls_save_and_quit = true; + +// @recomp_export void recomp_set_owls_save_and_quit(bool new_val): Set if owls should use their code to save and quit. If false is passed, owl saves now do nothing. +RECOMP_EXPORT void recomp_set_owls_save_and_quit(bool new_val) +{ + owls_save_and_quit = new_val; +} + +RECOMP_DECLARE_EVENT(recomp_on_owl_update(ObjWarpstone* this, PlayState* play)); +RECOMP_DECLARE_EVENT(recomp_on_owl_save(ObjWarpstone* this, PlayState* play)); +RECOMP_DECLARE_EVENT(recomp_after_owl_save(ObjWarpstone* this, PlayState* play)); + +// @recomp If autosave is enabled or owl save deletion is disabled, skip the part of the owl statue dialog that talks about the file being deleted on load, since it's not true. RECOMP_PATCH void ObjWarpstone_Update(Actor* thisx, PlayState* play) { ObjWarpstone* this = (ObjWarpstone*)thisx; s32 pad; + // @recomp_event recomp_on_owl_update(ObjWarpstone* this, PlayState* play): Allow mods to handle owl update frames. + recomp_on_owl_update(this, play); + if (this->isTalking) { if (Actor_TextboxIsClosing(&this->dyna.actor, play)) { this->isTalking = false; } else if ((Message_GetState(&play->msgCtx) == TEXT_STATE_CHOICE) && Message_ShouldAdvance(play)) { if (play->msgCtx.choiceIndex != 0) { + // @recomp_event recomp_on_owl_save(ObjWarpstone* this, PlayState* play): The player chose to save from an owl statue. + recomp_on_owl_save(this, play); + Audio_PlaySfx_MessageDecide(); - play->msgCtx.msgMode = MSGMODE_OWL_SAVE_0; + + // @recomp_use_export_var owls_save_and_quit: Only use normal owl save if quit flag is set. + if (owls_save_and_quit) { + play->msgCtx.msgMode = MSGMODE_OWL_SAVE_0; + } else { + Message_CloseTextbox(play); + } + play->msgCtx.unk120D6 = 0; play->msgCtx.unk120D4 = 0; gSaveContext.save.owlWarpId = OBJ_WARPSTONE_GET_OWL_WARP_ID(&this->dyna.actor); + + // @recomp_event recomp_after_owl_save(ObjWarpstone* this, PlayState* play): Owl save is finished. + recomp_after_owl_save(this, play); } else { Message_CloseTextbox(play); } @@ -734,8 +800,8 @@ RECOMP_PATCH void ObjWarpstone_Update(Actor* thisx, PlayState* play) { Actor_OfferTalkNearColChkInfoCylinder(&this->dyna.actor, play); } - // @recomp Skip the text talking about the save being deleted on load, if autosave is enabled. - if (recomp_autosave_enabled()) { + // @recomp_use_export_var loading_deletes_owl_save: Skip the text talking about the save being deleted on load, if autosave is enabled or if owl save deletion is disabled. + if (recomp_autosave_enabled() || !loading_deletes_owl_save) { if (this->isTalking && play->msgCtx.currentTextId == 0xC01 && play->msgCtx.msgBufPos == 269) { play->msgCtx.msgBufPos = 530; } diff --git a/patches/play_patches.c b/patches/play_patches.c index eedda5d..db5deeb 100644 --- a/patches/play_patches.c +++ b/patches/play_patches.c @@ -5,7 +5,7 @@ extern Input D_801F6C18; RECOMP_DECLARE_EVENT(recomp_on_play_main(PlayState* play)); -RECOMP_DECLARE_EVENT(recomp_before_play_update(PlayState* play)); +RECOMP_DECLARE_EVENT(recomp_on_play_update(PlayState* play)); RECOMP_DECLARE_EVENT(recomp_after_play_update(PlayState* play)); void controls_play_update(PlayState* play) { @@ -16,6 +16,8 @@ void controls_play_update(PlayState* play) { RECOMP_PATCH void Play_Main(GameState* thisx) { static Input* prevInput = NULL; PlayState* this = (PlayState*)thisx; + + // @recomp_event recomp_on_play_main(PlayState* play): Allow mods to execute code every frame. recomp_on_play_main(this); // @recomp @@ -37,9 +39,15 @@ RECOMP_PATCH void Play_Main(GameState* thisx) { this->state.gfxCtx = NULL; } camera_pre_play_update(this); - recomp_before_play_update(this); + + // @recomp_event recomp_on_play_update(PlayState* play): Play_Update is about to be called. + recomp_on_play_update(this); + Play_Update(this); + + // @recomp_event recomp_after_play_update(PlayState* play): Play_Update was called. recomp_after_play_update(this); + camera_post_play_update(this); analog_cam_post_play_update(this); autosave_post_play_update(this); diff --git a/patches/required_patches.c b/patches/required_patches.c index 721a41a..7aada58 100644 --- a/patches/required_patches.c +++ b/patches/required_patches.c @@ -7,6 +7,8 @@ void Main_InitMemory(void); void Main_InitScreen(void); +RECOMP_DECLARE_EVENT(recomp_on_init()); + // @recomp Patched to load the code segment in the recomp runtime. RECOMP_PATCH void Main_Init(void) { DmaRequest dmaReq; @@ -14,6 +16,9 @@ RECOMP_PATCH void Main_Init(void) { OSMesg msg[1]; size_t prevSize; + // @recomp_event recomp_on_init(): Allow mods to initialize themselves once. + recomp_on_init(); + osCreateMesgQueue(&mq, msg, ARRAY_COUNT(msg)); prevSize = gDmaMgrDmaBuffSize; From cd12bc0c2053c1235bd0aa9fdf84ac67e4f6e9cc Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 7 Oct 2024 01:35:56 -0400 Subject: [PATCH 18/29] Disable identical code folding to prevent mods from conflicting with themselves due to merged functions --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d4ef3ef..0b900bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,6 +196,11 @@ else() ) endif() +if (MSVC) + # Disable identical code folding, since this breaks mod function patching as multiple functions can get merged into one. + target_link_options(Zelda64Recompiled PRIVATE /OPT:NOICF) +endif() + if (WIN32) include(FetchContent) From aeefc1b6bb4470df88ee4e8f160b8af582b9545b Mon Sep 17 00:00:00 2001 From: David Chavez Date: Tue, 8 Oct 2024 05:27:13 +0200 Subject: [PATCH 19/29] chore(ci): Bundle Windows PDBs (#492) * update runners * Save PDB * Build RelWithDebInfo * Update copy on runners * Archive PDB separately * Update copy * Update PDB archive name --- .github/workflows/validate.yml | 13 ++++++++++--- README.md | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 442d031..b9f7c96 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -22,7 +22,7 @@ concurrency: cancel-in-progress: true jobs: build-linux: - runs-on: ${{ matrix.os }} + runs-on: ${{ format('blaze/compute/{0}-amd64', matrix.os) }} container: image: dcvz/n64recomp:ubuntu-18.04 volumes: @@ -108,7 +108,7 @@ jobs: name: Zelda64Recompiled-AppImage-X64-${{ matrix.type }} path: Zelda64Recompiled-*.AppImage build-linux-arm64: - runs-on: ${{ format('blaze/{0}', matrix.os) }} + runs-on: ${{ format('blaze/compute/{0}', matrix.os) }} strategy: matrix: type: [ Debug, Release ] @@ -199,7 +199,7 @@ jobs: runs-on: windows-latest strategy: matrix: - type: [ Debug, Release ] + type: [ Debug, RelWithDebInfo ] name: windows (${{ matrix.type }}) steps: - name: Checkout @@ -264,6 +264,7 @@ jobs: Move-Item -Path "cmake-build/dxcompiler.dll" -Destination "dxcompiler.dll" Move-Item -Path "cmake-build/dxil.dll" -Destination "dxil.dll" Move-Item -Path "cmake-build/SDL2.dll" -Destination "SDL2.dll" + Move-Item -Path "cmake-build/Zelda64Recompiled.pdb" -Destination "Zelda64Recompiled.pdb" Remove-Item -Path "assets/scss" -Recurse -Force - name: Archive Zelda64Recomp uses: actions/upload-artifact@v4 @@ -276,3 +277,9 @@ jobs: SDL2.dll assets/ gamecontrollerdb.txt + - name: Archive Debug Files + uses: actions/upload-artifact@v4 + with: + name: Zelda64Recompiled-PDB-${{ matrix.type }} + path: | + Zelda64Recompiled.pdb diff --git a/README.md b/README.md index d840f9e..95f9aa3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Zelda 64: Recompiled is a project that uses [N64: Recompiled](https://github.com
-_Thank you [Blaze](https://runblaze.dev) for supporting this project by providing Linux ARM64 and Apple Silicon macOS Github Action Runners!_ +_Special thanks to [Blaze](https://runblaze.dev) for their support of this project. They provide high-performance Linux (AMD64 & ARM64) and Apple Silicon macOS runners for GitHub Actions, greatly reducing our automated build times._ From fdbdf5edb07f4150ac2a127a11a6d0d4ea8678b8 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Fri, 11 Oct 2024 21:15:11 -0400 Subject: [PATCH 20/29] Add custom symbols toml and made non-relocatable versions of original gamestate functions, added base patch for Play_Init --- patches.toml | 2 +- patches/custom_syms.toml | 16 ++ patches/options.c | 5 +- patches/play_patches.c | 326 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 patches/custom_syms.toml diff --git a/patches.toml b/patches.toml index 38910ee..18a8856 100644 --- a/patches.toml +++ b/patches.toml @@ -9,7 +9,7 @@ single_file_output = true use_absolute_symbols = true # Point the recompiler at the symbol files so that it can resolve relocations during recompilation. func_reference_syms_file = "Zelda64RecompSyms/mm.us.rev1.syms.toml" -data_reference_syms_files = [ "Zelda64RecompSyms/mm.us.rev1.datasyms.toml", "Zelda64RecompSyms/mm.us.rev1.datasyms_static.toml" ] +data_reference_syms_files = [ "Zelda64RecompSyms/mm.us.rev1.datasyms.toml", "Zelda64RecompSyms/mm.us.rev1.datasyms_static.toml", "patches/custom_syms.toml" ] # Tell the recompiler to write the output binary. Doing this instead of using objcopy allows the recompiler to patch MIPS32 relocs. output_binary_path = "patches/patches.bin" # Do not emit warnings for unpaired LO16 values, as clang produces many of them. diff --git a/patches/custom_syms.toml b/patches/custom_syms.toml new file mode 100644 index 0000000..9128278 --- /dev/null +++ b/patches/custom_syms.toml @@ -0,0 +1,16 @@ +# Custom symbols used for patches. +[[section]] +name = "ABSOLUTE_SYMS" +vram = 0x00000000 +size = 0x0 + +symbols = [ + # Alternate references to symbols that would otherwise be relocated + { name = "Setup_Init_NORELOCATE", vram = 0x80173338 }, # Not relocatable, but here for uniformity with the other gamestate funcs + { name = "MapSelect_Init_NORELOCATE", vram = 0x80801B4C }, + { name = "ConsoleLogo_Init_NORELOCATE", vram = 0x8080074C }, + { name = "Play_Init_NORELOCATE", vram = 0x8016A2C8 }, + { name = "TitleSetup_Init_NORELOCATE", vram = 0x80803F30 }, + { name = "FileSelect_Init_NORELOCATE", vram = 0x80813C98 }, + { name = "DayTelop_Init_NORELOCATE", vram = 0x80815820 }, +] diff --git a/patches/options.c b/patches/options.c index c26133c..07c0a23 100644 --- a/patches/options.c +++ b/patches/options.c @@ -1036,6 +1036,9 @@ RECOMP_PATCH void FileSelect_ConfirmFile(GameState *thisx) { } } +// Non-relocatable reference to the original address of Play_Init. +void Play_Init_NORELOCATE(GameState*); + /** * Load the save for the appropriate file and start the game. * Update function for `SM_LOAD_GAME` @@ -1060,7 +1063,7 @@ RECOMP_PATCH void FileSelect_LoadGame(GameState* thisx) { gSaveContext.gameMode = GAMEMODE_NORMAL; STOP_GAMESTATE(&this->state); - SET_NEXT_GAMESTATE(&this->state, Play_Init, sizeof(PlayState)); + SET_NEXT_GAMESTATE(&this->state, Play_Init_NORELOCATE, sizeof(PlayState)); gSaveContext.respawnFlag = 0; gSaveContext.respawn[RESPAWN_MODE_DOWN].entrance = ENTR_LOAD_OPENING; diff --git a/patches/play_patches.c b/patches/play_patches.c index db5deeb..712b612 100644 --- a/patches/play_patches.c +++ b/patches/play_patches.c @@ -1,6 +1,30 @@ #include "play_patches.h" #include "z64debug_display.h" #include "input.h" +#include "prevent_bss_reordering.h" +#include "z64.h" +#include "regs.h" +#include "functions.h" +#include "z64vismono.h" +#include "z64visfbuf.h" +#include "buffers.h" + +#include "variables.h" +#include "macros.h" +#include "buffers.h" +#include "idle.h" +#include "sys_cfb.h" +#include "z64bombers_notebook.h" +#include "z64quake.h" +#include "z64rumble.h" +#include "z64shrink_window.h" +#include "z64view.h" + +#include "overlays/gamestates/ovl_daytelop/z_daytelop.h" +#include "overlays/gamestates/ovl_opening/z_opening.h" +#include "overlays/gamestates/ovl_file_choose/z_file_select.h" +#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" +#include "debug.h" extern Input D_801F6C18; @@ -98,3 +122,305 @@ RECOMP_PATCH s32 Room_HandleLoadCallbacks(PlayState* play, RoomContext* roomCtx) return 1; } + +void ZeldaArena_Init(void* start, size_t size); + +void Play_SpawnScene(PlayState* this, s32 sceneId, s32 spawn); +void Play_InitMotionBlur(void); + +extern s16 sTransitionFillTimer; +extern Input D_801F6C18; +extern TransitionTile sTransitionTile; +extern s32 gTransitionTileState; +extern VisMono sPlayVisMono; +extern Color_RGBA8_u32 gVisMonoColor; +extern VisFbuf sPlayVisFbuf; +extern VisFbuf* sPlayVisFbufInstance; +extern BombersNotebook sBombersNotebook; +extern u8 sBombersNotebookOpen; +extern u8 sMotionBlurStatus; + +extern s32 gDbgCamEnabled; +extern u8 D_801D0D54; + +extern u8 gPictoPhotoI8[]; +extern u8 D_80784600[]; + +// Non-relocatable references to the original addresses of these game state functions. +void DayTelop_Init_NORELOCATE(GameState*); +void TitleSetup_Init_NORELOCATE(GameState*); + +RECOMP_PATCH void Play_Init(GameState* thisx) { + PlayState* this = (PlayState*)thisx; + GraphicsContext* gfxCtx = this->state.gfxCtx; + s32 pad; + uintptr_t zAlloc; + s32 zAllocSize; + Player* player; + s32 i; + s32 spawn; + u8 sceneLayer; + s32 scene; + + if ((gSaveContext.respawnFlag == -4) || (gSaveContext.respawnFlag == -0x63)) { + if (CHECK_EVENTINF(EVENTINF_TRIGGER_DAYTELOP)) { + CLEAR_EVENTINF(EVENTINF_TRIGGER_DAYTELOP); + STOP_GAMESTATE(&this->state); + // Use non-relocatable reference to DayTelop_Init instead. + SET_NEXT_GAMESTATE(&this->state, DayTelop_Init_NORELOCATE, sizeof(DayTelopState)); + return; + } + + gSaveContext.unk_3CA7 = 1; + if (gSaveContext.respawnFlag == -0x63) { + gSaveContext.respawnFlag = 2; + } + } else { + gSaveContext.unk_3CA7 = 0; + } + + if (gSaveContext.save.entrance == -1) { + gSaveContext.save.entrance = 0; + STOP_GAMESTATE(&this->state); + // Use non-relocatable reference to TitleSetup_Init instead. + SET_NEXT_GAMESTATE(&this->state, TitleSetup_Init_NORELOCATE, sizeof(TitleSetupState)); + return; + } + + if ((gSaveContext.nextCutsceneIndex == 0xFFEF) || (gSaveContext.nextCutsceneIndex == 0xFFF0)) { + scene = ((void)0, gSaveContext.save.entrance) >> 9; + spawn = (((void)0, gSaveContext.save.entrance) >> 4) & 0x1F; + + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_CLEARED_SNOWHEAD_TEMPLE)) { + if (scene == ENTR_SCENE_MOUNTAIN_VILLAGE_WINTER) { + scene = ENTR_SCENE_MOUNTAIN_VILLAGE_SPRING; + } else if (scene == ENTR_SCENE_GORON_VILLAGE_WINTER) { + scene = ENTR_SCENE_GORON_VILLAGE_SPRING; + } else if (scene == ENTR_SCENE_PATH_TO_GORON_VILLAGE_WINTER) { + scene = ENTR_SCENE_PATH_TO_GORON_VILLAGE_SPRING; + } else if ((scene == ENTR_SCENE_SNOWHEAD) || (scene == ENTR_SCENE_PATH_TO_SNOWHEAD) || + (scene == ENTR_SCENE_PATH_TO_MOUNTAIN_VILLAGE) || (scene == ENTR_SCENE_GORON_SHRINE) || + (scene == ENTR_SCENE_GORON_RACETRACK)) { + gSaveContext.nextCutsceneIndex = 0xFFF0; + } + } + + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_CLEARED_WOODFALL_TEMPLE)) { + if (scene == ENTR_SCENE_SOUTHERN_SWAMP_POISONED) { + scene = ENTR_SCENE_SOUTHERN_SWAMP_CLEARED; + } else if (scene == ENTR_SCENE_WOODFALL) { + gSaveContext.nextCutsceneIndex = 0xFFF1; + } + } + + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_CLEARED_STONE_TOWER_TEMPLE) && (scene == ENTR_SCENE_IKANA_CANYON)) { + gSaveContext.nextCutsceneIndex = 0xFFF2; + } + + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_CLEARED_GREAT_BAY_TEMPLE) && + ((scene == ENTR_SCENE_GREAT_BAY_COAST) || (scene == ENTR_SCENE_ZORA_CAPE))) { + gSaveContext.nextCutsceneIndex = 0xFFF0; + } + + // "First cycle" Termina Field + if (INV_CONTENT(ITEM_OCARINA_OF_TIME) != ITEM_OCARINA_OF_TIME) { + if ((scene == ENTR_SCENE_TERMINA_FIELD) && + (((void)0, gSaveContext.save.entrance) != ENTRANCE(TERMINA_FIELD, 10))) { + gSaveContext.nextCutsceneIndex = 0xFFF4; + } + } + //! FAKE: + gSaveContext.save.entrance = + Entrance_Create(((void)0, scene), spawn, ((void)0, gSaveContext.save.entrance) & 0xF); + } + + GameState_Realloc(&this->state, 0); + KaleidoManager_Init(this); + ShrinkWindow_Init(); + View_Init(&this->view, gfxCtx); + Audio_SetExtraFilter(0); + Quake_Init(); + Distortion_Init(this); + + for (i = 0; i < ARRAY_COUNT(this->cameraPtrs); i++) { + this->cameraPtrs[i] = NULL; + } + + Camera_Init(&this->mainCamera, &this->view, &this->colCtx, this); + Camera_ChangeStatus(&this->mainCamera, CAM_STATUS_ACTIVE); + + for (i = 0; i < ARRAY_COUNT(this->subCameras); i++) { + Camera_Init(&this->subCameras[i], &this->view, &this->colCtx, this); + Camera_ChangeStatus(&this->subCameras[i], CAM_STATUS_INACTIVE); + } + + this->cameraPtrs[CAM_ID_MAIN] = &this->mainCamera; + this->cameraPtrs[CAM_ID_MAIN]->uid = CAM_ID_MAIN; + this->activeCamId = CAM_ID_MAIN; + + Camera_OverwriteStateFlags(&this->mainCamera, CAM_STATE_0 | CAM_STATE_CHECK_WATER | CAM_STATE_2 | CAM_STATE_3 | + CAM_STATE_4 | CAM_STATE_DISABLE_MODE_CHANGE | CAM_STATE_6); + Sram_Alloc(&this->state, &this->sramCtx); + Regs_InitData(this); + Message_Init(this); + GameOver_Init(this); + SoundSource_InitAll(this); + EffFootmark_Init(this); + Effect_Init(this); + EffectSS_Init(this, 100); + CollisionCheck_InitContext(this, &this->colChkCtx); + AnimationContext_Reset(&this->animationCtx); + Cutscene_InitContext(this, &this->csCtx); + + if (gSaveContext.nextCutsceneIndex != 0xFFEF) { + gSaveContext.save.cutsceneIndex = gSaveContext.nextCutsceneIndex; + gSaveContext.nextCutsceneIndex = 0xFFEF; + } + + if (gSaveContext.save.cutsceneIndex == 0xFFFD) { + gSaveContext.save.cutsceneIndex = 0; + } + + if (gSaveContext.nextDayTime != NEXT_TIME_NONE) { + gSaveContext.save.time = gSaveContext.nextDayTime; + gSaveContext.skyboxTime = gSaveContext.nextDayTime; + } + + if ((CURRENT_TIME >= CLOCK_TIME(18, 0)) || (CURRENT_TIME < CLOCK_TIME(6, 30))) { + gSaveContext.save.isNight = true; + } else { + gSaveContext.save.isNight = false; + } + + func_800EDDB0(this); + + if (((gSaveContext.gameMode != GAMEMODE_NORMAL) && (gSaveContext.gameMode != GAMEMODE_TITLE_SCREEN)) || + (gSaveContext.save.cutsceneIndex >= 0xFFF0)) { + gSaveContext.nayrusLoveTimer = 0; + Magic_Reset(this); + gSaveContext.sceneLayer = (gSaveContext.save.cutsceneIndex & 0xF) + 1; + + // Set saved cutscene to 0 so it doesn't immediately play, but instead let the `CutsceneManager` handle it. + gSaveContext.save.cutsceneIndex = 0; + } else { + gSaveContext.sceneLayer = 0; + } + + sceneLayer = gSaveContext.sceneLayer; + + Play_SpawnScene( + this, Entrance_GetSceneIdAbsolute(((void)0, gSaveContext.save.entrance) + ((void)0, gSaveContext.sceneLayer)), + Entrance_GetSpawnNum(((void)0, gSaveContext.save.entrance) + ((void)0, gSaveContext.sceneLayer))); + KaleidoScopeCall_Init(this); + Interface_Init(this); + + if (gSaveContext.nextDayTime != NEXT_TIME_NONE) { + if (gSaveContext.nextDayTime == NEXT_TIME_DAY) { + gSaveContext.save.day++; + gSaveContext.save.eventDayCount++; + gSaveContext.dogIsLost = true; + gSaveContext.nextDayTime = NEXT_TIME_DAY_SET; + } else { + gSaveContext.nextDayTime = NEXT_TIME_NIGHT_SET; + } + } + + Play_InitMotionBlur(); + + R_PAUSE_BG_PRERENDER_STATE = PAUSE_BG_PRERENDER_OFF; + R_PICTO_PHOTO_STATE = PICTO_PHOTO_STATE_OFF; + + PreRender_Init(&this->pauseBgPreRender); + PreRender_SetValuesSave(&this->pauseBgPreRender, gCfbWidth, gCfbHeight, NULL, NULL, NULL); + PreRender_SetValues(&this->pauseBgPreRender, gCfbWidth, gCfbHeight, NULL, NULL); + + this->unk_18E64 = gWorkBuffer; + this->pictoPhotoI8 = gPictoPhotoI8; + this->unk_18E68 = D_80784600; + this->unk_18E58 = D_80784600; + this->unk_18E60 = D_80784600; + gTransitionTileState = TRANS_TILE_OFF; + this->transitionMode = TRANS_MODE_OFF; + D_801D0D54 = false; + + FrameAdvance_Init(&this->frameAdvCtx); + Rand_Seed(osGetTime()); + Matrix_Init(&this->state); + + this->state.main = Play_Main; + this->state.destroy = Play_Destroy; + + this->transitionTrigger = TRANS_TRIGGER_END; + this->worldCoverAlpha = 0; + this->bgCoverAlpha = 0; + this->haltAllActors = false; + this->unk_18844 = false; + + if (gSaveContext.gameMode != GAMEMODE_TITLE_SCREEN) { + if (gSaveContext.nextTransitionType == TRANS_NEXT_TYPE_DEFAULT) { + this->transitionType = + (Entrance_GetTransitionFlags(((void)0, gSaveContext.save.entrance) + sceneLayer) >> 7) & 0x7F; + } else { + this->transitionType = gSaveContext.nextTransitionType; + gSaveContext.nextTransitionType = TRANS_NEXT_TYPE_DEFAULT; + } + } else { + this->transitionType = TRANS_TYPE_FADE_BLACK; + } + + TransitionFade_Init(&this->unk_18E48); + TransitionFade_SetType(&this->unk_18E48, 3); + TransitionFade_SetColor(&this->unk_18E48, RGBA8(160, 160, 160, 255)); + TransitionFade_Start(&this->unk_18E48); + VisMono_Init(&sPlayVisMono); + + gVisMonoColor.a = 0; + sPlayVisFbufInstance = &sPlayVisFbuf; + VisFbuf_Init(sPlayVisFbufInstance); + sPlayVisFbufInstance->lodProportion = 0.0f; + sPlayVisFbufInstance->mode = VIS_FBUF_MODE_GENERAL; + sPlayVisFbufInstance->primColor.r = 0; + sPlayVisFbufInstance->primColor.g = 0; + sPlayVisFbufInstance->primColor.b = 0; + sPlayVisFbufInstance->primColor.a = 0; + sPlayVisFbufInstance->envColor.r = 0; + sPlayVisFbufInstance->envColor.g = 0; + sPlayVisFbufInstance->envColor.b = 0; + sPlayVisFbufInstance->envColor.a = 0; + CutsceneFlags_UnsetAll(this); + THA_GetRemaining(&this->state.tha); + zAllocSize = THA_GetRemaining(&this->state.tha); + zAlloc = (uintptr_t)THA_AllocTailAlign16(&this->state.tha, zAllocSize); + + //! @bug: Incorrect ALIGN16s + ZeldaArena_Init((void*)((zAlloc + 8) & ~0xF), (zAllocSize - ((zAlloc + 8) & ~0xF)) + zAlloc); + + Actor_InitContext(this, &this->actorCtx, this->linkActorEntry); + + while (!Room_HandleLoadCallbacks(this, &this->roomCtx)) {} + + if ((CURRENT_DAY != 0) && ((this->roomCtx.curRoom.behaviorType1 == ROOM_BEHAVIOR_TYPE1_1) || + (this->roomCtx.curRoom.behaviorType1 == ROOM_BEHAVIOR_TYPE1_5))) { + Actor_Spawn(&this->actorCtx, this, ACTOR_EN_TEST4, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0); + } + + player = GET_PLAYER(this); + + Camera_InitFocalActorSettings(&this->mainCamera, &player->actor); + gDbgCamEnabled = false; + + if (PLAYER_GET_BG_CAM_INDEX(&player->actor) != 0xFF) { + Camera_ChangeActorCsCamIndex(&this->mainCamera, PLAYER_GET_BG_CAM_INDEX(&player->actor)); + } + + CutsceneManager_StoreCamera(&this->mainCamera); + Interface_SetSceneRestrictions(this); + Environment_PlaySceneSequence(this); + gSaveContext.seqId = this->sequenceCtx.seqId; + gSaveContext.ambienceId = this->sequenceCtx.ambienceId; + AnimationContext_Update(this, &this->animationCtx); + Cutscene_HandleEntranceTriggers(this); + gSaveContext.respawnFlag = 0; + sBombersNotebookOpen = false; + BombersNotebook_Init(&sBombersNotebook); +} From e5699e59a1cfb0f7af363123a483748a87f74efe Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sat, 12 Oct 2024 01:12:02 -0400 Subject: [PATCH 21/29] Update runtime to fix mod section alignment --- lib/N64ModernRuntime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index a4f5ee1..9e9ae17 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit a4f5ee10c8303474d9028468e84845b192e9297d +Subproject commit 9e9ae173ee2c378c6ff5f7bbfe452503a80212d9 From 8c8f5b889fcbc4fcdf91a93a373c43fffe802656 Mon Sep 17 00:00:00 2001 From: LittleCube Date: Sat, 12 Oct 2024 23:13:42 -0400 Subject: [PATCH 22/29] Add events and exports for Play_Init (#495) --- patches/play_patches.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/patches/play_patches.c b/patches/play_patches.c index 712b612..1d38632 100644 --- a/patches/play_patches.c +++ b/patches/play_patches.c @@ -150,6 +150,15 @@ extern u8 D_80784600[]; void DayTelop_Init_NORELOCATE(GameState*); void TitleSetup_Init_NORELOCATE(GameState*); +RECOMP_DECLARE_EVENT(recomp_on_play_init(PlayState* this)); + +bool allow_no_ocarina_tf = false; + +// @recomp_export void recomp_set_allow_no_ocarina(bool new_val): Set whether to force Termina Field to load normally even if Link has no ocarina. +RECOMP_EXPORT void recomp_set_allow_no_ocarina(bool new_val) { + allow_no_ocarina_tf = new_val; +} + RECOMP_PATCH void Play_Init(GameState* thisx) { PlayState* this = (PlayState*)thisx; GraphicsContext* gfxCtx = this->state.gfxCtx; @@ -162,6 +171,9 @@ RECOMP_PATCH void Play_Init(GameState* thisx) { u8 sceneLayer; s32 scene; + // @recomp_event recomp_on_play_init(PlayState* this): A new PlayState is being constructed. + recomp_on_play_init(this); + if ((gSaveContext.respawnFlag == -4) || (gSaveContext.respawnFlag == -0x63)) { if (CHECK_EVENTINF(EVENTINF_TRIGGER_DAYTELOP)) { CLEAR_EVENTINF(EVENTINF_TRIGGER_DAYTELOP); @@ -223,7 +235,8 @@ RECOMP_PATCH void Play_Init(GameState* thisx) { } // "First cycle" Termina Field - if (INV_CONTENT(ITEM_OCARINA_OF_TIME) != ITEM_OCARINA_OF_TIME) { + // @recomp_use_export_var allow_no_ocarina_tf: Skip loading into "First cycle" Termina Field if mods enable it. + if (!allow_no_ocarina_tf && INV_CONTENT(ITEM_OCARINA_OF_TIME) != ITEM_OCARINA_OF_TIME) { if ((scene == ENTR_SCENE_TERMINA_FIELD) && (((void)0, gSaveContext.save.entrance) != ENTRANCE(TERMINA_FIELD, 10))) { gSaveContext.nextCutsceneIndex = 0xFFF4; From 21ca074bf7489e615c382dd553b43ed075104a40 Mon Sep 17 00:00:00 2001 From: LittleCube Date: Sun, 13 Oct 2024 00:02:08 -0400 Subject: [PATCH 23/29] Add exports for fd anywhere and epona fix patches (#494) * add exports for fd anywhere and epona fix patches * use better logic for restoreHudVisibility * cleanup * fix a couple edge cases (being sent the bow while riding Epona) --- patches/input.c | 95 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/patches/input.c b/patches/input.c index 53f7eb3..f65d2cc 100644 --- a/patches/input.c +++ b/patches/input.c @@ -534,6 +534,13 @@ RECOMP_PATCH void Player_Action_86(Player *this, PlayState *play) { func_808550D0(play, this, this->unk_B10[4], this->unk_B10[5], (this->transformation == PLAYER_FORM_HUMAN) ? 0 : 1); } +bool no_bow_epona_fix = false; + +// @recomp_export void recomp_set_no_bow_epona_fix(bool new_val): Set whether to enable the fix for getting on Epona without a bow. +RECOMP_EXPORT void recomp_set_no_bow_epona_fix(bool new_val) { + no_bow_epona_fix = new_val; +} + extern s16 sPictoState; extern s16 sPictoPhotoBeingTaken; extern void* gWorkBuffer; @@ -603,24 +610,46 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; set_extra_item_slot_status(BTN_DISABLED); } else { - BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; + // @recomp_use_export_var no_bow_epona_fix: Part of the no bow Epona fix. + if (no_bow_epona_fix) { + if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_BOW) { + BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; + BUTTON_STATUS(EQUIP_SLOT_B) = BTN_ENABLED; + } + } else { + BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; + } if (play->unk_1887C >= 2) { Interface_LoadItemIconImpl(play, EQUIP_SLOT_B); } else if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_NONE) { - BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_NONE; + // @recomp_use_export_var no_bow_epona_fix: Part of the no bow Epona fix. + if (no_bow_epona_fix) { + gSaveContext.buttonStatus[EQUIP_SLOT_B] = BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B); + BUTTON_STATUS(EQUIP_SLOT_B) = BTN_DISABLED; + } else { + BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_NONE; + } } else { Interface_LoadItemIconImpl(play, EQUIP_SLOT_B); } - BUTTON_STATUS(EQUIP_SLOT_C_LEFT) = BTN_DISABLED; - BUTTON_STATUS(EQUIP_SLOT_C_DOWN) = BTN_DISABLED; - BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; - set_extra_item_slot_status(BTN_DISABLED); - Interface_SetHudVisibility(HUD_VISIBILITY_A_HEARTS_MAGIC_MINIMAP_WITH_OVERWRITE); + // @recomp_use_export_var no_bow_epona_fix: If the B button does not contain a sword, don't disable the UI. + if (!no_bow_epona_fix || BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) < ITEM_SWORD_KOKIRI || + BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) > ITEM_SWORD_GILDED) { + BUTTON_STATUS(EQUIP_SLOT_C_LEFT) = BTN_DISABLED; + BUTTON_STATUS(EQUIP_SLOT_C_DOWN) = BTN_DISABLED; + BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; + set_extra_item_slot_status(BTN_DISABLED); + Interface_SetHudVisibility(HUD_VISIBILITY_A_HEARTS_MAGIC_MINIMAP_WITH_OVERWRITE); + } } } + if (BUTTON_STATUS(EQUIP_SLOT_B) == BTN_DISABLED && BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) == ITEM_BOW) { + BUTTON_STATUS(EQUIP_SLOT_B) = BTN_ENABLED; + } + if (play->transitionMode != TRANS_MODE_OFF) { Interface_SetHudVisibility(HUD_VISIBILITY_NONE); } else if ((gSaveContext.minigameStatus == MINIGAME_STATUS_ACTIVE) && @@ -662,27 +691,51 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; set_extra_item_slot_status(BTN_DISABLED); } else { - BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; + // @recomp_use_export_var no_bow_epona_fix: Part of the no bow Epona fix. + if (no_bow_epona_fix) { + if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_BOW) { + BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; + BUTTON_STATUS(EQUIP_SLOT_B) = BTN_ENABLED; + } + } else { + BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; + } } if (play->unk_1887C >= 2) { Interface_LoadItemIconImpl(play, EQUIP_SLOT_B); } else if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_NONE) { - BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_NONE; + // @recomp_use_export_var no_bow_epona_fix: Part of the no bow Epona fix. + if (no_bow_epona_fix) { + gSaveContext.buttonStatus[EQUIP_SLOT_B] = BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B); + BUTTON_STATUS(EQUIP_SLOT_B) = BTN_DISABLED; + } else { + BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_NONE; + } } else { Interface_LoadItemIconImpl(play, EQUIP_SLOT_B); } if (BUTTON_STATUS(EQUIP_SLOT_B) == BTN_DISABLED) { - BUTTON_STATUS(EQUIP_SLOT_B) = BTN_ENABLED; - restoreHudVisibility = true; + // @recomp_use_export_var no_bow_epona_fix: Don't enable the B button unless it is being used for the bow. + if (!no_bow_epona_fix || BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) == ITEM_BOW) { + BUTTON_STATUS(EQUIP_SLOT_B) = BTN_ENABLED; + } + + // @recomp_use_export_var no_bow_epona_fix: Don't restore hud visibility from Epona without a sword. + if (!no_bow_epona_fix || (player->stateFlags1 & PLAYER_STATE1_800000) == 0) { + restoreHudVisibility = true; + } } - BUTTON_STATUS(EQUIP_SLOT_C_LEFT) = BTN_DISABLED; - BUTTON_STATUS(EQUIP_SLOT_C_DOWN) = BTN_DISABLED; - BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; - set_extra_item_slot_status(BTN_DISABLED); - Interface_SetHudVisibility(HUD_VISIBILITY_A_HEARTS_MAGIC_MINIMAP_WITH_OVERWRITE); + // @recomp_use_export_var no_bow_epona_fix: If the B button does not contain the bow, don't disable the UI. + if (!no_bow_epona_fix || BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) == ITEM_BOW) { + BUTTON_STATUS(EQUIP_SLOT_C_LEFT) = BTN_DISABLED; + BUTTON_STATUS(EQUIP_SLOT_C_DOWN) = BTN_DISABLED; + BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; + set_extra_item_slot_status(BTN_DISABLED); + Interface_SetHudVisibility(HUD_VISIBILITY_A_HEARTS_MAGIC_MINIMAP_WITH_OVERWRITE); + } if (play->transitionMode != TRANS_MODE_OFF) { Interface_SetHudVisibility(HUD_VISIBILITY_NONE); @@ -802,6 +855,13 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { } } +bool fd_anywhere = false; + +// @recomp_export void recomp_set_fd_anywhere(bool new_val): Set whether the Fierce Deity's Mask has scene restrictions. +RECOMP_EXPORT void recomp_set_fd_anywhere(bool new_val) { + fd_anywhere = new_val; +} + /** * A continuation of the if-else chain from Interface_UpdateButtonsPart1 * Also used directly when opening the pause menu i.e. skips part 1 @@ -1181,7 +1241,8 @@ RECOMP_PATCH void Interface_UpdateButtonsPart2(PlayState* play) { } } else if (GET_CUR_FORM_BTN_ITEM(i) == ITEM_MASK_FIERCE_DEITY) { // Fierce Deity's Mask is equipped - if ((play->sceneId != SCENE_MITURIN_BS) && (play->sceneId != SCENE_HAKUGIN_BS) && + // @recomp_use_export_var fd_anywhere: Allow the player to use the Fierce Deity's Mask anywhere if mods enable it. + if (!fd_anywhere && (play->sceneId != SCENE_MITURIN_BS) && (play->sceneId != SCENE_HAKUGIN_BS) && (play->sceneId != SCENE_SEA_BS) && (play->sceneId != SCENE_INISIE_BS) && (play->sceneId != SCENE_LAST_BS)) { if (BUTTON_STATUS(i) != BTN_DISABLED) { From 0f924106203c2b2a0153acce151cdbc93b423a9f Mon Sep 17 00:00:00 2001 From: LittleCube Date: Mon, 14 Oct 2024 16:53:30 -0400 Subject: [PATCH 24/29] Add recomp_after_play_init event (#496) * fix signature for recomp_set_allow_no_ocarina_tf, add new event recomp_after_play_init * say initialize instead of construct --- patches/play_patches.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/patches/play_patches.c b/patches/play_patches.c index 1d38632..04f7b88 100644 --- a/patches/play_patches.c +++ b/patches/play_patches.c @@ -151,11 +151,12 @@ void DayTelop_Init_NORELOCATE(GameState*); void TitleSetup_Init_NORELOCATE(GameState*); RECOMP_DECLARE_EVENT(recomp_on_play_init(PlayState* this)); +RECOMP_DECLARE_EVENT(recomp_after_play_init(PlayState* this)); bool allow_no_ocarina_tf = false; -// @recomp_export void recomp_set_allow_no_ocarina(bool new_val): Set whether to force Termina Field to load normally even if Link has no ocarina. -RECOMP_EXPORT void recomp_set_allow_no_ocarina(bool new_val) { +// @recomp_export void recomp_set_allow_no_ocarina_tf(bool new_val): Set whether to force Termina Field to load normally even if Link has no ocarina. +RECOMP_EXPORT void recomp_set_allow_no_ocarina_tf(bool new_val) { allow_no_ocarina_tf = new_val; } @@ -171,7 +172,7 @@ RECOMP_PATCH void Play_Init(GameState* thisx) { u8 sceneLayer; s32 scene; - // @recomp_event recomp_on_play_init(PlayState* this): A new PlayState is being constructed. + // @recomp_event recomp_on_play_init(PlayState* this): A new PlayState is being initialized. recomp_on_play_init(this); if ((gSaveContext.respawnFlag == -4) || (gSaveContext.respawnFlag == -0x63)) { @@ -436,4 +437,7 @@ RECOMP_PATCH void Play_Init(GameState* thisx) { gSaveContext.respawnFlag = 0; sBombersNotebookOpen = false; BombersNotebook_Init(&sBombersNotebook); + + // @recomp_event recomp_after_play_init(PlayState* this): The new PlayState has finished initializing. + recomp_after_play_init(this); } From e862a38135a73fa7df4de31ac13257bb8c304dae Mon Sep 17 00:00:00 2001 From: LittleCube Date: Sun, 20 Oct 2024 19:35:41 -0400 Subject: [PATCH 25/29] More vanilla bug fix exports (#497) --- patches/input.c | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/patches/input.c b/patches/input.c index f65d2cc..2962204 100644 --- a/patches/input.c +++ b/patches/input.c @@ -541,11 +541,24 @@ RECOMP_EXPORT void recomp_set_no_bow_epona_fix(bool new_val) { no_bow_epona_fix = new_val; } +bool h_and_d_no_sword_fix = false; + +// @recomp_export void recomp_set_h_and_d_no_sword_fix(bool new_val): Set whether to enable the fix for playing Honey and Darling without a sword. +RECOMP_EXPORT void recomp_set_h_and_d_no_sword_fix(bool new_val) { + h_and_d_no_sword_fix = new_val; +} + extern s16 sPictoState; extern s16 sPictoPhotoBeingTaken; extern void* gWorkBuffer; u16 func_801A5100(void); +#define ON_EPONA (player->stateFlags1 & PLAYER_STATE1_800000) +#define EPONA_FIX_ACTIVE (no_bow_epona_fix && ON_EPONA) + +#define AT_H_AND_D (play->sceneId == SCENE_BOWLING) +#define H_AND_D_FIX_ACTIVE (h_and_d_no_sword_fix && AT_H_AND_D) + // @recomp Patched to update status of extra buttons via set_extra_item_slot_status. RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { InterfaceContext* interfaceCtx = &play->interfaceCtx; @@ -555,11 +568,11 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { if (gSaveContext.save.cutsceneIndex < 0xFFF0) { gSaveContext.hudVisibilityForceButtonAlphasByStatus = false; - if ((player->stateFlags1 & PLAYER_STATE1_800000) || CHECK_WEEKEVENTREG(WEEKEVENTREG_08_01) || + if (ON_EPONA || CHECK_WEEKEVENTREG(WEEKEVENTREG_08_01) || (!CHECK_EVENTINF(EVENTINF_41) && (play->unk_1887C >= 2))) { // Riding Epona OR Honey & Darling minigame OR Horseback balloon minigame OR related to swamp boat // (non-minigame?) - if ((player->stateFlags1 & PLAYER_STATE1_800000) && (player->currentMask == PLAYER_MASK_BLAST) && + if (ON_EPONA && (player->currentMask == PLAYER_MASK_BLAST) && (gSaveContext.bButtonStatus == BTN_DISABLED)) { // Riding Epona with blast mask? restoreHudVisibility = true; @@ -611,7 +624,7 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { set_extra_item_slot_status(BTN_DISABLED); } else { // @recomp_use_export_var no_bow_epona_fix: Part of the no bow Epona fix. - if (no_bow_epona_fix) { + if (EPONA_FIX_ACTIVE) { if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_BOW) { BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; BUTTON_STATUS(EQUIP_SLOT_B) = BTN_ENABLED; @@ -624,7 +637,7 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { Interface_LoadItemIconImpl(play, EQUIP_SLOT_B); } else if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_NONE) { // @recomp_use_export_var no_bow_epona_fix: Part of the no bow Epona fix. - if (no_bow_epona_fix) { + if (EPONA_FIX_ACTIVE) { gSaveContext.buttonStatus[EQUIP_SLOT_B] = BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B); BUTTON_STATUS(EQUIP_SLOT_B) = BTN_DISABLED; } else { @@ -635,7 +648,7 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { } // @recomp_use_export_var no_bow_epona_fix: If the B button does not contain a sword, don't disable the UI. - if (!no_bow_epona_fix || BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) < ITEM_SWORD_KOKIRI || + if (!EPONA_FIX_ACTIVE || BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) < ITEM_SWORD_KOKIRI || BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) > ITEM_SWORD_GILDED) { BUTTON_STATUS(EQUIP_SLOT_C_LEFT) = BTN_DISABLED; BUTTON_STATUS(EQUIP_SLOT_C_DOWN) = BTN_DISABLED; @@ -669,12 +682,12 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; set_extra_item_slot_status(BTN_DISABLED); Interface_SetHudVisibility(HUD_VISIBILITY_A_B_MINIMAP); - } else if (player->stateFlags1 & PLAYER_STATE1_800000) { + } else if (ON_EPONA) { Interface_SetHudVisibility(HUD_VISIBILITY_A_B_MINIMAP); } } } else { - if (player->stateFlags1 & PLAYER_STATE1_800000) { + if (ON_EPONA) { Interface_SetHudVisibility(HUD_VISIBILITY_A_B_MINIMAP); } @@ -686,13 +699,16 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { } else { BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; } + if (h_and_d_no_sword_fix) { + BUTTON_STATUS(EQUIP_SLOT_B) = BTN_ENABLED; + } BUTTON_STATUS(EQUIP_SLOT_C_LEFT) = BTN_DISABLED; BUTTON_STATUS(EQUIP_SLOT_C_DOWN) = BTN_DISABLED; BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; set_extra_item_slot_status(BTN_DISABLED); } else { // @recomp_use_export_var no_bow_epona_fix: Part of the no bow Epona fix. - if (no_bow_epona_fix) { + if (EPONA_FIX_ACTIVE) { if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_BOW) { BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) = ITEM_BOW; BUTTON_STATUS(EQUIP_SLOT_B) = BTN_ENABLED; @@ -704,9 +720,9 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { if (play->unk_1887C >= 2) { Interface_LoadItemIconImpl(play, EQUIP_SLOT_B); - } else if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_NONE) { + } else if (gSaveContext.save.saveInfo.inventory.items[SLOT_BOW] == ITEM_NONE && !H_AND_D_FIX_ACTIVE) { // @recomp_use_export_var no_bow_epona_fix: Part of the no bow Epona fix. - if (no_bow_epona_fix) { + if (EPONA_FIX_ACTIVE) { gSaveContext.buttonStatus[EQUIP_SLOT_B] = BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B); BUTTON_STATUS(EQUIP_SLOT_B) = BTN_DISABLED; } else { @@ -729,7 +745,7 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { } // @recomp_use_export_var no_bow_epona_fix: If the B button does not contain the bow, don't disable the UI. - if (!no_bow_epona_fix || BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) == ITEM_BOW) { + if ((!no_bow_epona_fix || BUTTON_ITEM_EQUIP(CUR_FORM, EQUIP_SLOT_B) == ITEM_BOW) && !H_AND_D_FIX_ACTIVE) { BUTTON_STATUS(EQUIP_SLOT_C_LEFT) = BTN_DISABLED; BUTTON_STATUS(EQUIP_SLOT_C_DOWN) = BTN_DISABLED; BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; @@ -753,8 +769,11 @@ RECOMP_PATCH void Interface_UpdateButtonsPart1(PlayState* play) { BUTTON_STATUS(EQUIP_SLOT_C_RIGHT) = BTN_DISABLED; set_extra_item_slot_status(BTN_DISABLED); Interface_SetHudVisibility(HUD_VISIBILITY_A_B_MINIMAP); - } else if (player->stateFlags1 & PLAYER_STATE1_800000) { + } else if (ON_EPONA) { Interface_SetHudVisibility(HUD_VISIBILITY_A_B_MINIMAP); + } else if (H_AND_D_FIX_ACTIVE) { + Interface_LoadItemIconImpl(play, EQUIP_SLOT_B); + Interface_SetHudVisibility(HUD_VISIBILITY_B); } } } else if (sPictoState != PICTO_BOX_STATE_OFF) { From db4d9c668d7b9e3aa996c0ba4957df3326090134 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 21 Oct 2024 22:25:16 -0400 Subject: [PATCH 26/29] Update runtime for heap allocator --- lib/N64ModernRuntime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 9e9ae17..3e39c2e 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 9e9ae173ee2c378c6ff5f7bbfe452503a80212d9 +Subproject commit 3e39c2ec34340e245a5b7e353560b5a793b9990b From 323cf54888950496e7db69c14d9622b4b2fb71ac Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 10 Nov 2024 14:55:16 -0500 Subject: [PATCH 27/29] Update RT64 for optimized ubershaders and update recomp runtime --- include/zelda_render.h | 1 - lib/N64ModernRuntime | 2 +- lib/rt64 | 2 +- src/main/main.cpp | 2 +- src/main/rt64_render_context.cpp | 10 ---------- 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/include/zelda_render.h b/include/zelda_render.h index 2d909e4..91820b2 100644 --- a/include/zelda_render.h +++ b/include/zelda_render.h @@ -29,7 +29,6 @@ namespace zelda64 { void shutdown() override; uint32_t get_display_framerate() const override; float get_resolution_scale() const override; - void load_shader_cache(std::span cache_binary) override; protected: std::unique_ptr app; diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 3e39c2e..d5c81d0 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 3e39c2ec34340e245a5b7e353560b5a793b9990b +Subproject commit d5c81d0a6bf2e5f36747a095a7a060d7623bbf58 diff --git a/lib/rt64 b/lib/rt64 index 88c618c..c7e270c 160000 --- a/lib/rt64 +++ b/lib/rt64 @@ -1 +1 @@ -Subproject commit 88c618c1f8d8089f94e78537e49e0d77798fc0be +Subproject commit c7e270cf1588b7bbf73913d542ca5d40ee2728a2 diff --git a/src/main/main.cpp b/src/main/main.cpp index cbeb63a..bf13a28 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -331,6 +331,7 @@ std::vector supported_games = { .internal_name = "ZELDA MAJORA'S MASK", .game_id = u8"mm.n64.us.1.0", .mod_game_id = "mm", + .save_type = recomp::SaveType::Flashram, .is_enabled = true, .entrypoint_address = get_entrypoint_address(), .entrypoint = recomp_entrypoint, @@ -679,7 +680,6 @@ int main(int argc, char** argv) { printf("\n"); recomp::start( - 64 * 1024 * 1024, // 64MB to have plenty of room for loading mods project_version, {}, rsp_callbacks, diff --git a/src/main/rt64_render_context.cpp b/src/main/rt64_render_context.cpp index 8d5c735..e800d99 100644 --- a/src/main/rt64_render_context.cpp +++ b/src/main/rt64_render_context.cpp @@ -397,16 +397,6 @@ float zelda64::renderer::RT64Context::get_resolution_scale() const { } } -void zelda64::renderer::RT64Context::load_shader_cache(std::span cache_binary) { - // TODO figure out how to avoid a copy here. - std::istringstream cache_stream{std::string{cache_binary.data(), cache_binary.size()}}; - - if (!app->rasterShaderCache->loadOfflineList(cache_stream)) { - printf("Failed to preload shader cache!\n"); - assert(false); - } -} - RT64::UserConfiguration::Antialiasing zelda64::renderer::RT64MaxMSAA() { return device_max_msaa; } From 2ed55b4ff27c8e7ba16e1994085dd0d6bdb5e46e Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 17 Nov 2024 20:54:10 -0500 Subject: [PATCH 28/29] Update RT64 for S2DEX replacements --- include/zelda_render.h | 2 +- lib/rt64 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/zelda_render.h b/include/zelda_render.h index 91820b2..0143471 100644 --- a/include/zelda_render.h +++ b/include/zelda_render.h @@ -14,7 +14,7 @@ namespace RT64 { namespace zelda64 { namespace renderer { - class RT64Context : public ultramodern::renderer::RendererContext { + class RT64Context final : public ultramodern::renderer::RendererContext { public: ~RT64Context() override; RT64Context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode); diff --git a/lib/rt64 b/lib/rt64 index c7e270c..67422c3 160000 --- a/lib/rt64 +++ b/lib/rt64 @@ -1 +1 @@ -Subproject commit c7e270cf1588b7bbf73913d542ca5d40ee2728a2 +Subproject commit 67422c3b647058d3d38f2813a2abe79cf1638f13 From 552a82b3b010baaaa5a67bdfd85fcb6dab573136 Mon Sep 17 00:00:00 2001 From: danielryb <59661841+danielryb@users.noreply.github.com> Date: Thu, 21 Nov 2024 04:48:22 +0100 Subject: [PATCH 29/29] Add API function export for camera Z-targeting fixes (#503) --- patches/camera_patches.c | 108 ++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 18 deletions(-) diff --git a/patches/camera_patches.c b/patches/camera_patches.c index f0ae7e0..46e018f 100644 --- a/patches/camera_patches.c +++ b/patches/camera_patches.c @@ -9,6 +9,8 @@ #include "z64shrink_window.h" #include "z64player.h" +static bool camera_fixes = false; + static bool prev_analog_cam_active = false; static bool can_use_analog_cam = false; static bool analog_cam_active = false; @@ -26,6 +28,10 @@ float analog_camera_y_sensitivity = 500.0f; static const float analog_cam_threshold = 0.1f; +RECOMP_EXPORT void recomp_set_camera_fixes(bool new_val) { + camera_fixes = new_val; +} + void update_analog_camera_params(Camera* camera) { // recomp_printf("Camera at: %.2f %.2f %.2f\n" // " eye: %.2f %.2f %.2f\n" @@ -48,17 +54,17 @@ void update_analog_camera_params(Camera* camera) { void update_analog_cam(Camera* c) { can_use_analog_cam = true; - + Player* player = GET_PLAYER(c->play); // recomp_printf(" val: %d\n", func_80123434(player)); - + // Check if the player just started Z targeting and reset to auto cam if so. static bool prev_targeting_held = false; bool targeting_held = func_80123434(player) || player->lockOnActor != NULL; if (targeting_held && !prev_targeting_held) { analog_cam_active = false; } - + // Enable analog cam if the right stick is held. float input_x, input_y; recomp_get_camera_inputs(&input_x, &input_y); @@ -66,12 +72,12 @@ void update_analog_cam(Camera* c) { if (fabsf(input_x) >= analog_cam_threshold || fabsf(input_y) >= analog_cam_threshold) { analog_cam_active = true; } - + if (analog_cam_skip_once) { analog_cam_active = false; analog_cam_skip_once = false; } - + // Record the Z targeting state. prev_targeting_held = targeting_held; @@ -92,7 +98,7 @@ void update_analog_cam(Camera* c) { analog_camera_pos.pitch += analog_camera_pitch_vel; analog_camera_pos.yaw += analog_camera_yaw_vel; - + if (analog_camera_pos.pitch > 0x36B0) { analog_camera_pos.pitch = 0x36B0; } @@ -784,7 +790,7 @@ RECOMP_PATCH s32 Camera_Normal1(Camera* camera) { // @recomp Update the analog camera. if (recomp_analog_cam_enabled()) { update_analog_cam(camera); - + if (analog_cam_active) { spB4.pitch = analog_camera_pos.pitch; // spB4.r = analog_camera_pos.r; @@ -1017,14 +1023,14 @@ RECOMP_PATCH s32 Camera_Jump2(Camera* camera) { camera->pitchUpdateRateInv = 100.0f; camera->rUpdateRateInv = 100.0f; } - + spB4.pitch = CLAMP_MAX(spB4.pitch, DEG_TO_BINANG(60.43f)); spB4.pitch = CLAMP_MIN(spB4.pitch, -DEG_TO_BINANG(60.43f)); // @recomp Update the analog camera. if (recomp_analog_cam_enabled()) { update_analog_cam(camera); - + if (analog_cam_active) { spB4.pitch = analog_camera_pos.pitch; // spB4.r = analog_camera_pos.r; @@ -1064,6 +1070,10 @@ RECOMP_PATCH s32 Camera_Jump2(Camera* camera) { */ // @recomp Patched for analog cam. RECOMP_PATCH s32 Camera_Parallel1(Camera* camera) { + // @recomp + static bool prev_targeting_held = false; + bool isChargingDekuFlowerDive = !!(((Player*)camera->focalActor)->stateFlags3 & PLAYER_STATE3_100); + Vec3f* eye = &camera->eye; Vec3f* at = &camera->at; Vec3f* eyeNext = &camera->eyeNext; @@ -1089,6 +1099,10 @@ RECOMP_PATCH s32 Camera_Parallel1(Camera* camera) { CameraModeValue* values; f32 yNormal; + // @recomp Read timer4 from timer2. + s16 timer4 = rwData->timer2 >> 5; // @recomp Used to check if z-target can be quit. Mirrors timer2 values during z-target in unmodified function. + rwData->timer2 &= 0x001F; + if (!RELOAD_PARAMS(camera)) { } else { values = sCameraSettings[camera->setting].cameraModes[camera->mode].values; @@ -1151,6 +1165,8 @@ RECOMP_PATCH s32 Camera_Parallel1(Camera* camera) { rwData->timer2 = 20; } else { rwData->timer2 = 6; + // @recomp Initiate timer4 for z-target. + timer4 = 6; } if ((camera->focalActor == &GET_PLAYER(camera->play)->actor) && (camera->mode == CAM_MODE_CHARGE)) { @@ -1187,12 +1203,55 @@ RECOMP_PATCH s32 Camera_Parallel1(Camera* camera) { rwData->unk_26 = 1; camera->animState = 1; sCameraInterfaceFlags = roData->interfaceFlags; + + // @recomp Reset prev_targeting_held after transition. + prev_targeting_held = false; + break; } + // @recomp Change behavior for z-target only. + if (camera_fixes) { + if ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == PARALLEL1_FLAG_1) { + Player* player = GET_PLAYER(camera->play); + bool targeting_held = func_80123434(player) || player->lockOnActor != NULL; + + // @recomp Fix camera rotating with player if z-target gets released too fast after transition. + if ((targeting_held) && (!prev_targeting_held)) { + // @recomp Reset timer2 to avoid immediate rotation, if player presses, releases and presses z-target in a very short time-window. + rwData->timer2 = 6; + rwData->unk_1E = BINANG_ROT180(camera->focalActorPosRot.rot.y) + roData->unk_22; + } + + // @recomp Maintain vanilla behavior for quitting z-target. + if ((timer4 == 0) && (!targeting_held)) { + rwData->timer2 = 0; + } + + // @recomp Decrease timer4 only in cases where timer2 would be decreased. + if ((timer4 > 0) + && (rwData->timer3 <= 0)) { + timer4--; + } + + prev_targeting_held = targeting_held; + } + } + if (rwData->timer2 != 0) { switch (roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) { case PARALLEL1_FLAG_1: + if (camera_fixes) { + // @recomp Fix camera rotating with player if z-target gets released too fast after transition. + if (isChargingDekuFlowerDive) { + // @recomp Fix camera wiggle during dive into deku flower. + rwData->unk_1E = BINANG_ROT180(camera->focalActorPosRot.rot.y) + roData->unk_22; + } + rwData->unk_20 = roData->unk_20; + break; + } + // @recomp fallthrough + case (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1): rwData->unk_1E = BINANG_ROT180(camera->focalActorPosRot.rot.y) + roData->unk_22; rwData->unk_20 = roData->unk_20; @@ -1374,7 +1433,7 @@ RECOMP_PATCH s32 Camera_Parallel1(Camera* camera) { // @recomp Update the analog camera. if (recomp_analog_cam_enabled()) { update_analog_cam(camera); - + if (analog_cam_active) { sp90.pitch = analog_camera_pos.pitch; // sp90.r = analog_camera_pos.r; @@ -1400,10 +1459,20 @@ RECOMP_PATCH s32 Camera_Parallel1(Camera* camera) { func_800CBFA4(camera, at, eye, 3); } - if (rwData->timer2 != 0) { - sUpdateCameraDirection = true; + if (camera_fixes) { + // @recomp Fix camera not updating input dir for the first few frames after transition. + if ((isChargingDekuFlowerDive) && (rwData->timer2 != 0)) { + // @recomp Fix camera wiggle during dive into deku flower. + sUpdateCameraDirection = true; + } else { + sUpdateCameraDirection = false; + } } else { - sUpdateCameraDirection = false; + if (rwData->timer2 != 0) { + sUpdateCameraDirection = true; + } else { + sUpdateCameraDirection = false; + } } } @@ -1412,6 +1481,9 @@ RECOMP_PATCH s32 Camera_Parallel1(Camera* camera) { camera->atLerpStepScale = Camera_ClampLerpScale(camera, sp72 ? roData->unk_1C : roData->unk_18); rwData->unk_26 &= ~1; + // @recomp Save timer4 in unused bits of timer2. + rwData->timer2 |= timer4 << 5; + return 1; } @@ -1580,11 +1652,11 @@ RECOMP_PATCH s32 Camera_Normal3(Camera* camera) { Camera_UnsetStateFlag(camera, CAM_STATE_DISABLE_MODE_CHANGE); } } - + // @recomp Update the analog camera. if (recomp_analog_cam_enabled()) { update_analog_cam(camera); - + if (analog_cam_active) { sp80.pitch = analog_camera_pos.pitch; // sp80.r = analog_camera_pos.r; @@ -1822,7 +1894,7 @@ RECOMP_PATCH s32 Camera_Jump3(Camera* camera) { // @recomp Update the analog camera. if (recomp_analog_cam_enabled()) { update_analog_cam(camera); - + if (analog_cam_active) { spAC.pitch = analog_camera_pos.pitch; // spAC.r = analog_camera_pos.r; @@ -1871,7 +1943,7 @@ void analog_cam_post_play_update(PlayState* play) { // recomp_printf("prev_analog_cam_active: %d can_use_analog_cam: %d\n", prev_analog_cam_active, can_use_analog_cam); // recomp_printf("setting: %d mode: %d func: %d\n", active_cam->setting, active_cam->mode, sCameraSettings[active_cam->setting].cameraModes[active_cam->mode].funcId); // recomp_printf("active cam yaw %d\n", Camera_GetInputDirYaw(GET_ACTIVE_CAM(play))); - + // Update parameters for the analog cam if the game is unpaused. if (play->pauseCtx.state == PAUSE_STATE_OFF && R_PAUSE_BG_PRERENDER_STATE == PAUSE_BG_PRERENDER_OFF) { update_analog_camera_params(active_cam); @@ -2056,7 +2128,7 @@ RECOMP_PATCH void func_809EC568(Boss04* this, PlayState* play) { if (this->unk_704 == 70) { this->unk_2C8 = 300; this->unk_2D0 = 0.0f; - + D_809EE4D0 = 1; this->unk_2E2 = 60; this->unk_2E0 = 93;