Prototype quicksave functionality, disabled for now

This commit is contained in:
Mr-Wiseguy 2024-01-13 01:39:08 -05:00
parent a25f967094
commit ecfe2381b0
12 changed files with 420 additions and 3 deletions

View File

@ -63,6 +63,8 @@ target_sources(PatchesLib PRIVATE
${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c ${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 # Build patches elf
add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/patches/patches.elf add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/patches/patches.elf
COMMAND make COMMAND make
@ -119,6 +121,7 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/game/input.cpp ${CMAKE_SOURCE_DIR}/src/game/input.cpp
${CMAKE_SOURCE_DIR}/src/game/controls.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_renderer.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp

View File

@ -50,6 +50,10 @@ namespace recomp {
void handle_events(); void handle_events();
bool game_input_disabled(); bool game_input_disabled();
// TODO move these
void quicksave_save();
void quicksave_load();
} }
#endif #endif

View File

@ -48,6 +48,8 @@ DECLARE_FUNC(void, recomp_get_camera_inputs, float* x_out, float* y_out);
// TODO move these // TODO move these
DECLARE_FUNC(void, recomp_puts, const char* data, u32 size); DECLARE_FUNC(void, recomp_puts, const char* data, u32 size);
DECLARE_FUNC(void, recomp_exit); 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 #ifdef __cplusplus
} }

View File

@ -1,6 +1,9 @@
#ifndef __PATCHES_H__ #ifndef __PATCHES_H__
#define __PATCHES_H__ #define __PATCHES_H__
// TODO fix renaming symbols in patch recompilation
#define osRecvMesg osRecvMesg_recomp
#define osSendMesg osSendMesg_recomp
#include "global.h" #include "global.h"
#include "rt64_extended_gbi.h" #include "rt64_extended_gbi.h"

232
patches/quicksave_patches.c Normal file
View File

@ -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

View File

@ -5,3 +5,7 @@ recomp_get_item_inputs = 0x81000000;
recomp_puts = 0x81000004; recomp_puts = 0x81000004;
recomp_get_camera_inputs = 0x81000008; recomp_get_camera_inputs = 0x81000008;
recomp_exit = 0x8100000C; recomp_exit = 0x8100000C;
recomp_handle_quicksave_actions = 0x81000010;
recomp_handle_quicksave_actions_main = 0x81000014;
osRecvMesg_recomp = 0x81000018;
osSendMesg_recomp = 0x8100001C;

View File

@ -210,6 +210,24 @@ void recomp::poll_inputs() {
InputState.cur_controllers.push_back(controller); 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) { bool controller_button_state(int32_t input_id) {

121
src/game/quicksaving.cpp Normal file
View File

@ -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<QuicksaveAction> 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

View File

@ -175,9 +175,9 @@ EXPORT extern "C" void init() {
// Load overlays in the first 1MB // Load overlays in the first 1MB
load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024);
// Allocate rdram_buffer (16MB to give room for any extra addressable data used by recomp) // Allocate rdram_buffer
rdram_buffer = std::make_unique<uint8_t[]>(16 * 1024 * 1024); rdram_buffer = std::make_unique<uint8_t[]>(ultramodern::rdram_size);
std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024); std::memset(rdram_buffer.get(), 0, ultramodern::rdram_size);
// Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000)
do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000); do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000);

View File

@ -150,6 +150,9 @@ void scheduler_func() {
thread_queue_t running_thread_queue{}; thread_queue_t running_thread_queue{};
OSThread* cur_running_thread = nullptr; OSThread* cur_running_thread = nullptr;
ultramodern::set_native_thread_name("Scheduler Thread");
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Critical);
while (true) { while (true) {
OSThread* old_running_thread = cur_running_thread; OSThread* old_running_thread = cur_running_thread;
scheduler_context.action_count.wait(0); scheduler_context.action_count.wait(0);

View File

@ -114,6 +114,9 @@ void ultramodern::set_native_thread_priority(ThreadPriority pri) {
} }
#endif #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) { static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg) {
OSThread *self = TO_PTR(OSThread, self_); OSThread *self = TO_PTR(OSThread, self_);
debug_printf("[Thread] Thread created: %d\n", self->id); 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_name("Game Thread " + std::to_string(self->id));
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::High); 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. // Set initialized to false to indicate that this thread can be started.
self->context->initialized.store(true); self->context->initialized.store(true);
self->context->initialized.notify_all(); 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. // Dispose of this thread after it completes.
ultramodern::cleanup_thread(self); 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_) { extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) {

View File

@ -46,6 +46,7 @@ namespace ultramodern {
#endif #endif
// We need a place in rdram to hold the PI handles, so pick an address in extended rdram // 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 cart_handle = 0x80800000;
constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); 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 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 halt_self(RDRAM_ARG1);
void stop_thread(OSThread *t); void stop_thread(OSThread *t);
void cleanup_thread(OSThread *t); void cleanup_thread(OSThread *t);
uint32_t permanent_thread_count();
uint32_t temporary_thread_count();
enum class ThreadPriority { enum class ThreadPriority {
Low, Low,