mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2024-11-25 14:16:58 +01:00
Migrate to N64ModernRuntime (#354)
This commit is contained in:
parent
6e9ee3498b
commit
bec699f0bd
4
.github/workflows/validate.yml
vendored
4
.github/workflows/validate.yml
vendored
@ -56,7 +56,7 @@ jobs:
|
||||
run: |
|
||||
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
||||
cd N64RecompSource
|
||||
git checkout 6eb7d5bd3ee7f0b79f3fd7adbe931dccbacf7e1b
|
||||
git checkout 8dfed04919b7bfdd0fd34ff049eed7020dea0d71
|
||||
git submodule update --init --recursive
|
||||
|
||||
# enable ccache
|
||||
@ -130,7 +130,7 @@ jobs:
|
||||
run: |
|
||||
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
||||
cd N64RecompSource
|
||||
git checkout 6eb7d5bd3ee7f0b79f3fd7adbe931dccbacf7e1b
|
||||
git checkout 8dfed04919b7bfdd0fd34ff049eed7020dea0d71
|
||||
git submodule update --init --recursive
|
||||
|
||||
# enable ccache
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# VSCode file settings
|
||||
.vscode/settings.json
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
|
||||
# Input elf and rom files
|
||||
*.elf
|
||||
@ -56,3 +57,4 @@ node_modules/
|
||||
|
||||
# Recompiler Linux binary
|
||||
N64Recomp
|
||||
.DS_Store
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -16,3 +16,6 @@
|
||||
[submodule "lib/sse2neon"]
|
||||
path = lib/sse2neon
|
||||
url = https://github.com/DLTcollab/sse2neon.git
|
||||
[submodule "lib/N64ModernRuntime"]
|
||||
path = lib/N64ModernRuntime
|
||||
url = git@github.com:N64Recomp/N64ModernRuntime.git
|
||||
|
@ -13,10 +13,6 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
@ -35,6 +31,8 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/lib/lunasvg)
|
||||
SET(ENABLE_SVG_PLUGIN ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi)
|
||||
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime)
|
||||
|
||||
target_include_directories(rt64 PRIVATE ${CMAKE_BINARY_DIR}/rt64/src)
|
||||
|
||||
# RecompiledFuncs - Library containing the primary recompiler output
|
||||
@ -48,6 +46,8 @@ target_compile_options(RecompiledFuncs PRIVATE
|
||||
|
||||
target_include_directories(RecompiledFuncs PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/ultramodern/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/librecomp/include
|
||||
)
|
||||
|
||||
file(GLOB FUNC_C_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.c)
|
||||
@ -65,6 +65,8 @@ target_compile_options(PatchesLib PRIVATE
|
||||
|
||||
target_include_directories(PatchesLib PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/ultramodern/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/librecomp/include
|
||||
)
|
||||
|
||||
target_sources(PatchesLib PRIVATE
|
||||
@ -92,8 +94,8 @@ add_custom_command(OUTPUT
|
||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c
|
||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/recomp_overlays.inl
|
||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/funcs.h
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp
|
||||
COMMAND ./N64Recomp patches.toml && ${CMAKE_COMMAND} -E touch ${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp
|
||||
# TODO: Look into why modifying patches requires two builds to take
|
||||
COMMAND ./N64Recomp patches.toml
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.bin
|
||||
)
|
||||
@ -112,39 +114,9 @@ if (EXISTS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin)
|
||||
endif()
|
||||
|
||||
set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/audio.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/events.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/mesgqueue.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/misc_ultra.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/port_main.c
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/scheduling.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/threadqueue.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/task_win32.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/threads.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/timer.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/ultrainit.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/rt64_layer.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/ai.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/cont.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/dp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/eep.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/euc-jp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/files.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/flash.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/math_routines.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/overlays.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/pak.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/pi.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/ultra_stubs.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/ultra_translation.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/print.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/recomp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/sp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/vi.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/main/main.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/register_patches.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/game/input.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/controls.cpp
|
||||
@ -238,7 +210,7 @@ if (WIN32)
|
||||
target_sources(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/icons/app.rc)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(X11 REQUIRED)
|
||||
|
||||
@ -282,6 +254,8 @@ target_link_libraries(Zelda64Recompiled PRIVATE
|
||||
PatchesLib
|
||||
RecompiledFuncs
|
||||
SDL2
|
||||
librecomp
|
||||
ultramodern
|
||||
rt64
|
||||
RmlCore
|
||||
RmlDebugger
|
||||
|
9
include/ovl_patches.hpp
Normal file
9
include/ovl_patches.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef __OVL_PATCHES_HPP__
|
||||
#define __OVL_PATCHES_HPP__
|
||||
|
||||
namespace zelda64 {
|
||||
void register_overlays();
|
||||
void register_patches();
|
||||
}
|
||||
|
||||
#endif
|
327
include/recomp.h
327
include/recomp.h
@ -1,327 +0,0 @@
|
||||
#ifndef __RECOMP_H__
|
||||
#define __RECOMP_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <setjmp.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#if 0 // treat GPRs as 32-bit, should be better codegen
|
||||
typedef uint32_t gpr;
|
||||
|
||||
#define SIGNED(val) \
|
||||
((int32_t)(val))
|
||||
#else
|
||||
typedef uint64_t gpr;
|
||||
|
||||
#define SIGNED(val) \
|
||||
((int64_t)(val))
|
||||
#endif
|
||||
|
||||
#define ADD32(a, b) \
|
||||
((gpr)(int32_t)((a) + (b)))
|
||||
|
||||
#define SUB32(a, b) \
|
||||
((gpr)(int32_t)((a) - (b)))
|
||||
|
||||
#define MEM_W(offset, reg) \
|
||||
(*(int32_t*)(rdram + ((((reg) + (offset))) - 0xFFFFFFFF80000000)))
|
||||
//(*(int32_t*)(rdram + ((((reg) + (offset))) & 0x3FFFFFF)))
|
||||
|
||||
#define MEM_H(offset, reg) \
|
||||
(*(int16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000)))
|
||||
//(*(int16_t*)(rdram + ((((reg) + (offset)) ^ 2) & 0x3FFFFFF)))
|
||||
|
||||
#define MEM_B(offset, reg) \
|
||||
(*(int8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000)))
|
||||
//(*(int8_t*)(rdram + ((((reg) + (offset)) ^ 3) & 0x3FFFFFF)))
|
||||
|
||||
#define MEM_HU(offset, reg) \
|
||||
(*(uint16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000)))
|
||||
//(*(uint16_t*)(rdram + ((((reg) + (offset)) ^ 2) & 0x3FFFFFF)))
|
||||
|
||||
#define MEM_BU(offset, reg) \
|
||||
(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000)))
|
||||
//(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) & 0x3FFFFFF)))
|
||||
|
||||
#define SD(val, offset, reg) { \
|
||||
*(uint32_t*)(rdram + ((((reg) + (offset) + 4)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 0); \
|
||||
*(uint32_t*)(rdram + ((((reg) + (offset) + 0)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 32); \
|
||||
}
|
||||
|
||||
//#define SD(val, offset, reg) { \
|
||||
// *(uint32_t*)(rdram + ((((reg) + (offset) + 4)) & 0x3FFFFFF)) = (uint32_t)((val) >> 32); \
|
||||
// *(uint32_t*)(rdram + ((((reg) + (offset) + 0)) & 0x3FFFFFF)) = (uint32_t)((val) >> 0); \
|
||||
//}
|
||||
|
||||
static inline uint64_t load_doubleword(uint8_t* rdram, gpr reg, gpr offset) {
|
||||
uint64_t ret = 0;
|
||||
uint64_t lo = (uint64_t)(uint32_t)MEM_W(reg, offset + 4);
|
||||
uint64_t hi = (uint64_t)(uint32_t)MEM_W(reg, offset + 0);
|
||||
ret = (lo << 0) | (hi << 32);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define LD(offset, reg) \
|
||||
load_doubleword(rdram, offset, reg)
|
||||
|
||||
static inline gpr do_lwl(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Load the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t loaded_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the existing value and shift the loaded value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
gpr masked_value = initial_value & ~(0xFFFFFFFFu << (misalignment * 8));
|
||||
loaded_value <<= (misalignment * 8);
|
||||
|
||||
// Cast to int32_t to sign extend first
|
||||
return (gpr)(int32_t)(masked_value | loaded_value);
|
||||
}
|
||||
|
||||
static inline gpr do_lwr(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Load the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t loaded_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the existing value and shift the loaded value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
gpr masked_value = initial_value & ~(0xFFFFFFFFu >> (24 - misalignment * 8));
|
||||
loaded_value >>= (24 - misalignment * 8);
|
||||
|
||||
// Cast to int32_t to sign extend first
|
||||
return (gpr)(int32_t)(masked_value | loaded_value);
|
||||
}
|
||||
|
||||
static inline void do_swl(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Get the initial value of the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t initial_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the initial value and shift the input value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu >> (misalignment * 8));
|
||||
uint32_t shifted_input_value = ((uint32_t)val) >> (misalignment * 8);
|
||||
MEM_W(0, word_address) = masked_initial_value | shifted_input_value;
|
||||
}
|
||||
|
||||
static inline void do_swr(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Get the initial value of the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t initial_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the initial value and shift the input value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu << (24 - misalignment * 8));
|
||||
uint32_t shifted_input_value = ((uint32_t)val) << (24 - misalignment * 8);
|
||||
MEM_W(0, word_address) = masked_initial_value | shifted_input_value;
|
||||
}
|
||||
|
||||
#define S32(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define U32(val) \
|
||||
((uint32_t)(val))
|
||||
|
||||
#define S64(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define U64(val) \
|
||||
((uint64_t)(val))
|
||||
|
||||
#define MUL_S(val1, val2) \
|
||||
((val1) * (val2))
|
||||
|
||||
#define MUL_D(val1, val2) \
|
||||
((val1) * (val2))
|
||||
|
||||
#define DIV_S(val1, val2) \
|
||||
((val1) / (val2))
|
||||
|
||||
#define DIV_D(val1, val2) \
|
||||
((val1) / (val2))
|
||||
|
||||
#define CVT_S_W(val) \
|
||||
((float)((int32_t)(val)))
|
||||
|
||||
#define CVT_D_W(val) \
|
||||
((double)((int32_t)(val)))
|
||||
|
||||
#define CVT_D_S(val) \
|
||||
((double)(val))
|
||||
|
||||
#define CVT_S_D(val) \
|
||||
((float)(val))
|
||||
|
||||
#define TRUNC_W_S(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define TRUNC_W_D(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define TRUNC_L_S(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define TRUNC_L_D(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define DEFAULT_ROUNDING_MODE 0
|
||||
|
||||
static inline int32_t do_cvt_w_s(float val, unsigned int rounding_mode) {
|
||||
switch (rounding_mode) {
|
||||
case 0: // round to nearest value
|
||||
return (int32_t)lroundf(val);
|
||||
case 1: // round to zero (truncate)
|
||||
return (int32_t)val;
|
||||
case 2: // round to positive infinity (ceil)
|
||||
return (int32_t)ceilf(val);
|
||||
case 3: // round to negative infinity (floor)
|
||||
return (int32_t)floorf(val);
|
||||
}
|
||||
assert(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CVT_W_S(val) \
|
||||
do_cvt_w_s(val, rounding_mode)
|
||||
|
||||
static inline int32_t do_cvt_w_d(double val, unsigned int rounding_mode) {
|
||||
switch (rounding_mode) {
|
||||
case 0: // round to nearest value
|
||||
return (int32_t)lround(val);
|
||||
case 1: // round to zero (truncate)
|
||||
return (int32_t)val;
|
||||
case 2: // round to positive infinity (ceil)
|
||||
return (int32_t)ceil(val);
|
||||
case 3: // round to negative infinity (floor)
|
||||
return (int32_t)floor(val);
|
||||
}
|
||||
assert(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CVT_W_D(val) \
|
||||
do_cvt_w_d(val, rounding_mode)
|
||||
|
||||
#define NAN_CHECK(val) \
|
||||
assert(val == val)
|
||||
|
||||
//#define NAN_CHECK(val)
|
||||
|
||||
typedef union {
|
||||
double d;
|
||||
struct {
|
||||
float fl;
|
||||
float fh;
|
||||
};
|
||||
struct {
|
||||
uint32_t u32l;
|
||||
uint32_t u32h;
|
||||
};
|
||||
uint64_t u64;
|
||||
} fpr;
|
||||
|
||||
typedef struct {
|
||||
gpr r0, r1, r2, r3, r4, r5, r6, r7,
|
||||
r8, r9, r10, r11, r12, r13, r14, r15,
|
||||
r16, r17, r18, r19, r20, r21, r22, r23,
|
||||
r24, r25, r26, r27, r28, r29, r30, r31;
|
||||
fpr f0, f1, f2, f3, f4, f5, f6, f7,
|
||||
f8, f9, f10, f11, f12, f13, f14, f15,
|
||||
f16, f17, f18, f19, f20, f21, f22, f23,
|
||||
f24, f25, f26, f27, f28, f29, f30, f31;
|
||||
uint64_t hi, lo;
|
||||
uint32_t* f_odd;
|
||||
uint32_t status_reg;
|
||||
uint8_t mips3_float_mode;
|
||||
} recomp_context;
|
||||
|
||||
// Checks if the target is an even float register or that mips3 float mode is enabled
|
||||
#define CHECK_FR(ctx, idx) \
|
||||
assert(((idx) & 1) == 0 || (ctx)->mips3_float_mode)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void cop0_status_write(recomp_context* ctx, gpr value);
|
||||
gpr cop0_status_read(recomp_context* ctx);
|
||||
void switch_error(const char* func, uint32_t vram, uint32_t jtbl);
|
||||
void do_break(uint32_t vram);
|
||||
|
||||
typedef void (recomp_func_t)(uint8_t* rdram, recomp_context* ctx);
|
||||
|
||||
recomp_func_t* get_function(int32_t vram);
|
||||
|
||||
#define LOOKUP_FUNC(val) \
|
||||
get_function((int32_t)(val))
|
||||
|
||||
extern int32_t section_addresses[];
|
||||
|
||||
#define LO16(x) \
|
||||
((x) & 0xFFFF)
|
||||
|
||||
#define HI16(x) \
|
||||
(((x) >> 16) + (((x) >> 15) & 1))
|
||||
|
||||
#define RELOC_HI16(section_index, offset) \
|
||||
HI16(section_addresses[section_index] + (offset))
|
||||
|
||||
#define RELOC_LO16(section_index, offset) \
|
||||
LO16(section_addresses[section_index] + (offset))
|
||||
|
||||
// For Banjo-Tooie
|
||||
void recomp_syscall_handler(uint8_t* rdram, recomp_context* ctx, int32_t instruction_vram);
|
||||
|
||||
// For the Mario Party games (not working)
|
||||
//// This has to be in this file so it can be inlined
|
||||
//struct jmp_buf_storage {
|
||||
// jmp_buf buffer;
|
||||
//};
|
||||
//
|
||||
//struct RecompJmpBuf {
|
||||
// int32_t owner;
|
||||
// struct jmp_buf_storage* storage;
|
||||
// uint64_t magic;
|
||||
//};
|
||||
//
|
||||
//// Randomly generated constant
|
||||
//#define SETJMP_MAGIC 0xe17afdfa939a437bu
|
||||
//
|
||||
//int32_t osGetThreadEx(void);
|
||||
//
|
||||
//#define setjmp_recomp(rdram, ctx) { \
|
||||
// struct RecompJmpBuf* buf = (struct RecompJmpBuf*)(&rdram[(uint64_t)ctx->r4 - 0xFFFFFFFF80000000]); \
|
||||
// \
|
||||
// /* Check if this jump buffer was previously set up */ \
|
||||
// if (buf->magic == SETJMP_MAGIC) { \
|
||||
// /* If so, free the old jmp_buf */ \
|
||||
// free(buf->storage); \
|
||||
// } \
|
||||
// \
|
||||
// buf->magic = SETJMP_MAGIC; \
|
||||
// buf->owner = osGetThreadEx(); \
|
||||
// buf->storage = (struct jmp_buf_storage*)calloc(1, sizeof(struct jmp_buf_storage)); \
|
||||
// ctx->r2 = setjmp(buf->storage->buffer); \
|
||||
//}
|
||||
|
||||
void pause_self(uint8_t *rdram);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,54 +0,0 @@
|
||||
#ifndef __RECOMP_CONFIG_H__
|
||||
#define __RECOMP_CONFIG_H__
|
||||
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include "../ultramodern/config.hpp"
|
||||
|
||||
namespace recomp {
|
||||
constexpr std::u8string_view program_id = u8"Zelda64Recompiled";
|
||||
constexpr std::u8string_view mm_game_id = u8"mm.n64.us.1.0";
|
||||
constexpr std::string_view program_name = "Zelda 64: Recompiled";
|
||||
|
||||
void load_config();
|
||||
void save_config();
|
||||
|
||||
void reset_input_bindings();
|
||||
void reset_cont_input_bindings();
|
||||
void reset_kb_input_bindings();
|
||||
|
||||
std::filesystem::path get_app_folder_path();
|
||||
|
||||
bool get_debug_mode_enabled();
|
||||
void set_debug_mode_enabled(bool enabled);
|
||||
|
||||
enum class AutosaveMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
enum class AnalogCamMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::AutosaveMode, {
|
||||
{recomp::AutosaveMode::On, "On"},
|
||||
{recomp::AutosaveMode::Off, "Off"}
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::AnalogCamMode, {
|
||||
{recomp::AnalogCamMode::On, "On"},
|
||||
{recomp::AnalogCamMode::Off, "Off"}
|
||||
});
|
||||
|
||||
AutosaveMode get_autosave_mode();
|
||||
void set_autosave_mode(AutosaveMode mode);
|
||||
|
||||
AnalogCamMode get_analog_cam_mode();
|
||||
void set_analog_cam_mode(AnalogCamMode mode);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,40 +0,0 @@
|
||||
#ifndef __RECOMP_GAME__
|
||||
#define __RECOMP_GAME__
|
||||
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "rt64_layer.h"
|
||||
|
||||
namespace recomp {
|
||||
enum class Game {
|
||||
OoT,
|
||||
MM,
|
||||
None,
|
||||
Quit
|
||||
};
|
||||
enum class RomValidationError {
|
||||
Good,
|
||||
FailedToOpen,
|
||||
NotARom,
|
||||
IncorrectRom,
|
||||
NotYet,
|
||||
IncorrectVersion,
|
||||
OtherError
|
||||
};
|
||||
void check_all_stored_roms();
|
||||
bool load_stored_rom(Game game);
|
||||
RomValidationError select_rom(const std::filesystem::path& rom_path, Game game);
|
||||
bool is_rom_valid(Game game);
|
||||
bool is_rom_loaded();
|
||||
void set_rom_contents(std::vector<uint8_t>&& new_rom);
|
||||
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes);
|
||||
void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr);
|
||||
void start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks);
|
||||
void start_game(Game game);
|
||||
void message_box(const char* message);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,50 +0,0 @@
|
||||
#ifndef __RECOMP_HELPERS__
|
||||
#define __RECOMP_HELPERS__
|
||||
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultra64.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 {
|
||||
// static_assert in else workaround
|
||||
[] <bool flag = false>() {
|
||||
static_assert(flag, "Floats in a2/a3 not supported");
|
||||
}();
|
||||
}
|
||||
}
|
||||
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
|
@ -14,6 +14,7 @@
|
||||
namespace recomp {
|
||||
// x-macros to build input enums and arrays.
|
||||
// First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name.
|
||||
// TODO refactor this to allow projects to rename these, or get rid of the readable name and leave that up to individual projects to map.
|
||||
#define DEFINE_N64_BUTTON_INPUTS() \
|
||||
DEFINE_INPUT(A, 0x8000, "Action") \
|
||||
DEFINE_INPUT(B, 0x4000, "Attack/Cancel") \
|
||||
@ -168,20 +169,6 @@ namespace recomp {
|
||||
void apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out);
|
||||
void set_right_analog_suppressed(bool suppressed);
|
||||
|
||||
enum class TargetingMode {
|
||||
Switch,
|
||||
Hold,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::TargetingMode, {
|
||||
{recomp::TargetingMode::Switch, "Switch"},
|
||||
{recomp::TargetingMode::Hold, "Hold"}
|
||||
});
|
||||
|
||||
TargetingMode get_targeting_mode();
|
||||
void set_targeting_mode(TargetingMode mode);
|
||||
|
||||
enum class BackgroundInputMode {
|
||||
On,
|
||||
Off,
|
||||
@ -196,35 +183,8 @@ namespace recomp {
|
||||
BackgroundInputMode get_background_input_mode();
|
||||
void set_background_input_mode(BackgroundInputMode mode);
|
||||
|
||||
enum class CameraInvertMode {
|
||||
InvertNone,
|
||||
InvertX,
|
||||
InvertY,
|
||||
InvertBoth,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::CameraInvertMode, {
|
||||
{recomp::CameraInvertMode::InvertNone, "InvertNone"},
|
||||
{recomp::CameraInvertMode::InvertX, "InvertX"},
|
||||
{recomp::CameraInvertMode::InvertY, "InvertY"},
|
||||
{recomp::CameraInvertMode::InvertBoth, "InvertBoth"}
|
||||
});
|
||||
|
||||
CameraInvertMode get_camera_invert_mode();
|
||||
void set_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
CameraInvertMode get_analog_camera_invert_mode();
|
||||
void set_analog_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
bool game_input_disabled();
|
||||
bool all_input_disabled();
|
||||
|
||||
// TODO move these
|
||||
void quicksave_save();
|
||||
void quicksave_load();
|
||||
|
||||
void open_quit_game_prompt();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,10 +0,0 @@
|
||||
#ifndef __RECOMP_OVERLAYS_H__
|
||||
#define __RECOMP_OVERLAYS_H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size);
|
||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
|
||||
void init_overlays();
|
||||
|
||||
#endif
|
@ -14,7 +14,7 @@ namespace Rml {
|
||||
class Event;
|
||||
}
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
class UiEventListenerInstancer;
|
||||
|
||||
class MenuController {
|
||||
@ -118,6 +118,8 @@ namespace recomp {
|
||||
bool get_cont_active(void);
|
||||
void set_cont_active(bool active);
|
||||
void activate_mouse();
|
||||
|
||||
void message_box(const char* msg);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,94 +0,0 @@
|
||||
#ifndef __RSP_H__
|
||||
#define __RSP_H__
|
||||
|
||||
#include "rsp_vu.h"
|
||||
#include "recomp.h"
|
||||
#include <cstdio>
|
||||
|
||||
enum class RspExitReason {
|
||||
Invalid,
|
||||
Broke,
|
||||
ImemOverrun,
|
||||
UnhandledJumpTarget,
|
||||
Unsupported
|
||||
};
|
||||
|
||||
extern uint8_t dmem[];
|
||||
extern uint16_t rspReciprocals[512];
|
||||
extern uint16_t rspInverseSquareRoots[512];
|
||||
|
||||
#define RSP_MEM_B(offset, addr) \
|
||||
(*reinterpret_cast<int8_t*>(dmem + (0xFFF & (((offset) + (addr)) ^ 3))))
|
||||
|
||||
#define RSP_MEM_BU(offset, addr) \
|
||||
(*reinterpret_cast<uint8_t*>(dmem + (0xFFF & (((offset) + (addr)) ^ 3))))
|
||||
|
||||
static inline uint32_t RSP_MEM_W_LOAD(uint32_t offset, uint32_t addr) {
|
||||
uint32_t out;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
reinterpret_cast<uint8_t*>(&out)[i ^ 3] = RSP_MEM_BU(offset + i, addr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline void RSP_MEM_W_STORE(uint32_t offset, uint32_t addr, uint32_t val) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
RSP_MEM_BU(offset + i, addr) = reinterpret_cast<uint8_t*>(&val)[i ^ 3];
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t RSP_MEM_HU_LOAD(uint32_t offset, uint32_t addr) {
|
||||
uint16_t out;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
reinterpret_cast<uint8_t*>(&out)[(i + 2) ^ 3] = RSP_MEM_BU(offset + i, addr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline uint32_t RSP_MEM_H_LOAD(uint32_t offset, uint32_t addr) {
|
||||
int16_t out;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
reinterpret_cast<uint8_t*>(&out)[(i + 2) ^ 3] = RSP_MEM_BU(offset + i, addr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline void RSP_MEM_H_STORE(uint32_t offset, uint32_t addr, uint32_t val) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
RSP_MEM_BU(offset + i, addr) = reinterpret_cast<uint8_t*>(&val)[(i + 2) ^ 3];
|
||||
}
|
||||
}
|
||||
|
||||
#define RSP_ADD32(a, b) \
|
||||
((int32_t)((a) + (b)))
|
||||
|
||||
#define RSP_SUB32(a, b) \
|
||||
((int32_t)((a) - (b)))
|
||||
|
||||
#define RSP_SIGNED(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define SET_DMA_DMEM(dmem_addr) dma_dmem_address = (dmem_addr)
|
||||
#define SET_DMA_DRAM(dram_addr) dma_dram_address = (dram_addr)
|
||||
#define DO_DMA_READ(rd_len) dma_rdram_to_dmem(rdram, dma_dmem_address, dma_dram_address, (rd_len))
|
||||
#define DO_DMA_WRITE(wr_len) dma_dmem_to_rdram(rdram, dma_dmem_address, dma_dram_address, (wr_len))
|
||||
|
||||
static inline void dma_rdram_to_dmem(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t rd_len) {
|
||||
rd_len += 1; // Read length is inclusive
|
||||
dram_addr &= 0xFFFFF8;
|
||||
assert(dmem_addr + rd_len <= 0x1000);
|
||||
for (uint32_t i = 0; i < rd_len; i++) {
|
||||
RSP_MEM_B(i, dmem_addr) = MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dma_dmem_to_rdram(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t wr_len) {
|
||||
wr_len += 1; // Write length is inclusive
|
||||
dram_addr &= 0xFFFFF8;
|
||||
assert(dmem_addr + wr_len <= 0x1000);
|
||||
for (uint32_t i = 0; i < wr_len; i++) {
|
||||
MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)) = RSP_MEM_B(i, dmem_addr);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
203
include/rsp_vu.h
203
include/rsp_vu.h
@ -1,203 +0,0 @@
|
||||
// This file is modified from the Ares N64 emulator core. Ares can
|
||||
// be found at https://github.com/ares-emulator/ares. The original license
|
||||
// for this portion of Ares is as follows:
|
||||
// ----------------------------------------------------------------------
|
||||
// ares
|
||||
//
|
||||
// Copyright(c) 2004 - 2021 ares team, Near et al
|
||||
//
|
||||
// Permission to use, copy, modify, and /or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright noticeand this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
// ----------------------------------------------------------------------
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define ARCHITECTURE_SUPPORTS_SSE4_1 1
|
||||
#include <nmmintrin.h>
|
||||
using v128 = __m128i;
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define ARCHITECTURE_SUPPORTS_SSE4_1 1
|
||||
#include "sse2neon.h"
|
||||
using v128 = __m128i;
|
||||
#endif
|
||||
|
||||
namespace Accuracy {
|
||||
namespace RSP {
|
||||
#if ARCHITECTURE_SUPPORTS_SSE4_1
|
||||
constexpr bool SISD = false;
|
||||
constexpr bool SIMD = true;
|
||||
#else
|
||||
constexpr bool SISD = true;
|
||||
constexpr bool SIMD = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
using u8 = uint8_t;
|
||||
using s8 = int8_t;
|
||||
using u16 = uint16_t;
|
||||
using s16 = int16_t;
|
||||
using u32 = uint32_t;
|
||||
using s32 = int32_t;
|
||||
using u64 = uint64_t;
|
||||
using s64 = int64_t;
|
||||
using uint128_t = uint64_t[2];
|
||||
|
||||
template<u32 bits> inline auto sclamp(s64 x) -> s64 {
|
||||
enum : s64 { b = 1ull << (bits - 1), m = b - 1 };
|
||||
return (x > m) ? m : (x < -b) ? -b : x;
|
||||
}
|
||||
|
||||
template<u32 bits> inline auto sclip(s64 x) -> s64 {
|
||||
enum : u64 { b = 1ull << (bits - 1), m = b * 2 - 1 };
|
||||
return ((x & m) ^ b) - b;
|
||||
}
|
||||
|
||||
struct RSP {
|
||||
using r32 = uint32_t;
|
||||
using cr32 = const r32;
|
||||
|
||||
union r128 {
|
||||
struct { uint64_t u128[2]; };
|
||||
#if ARCHITECTURE_SUPPORTS_SSE4_1
|
||||
struct { __m128i v128; };
|
||||
|
||||
operator __m128i() const { return v128; }
|
||||
auto operator=(__m128i value) { v128 = value; }
|
||||
#endif
|
||||
|
||||
auto byte(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; }
|
||||
auto byte(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; }
|
||||
|
||||
auto element(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; }
|
||||
auto element(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; }
|
||||
|
||||
auto u8(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; }
|
||||
auto u8(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; }
|
||||
|
||||
auto s16(u32 index) -> int16_t& { return ((int16_t*)&u128)[7 - index]; }
|
||||
auto s16(u32 index) const -> int16_t { return ((int16_t*)&u128)[7 - index]; }
|
||||
|
||||
auto u16(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; }
|
||||
auto u16(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; }
|
||||
|
||||
//VCx registers
|
||||
auto get(u32 index) const -> bool { return u16(index) != 0; }
|
||||
auto set(u32 index, bool value) -> bool { return u16(index) = 0 - value, value; }
|
||||
|
||||
//vu-registers.cpp
|
||||
inline auto operator()(u32 index) const -> r128;
|
||||
};
|
||||
using cr128 = const r128;
|
||||
|
||||
struct VU {
|
||||
r128 r[32];
|
||||
r128 acch, accm, accl;
|
||||
r128 vcoh, vcol; //16-bit little endian
|
||||
r128 vcch, vccl; //16-bit little endian
|
||||
r128 vce; // 8-bit little endian
|
||||
s16 divin;
|
||||
s16 divout;
|
||||
bool divdp;
|
||||
} vpu;
|
||||
|
||||
static constexpr r128 zero{0};
|
||||
static constexpr r128 invert{(uint64_t)-1, (uint64_t)-1};
|
||||
|
||||
inline auto accumulatorGet(u32 index) const -> u64;
|
||||
inline auto accumulatorSet(u32 index, u64 value) -> void;
|
||||
inline auto accumulatorSaturate(u32 index, bool slice, u16 negative, u16 positive) const -> u16;
|
||||
|
||||
inline auto CFC2(r32& rt, u8 rd) -> void;
|
||||
inline auto CTC2(cr32& rt, u8 rd) -> void;
|
||||
template<u8 e> inline auto LBV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LDV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LFV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LHV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LLV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LPV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LQV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LRV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LSV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LTV(u8 vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LUV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LWV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto MFC2(r32& rt, cr128& vs) -> void;
|
||||
template<u8 e> inline auto MTC2(cr32& rt, r128& vs) -> void;
|
||||
template<u8 e> inline auto SBV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SDV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SFV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SHV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SLV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SPV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SQV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SRV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SSV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto STV(u8 vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SUV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SWV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto VABS(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VADD(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VADDC(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VAND(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VCH(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VCL(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VCR(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VEQ(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VGE(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VLT(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<bool U, u8 e>
|
||||
inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<0, e>(vd, vs, vt); }
|
||||
template<u8 e> inline auto VMACU(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<1, e>(vd, vs, vt); }
|
||||
inline auto VMACQ(r128& vd) -> void;
|
||||
template<u8 e> inline auto VMADH(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMADL(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMADM(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMADN(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMOV(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMRG(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMUDH(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMUDL(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMUDM(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMUDN(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<bool U, u8 e>
|
||||
inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<0, e>(rd, vs, vt); }
|
||||
template<u8 e> inline auto VMULU(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<1, e>(rd, vs, vt); }
|
||||
template<u8 e> inline auto VMULQ(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VNAND(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VNE(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
inline auto VNOP() -> void;
|
||||
template<u8 e> inline auto VNOR(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VNXOR(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VOR(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<bool L, u8 e>
|
||||
inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void { VRCP<0, e>(vd, de, vt); }
|
||||
template<u8 e> inline auto VRCPL(r128& vd, u8 de, cr128& vt) -> void { VRCP<1, e>(vd, de, vt); }
|
||||
template<u8 e> inline auto VRCPH(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<bool D, u8 e>
|
||||
inline auto VRND(r128& vd, u8 vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VRNDN(r128& vd, u8 vs, cr128& vt) -> void { VRND<0, e>(vd, vs, vt); }
|
||||
template<u8 e> inline auto VRNDP(r128& vd, u8 vs, cr128& vt) -> void { VRND<1, e>(vd, vs, vt); }
|
||||
template<bool L, u8 e>
|
||||
inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void { VRSQ<0, e>(vd, de, vt); }
|
||||
template<u8 e> inline auto VRSQL(r128& vd, u8 de, cr128& vt) -> void { VRSQ<1, e>(vd, de, vt); }
|
||||
template<u8 e> inline auto VRSQH(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VSAR(r128& vd, cr128& vs) -> void;
|
||||
template<u8 e> inline auto VSUB(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VSUBC(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VXOR(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VZERO(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,50 +0,0 @@
|
||||
#ifndef __RT64_LAYER_H__
|
||||
#define __RT64_LAYER_H__
|
||||
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../ultramodern/config.hpp"
|
||||
|
||||
namespace RT64 {
|
||||
struct Application;
|
||||
}
|
||||
|
||||
namespace ultramodern {
|
||||
enum class RT64SetupResult {
|
||||
Success,
|
||||
DynamicLibrariesNotFound,
|
||||
InvalidGraphicsAPI,
|
||||
GraphicsAPINotFound,
|
||||
GraphicsDeviceNotFound
|
||||
};
|
||||
|
||||
struct WindowHandle;
|
||||
struct RT64Context {
|
||||
public:
|
||||
~RT64Context();
|
||||
RT64Context(uint8_t* rdram, WindowHandle window_handle, bool developer_mode);
|
||||
bool valid() { return static_cast<bool>(app); }
|
||||
RT64SetupResult get_setup_result() { return setup_result; }
|
||||
|
||||
void update_config(const GraphicsConfig& old_config, const GraphicsConfig& new_config);
|
||||
void enable_instant_present();
|
||||
void send_dl(const OSTask* task);
|
||||
void update_screen(uint32_t vi_origin);
|
||||
void shutdown();
|
||||
void set_dummy_vi();
|
||||
uint32_t get_display_framerate();
|
||||
float get_resolution_scale();
|
||||
void load_shader_cache(std::span<const char> cache_binary);
|
||||
private:
|
||||
RT64SetupResult setup_result;
|
||||
std::unique_ptr<RT64::Application> app;
|
||||
};
|
||||
|
||||
RT64::UserConfiguration::Antialiasing RT64MaxMSAA();
|
||||
bool RT64SamplePositionsSupported();
|
||||
bool RT64HighPrecisionFBEnabled();
|
||||
}
|
||||
|
||||
void set_rt64_hooks();
|
||||
|
||||
#endif
|
||||
|
@ -1,23 +0,0 @@
|
||||
#ifndef __SECTIONS_H__
|
||||
#define __SECTIONS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "recomp.h"
|
||||
|
||||
#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
typedef struct {
|
||||
recomp_func_t* func;
|
||||
uint32_t offset;
|
||||
} FuncEntry;
|
||||
|
||||
typedef struct {
|
||||
uint32_t rom_addr;
|
||||
uint32_t ram_addr;
|
||||
uint32_t size;
|
||||
FuncEntry *funcs;
|
||||
size_t num_funcs;
|
||||
size_t index;
|
||||
} SectionTableEntry;
|
||||
|
||||
#endif
|
91
include/zelda_config.h
Normal file
91
include/zelda_config.h
Normal file
@ -0,0 +1,91 @@
|
||||
#ifndef __ZELDA_CONFIG_H__
|
||||
#define __ZELDA_CONFIG_H__
|
||||
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include "ultramodern/config.hpp"
|
||||
|
||||
namespace zelda64 {
|
||||
constexpr std::u8string_view program_id = u8"Zelda64Recompiled";
|
||||
constexpr std::string_view program_name = "Zelda 64: Recompiled";
|
||||
|
||||
// TODO: Move loading configs to the runtime once we have a way to allow per-project customization.
|
||||
void load_config();
|
||||
void save_config();
|
||||
|
||||
void reset_input_bindings();
|
||||
void reset_cont_input_bindings();
|
||||
void reset_kb_input_bindings();
|
||||
|
||||
std::filesystem::path get_app_folder_path();
|
||||
|
||||
bool get_debug_mode_enabled();
|
||||
void set_debug_mode_enabled(bool enabled);
|
||||
|
||||
enum class AutosaveMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::AutosaveMode, {
|
||||
{zelda64::AutosaveMode::On, "On"},
|
||||
{zelda64::AutosaveMode::Off, "Off"}
|
||||
});
|
||||
|
||||
enum class TargetingMode {
|
||||
Switch,
|
||||
Hold,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::TargetingMode, {
|
||||
{zelda64::TargetingMode::Switch, "Switch"},
|
||||
{zelda64::TargetingMode::Hold, "Hold"}
|
||||
});
|
||||
|
||||
TargetingMode get_targeting_mode();
|
||||
void set_targeting_mode(TargetingMode mode);
|
||||
|
||||
enum class CameraInvertMode {
|
||||
InvertNone,
|
||||
InvertX,
|
||||
InvertY,
|
||||
InvertBoth,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::CameraInvertMode, {
|
||||
{zelda64::CameraInvertMode::InvertNone, "InvertNone"},
|
||||
{zelda64::CameraInvertMode::InvertX, "InvertX"},
|
||||
{zelda64::CameraInvertMode::InvertY, "InvertY"},
|
||||
{zelda64::CameraInvertMode::InvertBoth, "InvertBoth"}
|
||||
});
|
||||
|
||||
CameraInvertMode get_camera_invert_mode();
|
||||
void set_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
CameraInvertMode get_analog_camera_invert_mode();
|
||||
void set_analog_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
enum class AnalogCamMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::AnalogCamMode, {
|
||||
{zelda64::AnalogCamMode::On, "On"},
|
||||
{zelda64::AnalogCamMode::Off, "Off"}
|
||||
});
|
||||
|
||||
AutosaveMode get_autosave_mode();
|
||||
void set_autosave_mode(AutosaveMode mode);
|
||||
|
||||
AnalogCamMode get_analog_cam_mode();
|
||||
void set_analog_cam_mode(AnalogCamMode mode);
|
||||
|
||||
void open_quit_game_prompt();
|
||||
};
|
||||
|
||||
#endif
|
@ -1,10 +1,10 @@
|
||||
#ifndef __RECOMP_DEBUG_H__
|
||||
#define __RECOMP_DEBUG_H__
|
||||
#ifndef __ZELDA_DEBUG_H__
|
||||
#define __ZELDA_DEBUG_H__
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace recomp {
|
||||
namespace zelda64 {
|
||||
struct SceneWarps {
|
||||
int index;
|
||||
std::string name;
|
9
include/zelda_game.h
Normal file
9
include/zelda_game.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef __ZELDA_GAME_H__
|
||||
#define __ZELDA_GAME_H__
|
||||
|
||||
namespace zelda64 {
|
||||
void quicksave_save();
|
||||
void quicksave_load();
|
||||
};
|
||||
|
||||
#endif
|
@ -1,7 +1,7 @@
|
||||
#ifndef __RECOMP_SOUND_H__
|
||||
#define __RECOMP_SOUND_H__
|
||||
#ifndef __ZELDA_SOUND_H__
|
||||
#define __ZELDA_SOUND_H__
|
||||
|
||||
namespace recomp {
|
||||
namespace zelda64 {
|
||||
void reset_sound_settings();
|
||||
void set_main_volume(int volume);
|
||||
int get_main_volume();
|
1
lib/N64ModernRuntime
Submodule
1
lib/N64ModernRuntime
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ec7e81b45d9a622cb3e45865ce5d7d3536b26534
|
@ -27,7 +27,7 @@ $(C_OBJS): %.o : %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $< -MMD -MF $(@:.o=.d) -c -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(C_OBJS) $(TARGET) $(DATABIN)
|
||||
rm -rf $(C_OBJS) $(TARGET) $(DATABIN) $(C_DEPS)
|
||||
|
||||
-include $(C_DEPS)
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
#ifdef MIPS
|
||||
#include "ultra64.h"
|
||||
#else
|
||||
#include "recomp.h"
|
||||
#include "librecomp/recomp.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -1,8 +1,8 @@
|
||||
#include "recomp_config.h"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_files.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
#include "zelda_sound.h"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "librecomp/files.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
@ -18,6 +18,7 @@ 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::Resolution::Auto;
|
||||
constexpr auto hr_default = ultramodern::HUDRatioMode::Clamp16x9;
|
||||
@ -127,7 +128,7 @@ namespace recomp {
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path recomp::get_app_folder_path() {
|
||||
std::filesystem::path zelda64::get_app_folder_path() {
|
||||
std::filesystem::path recomp_dir{};
|
||||
|
||||
#if defined(_WIN32)
|
||||
@ -135,7 +136,7 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||
PWSTR known_path = NULL;
|
||||
HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &known_path);
|
||||
if (result == S_OK) {
|
||||
recomp_dir = std::filesystem::path{known_path} / recomp::program_id;
|
||||
recomp_dir = std::filesystem::path{known_path} / zelda64::program_id;
|
||||
}
|
||||
|
||||
CoTaskMemFree(known_path);
|
||||
@ -147,7 +148,7 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||
}
|
||||
|
||||
if (homedir != nullptr) {
|
||||
recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{recomp::program_id});
|
||||
recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{zelda64::program_id});
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -198,33 +199,33 @@ bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::j
|
||||
bool save_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
recomp::to_json(config_json["targeting_mode"], recomp::get_targeting_mode());
|
||||
zelda64::to_json(config_json["targeting_mode"], zelda64::get_targeting_mode());
|
||||
recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode());
|
||||
config_json["rumble_strength"] = recomp::get_rumble_strength();
|
||||
config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity();
|
||||
config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity();
|
||||
config_json["joystick_deadzone"] = recomp::get_joystick_deadzone();
|
||||
config_json["autosave_mode"] = recomp::get_autosave_mode();
|
||||
config_json["camera_invert_mode"] = recomp::get_camera_invert_mode();
|
||||
config_json["analog_cam_mode"] = recomp::get_analog_cam_mode();
|
||||
config_json["analog_camera_invert_mode"] = recomp::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = recomp::get_debug_mode_enabled();
|
||||
config_json["autosave_mode"] = zelda64::get_autosave_mode();
|
||||
config_json["camera_invert_mode"] = zelda64::get_camera_invert_mode();
|
||||
config_json["analog_cam_mode"] = zelda64::get_analog_cam_mode();
|
||||
config_json["analog_camera_invert_mode"] = zelda64::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = zelda64::get_debug_mode_enabled();
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void set_general_settings_from_json(const nlohmann::json& config_json) {
|
||||
recomp::set_targeting_mode(from_or_default(config_json, "targeting_mode", recomp::TargetingMode::Switch));
|
||||
zelda64::set_targeting_mode(from_or_default(config_json, "targeting_mode", zelda64::TargetingMode::Switch));
|
||||
recomp::set_background_input_mode(from_or_default(config_json, "background_input_mode", recomp::BackgroundInputMode::On));
|
||||
recomp::set_rumble_strength(from_or_default(config_json, "rumble_strength", 25));
|
||||
recomp::set_gyro_sensitivity(from_or_default(config_json, "gyro_sensitivity", 50));
|
||||
recomp::set_mouse_sensitivity(from_or_default(config_json, "mouse_sensitivity", is_steam_deck ? 50 : 0));
|
||||
recomp::set_joystick_deadzone(from_or_default(config_json, "joystick_deadzone", 5));
|
||||
recomp::set_autosave_mode(from_or_default(config_json, "autosave_mode", recomp::AutosaveMode::On));
|
||||
recomp::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", recomp::CameraInvertMode::InvertY));
|
||||
recomp::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", recomp::AnalogCamMode::Off));
|
||||
recomp::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", recomp::CameraInvertMode::InvertNone));
|
||||
recomp::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||
zelda64::set_autosave_mode(from_or_default(config_json, "autosave_mode", zelda64::AutosaveMode::On));
|
||||
zelda64::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", zelda64::CameraInvertMode::InvertY));
|
||||
zelda64::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", zelda64::AnalogCamMode::Off));
|
||||
zelda64::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", zelda64::CameraInvertMode::InvertNone));
|
||||
zelda64::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||
}
|
||||
|
||||
bool load_general_config(const std::filesystem::path& path) {
|
||||
@ -277,16 +278,16 @@ void assign_all_mappings(recomp::InputDevice device, const recomp::DefaultN64Map
|
||||
assign_mapping_complete(device, recomp::GameInput::TOGGLE_MENU, values.toggle_menu);
|
||||
};
|
||||
|
||||
void recomp::reset_input_bindings() {
|
||||
void zelda64::reset_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void recomp::reset_cont_input_bindings() {
|
||||
void zelda64::reset_cont_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void recomp::reset_kb_input_bindings() {
|
||||
void zelda64::reset_kb_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
}
|
||||
|
||||
@ -369,8 +370,8 @@ bool load_input_device_from_json(const nlohmann::json& config_json, recomp::Inpu
|
||||
cur_input,
|
||||
recomp::get_default_mapping_for_input(
|
||||
device == recomp::InputDevice::Keyboard ?
|
||||
recomp::default_n64_keyboard_mappings :
|
||||
recomp::default_n64_controller_mappings,
|
||||
recomp::default_n64_keyboard_mappings :
|
||||
recomp::default_n64_controller_mappings,
|
||||
cur_input
|
||||
)
|
||||
);
|
||||
@ -408,9 +409,9 @@ bool load_controls_config(const std::filesystem::path& path) {
|
||||
bool save_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_json["main_volume"] = recomp::get_main_volume();
|
||||
config_json["bgm_volume"] = recomp::get_bgm_volume();
|
||||
config_json["low_health_beeps"] = recomp::get_low_health_beeps_enabled();
|
||||
config_json["main_volume"] = zelda64::get_main_volume();
|
||||
config_json["bgm_volume"] = zelda64::get_bgm_volume();
|
||||
config_json["low_health_beeps"] = zelda64::get_low_health_beeps_enabled();
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
@ -421,17 +422,17 @@ bool load_sound_config(const std::filesystem::path& path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recomp::reset_sound_settings();
|
||||
call_if_key_exists(recomp::set_main_volume, config_json, "main_volume");
|
||||
call_if_key_exists(recomp::set_bgm_volume, config_json, "bgm_volume");
|
||||
call_if_key_exists(recomp::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
||||
zelda64::reset_sound_settings();
|
||||
call_if_key_exists(zelda64::set_main_volume, config_json, "main_volume");
|
||||
call_if_key_exists(zelda64::set_bgm_volume, config_json, "bgm_volume");
|
||||
call_if_key_exists(zelda64::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
||||
return true;
|
||||
}
|
||||
|
||||
void recomp::load_config() {
|
||||
void zelda64::load_config() {
|
||||
detect_steam_deck();
|
||||
|
||||
std::filesystem::path recomp_dir = recomp::get_app_folder_path();
|
||||
std::filesystem::path recomp_dir = zelda64::get_app_folder_path();
|
||||
std::filesystem::path general_path = recomp_dir / general_filename;
|
||||
std::filesystem::path graphics_path = recomp_dir / graphics_filename;
|
||||
std::filesystem::path controls_path = recomp_dir / controls_filename;
|
||||
@ -455,18 +456,18 @@ void recomp::load_config() {
|
||||
}
|
||||
|
||||
if (!load_controls_config(controls_path)) {
|
||||
recomp::reset_input_bindings();
|
||||
zelda64::reset_input_bindings();
|
||||
save_controls_config(controls_path);
|
||||
}
|
||||
|
||||
if (!load_sound_config(sound_path)) {
|
||||
recomp::reset_sound_settings();
|
||||
zelda64::reset_sound_settings();
|
||||
save_sound_config(sound_path);
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::save_config() {
|
||||
std::filesystem::path recomp_dir = recomp::get_app_folder_path();
|
||||
void zelda64::save_config() {
|
||||
std::filesystem::path recomp_dir = zelda64::get_app_folder_path();
|
||||
|
||||
if (recomp_dir.empty()) {
|
||||
return;
|
||||
|
@ -1,8 +1,8 @@
|
||||
#include <array>
|
||||
|
||||
#include "recomp_helpers.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "recomp_input.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
|
||||
// Arrays that hold the mappings for every input for keyboard and controller respectively.
|
||||
using input_mapping = std::array<recomp::InputField, recomp::bindings_per_input>;
|
||||
|
@ -1,13 +1,13 @@
|
||||
#include <atomic>
|
||||
#include "recomp_debug.h"
|
||||
#include "recomp_helpers.h"
|
||||
#include "zelda_debug.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "../patches/input.h"
|
||||
|
||||
std::atomic<uint16_t> pending_warp = 0xFFFF;
|
||||
std::atomic<uint32_t> pending_set_time = 0xFFFF;
|
||||
|
||||
void recomp::do_warp(int area, int scene, int entrance) {
|
||||
const recomp::SceneWarps game_scene = recomp::game_warps[area].scenes[scene];
|
||||
void zelda64::do_warp(int area, int scene, int entrance) {
|
||||
const zelda64::SceneWarps game_scene = zelda64::game_warps[area].scenes[scene];
|
||||
int game_scene_index = game_scene.index;
|
||||
pending_warp.store(((game_scene_index & 0xFF) << 8) | ((entrance & 0x0F) << 4));
|
||||
}
|
||||
@ -17,7 +17,7 @@ extern "C" void recomp_get_pending_warp(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, pending_warp.exchange(0xFFFF));
|
||||
}
|
||||
|
||||
void recomp::set_time(uint8_t day, uint8_t hour, uint8_t minute) {
|
||||
void zelda64::set_time(uint8_t day, uint8_t hour, uint8_t minute) {
|
||||
pending_set_time.store((day << 16) | (uint16_t(hour) << 8) | minute);
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "librecomp/recomp.h"
|
||||
#include "recomp_input.h"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "SDL.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "promptfont.h"
|
||||
#include "GamepadMotion.hpp"
|
||||
|
||||
@ -75,13 +75,13 @@ void recomp::stop_scanning_input() {
|
||||
|
||||
void queue_if_enabled(SDL_Event* event) {
|
||||
if (!recomp::all_input_disabled()) {
|
||||
recomp::queue_event(*event);
|
||||
recompui::queue_event(*event);
|
||||
}
|
||||
}
|
||||
|
||||
static std::atomic_bool cursor_enabled = true;
|
||||
|
||||
void recomp::set_cursor_visible(bool visible) {
|
||||
void recompui::set_cursor_visible(bool visible) {
|
||||
cursor_enabled.store(visible);
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) ||
|
||||
keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11
|
||||
) {
|
||||
recomp::toggle_fullscreen();
|
||||
recompui::toggle_fullscreen();
|
||||
}
|
||||
if (scanning_device != recomp::InputDevice::COUNT) {
|
||||
if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
|
||||
@ -155,12 +155,12 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recomp::get_current_menu() != recomp::Menu::Config) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
if (recompui::get_current_menu() != recompui::Menu::Config) {
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
}
|
||||
|
||||
recomp::open_quit_game_prompt();
|
||||
recomp::activate_mouse();
|
||||
zelda64::open_quit_game_prompt();
|
||||
recompui::activate_mouse();
|
||||
break;
|
||||
}
|
||||
case SDL_EventType::SDL_MOUSEWHEEL:
|
||||
@ -435,10 +435,10 @@ void recomp::poll_inputs() {
|
||||
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();
|
||||
zelda64::quicksave_save();
|
||||
}
|
||||
else if (load_is_held && !load_was_held) {
|
||||
recomp::quicksave_load();
|
||||
zelda64::quicksave_load();
|
||||
}
|
||||
save_was_held = save_is_held;
|
||||
}
|
||||
@ -649,7 +649,7 @@ void recomp::set_right_analog_suppressed(bool suppressed) {
|
||||
|
||||
bool recomp::game_input_disabled() {
|
||||
// Disable input if any menu is open.
|
||||
return recomp::get_current_menu() != recomp::Menu::None;
|
||||
return recompui::get_current_menu() != recompui::Menu::None;
|
||||
}
|
||||
|
||||
bool recomp::all_input_disabled() {
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
#if 0
|
||||
|
||||
#include "recomp_helpers.h"
|
||||
#include "recomp_input.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "librecomp/input.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
|
||||
enum class QuicksaveAction {
|
||||
None,
|
||||
@ -14,11 +14,11 @@ enum class QuicksaveAction {
|
||||
|
||||
std::atomic<QuicksaveAction> cur_quicksave_action = QuicksaveAction::None;
|
||||
|
||||
void recomp::quicksave_save() {
|
||||
void zelda64::quicksave_save() {
|
||||
cur_quicksave_action.store(QuicksaveAction::Save);
|
||||
}
|
||||
|
||||
void recomp::quicksave_load() {
|
||||
void zelda64::quicksave_load() {
|
||||
cur_quicksave_action.store(QuicksaveAction::Load);
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
#include <cmath>
|
||||
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "recomp_config.h"
|
||||
#include "librecomp/recomp.h"
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_helpers.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "../patches/input.h"
|
||||
#include "../patches/graphics.h"
|
||||
#include "../patches/sound.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../ultramodern/config.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "ultramodern/rt64_layer.hpp"
|
||||
|
||||
extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
recomp::poll_inputs();
|
||||
@ -62,7 +62,7 @@ extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config();
|
||||
float original = _arg<0, float>(rdram, ctx);
|
||||
int width, height;
|
||||
recomp::get_window_size(width, height);
|
||||
recompui::get_window_size(width, height);
|
||||
|
||||
switch (graphics_config.ar_option) {
|
||||
case RT64::UserConfiguration::AspectRatio::Original:
|
||||
@ -76,15 +76,15 @@ extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_targeting_mode(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<int>(recomp::get_targeting_mode()));
|
||||
_return(ctx, static_cast<int>(zelda64::get_targeting_mode()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_bgm_volume(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, recomp::get_bgm_volume() / 100.0f);
|
||||
_return(ctx, zelda64::get_bgm_volume() / 100.0f);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_low_health_beeps_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<u32>(recomp::get_low_health_beeps_enabled()));
|
||||
_return(ctx, static_cast<u32>(zelda64::get_low_health_beeps_enabled()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) {
|
||||
@ -92,7 +92,7 @@ extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void recomp_autosave_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<s32>(recomp::get_autosave_mode() == recomp::AutosaveMode::On));
|
||||
_return(ctx, static_cast<s32>(zelda64::get_autosave_mode() == zelda64::AutosaveMode::On));
|
||||
}
|
||||
|
||||
extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) {
|
||||
@ -115,24 +115,24 @@ extern "C" void recomp_get_inverted_axes(uint8_t* rdram, recomp_context* ctx) {
|
||||
s32* x_out = _arg<0, s32*>(rdram, ctx);
|
||||
s32* y_out = _arg<1, s32*>(rdram, ctx);
|
||||
|
||||
recomp::CameraInvertMode mode = recomp::get_camera_invert_mode();
|
||||
zelda64::CameraInvertMode mode = zelda64::get_camera_invert_mode();
|
||||
|
||||
*x_out = (mode == recomp::CameraInvertMode::InvertX || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*x_out = (mode == zelda64::CameraInvertMode::InvertX || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == zelda64::CameraInvertMode::InvertY || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_analog_inverted_axes(uint8_t* rdram, recomp_context* ctx) {
|
||||
s32* x_out = _arg<0, s32*>(rdram, ctx);
|
||||
s32* y_out = _arg<1, s32*>(rdram, ctx);
|
||||
|
||||
recomp::CameraInvertMode mode = recomp::get_analog_camera_invert_mode();
|
||||
zelda64::CameraInvertMode mode = zelda64::get_analog_camera_invert_mode();
|
||||
|
||||
*x_out = (mode == recomp::CameraInvertMode::InvertX || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*x_out = (mode == zelda64::CameraInvertMode::InvertX || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == zelda64::CameraInvertMode::InvertY || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
}
|
||||
|
||||
extern "C" void recomp_analog_cam_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return<s32>(ctx, recomp::get_analog_cam_mode() == recomp::AnalogCamMode::On);
|
||||
_return<s32>(ctx, zelda64::get_analog_cam_mode() == zelda64::AnalogCamMode::On);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "recomp_debug.h"
|
||||
#include "zelda_debug.h"
|
||||
|
||||
std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
std::vector<zelda64::AreaWarps> zelda64::game_warps {
|
||||
{ "Clock Town", {
|
||||
{
|
||||
0, "Mayor's Residence", {
|
||||
|
@ -6,11 +6,12 @@
|
||||
#include <filesystem>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "nfd.h"
|
||||
|
||||
#include "../../ultramodern/ultra64.h"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/ultra64.h"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#define SDL_MAIN_HANDLED
|
||||
#ifdef _WIN32
|
||||
#include "SDL.h"
|
||||
@ -21,9 +22,14 @@
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "zelda_config.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "ovl_patches.hpp"
|
||||
#include "librecomp/game.hpp"
|
||||
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
#include "mm_shader_cache.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@ -192,7 +198,7 @@ void queue_samples(int16_t* audio_data, size_t sample_count) {
|
||||
|
||||
// Convert the audio from 16-bit values to floats and swap the audio channels into the
|
||||
// swap buffer to correct for the address xor caused by endianness handling.
|
||||
float cur_main_volume = recomp::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
|
||||
float cur_main_volume = zelda64::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
|
||||
for (size_t i = 0; i < sample_count; i += input_channels) {
|
||||
swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f) * cur_main_volume;
|
||||
swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f) * cur_main_volume;
|
||||
@ -302,6 +308,42 @@ void reset_audio(uint32_t output_freq) {
|
||||
update_audio_converter();
|
||||
}
|
||||
|
||||
extern RspUcodeFunc njpgdspMain;
|
||||
extern RspUcodeFunc aspMain;
|
||||
|
||||
RspUcodeFunc* get_rsp_microcode(const OSTask* task) {
|
||||
switch (task->t.type) {
|
||||
case M_AUDTASK:
|
||||
return aspMain;
|
||||
|
||||
case M_NJPEGTASK:
|
||||
return njpgdspMain;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown task: %" PRIu32 "\n", task->t.type);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
|
||||
gpr get_entrypoint_address();
|
||||
|
||||
// array of supported GameEntry objects
|
||||
std::vector<recomp::GameEntry> supported_games = {
|
||||
{
|
||||
.rom_hash = 0xEF18B4A9E2386169ULL,
|
||||
.internal_name = "ZELDA MAJORA'S MASK",
|
||||
.game_id = u8"mm.n64.us.1.0",
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
.cache_data = {mm_shader_cache_bytes, sizeof(mm_shader_cache_bytes)},
|
||||
#endif
|
||||
.is_enabled = true,
|
||||
.entrypoint_address = get_entrypoint_address(),
|
||||
.entrypoint = recomp_entrypoint,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -333,7 +375,20 @@ int main(int argc, char** argv) {
|
||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
reset_audio(48000);
|
||||
|
||||
recomp::load_config();
|
||||
// Register supported games and patches
|
||||
for (const auto& game : supported_games) {
|
||||
recomp::register_game(game);
|
||||
}
|
||||
|
||||
zelda64::register_overlays();
|
||||
zelda64::register_patches();
|
||||
|
||||
recomp::register_config_path(zelda64::get_app_folder_path());
|
||||
zelda64::load_config();
|
||||
|
||||
recomp::rsp::callbacks_t rsp_callbacks{
|
||||
.get_rsp_microcode = get_rsp_microcode,
|
||||
};
|
||||
|
||||
ultramodern::gfx_callbacks_t gfx_callbacks{
|
||||
.create_gfx = create_gfx,
|
||||
@ -353,7 +408,16 @@ int main(int argc, char** argv) {
|
||||
.set_rumble = recomp::set_rumble,
|
||||
};
|
||||
|
||||
recomp::start({}, audio_callbacks, input_callbacks, gfx_callbacks);
|
||||
ultramodern::events::callbacks_t thread_callbacks{
|
||||
.vi_callback = recomp::update_rumble,
|
||||
.gfx_init_callback = recompui::update_supported_options,
|
||||
};
|
||||
|
||||
ultramodern::error_handling::callbacks_t error_handling_callbacks{
|
||||
.message_box = recompui::message_box,
|
||||
};
|
||||
|
||||
recomp::start({}, rsp_callbacks, audio_callbacks, input_callbacks, gfx_callbacks, thread_callbacks, error_handling_callbacks);
|
||||
|
||||
NFD_Quit();
|
||||
|
||||
|
19
src/main/register_overlays.cpp
Normal file
19
src/main/register_overlays.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "ovl_patches.hpp"
|
||||
#include "../../RecompiledFuncs/recomp_overlays.inl"
|
||||
|
||||
#include "librecomp/overlays.hpp"
|
||||
|
||||
void zelda64::register_overlays() {
|
||||
recomp::overlay_section_table_data_t sections {
|
||||
.code_sections = section_table,
|
||||
.num_code_sections = ARRLEN(section_table),
|
||||
.total_num_sections = num_sections,
|
||||
};
|
||||
|
||||
recomp::overlays_by_index_t overlays {
|
||||
.table = overlay_sections_by_index,
|
||||
.len = ARRLEN(overlay_sections_by_index),
|
||||
};
|
||||
|
||||
recomp::register_overlays(sections, overlays);
|
||||
}
|
12
src/main/register_patches.cpp
Normal file
12
src/main/register_patches.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "ovl_patches.hpp"
|
||||
#include "../../RecompiledPatches/patches_bin.h"
|
||||
#include "../../RecompiledPatches/recomp_overlays.inl"
|
||||
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "librecomp/game.hpp"
|
||||
|
||||
void zelda64::register_patches() {
|
||||
// TODO: This is a bit awful, maybe provide only one functions that does both in librecomp?
|
||||
recomp::register_patch(mm_patches_bin, sizeof(mm_patches_bin));
|
||||
recomp::register_patch_section(section_table);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
#define VI_NTSC_CLOCK 48681812
|
||||
|
||||
extern "C" void osAiSetFrequency_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint32_t freq = ctx->r4;
|
||||
// This makes actual audio frequency more accurate to console, but may not be desirable
|
||||
//uint32_t dacRate = (uint32_t)(((float)VI_NTSC_CLOCK / freq) + 0.5f);
|
||||
//freq = VI_NTSC_CLOCK / dacRate;
|
||||
ctx->r2 = freq;
|
||||
ultramodern::set_audio_frequency(freq);
|
||||
}
|
||||
|
||||
extern "C" void osAiSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::queue_audio_buffer(rdram, ctx->r4, ctx->r5);
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osAiGetLength_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = ultramodern::get_remaining_audio_bytes();
|
||||
}
|
||||
|
||||
extern "C" void osAiGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 0x00000000; // Pretend the audio DMAs finish instantly
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp_helpers.h"
|
||||
|
||||
static ultramodern::input_callbacks_t input_callbacks;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point input_poll_time;
|
||||
|
||||
void update_poll_time() {
|
||||
input_poll_time = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
extern "C" void recomp_set_current_frame_poll_id(uint8_t* rdram, recomp_context* ctx) {
|
||||
// TODO reimplement the system for tagging polls with IDs to handle games with multithreaded input polling.
|
||||
}
|
||||
|
||||
extern "C" void recomp_measure_latency(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::measure_input_latency();
|
||||
}
|
||||
|
||||
void ultramodern::measure_input_latency() {
|
||||
// printf("Delta: %ld micros\n", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - input_poll_time));
|
||||
}
|
||||
|
||||
void set_input_callbacks(const ultramodern::input_callbacks_t& callbacks) {
|
||||
input_callbacks = callbacks;
|
||||
}
|
||||
|
||||
static int max_controllers = 0;
|
||||
|
||||
extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
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;
|
||||
|
||||
// Mark controller 0 as present
|
||||
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
|
||||
MEM_B(2, status) = 0x00; // status: 0 (from joybus)
|
||||
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
|
||||
|
||||
max_controllers = 4;
|
||||
|
||||
// Mark controllers 1-3 as not connected
|
||||
for (size_t controller = 1; controller < max_controllers; controller++) {
|
||||
// Libultra doesn't write status or type for absent controllers
|
||||
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
if (input_callbacks.poll_input) {
|
||||
input_callbacks.poll_input();
|
||||
}
|
||||
update_poll_time();
|
||||
|
||||
ultramodern::send_si_message(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pad = _arg<0, PTR(void)>(rdram, ctx);
|
||||
|
||||
uint16_t buttons = 0;
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
|
||||
if (input_callbacks.get_input) {
|
||||
input_callbacks.get_input(&buttons, &x, &y);
|
||||
}
|
||||
|
||||
if (max_controllers > 0) {
|
||||
// button
|
||||
MEM_H(0, pad) = buttons;
|
||||
// stick_x
|
||||
MEM_B(2, pad) = (int8_t)(127 * x);
|
||||
// stick_y
|
||||
MEM_B(3, pad) = (int8_t)(127 * y);
|
||||
// errno
|
||||
MEM_B(4, pad) = 0;
|
||||
}
|
||||
for (int controller = 1; controller < max_controllers; controller++) {
|
||||
MEM_B(6 * controller + 4, pad) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ultramodern::send_si_message(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
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)
|
||||
MEM_B(2, status) = 0x01; // status: 0x01 (from joybus, indicates that a pak is plugged into the controller)
|
||||
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
|
||||
|
||||
// Mark controllers 1-3 as not connected
|
||||
for (size_t controller = 1; controller < max_controllers; controller++) {
|
||||
// Libultra doesn't write status or type for absent controllers
|
||||
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
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) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 flag = _arg<1, s32>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(flag);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<1, PTR(void)>(rdram, ctx);
|
||||
s32 channel = _arg<2, s32>(rdram, ctx);
|
||||
MEM_W(8, pfs) = channel;
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(true);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(false);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
#include "recomp.h"
|
||||
|
||||
enum class RDPStatusBit {
|
||||
XbusDmem = 0,
|
||||
Freeze = 1,
|
||||
Flush = 2,
|
||||
CommandBusy = 6,
|
||||
BufferReady = 7,
|
||||
DmaBusy = 8,
|
||||
EndValid = 9,
|
||||
StartValid = 10,
|
||||
};
|
||||
|
||||
constexpr void update_bit(uint32_t& state, uint32_t flags, RDPStatusBit bit) {
|
||||
int set_bit_pos = (int)bit * 2 + 0;
|
||||
int reset_bit_pos = (int)bit * 2 + 1;
|
||||
bool set = (flags & (1U << set_bit_pos)) != 0;
|
||||
bool reset = (flags & (1U << reset_bit_pos)) != 0;
|
||||
|
||||
if (set ^ reset) {
|
||||
if (set) {
|
||||
state |= (1U << (int)bit);
|
||||
}
|
||||
else {
|
||||
state &= ~(1U << (int)bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rdp_state = 1 << (int)RDPStatusBit::BufferReady;
|
||||
|
||||
extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osDpGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = rdp_state;
|
||||
}
|
||||
|
||||
extern "C" void osDpSetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::XbusDmem);
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::Freeze);
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::Flush);
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
|
||||
constexpr int eeprom_block_size = 8;
|
||||
constexpr int eep4_size = 4096;
|
||||
constexpr int eep4_block_count = eep4_size / eeprom_block_size;
|
||||
constexpr int eep16_size = 16384;
|
||||
constexpr int eep16_block_count = eep16_size / eeprom_block_size;
|
||||
|
||||
extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 0x02; // EEP16K
|
||||
}
|
||||
|
||||
extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint8_t eep_address = ctx->r5;
|
||||
gpr buffer = ctx->r6;
|
||||
int32_t nbytes = ctx->r7;
|
||||
|
||||
assert(!(nbytes & 7));
|
||||
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||
|
||||
save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint8_t eep_address = ctx->r5;
|
||||
gpr buffer = ctx->r6;
|
||||
int32_t nbytes = ctx->r7;
|
||||
|
||||
assert(!(nbytes & 7));
|
||||
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||
|
||||
save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
#ifndef __EUC_JP_H__
|
||||
#define __EUC_JP_H__
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Encoding {
|
||||
std::string decode_eucjp(std::string_view src);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,51 +0,0 @@
|
||||
#include "recomp_files.h"
|
||||
|
||||
constexpr std::u8string_view backup_suffix = u8".bak";
|
||||
constexpr std::u8string_view temp_suffix = u8".temp";
|
||||
|
||||
std::ifstream recomp::open_input_backup_file(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::filesystem::path backup_path{filepath};
|
||||
backup_path += backup_suffix;
|
||||
return std::ifstream{backup_path, mode};
|
||||
}
|
||||
|
||||
std::ifstream recomp::open_input_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::ifstream ret{filepath, mode};
|
||||
|
||||
// Check if the file failed to open and open the corresponding backup file instead if so.
|
||||
if (!ret.good()) {
|
||||
return open_input_backup_file(filepath, mode);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::ofstream recomp::open_output_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::filesystem::path temp_path{filepath};
|
||||
temp_path += temp_suffix;
|
||||
std::ofstream temp_file_out{ temp_path, mode };
|
||||
|
||||
return temp_file_out;
|
||||
}
|
||||
|
||||
bool recomp::finalize_output_file_with_backup(const std::filesystem::path& filepath) {
|
||||
std::filesystem::path backup_path{filepath};
|
||||
backup_path += backup_suffix;
|
||||
|
||||
std::filesystem::path temp_path{filepath};
|
||||
temp_path += temp_suffix;
|
||||
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(filepath, ec)) {
|
||||
std::filesystem::copy_file(filepath, backup_path, std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::filesystem::copy_file(temp_path, filepath, std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
std::filesystem::remove(temp_path, ec);
|
||||
return true;
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
// TODO move this out into ultramodern code
|
||||
|
||||
constexpr uint32_t flash_size = 1024 * 1024 / 8; // 1Mbit
|
||||
constexpr uint32_t page_size = 128;
|
||||
constexpr uint32_t pages_per_sector = 128;
|
||||
constexpr uint32_t page_count = flash_size / page_size;
|
||||
constexpr uint32_t sector_size = page_size * pages_per_sector;
|
||||
constexpr uint32_t sector_count = flash_size / sector_size;
|
||||
|
||||
void save_write_ptr(const void* in, uint32_t offset, uint32_t count);
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_clear(uint32_t start, uint32_t size, char value);
|
||||
|
||||
std::array<char, page_size> write_buffer;
|
||||
|
||||
extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = ultramodern::flash_handle;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
PTR(u8) flash_status = ctx->r4;
|
||||
|
||||
MEM_B(0, flash_status) = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
PTR(u32) flash_type = ctx->r4;
|
||||
PTR(u32) flash_maker = ctx->r5;
|
||||
|
||||
// Mimic a real flash chip's type and maker, as some games actually check if one is present.
|
||||
MEM_W(0, flash_type) = 0x11118001;
|
||||
MEM_W(0, flash_maker) = 0x00C2001E;
|
||||
}
|
||||
|
||||
extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
|
||||
}
|
||||
|
||||
extern "C" void osFlashAllErase_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
save_clear(0, ultramodern::save_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
save_clear(0, ultramodern::save_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// This function is named sector but really means page.
|
||||
extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = (uint32_t)ctx->r4;
|
||||
|
||||
// Prevent out of bounds erase
|
||||
if (page_num >= page_count) {
|
||||
ctx->r2 = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
save_clear(page_num * page_size, page_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// Same naming issue as above.
|
||||
extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = (uint32_t)ctx->r4;
|
||||
|
||||
// Prevent out of bounds erase
|
||||
if (page_num >= page_count) {
|
||||
ctx->r2 = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
save_clear(page_num * page_size, page_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// All erases are blocking in this implementation, so this should always return OK.
|
||||
ctx->r2 = 0; // FLASH_STATUS_ERASE_OK
|
||||
}
|
||||
|
||||
extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4);
|
||||
int32_t pri = ctx->r5;
|
||||
PTR(void) dramAddr = ctx->r6;
|
||||
PTR(OSMesgQueue) mq = ctx->r7;
|
||||
|
||||
// Copy the input data into the write buffer
|
||||
for (size_t i = 0; i < page_size; i++) {
|
||||
write_buffer[i] = MEM_B(i, dramAddr);
|
||||
}
|
||||
|
||||
// Send the message indicating write completion
|
||||
osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = ctx->r4;
|
||||
|
||||
// Copy the write buffer into the save file
|
||||
save_write_ptr(write_buffer.data(), page_num * page_size, page_size);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4);
|
||||
int32_t pri = ctx->r5;
|
||||
uint32_t page_num = ctx->r6;
|
||||
PTR(void) dramAddr = ctx->r7;
|
||||
uint32_t n_pages = MEM_W(0x10, ctx->r29);
|
||||
PTR(OSMesgQueue) mq = MEM_W(0x14, ctx->r29);
|
||||
|
||||
uint32_t offset = page_num * page_size;
|
||||
uint32_t count = n_pages * page_size;
|
||||
|
||||
// Read from the save file into the provided buffer
|
||||
save_read(PASS_RDRAM dramAddr, offset, count);
|
||||
|
||||
// Send the message indicating read completion
|
||||
osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashChange_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
|
||||
extern "C" void __udivdi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __divdi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
int64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __umoddi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a % b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_div_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ll_div_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
int64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ll_mul_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a * b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_rem_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a % b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_to_d_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
double ret = (double)a;
|
||||
|
||||
ctx->f0.d = ret;
|
||||
}
|
||||
|
||||
extern "C" void __ull_to_f_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
float ret = (float)a;
|
||||
|
||||
ctx->f0.fl = ret;
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "../RecompiledFuncs/recomp_overlays.inl"
|
||||
|
||||
constexpr size_t num_code_sections = ARRLEN(section_table);
|
||||
|
||||
// SectionTableEntry sections[] defined in recomp_overlays.inl
|
||||
|
||||
struct LoadedSection {
|
||||
int32_t loaded_ram_addr;
|
||||
size_t section_table_index;
|
||||
|
||||
LoadedSection(int32_t loaded_ram_addr_, size_t section_table_index_) {
|
||||
loaded_ram_addr = loaded_ram_addr_;
|
||||
section_table_index = section_table_index_;
|
||||
}
|
||||
|
||||
bool operator<(const LoadedSection& rhs) {
|
||||
return loaded_ram_addr < rhs.loaded_ram_addr;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<LoadedSection> loaded_sections{};
|
||||
std::unordered_map<int32_t, recomp_func_t*> func_map{};
|
||||
|
||||
void load_overlay(size_t section_table_index, int32_t ram) {
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
|
||||
const FuncEntry& func = section.funcs[function_index];
|
||||
func_map[ram + func.offset] = func.func;
|
||||
}
|
||||
loaded_sections.emplace_back(ram, section_table_index);
|
||||
section_addresses[section.index] = ram;
|
||||
}
|
||||
|
||||
void load_special_overlay(const SectionTableEntry& section, int32_t ram) {
|
||||
for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
|
||||
const FuncEntry& func = section.funcs[function_index];
|
||||
func_map[ram + func.offset] = func.func;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern "C" {
|
||||
int32_t section_addresses[num_sections];
|
||||
}
|
||||
|
||||
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) {
|
||||
// Search for the first section that's included in the loaded rom range
|
||||
// Sections were sorted by `init_overlays` so we can use the bounds functions
|
||||
auto lower = std::lower_bound(§ion_table[0], §ion_table[num_code_sections], rom,
|
||||
[](const SectionTableEntry& entry, uint32_t addr) {
|
||||
return entry.rom_addr < addr;
|
||||
}
|
||||
);
|
||||
auto upper = std::upper_bound(§ion_table[0], §ion_table[num_code_sections], (uint32_t)(rom + size),
|
||||
[](uint32_t addr, const SectionTableEntry& entry) {
|
||||
return addr < entry.size + entry.rom_addr;
|
||||
}
|
||||
);
|
||||
// Load the overlays that were found
|
||||
for (auto it = lower; it != upper; ++it) {
|
||||
load_overlay(std::distance(§ion_table[0], it), it->rom_addr - rom + ram_addr);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
|
||||
|
||||
extern "C" void unload_overlay_by_id(uint32_t id) {
|
||||
uint32_t section_table_index = overlay_sections_by_index[id];
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
|
||||
auto find_it = std::find_if(loaded_sections.begin(), loaded_sections.end(), [section_table_index](const LoadedSection& s) { return s.section_table_index == section_table_index; });
|
||||
|
||||
if (find_it != loaded_sections.end()) {
|
||||
// Determine where each function was loaded to and remove that entry from the function map
|
||||
for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
|
||||
const auto& func = section.funcs[func_index];
|
||||
uint32_t func_address = func.offset + find_it->loaded_ram_addr;
|
||||
func_map.erase(func_address);
|
||||
}
|
||||
// Reset the section's address in the address table
|
||||
section_addresses[section.index] = section.ram_addr;
|
||||
// Remove the section from the loaded section map
|
||||
loaded_sections.erase(find_it);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void load_overlay_by_id(uint32_t id, uint32_t ram_addr) {
|
||||
uint32_t section_table_index = overlay_sections_by_index[id];
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
int32_t prev_address = section_addresses[section.index];
|
||||
if (/*ram_addr >= 0x80000000 && ram_addr < 0x81000000) {*/ prev_address == section.ram_addr) {
|
||||
load_overlay(section_table_index, ram_addr);
|
||||
}
|
||||
else {
|
||||
int32_t new_address = prev_address + ram_addr;
|
||||
unload_overlay_by_id(id);
|
||||
load_overlay(section_table_index, new_address);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
|
||||
for (auto it = loaded_sections.begin(); it != loaded_sections.end();) {
|
||||
const auto& section = section_table[it->section_table_index];
|
||||
|
||||
// Check if the unloaded region overlaps with the loaded section
|
||||
if (ram_addr < (it->loaded_ram_addr + section.size) && (ram_addr + size) >= it->loaded_ram_addr) {
|
||||
// Check if the section isn't entirely in the loaded region
|
||||
if (ram_addr > it->loaded_ram_addr || (ram_addr + size) < (it->loaded_ram_addr + section.size)) {
|
||||
fprintf(stderr,
|
||||
"Cannot partially unload section\n"
|
||||
" rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n"
|
||||
" unloaded_ram: 0x%08X unloaded_size : 0x%08X\n",
|
||||
section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size);
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
// Determine where each function was loaded to and remove that entry from the function map
|
||||
for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
|
||||
const auto& func = section.funcs[func_index];
|
||||
uint32_t func_address = func.offset + it->loaded_ram_addr;
|
||||
func_map.erase(func_address);
|
||||
}
|
||||
// Reset the section's address in the address table
|
||||
section_addresses[section.index] = section.ram_addr;
|
||||
// Remove the section from the loaded section map
|
||||
it = loaded_sections.erase(it);
|
||||
// Skip incrementing the iterator
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void load_patch_functions();
|
||||
|
||||
void init_overlays() {
|
||||
for (size_t section_index = 0; section_index < num_code_sections; section_index++) {
|
||||
section_addresses[section_table[section_index].index] = section_table[section_index].ram_addr;
|
||||
}
|
||||
|
||||
// Sort the executable sections by rom address
|
||||
std::sort(§ion_table[0], §ion_table[num_code_sections],
|
||||
[](const SectionTableEntry& a, const SectionTableEntry& b) {
|
||||
return a.rom_addr < b.rom_addr;
|
||||
}
|
||||
);
|
||||
|
||||
load_patch_functions();
|
||||
}
|
||||
|
||||
extern "C" recomp_func_t * get_function(int32_t addr) {
|
||||
auto func_find = func_map.find(addr);
|
||||
if (func_find == func_map.end()) {
|
||||
fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
return func_find->second;
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
extern "C" void osPfsInitPak_recomp(uint8_t * rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFreeBlocks_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsAllocateFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsDeleteFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFileState_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFindFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsReadWriteFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsChecker_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "recomp.h"
|
||||
#include "../../RecompiledPatches/recomp_overlays.inl"
|
||||
|
||||
void load_special_overlay(const SectionTableEntry& section, int32_t ram);
|
||||
|
||||
void load_patch_functions() {
|
||||
load_special_overlay(section_table[0], section_table[0].ram_addr);
|
||||
}
|
@ -1,302 +0,0 @@
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include "recomp.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_files.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
static std::vector<uint8_t> rom;
|
||||
|
||||
bool recomp::is_rom_loaded() {
|
||||
return !rom.empty();
|
||||
}
|
||||
|
||||
void recomp::set_rom_contents(std::vector<uint8_t>&& new_rom) {
|
||||
rom = std::move(new_rom);
|
||||
}
|
||||
|
||||
// Flashram occupies the same physical address as sram, but that issue is avoided because libultra exposes
|
||||
// a high-level interface for flashram. Because that high-level interface is reimplemented, low level accesses
|
||||
// that involve physical addresses don't need to be handled for flashram.
|
||||
constexpr uint32_t sram_base = 0x08000000;
|
||||
constexpr uint32_t rom_base = 0x10000000;
|
||||
constexpr uint32_t drive_base = 0x06000000;
|
||||
|
||||
constexpr uint32_t k1_to_phys(uint32_t addr) {
|
||||
return addr & 0x1FFFFFFF;
|
||||
}
|
||||
|
||||
constexpr uint32_t phys_to_k1(uint32_t addr) {
|
||||
return addr | 0xA0000000;
|
||||
}
|
||||
|
||||
extern "C" void __osPiGetAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osPiRelAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void osCartRomInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::cart_handle);
|
||||
handle->type = 0; // cart
|
||||
handle->baseAddress = phys_to_k1(rom_base);
|
||||
handle->domain = 0;
|
||||
|
||||
ctx->r2 = (gpr)ultramodern::cart_handle;
|
||||
}
|
||||
|
||||
extern "C" void osDriveRomInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::drive_handle);
|
||||
handle->type = 1; // bulk
|
||||
handle->baseAddress = phys_to_k1(drive_base);
|
||||
handle->domain = 0;
|
||||
|
||||
ctx->r2 = (gpr)ultramodern::drive_handle;
|
||||
}
|
||||
|
||||
extern "C" void osCreatePiManager_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
void recomp::do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes) {
|
||||
// TODO use word copies when possible
|
||||
|
||||
// TODO handle misaligned DMA
|
||||
assert((physical_addr & 0x1) == 0 && "Only PI DMA from aligned ROM addresses is currently supported");
|
||||
assert((ram_address & 0x7) == 0 && "Only PI DMA to aligned RDRAM addresses is currently supported");
|
||||
assert((num_bytes & 0x1) == 0 && "Only PI DMA with aligned sizes is currently supported");
|
||||
uint8_t* rom_addr = rom.data() + physical_addr - rom_base;
|
||||
for (size_t i = 0; i < num_bytes; i++) {
|
||||
MEM_B(i, ram_address) = *rom_addr;
|
||||
rom_addr++;
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr) {
|
||||
assert((physical_addr & 0x3) == 0 && "PIO not 4-byte aligned in device, currently unsupported");
|
||||
assert((ram_address & 0x3) == 0 && "PIO not 4-byte aligned in RDRAM, currently unsupported");
|
||||
uint8_t* rom_addr = rom.data() + physical_addr - rom_base;
|
||||
MEM_B(0, ram_address) = *rom_addr++;
|
||||
MEM_B(1, ram_address) = *rom_addr++;
|
||||
MEM_B(2, ram_address) = *rom_addr++;
|
||||
MEM_B(3, ram_address) = *rom_addr++;
|
||||
}
|
||||
|
||||
struct {
|
||||
std::array<char, 0x20000> save_buffer;
|
||||
std::thread saving_thread;
|
||||
moodycamel::LightweightSemaphore write_sempahore;
|
||||
std::mutex save_buffer_mutex;
|
||||
} save_context;
|
||||
|
||||
const std::u8string save_folder = u8"saves";
|
||||
const std::u8string save_filename = std::u8string{recomp::mm_game_id} + u8".bin";
|
||||
|
||||
std::filesystem::path get_save_file_path() {
|
||||
return recomp::get_app_folder_path() / save_folder / save_filename;
|
||||
}
|
||||
|
||||
void update_save_file() {
|
||||
bool saving_failed = false;
|
||||
{
|
||||
std::ofstream save_file = recomp::open_output_file_with_backup(get_save_file_path(), std::ios_base::binary);
|
||||
|
||||
if (save_file.good()) {
|
||||
std::lock_guard lock{ save_context.save_buffer_mutex };
|
||||
save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
}
|
||||
else {
|
||||
saving_failed = false;
|
||||
}
|
||||
}
|
||||
if (!saving_failed) {
|
||||
saving_failed = !recomp::finalize_output_file_with_backup(get_save_file_path());
|
||||
}
|
||||
if (saving_failed) {
|
||||
recomp::message_box("Failed to write to the save file. Check your file permissions and whether the save folder has been moved to Dropbox or similar, as this can cause issues.");
|
||||
}
|
||||
}
|
||||
|
||||
extern std::atomic_bool exited;
|
||||
|
||||
void saving_thread_func(RDRAM_ARG1) {
|
||||
while (!exited) {
|
||||
bool save_buffer_updated = false;
|
||||
// Repeatedly wait for a new action to be sent.
|
||||
constexpr int64_t wait_time_microseconds = 10000;
|
||||
constexpr int max_actions = 128;
|
||||
int num_actions = 0;
|
||||
|
||||
// Wait up to the given timeout for a write to come in. Allow multiple writes to coalesce together into a single save.
|
||||
// Cap the number of coalesced writes to guarantee that the save buffer eventually gets written out to the file even if the game
|
||||
// is constantly sending writes.
|
||||
while (save_context.write_sempahore.wait(wait_time_microseconds) && num_actions < max_actions) {
|
||||
save_buffer_updated = true;
|
||||
num_actions++;
|
||||
}
|
||||
|
||||
// If an action came through that affected the save file, save the updated contents.
|
||||
if (save_buffer_updated) {
|
||||
update_save_file();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void save_write_ptr(const void* in, uint32_t offset, uint32_t count) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
memcpy(&save_context.save_buffer[offset], in, count);
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
save_context.save_buffer[offset + i] = MEM_B(i, rdram_address);
|
||||
}
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) {
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
MEM_B(i, rdram_address) = save_context.save_buffer[offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
void save_clear(uint32_t start, uint32_t size, char value) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
std::fill_n(save_context.save_buffer.begin() + start, size, value);
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void ultramodern::init_saving(RDRAM_ARG1) {
|
||||
std::filesystem::path save_file_path = get_save_file_path();
|
||||
|
||||
// Ensure the save file directory exists.
|
||||
std::filesystem::create_directories(save_file_path.parent_path());
|
||||
|
||||
// Read the save file if it exists.
|
||||
std::ifstream save_file = recomp::open_input_file_with_backup(save_file_path, std::ios_base::binary);
|
||||
if (save_file.good()) {
|
||||
save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
}
|
||||
else {
|
||||
// Otherwise clear the save file to all zeroes.
|
||||
save_context.save_buffer.fill(0);
|
||||
}
|
||||
|
||||
save_context.saving_thread = std::thread{saving_thread_func, PASS_RDRAM};
|
||||
}
|
||||
|
||||
void ultramodern::join_saving_thread() {
|
||||
save_context.saving_thread.join();
|
||||
}
|
||||
|
||||
void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) {
|
||||
// TODO asynchronous transfer
|
||||
// TODO implement unaligned DMA correctly
|
||||
if (direction == 0) {
|
||||
if (physical_addr >= rom_base) {
|
||||
// read cart rom
|
||||
recomp::do_rom_read(rdram, rdram_address, physical_addr, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else if (physical_addr >= sram_base) {
|
||||
// read sram
|
||||
save_read(rdram, rdram_address, physical_addr - sram_base, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else {
|
||||
fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr);
|
||||
}
|
||||
} else {
|
||||
if (physical_addr >= rom_base) {
|
||||
// write cart rom
|
||||
throw std::runtime_error("ROM DMA write unimplemented");
|
||||
} else if (physical_addr >= sram_base) {
|
||||
// write sram
|
||||
save_write(rdram, rdram_address, physical_addr - sram_base, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else {
|
||||
fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
|
||||
uint32_t mb = ctx->r4;
|
||||
uint32_t pri = ctx->r5;
|
||||
uint32_t direction = ctx->r6;
|
||||
uint32_t devAddr = ctx->r7 | rom_base;
|
||||
gpr dramAddr = MEM_W(0x10, ctx->r29);
|
||||
uint32_t size = MEM_W(0x14, ctx->r29);
|
||||
PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29);
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
|
||||
|
||||
do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5);
|
||||
uint32_t direction = ctx->r6;
|
||||
uint32_t devAddr = handle->baseAddress | mb->devAddr;
|
||||
gpr dramAddr = mb->dramAddr;
|
||||
uint32_t size = mb->size;
|
||||
PTR(OSMesgQueue) mq = mb->hdr.retQueue;
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
|
||||
|
||||
do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEPiReadIo_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
|
||||
uint32_t devAddr = handle->baseAddress | ctx->r5;
|
||||
gpr dramAddr = ctx->r6;
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
if (physical_addr > rom_base) {
|
||||
// cart rom
|
||||
recomp::do_rom_pio(PASS_RDRAM dramAddr, physical_addr);
|
||||
} else {
|
||||
// sram
|
||||
assert(false && "SRAM ReadIo unimplemented");
|
||||
}
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osPiGetStatus_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osPiRawStartDma_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
#include <vector>
|
||||
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
#include "euc-jp.h"
|
||||
|
||||
extern "C" void __checkHardware_msp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __checkHardware_kmc_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __checkHardware_isv_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_msp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_kmc_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_isv_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void isPrintfInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osRdbSend_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
gpr buf = ctx->r4;
|
||||
size_t size = ctx->r5;
|
||||
u32 type = (u32)ctx->r6;
|
||||
std::unique_ptr<char[]> to_print = std::make_unique<char[]>(size + 1);
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
to_print[i] = MEM_B(i, buf);
|
||||
}
|
||||
to_print[size] = '\x00';
|
||||
|
||||
fwrite(to_print.get(), 1, size, stdout);
|
||||
|
||||
ctx->r2 = size;
|
||||
}
|
||||
|
||||
extern "C" void is_proutSyncPrintf_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// Buffering to speed up print performance
|
||||
static std::vector<char> print_buffer;
|
||||
|
||||
gpr buf = ctx->r5;
|
||||
size_t size = ctx->r6;
|
||||
|
||||
//for (size_t i = 0; i < size; i++) {
|
||||
// // Add the new character to the buffer
|
||||
// char cur_char = MEM_B(i, buf);
|
||||
|
||||
// // If the new character is a newline, flush the buffer
|
||||
// if (cur_char == '\n') {
|
||||
// std::string utf8_str = Encoding::decode_eucjp(std::string_view{ print_buffer.data(), print_buffer.size() });
|
||||
// puts(utf8_str.c_str());
|
||||
// print_buffer.clear();
|
||||
// } else {
|
||||
// print_buffer.push_back(cur_char);
|
||||
// }
|
||||
//}
|
||||
|
||||
//fwrite(to_print.get(), size, 1, stdout);
|
||||
|
||||
ctx->r2 = 1;
|
||||
}
|
@ -1,445 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "xxHash/xxh3.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../../RecompiledPatches/patches_bin.h"
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
#include "mm_shader_cache.h"
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
inline uint32_t byteswap(uint32_t val) {
|
||||
return _byteswap_ulong(val);
|
||||
}
|
||||
#else
|
||||
constexpr uint32_t byteswap(uint32_t val) {
|
||||
return __builtin_bswap32(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct RomEntry {
|
||||
uint64_t xxhash3_value;
|
||||
std::u8string stored_filename;
|
||||
std::string internal_rom_name;
|
||||
};
|
||||
|
||||
const std::unordered_map<recomp::Game, RomEntry> game_roms {
|
||||
{ recomp::Game::MM, { 0xEF18B4A9E2386169ULL, std::u8string{recomp::mm_game_id} + u8".z64", "ZELDA MAJORA'S MASK" }},
|
||||
};
|
||||
|
||||
bool check_hash(const std::vector<uint8_t>& rom_data, uint64_t expected_hash) {
|
||||
uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size());
|
||||
|
||||
return calculated_hash == expected_hash;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> read_file(const std::filesystem::path& path) {
|
||||
std::vector<uint8_t> ret;
|
||||
|
||||
std::ifstream file{ path, std::ios::binary};
|
||||
|
||||
if (file.good()) {
|
||||
file.seekg(0, std::ios::end);
|
||||
ret.resize(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
file.read(reinterpret_cast<char*>(ret.data()), ret.size());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool write_file(const std::filesystem::path& path, const std::vector<uint8_t>& data) {
|
||||
std::ofstream out_file{ path, std::ios::binary };
|
||||
|
||||
if (!out_file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out_file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_stored_rom(const RomEntry& game_entry) {
|
||||
|
||||
std::vector stored_rom_data = read_file(recomp::get_app_folder_path() / game_entry.stored_filename);
|
||||
|
||||
if (!check_hash(stored_rom_data, game_entry.xxhash3_value)) {
|
||||
// Incorrect hash, remove the stored ROM file if it exists.
|
||||
std::filesystem::remove(recomp::get_app_folder_path() / game_entry.stored_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::unordered_set<recomp::Game> valid_game_roms;
|
||||
|
||||
bool recomp::is_rom_valid(recomp::Game game) {
|
||||
return valid_game_roms.contains(game);
|
||||
}
|
||||
|
||||
void recomp::check_all_stored_roms() {
|
||||
for (const auto& cur_rom_entry: game_roms) {
|
||||
if (check_stored_rom(cur_rom_entry.second)) {
|
||||
valid_game_roms.insert(cur_rom_entry.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::load_stored_rom(recomp::Game game) {
|
||||
auto find_it = game_roms.find(game);
|
||||
|
||||
if (find_it == game_roms.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> stored_rom_data = read_file(recomp::get_app_folder_path() / find_it->second.stored_filename);
|
||||
|
||||
if (!check_hash(stored_rom_data, find_it->second.xxhash3_value)) {
|
||||
// The ROM no longer has the right hash, delete it.
|
||||
std::filesystem::remove(recomp::get_app_folder_path() / find_it->second.stored_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
recomp::set_rom_contents(std::move(stored_rom_data));
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::array<uint8_t, 4> first_rom_bytes { 0x80, 0x37, 0x12, 0x40 };
|
||||
|
||||
enum class ByteswapType {
|
||||
NotByteswapped,
|
||||
Byteswapped4,
|
||||
Byteswapped2,
|
||||
Invalid
|
||||
};
|
||||
|
||||
ByteswapType check_rom_start(const std::vector<uint8_t>& rom_data) {
|
||||
if (rom_data.size() < 4) {
|
||||
return ByteswapType::Invalid;
|
||||
}
|
||||
|
||||
bool matched_all = true;
|
||||
|
||||
auto check_match = [&](uint8_t index0, uint8_t index1, uint8_t index2, uint8_t index3) {
|
||||
return
|
||||
rom_data[0] == first_rom_bytes[index0] &&
|
||||
rom_data[1] == first_rom_bytes[index1] &&
|
||||
rom_data[2] == first_rom_bytes[index2] &&
|
||||
rom_data[3] == first_rom_bytes[index3];
|
||||
};
|
||||
|
||||
// Check if the ROM is already in the correct byte order.
|
||||
if (check_match(0,1,2,3)) {
|
||||
return ByteswapType::NotByteswapped;
|
||||
}
|
||||
|
||||
// Check if the ROM has been byteswapped in groups of 4 bytes.
|
||||
if (check_match(3,2,1,0)) {
|
||||
return ByteswapType::Byteswapped4;
|
||||
}
|
||||
|
||||
// Check if the ROM has been byteswapped in groups of 2 bytes.
|
||||
if (check_match(1,0,3,2)) {
|
||||
return ByteswapType::Byteswapped2;
|
||||
}
|
||||
|
||||
// No match found.
|
||||
return ByteswapType::Invalid;
|
||||
}
|
||||
|
||||
void byteswap_data(std::vector<uint8_t>& rom_data, size_t index_xor) {
|
||||
for (size_t rom_pos = 0; rom_pos < rom_data.size(); rom_pos += 4) {
|
||||
uint8_t temp0 = rom_data[rom_pos + 0];
|
||||
uint8_t temp1 = rom_data[rom_pos + 1];
|
||||
uint8_t temp2 = rom_data[rom_pos + 2];
|
||||
uint8_t temp3 = rom_data[rom_pos + 3];
|
||||
|
||||
rom_data[rom_pos + (0 ^ index_xor)] = temp0;
|
||||
rom_data[rom_pos + (1 ^ index_xor)] = temp1;
|
||||
rom_data[rom_pos + (2 ^ index_xor)] = temp2;
|
||||
rom_data[rom_pos + (3 ^ index_xor)] = temp3;
|
||||
}
|
||||
}
|
||||
|
||||
recomp::RomValidationError recomp::select_rom(const std::filesystem::path& rom_path, Game game) {
|
||||
auto find_it = game_roms.find(game);
|
||||
|
||||
if (find_it == game_roms.end()) {
|
||||
return recomp::RomValidationError::OtherError;
|
||||
}
|
||||
|
||||
const RomEntry& game_entry = find_it->second;
|
||||
|
||||
std::vector<uint8_t> rom_data = read_file(rom_path);
|
||||
|
||||
if (rom_data.empty()) {
|
||||
return recomp::RomValidationError::FailedToOpen;
|
||||
}
|
||||
|
||||
// Pad the rom to the nearest multiple of 4 bytes.
|
||||
rom_data.resize((rom_data.size() + 3) & ~3);
|
||||
|
||||
ByteswapType byteswap_type = check_rom_start(rom_data);
|
||||
|
||||
switch (byteswap_type) {
|
||||
case ByteswapType::Invalid:
|
||||
return recomp::RomValidationError::NotARom;
|
||||
case ByteswapType::Byteswapped2:
|
||||
byteswap_data(rom_data, 1);
|
||||
break;
|
||||
case ByteswapType::Byteswapped4:
|
||||
byteswap_data(rom_data, 3);
|
||||
break;
|
||||
case ByteswapType::NotByteswapped:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!check_hash(rom_data, game_entry.xxhash3_value)) {
|
||||
const std::string_view name{ reinterpret_cast<const char*>(rom_data.data()) + 0x20, game_entry.internal_rom_name.size()};
|
||||
if (name == game_entry.internal_rom_name) {
|
||||
return recomp::RomValidationError::IncorrectVersion;
|
||||
}
|
||||
else {
|
||||
if (game == recomp::Game::MM && std::string_view{ reinterpret_cast<const char*>(rom_data.data()) + 0x20, 19 } == "THE LEGEND OF ZELDA") {
|
||||
return recomp::RomValidationError::NotYet;
|
||||
}
|
||||
else {
|
||||
return recomp::RomValidationError::IncorrectRom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_file(recomp::get_app_folder_path() / game_entry.stored_filename, rom_data);
|
||||
|
||||
return recomp::RomValidationError::Good;
|
||||
}
|
||||
|
||||
extern "C" void osGetMemSize_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 8 * 1024 * 1024;
|
||||
}
|
||||
|
||||
enum class StatusReg {
|
||||
FR = 0x04000000,
|
||||
};
|
||||
|
||||
extern "C" void cop0_status_write(recomp_context* ctx, gpr value) {
|
||||
uint32_t old_sr = ctx->status_reg;
|
||||
uint32_t new_sr = (uint32_t)value;
|
||||
uint32_t changed = old_sr ^ new_sr;
|
||||
|
||||
// Check if the FR bit changed
|
||||
if (changed & (uint32_t)StatusReg::FR) {
|
||||
// Check if the FR bit was set
|
||||
if (new_sr & (uint32_t)StatusReg::FR) {
|
||||
// FR = 1, odd single floats point to their own registers
|
||||
ctx->f_odd = &ctx->f1.u32l;
|
||||
ctx->mips3_float_mode = true;
|
||||
}
|
||||
// Otherwise, it was cleared
|
||||
else {
|
||||
// FR = 0, odd single floats point to the upper half of the previous register
|
||||
ctx->f_odd = &ctx->f0.u32h;
|
||||
ctx->mips3_float_mode = false;
|
||||
}
|
||||
|
||||
// Remove the FR bit from the changed bits as it's been handled
|
||||
changed &= ~(uint32_t)StatusReg::FR;
|
||||
}
|
||||
|
||||
// If any other bits were changed, assert false as they're not handled currently
|
||||
if (changed) {
|
||||
printf("Unhandled status register bits changed: 0x%08X\n", changed);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Update the status register in the context
|
||||
ctx->status_reg = new_sr;
|
||||
}
|
||||
|
||||
extern "C" gpr cop0_status_read(recomp_context* ctx) {
|
||||
return (gpr)(int32_t)ctx->status_reg;
|
||||
}
|
||||
|
||||
extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
|
||||
printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
extern "C" void do_break(uint32_t vram) {
|
||||
printf("Encountered break at original vram 0x%08X\n", vram);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg) {
|
||||
recomp_context ctx{};
|
||||
ctx.r29 = sp;
|
||||
ctx.r4 = arg;
|
||||
ctx.mips3_float_mode = 0;
|
||||
ctx.f_odd = &ctx.f0.u32h;
|
||||
recomp_func_t* func = get_function(addr);
|
||||
func(rdram, &ctx);
|
||||
}
|
||||
|
||||
// Recomp generation functions
|
||||
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
|
||||
gpr get_entrypoint_address();
|
||||
const char* get_rom_name();
|
||||
|
||||
void read_patch_data(uint8_t* rdram, gpr patch_data_address) {
|
||||
for (size_t i = 0; i < sizeof(mm_patches_bin); i++) {
|
||||
MEM_B(i, patch_data_address) = mm_patches_bin[i];
|
||||
}
|
||||
}
|
||||
|
||||
void init(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Initialize the overlays
|
||||
init_overlays();
|
||||
|
||||
// Get entrypoint from recomp function
|
||||
gpr entrypoint = get_entrypoint_address();
|
||||
|
||||
// Load overlays in the first 1MB
|
||||
load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024);
|
||||
|
||||
// Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000)
|
||||
recomp::do_rom_read(rdram, entrypoint, 0x10001000, 0x100000);
|
||||
|
||||
// Read in any extra data from patches
|
||||
read_patch_data(rdram, (gpr)(s32)0x80801000);
|
||||
|
||||
// Set up stack pointer
|
||||
ctx->r29 = 0xFFFFFFFF803FFFF0u;
|
||||
|
||||
// Set up context floats
|
||||
ctx->f_odd = &ctx->f0.u32h;
|
||||
ctx->mips3_float_mode = false;
|
||||
|
||||
// Initialize variables normally set by IPL3
|
||||
constexpr int32_t osTvType = 0x80000300;
|
||||
constexpr int32_t osRomType = 0x80000304;
|
||||
constexpr int32_t osRomBase = 0x80000308;
|
||||
constexpr int32_t osResetType = 0x8000030c;
|
||||
constexpr int32_t osCicId = 0x80000310;
|
||||
constexpr int32_t osVersion = 0x80000314;
|
||||
constexpr int32_t osMemSize = 0x80000318;
|
||||
constexpr int32_t osAppNMIBuffer = 0x8000031c;
|
||||
MEM_W(osTvType, 0) = 1; // NTSC
|
||||
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
|
||||
MEM_W(osResetType, 0) = 0; // cold reset
|
||||
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
|
||||
}
|
||||
|
||||
std::atomic<recomp::Game> game_started = recomp::Game::None;
|
||||
|
||||
void recomp::start_game(recomp::Game game) {
|
||||
game_started.store(game);
|
||||
game_started.notify_all();
|
||||
}
|
||||
|
||||
bool ultramodern::is_game_started() {
|
||||
return game_started.load() != recomp::Game::None;
|
||||
}
|
||||
|
||||
void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks);
|
||||
void set_input_callbacks(const ultramodern::input_callbacks_t& callback);
|
||||
|
||||
std::atomic_bool exited = false;
|
||||
|
||||
void ultramodern::quit() {
|
||||
exited.store(true);
|
||||
recomp::Game desired = recomp::Game::None;
|
||||
game_started.compare_exchange_strong(desired, recomp::Game::Quit);
|
||||
game_started.notify_all();
|
||||
}
|
||||
|
||||
void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks_) {
|
||||
recomp::check_all_stored_roms();
|
||||
set_audio_callbacks(audio_callbacks);
|
||||
set_input_callbacks(input_callbacks);
|
||||
|
||||
ultramodern::gfx_callbacks_t gfx_callbacks = gfx_callbacks_;
|
||||
|
||||
ultramodern::gfx_callbacks_t::gfx_data_t gfx_data{};
|
||||
|
||||
if (gfx_callbacks.create_gfx) {
|
||||
gfx_data = gfx_callbacks.create_gfx();
|
||||
}
|
||||
|
||||
if (window_handle == ultramodern::WindowHandle{}) {
|
||||
if (gfx_callbacks.create_window) {
|
||||
window_handle = gfx_callbacks.create_window(gfx_data);
|
||||
}
|
||||
else {
|
||||
assert(false && "No create_window callback provided");
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate rdram_buffer
|
||||
std::unique_ptr<uint8_t[]> rdram_buffer = std::make_unique<uint8_t[]>(ultramodern::rdram_size);
|
||||
std::memset(rdram_buffer.get(), 0, ultramodern::rdram_size);
|
||||
|
||||
std::thread game_thread{[](ultramodern::WindowHandle window_handle, uint8_t* rdram) {
|
||||
debug_printf("[Recomp] Starting\n");
|
||||
|
||||
ultramodern::set_native_thread_name("Game Start Thread");
|
||||
|
||||
ultramodern::preinit(rdram, window_handle);
|
||||
|
||||
game_started.wait(recomp::Game::None);
|
||||
recomp_context context{};
|
||||
|
||||
switch (game_started.load()) {
|
||||
case recomp::Game::MM:
|
||||
if (!recomp::load_stored_rom(recomp::Game::MM)) {
|
||||
recomp::message_box("Error opening stored ROM! Please restart this program.");
|
||||
}
|
||||
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
ultramodern::load_shader_cache({mm_shader_cache_bytes, sizeof(mm_shader_cache_bytes)});
|
||||
#endif
|
||||
|
||||
init(rdram, &context);
|
||||
try {
|
||||
recomp_entrypoint(rdram, &context);
|
||||
} catch (ultramodern::thread_terminated& terminated) {
|
||||
|
||||
}
|
||||
break;
|
||||
case recomp::Game::Quit:
|
||||
break;
|
||||
}
|
||||
|
||||
debug_printf("[Recomp] Quitting\n");
|
||||
}, window_handle, rdram_buffer.get()};
|
||||
|
||||
while (!exited) {
|
||||
ultramodern::sleep_milliseconds(1);
|
||||
if (gfx_callbacks.update_gfx != nullptr) {
|
||||
gfx_callbacks.update_gfx(gfx_data);
|
||||
}
|
||||
}
|
||||
game_thread.join();
|
||||
ultramodern::join_event_threads();
|
||||
ultramodern::join_thread_cleaner_thread();
|
||||
ultramodern::join_saving_thread();
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
bool dump_frame = false;
|
||||
|
||||
extern "C" void osSpTaskStartGo_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
//printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4);
|
||||
OSTask* task = TO_PTR(OSTask, ctx->r4);
|
||||
if (task->t.type == M_GFXTASK) {
|
||||
//printf("[sp] Gfx task: %08X\n", (uint32_t)ctx->r4);
|
||||
} else if (task->t.type == M_AUDTASK) {
|
||||
//printf("[sp] Audio task: %08X\n", (uint32_t)ctx->r4);
|
||||
}
|
||||
// For debugging
|
||||
if (dump_frame) {
|
||||
char addr_str[32];
|
||||
constexpr size_t ram_size = 0x800000;
|
||||
std::unique_ptr<char[]> ram_unswapped = std::make_unique<char[]>(ram_size);
|
||||
snprintf(addr_str, sizeof(addr_str) - 1, "%08X", task->t.data_ptr);
|
||||
addr_str[sizeof(addr_str) - 1] = '\0';
|
||||
std::ofstream dump_file{ "ramdump" + std::string{ addr_str } + ".bin", std::ios::binary};
|
||||
|
||||
for (size_t i = 0; i < ram_size; i++) {
|
||||
ram_unswapped[i] = rdram[i ^ 3];
|
||||
}
|
||||
|
||||
dump_file.write(ram_unswapped.get(), ram_size);
|
||||
dump_frame = false;
|
||||
}
|
||||
ultramodern::submit_rsp_task(rdram, ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskYield_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Ignore yield requests (acts as if the task completed before it received the yield request)
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskYielded_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Task yield requests are ignored, so always return 0 as tasks will never be yielded
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __osSpSetPc_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
// None of these functions need to be reimplemented, so stub them out
|
||||
extern "C" void osUnmapTLBAll_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// TODO this will need to be implemented in the future for any games that actually use the TLB
|
||||
}
|
||||
|
||||
extern "C" void osVoiceInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 11; // CONT_ERR_DEVICE
|
||||
}
|
||||
|
||||
extern "C" void osVoiceSetWord_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceCheckWord_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceStopReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceMaskDictionary_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceStartReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceControlGain_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceGetReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceClearDictionary_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
@ -1,163 +0,0 @@
|
||||
#include <memory>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osInitialize_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osInitialize();
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_common_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osInitialize();
|
||||
}
|
||||
|
||||
extern "C" void osCreateThread_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osCreateThread(rdram, (int32_t)ctx->r4, (OSId)ctx->r5, (int32_t)ctx->r6, (int32_t)ctx->r7,
|
||||
(int32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29));
|
||||
}
|
||||
|
||||
extern "C" void osStartThread_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osStartThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osStopThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osStopThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osDestroyThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osDestroyThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osYieldThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
// osYieldThread(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5);
|
||||
}
|
||||
|
||||
extern "C" void osGetThreadPri_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetThreadPri(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osGetThreadId_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetThreadId(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osCreateMesgQueue_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osCreateMesgQueue(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osRecvMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osRecvMesg(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osSendMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osSendMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osJamMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osJamMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osSetEventMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osSetEventMesg(rdram, (OSEvent)ctx->r4, (int32_t)ctx->r5, (OSMesg)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osViSetEvent_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osViSetEvent(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (u32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetCount();
|
||||
}
|
||||
|
||||
extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t total_count = osGetTime();
|
||||
ctx->r2 = (int32_t)(total_count >> 32);
|
||||
ctx->r3 = (int32_t)(total_count >> 0);
|
||||
}
|
||||
|
||||
extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu);
|
||||
uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10);
|
||||
ctx->r2 = osSetTimer(rdram, (int32_t)ctx->r4, countdown, interval, (int32_t)MEM_W(0x18, ctx->r29), (OSMesg)MEM_W(0x1C, ctx->r29));
|
||||
}
|
||||
|
||||
extern "C" void osStopTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osStopTimer(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osVirtualToPhysical((int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osInvalICache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osWritebackDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osWritebackDCacheAll_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osSetIntMask_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osDisableInt_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osRestoreInt_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osSetFpcCsr_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// For the Mario Party games (not working)
|
||||
//extern "C" void longjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// RecompJmpBuf* buf = TO_PTR(RecompJmpBuf, ctx->r4);
|
||||
//
|
||||
// // Check if this is a buffer that was set up with setjmp
|
||||
// if (buf->magic == SETJMP_MAGIC) {
|
||||
// // If so, longjmp to it
|
||||
// // Setjmp/longjmp does not work across threads, so verify that this buffer was made by this thread
|
||||
// assert(buf->owner == ultramodern::this_thread());
|
||||
// longjmp(buf->storage->buffer, ctx->r5);
|
||||
// } else {
|
||||
// // Otherwise, check if it was one built manually by the game with $ra pointing to a function
|
||||
// gpr sp = MEM_W(0, ctx->r4);
|
||||
// gpr ra = MEM_W(4, ctx->r4);
|
||||
// ctx->r29 = sp;
|
||||
// recomp_func_t* target = LOOKUP_FUNC(ra);
|
||||
// if (target == nullptr) {
|
||||
// fprintf(stderr, "Failed to find function for manual longjmp\n");
|
||||
// std::quick_exit(EXIT_FAILURE);
|
||||
// }
|
||||
// target(rdram, ctx);
|
||||
//
|
||||
// // TODO kill this thread if the target function returns
|
||||
// assert(false);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//#undef setjmp_recomp
|
||||
//extern "C" void setjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// fprintf(stderr, "Program called setjmp_recomp\n");
|
||||
// std::quick_exit(EXIT_FAILURE);
|
||||
//}
|
||||
//
|
||||
//extern "C" int32_t osGetThreadEx(void) {
|
||||
// return ultramodern::this_thread();
|
||||
//}
|
@ -1,47 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osViSetYScale_recomp(uint8_t* rdram, recomp_context * ctx) {
|
||||
osViSetYScale(ctx->f12.fl);
|
||||
}
|
||||
|
||||
extern "C" void osViSetXScale_recomp(uint8_t* rdram, recomp_context * ctx) {
|
||||
osViSetXScale(ctx->f12.fl);
|
||||
}
|
||||
|
||||
extern "C" void osCreateViManager_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViBlack_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViBlack((uint32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViSetSpecialFeatures_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSetSpecialFeatures((uint32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = (gpr)(int32_t)osViGetCurrentFramebuffer();
|
||||
}
|
||||
|
||||
extern "C" void osViGetNextFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = (gpr)(int32_t)osViGetNextFramebuffer();
|
||||
}
|
||||
|
||||
extern "C" void osViSwapBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSwapBuffer(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSetMode(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern uint64_t total_vis;
|
||||
|
||||
extern "C" void wait_one_frame(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint64_t cur_vis = total_vis;
|
||||
while (cur_vis == total_vis) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
|
||||
using ColourMap = Rml::UnorderedMap<Rml::String, Rml::Colourb>;
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
class PropertyParserColorHack : public Rml::PropertyParser {
|
||||
public:
|
||||
PropertyParserColorHack();
|
||||
|
@ -1,13 +1,13 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_debug.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "zelda_config.h"
|
||||
#include "zelda_debug.h"
|
||||
#include "promptfont.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "RmlUi/Core.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "ultramodern/rt64_layer.hpp"
|
||||
|
||||
ultramodern::GraphicsConfig new_options;
|
||||
Rml::DataModelHandle nav_help_model_handle;
|
||||
@ -16,9 +16,9 @@ Rml::DataModelHandle controls_model_handle;
|
||||
Rml::DataModelHandle graphics_model_handle;
|
||||
Rml::DataModelHandle sound_options_model_handle;
|
||||
|
||||
recomp::PromptContext prompt_context;
|
||||
recompui::PromptContext prompt_context;
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
const std::unordered_map<ButtonVariant, std::string> button_variants {
|
||||
{ButtonVariant::Primary, "primary"},
|
||||
{ButtonVariant::Secondary, "secondary"},
|
||||
@ -164,7 +164,7 @@ static bool cont_active = true;
|
||||
static recomp::InputDevice cur_device = recomp::InputDevice::Controller;
|
||||
|
||||
void recomp::finish_scanning_input(recomp::InputField scanned_field) {
|
||||
recomp::set_input_binding(static_cast<recomp::GameInput>(scanned_input_index), scanned_binding_index, cur_device, scanned_field);
|
||||
recomp::set_input_binding(static_cast<recomp::GameInput>(scanned_input_index), scanned_binding_index, cur_device, scanned_field);
|
||||
scanned_input_index = -1;
|
||||
scanned_binding_index = -1;
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
@ -173,7 +173,7 @@ void recomp::finish_scanning_input(recomp::InputField scanned_field) {
|
||||
}
|
||||
|
||||
void recomp::cancel_scanning_input() {
|
||||
recomp::stop_scanning_input();
|
||||
recomp::stop_scanning_input();
|
||||
scanned_input_index = -1;
|
||||
scanned_binding_index = -1;
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
@ -198,13 +198,13 @@ void recomp::config_menu_set_cont_or_kb(bool cont_interacted) {
|
||||
}
|
||||
|
||||
void close_config_menu_impl() {
|
||||
recomp::save_config();
|
||||
zelda64::save_config();
|
||||
|
||||
if (ultramodern::is_game_started()) {
|
||||
recomp::set_current_menu(recomp::Menu::None);
|
||||
recompui::set_current_menu(recompui::Menu::None);
|
||||
}
|
||||
else {
|
||||
recomp::set_current_menu(recomp::Menu::Launcher);
|
||||
recompui::set_current_menu(recompui::Menu::Launcher);
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,8 +241,8 @@ void close_config_menu() {
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
close_config_menu_impl();
|
||||
},
|
||||
recomp::ButtonVariant::Success,
|
||||
recomp::ButtonVariant::Error,
|
||||
recompui::ButtonVariant::Success,
|
||||
recompui::ButtonVariant::Error,
|
||||
true,
|
||||
"config__close-menu-button"
|
||||
);
|
||||
@ -252,7 +252,7 @@ void close_config_menu() {
|
||||
close_config_menu_impl();
|
||||
}
|
||||
|
||||
void recomp::open_quit_game_prompt() {
|
||||
void zelda64::open_quit_game_prompt() {
|
||||
prompt_context.open_prompt(
|
||||
"Are you sure you want to quit?",
|
||||
"Any progress since your last save will be lost.",
|
||||
@ -262,8 +262,8 @@ void recomp::open_quit_game_prompt() {
|
||||
ultramodern::quit();
|
||||
},
|
||||
[]() {},
|
||||
recomp::ButtonVariant::Error,
|
||||
recomp::ButtonVariant::Tertiary,
|
||||
recompui::ButtonVariant::Error,
|
||||
recompui::ButtonVariant::Tertiary,
|
||||
true,
|
||||
"config__quit-game-button"
|
||||
);
|
||||
@ -275,12 +275,12 @@ struct ControlOptionsContext {
|
||||
int gyro_sensitivity; // 0 to 100
|
||||
int mouse_sensitivity; // 0 to 100
|
||||
int joystick_deadzone; // 0 to 100
|
||||
recomp::TargetingMode targeting_mode;
|
||||
zelda64::TargetingMode targeting_mode;
|
||||
recomp::BackgroundInputMode background_input_mode;
|
||||
recomp::AutosaveMode autosave_mode;
|
||||
recomp::CameraInvertMode camera_invert_mode;
|
||||
recomp::AnalogCamMode analog_cam_mode;
|
||||
recomp::CameraInvertMode analog_camera_invert_mode;
|
||||
zelda64::AutosaveMode autosave_mode;
|
||||
zelda64::CameraInvertMode camera_invert_mode;
|
||||
zelda64::AnalogCamMode analog_cam_mode;
|
||||
zelda64::CameraInvertMode analog_camera_invert_mode;
|
||||
};
|
||||
|
||||
ControlOptionsContext control_options_context;
|
||||
@ -329,11 +329,11 @@ void recomp::set_joystick_deadzone(int deadzone) {
|
||||
}
|
||||
}
|
||||
|
||||
recomp::TargetingMode recomp::get_targeting_mode() {
|
||||
zelda64::TargetingMode zelda64::get_targeting_mode() {
|
||||
return control_options_context.targeting_mode;
|
||||
}
|
||||
|
||||
void recomp::set_targeting_mode(recomp::TargetingMode mode) {
|
||||
void zelda64::set_targeting_mode(zelda64::TargetingMode mode) {
|
||||
control_options_context.targeting_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("targeting_mode");
|
||||
@ -357,44 +357,44 @@ void recomp::set_background_input_mode(recomp::BackgroundInputMode mode) {
|
||||
);
|
||||
}
|
||||
|
||||
recomp::AutosaveMode recomp::get_autosave_mode() {
|
||||
zelda64::AutosaveMode zelda64::get_autosave_mode() {
|
||||
return control_options_context.autosave_mode;
|
||||
}
|
||||
|
||||
void recomp::set_autosave_mode(recomp::AutosaveMode mode) {
|
||||
void zelda64::set_autosave_mode(zelda64::AutosaveMode mode) {
|
||||
control_options_context.autosave_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("autosave_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::CameraInvertMode recomp::get_camera_invert_mode() {
|
||||
zelda64::CameraInvertMode zelda64::get_camera_invert_mode() {
|
||||
return control_options_context.camera_invert_mode;
|
||||
}
|
||||
|
||||
void recomp::set_camera_invert_mode(recomp::CameraInvertMode mode) {
|
||||
void zelda64::set_camera_invert_mode(zelda64::CameraInvertMode mode) {
|
||||
control_options_context.camera_invert_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("camera_invert_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::AnalogCamMode recomp::get_analog_cam_mode() {
|
||||
zelda64::AnalogCamMode zelda64::get_analog_cam_mode() {
|
||||
return control_options_context.analog_cam_mode;
|
||||
}
|
||||
|
||||
void recomp::set_analog_cam_mode(recomp::AnalogCamMode mode) {
|
||||
void zelda64::set_analog_cam_mode(zelda64::AnalogCamMode mode) {
|
||||
control_options_context.analog_cam_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("analog_cam_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::CameraInvertMode recomp::get_analog_camera_invert_mode() {
|
||||
zelda64::CameraInvertMode zelda64::get_analog_camera_invert_mode() {
|
||||
return control_options_context.analog_camera_invert_mode;
|
||||
}
|
||||
|
||||
void recomp::set_analog_camera_invert_mode(recomp::CameraInvertMode mode) {
|
||||
void zelda64::set_analog_camera_invert_mode(zelda64::CameraInvertMode mode) {
|
||||
control_options_context.analog_camera_invert_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("analog_camera_invert_mode");
|
||||
@ -417,43 +417,43 @@ struct SoundOptionsContext {
|
||||
|
||||
SoundOptionsContext sound_options_context;
|
||||
|
||||
void recomp::reset_sound_settings() {
|
||||
void zelda64::reset_sound_settings() {
|
||||
sound_options_context.reset();
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyAllVariables();
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_main_volume(int volume) {
|
||||
void zelda64::set_main_volume(int volume) {
|
||||
sound_options_context.main_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("main_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_main_volume() {
|
||||
int zelda64::get_main_volume() {
|
||||
return sound_options_context.main_volume.load();
|
||||
}
|
||||
|
||||
void recomp::set_bgm_volume(int volume) {
|
||||
void zelda64::set_bgm_volume(int volume) {
|
||||
sound_options_context.bgm_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("bgm_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_bgm_volume() {
|
||||
int zelda64::get_bgm_volume() {
|
||||
return sound_options_context.bgm_volume.load();
|
||||
}
|
||||
|
||||
void recomp::set_low_health_beeps_enabled(bool enabled) {
|
||||
void zelda64::set_low_health_beeps_enabled(bool enabled) {
|
||||
sound_options_context.low_health_beeps_enabled.store((int)enabled);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("low_health_beeps_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::get_low_health_beeps_enabled() {
|
||||
bool zelda64::get_low_health_beeps_enabled() {
|
||||
return (bool)sound_options_context.low_health_beeps_enabled.load();
|
||||
}
|
||||
|
||||
@ -471,7 +471,7 @@ struct DebugContext {
|
||||
bool debug_enabled = false;
|
||||
|
||||
DebugContext() {
|
||||
for (const auto& area : recomp::game_warps) {
|
||||
for (const auto& area : zelda64::game_warps) {
|
||||
area_names.emplace_back(area.name);
|
||||
}
|
||||
update_warp_names();
|
||||
@ -479,15 +479,15 @@ struct DebugContext {
|
||||
|
||||
void update_warp_names() {
|
||||
scene_names.clear();
|
||||
for (const auto& scene : recomp::game_warps[area_index].scenes) {
|
||||
for (const auto& scene : zelda64::game_warps[area_index].scenes) {
|
||||
scene_names.emplace_back(scene.name);
|
||||
}
|
||||
|
||||
entrance_names = recomp::game_warps[area_index].scenes[scene_index].entrances;
|
||||
entrance_names = zelda64::game_warps[area_index].scenes[scene_index].entrances;
|
||||
}
|
||||
};
|
||||
|
||||
void recomp::update_rml_display_refresh_rate() {
|
||||
void recompui::update_rml_display_refresh_rate() {
|
||||
static uint32_t lastRate = 0;
|
||||
if (!graphics_model_handle) return;
|
||||
|
||||
@ -500,7 +500,7 @@ void recomp::update_rml_display_refresh_rate() {
|
||||
|
||||
DebugContext debug_context;
|
||||
|
||||
class ConfigMenu : public recomp::MenuController {
|
||||
class ConfigMenu : public recompui::MenuController {
|
||||
public:
|
||||
ConfigMenu() {
|
||||
|
||||
@ -511,13 +511,13 @@ public:
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
return context->LoadDocument("assets/config_menu.rml");
|
||||
}
|
||||
void register_events(recomp::UiEventListenerInstancer& listener) override {
|
||||
recomp::register_event(listener, "apply_options",
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "apply_options",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
apply_graphics_config();
|
||||
});
|
||||
recomp::register_event(listener, "config_keydown",
|
||||
recompui::register_event(listener, "config_keydown",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (!prompt_context.open && event.GetId() == Rml::EventId::Keydown) {
|
||||
auto key = event.GetParameter<Rml::Input::KeyIdentifier>("key_identifier", Rml::Input::KeyIdentifier::KI_UNKNOWN);
|
||||
@ -533,23 +533,23 @@ public:
|
||||
}
|
||||
});
|
||||
// This needs to be separate from `close_config_menu` so it ensures that the event is only on the target
|
||||
recomp::register_event(listener, "close_config_menu_backdrop",
|
||||
recompui::register_event(listener, "close_config_menu_backdrop",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||
close_config_menu();
|
||||
}
|
||||
});
|
||||
recomp::register_event(listener, "close_config_menu",
|
||||
recompui::register_event(listener, "close_config_menu",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
close_config_menu();
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "open_quit_game_prompt",
|
||||
recompui::register_event(listener, "open_quit_game_prompt",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::open_quit_game_prompt();
|
||||
zelda64::open_quit_game_prompt();
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "toggle_input_device",
|
||||
recompui::register_event(listener, "toggle_input_device",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
cur_device = cur_device == recomp::InputDevice::Controller
|
||||
? recomp::InputDevice::Keyboard
|
||||
@ -558,7 +558,7 @@ public:
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "area_index_changed",
|
||||
recompui::register_event(listener, "area_index_changed",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
debug_context.area_index = event.GetParameter<int>("value", 0);
|
||||
debug_context.scene_index = 0;
|
||||
@ -570,7 +570,7 @@ public:
|
||||
debug_context.model_handle.DirtyVariable("entrance_names");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "scene_index_changed",
|
||||
recompui::register_event(listener, "scene_index_changed",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
debug_context.scene_index = event.GetParameter<int>("value", 0);
|
||||
debug_context.entrance_index = 0;
|
||||
@ -579,14 +579,14 @@ public:
|
||||
debug_context.model_handle.DirtyVariable("entrance_names");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "do_warp",
|
||||
recompui::register_event(listener, "do_warp",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::do_warp(debug_context.area_index, debug_context.scene_index, debug_context.entrance_index);
|
||||
zelda64::do_warp(debug_context.area_index, debug_context.scene_index, debug_context.entrance_index);
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "set_time",
|
||||
recompui::register_event(listener, "set_time",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_time(debug_context.set_time_day, debug_context.set_time_hour, debug_context.set_time_minute);
|
||||
zelda64::set_time(debug_context.set_time_day, debug_context.set_time_hour, debug_context.set_time_minute);
|
||||
});
|
||||
}
|
||||
|
||||
@ -722,7 +722,7 @@ public:
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
scanned_input_index = inputs.at(0).Get<size_t>();
|
||||
scanned_binding_index = inputs.at(1).Get<size_t>();
|
||||
recomp::start_scanning_input(cur_device);
|
||||
recomp::start_scanning_input(cur_device);
|
||||
model_handle.DirtyVariable("active_binding_input");
|
||||
model_handle.DirtyVariable("active_binding_slot");
|
||||
});
|
||||
@ -730,18 +730,18 @@ public:
|
||||
constructor.BindEventCallback("reset_input_bindings_to_defaults",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
if (cur_device == recomp::InputDevice::Controller) {
|
||||
recomp::reset_cont_input_bindings();
|
||||
zelda64::reset_cont_input_bindings();
|
||||
} else {
|
||||
recomp::reset_kb_input_bindings();
|
||||
zelda64::reset_kb_input_bindings();
|
||||
}
|
||||
model_handle.DirtyAllVariables();
|
||||
});
|
||||
|
||||
constructor.BindEventCallback("clear_input_bindings",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>());
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>());
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
recomp::set_input_binding(input, binding_index, cur_device, recomp::InputField{});
|
||||
recomp::set_input_binding(input, binding_index, cur_device, recomp::InputField{});
|
||||
}
|
||||
model_handle.DirtyVariable("inputs");
|
||||
});
|
||||
@ -776,7 +776,7 @@ public:
|
||||
|
||||
virtual int Size(void* ptr) override { return recomp::bindings_per_input; }
|
||||
virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>((uintptr_t)ptr);
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>((uintptr_t)ptr);
|
||||
return Rml::DataVariable{&input_field_definition_instance, &recomp::get_input_binding(input, address.index, cur_device)};
|
||||
}
|
||||
};
|
||||
@ -812,7 +812,7 @@ public:
|
||||
return Rml::DataVariable(&binding_array_var_instance, nullptr);
|
||||
}
|
||||
else {
|
||||
recomp::GameInput input = recomp::get_input_from_enum_name(address.name);
|
||||
recomp::GameInput input = recomp::get_input_from_enum_name(address.name);
|
||||
if (input != recomp::GameInput::COUNT) {
|
||||
return Rml::DataVariable(&binding_container_var_instance, (void*)(uintptr_t)input);
|
||||
}
|
||||
@ -966,14 +966,14 @@ public:
|
||||
constructor.Bind("prompt__confirmLabel", &prompt_context.confirmLabel);
|
||||
constructor.Bind("prompt__cancelLabel", &prompt_context.cancelLabel);
|
||||
|
||||
constructor.BindEventCallback("prompt__on_click", &recomp::PromptContext::on_click, &prompt_context);
|
||||
constructor.BindEventCallback("prompt__on_click", &recompui::PromptContext::on_click, &prompt_context);
|
||||
|
||||
prompt_context.model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_bindings(Rml::Context* context) override {
|
||||
// initially set cont state for ui help
|
||||
recomp::config_menu_set_cont_or_kb(recomp::get_cont_active());
|
||||
recomp::config_menu_set_cont_or_kb(recompui::get_cont_active());
|
||||
make_nav_help_bindings(context);
|
||||
make_general_bindings(context);
|
||||
make_controls_bindings(context);
|
||||
@ -984,22 +984,22 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<recomp::MenuController> recomp::create_config_menu() {
|
||||
std::unique_ptr<recompui::MenuController> recompui::create_config_menu() {
|
||||
return std::make_unique<ConfigMenu>();
|
||||
}
|
||||
|
||||
bool recomp::get_debug_mode_enabled() {
|
||||
bool zelda64::get_debug_mode_enabled() {
|
||||
return debug_context.debug_enabled;
|
||||
}
|
||||
|
||||
void recomp::set_debug_mode_enabled(bool enabled) {
|
||||
void zelda64::set_debug_mode_enabled(bool enabled) {
|
||||
debug_context.debug_enabled = enabled;
|
||||
if (debug_context.model_handle) {
|
||||
debug_context.model_handle.DirtyVariable("debug_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::update_supported_options() {
|
||||
void recompui::update_supported_options() {
|
||||
msaa2x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
msaa4x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
msaa8x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
@ -1010,7 +1010,7 @@ void recomp::update_supported_options() {
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
}
|
||||
|
||||
void recomp::toggle_fullscreen() {
|
||||
void recompui::toggle_fullscreen() {
|
||||
new_options.wm_option = (new_options.wm_option == ultramodern::WindowMode::Windowed) ? ultramodern::WindowMode::Fullscreen : ultramodern::WindowMode::Windowed;
|
||||
apply_graphics_config();
|
||||
graphics_model_handle.DirtyVariable("wm_option");
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_game.h"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "librecomp/game.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "RmlUi/Core.h"
|
||||
#include "nfd.h"
|
||||
#include <filesystem>
|
||||
@ -11,6 +11,8 @@ std::string version_number = "v1.1.1";
|
||||
Rml::DataModelHandle model_handle;
|
||||
bool mm_rom_valid = false;
|
||||
|
||||
extern std::vector<recomp::GameEntry> supported_games;
|
||||
|
||||
void select_rom() {
|
||||
nfdnchar_t* native_path = nullptr;
|
||||
nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
|
||||
@ -21,39 +23,39 @@ void select_rom() {
|
||||
NFD_FreePathN(native_path);
|
||||
native_path = nullptr;
|
||||
|
||||
recomp::RomValidationError rom_error = recomp::select_rom(path, recomp::Game::MM);
|
||||
|
||||
switch (rom_error) {
|
||||
case recomp::RomValidationError::Good:
|
||||
mm_rom_valid = true;
|
||||
model_handle.DirtyVariable("mm_rom_valid");
|
||||
break;
|
||||
case recomp::RomValidationError::FailedToOpen:
|
||||
recomp::message_box("Failed to open ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotARom:
|
||||
recomp::message_box("This is not a valid ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectRom:
|
||||
recomp::message_box("This ROM is not the correct game.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotYet:
|
||||
recomp::message_box("This game isn't supported yet.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectVersion:
|
||||
recomp::message_box("This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
||||
break;
|
||||
case recomp::RomValidationError::OtherError:
|
||||
recomp::message_box("An unknown error has occurred.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
recomp::RomValidationError rom_error = recomp::select_rom(path, supported_games[0].game_id);
|
||||
switch (rom_error) {
|
||||
case recomp::RomValidationError::Good:
|
||||
mm_rom_valid = true;
|
||||
model_handle.DirtyVariable("mm_rom_valid");
|
||||
break;
|
||||
case recomp::RomValidationError::FailedToOpen:
|
||||
recompui::message_box("Failed to open ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotARom:
|
||||
recompui::message_box("This is not a valid ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectRom:
|
||||
recompui::message_box("This ROM is not the correct game.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotYet:
|
||||
recompui::message_box("This game isn't supported yet.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectVersion:
|
||||
recompui::message_box(
|
||||
"This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
||||
break;
|
||||
case recomp::RomValidationError::OtherError:
|
||||
recompui::message_box("An unknown error has occurred.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LauncherMenu : public recomp::MenuController {
|
||||
class LauncherMenu : public recompui::MenuController {
|
||||
public:
|
||||
LauncherMenu() {
|
||||
mm_rom_valid = recomp::is_rom_valid(recomp::Game::MM);
|
||||
mm_rom_valid = recomp::is_rom_valid(supported_games[0].game_id);
|
||||
}
|
||||
~LauncherMenu() override {
|
||||
|
||||
@ -61,37 +63,37 @@ public:
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
return context->LoadDocument("assets/launcher.rml");
|
||||
}
|
||||
void register_events(recomp::UiEventListenerInstancer& listener) override {
|
||||
recomp::register_event(listener, "select_rom",
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "select_rom",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
select_rom();
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "rom_selected",
|
||||
recompui::register_event(listener, "rom_selected",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
mm_rom_valid = true;
|
||||
model_handle.DirtyVariable("mm_rom_valid");
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "start_game",
|
||||
recompui::register_event(listener, "start_game",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::start_game(recomp::Game::MM);
|
||||
recomp::set_current_menu(recomp::Menu::None);
|
||||
recomp::start_game(supported_games[0].game_id);
|
||||
recompui::set_current_menu(recompui::Menu::None);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "open_controls",
|
||||
recompui::register_event(listener, "open_controls",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
recomp::set_config_submenu(recomp::ConfigSubmenu::Controls);
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::Controls);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "open_settings",
|
||||
recompui::register_event(listener, "open_settings",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
recomp::set_config_submenu(recomp::ConfigSubmenu::General);
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::General);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "exit_game",
|
||||
recompui::register_event(listener, "exit_game",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
ultramodern::quit();
|
||||
}
|
||||
@ -107,6 +109,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<recomp::MenuController> recomp::create_launcher_menu() {
|
||||
std::unique_ptr<recompui::MenuController> recompui::create_launcher_menu() {
|
||||
return std::make_unique<LauncherMenu>();
|
||||
}
|
||||
|
@ -4,16 +4,22 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#ifdef _WIN32
|
||||
#include <SDL_video.h>
|
||||
#else
|
||||
#include <SDL2/SDL_video.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "librecomp/game.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "ui_rml_hacks.hpp"
|
||||
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
#include "rt64_layer.h"
|
||||
#include "ultramodern/rt64_layer.hpp"
|
||||
#include "rt64_render_hooks.h"
|
||||
#include "rt64_render_interface_builders.h"
|
||||
|
||||
@ -718,7 +724,7 @@ Rml::Element* get_target(Rml::ElementDocument* document, Rml::Element* element)
|
||||
return element;
|
||||
}
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
class UiEventListener : public Rml::EventListener {
|
||||
event_handler_t* handler_;
|
||||
Rml::String param_;
|
||||
@ -759,7 +765,7 @@ namespace recomp {
|
||||
};
|
||||
}
|
||||
|
||||
void recomp::register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler) {
|
||||
void recompui::register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler) {
|
||||
listener.register_event(name, handler);
|
||||
}
|
||||
|
||||
@ -779,8 +785,8 @@ Rml::Element* find_autofocus_element(Rml::Element* start) {
|
||||
struct UIContext {
|
||||
struct UIRenderContext render;
|
||||
class {
|
||||
std::unordered_map<recomp::Menu, std::unique_ptr<recomp::MenuController>> menus;
|
||||
std::unordered_map<recomp::Menu, Rml::ElementDocument*> documents;
|
||||
std::unordered_map<recompui::Menu, std::unique_ptr<recompui::MenuController>> menus;
|
||||
std::unordered_map<recompui::Menu, Rml::ElementDocument*> documents;
|
||||
Rml::ElementDocument* current_document;
|
||||
Rml::Element* prev_focused;
|
||||
bool mouse_is_active_changed = false;
|
||||
@ -794,13 +800,13 @@ struct UIContext {
|
||||
std::unique_ptr<SystemInterface_SDL> system_interface;
|
||||
std::unique_ptr<RmlRenderInterface_RT64> render_interface;
|
||||
Rml::Context* context;
|
||||
recomp::UiEventListenerInstancer event_listener_instancer;
|
||||
recompui::UiEventListenerInstancer event_listener_instancer;
|
||||
|
||||
void unload() {
|
||||
render_interface.reset();
|
||||
}
|
||||
|
||||
void swap_document(recomp::Menu menu) {
|
||||
void swap_document(recompui::Menu menu) {
|
||||
if (current_document != nullptr) {
|
||||
Rml::Element* window_el = current_document->GetElementById("window");
|
||||
if (window_el != nullptr) {
|
||||
@ -831,7 +837,7 @@ struct UIContext {
|
||||
mouse_is_active_initialized = false;
|
||||
}
|
||||
|
||||
void swap_config_menu(recomp::ConfigSubmenu submenu) {
|
||||
void swap_config_menu(recompui::ConfigSubmenu submenu) {
|
||||
if (current_document != nullptr) {
|
||||
Rml::Element* config_tabset_base = current_document->GetElementById("config_tabset");
|
||||
if (config_tabset_base != nullptr) {
|
||||
@ -911,7 +917,7 @@ struct UIContext {
|
||||
}
|
||||
|
||||
if (mouse_is_active_initialized) {
|
||||
recomp::set_cursor_visible(mouse_is_active);
|
||||
recompui::set_cursor_visible(mouse_is_active);
|
||||
}
|
||||
|
||||
if (current_document == nullptr) {
|
||||
@ -939,7 +945,6 @@ struct UIContext {
|
||||
if (cont_is_active || non_mouse_interacted) {
|
||||
if (non_mouse_interacted) {
|
||||
auto focusedEl = current_document->GetFocusLeafNode();
|
||||
Rml::Variant* ti = focusedEl == nullptr ? nullptr : focusedEl->GetAttribute("tab-index");
|
||||
if (focusedEl == nullptr || RecompRml::CanFocusElement(focusedEl) != RecompRml::CanFocus::Yes) {
|
||||
Rml::Element* element = find_autofocus_element(current_document);
|
||||
if (element != nullptr) {
|
||||
@ -981,14 +986,14 @@ struct UIContext {
|
||||
}
|
||||
}
|
||||
|
||||
void add_menu(recomp::Menu menu, std::unique_ptr<recomp::MenuController>&& controller) {
|
||||
void add_menu(recompui::Menu menu, std::unique_ptr<recompui::MenuController>&& controller) {
|
||||
menus.emplace(menu, std::move(controller));
|
||||
}
|
||||
|
||||
void update_config_menu_loop(bool menu_changed) {
|
||||
static int prevTab = -1;
|
||||
if (menu_changed) prevTab = -1;
|
||||
recomp::update_rml_display_refresh_rate();
|
||||
recompui::update_rml_display_refresh_rate();
|
||||
|
||||
Rml::ElementTabSet *tabset = (Rml::ElementTabSet *)current_document->GetElementById("config_tabset");
|
||||
if (tabset == nullptr) return;
|
||||
@ -1022,7 +1027,7 @@ struct UIContext {
|
||||
void update_prompt_loop(void) {
|
||||
static bool wasShowingPrompt = false;
|
||||
|
||||
recomp::PromptContext *ctx = recomp::get_prompt_context();
|
||||
recompui::PromptContext *ctx = recompui::get_prompt_context();
|
||||
if (!ctx->open && wasShowingPrompt) {
|
||||
Rml::Element* focused = current_document->GetFocusLeafNode();
|
||||
if (focused) focused->Blur();
|
||||
@ -1088,8 +1093,8 @@ struct UIContext {
|
||||
|
||||
Rml::Element *confirmButton = current_document->GetElementById("prompt__confirm-button");
|
||||
Rml::Element *cancelButton = current_document->GetElementById("prompt__cancel-button");
|
||||
if (confirmButton != nullptr) confirmButton->SetClassNames("button button--" + recomp::button_variants.at(ctx->confirmVariant));
|
||||
if (cancelButton != nullptr) cancelButton->SetClassNames( "button button--" + recomp::button_variants.at(ctx->cancelVariant));
|
||||
if (confirmButton != nullptr) confirmButton->SetClassNames("button button--" + recompui::button_variants.at(ctx->confirmVariant));
|
||||
if (cancelButton != nullptr) cancelButton->SetClassNames( "button button--" + recompui::button_variants.at(ctx->cancelVariant));
|
||||
}
|
||||
} rml;
|
||||
};
|
||||
@ -1100,7 +1105,7 @@ std::mutex ui_context_mutex{};
|
||||
// TODO make this not be global
|
||||
extern SDL_Window* window;
|
||||
|
||||
void recomp::get_window_size(int& width, int& height) {
|
||||
void recompui::get_window_size(int& width, int& height) {
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
}
|
||||
|
||||
@ -1110,8 +1115,8 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
#endif
|
||||
ui_context = std::make_unique<UIContext>();
|
||||
|
||||
ui_context->rml.add_menu(recomp::Menu::Config, recomp::create_config_menu());
|
||||
ui_context->rml.add_menu(recomp::Menu::Launcher, recomp::create_launcher_menu());
|
||||
ui_context->rml.add_menu(recompui::Menu::Config, recompui::create_config_menu());
|
||||
ui_context->rml.add_menu(recompui::Menu::Launcher, recompui::create_launcher_menu());
|
||||
|
||||
ui_context->render.interface = interface;
|
||||
ui_context->render.device = device;
|
||||
@ -1129,7 +1134,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
Rml::Initialise();
|
||||
|
||||
// Apply the hack to replace RmlUi's default color parser with one that conforms to HTML5 alpha parsing for SASS compatibility
|
||||
recomp::apply_color_hack();
|
||||
recompui::apply_color_hack();
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
@ -1167,16 +1172,16 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
|
||||
moodycamel::ConcurrentQueue<SDL_Event> ui_event_queue{};
|
||||
|
||||
void recomp::queue_event(const SDL_Event& event) {
|
||||
void recompui::queue_event(const SDL_Event& event) {
|
||||
ui_event_queue.enqueue(event);
|
||||
}
|
||||
|
||||
bool recomp::try_deque_event(SDL_Event& out) {
|
||||
bool recompui::try_deque_event(SDL_Event& out) {
|
||||
return ui_event_queue.try_dequeue(out);
|
||||
}
|
||||
|
||||
std::atomic<recomp::Menu> open_menu = recomp::Menu::Launcher;
|
||||
std::atomic<recomp::ConfigSubmenu> open_config_submenu = recomp::ConfigSubmenu::Count;
|
||||
std::atomic<recompui::Menu> open_menu = recompui::Menu::Launcher;
|
||||
std::atomic<recompui::ConfigSubmenu> open_config_submenu = recompui::ConfigSubmenu::Count;
|
||||
|
||||
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
||||
switch (button.button) {
|
||||
@ -1236,15 +1241,15 @@ void apply_background_input_mode() {
|
||||
last_input_mode = cur_input_mode;
|
||||
}
|
||||
|
||||
bool recomp::get_cont_active() {
|
||||
bool recompui::get_cont_active() {
|
||||
return ui_context->rml.cont_is_active;
|
||||
}
|
||||
|
||||
void recomp::set_cont_active(bool active) {
|
||||
void recompui::set_cont_active(bool active) {
|
||||
ui_context->rml.cont_is_active = active;
|
||||
}
|
||||
|
||||
void recomp::activate_mouse() {
|
||||
void recompui::activate_mouse() {
|
||||
ui_context->rml.update_primary_input(true, false);
|
||||
ui_context->rml.update_focus(true, false);
|
||||
}
|
||||
@ -1267,12 +1272,12 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
bool reload_sheets = is_reload_held && !was_reload_held;
|
||||
was_reload_held = is_reload_held;
|
||||
|
||||
static recomp::Menu prev_menu = recomp::Menu::None;
|
||||
recomp::Menu cur_menu = open_menu.load();
|
||||
static recompui::Menu prev_menu = recompui::Menu::None;
|
||||
recompui::Menu cur_menu = open_menu.load();
|
||||
|
||||
if (reload_sheets) {
|
||||
ui_context->rml.load_documents();
|
||||
prev_menu = recomp::Menu::None;
|
||||
prev_menu = recompui::Menu::None;
|
||||
}
|
||||
|
||||
bool menu_changed = cur_menu != prev_menu;
|
||||
@ -1280,10 +1285,10 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
ui_context->rml.swap_document(cur_menu);
|
||||
}
|
||||
|
||||
recomp::ConfigSubmenu config_submenu = open_config_submenu.load();
|
||||
if (config_submenu != recomp::ConfigSubmenu::Count) {
|
||||
recompui::ConfigSubmenu config_submenu = open_config_submenu.load();
|
||||
if (config_submenu != recompui::ConfigSubmenu::Count) {
|
||||
ui_context->rml.swap_config_menu(config_submenu);
|
||||
open_config_submenu.store(recomp::ConfigSubmenu::Count);
|
||||
open_config_submenu.store(recompui::ConfigSubmenu::Count);
|
||||
}
|
||||
|
||||
prev_menu = cur_menu;
|
||||
@ -1296,15 +1301,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
bool cont_interacted = false;
|
||||
bool kb_interacted = false;
|
||||
|
||||
if (cur_menu == recomp::Menu::Config) {
|
||||
if (cur_menu == recompui::Menu::Config) {
|
||||
ui_context->rml.update_config_menu_loop(menu_changed);
|
||||
}
|
||||
if (cur_menu != recomp::Menu::None) {
|
||||
if (cur_menu != recompui::Menu::None) {
|
||||
ui_context->rml.update_prompt_loop();
|
||||
}
|
||||
|
||||
while (recomp::try_deque_event(cur_event)) {
|
||||
bool menu_is_open = cur_menu != recomp::Menu::None;
|
||||
while (recompui::try_deque_event(cur_event)) {
|
||||
bool menu_is_open = cur_menu != recompui::Menu::None;
|
||||
|
||||
if (!recomp::all_input_disabled()) {
|
||||
// Implement some additional behavior for specific events on top of what RmlUi normally does with them.
|
||||
@ -1323,7 +1328,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
last_mouse_pos[1] = cur_event.motion.y;
|
||||
|
||||
// if controller is the primary input, don't use mouse movement to allow cursor to reactivate
|
||||
if (recomp::get_cont_active()) {
|
||||
if (recompui::get_cont_active()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1401,15 +1406,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
}
|
||||
|
||||
if (open_config) {
|
||||
cur_menu = recomp::Menu::Config;
|
||||
open_menu.store(recomp::Menu::Config);
|
||||
cur_menu = recompui::Menu::Config;
|
||||
open_menu.store(recompui::Menu::Config);
|
||||
ui_context->rml.swap_document(cur_menu);
|
||||
}
|
||||
}
|
||||
} // end dequeue event loop
|
||||
|
||||
if (cont_interacted || kb_interacted || mouse_clicked) {
|
||||
recomp::set_cont_active(cont_interacted);
|
||||
recompui::set_cont_active(cont_interacted);
|
||||
}
|
||||
recomp::config_menu_set_cont_or_kb(ui_context->rml.cont_is_active);
|
||||
|
||||
@ -1421,7 +1426,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
ui_context->rml.update_primary_input(mouse_moved, non_mouse_interacted);
|
||||
ui_context->rml.update_focus(mouse_moved, non_mouse_interacted);
|
||||
|
||||
if (cur_menu != recomp::Menu::None) {
|
||||
if (cur_menu != recompui::Menu::None) {
|
||||
int width = swap_chain_framebuffer->getWidth();
|
||||
int height = swap_chain_framebuffer->getHeight();
|
||||
|
||||
@ -1457,25 +1462,25 @@ void set_rt64_hooks() {
|
||||
RT64::SetRenderHooks(init_hook, draw_hook, deinit_hook);
|
||||
}
|
||||
|
||||
void recomp::set_current_menu(Menu menu) {
|
||||
void recompui::set_current_menu(Menu menu) {
|
||||
open_menu.store(menu);
|
||||
if (menu == recomp::Menu::None) {
|
||||
if (menu == recompui::Menu::None) {
|
||||
ui_context->rml.system_interface->SetMouseCursor("arrow");
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_config_submenu(recomp::ConfigSubmenu submenu) {
|
||||
void recompui::set_config_submenu(recompui::ConfigSubmenu submenu) {
|
||||
open_config_submenu.store(submenu);
|
||||
}
|
||||
|
||||
void recomp::destroy_ui() {
|
||||
void recompui::destroy_ui() {
|
||||
}
|
||||
|
||||
recomp::Menu recomp::get_current_menu() {
|
||||
recompui::Menu recompui::get_current_menu() {
|
||||
return open_menu.load();
|
||||
}
|
||||
|
||||
void recomp::message_box(const char* msg) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, recomp::program_name.data(), msg, nullptr);
|
||||
void recompui::message_box(const char* msg) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, zelda64::program_name.data(), msg, nullptr);
|
||||
printf("[ERROR] %s\n", msg);
|
||||
}
|
||||
|
@ -1,67 +0,0 @@
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
#include <cassert>
|
||||
|
||||
static uint32_t sample_rate = 48000;
|
||||
|
||||
static ultramodern::audio_callbacks_t audio_callbacks;
|
||||
|
||||
void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks) {
|
||||
audio_callbacks = callbacks;
|
||||
}
|
||||
|
||||
void ultramodern::init_audio() {
|
||||
// Pick an initial dummy sample rate; this will be set by the game later to the true sample rate.
|
||||
set_audio_frequency(48000);
|
||||
}
|
||||
|
||||
void ultramodern::set_audio_frequency(uint32_t freq) {
|
||||
if (audio_callbacks.set_frequency) {
|
||||
audio_callbacks.set_frequency(freq);
|
||||
}
|
||||
sample_rate = freq;
|
||||
}
|
||||
|
||||
void ultramodern::queue_audio_buffer(RDRAM_ARG PTR(int16_t) audio_data_, uint32_t byte_count) {
|
||||
// Ensure that the byte count is an integer multiple of samples.
|
||||
assert((byte_count & 1) == 0);
|
||||
|
||||
// Calculate the number of samples from the number of bytes.
|
||||
uint32_t sample_count = byte_count / sizeof(int16_t);
|
||||
|
||||
// Queue the swapped audio data.
|
||||
if (audio_callbacks.queue_samples) {
|
||||
audio_callbacks.queue_samples(TO_PTR(int16_t, audio_data_), sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
// For SDL2
|
||||
//uint32_t buffer_offset_frames = 1;
|
||||
// For Godot
|
||||
float buffer_offset_frames = 0.5f;
|
||||
|
||||
// If there's ever any audio popping, check here first. Some games are very sensitive to
|
||||
// the remaining sample count and reporting a number that's too high here can lead to issues.
|
||||
// Reporting a number that's too low can lead to audio lag in some games.
|
||||
uint32_t ultramodern::get_remaining_audio_bytes() {
|
||||
// Get the number of remaining buffered audio bytes.
|
||||
uint32_t buffered_byte_count;
|
||||
if (audio_callbacks.get_frames_remaining != nullptr) {
|
||||
buffered_byte_count = audio_callbacks.get_frames_remaining() * 2 * sizeof(int16_t);
|
||||
}
|
||||
else {
|
||||
buffered_byte_count = 100;
|
||||
}
|
||||
// Adjust the reported count to be some number of refreshes in the future, which helps ensure that
|
||||
// there are enough samples even if the audio thread experiences a small amount of lag. This prevents
|
||||
// audio popping on games that use the buffered audio byte count to determine how many samples
|
||||
// to generate.
|
||||
uint32_t samples_per_vi = (sample_rate / 60);
|
||||
if (buffered_byte_count > static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) {
|
||||
buffered_byte_count -= static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi);
|
||||
}
|
||||
else {
|
||||
buffered_byte_count = 0;
|
||||
}
|
||||
return buffered_byte_count;
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
#ifndef __CONFIG_HPP__
|
||||
#define __CONFIG_HPP__
|
||||
|
||||
#include "common/rt64_user_configuration.h"
|
||||
|
||||
namespace ultramodern {
|
||||
enum class Resolution {
|
||||
Original,
|
||||
Original2x,
|
||||
Auto,
|
||||
OptionCount
|
||||
};
|
||||
enum class WindowMode {
|
||||
Windowed,
|
||||
Fullscreen,
|
||||
OptionCount
|
||||
};
|
||||
enum class HUDRatioMode {
|
||||
Original,
|
||||
Clamp16x9,
|
||||
Full,
|
||||
OptionCount
|
||||
};
|
||||
enum class GraphicsApi {
|
||||
Auto,
|
||||
D3D12,
|
||||
Vulkan,
|
||||
OptionCount
|
||||
};
|
||||
enum class HighPrecisionFramebuffer {
|
||||
Auto,
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
struct GraphicsConfig {
|
||||
Resolution res_option;
|
||||
WindowMode wm_option;
|
||||
HUDRatioMode hr_option;
|
||||
GraphicsApi api_option;
|
||||
RT64::UserConfiguration::AspectRatio ar_option;
|
||||
RT64::UserConfiguration::Antialiasing msaa_option;
|
||||
RT64::UserConfiguration::RefreshRate rr_option;
|
||||
HighPrecisionFramebuffer hpfb_option;
|
||||
int rr_manual_value;
|
||||
int ds_option;
|
||||
bool developer_mode;
|
||||
|
||||
auto operator<=>(const GraphicsConfig& rhs) const = default;
|
||||
};
|
||||
|
||||
void set_graphics_config(const GraphicsConfig& config);
|
||||
GraphicsConfig get_graphics_config();
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::Resolution, {
|
||||
{ultramodern::Resolution::Original, "Original"},
|
||||
{ultramodern::Resolution::Original2x, "Original2x"},
|
||||
{ultramodern::Resolution::Auto, "Auto"},
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::WindowMode, {
|
||||
{ultramodern::WindowMode::Windowed, "Windowed"},
|
||||
{ultramodern::WindowMode::Fullscreen, "Fullscreen"}
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::HUDRatioMode, {
|
||||
{ultramodern::HUDRatioMode::Original, "Original"},
|
||||
{ultramodern::HUDRatioMode::Clamp16x9, "Clamp16x9"},
|
||||
{ultramodern::HUDRatioMode::Full, "Full"},
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::GraphicsApi, {
|
||||
{ultramodern::GraphicsApi::Auto, "Auto"},
|
||||
{ultramodern::GraphicsApi::D3D12, "D3D12"},
|
||||
{ultramodern::GraphicsApi::Vulkan, "Vulkan"},
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::HighPrecisionFramebuffer, {
|
||||
{ultramodern::HighPrecisionFramebuffer::Auto, "Auto"},
|
||||
{ultramodern::HighPrecisionFramebuffer::On, "On"},
|
||||
{ultramodern::HighPrecisionFramebuffer::Off, "Off"},
|
||||
});
|
||||
};
|
||||
|
||||
#endif
|
@ -1,620 +0,0 @@
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <cstring>
|
||||
|
||||
#include "blockingconcurrentqueue.h"
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
#include "config.hpp"
|
||||
#include "rt64_layer.h"
|
||||
#include "recomp.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "rsp.h"
|
||||
|
||||
struct SpTaskAction {
|
||||
OSTask task;
|
||||
};
|
||||
|
||||
struct SwapBuffersAction {
|
||||
uint32_t origin;
|
||||
};
|
||||
|
||||
struct UpdateConfigAction {
|
||||
};
|
||||
|
||||
struct LoadShaderCacheAction {
|
||||
std::span<const char> data;
|
||||
};
|
||||
|
||||
using Action = std::variant<SpTaskAction, SwapBuffersAction, UpdateConfigAction, LoadShaderCacheAction>;
|
||||
|
||||
static struct {
|
||||
struct {
|
||||
std::thread thread;
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
PTR(void) current_buffer = NULLPTR;
|
||||
PTR(void) next_buffer = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
int retrace_count = 1;
|
||||
} vi;
|
||||
struct {
|
||||
std::thread gfx_thread;
|
||||
std::thread task_thread;
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
} sp;
|
||||
struct {
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
} dp;
|
||||
struct {
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
} ai;
|
||||
struct {
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
} si;
|
||||
// The same message queue may be used for multiple events, so share a mutex for all of them
|
||||
std::mutex message_mutex;
|
||||
uint8_t* rdram;
|
||||
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
|
||||
moodycamel::BlockingConcurrentQueue<OSTask*> sp_task_queue{};
|
||||
moodycamel::ConcurrentQueue<OSThread*> deleted_threads{};
|
||||
} events_context{};
|
||||
|
||||
extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) {
|
||||
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
|
||||
switch (event_id) {
|
||||
case OS_EVENT_SP:
|
||||
events_context.sp.msg = msg;
|
||||
events_context.sp.mq = mq_;
|
||||
break;
|
||||
case OS_EVENT_DP:
|
||||
events_context.dp.msg = msg;
|
||||
events_context.dp.mq = mq_;
|
||||
break;
|
||||
case OS_EVENT_AI:
|
||||
events_context.ai.msg = msg;
|
||||
events_context.ai.mq = mq_;
|
||||
break;
|
||||
case OS_EVENT_SI:
|
||||
events_context.si.msg = msg;
|
||||
events_context.si.mq = mq_;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 retrace_count) {
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
events_context.vi.mq = mq_;
|
||||
events_context.vi.msg = msg;
|
||||
events_context.vi.retrace_count = retrace_count;
|
||||
}
|
||||
|
||||
uint64_t total_vis = 0;
|
||||
|
||||
|
||||
extern std::atomic_bool exited;
|
||||
|
||||
void set_dummy_vi();
|
||||
|
||||
void vi_thread_func() {
|
||||
ultramodern::set_native_thread_name("VI Thread");
|
||||
// This thread should be prioritized over every other thread in the application, as it's what allows
|
||||
// the game to generate new audio and gfx lists.
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Critical);
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int remaining_retraces = events_context.vi.retrace_count;
|
||||
|
||||
while (!exited) {
|
||||
// Determine the next VI time (more accurate than adding 16ms each VI interrupt)
|
||||
auto next = ultramodern::get_start() + (total_vis * 1000000us) / (60 * ultramodern::get_speed_multiplier());
|
||||
//if (next > std::chrono::high_resolution_clock::now()) {
|
||||
// printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n",
|
||||
// (next - std::chrono::high_resolution_clock::now()) / 1us,
|
||||
// (std::chrono::high_resolution_clock::now() - events_context.start) / 1us,
|
||||
// (next - events_context.start) / 1us);
|
||||
//} else {
|
||||
// printf("No need to sleep\n");
|
||||
//}
|
||||
// Detect if there's more than a second to wait and wait a fixed amount instead for the next VI if so, as that usually means the system clock went back in time.
|
||||
if (std::chrono::floor<std::chrono::seconds>(next - std::chrono::high_resolution_clock::now()) > 1s) {
|
||||
// printf("Skipping the next VI wait\n");
|
||||
next = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
ultramodern::sleep_until(next);
|
||||
// Calculate how many VIs have passed
|
||||
uint64_t new_total_vis = (ultramodern::time_since_start() * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1;
|
||||
if (new_total_vis > total_vis + 1) {
|
||||
//printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1);
|
||||
}
|
||||
total_vis = new_total_vis;
|
||||
|
||||
remaining_retraces--;
|
||||
|
||||
{
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
uint8_t* rdram = events_context.rdram;
|
||||
if (remaining_retraces == 0) {
|
||||
remaining_retraces = events_context.vi.retrace_count;
|
||||
|
||||
if (ultramodern::is_game_started()) {
|
||||
if (events_context.vi.mq != NULLPTR) {
|
||||
if (osSendMesg(PASS_RDRAM events_context.vi.mq, events_context.vi.msg, OS_MESG_NOBLOCK) == -1) {
|
||||
//printf("Game skipped a VI frame!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
set_dummy_vi();
|
||||
static bool swap = false;
|
||||
uint32_t vi_origin = 0x400 + 0x280; // Skip initial RDRAM contents and add the usual origin offset
|
||||
// Offset by one FB every other frame so RT64 continues drawing
|
||||
if (swap) {
|
||||
vi_origin += 0x25800;
|
||||
}
|
||||
osViSwapBuffer(rdram, vi_origin);
|
||||
swap = !swap;
|
||||
}
|
||||
}
|
||||
if (events_context.ai.mq != NULLPTR) {
|
||||
if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) {
|
||||
//printf("Game skipped a AI frame!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO move recomp code out of ultramodern.
|
||||
recomp::update_rumble();
|
||||
}
|
||||
}
|
||||
|
||||
void sp_complete() {
|
||||
uint8_t* rdram = events_context.rdram;
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
osSendMesg(PASS_RDRAM events_context.sp.mq, events_context.sp.msg, OS_MESG_NOBLOCK);
|
||||
}
|
||||
|
||||
void dp_complete() {
|
||||
uint8_t* rdram = events_context.rdram;
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK);
|
||||
}
|
||||
|
||||
uint8_t dmem[0x1000];
|
||||
uint16_t rspReciprocals[512];
|
||||
uint16_t rspInverseSquareRoots[512];
|
||||
|
||||
using RspUcodeFunc = RspExitReason(uint8_t* rdram);
|
||||
extern RspUcodeFunc njpgdspMain;
|
||||
extern RspUcodeFunc aspMain;
|
||||
|
||||
// From Ares emulator. For license details, see rsp_vu.h
|
||||
void rsp_constants_init() {
|
||||
rspReciprocals[0] = u16(~0);
|
||||
for (u16 index = 1; index < 512; index++) {
|
||||
u64 a = index + 512;
|
||||
u64 b = (u64(1) << 34) / a;
|
||||
rspReciprocals[index] = u16((b + 1) >> 8);
|
||||
}
|
||||
|
||||
for (u16 index = 0; index < 512; index++) {
|
||||
u64 a = (index + 512) >> ((index % 2 == 1) ? 1 : 0);
|
||||
u64 b = 1 << 17;
|
||||
//find the largest b where b < 1.0 / sqrt(a)
|
||||
while (a * (b + 1) * (b + 1) < (u64(1) << 44)) b++;
|
||||
rspInverseSquareRoots[index] = u16(b >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Runs a recompiled RSP microcode
|
||||
void run_rsp_microcode(uint8_t* rdram, const OSTask* task, RspUcodeFunc* ucode_func) {
|
||||
// Load the OSTask into DMEM
|
||||
memcpy(&dmem[0xFC0], task, sizeof(OSTask));
|
||||
// Load the ucode data into DMEM
|
||||
dma_rdram_to_dmem(rdram, 0x0000, task->t.ucode_data, 0xF80 - 1);
|
||||
// Run the ucode
|
||||
RspExitReason exit_reason = ucode_func(rdram);
|
||||
// Ensure that the ucode exited correctly
|
||||
assert(exit_reason == RspExitReason::Broke);
|
||||
}
|
||||
|
||||
|
||||
void task_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready) {
|
||||
ultramodern::set_native_thread_name("SP Task Thread");
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal);
|
||||
|
||||
// Notify the caller thread that this thread is ready.
|
||||
thread_ready->signal();
|
||||
|
||||
while (true) {
|
||||
// Wait until an RSP task has been sent
|
||||
OSTask* task;
|
||||
events_context.sp_task_queue.wait_dequeue(task);
|
||||
|
||||
if (task == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the correct function based on the task type
|
||||
if (task->t.type == M_AUDTASK) {
|
||||
run_rsp_microcode(rdram, task, aspMain);
|
||||
}
|
||||
else if (task->t.type == M_NJPEGTASK) {
|
||||
run_rsp_microcode(rdram, task, njpgdspMain);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "Unknown task type: %" PRIu32 "\n", task->t.type);
|
||||
assert(false);
|
||||
std::quick_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Tell the game that the RSP has completed
|
||||
sp_complete();
|
||||
}
|
||||
}
|
||||
|
||||
static std::atomic<ultramodern::GraphicsConfig> cur_config{};
|
||||
|
||||
void ultramodern::set_graphics_config(const ultramodern::GraphicsConfig& config) {
|
||||
cur_config = config;
|
||||
events_context.action_queue.enqueue(UpdateConfigAction{});
|
||||
}
|
||||
|
||||
ultramodern::GraphicsConfig ultramodern::get_graphics_config() {
|
||||
return cur_config;
|
||||
}
|
||||
|
||||
std::atomic_uint32_t display_refresh_rate = 60;
|
||||
std::atomic<float> resolution_scale = 1.0f;
|
||||
|
||||
uint32_t ultramodern::get_target_framerate(uint32_t original) {
|
||||
ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config();
|
||||
|
||||
switch (graphics_config.rr_option) {
|
||||
case RT64::UserConfiguration::RefreshRate::Original:
|
||||
default:
|
||||
return original;
|
||||
case RT64::UserConfiguration::RefreshRate::Manual:
|
||||
return graphics_config.rr_manual_value;
|
||||
case RT64::UserConfiguration::RefreshRate::Display:
|
||||
return display_refresh_rate.load();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ultramodern::get_display_refresh_rate() {
|
||||
return display_refresh_rate.load();
|
||||
}
|
||||
|
||||
float ultramodern::get_resolution_scale() {
|
||||
return resolution_scale.load();
|
||||
}
|
||||
|
||||
void ultramodern::load_shader_cache(std::span<const char> cache_data) {
|
||||
events_context.action_queue.enqueue(LoadShaderCacheAction{cache_data});
|
||||
}
|
||||
|
||||
std::atomic<ultramodern::RT64SetupResult> rt64_setup_result = ultramodern::RT64SetupResult::Success;
|
||||
|
||||
void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready, ultramodern::WindowHandle window_handle) {
|
||||
bool enabled_instant_present = false;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ultramodern::set_native_thread_name("Gfx Thread");
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal);
|
||||
|
||||
ultramodern::GraphicsConfig old_config = ultramodern::get_graphics_config();
|
||||
|
||||
ultramodern::RT64Context rt64{rdram, window_handle, cur_config.load().developer_mode};
|
||||
|
||||
if (!rt64.valid()) {
|
||||
// TODO move recomp code out of ultramodern.
|
||||
rt64_setup_result.store(rt64.get_setup_result());
|
||||
// Notify the caller thread that this thread is ready.
|
||||
thread_ready->signal();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO move recomp code out of ultramodern.
|
||||
recomp::update_supported_options();
|
||||
|
||||
rsp_constants_init();
|
||||
|
||||
// Notify the caller thread that this thread is ready.
|
||||
thread_ready->signal();
|
||||
|
||||
while (!exited) {
|
||||
// Try to pull an action from the queue
|
||||
Action action;
|
||||
if (events_context.action_queue.wait_dequeue_timed(action, 1ms)) {
|
||||
// Determine the action type and act on it
|
||||
if (const auto* task_action = std::get_if<SpTaskAction>(&action)) {
|
||||
// Turn on instant present if the game has been started and it hasn't been turned on yet.
|
||||
if (ultramodern::is_game_started() && !enabled_instant_present) {
|
||||
rt64.enable_instant_present();
|
||||
enabled_instant_present = true;
|
||||
}
|
||||
// Tell the game that the RSP completed instantly. This will allow it to queue other task types, but it won't
|
||||
// start another graphics task until the RDP is also complete. Games usually preserve the RSP inputs until the RDP
|
||||
// is finished as well, so sending this early shouldn't be an issue in most cases.
|
||||
// If this causes issues then the logic can be replaced with responding to yield requests.
|
||||
sp_complete();
|
||||
ultramodern::measure_input_latency();
|
||||
|
||||
auto rt64_start = std::chrono::high_resolution_clock::now();
|
||||
rt64.send_dl(&task_action->task);
|
||||
auto rt64_end = std::chrono::high_resolution_clock::now();
|
||||
dp_complete();
|
||||
// printf("RT64 ProcessDList time: %d us\n", static_cast<u32>(std::chrono::duration_cast<std::chrono::microseconds>(rt64_end - rt64_start).count()));
|
||||
}
|
||||
else if (const auto* swap_action = std::get_if<SwapBuffersAction>(&action)) {
|
||||
events_context.vi.current_buffer = events_context.vi.next_buffer;
|
||||
rt64.update_screen(swap_action->origin);
|
||||
display_refresh_rate = rt64.get_display_framerate();
|
||||
resolution_scale = rt64.get_resolution_scale();
|
||||
}
|
||||
else if (const auto* config_action = std::get_if<UpdateConfigAction>(&action)) {
|
||||
ultramodern::GraphicsConfig new_config = cur_config;
|
||||
if (old_config != new_config) {
|
||||
rt64.update_config(old_config, new_config);
|
||||
old_config = new_config;
|
||||
}
|
||||
}
|
||||
else if (const auto* load_shader_cache_action = std::get_if<LoadShaderCacheAction>(&action)) {
|
||||
rt64.load_shader_cache(load_shader_cache_action->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO move recomp code out of ultramodern.
|
||||
recomp::destroy_ui();
|
||||
rt64.shutdown();
|
||||
}
|
||||
|
||||
extern unsigned int VI_STATUS_REG;
|
||||
extern unsigned int VI_ORIGIN_REG;
|
||||
extern unsigned int VI_WIDTH_REG;
|
||||
extern unsigned int VI_INTR_REG;
|
||||
extern unsigned int VI_V_CURRENT_LINE_REG;
|
||||
extern unsigned int VI_TIMING_REG;
|
||||
extern unsigned int VI_V_SYNC_REG;
|
||||
extern unsigned int VI_H_SYNC_REG;
|
||||
extern unsigned int VI_LEAP_REG;
|
||||
extern unsigned int VI_H_START_REG;
|
||||
extern unsigned int VI_V_START_REG;
|
||||
extern unsigned int VI_V_BURST_REG;
|
||||
extern unsigned int VI_X_SCALE_REG;
|
||||
extern unsigned int VI_Y_SCALE_REG;
|
||||
|
||||
uint32_t hstart = 0;
|
||||
uint32_t vi_origin_offset = 320 * sizeof(uint16_t);
|
||||
bool vi_black = false;
|
||||
|
||||
void set_dummy_vi() {
|
||||
VI_STATUS_REG = 0x311E;
|
||||
VI_WIDTH_REG = 0x140;
|
||||
VI_V_SYNC_REG = 0x20D;
|
||||
VI_H_SYNC_REG = 0xC15;
|
||||
VI_LEAP_REG = 0x0C150C15;
|
||||
hstart = 0x006C02EC;
|
||||
VI_X_SCALE_REG = 0x200;
|
||||
VI_V_CURRENT_LINE_REG = 0x0;
|
||||
vi_origin_offset = 0x280;
|
||||
VI_Y_SCALE_REG = 0x400;
|
||||
VI_V_START_REG = 0x2501FF;
|
||||
VI_V_BURST_REG = 0xE0204;
|
||||
VI_INTR_REG = 0x2;
|
||||
}
|
||||
|
||||
extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) {
|
||||
if (vi_black) {
|
||||
VI_H_START_REG = 0;
|
||||
} else {
|
||||
VI_H_START_REG = hstart;
|
||||
}
|
||||
events_context.vi.next_buffer = frameBufPtr;
|
||||
events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + vi_origin_offset });
|
||||
}
|
||||
|
||||
extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) {
|
||||
OSViMode* mode = TO_PTR(OSViMode, mode_);
|
||||
VI_STATUS_REG = mode->comRegs.ctrl;
|
||||
VI_WIDTH_REG = mode->comRegs.width;
|
||||
// burst
|
||||
VI_V_SYNC_REG = mode->comRegs.vSync;
|
||||
VI_H_SYNC_REG = mode->comRegs.hSync;
|
||||
VI_LEAP_REG = mode->comRegs.leap;
|
||||
hstart = mode->comRegs.hStart;
|
||||
VI_X_SCALE_REG = mode->comRegs.xScale;
|
||||
VI_V_CURRENT_LINE_REG = mode->comRegs.vCurrent;
|
||||
|
||||
// TODO swap these every VI to account for fields changing
|
||||
vi_origin_offset = mode->fldRegs[0].origin;
|
||||
VI_Y_SCALE_REG = mode->fldRegs[0].yScale;
|
||||
VI_V_START_REG = mode->fldRegs[0].vStart;
|
||||
VI_V_BURST_REG = mode->fldRegs[0].vBurst;
|
||||
VI_INTR_REG = mode->fldRegs[0].vIntr;
|
||||
}
|
||||
|
||||
#define VI_CTRL_TYPE_16 0x00002
|
||||
#define VI_CTRL_TYPE_32 0x00003
|
||||
#define VI_CTRL_GAMMA_DITHER_ON 0x00004
|
||||
#define VI_CTRL_GAMMA_ON 0x00008
|
||||
#define VI_CTRL_DIVOT_ON 0x00010
|
||||
#define VI_CTRL_SERRATE_ON 0x00040
|
||||
#define VI_CTRL_ANTIALIAS_MASK 0x00300
|
||||
#define VI_CTRL_ANTIALIAS_MODE_1 0x00100
|
||||
#define VI_CTRL_ANTIALIAS_MODE_2 0x00200
|
||||
#define VI_CTRL_ANTIALIAS_MODE_3 0x00300
|
||||
#define VI_CTRL_PIXEL_ADV_MASK 0x01000
|
||||
#define VI_CTRL_PIXEL_ADV_1 0x01000
|
||||
#define VI_CTRL_PIXEL_ADV_2 0x02000
|
||||
#define VI_CTRL_PIXEL_ADV_3 0x03000
|
||||
#define VI_CTRL_DITHER_FILTER_ON 0x10000
|
||||
|
||||
#define OS_VI_GAMMA_ON 0x0001
|
||||
#define OS_VI_GAMMA_OFF 0x0002
|
||||
#define OS_VI_GAMMA_DITHER_ON 0x0004
|
||||
#define OS_VI_GAMMA_DITHER_OFF 0x0008
|
||||
#define OS_VI_DIVOT_ON 0x0010
|
||||
#define OS_VI_DIVOT_OFF 0x0020
|
||||
#define OS_VI_DITHER_FILTER_ON 0x0040
|
||||
#define OS_VI_DITHER_FILTER_OFF 0x0080
|
||||
|
||||
extern "C" void osViSetSpecialFeatures(uint32_t func) {
|
||||
if ((func & OS_VI_GAMMA_ON) != 0) {
|
||||
VI_STATUS_REG |= VI_CTRL_GAMMA_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_GAMMA_OFF) != 0) {
|
||||
VI_STATUS_REG &= ~VI_CTRL_GAMMA_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_GAMMA_DITHER_ON) != 0) {
|
||||
VI_STATUS_REG |= VI_CTRL_GAMMA_DITHER_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_GAMMA_DITHER_OFF) != 0) {
|
||||
VI_STATUS_REG &= ~VI_CTRL_GAMMA_DITHER_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_DIVOT_ON) != 0) {
|
||||
VI_STATUS_REG |= VI_CTRL_DIVOT_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_DIVOT_OFF) != 0) {
|
||||
VI_STATUS_REG &= ~VI_CTRL_DIVOT_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_DITHER_FILTER_ON) != 0) {
|
||||
VI_STATUS_REG |= VI_CTRL_DITHER_FILTER_ON;
|
||||
VI_STATUS_REG &= ~VI_CTRL_ANTIALIAS_MASK;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_DITHER_FILTER_OFF) != 0) {
|
||||
VI_STATUS_REG &= ~VI_CTRL_DITHER_FILTER_ON;
|
||||
//VI_STATUS_REG |= __osViNext->modep->comRegs.ctrl & VI_CTRL_ANTIALIAS_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osViBlack(uint8_t active) {
|
||||
vi_black = active;
|
||||
}
|
||||
|
||||
extern "C" void osViSetXScale(float scale) {
|
||||
if (scale != 1.0f) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osViSetYScale(float scale) {
|
||||
if (scale != 1.0f) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" PTR(void) osViGetNextFramebuffer() {
|
||||
return events_context.vi.next_buffer;
|
||||
}
|
||||
|
||||
extern "C" PTR(void) osViGetCurrentFramebuffer() {
|
||||
return events_context.vi.current_buffer;
|
||||
}
|
||||
|
||||
void ultramodern::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) {
|
||||
OSTask* task = TO_PTR(OSTask, task_);
|
||||
|
||||
// Send gfx tasks to the graphics action queue
|
||||
if (task->t.type == M_GFXTASK) {
|
||||
events_context.action_queue.enqueue(SpTaskAction{ *task });
|
||||
}
|
||||
// Set all other tasks as the RSP task
|
||||
else {
|
||||
events_context.sp_task_queue.enqueue(task);
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::send_si_message(RDRAM_ARG1) {
|
||||
osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK);
|
||||
}
|
||||
|
||||
std::string get_graphics_api_name(ultramodern::GraphicsApi api) {
|
||||
if (api == ultramodern::GraphicsApi::Auto) {
|
||||
#if defined(_WIN32)
|
||||
api = ultramodern::GraphicsApi::D3D12;
|
||||
#elif defined(__gnu_linux__)
|
||||
api = ultramodern::GraphicsApi::Vulkan;
|
||||
#else
|
||||
static_assert(false && "Unimplemented")
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (api) {
|
||||
case ultramodern::GraphicsApi::D3D12:
|
||||
return "D3D12";
|
||||
case ultramodern::GraphicsApi::Vulkan:
|
||||
return "Vulkan";
|
||||
default:
|
||||
return "[Unknown graphics API]";
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle) {
|
||||
moodycamel::LightweightSemaphore gfx_thread_ready;
|
||||
moodycamel::LightweightSemaphore task_thread_ready;
|
||||
events_context.rdram = rdram;
|
||||
events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, &gfx_thread_ready, window_handle };
|
||||
events_context.sp.task_thread = std::thread{ task_thread_func, rdram, &task_thread_ready };
|
||||
|
||||
// Wait for the two sp threads to be ready before continuing to prevent the game from
|
||||
// running before we're able to handle RSP tasks.
|
||||
gfx_thread_ready.wait();
|
||||
task_thread_ready.wait();
|
||||
|
||||
ultramodern::RT64SetupResult setup_result = rt64_setup_result.load();
|
||||
if (rt64_setup_result != ultramodern::RT64SetupResult::Success) {
|
||||
auto show_rt64_error = [](const std::string& msg) {
|
||||
// TODO move recomp code out of ultramodern (message boxes).
|
||||
recomp::message_box(("An error has been encountered on startup: " + msg).c_str());
|
||||
};
|
||||
const std::string driver_os_suffix = "\nPlease make sure your GPU drivers and your OS are up to date.";
|
||||
switch (rt64_setup_result) {
|
||||
case ultramodern::RT64SetupResult::DynamicLibrariesNotFound:
|
||||
show_rt64_error("Failed to load dynamic libraries. Make sure the DLLs are next to the recomp executable.");
|
||||
break;
|
||||
case ultramodern::RT64SetupResult::InvalidGraphicsAPI:
|
||||
show_rt64_error(get_graphics_api_name(cur_config.load().api_option) + " is not supported on this platform. Please select a different graphics API.");
|
||||
break;
|
||||
case ultramodern::RT64SetupResult::GraphicsAPINotFound:
|
||||
show_rt64_error("Unable to initialize " + get_graphics_api_name(cur_config.load().api_option) + "." + driver_os_suffix);
|
||||
break;
|
||||
case ultramodern::RT64SetupResult::GraphicsDeviceNotFound:
|
||||
show_rt64_error("Unable to find compatible graphics device." + driver_os_suffix);
|
||||
break;
|
||||
}
|
||||
throw std::runtime_error("Failed to initialize RT64");
|
||||
}
|
||||
|
||||
events_context.vi.thread = std::thread{ vi_thread_func };
|
||||
}
|
||||
|
||||
void ultramodern::join_event_threads() {
|
||||
events_context.sp.gfx_thread.join();
|
||||
events_context.vi.thread.join();
|
||||
|
||||
// Send a null RSP task to indicate that the RSP task thread should exit.
|
||||
events_context.sp_task_queue.enqueue(nullptr);
|
||||
events_context.sp.task_thread.join();
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
#include <thread>
|
||||
|
||||
#include "blockingconcurrentqueue.h"
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
struct QueuedMessage {
|
||||
PTR(OSMesgQueue) mq;
|
||||
OSMesg mesg;
|
||||
bool jam;
|
||||
};
|
||||
|
||||
static moodycamel::BlockingConcurrentQueue<QueuedMessage> external_messages {};
|
||||
|
||||
void enqueue_external_message(PTR(OSMesgQueue) mq, OSMesg msg, bool jam) {
|
||||
external_messages.enqueue({mq, msg, jam});
|
||||
}
|
||||
|
||||
bool do_send(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, bool jam, bool block);
|
||||
|
||||
void dequeue_external_messages(RDRAM_ARG1) {
|
||||
QueuedMessage to_send;
|
||||
while (external_messages.try_dequeue(to_send)) {
|
||||
do_send(PASS_RDRAM to_send.mq, to_send.mesg, to_send.jam, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::wait_for_external_message(RDRAM_ARG1) {
|
||||
QueuedMessage to_send;
|
||||
external_messages.wait_dequeue(to_send);
|
||||
do_send(PASS_RDRAM to_send.mq, to_send.mesg, to_send.jam, false);
|
||||
}
|
||||
|
||||
extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
mq->blocked_on_recv = NULLPTR;
|
||||
mq->blocked_on_send = NULLPTR;
|
||||
mq->msgCount = count;
|
||||
mq->msg = msg;
|
||||
mq->validCount = 0;
|
||||
mq->first = 0;
|
||||
}
|
||||
|
||||
s32 MQ_GET_COUNT(OSMesgQueue *mq) {
|
||||
return mq->validCount;
|
||||
}
|
||||
|
||||
s32 MQ_IS_EMPTY(OSMesgQueue *mq) {
|
||||
return mq->validCount == 0;
|
||||
}
|
||||
|
||||
s32 MQ_IS_FULL(OSMesgQueue* mq) {
|
||||
return MQ_GET_COUNT(mq) >= mq->msgCount;
|
||||
}
|
||||
|
||||
bool do_send(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, bool jam, bool block) {
|
||||
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
|
||||
if (!block) {
|
||||
// If non-blocking, fail if the queue is full.
|
||||
if (MQ_IS_FULL(mq)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Otherwise, yield this thread until the queue has room.
|
||||
while (MQ_IS_FULL(mq)) {
|
||||
debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, ultramodern::this_thread())->id);
|
||||
ultramodern::thread_queue_insert(PASS_RDRAM GET_MEMBER(OSMesgQueue, mq_, blocked_on_send), ultramodern::this_thread());
|
||||
ultramodern::run_next_thread_and_wait(PASS_RDRAM1);
|
||||
}
|
||||
}
|
||||
|
||||
if (jam) {
|
||||
// Jams insert at the head of the message queue's buffer.
|
||||
mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount;
|
||||
TO_PTR(OSMesg, mq->msg)[mq->first] = msg;
|
||||
mq->validCount++;
|
||||
}
|
||||
else {
|
||||
// Sends insert at the tail of the message queue's buffer.
|
||||
s32 last = (mq->first + mq->validCount) % mq->msgCount;
|
||||
TO_PTR(OSMesg, mq->msg)[last] = msg;
|
||||
mq->validCount++;
|
||||
}
|
||||
|
||||
// If any threads were blocked on receiving from this message queue, pop the first one and schedule it.
|
||||
PTR(PTR(OSThread)) blocked_queue = GET_MEMBER(OSMesgQueue, mq_, blocked_on_recv);
|
||||
if (!ultramodern::thread_queue_empty(PASS_RDRAM blocked_queue)) {
|
||||
ultramodern::schedule_running_thread(PASS_RDRAM ultramodern::thread_queue_pop(PASS_RDRAM blocked_queue));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool do_recv(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, bool block) {
|
||||
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
|
||||
if (!block) {
|
||||
// If non-blocking, fail if the queue is empty
|
||||
if (MQ_IS_EMPTY(mq)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, yield this thread in a loop until the queue is no longer full
|
||||
while (MQ_IS_EMPTY(mq)) {
|
||||
debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, ultramodern::this_thread())->id);
|
||||
ultramodern::thread_queue_insert(PASS_RDRAM GET_MEMBER(OSMesgQueue, mq_, blocked_on_recv), ultramodern::this_thread());
|
||||
ultramodern::run_next_thread_and_wait(PASS_RDRAM1);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg_ != NULLPTR) {
|
||||
*TO_PTR(OSMesg, msg_) = TO_PTR(OSMesg, mq->msg)[mq->first];
|
||||
}
|
||||
|
||||
mq->first = (mq->first + 1) % mq->msgCount;
|
||||
mq->validCount--;
|
||||
|
||||
// If any threads were blocked on sending to this message queue, pop the first one and schedule it.
|
||||
PTR(PTR(OSThread)) blocked_queue = GET_MEMBER(OSMesgQueue, mq_, blocked_on_send);
|
||||
if (!ultramodern::thread_queue_empty(PASS_RDRAM blocked_queue)) {
|
||||
ultramodern::schedule_running_thread(PASS_RDRAM ultramodern::thread_queue_pop(PASS_RDRAM blocked_queue));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
bool jam = false;
|
||||
|
||||
// Don't directly send to the message queue if this isn't a game thread to avoid contention.
|
||||
if (!ultramodern::is_game_thread()) {
|
||||
enqueue_external_message(mq_, msg, jam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle any messages that have been received from an external thread.
|
||||
dequeue_external_messages(PASS_RDRAM1);
|
||||
|
||||
// Try to send the message.
|
||||
bool sent = do_send(PASS_RDRAM mq_, msg, jam, flags == OS_MESG_BLOCK);
|
||||
|
||||
// Check the queue to see if this thread should swap execution to another.
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
|
||||
return sent ? 0 : -1;
|
||||
}
|
||||
|
||||
extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
bool jam = true;
|
||||
|
||||
// Don't directly send to the message queue if this isn't a game thread to avoid contention.
|
||||
if (!ultramodern::is_game_thread()) {
|
||||
enqueue_external_message(mq_, msg, jam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle any messages that have been received from an external thread.
|
||||
dequeue_external_messages(PASS_RDRAM1);
|
||||
|
||||
// Try to send the message.
|
||||
bool sent = do_send(PASS_RDRAM mq_, msg, jam, flags == OS_MESG_BLOCK);
|
||||
|
||||
// Check the queue to see if this thread should swap execution to another.
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
|
||||
return sent ? 0 : -1;
|
||||
}
|
||||
|
||||
extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
|
||||
assert(ultramodern::is_game_thread() && "RecvMesg not allowed outside of game threads.");
|
||||
|
||||
// Handle any messages that have been received from an external thread.
|
||||
dequeue_external_messages(PASS_RDRAM1);
|
||||
|
||||
// Try to receive a message.
|
||||
bool received = do_recv(PASS_RDRAM mq_, msg_, flags == OS_MESG_BLOCK);
|
||||
|
||||
// Check the queue to see if this thread should swap execution to another.
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
|
||||
return received ? 0 : -1;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
#include "ultra64.h"
|
||||
|
||||
#define K0BASE 0x80000000
|
||||
#define K1BASE 0xA0000000
|
||||
#define K2BASE 0xC0000000
|
||||
#define IS_KSEG0(x) ((u32)(x) >= K0BASE && (u32)(x) < K1BASE)
|
||||
#define IS_KSEG1(x) ((u32)(x) >= K1BASE && (u32)(x) < K2BASE)
|
||||
#define K0_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg0 to physical */
|
||||
#define K1_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg1 to physical */
|
||||
|
||||
u32 osVirtualToPhysical(PTR(void) addr) {
|
||||
uintptr_t addr_val = (uintptr_t)addr;
|
||||
if (IS_KSEG0(addr_val)) {
|
||||
return K0_TO_PHYS(addr_val);
|
||||
} else if (IS_KSEG1(addr_val)) {
|
||||
return K1_TO_PHYS(addr_val);
|
||||
} else {
|
||||
// TODO handle TLB mappings
|
||||
return (u32)addr_val;
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
#if 0
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "ultra64.h"
|
||||
|
||||
#define THREAD_STACK_SIZE 0x1000
|
||||
|
||||
u8 idle_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 main_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 thread3_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 thread4_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
|
||||
OSThread idle_thread;
|
||||
OSThread main_thread;
|
||||
OSThread thread3;
|
||||
OSThread thread4;
|
||||
|
||||
OSMesgQueue queue;
|
||||
OSMesg buf[1];
|
||||
|
||||
void thread3_func(UNUSED void *arg) {
|
||||
OSMesg val;
|
||||
printf("Thread3 recv\n");
|
||||
fflush(stdout);
|
||||
osRecvMesg(&queue, &val, OS_MESG_BLOCK);
|
||||
printf("Thread3 complete: %d\n", (int)(intptr_t)val);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void thread4_func(void *arg) {
|
||||
printf("Thread4 send %d\n", (int)(intptr_t)arg);
|
||||
fflush(stdout);
|
||||
osSendMesg(&queue, arg, OS_MESG_BLOCK);
|
||||
printf("Thread4 complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void main_thread_func(UNUSED void* arg) {
|
||||
osCreateMesgQueue(&queue, buf, sizeof(buf) / sizeof(buf[0]));
|
||||
|
||||
printf("main thread creating thread 3\n");
|
||||
osCreateThread(&thread3, 3, thread3_func, NULL, &thread3_stack[THREAD_STACK_SIZE], 14);
|
||||
printf("main thread starting thread 3\n");
|
||||
osStartThread(&thread3);
|
||||
|
||||
printf("main thread creating thread 4\n");
|
||||
osCreateThread(&thread4, 4, thread4_func, (void*)10, &thread4_stack[THREAD_STACK_SIZE], 13);
|
||||
printf("main thread starting thread 4\n");
|
||||
osStartThread(&thread4);
|
||||
|
||||
while (1) {
|
||||
printf("main thread doin stuff\n");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void idle_thread_func(UNUSED void* arg) {
|
||||
printf("idle thread\n");
|
||||
printf("creating main thread\n");
|
||||
osCreateThread(&main_thread, 2, main_thread_func, NULL, &main_stack[THREAD_STACK_SIZE], 11);
|
||||
printf("starting main thread\n");
|
||||
osStartThread(&main_thread);
|
||||
|
||||
// Set this thread's priority to 0, making it the idle thread
|
||||
osSetThreadPri(NULL, 0);
|
||||
|
||||
// idle
|
||||
while (1) {
|
||||
printf("idle thread doin stuff\n");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void bootproc(void) {
|
||||
osInitialize();
|
||||
|
||||
osCreateThread(&idle_thread, 1, idle_thread_func, NULL, &idle_stack[THREAD_STACK_SIZE], 127);
|
||||
printf("Starting idle thread\n");
|
||||
osStartThread(&idle_thread);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,319 +0,0 @@
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
// #include <Windows.h>
|
||||
|
||||
#define HLSL_CPU
|
||||
#include "hle/rt64_application.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "rt64_render_hooks.h"
|
||||
|
||||
ultramodern::RT64Context::~RT64Context() = default;
|
||||
|
||||
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
||||
static bool sample_positions_supported = false;
|
||||
static bool high_precision_fb_enabled = false;
|
||||
|
||||
static uint8_t DMEM[0x1000];
|
||||
static uint8_t IMEM[0x1000];
|
||||
|
||||
unsigned int MI_INTR_REG = 0;
|
||||
|
||||
unsigned int DPC_START_REG = 0;
|
||||
unsigned int DPC_END_REG = 0;
|
||||
unsigned int DPC_CURRENT_REG = 0;
|
||||
unsigned int DPC_STATUS_REG = 0;
|
||||
unsigned int DPC_CLOCK_REG = 0;
|
||||
unsigned int DPC_BUFBUSY_REG = 0;
|
||||
unsigned int DPC_PIPEBUSY_REG = 0;
|
||||
unsigned int DPC_TMEM_REG = 0;
|
||||
|
||||
unsigned int VI_STATUS_REG = 0;
|
||||
unsigned int VI_ORIGIN_REG = 0;
|
||||
unsigned int VI_WIDTH_REG = 0;
|
||||
unsigned int VI_INTR_REG = 0;
|
||||
unsigned int VI_V_CURRENT_LINE_REG = 0;
|
||||
unsigned int VI_TIMING_REG = 0;
|
||||
unsigned int VI_V_SYNC_REG = 0;
|
||||
unsigned int VI_H_SYNC_REG = 0;
|
||||
unsigned int VI_LEAP_REG = 0;
|
||||
unsigned int VI_H_START_REG = 0;
|
||||
unsigned int VI_V_START_REG = 0;
|
||||
unsigned int VI_V_BURST_REG = 0;
|
||||
unsigned int VI_X_SCALE_REG = 0;
|
||||
unsigned int VI_Y_SCALE_REG = 0;
|
||||
|
||||
void dummy_check_interrupts() {
|
||||
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampleCounts bits) {
|
||||
if (bits & RT64::RenderSampleCount::Bits::COUNT_2) {
|
||||
if (bits & RT64::RenderSampleCount::Bits::COUNT_4) {
|
||||
if (bits & RT64::RenderSampleCount::Bits::COUNT_8) {
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
}
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
}
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
};
|
||||
return RT64::UserConfiguration::Antialiasing::None;
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::InternalColorFormat to_rt64(ultramodern::HighPrecisionFramebuffer option) {
|
||||
switch (option) {
|
||||
case ultramodern::HighPrecisionFramebuffer::Off:
|
||||
return RT64::UserConfiguration::InternalColorFormat::Standard;
|
||||
case ultramodern::HighPrecisionFramebuffer::On:
|
||||
return RT64::UserConfiguration::InternalColorFormat::High;
|
||||
case ultramodern::HighPrecisionFramebuffer::Auto:
|
||||
return RT64::UserConfiguration::InternalColorFormat::Automatic;
|
||||
default:
|
||||
return RT64::UserConfiguration::InternalColorFormat::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
void set_application_user_config(RT64::Application* application, const ultramodern::GraphicsConfig& config) {
|
||||
switch (config.res_option) {
|
||||
default:
|
||||
case ultramodern::Resolution::Auto:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::WindowIntegerScale;
|
||||
application->userConfig.downsampleMultiplier = 1;
|
||||
break;
|
||||
case ultramodern::Resolution::Original:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual;
|
||||
application->userConfig.resolutionMultiplier = config.ds_option;
|
||||
application->userConfig.downsampleMultiplier = config.ds_option;
|
||||
break;
|
||||
case ultramodern::Resolution::Original2x:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual;
|
||||
application->userConfig.resolutionMultiplier = 2.0 * config.ds_option;
|
||||
application->userConfig.downsampleMultiplier = config.ds_option;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (config.hr_option) {
|
||||
default:
|
||||
case ultramodern::HUDRatioMode::Original:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Original;
|
||||
break;
|
||||
case ultramodern::HUDRatioMode::Clamp16x9:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Manual;
|
||||
application->userConfig.extAspectTarget = 16.0/9.0;
|
||||
break;
|
||||
case ultramodern::HUDRatioMode::Full:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Expand;
|
||||
break;
|
||||
}
|
||||
|
||||
application->userConfig.aspectRatio = config.ar_option;
|
||||
application->userConfig.antialiasing = config.msaa_option;
|
||||
application->userConfig.refreshRate = config.rr_option;
|
||||
application->userConfig.refreshRateTarget = config.rr_manual_value;
|
||||
application->userConfig.internalColorFormat = to_rt64(config.hpfb_option);
|
||||
}
|
||||
|
||||
ultramodern::RT64SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) {
|
||||
switch (rt64_result) {
|
||||
case RT64::Application::SetupResult::Success:
|
||||
return ultramodern::RT64SetupResult::Success;
|
||||
case RT64::Application::SetupResult::DynamicLibrariesNotFound:
|
||||
return ultramodern::RT64SetupResult::DynamicLibrariesNotFound;
|
||||
case RT64::Application::SetupResult::InvalidGraphicsAPI:
|
||||
return ultramodern::RT64SetupResult::InvalidGraphicsAPI;
|
||||
case RT64::Application::SetupResult::GraphicsAPINotFound:
|
||||
return ultramodern::RT64SetupResult::GraphicsAPINotFound;
|
||||
case RT64::Application::SetupResult::GraphicsDeviceNotFound:
|
||||
return ultramodern::RT64SetupResult::GraphicsDeviceNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle window_handle, bool debug) {
|
||||
static unsigned char dummy_rom_header[0x40];
|
||||
set_rt64_hooks();
|
||||
|
||||
// Set up the RT64 application core fields.
|
||||
RT64::Application::Core appCore{};
|
||||
#if defined(_WIN32)
|
||||
appCore.window = window_handle.window;
|
||||
#elif defined(__ANDROID__)
|
||||
assert(false && "Unimplemented");
|
||||
#elif defined(__linux__)
|
||||
appCore.window.display = window_handle.display;
|
||||
appCore.window.window = window_handle.window;
|
||||
#endif
|
||||
|
||||
appCore.checkInterrupts = dummy_check_interrupts;
|
||||
|
||||
appCore.HEADER = dummy_rom_header;
|
||||
appCore.RDRAM = rdram;
|
||||
appCore.DMEM = DMEM;
|
||||
appCore.IMEM = IMEM;
|
||||
|
||||
appCore.MI_INTR_REG = &MI_INTR_REG;
|
||||
|
||||
appCore.DPC_START_REG = &DPC_START_REG;
|
||||
appCore.DPC_END_REG = &DPC_END_REG;
|
||||
appCore.DPC_CURRENT_REG = &DPC_CURRENT_REG;
|
||||
appCore.DPC_STATUS_REG = &DPC_STATUS_REG;
|
||||
appCore.DPC_CLOCK_REG = &DPC_CLOCK_REG;
|
||||
appCore.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG;
|
||||
appCore.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG;
|
||||
appCore.DPC_TMEM_REG = &DPC_TMEM_REG;
|
||||
|
||||
appCore.VI_STATUS_REG = &VI_STATUS_REG;
|
||||
appCore.VI_ORIGIN_REG = &VI_ORIGIN_REG;
|
||||
appCore.VI_WIDTH_REG = &VI_WIDTH_REG;
|
||||
appCore.VI_INTR_REG = &VI_INTR_REG;
|
||||
appCore.VI_V_CURRENT_LINE_REG = &VI_V_CURRENT_LINE_REG;
|
||||
appCore.VI_TIMING_REG = &VI_TIMING_REG;
|
||||
appCore.VI_V_SYNC_REG = &VI_V_SYNC_REG;
|
||||
appCore.VI_H_SYNC_REG = &VI_H_SYNC_REG;
|
||||
appCore.VI_LEAP_REG = &VI_LEAP_REG;
|
||||
appCore.VI_H_START_REG = &VI_H_START_REG;
|
||||
appCore.VI_V_START_REG = &VI_V_START_REG;
|
||||
appCore.VI_V_BURST_REG = &VI_V_BURST_REG;
|
||||
appCore.VI_X_SCALE_REG = &VI_X_SCALE_REG;
|
||||
appCore.VI_Y_SCALE_REG = &VI_Y_SCALE_REG;
|
||||
|
||||
// Set up the RT64 application configuration fields.
|
||||
RT64::ApplicationConfiguration appConfig;
|
||||
appConfig.useConfigurationFile = false;
|
||||
|
||||
// Create the RT64 application.
|
||||
app = std::make_unique<RT64::Application>(appCore, appConfig);
|
||||
|
||||
// Set initial user config settings based on the current settings.
|
||||
ultramodern::GraphicsConfig cur_config = ultramodern::get_graphics_config();
|
||||
set_application_user_config(app.get(), cur_config);
|
||||
app->userConfig.developerMode = debug;
|
||||
// Force gbi depth branches to prevent LODs from kicking in.
|
||||
app->enhancementConfig.f3dex.forceBranch = true;
|
||||
// Scale LODs based on the output resolution.
|
||||
app->enhancementConfig.textureLOD.scale = true;
|
||||
// Pick an API if the user has set an override.
|
||||
switch (cur_config.api_option) {
|
||||
case ultramodern::GraphicsApi::D3D12:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::D3D12;
|
||||
break;
|
||||
case ultramodern::GraphicsApi::Vulkan:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan;
|
||||
break;
|
||||
default:
|
||||
case ultramodern::GraphicsApi::Auto:
|
||||
// Don't override if auto is selected.
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up the RT64 application.
|
||||
uint32_t thread_id = 0;
|
||||
#ifdef _WIN32
|
||||
thread_id = window_handle.thread_id;
|
||||
#endif
|
||||
setup_result = map_setup_result(app->setup(thread_id));
|
||||
if (setup_result != ultramodern::RT64SetupResult::Success) {
|
||||
app = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the application's fullscreen state.
|
||||
app->setFullScreen(cur_config.wm_option == ultramodern::WindowMode::Fullscreen);
|
||||
|
||||
// Check if the selected device actually supports MSAA sample positions and MSAA for for the formats that will be used
|
||||
// and downgrade the configuration accordingly.
|
||||
if (app->device->getCapabilities().sampleLocations) {
|
||||
RT64::RenderSampleCounts color_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::R8G8B8A8_UNORM);
|
||||
RT64::RenderSampleCounts depth_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::D32_FLOAT);
|
||||
RT64::RenderSampleCounts common_sample_counts = color_sample_counts & depth_sample_counts;
|
||||
device_max_msaa = compute_max_supported_aa(common_sample_counts);
|
||||
sample_positions_supported = true;
|
||||
}
|
||||
else {
|
||||
device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
||||
sample_positions_supported = false;
|
||||
}
|
||||
|
||||
high_precision_fb_enabled = app->shaderLibrary->usesHDR;
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::send_dl(const OSTask* task) {
|
||||
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);
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::update_screen(uint32_t vi_origin) {
|
||||
VI_ORIGIN_REG = vi_origin;
|
||||
|
||||
app->updateScreen();
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::shutdown() {
|
||||
if (app != nullptr) {
|
||||
app->end();
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::update_config(const ultramodern::GraphicsConfig& old_config, const ultramodern::GraphicsConfig& new_config) {
|
||||
if (new_config.wm_option != old_config.wm_option) {
|
||||
app->setFullScreen(new_config.wm_option == ultramodern::WindowMode::Fullscreen);
|
||||
}
|
||||
|
||||
set_application_user_config(app.get(), new_config);
|
||||
|
||||
app->updateUserConfig(true);
|
||||
|
||||
if (new_config.msaa_option != old_config.msaa_option) {
|
||||
app->updateMultisampling();
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::enable_instant_present() {
|
||||
// Enable the present early presentation mode for minimal latency.
|
||||
app->enhancementConfig.presentation.mode = RT64::EnhancementConfiguration::Presentation::Mode::PresentEarly;
|
||||
|
||||
app->updateEnhancementConfig();
|
||||
}
|
||||
|
||||
uint32_t ultramodern::RT64Context::get_display_framerate() {
|
||||
return app->presentQueue->ext.sharedResources->swapChainRate;
|
||||
}
|
||||
|
||||
float ultramodern::RT64Context::get_resolution_scale() {
|
||||
constexpr int ReferenceHeight = 240;
|
||||
switch (app->userConfig.resolution) {
|
||||
case RT64::UserConfiguration::Resolution::WindowIntegerScale:
|
||||
if (app->sharedQueueResources->swapChainHeight > 0) {
|
||||
return std::max(float((app->sharedQueueResources->swapChainHeight + ReferenceHeight - 1) / ReferenceHeight), 1.0f);
|
||||
}
|
||||
else {
|
||||
return 1.0f;
|
||||
}
|
||||
case RT64::UserConfiguration::Resolution::Manual:
|
||||
return float(app->userConfig.resolutionMultiplier);
|
||||
case RT64::UserConfiguration::Resolution::Original:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::load_shader_cache(std::span<const char> 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 ultramodern::RT64MaxMSAA() {
|
||||
return device_max_msaa;
|
||||
}
|
||||
|
||||
bool ultramodern::RT64SamplePositionsSupported() {
|
||||
return sample_positions_supported;
|
||||
}
|
||||
|
||||
bool ultramodern::RT64HighPrecisionFBEnabled() {
|
||||
return high_precision_fb_enabled;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
void ultramodern::schedule_running_thread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
debug_printf("[Scheduling] Adding thread %d to the running queue\n", TO_PTR(OSThread, t_)->id);
|
||||
thread_queue_insert(PASS_RDRAM running_queue, t_);
|
||||
TO_PTR(OSThread, t_)->state = OSThreadState::QUEUED;
|
||||
}
|
||||
|
||||
void swap_to_thread(RDRAM_ARG OSThread *to) {
|
||||
debug_printf("[Scheduling] Thread %d giving execution to thread %d\n", TO_PTR(OSThread, ultramodern::this_thread())->id, to->id);
|
||||
// Insert this thread in the running queue.
|
||||
ultramodern::thread_queue_insert(PASS_RDRAM ultramodern::running_queue, ultramodern::this_thread());
|
||||
TO_PTR(OSThread, ultramodern::this_thread())->state = OSThreadState::QUEUED;
|
||||
// Unpause the target thread and wait for this one to be unpaused.
|
||||
ultramodern::resume_thread_and_wait(PASS_RDRAM to);
|
||||
}
|
||||
|
||||
void ultramodern::check_running_queue(RDRAM_ARG1) {
|
||||
// Check if there are any threads in the running queue.
|
||||
if (!thread_queue_empty(PASS_RDRAM running_queue)) {
|
||||
// Check if the highest priority thread in the queue is higher priority than the current thread.
|
||||
OSThread* next_thread = TO_PTR(OSThread, ultramodern::thread_queue_peek(PASS_RDRAM running_queue));
|
||||
OSThread* self = TO_PTR(OSThread, ultramodern::this_thread());
|
||||
if (next_thread->priority > self->priority) {
|
||||
ultramodern::thread_queue_pop(PASS_RDRAM running_queue);
|
||||
// Swap to the higher priority thread.
|
||||
swap_to_thread(PASS_RDRAM next_thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void pause_self(RDRAM_ARG1) {
|
||||
while (true) {
|
||||
// Wait until an external message arrives, then allow the next thread to run.
|
||||
ultramodern::wait_for_external_message(PASS_RDRAM1);
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
extern "C" unsigned int sleep(unsigned int seconds) {
|
||||
Sleep(seconds * 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
@ -1,66 +0,0 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
static PTR(OSThread) running_queue_impl = NULLPTR;
|
||||
|
||||
static PTR(OSThread)* queue_to_ptr(RDRAM_ARG PTR(PTR(OSThread)) queue) {
|
||||
if (queue == ultramodern::running_queue) {
|
||||
return &running_queue_impl;
|
||||
}
|
||||
return TO_PTR(PTR(OSThread), queue);
|
||||
}
|
||||
|
||||
void ultramodern::thread_queue_insert(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) toadd_) {
|
||||
PTR(OSThread)* cur = queue_to_ptr(PASS_RDRAM queue_);
|
||||
OSThread* toadd = TO_PTR(OSThread, toadd_);
|
||||
debug_printf("[Thread Queue] Inserting thread %d into queue 0x%08X\n", toadd->id, (uintptr_t)queue_);
|
||||
while (*cur && TO_PTR(OSThread, *cur)->priority > toadd->priority) {
|
||||
cur = &TO_PTR(OSThread, *cur)->next;
|
||||
}
|
||||
toadd->next = (*cur);
|
||||
toadd->queue = queue_;
|
||||
*cur = toadd_;
|
||||
|
||||
debug_printf(" Contains:");
|
||||
cur = queue_to_ptr(PASS_RDRAM queue_);
|
||||
while (*cur) {
|
||||
debug_printf("%d (%d) ", TO_PTR(OSThread, *cur)->id, TO_PTR(OSThread, *cur)->priority);
|
||||
cur = &TO_PTR(OSThread, *cur)->next;
|
||||
}
|
||||
debug_printf("\n");
|
||||
}
|
||||
|
||||
PTR(OSThread) ultramodern::thread_queue_pop(RDRAM_ARG PTR(PTR(OSThread)) queue_) {
|
||||
PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_);
|
||||
PTR(OSThread) ret = *queue;
|
||||
*queue = TO_PTR(OSThread, ret)->next;
|
||||
TO_PTR(OSThread, ret)->queue = NULLPTR;
|
||||
debug_printf("[Thread Queue] Popped thread %d from queue 0x%08X\n", TO_PTR(OSThread, ret)->id, (uintptr_t)queue_);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ultramodern::thread_queue_remove(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) t_) {
|
||||
debug_printf("[Thread Queue] Removing thread %d from queue 0x%08X\n", TO_PTR(OSThread, t_)->id, (uintptr_t)queue_);
|
||||
|
||||
PTR(PTR(OSThread)) cur = queue_;
|
||||
while (cur != NULLPTR) {
|
||||
PTR(OSThread)* cur_ptr = queue_to_ptr(PASS_RDRAM queue_);
|
||||
if (*cur_ptr == t_) {
|
||||
return true;
|
||||
}
|
||||
cur = TO_PTR(OSThread, *cur_ptr)->next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ultramodern::thread_queue_empty(RDRAM_ARG PTR(PTR(OSThread)) queue_) {
|
||||
PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_);
|
||||
return *queue == NULLPTR;
|
||||
}
|
||||
|
||||
PTR(OSThread) ultramodern::thread_queue_peek(RDRAM_ARG PTR(PTR(OSThread)) queue_) {
|
||||
PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_);
|
||||
return *queue;
|
||||
}
|
@ -1,346 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
#include "blockingconcurrentqueue.h"
|
||||
|
||||
// Native APIs only used to set thread names for easier debugging
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
extern "C" void bootproc();
|
||||
|
||||
thread_local bool is_main_thread = false;
|
||||
// Whether this thread is part of the game (i.e. the start thread or one spawned by osCreateThread)
|
||||
thread_local bool is_game_thread = false;
|
||||
thread_local PTR(OSThread) thread_self = NULLPTR;
|
||||
|
||||
void ultramodern::set_main_thread() {
|
||||
::is_game_thread = true;
|
||||
is_main_thread = true;
|
||||
}
|
||||
|
||||
bool ultramodern::is_game_thread() {
|
||||
return ::is_game_thread;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int main(int argc, char** argv) {
|
||||
ultramodern::set_main_thread();
|
||||
|
||||
bootproc();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg);
|
||||
#else
|
||||
#define run_thread_function(func, sp, arg) func(arg)
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
void ultramodern::set_native_thread_name(const std::string& name) {
|
||||
std::wstring wname{name.begin(), name.end()};
|
||||
|
||||
HRESULT r;
|
||||
r = SetThreadDescription(
|
||||
GetCurrentThread(),
|
||||
wname.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
void ultramodern::set_native_thread_priority(ThreadPriority pri) {
|
||||
int nPriority = THREAD_PRIORITY_NORMAL;
|
||||
|
||||
// Convert ThreadPriority to Win32 priority
|
||||
switch (pri) {
|
||||
case ThreadPriority::Low:
|
||||
nPriority = THREAD_PRIORITY_BELOW_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::Normal:
|
||||
nPriority = THREAD_PRIORITY_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::High:
|
||||
nPriority = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::VeryHigh:
|
||||
nPriority = THREAD_PRIORITY_HIGHEST;
|
||||
break;
|
||||
case ThreadPriority::Critical:
|
||||
nPriority = THREAD_PRIORITY_TIME_CRITICAL;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Invalid thread priority!");
|
||||
break;
|
||||
}
|
||||
// SetThreadPriority(GetCurrentThread(), nPriority);
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
void ultramodern::set_native_thread_name(const std::string& name) {
|
||||
pthread_setname_np(pthread_self(), name.c_str());
|
||||
}
|
||||
|
||||
void ultramodern::set_native_thread_priority(ThreadPriority pri) {
|
||||
// TODO linux thread priority
|
||||
// printf("set_native_thread_priority unimplemented\n");
|
||||
// int nPriority = THREAD_PRIORITY_NORMAL;
|
||||
|
||||
// // Convert ThreadPriority to Win32 priority
|
||||
// switch (pri) {
|
||||
// case ThreadPriority::Low:
|
||||
// nPriority = THREAD_PRIORITY_BELOW_NORMAL;
|
||||
// break;
|
||||
// case ThreadPriority::Normal:
|
||||
// nPriority = THREAD_PRIORITY_NORMAL;
|
||||
// break;
|
||||
// case ThreadPriority::High:
|
||||
// nPriority = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||
// break;
|
||||
// case ThreadPriority::VeryHigh:
|
||||
// nPriority = THREAD_PRIORITY_HIGHEST;
|
||||
// break;
|
||||
// case ThreadPriority::Critical:
|
||||
// nPriority = THREAD_PRIORITY_TIME_CRITICAL;
|
||||
// break;
|
||||
// default:
|
||||
// throw std::runtime_error("Invalid thread priority!");
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
#endif
|
||||
|
||||
std::atomic_int temporary_threads = 0;
|
||||
std::atomic_int permanent_threads = 0;
|
||||
|
||||
void wait_for_resumed(RDRAM_ARG UltraThreadContext* thread_context) {
|
||||
TO_PTR(OSThread, ultramodern::this_thread())->context->running.wait();
|
||||
// If this thread's context was replaced by another thread or deleted, destroy it again from its own context.
|
||||
// This will trigger thread cleanup instead.
|
||||
if (TO_PTR(OSThread, ultramodern::this_thread())->context != thread_context) {
|
||||
osDestroyThread(PASS_RDRAM NULLPTR);
|
||||
}
|
||||
}
|
||||
|
||||
void resume_thread(OSThread* t) {
|
||||
debug_printf("[Thread] Resuming execution of thread %d\n", t->id);
|
||||
t->context->running.signal();
|
||||
}
|
||||
|
||||
void run_next_thread(RDRAM_ARG1) {
|
||||
if (ultramodern::thread_queue_empty(PASS_RDRAM ultramodern::running_queue)) {
|
||||
throw std::runtime_error("No threads left to run!\n");
|
||||
}
|
||||
|
||||
OSThread* to_run = TO_PTR(OSThread, ultramodern::thread_queue_pop(PASS_RDRAM ultramodern::running_queue));
|
||||
debug_printf("[Scheduling] Resuming execution of thread %d\n", to_run->id);
|
||||
to_run->context->running.signal();
|
||||
}
|
||||
|
||||
void ultramodern::run_next_thread_and_wait(RDRAM_ARG1) {
|
||||
UltraThreadContext* cur_context = TO_PTR(OSThread, thread_self)->context;
|
||||
run_next_thread(PASS_RDRAM1);
|
||||
wait_for_resumed(PASS_RDRAM cur_context);
|
||||
}
|
||||
|
||||
void ultramodern::resume_thread_and_wait(RDRAM_ARG OSThread *t) {
|
||||
UltraThreadContext* cur_context = TO_PTR(OSThread, thread_self)->context;
|
||||
resume_thread(t);
|
||||
wait_for_resumed(PASS_RDRAM cur_context);
|
||||
}
|
||||
|
||||
static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg, UltraThreadContext* thread_context) {
|
||||
OSThread *self = TO_PTR(OSThread, self_);
|
||||
debug_printf("[Thread] Thread created: %d\n", self->id);
|
||||
thread_self = self_;
|
||||
is_game_thread = true;
|
||||
|
||||
// Set the thread name
|
||||
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);
|
||||
}
|
||||
|
||||
// Signal the initialized semaphore to indicate that this thread can be started.
|
||||
thread_context->initialized.signal();
|
||||
|
||||
debug_printf("[Thread] Thread waiting to be started: %d\n", self->id);
|
||||
|
||||
// Wait until the thread is marked as running.
|
||||
wait_for_resumed(PASS_RDRAM thread_context);
|
||||
|
||||
// Make sure the thread wasn't replaced or destroyed before it was started.
|
||||
if (self->context == thread_context) {
|
||||
debug_printf("[Thread] Thread started: %d\n", self->id);
|
||||
try {
|
||||
// Run the thread's function with the provided argument.
|
||||
run_thread_function(PASS_RDRAM entrypoint, self->sp, arg);
|
||||
} catch (ultramodern::thread_terminated& terminated) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug_printf("[Thread] Thread destroyed before being started: %d\n", self->id);
|
||||
}
|
||||
|
||||
// Check if the thread hasn't been destroyed or replaced. If so, then the thread terminated or destroyed itself,
|
||||
// so mark this thread as destroyed and run the next queued thread.
|
||||
if (self->context == thread_context) {
|
||||
self->context = nullptr;
|
||||
run_next_thread(PASS_RDRAM1);
|
||||
}
|
||||
|
||||
// Dispose of this thread now that it's completed or terminated.
|
||||
ultramodern::cleanup_thread(thread_context);
|
||||
|
||||
// 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_) {
|
||||
OSThread* t = TO_PTR(OSThread, t_);
|
||||
debug_printf("[os] Start Thread %d\n", t->id);
|
||||
|
||||
// Wait until the thread is initialized to indicate that it's ready to be started.
|
||||
t->context->initialized.wait();
|
||||
|
||||
debug_printf("[os] Thread %d is ready to be started\n", t->id);
|
||||
|
||||
// If this is a game thread, insert the new thread into the running queue and then check the running queue.
|
||||
if (thread_self) {
|
||||
ultramodern::schedule_running_thread(PASS_RDRAM t_);
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
}
|
||||
// Otherwise, immediately start the thread and terminate this one.
|
||||
else {
|
||||
t->state = OSThreadState::QUEUED;
|
||||
resume_thread(t);
|
||||
//throw ultramodern::thread_terminated{};
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) {
|
||||
debug_printf("[os] Create Thread %d\n", id);
|
||||
OSThread *t = TO_PTR(OSThread, t_);
|
||||
|
||||
t->next = NULLPTR;
|
||||
t->queue = NULLPTR;
|
||||
t->priority = pri;
|
||||
t->id = id;
|
||||
t->state = OSThreadState::STOPPED;
|
||||
t->sp = sp - 0x10; // Set up the first stack frame
|
||||
|
||||
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
|
||||
// Pass the context as an argument to the thread function to ensure that it can't get cleared before the thread captures its value.
|
||||
t->context = new UltraThreadContext{};
|
||||
t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg, t->context};
|
||||
}
|
||||
|
||||
extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
if (t_ == NULLPTR) {
|
||||
t_ = thread_self;
|
||||
}
|
||||
OSThread* t = TO_PTR(OSThread, t_);
|
||||
// Check if the thread is destroying itself (arg is null or thread_self)
|
||||
if (t_ == thread_self) {
|
||||
throw ultramodern::thread_terminated{};
|
||||
}
|
||||
// Otherwise if the thread isn't stopped, remove it from its currrent queue.,
|
||||
if (t->state != OSThreadState::STOPPED) {
|
||||
ultramodern::thread_queue_remove(PASS_RDRAM t->queue, t_);
|
||||
}
|
||||
// Check if the thread has already been destroyed to prevent destroying it again.
|
||||
UltraThreadContext* cur_context = t->context;
|
||||
if (cur_context != nullptr) {
|
||||
// Mark the target thread as destroyed and resume it. When it starts it'll check this and terminate itself instead of resuming.
|
||||
t->context = nullptr;
|
||||
cur_context->running.signal();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t_, OSPri pri) {
|
||||
if (t_ == NULLPTR) {
|
||||
t_ = thread_self;
|
||||
}
|
||||
OSThread* t = TO_PTR(OSThread, t_);
|
||||
|
||||
if (t->priority != pri) {
|
||||
t->priority = pri;
|
||||
|
||||
if (t_ != ultramodern::this_thread() && t->state != OSThreadState::STOPPED) {
|
||||
ultramodern::thread_queue_remove(PASS_RDRAM t->queue, t_);
|
||||
ultramodern::thread_queue_insert(PASS_RDRAM t->queue, t_);
|
||||
}
|
||||
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) t) {
|
||||
if (t == NULLPTR) {
|
||||
t = thread_self;
|
||||
}
|
||||
return TO_PTR(OSThread, t)->priority;
|
||||
}
|
||||
|
||||
extern "C" OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t) {
|
||||
if (t == NULLPTR) {
|
||||
t = thread_self;
|
||||
}
|
||||
return TO_PTR(OSThread, t)->id;
|
||||
}
|
||||
|
||||
PTR(OSThread) ultramodern::this_thread() {
|
||||
return thread_self;
|
||||
}
|
||||
|
||||
static std::thread thread_cleaner_thread;
|
||||
static moodycamel::BlockingConcurrentQueue<UltraThreadContext*> deleted_threads{};
|
||||
extern std::atomic_bool exited;
|
||||
|
||||
void thread_cleaner_func() {
|
||||
using namespace std::chrono_literals;
|
||||
while (!exited) {
|
||||
UltraThreadContext* to_delete;
|
||||
if (deleted_threads.wait_dequeue_timed(to_delete, 10ms)) {
|
||||
debug_printf("[Cleanup] Deleting thread context %p\n", to_delete);
|
||||
|
||||
to_delete->host_thread.join();
|
||||
delete to_delete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::init_thread_cleanup() {
|
||||
thread_cleaner_thread = std::thread{thread_cleaner_func};
|
||||
}
|
||||
|
||||
void ultramodern::cleanup_thread(UltraThreadContext *cur_context) {
|
||||
deleted_threads.enqueue(cur_context);
|
||||
}
|
||||
|
||||
void ultramodern::join_thread_cleaner_thread() {
|
||||
thread_cleaner_thread.join();
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
#include <set>
|
||||
#include "blockingconcurrentqueue.h"
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
|
||||
// Start time for the program
|
||||
static std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now();
|
||||
// Game speed multiplier (1 means no speedup)
|
||||
constexpr uint32_t speed_multiplier = 1;
|
||||
// N64 CPU counter ticks per millisecond
|
||||
constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier;
|
||||
|
||||
struct OSTimer {
|
||||
PTR(OSTimer) unused1;
|
||||
PTR(OSTimer) unused2;
|
||||
OSTime interval;
|
||||
OSTime timestamp;
|
||||
PTR(OSMesgQueue) mq;
|
||||
OSMesg msg;
|
||||
};
|
||||
|
||||
struct AddTimerAction {
|
||||
PTR(OSTimer) timer;
|
||||
};
|
||||
|
||||
struct RemoveTimerAction {
|
||||
PTR(OSTimer) timer;
|
||||
};
|
||||
|
||||
using Action = std::variant<AddTimerAction, RemoveTimerAction>;
|
||||
|
||||
struct {
|
||||
std::thread thread;
|
||||
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
|
||||
} timer_context;
|
||||
|
||||
uint64_t duration_to_ticks(std::chrono::high_resolution_clock::duration duration) {
|
||||
uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
|
||||
// More accurate than using a floating point timer, will only overflow after running for 12.47 years
|
||||
// Units: (micros * (counts/millis)) / (micros/millis) = counts
|
||||
uint64_t total_count = (delta_micros * counter_per_ms) / 1000;
|
||||
|
||||
return total_count;
|
||||
}
|
||||
|
||||
std::chrono::microseconds ticks_to_duration(uint64_t ticks) {
|
||||
using namespace std::chrono_literals;
|
||||
return ticks * 1000us / counter_per_ms;
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point ticks_to_timepoint(uint64_t ticks) {
|
||||
return start_time + ticks_to_duration(ticks);
|
||||
}
|
||||
|
||||
uint64_t time_now() {
|
||||
return duration_to_ticks(std::chrono::high_resolution_clock::now() - start_time);
|
||||
}
|
||||
|
||||
void timer_thread(RDRAM_ARG1) {
|
||||
ultramodern::set_native_thread_name("Timer Thread");
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::VeryHigh);
|
||||
|
||||
// Lambda comparator function to keep the set ordered
|
||||
auto timer_sort = [PASS_RDRAM1](PTR(OSTimer) a_, PTR(OSTimer) b_) {
|
||||
OSTimer* a = TO_PTR(OSTimer, a_);
|
||||
OSTimer* b = TO_PTR(OSTimer, b_);
|
||||
|
||||
// Order by timestamp if the timers have different timestamps
|
||||
if (a->timestamp != b->timestamp) {
|
||||
return a->timestamp < b->timestamp;
|
||||
}
|
||||
|
||||
// If they have the exact same timestamp then order by address instead
|
||||
return a < b;
|
||||
};
|
||||
|
||||
// Ordered set of timers that are currently active
|
||||
std::set<PTR(OSTimer), decltype(timer_sort)> active_timers{timer_sort};
|
||||
|
||||
// Lambda to process a timer action to handle adding and removing timers
|
||||
auto process_timer_action = [&](const Action& action) {
|
||||
// Determine the action type and act on it
|
||||
if (const auto* add_action = std::get_if<AddTimerAction>(&action)) {
|
||||
active_timers.insert(add_action->timer);
|
||||
} else if (const auto* remove_action = std::get_if<RemoveTimerAction>(&action)) {
|
||||
active_timers.erase(remove_action->timer);
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
// Empty the action queue
|
||||
Action cur_action;
|
||||
while (timer_context.action_queue.try_dequeue(cur_action)) {
|
||||
process_timer_action(cur_action);
|
||||
}
|
||||
|
||||
// If there's no timer to act on, wait for one to come in from the action queue
|
||||
while (active_timers.empty()) {
|
||||
timer_context.action_queue.wait_dequeue(cur_action);
|
||||
process_timer_action(cur_action);
|
||||
}
|
||||
|
||||
// Get the timer that's closest to running out
|
||||
PTR(OSTimer) cur_timer_ = *active_timers.begin();
|
||||
OSTimer* cur_timer = TO_PTR(OSTimer, cur_timer_);
|
||||
|
||||
// Remove the timer from the queue (it may get readded if waiting is interrupted)
|
||||
active_timers.erase(cur_timer_);
|
||||
|
||||
// Determine how long to wait to reach the timer's timestamp
|
||||
auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Wait for either the duration to complete or a new action to come through
|
||||
if (wait_duration.count() >= 0 && timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) {
|
||||
// Timer was interrupted by a new action
|
||||
// Add the current timer back to the queue (done first in case the action is to remove this timer)
|
||||
active_timers.insert(cur_timer_);
|
||||
// Process the new action
|
||||
process_timer_action(cur_action);
|
||||
}
|
||||
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
|
||||
if (cur_timer->interval != 0) {
|
||||
cur_timer->timestamp = cur_timer->interval + time_now();
|
||||
active_timers.insert(cur_timer_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::init_timers(RDRAM_ARG1) {
|
||||
timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 };
|
||||
timer_context.thread.detach();
|
||||
}
|
||||
|
||||
uint32_t ultramodern::get_speed_multiplier() {
|
||||
return speed_multiplier;
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point ultramodern::get_start() {
|
||||
return start_time;
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::duration ultramodern::time_since_start() {
|
||||
return std::chrono::high_resolution_clock::now() - start_time;
|
||||
}
|
||||
|
||||
extern "C" u32 osGetCount() {
|
||||
uint64_t total_count = time_now();
|
||||
|
||||
// Allow for overflows, which is how osGetCount behaves
|
||||
return (uint32_t)total_count;
|
||||
}
|
||||
|
||||
extern "C" OSTime osGetTime() {
|
||||
uint64_t total_count = time_now();
|
||||
|
||||
return total_count;
|
||||
}
|
||||
|
||||
extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) {
|
||||
OSTimer* t = TO_PTR(OSTimer, t_);
|
||||
|
||||
// Determine the time when this timer will trigger off
|
||||
if (countdown == 0) {
|
||||
// Set the timestamp based on the interval
|
||||
t->timestamp = interval + time_now();
|
||||
} else {
|
||||
t->timestamp = countdown + time_now();
|
||||
}
|
||||
t->interval = interval;
|
||||
t->mq = mq;
|
||||
t->msg = msg;
|
||||
|
||||
timer_context.action_queue.enqueue(AddTimerAction{ t_ });
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) {
|
||||
timer_context.action_queue.enqueue(RemoveTimerAction{ t_ });
|
||||
|
||||
// TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// The implementations of std::chrono::sleep_until and sleep_for were affected by changing the system clock backwards in older versions
|
||||
// of Microsoft's STL. This was fixed as of Visual Studio 2022 17.9, but to be safe ultramodern uses Win32 Sleep directly.
|
||||
void ultramodern::sleep_milliseconds(uint32_t millis) {
|
||||
Sleep(millis);
|
||||
}
|
||||
|
||||
void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) {
|
||||
auto time_now = std::chrono::high_resolution_clock::now();
|
||||
if (time_point > time_now) {
|
||||
long long delta_ms = std::chrono::ceil<std::chrono::milliseconds>(time_point - time_now).count();
|
||||
// printf("Sleeping %lld %d ms\n", delta_ms, (uint32_t)delta_ms);
|
||||
Sleep(delta_ms);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void ultramodern::sleep_milliseconds(uint32_t millis) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{millis});
|
||||
}
|
||||
|
||||
void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) {
|
||||
std::this_thread::sleep_until(time_point);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,260 +0,0 @@
|
||||
#ifndef __ULTRA64_ultramodern_H__
|
||||
#define __ULTRA64_ultramodern_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define UNUSED __attribute__((unused))
|
||||
#define ALIGNED(x) __attribute__((aligned(x)))
|
||||
#else
|
||||
#define UNUSED
|
||||
#define ALIGNED(x)
|
||||
#endif
|
||||
|
||||
typedef int64_t s64;
|
||||
typedef uint64_t u64;
|
||||
typedef int32_t s32;
|
||||
typedef uint32_t u32;
|
||||
typedef int16_t s16;
|
||||
typedef uint16_t u16;
|
||||
typedef int8_t s8;
|
||||
typedef uint8_t u8;
|
||||
|
||||
#if 0 // For native compilation
|
||||
# define PTR(x) x*
|
||||
# define RDRAM_ARG
|
||||
# define RDRAM_ARG1
|
||||
# define PASS_RDRAM
|
||||
# define PASS_RDRAM1
|
||||
# define TO_PTR(type, var) var
|
||||
# define GET_MEMBER(type, addr, member) (&addr->member)
|
||||
# ifdef __cplusplus
|
||||
# define NULLPTR nullptr
|
||||
# endif
|
||||
#else
|
||||
# define PTR(x) int32_t
|
||||
# define RDRAM_ARG uint8_t *rdram,
|
||||
# define RDRAM_ARG1 uint8_t *rdram
|
||||
# define PASS_RDRAM rdram,
|
||||
# define PASS_RDRAM1 rdram
|
||||
# define TO_PTR(type, var) ((type*)(&rdram[(uint64_t)var - 0xFFFFFFFF80000000]))
|
||||
# define GET_MEMBER(type, addr, member) (addr + (intptr_t)&(((type*)nullptr)->member))
|
||||
# ifdef __cplusplus
|
||||
# define NULLPTR (PTR(void))0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL (PTR(void) 0)
|
||||
#endif
|
||||
|
||||
#define OS_MESG_NOBLOCK 0
|
||||
#define OS_MESG_BLOCK 1
|
||||
|
||||
typedef s32 OSPri;
|
||||
typedef s32 OSId;
|
||||
|
||||
typedef u64 OSTime;
|
||||
|
||||
#define OS_EVENT_SW1 0 /* CPU SW1 interrupt */
|
||||
#define OS_EVENT_SW2 1 /* CPU SW2 interrupt */
|
||||
#define OS_EVENT_CART 2 /* Cartridge interrupt: used by rmon */
|
||||
#define OS_EVENT_COUNTER 3 /* Counter int: used by VI/Timer Mgr */
|
||||
#define OS_EVENT_SP 4 /* SP task done interrupt */
|
||||
#define OS_EVENT_SI 5 /* SI (controller) interrupt */
|
||||
#define OS_EVENT_AI 6 /* AI interrupt */
|
||||
#define OS_EVENT_VI 7 /* VI interrupt: used by VI/Timer Mgr */
|
||||
#define OS_EVENT_PI 8 /* PI interrupt: used by PI Manager */
|
||||
#define OS_EVENT_DP 9 /* DP full sync interrupt */
|
||||
#define OS_EVENT_CPU_BREAK 10 /* CPU breakpoint: used by rmon */
|
||||
#define OS_EVENT_SP_BREAK 11 /* SP breakpoint: used by rmon */
|
||||
#define OS_EVENT_FAULT 12 /* CPU fault event: used by rmon */
|
||||
#define OS_EVENT_THREADSTATUS 13 /* CPU thread status: used by rmon */
|
||||
#define OS_EVENT_PRENMI 14 /* Pre NMI interrupt */
|
||||
|
||||
#define M_GFXTASK 1
|
||||
#define M_AUDTASK 2
|
||||
#define M_VIDTASK 3
|
||||
#define M_NJPEGTASK 4
|
||||
|
||||
/////////////
|
||||
// Structs //
|
||||
/////////////
|
||||
|
||||
// Threads
|
||||
|
||||
typedef struct UltraThreadContext UltraThreadContext;
|
||||
|
||||
typedef enum {
|
||||
STOPPED,
|
||||
QUEUED,
|
||||
RUNNING,
|
||||
BLOCKED
|
||||
} OSThreadState;
|
||||
|
||||
typedef struct OSThread_t {
|
||||
PTR(struct OSThread_t) next; // Next thread in the given queue
|
||||
OSPri priority;
|
||||
PTR(PTR(struct OSThread_t)) queue; // Queue this thread is in, if any
|
||||
uint32_t pad2;
|
||||
uint16_t flags; // These two are swapped to reflect rdram byteswapping
|
||||
uint16_t state;
|
||||
OSId id;
|
||||
int32_t pad3;
|
||||
UltraThreadContext* context; // An actual pointer regardless of platform
|
||||
int32_t sp;
|
||||
} OSThread;
|
||||
|
||||
typedef u32 OSEvent;
|
||||
typedef PTR(void) OSMesg;
|
||||
|
||||
typedef struct OSMesgQueue {
|
||||
PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */
|
||||
PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */
|
||||
s32 validCount; /* Number of messages in the queue */
|
||||
s32 first; /* Index of the first message in the ring buffer */
|
||||
s32 msgCount; /* Size of message buffer */
|
||||
PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */
|
||||
} OSMesgQueue;
|
||||
|
||||
// RSP
|
||||
|
||||
typedef struct {
|
||||
u32 type;
|
||||
u32 flags;
|
||||
|
||||
PTR(u64) ucode_boot;
|
||||
u32 ucode_boot_size;
|
||||
|
||||
PTR(u64) ucode;
|
||||
u32 ucode_size;
|
||||
|
||||
PTR(u64) ucode_data;
|
||||
u32 ucode_data_size;
|
||||
|
||||
PTR(u64) dram_stack;
|
||||
u32 dram_stack_size;
|
||||
|
||||
PTR(u64) output_buff;
|
||||
PTR(u64) output_buff_size;
|
||||
|
||||
PTR(u64) data_ptr;
|
||||
u32 data_size;
|
||||
|
||||
PTR(u64) yield_data_ptr;
|
||||
u32 yield_data_size;
|
||||
} OSTask_s;
|
||||
|
||||
typedef union {
|
||||
OSTask_s t;
|
||||
int64_t force_structure_alignment;
|
||||
} OSTask;
|
||||
|
||||
// PI
|
||||
|
||||
struct OSIoMesgHdr {
|
||||
// These 3 reversed due to endianness
|
||||
u8 status; /* Return status */
|
||||
u8 pri; /* Message priority (High or Normal) */
|
||||
u16 type; /* Message type */
|
||||
PTR(OSMesgQueue) retQueue; /* Return message queue to notify I/O completion */
|
||||
};
|
||||
|
||||
struct OSIoMesg {
|
||||
OSIoMesgHdr hdr; /* Message header */
|
||||
PTR(void) dramAddr; /* RDRAM buffer address (DMA) */
|
||||
u32 devAddr; /* Device buffer address (DMA) */
|
||||
u32 size; /* DMA transfer size in bytes */
|
||||
u32 piHandle; /* PI device handle */
|
||||
};
|
||||
|
||||
struct OSPiHandle {
|
||||
PTR(OSPiHandle_s) unused; /* point to next handle on the table */
|
||||
// These four members reversed due to endianness
|
||||
u8 relDuration; /* domain release duration */
|
||||
u8 pageSize; /* domain page size */
|
||||
u8 latency; /* domain latency */
|
||||
u8 type; /* DEVICE_TYPE_BULK for disk */
|
||||
// These three members reversed due to endianness
|
||||
u16 padding; /* struct alignment padding */
|
||||
u8 domain; /* which domain */
|
||||
u8 pulse; /* domain pulse width */
|
||||
u32 baseAddress; /* Domain address */
|
||||
u32 speed; /* for roms only */
|
||||
/* The following are "private" elements" */
|
||||
u32 transferInfo[18]; /* for disk only */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
u32 ctrl;
|
||||
u32 width;
|
||||
u32 burst;
|
||||
u32 vSync;
|
||||
u32 hSync;
|
||||
u32 leap;
|
||||
u32 hStart;
|
||||
u32 xScale;
|
||||
u32 vCurrent;
|
||||
} OSViCommonRegs;
|
||||
|
||||
typedef struct {
|
||||
u32 origin;
|
||||
u32 yScale;
|
||||
u32 vStart;
|
||||
u32 vBurst;
|
||||
u32 vIntr;
|
||||
} OSViFieldRegs;
|
||||
|
||||
typedef struct {
|
||||
u8 padding[3];
|
||||
u8 type;
|
||||
OSViCommonRegs comRegs;
|
||||
OSViFieldRegs fldRegs[2];
|
||||
} OSViMode;
|
||||
|
||||
///////////////
|
||||
// Functions //
|
||||
///////////////
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
void osInitialize(void);
|
||||
|
||||
typedef void (thread_func_t)(PTR(void));
|
||||
|
||||
void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry, PTR(void) arg, PTR(void) sp, OSPri p);
|
||||
void osStartThread(RDRAM_ARG PTR(OSThread) t);
|
||||
void osStopThread(RDRAM_ARG PTR(OSThread) t);
|
||||
void osDestroyThread(RDRAM_ARG PTR(OSThread) t);
|
||||
void osYieldThread(RDRAM_ARG1);
|
||||
void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri);
|
||||
OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) thread);
|
||||
OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t);
|
||||
|
||||
void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
|
||||
s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
|
||||
s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
|
||||
s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
|
||||
void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg);
|
||||
void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue), OSMesg, u32);
|
||||
void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr);
|
||||
void osViSetMode(RDRAM_ARG PTR(OSViMode));
|
||||
void osViSetSpecialFeatures(uint32_t func);
|
||||
void osViBlack(uint8_t active);
|
||||
void osViSetXScale(float scale);
|
||||
void osViSetYScale(float scale);
|
||||
PTR(void) osViGetNextFramebuffer();
|
||||
PTR(void) osViGetCurrentFramebuffer();
|
||||
u32 osGetCount();
|
||||
OSTime osGetTime();
|
||||
int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg);
|
||||
int osStopTimer(RDRAM_ARG PTR(OSTimer) timer);
|
||||
u32 osVirtualToPhysical(PTR(void) addr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,14 +0,0 @@
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
void ultramodern::preinit(RDRAM_ARG ultramodern::WindowHandle window_handle) {
|
||||
ultramodern::set_main_thread();
|
||||
ultramodern::init_events(PASS_RDRAM window_handle);
|
||||
ultramodern::init_timers(PASS_RDRAM1);
|
||||
ultramodern::init_audio();
|
||||
ultramodern::init_saving(PASS_RDRAM1);
|
||||
ultramodern::init_thread_cleanup();
|
||||
}
|
||||
|
||||
extern "C" void osInitialize() {
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
#ifndef __ultramodern_HPP__
|
||||
#define __ultramodern_HPP__
|
||||
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <span>
|
||||
|
||||
#undef MOODYCAMEL_DELETE_FUNCTION
|
||||
#define MOODYCAMEL_DELETE_FUNCTION = delete
|
||||
#include "lightweightsemaphore.h"
|
||||
#include "ultra64.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <Windows.h>
|
||||
#elif defined(__ANDROID__)
|
||||
# include "android/native_window.h"
|
||||
#elif defined(__linux__)
|
||||
# include "X11/Xlib.h"
|
||||
# undef None
|
||||
# undef Status
|
||||
# undef LockMask
|
||||
# undef Always
|
||||
# undef Success
|
||||
#endif
|
||||
|
||||
struct UltraThreadContext {
|
||||
std::thread host_thread;
|
||||
moodycamel::LightweightSemaphore running;
|
||||
moodycamel::LightweightSemaphore initialized;
|
||||
};
|
||||
|
||||
namespace ultramodern {
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Native HWND handle to the target window.
|
||||
struct WindowHandle {
|
||||
HWND window;
|
||||
DWORD thread_id = (DWORD)-1;
|
||||
auto operator<=>(const WindowHandle&) const = default;
|
||||
};
|
||||
#elif defined(__ANDROID__)
|
||||
using WindowHandle = ANativeWindow*;
|
||||
#elif defined(__linux__)
|
||||
struct WindowHandle {
|
||||
Display* display;
|
||||
Window window;
|
||||
auto operator<=>(const WindowHandle&) const = default;
|
||||
};
|
||||
#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 drive_handle = (int32_t)(cart_handle + sizeof(OSPiHandle));
|
||||
constexpr int32_t flash_handle = (int32_t)(drive_handle + sizeof(OSPiHandle));
|
||||
constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash
|
||||
|
||||
// Initialization.
|
||||
void preinit(RDRAM_ARG WindowHandle window_handle);
|
||||
void init_saving(RDRAM_ARG1);
|
||||
void init_events(RDRAM_ARG WindowHandle window_handle);
|
||||
void init_timers(RDRAM_ARG1);
|
||||
void init_thread_cleanup();
|
||||
|
||||
// Thread queues.
|
||||
constexpr PTR(PTR(OSThread)) running_queue = (PTR(PTR(OSThread)))-1;
|
||||
|
||||
void thread_queue_insert(RDRAM_ARG PTR(PTR(OSThread)) queue, PTR(OSThread) toadd);
|
||||
PTR(OSThread) thread_queue_pop(RDRAM_ARG PTR(PTR(OSThread)) queue);
|
||||
bool thread_queue_remove(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) t_);
|
||||
bool thread_queue_empty(RDRAM_ARG PTR(PTR(OSThread)) queue);
|
||||
PTR(OSThread) thread_queue_peek(RDRAM_ARG PTR(PTR(OSThread)) queue);
|
||||
|
||||
// Message queues.
|
||||
void wait_for_external_message(RDRAM_ARG1);
|
||||
|
||||
// Thread scheduling.
|
||||
void check_running_queue(RDRAM_ARG1);
|
||||
void run_next_thread_and_wait(RDRAM_ARG1);
|
||||
void resume_thread_and_wait(RDRAM_ARG OSThread* t);
|
||||
void schedule_running_thread(RDRAM_ARG PTR(OSThread) t);
|
||||
void cleanup_thread(UltraThreadContext* thread_context);
|
||||
uint32_t permanent_thread_count();
|
||||
uint32_t temporary_thread_count();
|
||||
struct thread_terminated : std::exception {};
|
||||
|
||||
enum class ThreadPriority {
|
||||
Low,
|
||||
Normal,
|
||||
High,
|
||||
VeryHigh,
|
||||
Critical
|
||||
};
|
||||
|
||||
void set_native_thread_name(const std::string& name);
|
||||
void set_native_thread_priority(ThreadPriority pri);
|
||||
PTR(OSThread) this_thread();
|
||||
void set_main_thread();
|
||||
bool is_game_thread();
|
||||
void submit_rsp_task(RDRAM_ARG PTR(OSTask) task);
|
||||
void send_si_message(RDRAM_ARG1);
|
||||
uint32_t get_speed_multiplier();
|
||||
|
||||
// Time
|
||||
std::chrono::high_resolution_clock::time_point get_start();
|
||||
std::chrono::high_resolution_clock::duration time_since_start();
|
||||
void measure_input_latency();
|
||||
void sleep_milliseconds(uint32_t millis);
|
||||
void sleep_until(const std::chrono::high_resolution_clock::time_point& time_point);
|
||||
|
||||
// Graphics
|
||||
uint32_t get_target_framerate(uint32_t original);
|
||||
uint32_t get_display_refresh_rate();
|
||||
float get_resolution_scale();
|
||||
void load_shader_cache(std::span<const char> cache_data);
|
||||
|
||||
// Audio
|
||||
void init_audio();
|
||||
void set_audio_frequency(uint32_t freq);
|
||||
void queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data, uint32_t byte_count);
|
||||
uint32_t get_remaining_audio_bytes();
|
||||
|
||||
struct audio_callbacks_t {
|
||||
using queue_samples_t = void(int16_t*, size_t);
|
||||
using get_samples_remaining_t = size_t();
|
||||
using set_frequency_t = void(uint32_t);
|
||||
queue_samples_t* queue_samples;
|
||||
get_samples_remaining_t* get_frames_remaining;
|
||||
set_frequency_t* set_frequency;
|
||||
};
|
||||
|
||||
// Input
|
||||
struct input_callbacks_t {
|
||||
using poll_input_t = void(void);
|
||||
using get_input_t = void(uint16_t*, float*, float*);
|
||||
using set_rumble_t = void(bool);
|
||||
poll_input_t* poll_input;
|
||||
get_input_t* get_input;
|
||||
set_rumble_t* set_rumble;
|
||||
};
|
||||
|
||||
struct gfx_callbacks_t {
|
||||
using gfx_data_t = void*;
|
||||
using create_gfx_t = gfx_data_t();
|
||||
using create_window_t = WindowHandle(gfx_data_t);
|
||||
using update_gfx_t = void(gfx_data_t);
|
||||
create_gfx_t* create_gfx;
|
||||
create_window_t* create_window;
|
||||
update_gfx_t* update_gfx;
|
||||
};
|
||||
bool is_game_started();
|
||||
void quit();
|
||||
void join_event_threads();
|
||||
void join_thread_cleaner_thread();
|
||||
void join_saving_thread();
|
||||
|
||||
} // namespace ultramodern
|
||||
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
#define debug_printf(...)
|
||||
//#define debug_printf(...) printf(__VA_ARGS__);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user