Removed use of std::exit and changed recomp runtime to exit normally, added helpers for getting recompiled args and returning values, added example of patch code calling native code

This commit is contained in:
Mr-Wiseguy 2023-11-12 14:47:38 -05:00
parent 398988a961
commit 8188aee2c1
21 changed files with 264 additions and 60 deletions

46
include/recomp_helpers.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef __RECOMP_HELPERS__
#define __RECOMP_HELPERS__
#include "recomp.h"
template<int index, typename T>
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<T, float>) {
if constexpr (index < 2) {
static_assert(index != 1, "Floats in arg 1 not supported");
return ctx->f12.fl;
}
else {
return std::bit_cast<T>(raw_arg);
}
}
else if constexpr (std::is_pointer_v<T>) {
static_assert (!std::is_pointer_v<std::remove_pointer_t<T>>, "Double pointers not supported");
return TO_PTR(std::remove_pointer_t<T>, raw_arg);
}
else if constexpr (std::is_integral_v<T>) {
static_assert(sizeof(T) <= 4, "64-bit args not supported");
return static_cast<T>(raw_arg);
}
else {
// static_assert in else workaround
[] <bool flag = false>() {
static_assert(flag, "Unsupported type");
}();
}
}
template <typename T>
void _return(recomp_context* ctx, T val) {
static_assert(sizeof(T) <= 4 && "Only 32-bit value returns supported currently");
if (std::is_same_v<T, float>) {
ctx->f0.fl = val;
}
else if (std::is_integral_v<T> && sizeof(T) <= 4) {
ctx->r2 = int32_t(val);
}
}
#endif

View File

@ -21,5 +21,6 @@ enum class Menu {
};
void set_current_menu(Menu menu);
void destroy_ui();
#endif

View File

@ -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();

View File

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

View File

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

View File

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

24
patches/input.c Normal file
View File

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

35
patches/input.h Normal file
View File

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

6
patches/patches.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef __PATCHES_H__
#define __PATCHES_H__
#include "global.h"
#endif

View File

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

View File

@ -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();
}

View File

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

View File

@ -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);
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) {

View File

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

View File

@ -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() {

View File

@ -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<s32>(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<s32>(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;
}
}

View File

@ -99,7 +99,7 @@ std::vector<GameControllerButtonMapping> controller_button_map{
std::vector<SDL_JoystickID> 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);
}
}

View File

@ -122,6 +122,28 @@ extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
std::unique_ptr<uint8_t[]> 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<uint8_t[]> patches_data = std::make_unique<uint8_t[]>(patches_data_size);
patches_data_file.read(reinterpret_cast<char*>(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();
}

View File

@ -130,3 +130,7 @@ void RT64UpdateScreen(uint32_t vi_origin) {
void RT64ChangeWindow() {
ChangeWindow();
}
void RT64Shutdown() {
PluginShutdown();
}

View File

@ -587,6 +587,10 @@ struct {
Rml::Context* context;
std::unique_ptr<Rml::EventListenerInstancer> event_listener_instancer;
void unload() {
render_interface.reset();
}
void swap_document(Menu menu) {
if (current_document != nullptr) {
current_document->Hide();
@ -752,3 +756,8 @@ void set_rt64_hooks() {
void set_current_menu(Menu menu) {
open_menu.store(menu);
}
void destroy_ui() {
Rml::Shutdown();
UIContext.rml.unload();
}

View File

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