diff --git a/CMakeLists.txt b/CMakeLists.txt index c3825ac..1bb0222 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,8 @@ target_sources(PatchesLib PRIVATE ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c ) +set_source_files_properties(${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c PROPERTIES COMPILE_FLAGS -fno-strict-aliasing) + # Build patches elf add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/patches/patches.elf COMMAND make @@ -119,6 +121,7 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/game/input.cpp ${CMAKE_SOURCE_DIR}/src/game/controls.cpp + ${CMAKE_SOURCE_DIR}/src/game/quicksaving.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp diff --git a/include/recomp_input.h b/include/recomp_input.h index 3e9af3f..14734d0 100644 --- a/include/recomp_input.h +++ b/include/recomp_input.h @@ -50,6 +50,10 @@ namespace recomp { void handle_events(); bool game_input_disabled(); + + // TODO move these + void quicksave_save(); + void quicksave_load(); } #endif diff --git a/patches/input.h b/patches/input.h index 3b24ab4..d6f0501 100644 --- a/patches/input.h +++ b/patches/input.h @@ -48,6 +48,8 @@ DECLARE_FUNC(void, recomp_get_camera_inputs, float* x_out, float* y_out); // TODO move these DECLARE_FUNC(void, recomp_puts, const char* data, u32 size); DECLARE_FUNC(void, recomp_exit); +DECLARE_FUNC(void, recomp_handle_quicksave_actions, OSMesgQueue* enter_mq, OSMesgQueue* exit_mq); +DECLARE_FUNC(void, recomp_handle_quicksave_actions_main, OSMesgQueue* enter_mq, OSMesgQueue* exit_mq); #ifdef __cplusplus } diff --git a/patches/patches.h b/patches/patches.h index d0d7656..ff76e97 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -1,6 +1,9 @@ #ifndef __PATCHES_H__ #define __PATCHES_H__ +// TODO fix renaming symbols in patch recompilation +#define osRecvMesg osRecvMesg_recomp +#define osSendMesg osSendMesg_recomp #include "global.h" #include "rt64_extended_gbi.h" diff --git a/patches/quicksave_patches.c b/patches/quicksave_patches.c new file mode 100644 index 0000000..227accf --- /dev/null +++ b/patches/quicksave_patches.c @@ -0,0 +1,232 @@ +// Quicksaving is disabled for now + +/* To reenable it, a few changes to the recompiled functions are also needed: + +in Main: +L_80174E84: + // or $a0, $s3, $zero + ctx->r4 = ctx->r19 | 0; + // addiu $a1, $sp, 0x38 + ctx->r5 = ADD32(ctx->r29, 0X38); + // jal 0x80087ED0 + // addiu $a2, $zero, 0x1 + ctx->r6 = ADD32(0, 0X1); + osRecvMesg_recomp(rdram, ctx); + +handle_quicksave_actions(rdram, ctx); + +in Graph_ThreadEntry: +L_801749C8: + +handle_quicksave_actions_main(rdram, ctx); + +ctx->r4 = ctx->r17 | 0; + // jal 0x80174868 + // or $a1, $s0, $zero + ctx->r5 = ctx->r16 | 0; + Graph_Update(rdram, ctx); + +in PadMgr_ThreadEntry: +L_801760A0: + // or $a1, $s2, $zero + ctx->r5 = ctx->r18 | 0; + // jal 0x80087ED0 + // or $a2, $s3, $zero + ctx->r6 = ctx->r19 | 0; + osRecvMesg_recomp(rdram, ctx); + +handle_quicksave_actions(rdram, ctx); + +in AudioMgr_ThreadEntry: +L_80172F58: + +handle_quicksave_actions(rdram, ctx); + +ctx->r4 = ctx->r22 | 0; + // or $a1, $s2, $zero + ctx->r5 = ctx->r18 | 0; + // jal 0x80087ED0 + // or $a2, $s3, $zero + ctx->r6 = ctx->r19 | 0; + osRecvMesg_recomp(rdram, ctx); + +*/ + +#if 0 + +#include "patches.h" +#include "input.h" +#include "audiomgr.h" + +// ultramodern's message queues can be statically initialized, so we don't actually need to call osCreateMesgQueue for these. +OSMesg quicksave_enter_mq_buffer[20]; +OSMesgQueue quicksave_enter_mq = { + .msgCount = ARRAY_COUNT(quicksave_enter_mq_buffer), + .msg = quicksave_enter_mq_buffer, + .validCount = 0, + .first = 0 +}; + +OSMesg quicksave_exit_mq_buffer[20]; +OSMesgQueue quicksave_exit_mq = { + .msgCount = ARRAY_COUNT(quicksave_exit_mq_buffer), + .msg = quicksave_exit_mq_buffer, + .validCount = 0, + .first = 0 +}; + +void handle_quicksave_actions() { + recomp_handle_quicksave_actions(&quicksave_enter_mq, &quicksave_exit_mq); +} + +extern OSMesgQueue sIrqMgrMsgQueue; // For Main thread +s16 main_irq_message = 0; // dummy value for sending a pointer to the main thread + +#define RECOMP_IRQ_QUICKSAVE 0x2A2 + +void IrqMgr_ThreadEntry(IrqMgr* irqmgr) { + u32 interrupt; + u32 stop; + + interrupt = 0; + stop = 0; + while (stop == 0) { + if (stop) { + ; + } + + osRecvMesg(&irqmgr->irqQueue, (OSMesg*)&interrupt, OS_MESG_BLOCK); + switch (interrupt) { + case 0x29A: + IrqMgr_HandleRetrace(irqmgr); + break; + case 0x29D: + IrqMgr_HandlePreNMI(irqmgr); + break; + case 0x29F: + IrqMgr_HandlePRENMI450(irqmgr); + break; + case 0x2A0: + IrqMgr_HandlePRENMI480(irqmgr); + break; + case 0x2A1: + IrqMgr_HandlePRENMI500(irqmgr); + break; + case RECOMP_IRQ_QUICKSAVE: + handle_quicksave_actions(); + break; + } + } +} + +extern IrqMgr gIrqMgr; + +#define RSP_DONE_MSG 667 +#define RDP_DONE_MSG 668 +#define ENTRY_MSG 670 +#define RDP_AUDIO_CANCEL_MSG 671 +#define RSP_GFX_CANCEL_MSG 672 +#define RECOMP_QUICKSAVE_ACTION 673 // @recomp + + +void Sched_ThreadEntry(void* arg) { + s32 msg = 0; + SchedContext* sched = (SchedContext*)arg; + + while (true) { + osRecvMesg(&sched->interruptQ, (OSMesg*)&msg, OS_MESG_BLOCK); + + // Check if it's a message from another thread or the OS + switch (msg) { + case RDP_AUDIO_CANCEL_MSG: + Sched_HandleAudioCancel(sched); + continue; + + case RSP_GFX_CANCEL_MSG: + Sched_HandleGfxCancel(sched); + continue; + + case ENTRY_MSG: + Sched_HandleEntry(sched); + continue; + + case RSP_DONE_MSG: + Sched_HandleRSPDone(sched); + continue; + + case RDP_DONE_MSG: + Sched_HandleRDPDone(sched); + continue; + + case RECOMP_QUICKSAVE_ACTION: + handle_quicksave_actions(); + continue; + } + + // Check if it's a message from the IrqMgr + switch (((OSScMsg*)msg)->type) { + case OS_SC_RETRACE_MSG: + Sched_HandleRetrace(sched); + continue; + + case OS_SC_PRE_NMI_MSG: + Sched_HandleReset(sched); + continue; + + case OS_SC_NMI_MSG: + Sched_HandleStop(sched); + continue; + } + } +} + +extern PadMgr* sPadMgrInstance; +s16 padmgr_dummy_message = 0; + +extern AudioMgr sAudioMgr; +s16 audiomgr_dummy_message = 0; + +extern OSMesgQueue sDmaMgrMsgQueue; +#define RECOMP_DMAMGR_QUICKSAVE_MESSAGE 1 + +void DmaMgr_ThreadEntry(void* a0) { + OSMesg msg; + DmaRequest* req; + + while (1) { + osRecvMesg(&sDmaMgrMsgQueue, &msg, OS_MESG_BLOCK); + + if (msg == NULL) { + break; + } + + if (msg == (OSMesg)RECOMP_DMAMGR_QUICKSAVE_MESSAGE) { + handle_quicksave_actions(); + continue; + } + + req = (DmaRequest*)msg; + + DmaMgr_ProcessMsg(req); + if (req->notifyQueue) { + osSendMesg(req->notifyQueue, req->notifyMsg, OS_MESG_NOBLOCK); + } + } +} + +extern SchedContext gSchedContext; + +void handle_quicksave_actions_main() { + recomp_handle_quicksave_actions_main(&quicksave_enter_mq, &quicksave_exit_mq); +} + +void wake_threads_for_quicksave_action() { + // Wake up the Main thread + osSendMesg(&sIrqMgrMsgQueue, &main_irq_message, OS_MESG_BLOCK); + // Wake up the IrqMgr thread + osSendMesg(&gIrqMgr.irqQueue, (OSMesg)RECOMP_IRQ_QUICKSAVE, OS_MESG_BLOCK); + // Wake up the PadMgr thread + osSendMesg(&sPadMgrInstance->interruptQueue, &padmgr_dummy_message, OS_MESG_BLOCK); + // Wake up the AudioMgr thread + osSendMesg(&sAudioMgr.interruptQueue, &audiomgr_dummy_message, OS_MESG_BLOCK); + // Wake up the DmaMgr thread + osSendMesg(&sDmaMgrMsgQueue, (OSMesg)RECOMP_DMAMGR_QUICKSAVE_MESSAGE, OS_MESG_BLOCK); + // Wake up the Sched thread + osSendMesg(&gSchedContext.interruptQ, (OSMesg)RECOMP_QUICKSAVE_ACTION, OS_MESG_BLOCK); +} + +#endif diff --git a/patches/syms.ld b/patches/syms.ld index ad8c801..771d9c7 100644 --- a/patches/syms.ld +++ b/patches/syms.ld @@ -5,3 +5,7 @@ recomp_get_item_inputs = 0x81000000; recomp_puts = 0x81000004; recomp_get_camera_inputs = 0x81000008; recomp_exit = 0x8100000C; +recomp_handle_quicksave_actions = 0x81000010; +recomp_handle_quicksave_actions_main = 0x81000014; +osRecvMesg_recomp = 0x81000018; +osSendMesg_recomp = 0x8100001C; diff --git a/src/game/input.cpp b/src/game/input.cpp index e37b24d..a3a8bfa 100644 --- a/src/game/input.cpp +++ b/src/game/input.cpp @@ -210,6 +210,24 @@ void recomp::poll_inputs() { InputState.cur_controllers.push_back(controller); } } + + // Quicksaving is disabled for now and will likely have more limited functionality + // when restored, rather than allowing saving and loading at any point in time. + #if 0 + if (InputState.keys) { + static bool save_was_held = false; + static bool load_was_held = false; + bool save_is_held = InputState.keys[SDL_SCANCODE_F5] != 0; + bool load_is_held = InputState.keys[SDL_SCANCODE_F7] != 0; + if (save_is_held && !save_was_held) { + recomp::quicksave_save(); + } + else if (load_is_held && !load_was_held) { + recomp::quicksave_load(); + } + save_was_held = save_is_held; + } + #endif } bool controller_button_state(int32_t input_id) { diff --git a/src/game/quicksaving.cpp b/src/game/quicksaving.cpp new file mode 100644 index 0000000..08975f4 --- /dev/null +++ b/src/game/quicksaving.cpp @@ -0,0 +1,121 @@ +// Quicksaving is disabled for now + +#if 0 + +#include "recomp_helpers.h" +#include "recomp_input.h" +#include "../ultramodern/ultramodern.hpp" + +enum class QuicksaveAction { + None, + Save, + Load +}; + +std::atomic cur_quicksave_action = QuicksaveAction::None; + +void recomp::quicksave_save() { + cur_quicksave_action.store(QuicksaveAction::Save); +} + +void recomp::quicksave_load() { + cur_quicksave_action.store(QuicksaveAction::Load); +} + +uint8_t saved_rdram[ultramodern::rdram_size]; + +thread_local recomp_context saved_context; + +void save_context(recomp_context* ctx) { + saved_context = *ctx; +} + +void load_context(recomp_context* ctx) { + *ctx = saved_context; + + // Restore the pointer to the odd floats for correctly handling mips3 float mode. + if (ctx->mips3_float_mode) { + // FR = 1, odd single floats point to their own registers + ctx->f_odd = &ctx->f1.u32l; + } + else { + // FR = 0, odd single floats point to the upper half of the previous register + ctx->f_odd = &ctx->f0.u32h; + } +} + +extern "C" void recomp_handle_quicksave_actions(uint8_t* rdram, recomp_context* ctx) { + QuicksaveAction action = cur_quicksave_action.load(); + + if (action != QuicksaveAction::None) { + PTR(OSMesgQueue) quicksave_enter_mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx); + PTR(OSMesgQueue) quicksave_exit_mq = _arg<1, PTR(OSMesgQueue)>(rdram, ctx); + + printf("saving context for thread %d\n", TO_PTR(OSThread, ultramodern::this_thread())->id); + + // Save or load the thread's context as needed based on the action. + if (action == QuicksaveAction::Save) { + save_context(ctx); + } + else if (action == QuicksaveAction::Load) { + load_context(ctx); + } + else { + assert(false); + } + + // Tell the main thread that one of the other permanent threads is ready for performing a quicksave action. + osSendMesg(rdram, quicksave_enter_mq, NULLPTR, OS_MESG_NOBLOCK); + // Wait for the main thread to signal that other permanent threads are safe to continue. + osRecvMesg(rdram, quicksave_exit_mq, NULLPTR, OS_MESG_BLOCK); + } +} + +extern "C" void wake_threads_for_quicksave_action(uint8_t* rdram, recomp_context* ctx); + +extern "C" void recomp_handle_quicksave_actions_main(uint8_t* rdram, recomp_context* ctx) { + QuicksaveAction action = cur_quicksave_action.load(); + + if (action != QuicksaveAction::None) { + PTR(OSMesgQueue) quicksave_enter_mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx); + PTR(OSMesgQueue) quicksave_exit_mq = _arg<1, PTR(OSMesgQueue)>(rdram, ctx); + + wake_threads_for_quicksave_action(rdram, ctx); + + // Wait for all other permanent threads (hence the minus 1) to signal that they're ready for a quicksave action. + for (uint32_t i = 0; i < ultramodern::permanent_thread_count() - 1; i++) { + osRecvMesg(rdram, quicksave_enter_mq, NULLPTR, OS_MESG_BLOCK); + } + + // Allow any temporary threads to complete by lowering this thread's priority to 0. + // TODO this won't cause all temporary threads to complete if any are blocked by permanent threads + // or events like timers. Situations like that will need to be handed on a case-by-case basis for a given game. + if (ultramodern::temporary_thread_count() != 0) { + OSPri old_pri = osGetThreadPri(rdram, NULLPTR); + osSetThreadPri(rdram, NULLPTR, 0); + + osSetThreadPri(rdram, NULLPTR, old_pri); + } + + if (action == QuicksaveAction::Save) { + std::copy(rdram, rdram + ultramodern::rdram_size, saved_rdram); + } + else if (action == QuicksaveAction::Load) { + std::copy(saved_rdram, saved_rdram + ultramodern::rdram_size, rdram); + } + else { + assert(false); + } + + printf("Quicksave action complete\n"); + + cur_quicksave_action.store(QuicksaveAction::None); + + // Tell all other permanent threads that they're good to continue. + for (uint32_t i = 0; i < ultramodern::permanent_thread_count() - 1; i++) { + osSendMesg(rdram, quicksave_exit_mq, NULLPTR, OS_MESG_BLOCK); + } + } +} + +#endif diff --git a/src/recomp/recomp.cpp b/src/recomp/recomp.cpp index ac26006..d7271f1 100644 --- a/src/recomp/recomp.cpp +++ b/src/recomp/recomp.cpp @@ -175,9 +175,9 @@ EXPORT extern "C" void init() { // Load overlays in the first 1MB load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); - // Allocate rdram_buffer (16MB to give room for any extra addressable data used by recomp) - rdram_buffer = std::make_unique(16 * 1024 * 1024); - std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024); + // Allocate rdram_buffer + rdram_buffer = std::make_unique(ultramodern::rdram_size); + std::memset(rdram_buffer.get(), 0, ultramodern::rdram_size); // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000); diff --git a/ultramodern/scheduler.cpp b/ultramodern/scheduler.cpp index 678b5bb..3153da9 100644 --- a/ultramodern/scheduler.cpp +++ b/ultramodern/scheduler.cpp @@ -150,6 +150,9 @@ void scheduler_func() { thread_queue_t running_thread_queue{}; OSThread* cur_running_thread = nullptr; + ultramodern::set_native_thread_name("Scheduler Thread"); + ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Critical); + while (true) { OSThread* old_running_thread = cur_running_thread; scheduler_context.action_count.wait(0); diff --git a/ultramodern/threads.cpp b/ultramodern/threads.cpp index abb3b57..ddba4ff 100644 --- a/ultramodern/threads.cpp +++ b/ultramodern/threads.cpp @@ -114,6 +114,9 @@ void ultramodern::set_native_thread_priority(ThreadPriority pri) { } #endif +std::atomic_int temporary_threads = 0; +std::atomic_int permanent_threads = 0; + static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg) { OSThread *self = TO_PTR(OSThread, self_); debug_printf("[Thread] Thread created: %d\n", self->id); @@ -124,6 +127,14 @@ static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entry ultramodern::set_native_thread_name("Game Thread " + std::to_string(self->id)); ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::High); + // TODO fix these being hardcoded (this is only used for quicksaving) + if ((self->id == 2 && self->priority == 5) || self->id == 13) { // slowly, flashrom + temporary_threads.fetch_add(1); + } + else if (self->id != 1 && self->id != 2) { // ignore idle and fault + permanent_threads.fetch_add(1); + } + // Set initialized to false to indicate that this thread can be started. self->context->initialized.store(true); self->context->initialized.notify_all(); @@ -145,6 +156,19 @@ static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entry // Dispose of this thread after it completes. ultramodern::cleanup_thread(self); + + // TODO fix these being hardcoded (this is only used for quicksaving) + if ((self->id == 2 && self->priority == 5) || self->id == 13) { // slowly, flashrom + temporary_threads.fetch_sub(1); + } +} + +uint32_t ultramodern::permanent_thread_count() { + return permanent_threads.load(); +} + +uint32_t ultramodern::temporary_thread_count() { + return temporary_threads.load(); } extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) { diff --git a/ultramodern/ultramodern.hpp b/ultramodern/ultramodern.hpp index 7cad584..cf36938 100644 --- a/ultramodern/ultramodern.hpp +++ b/ultramodern/ultramodern.hpp @@ -46,6 +46,7 @@ namespace ultramodern { #endif // We need a place in rdram to hold the PI handles, so pick an address in extended rdram +constexpr uint32_t rdram_size = 1024 * 1024 * 16; // 16MB to give extra room for anything custom constexpr int32_t cart_handle = 0x80800000; constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash @@ -68,6 +69,8 @@ void pause_self(RDRAM_ARG1); void halt_self(RDRAM_ARG1); void stop_thread(OSThread *t); void cleanup_thread(OSThread *t); +uint32_t permanent_thread_count(); +uint32_t temporary_thread_count(); enum class ThreadPriority { Low,