diff --git a/include/recomp_helpers.h b/include/recomp_helpers.h new file mode 100644 index 0000000..ef9221e --- /dev/null +++ b/include/recomp_helpers.h @@ -0,0 +1,46 @@ +#ifndef __RECOMP_HELPERS__ +#define __RECOMP_HELPERS__ + +#include "recomp.h" + +template +T _arg(uint8_t* rdram, recomp_context* ctx) { + static_assert(index < 4, "Only args 0 through 3 supported"); + gpr raw_arg = (&ctx->r4)[index]; + if constexpr (std::is_same_v) { + if constexpr (index < 2) { + static_assert(index != 1, "Floats in arg 1 not supported"); + return ctx->f12.fl; + } + else { + return std::bit_cast(raw_arg); + } + } + else if constexpr (std::is_pointer_v) { + static_assert (!std::is_pointer_v>, "Double pointers not supported"); + return TO_PTR(std::remove_pointer_t, raw_arg); + } + else if constexpr (std::is_integral_v) { + static_assert(sizeof(T) <= 4, "64-bit args not supported"); + return static_cast(raw_arg); + } + else { + // static_assert in else workaround + [] () { + static_assert(flag, "Unsupported type"); + }(); + } +} + +template +void _return(recomp_context* ctx, T val) { + static_assert(sizeof(T) <= 4 && "Only 32-bit value returns supported currently"); + if (std::is_same_v) { + ctx->f0.fl = val; + } + else if (std::is_integral_v && sizeof(T) <= 4) { + ctx->r2 = int32_t(val); + } +} + +#endif \ No newline at end of file diff --git a/include/recomp_ui.h b/include/recomp_ui.h index a297b58..c678418 100644 --- a/include/recomp_ui.h +++ b/include/recomp_ui.h @@ -21,5 +21,6 @@ enum class Menu { }; void set_current_menu(Menu menu); +void destroy_ui(); #endif diff --git a/include/rt64_layer.h b/include/rt64_layer.h index 51c0d00..b74ad3f 100644 --- a/include/rt64_layer.h +++ b/include/rt64_layer.h @@ -76,6 +76,7 @@ DLLIMPORT void ProcessRDPList(void); DLLIMPORT void ProcessDList(void); DLLIMPORT void UpdateScreen(void); DLLIMPORT void ChangeWindow(void); +DLLIMPORT void PluginShutdown(void); void set_rt64_hooks(); diff --git a/patches/Makefile b/patches/Makefile index c53ede8..4cd2ae9 100644 --- a/patches/Makefile +++ b/patches/Makefile @@ -5,7 +5,7 @@ LD := ld.lld OBJCOPY := llvm-objcopy CFLAGS := -target mips -mips2 -mabi=32 -O2 -mno-odd-spreg -fomit-frame-pointer -G0 -Wall -Wextra -Wno-incompatible-library-redeclaration -Wno-unused-parameter -Wno-unknown-pragmas -Wno-unused-variable -CPPFLAGS := -nostdinc -D_LANGUAGE_C -I ../../mm/include -I ../../mm/src -I ../../mm/build -I ../../mm/assets +CPPFLAGS := -nostdinc -D_LANGUAGE_C -DMIPS -I ../../mm/include -I ../../mm/src -I ../../mm/build -I ../../mm/assets LDFLAGS := -nostdlib -T patches.ld -T syms.ld BINFLAGS := -O binary diff --git a/patches/cheats.c b/patches/cheats.c index 41aff89..639a1ad 100644 --- a/patches/cheats.c +++ b/patches/cheats.c @@ -1,5 +1,4 @@ -#define Audio_PlaySfx play_sound -#include "global.h" +#include "patches.h" // Infinite magic s32 Magic_Consume(PlayState* play, s16 magicToConsume, s16 type) { diff --git a/patches/culling.c b/patches/culling.c index 6d83a64..4734c1c 100644 --- a/patches/culling.c +++ b/patches/culling.c @@ -1,4 +1,4 @@ -#include "global.h" +#include "patches.h" // Disable frustum culling for actors, but leave distance culling intact s32 func_800BA2FC(PlayState* play, Actor* actor, Vec3f* projectedPos, f32 projectedW) { diff --git a/patches/input.c b/patches/input.c new file mode 100644 index 0000000..addb681 --- /dev/null +++ b/patches/input.c @@ -0,0 +1,24 @@ +#include "patches.h" +#include "input.h" + +u32 sPlayerItemButtons[] = { + BTN_B, + BTN_CLEFT, + BTN_CDOWN, + BTN_CRIGHT, +}; + +// Return currently-pressed button, in order of priority B, CLEFT, CDOWN, CRIGHT. +EquipSlot func_8082FDC4(void) { + EquipSlot i; + RecompInputs cur_inputs; + recomp_get_item_inputs(&cur_inputs); + + for (i = 0; i < ARRAY_COUNT(sPlayerItemButtons); i++) { + if (CHECK_BTN_ALL(cur_inputs.buttons, sPlayerItemButtons[i])) { + break; + } + } + + return i; +} diff --git a/patches/input.h b/patches/input.h new file mode 100644 index 0000000..6ef1c1a --- /dev/null +++ b/patches/input.h @@ -0,0 +1,35 @@ +#ifndef __INPUT_H__ +#define __INPUT_H__ + +#ifdef MIPS +#include "ultra64.h" +#else +#include "recomp.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct RecompInputs { + u32 buttons; + float x; + float y; +} RecompInputs; + + +#ifdef MIPS +# define DECLARE_FUNC(type, name, ...) \ + type name(__VA_ARGS__); +#else +# define DECLARE_FUNC(type, name, ...) \ + void name(uint8_t* rdram, recomp_context* ctx); +#endif + +DECLARE_FUNC(void, recomp_get_item_inputs, RecompInputs* inputs); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/patches/patches.h b/patches/patches.h new file mode 100644 index 0000000..1e41569 --- /dev/null +++ b/patches/patches.h @@ -0,0 +1,6 @@ +#ifndef __PATCHES_H__ +#define __PATCHES_H__ + +#include "global.h" + +#endif diff --git a/patches/syms.ld b/patches/syms.ld index 3dbbf77..5152687 100644 --- a/patches/syms.ld +++ b/patches/syms.ld @@ -1,7 +1,13 @@ __start = 0x80000000; + +/* Dummy addresses that get recompiled into function calls */ +recomp_get_item_inputs = 0x81000000; + /* TODO pull these symbols from the elf file directly */ Player_PostLimbDrawGameplay = 0x80128BD0; Player_DrawImpl = 0x801246F4; gRegEditor = 0x801f3f60; -play_sound = 0x8019f0c8; +Audio_PlaySfx = 0x8019f0c8; gSaveContext = 0x801ef670; +Interface_SetHudVisibility = 0x8010ef68; +Player_GetItemOnButton = 0x8012364C; diff --git a/portultra/events.cpp b/portultra/events.cpp index 629ed32..dc1f7e0 100644 --- a/portultra/events.cpp +++ b/portultra/events.cpp @@ -14,6 +14,7 @@ #include "ultra64.h" #include "multilibultra.hpp" #include "recomp.h" +#include "recomp_ui.h" #include "rsp.h" struct SpTaskAction { @@ -42,17 +43,14 @@ static struct { OSMesg msg = (OSMesg)0; } sp; struct { - std::thread thread; PTR(OSMesgQueue) mq = NULLPTR; OSMesg msg = (OSMesg)0; } dp; struct { - std::thread thread; PTR(OSMesgQueue) mq = NULLPTR; OSMesg msg = (OSMesg)0; } ai; struct { - std::thread thread; PTR(OSMesgQueue) mq = NULLPTR; OSMesg msg = (OSMesg)0; } si; @@ -95,6 +93,9 @@ extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 ret uint64_t total_vis = 0; + +extern std::atomic_bool exited; + void set_dummy_vi(); void vi_thread_func() { @@ -106,7 +107,7 @@ void vi_thread_func() { int remaining_retraces = events_context.vi.retrace_count; - while (true) { + while (!exited) { // Determine the next VI time (more accurate than adding 16ms each VI interrupt) auto next = Multilibultra::get_start() + (total_vis * 1000000us) / (60 * Multilibultra::get_speed_multiplier()); //if (next > std::chrono::system_clock::now()) { @@ -177,6 +178,7 @@ void RT64Init(uint8_t* rom, uint8_t* rdram, Multilibultra::WindowHandle window_h void RT64SendDL(uint8_t* rdram, const OSTask* task); void RT64UpdateScreen(uint32_t vi_origin); void RT64ChangeWindow(); +void RT64Shutdown(); uint8_t dmem[0x1000]; uint16_t rspReciprocals[512]; @@ -225,10 +227,14 @@ void task_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_rea thread_ready->test_and_set(); thread_ready->notify_all(); - while (1) { + while (true) { // Wait until an RSP task has been sent events_context.sp_task.wait(nullptr); + if (exited) { + return; + } + // Retrieve the task pointer and clear the pending RSP task OSTask* task = events_context.sp_task; events_context.sp_task.store(nullptr); @@ -265,7 +271,7 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read thread_ready->test_and_set(); thread_ready->notify_all(); - while (true) { + while (!exited) { // Try to pull an action from the queue Action action; if (events_context.action_queue.wait_dequeue_timed(action, 1ms)) { @@ -284,6 +290,9 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read } } } + destroy_ui(); + // TODO restore this call once the RT64 shutdown issue is fixed. + // RT64Shutdown(); } extern unsigned int VI_STATUS_REG; @@ -469,3 +478,21 @@ void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom, Multilibultra::Win events_context.vi.thread = std::thread{ vi_thread_func }; } + +void Multilibultra::join_event_threads() { + events_context.sp.gfx_thread.join(); + events_context.vi.thread.join(); + + // Send a dummy RSP task so that the task thread is able to exit it's atomic wait and terminate. + OSTask dummy_task{}; + OSTask* expected = nullptr; + + // Attempt to exchange the task with the dummy task one until it was nullptr, as that indicates the + // task thread was ready for a new task. + do { + expected = nullptr; + } while (!events_context.sp_task.compare_exchange_weak(expected, &dummy_task)); + events_context.sp_task.notify_all(); + + events_context.sp.task_thread.join(); +} diff --git a/portultra/multilibultra.hpp b/portultra/multilibultra.hpp index 487e836..533db5f 100644 --- a/portultra/multilibultra.hpp +++ b/portultra/multilibultra.hpp @@ -125,6 +125,8 @@ struct gfx_callbacks_t { void start(WindowHandle window_handle, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks); void start_game(int game); bool is_game_started(); +void quit(); +void join_event_threads(); } // namespace Multilibultra diff --git a/portultra/scheduler.cpp b/portultra/scheduler.cpp index 3030683..60cfb9e 100644 --- a/portultra/scheduler.cpp +++ b/portultra/scheduler.cpp @@ -144,6 +144,8 @@ void swap_running_thread(thread_queue_t& running_thread_queue, OSThread*& cur_ru } } +extern std::atomic_bool exited; + void scheduler_func() { thread_queue_t running_thread_queue{}; OSThread* cur_running_thread = nullptr; @@ -169,8 +171,13 @@ void scheduler_func() { // Handle threads that have changed priority handle_thread_reprioritization(running_thread_queue); - // Determine which thread to run, stopping the current running thread if necessary - swap_running_thread(running_thread_queue, cur_running_thread); + if (!exited) { + // Determine which thread to run, stopping the current running thread if necessary + swap_running_thread(running_thread_queue, cur_running_thread); + } + else { + return; + } std::this_thread::yield(); if (old_running_thread != cur_running_thread && old_running_thread && cur_running_thread) { diff --git a/portultra/threads.cpp b/portultra/threads.cpp index 9d6e69c..5a35e30 100644 --- a/portultra/threads.cpp +++ b/portultra/threads.cpp @@ -161,12 +161,6 @@ extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) { } else { Multilibultra::schedule_running_thread(t); } - - // The main thread "becomes" the first thread started, so join on it and exit after it completes. - if (is_main_thread) { - t->context->host_thread.join(); - std::exit(EXIT_SUCCESS); - } } extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) { diff --git a/portultra/timer.cpp b/portultra/timer.cpp index 15d1d63..ca53605 100644 --- a/portultra/timer.cpp +++ b/portultra/timer.cpp @@ -121,7 +121,8 @@ void timer_thread(RDRAM_ARG1) { active_timers.insert(cur_timer_); // Process the new action process_timer_action(cur_action); - } else { + } + else { // Waiting for the timer completed, so send the timer's message to its message queue osSendMesg(PASS_RDRAM cur_timer->mq, cur_timer->msg, OS_MESG_NOBLOCK); // If the timer has a specified interval then reload it with that value @@ -135,6 +136,7 @@ void timer_thread(RDRAM_ARG1) { void Multilibultra::init_timers(RDRAM_ARG1) { timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 }; + timer_context.thread.detach(); } uint32_t Multilibultra::get_speed_multiplier() { diff --git a/src/cont.cpp b/src/cont.cpp index 75cdfcd..b6b2663 100644 --- a/src/cont.cpp +++ b/src/cont.cpp @@ -1,5 +1,5 @@ #include "../portultra/multilibultra.hpp" -#include "recomp.h" +#include "recomp_helpers.h" static Multilibultra::input_callbacks_t input_callbacks; @@ -10,8 +10,8 @@ void set_input_callbacks(const Multilibultra::input_callbacks_t& callbacks) { static int max_controllers = 0; extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) { - gpr bitpattern = ctx->r5; - gpr status = ctx->r6; + PTR(void) bitpattern = _arg<1, PTR(void)>(rdram, ctx); + PTR(void) status = _arg<2, PTR(void)>(rdram, ctx); // Set bit 0 to indicate that controller 0 is present MEM_B(0, bitpattern) = 0x01; @@ -29,22 +29,15 @@ extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) { MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4 } - ctx->r2 = 0; + _return(ctx, 0); } extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) { Multilibultra::send_si_message(); } -struct OSContPad { - u16 button; - s8 stick_x; /* -80 <= stick_x <= 80 */ - s8 stick_y; /* -80 <= stick_y <= 80 */ - u8 errno_; -}; - extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) { - int32_t pad = (int32_t)ctx->r4; + PTR(void) pad = _arg<0, PTR(void)>(rdram, ctx); uint16_t buttons = 0; float x = 0.0f; @@ -74,7 +67,7 @@ extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) { } extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) { - gpr status = ctx->r4; + PTR(void) status = _arg<0, PTR(void)>(rdram, ctx); // Mark controller 0 as present MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus) @@ -89,8 +82,8 @@ extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) { } extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) { - max_controllers = std::min((unsigned int)ctx->r4, 4u); - ctx->r2 = 0; + max_controllers = std::min(_arg<0, u8>(rdram, ctx), u8(4)); + _return(ctx, 0); } extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) { @@ -108,3 +101,16 @@ extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) { extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) { ; } + +#include "../patches/input.h" + +extern "C" void recomp_get_item_inputs(uint8_t* rdram, recomp_context* ctx) { + RecompInputs* inputs = _arg<0, RecompInputs*>(rdram, ctx); + + if (input_callbacks.get_input) { + u16 buttons; + input_callbacks.get_input(&buttons, &inputs->x, &inputs->y); + // TODO remap the inputs for items here + inputs->buttons = buttons; + } +} diff --git a/src/main/main.cpp b/src/main/main.cpp index ab7aafb..0d3fac7 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -99,7 +99,7 @@ std::vector controller_button_map{ std::vector controllers{}; -int sdl_event_filter(void* userdata, SDL_Event* event) { +bool sdl_event_filter(void* userdata, SDL_Event* event) { switch (event->type) { //case SDL_EventType::SDL_KEYUP: //case SDL_EventType::SDL_KEYDOWN: @@ -138,13 +138,13 @@ int sdl_event_filter(void* userdata, SDL_Event* event) { } break; case SDL_EventType::SDL_QUIT: - std::quick_exit(EXIT_SUCCESS); - break; + Multilibultra::quit(); + return true; default: queue_event(*event); break; } - return 1; + return false; } Multilibultra::gfx_callbacks_t::gfx_data_t create_gfx() { @@ -185,8 +185,9 @@ void update_gfx(void*) { constexpr int max_events_per_frame = 16; SDL_Event cur_event; int i = 0; - while (i++ < max_events_per_frame && SDL_PollEvent(&cur_event)) { - sdl_event_filter(nullptr, &cur_event); + static bool exited = false; + while (i++ < max_events_per_frame && SDL_PollEvent(&cur_event) && !exited) { + exited = sdl_event_filter(nullptr, &cur_event); } } diff --git a/src/recomp.cpp b/src/recomp.cpp index 6d5a76b..17f2f6e 100644 --- a/src/recomp.cpp +++ b/src/recomp.cpp @@ -122,6 +122,28 @@ extern "C" void unload_overlays(int32_t ram_addr, uint32_t size); std::unique_ptr rdram_buffer; recomp_context context{}; +void read_patch_data(uint8_t* rdram, gpr patch_data_address) { + const char patches_data_file_path[] = "patches/patches.bin"; + std::ifstream patches_data_file{ patches_data_file_path, std::ios::binary }; + + if (!patches_data_file) { + fprintf(stderr, "Failed to open patches data file: %s\n", patches_data_file_path); + exit(EXIT_FAILURE); + } + + patches_data_file.seekg(0, std::ios::end); + size_t patches_data_size = patches_data_file.tellg(); + patches_data_file.seekg(0, std::ios::beg); + + std::unique_ptr patches_data = std::make_unique(patches_data_size); + + patches_data_file.read(reinterpret_cast(patches_data.get()), patches_data_size); + + for (size_t i = 0; i < patches_data_size; i++) { + MEM_B(i, patch_data_address) = patches_data[i]; + } +} + EXPORT extern "C" void init() { { std::ifstream rom_file{ get_rom_name(), std::ios::binary }; @@ -160,6 +182,9 @@ EXPORT extern "C" void init() { // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000); + // Read in any extra data from patches + read_patch_data(rdram_buffer.get(), (gpr)(s32)0x80800100); + // Set up stack pointer context.r29 = 0xFFFFFFFF803FFFF0u; @@ -197,6 +222,15 @@ bool Multilibultra::is_game_started() { void set_audio_callbacks(const Multilibultra::audio_callbacks_t& callbacks); void set_input_callbacks(const Multilibultra::input_callbacks_t& callback); +std::atomic_bool exited = false; + +void Multilibultra::quit() { + exited.store(true); + int desired = -1; + game_started.compare_exchange_strong(desired, -2); + game_started.notify_all(); +} + void Multilibultra::start(WindowHandle window_handle, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks_) { set_audio_callbacks(audio_callbacks); set_input_callbacks(input_callbacks); @@ -231,16 +265,20 @@ void Multilibultra::start(WindowHandle window_handle, const audio_callbacks_t& a case 0: recomp_entrypoint(rdram_buffer.get(), &context); break; + case -2: + break; } debug_printf("[Recomp] Quitting\n"); }, window_handle}; - while (true) { + while (!exited) { using namespace std::chrono_literals; std::this_thread::sleep_for(1ms); if (gfx_callbacks.update_gfx != nullptr) { gfx_callbacks.update_gfx(gfx_data); } } + game_thread.join(); + Multilibultra::join_event_threads(); } diff --git a/src/rt64_layer.cpp b/src/rt64_layer.cpp index 2d9be82..ab75aab 100644 --- a/src/rt64_layer.cpp +++ b/src/rt64_layer.cpp @@ -130,3 +130,7 @@ void RT64UpdateScreen(uint32_t vi_origin) { void RT64ChangeWindow() { ChangeWindow(); } + +void RT64Shutdown() { + PluginShutdown(); +} diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index 132a26b..b4ae510 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -587,6 +587,10 @@ struct { Rml::Context* context; std::unique_ptr event_listener_instancer; + void unload() { + render_interface.reset(); + } + void swap_document(Menu menu) { if (current_document != nullptr) { current_document->Hide(); @@ -751,4 +755,9 @@ void set_rt64_hooks() { void set_current_menu(Menu menu) { open_menu.store(menu); -} \ No newline at end of file +} + +void destroy_ui() { + Rml::Shutdown(); + UIContext.rml.unload(); +} diff --git a/us.rev1.toml b/us.rev1.toml index 2fe8087..610870b 100644 --- a/us.rev1.toml +++ b/us.rev1.toml @@ -10,10 +10,15 @@ relocatable_sections_path = "overlays.us.rev1.txt" [patches] stubs = [ # Stub out unused functions that directly manipulate RCP status. - "func_80084940", - "func_80084968", + "RcpUtils_PrintRegisterStatus", + "RcpUtils_Reset", # Stub out an unnecessary function that accesses kseg1 addresses. - "func_800818F4" + "CIC6105_Init" +] + +ignored = [ + # Not actually a function + "D_80186028" ] # Hooks @@ -25,23 +30,14 @@ args = ["u32", "u32", "u32"] # Function hooks for overlay loading. [[patches.hook]] -func = "Idle_InitCodeAndMemory" -calls = "load_overlays" -args = ["a2", "a1", "a3"] +func = "Main_Init" +text = " load_overlays((uint32_t)ctx->r6, (uint32_t)ctx->r5, (uint32_t)ctx->r7);" after_vram = 0x800802A4 [[patches.hook]] -func = "Load2_LoadOverlay" -calls = "load_overlays" -# args = [ -# "a0", # $a0 contains rom start -# {operation = "load", type = "u32", base = "sp", offset = 0x10}, # sp + 10 contains the ram address -# {operation = "subtract", arguments = ["a1", "a0"]} # Calculate size from rom end - rom start -# ] -args = ["a1", "a0", "a2"] -# This vram address is an instruction in a delay slot. In that case, the recompiler will emit the -# hook call after this instruction is run and before the function is called. -after_vram = 0x80085048 +func = "Overlay_Load" +text = " load_overlays((uint32_t)ctx->r4, MEM_W(0x10, ctx->r29), (uint32_t)(ctx->r5 - ctx->r4));" +# No after_vram means this will be placed at the start of the function # Single-instruction patches