Migrate to N64ModernRuntime (#354)
This commit is contained in:
parent
6e9ee3498b
commit
bec699f0bd
|
@ -56,7 +56,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
||||||
cd N64RecompSource
|
cd N64RecompSource
|
||||||
git checkout 6eb7d5bd3ee7f0b79f3fd7adbe931dccbacf7e1b
|
git checkout 8dfed04919b7bfdd0fd34ff049eed7020dea0d71
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
||||||
# enable ccache
|
# enable ccache
|
||||||
|
@ -130,7 +130,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
||||||
cd N64RecompSource
|
cd N64RecompSource
|
||||||
git checkout 6eb7d5bd3ee7f0b79f3fd7adbe931dccbacf7e1b
|
git checkout 8dfed04919b7bfdd0fd34ff049eed7020dea0d71
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
||||||
# enable ccache
|
# enable ccache
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# VSCode file settings
|
# VSCode file settings
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
.vscode/c_cpp_properties.json
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
|
||||||
# Input elf and rom files
|
# Input elf and rom files
|
||||||
*.elf
|
*.elf
|
||||||
|
@ -56,3 +57,4 @@ node_modules/
|
||||||
|
|
||||||
# Recompiler Linux binary
|
# Recompiler Linux binary
|
||||||
N64Recomp
|
N64Recomp
|
||||||
|
.DS_Store
|
||||||
|
|
|
@ -16,3 +16,6 @@
|
||||||
[submodule "lib/sse2neon"]
|
[submodule "lib/sse2neon"]
|
||||||
path = lib/sse2neon
|
path = lib/sse2neon
|
||||||
url = https://github.com/DLTcollab/sse2neon.git
|
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)
|
cmake_policy(SET CMP0135 NEW)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
|
||||||
set(LINUX TRUE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
set (CMAKE_RUNTIME_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)
|
SET(ENABLE_SVG_PLUGIN ON CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi)
|
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi)
|
||||||
|
|
||||||
|
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime)
|
||||||
|
|
||||||
target_include_directories(rt64 PRIVATE ${CMAKE_BINARY_DIR}/rt64/src)
|
target_include_directories(rt64 PRIVATE ${CMAKE_BINARY_DIR}/rt64/src)
|
||||||
|
|
||||||
# RecompiledFuncs - Library containing the primary recompiler output
|
# RecompiledFuncs - Library containing the primary recompiler output
|
||||||
|
@ -48,6 +46,8 @@ target_compile_options(RecompiledFuncs PRIVATE
|
||||||
|
|
||||||
target_include_directories(RecompiledFuncs PRIVATE
|
target_include_directories(RecompiledFuncs PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/include
|
${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)
|
file(GLOB FUNC_C_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.c)
|
||||||
|
@ -65,6 +65,8 @@ target_compile_options(PatchesLib PRIVATE
|
||||||
|
|
||||||
target_include_directories(PatchesLib PRIVATE
|
target_include_directories(PatchesLib PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/include
|
${CMAKE_SOURCE_DIR}/include
|
||||||
|
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/ultramodern/include
|
||||||
|
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/librecomp/include
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(PatchesLib PRIVATE
|
target_sources(PatchesLib PRIVATE
|
||||||
|
@ -92,8 +94,8 @@ add_custom_command(OUTPUT
|
||||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c
|
${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c
|
||||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/recomp_overlays.inl
|
${CMAKE_SOURCE_DIR}/RecompiledPatches/recomp_overlays.inl
|
||||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/funcs.h
|
${CMAKE_SOURCE_DIR}/RecompiledPatches/funcs.h
|
||||||
${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp
|
# TODO: Look into why modifying patches requires two builds to take
|
||||||
COMMAND ./N64Recomp patches.toml && ${CMAKE_COMMAND} -E touch ${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp
|
COMMAND ./N64Recomp patches.toml
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.bin
|
DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.bin
|
||||||
)
|
)
|
||||||
|
@ -112,40 +114,10 @@ if (EXISTS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set (SOURCES
|
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/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/input.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/game/controls.cpp
|
${CMAKE_SOURCE_DIR}/src/game/controls.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/game/config.cpp
|
${CMAKE_SOURCE_DIR}/src/game/config.cpp
|
||||||
|
@ -225,7 +197,7 @@ if (WIN32)
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxil.dll"
|
"${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxil.dll"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxcompiler.dll"
|
"${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxcompiler.dll"
|
||||||
$<TARGET_FILE_DIR:Zelda64Recompiled>)
|
$<TARGET_FILE_DIR:Zelda64Recompiled>)
|
||||||
|
|
||||||
set_target_properties(
|
set_target_properties(
|
||||||
Zelda64Recompiled
|
Zelda64Recompiled
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
|
@ -238,10 +210,10 @@ if (WIN32)
|
||||||
target_sources(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/icons/app.rc)
|
target_sources(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/icons/app.rc)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (LINUX)
|
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
find_package(X11 REQUIRED)
|
find_package(X11 REQUIRED)
|
||||||
|
|
||||||
# Generate icon_bytes.c from the app icon PNG.
|
# Generate icon_bytes.c from the app icon PNG.
|
||||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.h
|
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.h
|
||||||
COMMAND file_to_c ${CMAKE_SOURCE_DIR}/icons/512.png icon_bytes ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.h
|
COMMAND file_to_c ${CMAKE_SOURCE_DIR}/icons/512.png icon_bytes ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.h
|
||||||
|
@ -274,7 +246,7 @@ if (LINUX)
|
||||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
target_link_libraries(Zelda64Recompiled PRIVATE "-latomic -static-libstdc++" ${CMAKE_DL_LIBS} Threads::Threads)
|
target_link_libraries(Zelda64Recompiled PRIVATE "-latomic -static-libstdc++" ${CMAKE_DL_LIBS} Threads::Threads)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -282,6 +254,8 @@ target_link_libraries(Zelda64Recompiled PRIVATE
|
||||||
PatchesLib
|
PatchesLib
|
||||||
RecompiledFuncs
|
RecompiledFuncs
|
||||||
SDL2
|
SDL2
|
||||||
|
librecomp
|
||||||
|
ultramodern
|
||||||
rt64
|
rt64
|
||||||
RmlCore
|
RmlCore
|
||||||
RmlDebugger
|
RmlDebugger
|
||||||
|
|
|
@ -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 {
|
namespace recomp {
|
||||||
// x-macros to build input enums and arrays.
|
// 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.
|
// 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 DEFINE_N64_BUTTON_INPUTS() \
|
||||||
DEFINE_INPUT(A, 0x8000, "Action") \
|
DEFINE_INPUT(A, 0x8000, "Action") \
|
||||||
DEFINE_INPUT(B, 0x4000, "Attack/Cancel") \
|
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 apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out);
|
||||||
void set_right_analog_suppressed(bool suppressed);
|
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 {
|
enum class BackgroundInputMode {
|
||||||
On,
|
On,
|
||||||
Off,
|
Off,
|
||||||
|
@ -196,35 +183,8 @@ namespace recomp {
|
||||||
BackgroundInputMode get_background_input_mode();
|
BackgroundInputMode get_background_input_mode();
|
||||||
void set_background_input_mode(BackgroundInputMode 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 game_input_disabled();
|
||||||
bool all_input_disabled();
|
bool all_input_disabled();
|
||||||
|
|
||||||
// TODO move these
|
|
||||||
void quicksave_save();
|
|
||||||
void quicksave_load();
|
|
||||||
|
|
||||||
void open_quit_game_prompt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#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;
|
class Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace recomp {
|
namespace recompui {
|
||||||
class UiEventListenerInstancer;
|
class UiEventListenerInstancer;
|
||||||
|
|
||||||
class MenuController {
|
class MenuController {
|
||||||
|
@ -118,6 +118,8 @@ namespace recomp {
|
||||||
bool get_cont_active(void);
|
bool get_cont_active(void);
|
||||||
void set_cont_active(bool active);
|
void set_cont_active(bool active);
|
||||||
void activate_mouse();
|
void activate_mouse();
|
||||||
|
|
||||||
|
void message_box(const char* msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#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
|
|
|
@ -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__
|
#ifndef __ZELDA_DEBUG_H__
|
||||||
#define __RECOMP_DEBUG_H__
|
#define __ZELDA_DEBUG_H__
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace recomp {
|
namespace zelda64 {
|
||||||
struct SceneWarps {
|
struct SceneWarps {
|
||||||
int index;
|
int index;
|
||||||
std::string name;
|
std::string name;
|
|
@ -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__
|
#ifndef __ZELDA_SOUND_H__
|
||||||
#define __RECOMP_SOUND_H__
|
#define __ZELDA_SOUND_H__
|
||||||
|
|
||||||
namespace recomp {
|
namespace zelda64 {
|
||||||
void reset_sound_settings();
|
void reset_sound_settings();
|
||||||
void set_main_volume(int volume);
|
void set_main_volume(int volume);
|
||||||
int get_main_volume();
|
int get_main_volume();
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ec7e81b45d9a622cb3e45865ce5d7d3536b26534
|
|
@ -27,7 +27,7 @@ $(C_OBJS): %.o : %.c
|
||||||
$(CC) $(CFLAGS) $(CPPFLAGS) $< -MMD -MF $(@:.o=.d) -c -o $@
|
$(CC) $(CFLAGS) $(CPPFLAGS) $< -MMD -MF $(@:.o=.d) -c -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(C_OBJS) $(TARGET) $(DATABIN)
|
rm -rf $(C_OBJS) $(TARGET) $(DATABIN) $(C_DEPS)
|
||||||
|
|
||||||
-include $(C_DEPS)
|
-include $(C_DEPS)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#ifdef MIPS
|
#ifdef MIPS
|
||||||
#include "ultra64.h"
|
#include "ultra64.h"
|
||||||
#else
|
#else
|
||||||
#include "recomp.h"
|
#include "librecomp/recomp.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include "recomp_config.h"
|
#include "zelda_config.h"
|
||||||
#include "recomp_input.h"
|
#include "recomp_input.h"
|
||||||
#include "recomp_sound.h"
|
#include "zelda_sound.h"
|
||||||
#include "recomp_files.h"
|
#include "ultramodern/config.hpp"
|
||||||
#include "../../ultramodern/config.hpp"
|
#include "librecomp/files.hpp"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#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 graphics_filename = u8"graphics.json";
|
||||||
constexpr std::u8string_view controls_filename = u8"controls.json";
|
constexpr std::u8string_view controls_filename = u8"controls.json";
|
||||||
constexpr std::u8string_view sound_filename = u8"sound.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 res_default = ultramodern::Resolution::Auto;
|
||||||
constexpr auto hr_default = ultramodern::HUDRatioMode::Clamp16x9;
|
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{};
|
std::filesystem::path recomp_dir{};
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
@ -135,7 +136,7 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||||
PWSTR known_path = NULL;
|
PWSTR known_path = NULL;
|
||||||
HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &known_path);
|
HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &known_path);
|
||||||
if (result == S_OK) {
|
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);
|
CoTaskMemFree(known_path);
|
||||||
|
@ -147,7 +148,7 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (homedir != nullptr) {
|
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
|
#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) {
|
bool save_general_config(const std::filesystem::path& path) {
|
||||||
nlohmann::json config_json{};
|
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());
|
recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode());
|
||||||
config_json["rumble_strength"] = recomp::get_rumble_strength();
|
config_json["rumble_strength"] = recomp::get_rumble_strength();
|
||||||
config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity();
|
config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity();
|
||||||
config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity();
|
config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity();
|
||||||
config_json["joystick_deadzone"] = recomp::get_joystick_deadzone();
|
config_json["joystick_deadzone"] = recomp::get_joystick_deadzone();
|
||||||
config_json["autosave_mode"] = recomp::get_autosave_mode();
|
config_json["autosave_mode"] = zelda64::get_autosave_mode();
|
||||||
config_json["camera_invert_mode"] = recomp::get_camera_invert_mode();
|
config_json["camera_invert_mode"] = zelda64::get_camera_invert_mode();
|
||||||
config_json["analog_cam_mode"] = recomp::get_analog_cam_mode();
|
config_json["analog_cam_mode"] = zelda64::get_analog_cam_mode();
|
||||||
config_json["analog_camera_invert_mode"] = recomp::get_analog_camera_invert_mode();
|
config_json["analog_camera_invert_mode"] = zelda64::get_analog_camera_invert_mode();
|
||||||
config_json["debug_mode"] = recomp::get_debug_mode_enabled();
|
config_json["debug_mode"] = zelda64::get_debug_mode_enabled();
|
||||||
|
|
||||||
return save_json_with_backups(path, config_json);
|
return save_json_with_backups(path, config_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_general_settings_from_json(const nlohmann::json& 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_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_rumble_strength(from_or_default(config_json, "rumble_strength", 25));
|
||||||
recomp::set_gyro_sensitivity(from_or_default(config_json, "gyro_sensitivity", 50));
|
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_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_joystick_deadzone(from_or_default(config_json, "joystick_deadzone", 5));
|
||||||
recomp::set_autosave_mode(from_or_default(config_json, "autosave_mode", recomp::AutosaveMode::On));
|
zelda64::set_autosave_mode(from_or_default(config_json, "autosave_mode", zelda64::AutosaveMode::On));
|
||||||
recomp::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", recomp::CameraInvertMode::InvertY));
|
zelda64::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", zelda64::CameraInvertMode::InvertY));
|
||||||
recomp::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", recomp::AnalogCamMode::Off));
|
zelda64::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", zelda64::AnalogCamMode::Off));
|
||||||
recomp::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", recomp::CameraInvertMode::InvertNone));
|
zelda64::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", zelda64::CameraInvertMode::InvertNone));
|
||||||
recomp::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
zelda64::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool load_general_config(const std::filesystem::path& path) {
|
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);
|
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::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_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);
|
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);
|
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,
|
cur_input,
|
||||||
recomp::get_default_mapping_for_input(
|
recomp::get_default_mapping_for_input(
|
||||||
device == recomp::InputDevice::Keyboard ?
|
device == recomp::InputDevice::Keyboard ?
|
||||||
recomp::default_n64_keyboard_mappings :
|
recomp::default_n64_keyboard_mappings :
|
||||||
recomp::default_n64_controller_mappings,
|
recomp::default_n64_controller_mappings,
|
||||||
cur_input
|
cur_input
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -408,10 +409,10 @@ bool load_controls_config(const std::filesystem::path& path) {
|
||||||
bool save_sound_config(const std::filesystem::path& path) {
|
bool save_sound_config(const std::filesystem::path& path) {
|
||||||
nlohmann::json config_json{};
|
nlohmann::json config_json{};
|
||||||
|
|
||||||
config_json["main_volume"] = recomp::get_main_volume();
|
config_json["main_volume"] = zelda64::get_main_volume();
|
||||||
config_json["bgm_volume"] = recomp::get_bgm_volume();
|
config_json["bgm_volume"] = zelda64::get_bgm_volume();
|
||||||
config_json["low_health_beeps"] = recomp::get_low_health_beeps_enabled();
|
config_json["low_health_beeps"] = zelda64::get_low_health_beeps_enabled();
|
||||||
|
|
||||||
return save_json_with_backups(path, config_json);
|
return save_json_with_backups(path, config_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,17 +422,17 @@ bool load_sound_config(const std::filesystem::path& path) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::reset_sound_settings();
|
zelda64::reset_sound_settings();
|
||||||
call_if_key_exists(recomp::set_main_volume, config_json, "main_volume");
|
call_if_key_exists(zelda64::set_main_volume, config_json, "main_volume");
|
||||||
call_if_key_exists(recomp::set_bgm_volume, config_json, "bgm_volume");
|
call_if_key_exists(zelda64::set_bgm_volume, config_json, "bgm_volume");
|
||||||
call_if_key_exists(recomp::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
call_if_key_exists(zelda64::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::load_config() {
|
void zelda64::load_config() {
|
||||||
detect_steam_deck();
|
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 general_path = recomp_dir / general_filename;
|
||||||
std::filesystem::path graphics_path = recomp_dir / graphics_filename;
|
std::filesystem::path graphics_path = recomp_dir / graphics_filename;
|
||||||
std::filesystem::path controls_path = recomp_dir / controls_filename;
|
std::filesystem::path controls_path = recomp_dir / controls_filename;
|
||||||
|
@ -455,18 +456,18 @@ void recomp::load_config() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!load_controls_config(controls_path)) {
|
if (!load_controls_config(controls_path)) {
|
||||||
recomp::reset_input_bindings();
|
zelda64::reset_input_bindings();
|
||||||
save_controls_config(controls_path);
|
save_controls_config(controls_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!load_sound_config(sound_path)) {
|
if (!load_sound_config(sound_path)) {
|
||||||
recomp::reset_sound_settings();
|
zelda64::reset_sound_settings();
|
||||||
save_sound_config(sound_path);
|
save_sound_config(sound_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::save_config() {
|
void zelda64::save_config() {
|
||||||
std::filesystem::path recomp_dir = recomp::get_app_folder_path();
|
std::filesystem::path recomp_dir = zelda64::get_app_folder_path();
|
||||||
|
|
||||||
if (recomp_dir.empty()) {
|
if (recomp_dir.empty()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "recomp_helpers.h"
|
#include "librecomp/helpers.hpp"
|
||||||
#include "recomp_input.h"
|
#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.
|
// Arrays that hold the mappings for every input for keyboard and controller respectively.
|
||||||
using input_mapping = std::array<recomp::InputField, recomp::bindings_per_input>;
|
using input_mapping = std::array<recomp::InputField, recomp::bindings_per_input>;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include "recomp_debug.h"
|
#include "zelda_debug.h"
|
||||||
#include "recomp_helpers.h"
|
#include "librecomp/helpers.hpp"
|
||||||
#include "../patches/input.h"
|
#include "../patches/input.h"
|
||||||
|
|
||||||
std::atomic<uint16_t> pending_warp = 0xFFFF;
|
std::atomic<uint16_t> pending_warp = 0xFFFF;
|
||||||
std::atomic<uint32_t> pending_set_time = 0xFFFF;
|
std::atomic<uint32_t> pending_set_time = 0xFFFF;
|
||||||
|
|
||||||
void recomp::do_warp(int area, int scene, int entrance) {
|
void zelda64::do_warp(int area, int scene, int entrance) {
|
||||||
const recomp::SceneWarps game_scene = recomp::game_warps[area].scenes[scene];
|
const zelda64::SceneWarps game_scene = zelda64::game_warps[area].scenes[scene];
|
||||||
int game_scene_index = game_scene.index;
|
int game_scene_index = game_scene.index;
|
||||||
pending_warp.store(((game_scene_index & 0xFF) << 8) | ((entrance & 0x0F) << 4));
|
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));
|
_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);
|
pending_set_time.store((day << 16) | (uint16_t(hour) << 8) | minute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "../ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
#include "recomp.h"
|
#include "librecomp/recomp.h"
|
||||||
#include "recomp_input.h"
|
#include "recomp_input.h"
|
||||||
|
#include "zelda_config.h"
|
||||||
#include "recomp_ui.h"
|
#include "recomp_ui.h"
|
||||||
#include "SDL.h"
|
#include "SDL.h"
|
||||||
#include "rt64_layer.h"
|
|
||||||
#include "promptfont.h"
|
#include "promptfont.h"
|
||||||
#include "GamepadMotion.hpp"
|
#include "GamepadMotion.hpp"
|
||||||
|
|
||||||
|
@ -75,13 +75,13 @@ void recomp::stop_scanning_input() {
|
||||||
|
|
||||||
void queue_if_enabled(SDL_Event* event) {
|
void queue_if_enabled(SDL_Event* event) {
|
||||||
if (!recomp::all_input_disabled()) {
|
if (!recomp::all_input_disabled()) {
|
||||||
recomp::queue_event(*event);
|
recompui::queue_event(*event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::atomic_bool cursor_enabled = true;
|
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);
|
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)) ||
|
if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) ||
|
||||||
keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11
|
keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11
|
||||||
) {
|
) {
|
||||||
recomp::toggle_fullscreen();
|
recompui::toggle_fullscreen();
|
||||||
}
|
}
|
||||||
if (scanning_device != recomp::InputDevice::COUNT) {
|
if (scanning_device != recomp::InputDevice::COUNT) {
|
||||||
if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
|
if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
|
||||||
|
@ -155,12 +155,12 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recomp::get_current_menu() != recomp::Menu::Config) {
|
if (recompui::get_current_menu() != recompui::Menu::Config) {
|
||||||
recomp::set_current_menu(recomp::Menu::Config);
|
recompui::set_current_menu(recompui::Menu::Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::open_quit_game_prompt();
|
zelda64::open_quit_game_prompt();
|
||||||
recomp::activate_mouse();
|
recompui::activate_mouse();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SDL_EventType::SDL_MOUSEWHEEL:
|
case SDL_EventType::SDL_MOUSEWHEEL:
|
||||||
|
@ -435,10 +435,10 @@ void recomp::poll_inputs() {
|
||||||
bool save_is_held = InputState.keys[SDL_SCANCODE_F5] != 0;
|
bool save_is_held = InputState.keys[SDL_SCANCODE_F5] != 0;
|
||||||
bool load_is_held = InputState.keys[SDL_SCANCODE_F7] != 0;
|
bool load_is_held = InputState.keys[SDL_SCANCODE_F7] != 0;
|
||||||
if (save_is_held && !save_was_held) {
|
if (save_is_held && !save_was_held) {
|
||||||
recomp::quicksave_save();
|
zelda64::quicksave_save();
|
||||||
}
|
}
|
||||||
else if (load_is_held && !load_was_held) {
|
else if (load_is_held && !load_was_held) {
|
||||||
recomp::quicksave_load();
|
zelda64::quicksave_load();
|
||||||
}
|
}
|
||||||
save_was_held = save_is_held;
|
save_was_held = save_is_held;
|
||||||
}
|
}
|
||||||
|
@ -649,7 +649,7 @@ void recomp::set_right_analog_suppressed(bool suppressed) {
|
||||||
|
|
||||||
bool recomp::game_input_disabled() {
|
bool recomp::game_input_disabled() {
|
||||||
// Disable input if any menu is open.
|
// 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() {
|
bool recomp::all_input_disabled() {
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
|
|
||||||
#include "recomp_helpers.h"
|
#include "librecomp/helpers.hpp"
|
||||||
#include "recomp_input.h"
|
#include "librecomp/input.hpp"
|
||||||
#include "../ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
|
|
||||||
enum class QuicksaveAction {
|
enum class QuicksaveAction {
|
||||||
None,
|
None,
|
||||||
|
@ -14,11 +14,11 @@ enum class QuicksaveAction {
|
||||||
|
|
||||||
std::atomic<QuicksaveAction> cur_quicksave_action = QuicksaveAction::None;
|
std::atomic<QuicksaveAction> cur_quicksave_action = QuicksaveAction::None;
|
||||||
|
|
||||||
void recomp::quicksave_save() {
|
void zelda64::quicksave_save() {
|
||||||
cur_quicksave_action.store(QuicksaveAction::Save);
|
cur_quicksave_action.store(QuicksaveAction::Save);
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::quicksave_load() {
|
void zelda64::quicksave_load() {
|
||||||
cur_quicksave_action.store(QuicksaveAction::Load);
|
cur_quicksave_action.store(QuicksaveAction::Load);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include "recomp.h"
|
#include "librecomp/recomp.h"
|
||||||
#include "recomp_overlays.h"
|
#include "librecomp/overlays.hpp"
|
||||||
#include "recomp_config.h"
|
#include "zelda_config.h"
|
||||||
#include "recomp_input.h"
|
#include "recomp_input.h"
|
||||||
#include "recomp_ui.h"
|
#include "recomp_ui.h"
|
||||||
#include "recomp_sound.h"
|
#include "zelda_sound.h"
|
||||||
#include "recomp_helpers.h"
|
#include "librecomp/helpers.hpp"
|
||||||
#include "rt64_layer.h"
|
|
||||||
#include "../patches/input.h"
|
#include "../patches/input.h"
|
||||||
#include "../patches/graphics.h"
|
#include "../patches/graphics.h"
|
||||||
#include "../patches/sound.h"
|
#include "../patches/sound.h"
|
||||||
#include "../ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
#include "../ultramodern/config.hpp"
|
#include "ultramodern/config.hpp"
|
||||||
|
#include "ultramodern/rt64_layer.hpp"
|
||||||
|
|
||||||
extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||||
recomp::poll_inputs();
|
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();
|
ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config();
|
||||||
float original = _arg<0, float>(rdram, ctx);
|
float original = _arg<0, float>(rdram, ctx);
|
||||||
int width, height;
|
int width, height;
|
||||||
recomp::get_window_size(width, height);
|
recompui::get_window_size(width, height);
|
||||||
|
|
||||||
switch (graphics_config.ar_option) {
|
switch (graphics_config.ar_option) {
|
||||||
case RT64::UserConfiguration::AspectRatio::Original:
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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* x_out = _arg<0, s32*>(rdram, ctx);
|
||||||
s32* y_out = _arg<1, 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);
|
*x_out = (mode == zelda64::CameraInvertMode::InvertX || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||||
*y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::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) {
|
extern "C" void recomp_get_analog_inverted_axes(uint8_t* rdram, recomp_context* ctx) {
|
||||||
s32* x_out = _arg<0, s32*>(rdram, ctx);
|
s32* x_out = _arg<0, s32*>(rdram, ctx);
|
||||||
s32* y_out = _arg<1, 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);
|
*x_out = (mode == zelda64::CameraInvertMode::InvertX || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||||
*y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::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) {
|
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) {
|
extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#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", {
|
{ "Clock Town", {
|
||||||
{
|
{
|
||||||
0, "Mayor's Residence", {
|
0, "Mayor's Residence", {
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
#include "nfd.h"
|
#include "nfd.h"
|
||||||
|
|
||||||
#include "../../ultramodern/ultra64.h"
|
#include "ultramodern/ultra64.h"
|
||||||
#include "../../ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
#define SDL_MAIN_HANDLED
|
#define SDL_MAIN_HANDLED
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include "SDL.h"
|
#include "SDL.h"
|
||||||
|
@ -21,9 +22,14 @@
|
||||||
|
|
||||||
#include "recomp_ui.h"
|
#include "recomp_ui.h"
|
||||||
#include "recomp_input.h"
|
#include "recomp_input.h"
|
||||||
#include "recomp_config.h"
|
#include "zelda_config.h"
|
||||||
#include "recomp_game.h"
|
#include "zelda_sound.h"
|
||||||
#include "recomp_sound.h"
|
#include "ovl_patches.hpp"
|
||||||
|
#include "librecomp/game.hpp"
|
||||||
|
|
||||||
|
#ifdef HAS_MM_SHADER_CACHE
|
||||||
|
#include "mm_shader_cache.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#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
|
// 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.
|
// 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) {
|
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 + 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;
|
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();
|
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) {
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -333,7 +375,20 @@ int main(int argc, char** argv) {
|
||||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||||
reset_audio(48000);
|
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{
|
ultramodern::gfx_callbacks_t gfx_callbacks{
|
||||||
.create_gfx = create_gfx,
|
.create_gfx = create_gfx,
|
||||||
|
@ -353,8 +408,17 @@ int main(int argc, char** argv) {
|
||||||
.set_rumble = recomp::set_rumble,
|
.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();
|
NFD_Quit();
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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>;
|
using ColourMap = Rml::UnorderedMap<Rml::String, Rml::Colourb>;
|
||||||
|
|
||||||
namespace recomp {
|
namespace recompui {
|
||||||
class PropertyParserColorHack : public Rml::PropertyParser {
|
class PropertyParserColorHack : public Rml::PropertyParser {
|
||||||
public:
|
public:
|
||||||
PropertyParserColorHack();
|
PropertyParserColorHack();
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#include "recomp_ui.h"
|
#include "recomp_ui.h"
|
||||||
#include "recomp_input.h"
|
#include "recomp_input.h"
|
||||||
#include "recomp_sound.h"
|
#include "zelda_sound.h"
|
||||||
#include "recomp_config.h"
|
#include "zelda_config.h"
|
||||||
#include "recomp_debug.h"
|
#include "zelda_debug.h"
|
||||||
#include "promptfont.h"
|
#include "promptfont.h"
|
||||||
#include "../../ultramodern/config.hpp"
|
#include "ultramodern/config.hpp"
|
||||||
#include "../../ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
#include "RmlUi/Core.h"
|
#include "RmlUi/Core.h"
|
||||||
#include "rt64_layer.h"
|
#include "ultramodern/rt64_layer.hpp"
|
||||||
|
|
||||||
ultramodern::GraphicsConfig new_options;
|
ultramodern::GraphicsConfig new_options;
|
||||||
Rml::DataModelHandle nav_help_model_handle;
|
Rml::DataModelHandle nav_help_model_handle;
|
||||||
|
@ -16,9 +16,9 @@ Rml::DataModelHandle controls_model_handle;
|
||||||
Rml::DataModelHandle graphics_model_handle;
|
Rml::DataModelHandle graphics_model_handle;
|
||||||
Rml::DataModelHandle sound_options_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 {
|
const std::unordered_map<ButtonVariant, std::string> button_variants {
|
||||||
{ButtonVariant::Primary, "primary"},
|
{ButtonVariant::Primary, "primary"},
|
||||||
{ButtonVariant::Secondary, "secondary"},
|
{ButtonVariant::Secondary, "secondary"},
|
||||||
|
@ -164,7 +164,7 @@ static bool cont_active = true;
|
||||||
static recomp::InputDevice cur_device = recomp::InputDevice::Controller;
|
static recomp::InputDevice cur_device = recomp::InputDevice::Controller;
|
||||||
|
|
||||||
void recomp::finish_scanning_input(recomp::InputField scanned_field) {
|
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_input_index = -1;
|
||||||
scanned_binding_index = -1;
|
scanned_binding_index = -1;
|
||||||
controls_model_handle.DirtyVariable("inputs");
|
controls_model_handle.DirtyVariable("inputs");
|
||||||
|
@ -173,7 +173,7 @@ void recomp::finish_scanning_input(recomp::InputField scanned_field) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::cancel_scanning_input() {
|
void recomp::cancel_scanning_input() {
|
||||||
recomp::stop_scanning_input();
|
recomp::stop_scanning_input();
|
||||||
scanned_input_index = -1;
|
scanned_input_index = -1;
|
||||||
scanned_binding_index = -1;
|
scanned_binding_index = -1;
|
||||||
controls_model_handle.DirtyVariable("inputs");
|
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() {
|
void close_config_menu_impl() {
|
||||||
recomp::save_config();
|
zelda64::save_config();
|
||||||
|
|
||||||
if (ultramodern::is_game_started()) {
|
if (ultramodern::is_game_started()) {
|
||||||
recomp::set_current_menu(recomp::Menu::None);
|
recompui::set_current_menu(recompui::Menu::None);
|
||||||
}
|
}
|
||||||
else {
|
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();
|
graphics_model_handle.DirtyAllVariables();
|
||||||
close_config_menu_impl();
|
close_config_menu_impl();
|
||||||
},
|
},
|
||||||
recomp::ButtonVariant::Success,
|
recompui::ButtonVariant::Success,
|
||||||
recomp::ButtonVariant::Error,
|
recompui::ButtonVariant::Error,
|
||||||
true,
|
true,
|
||||||
"config__close-menu-button"
|
"config__close-menu-button"
|
||||||
);
|
);
|
||||||
|
@ -252,7 +252,7 @@ void close_config_menu() {
|
||||||
close_config_menu_impl();
|
close_config_menu_impl();
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::open_quit_game_prompt() {
|
void zelda64::open_quit_game_prompt() {
|
||||||
prompt_context.open_prompt(
|
prompt_context.open_prompt(
|
||||||
"Are you sure you want to quit?",
|
"Are you sure you want to quit?",
|
||||||
"Any progress since your last save will be lost.",
|
"Any progress since your last save will be lost.",
|
||||||
|
@ -262,8 +262,8 @@ void recomp::open_quit_game_prompt() {
|
||||||
ultramodern::quit();
|
ultramodern::quit();
|
||||||
},
|
},
|
||||||
[]() {},
|
[]() {},
|
||||||
recomp::ButtonVariant::Error,
|
recompui::ButtonVariant::Error,
|
||||||
recomp::ButtonVariant::Tertiary,
|
recompui::ButtonVariant::Tertiary,
|
||||||
true,
|
true,
|
||||||
"config__quit-game-button"
|
"config__quit-game-button"
|
||||||
);
|
);
|
||||||
|
@ -275,12 +275,12 @@ struct ControlOptionsContext {
|
||||||
int gyro_sensitivity; // 0 to 100
|
int gyro_sensitivity; // 0 to 100
|
||||||
int mouse_sensitivity; // 0 to 100
|
int mouse_sensitivity; // 0 to 100
|
||||||
int joystick_deadzone; // 0 to 100
|
int joystick_deadzone; // 0 to 100
|
||||||
recomp::TargetingMode targeting_mode;
|
zelda64::TargetingMode targeting_mode;
|
||||||
recomp::BackgroundInputMode background_input_mode;
|
recomp::BackgroundInputMode background_input_mode;
|
||||||
recomp::AutosaveMode autosave_mode;
|
zelda64::AutosaveMode autosave_mode;
|
||||||
recomp::CameraInvertMode camera_invert_mode;
|
zelda64::CameraInvertMode camera_invert_mode;
|
||||||
recomp::AnalogCamMode analog_cam_mode;
|
zelda64::AnalogCamMode analog_cam_mode;
|
||||||
recomp::CameraInvertMode analog_camera_invert_mode;
|
zelda64::CameraInvertMode analog_camera_invert_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
ControlOptionsContext control_options_context;
|
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;
|
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;
|
control_options_context.targeting_mode = mode;
|
||||||
if (general_model_handle) {
|
if (general_model_handle) {
|
||||||
general_model_handle.DirtyVariable("targeting_mode");
|
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;
|
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;
|
control_options_context.autosave_mode = mode;
|
||||||
if (general_model_handle) {
|
if (general_model_handle) {
|
||||||
general_model_handle.DirtyVariable("autosave_mode");
|
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;
|
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;
|
control_options_context.camera_invert_mode = mode;
|
||||||
if (general_model_handle) {
|
if (general_model_handle) {
|
||||||
general_model_handle.DirtyVariable("camera_invert_mode");
|
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;
|
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;
|
control_options_context.analog_cam_mode = mode;
|
||||||
if (general_model_handle) {
|
if (general_model_handle) {
|
||||||
general_model_handle.DirtyVariable("analog_cam_mode");
|
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;
|
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;
|
control_options_context.analog_camera_invert_mode = mode;
|
||||||
if (general_model_handle) {
|
if (general_model_handle) {
|
||||||
general_model_handle.DirtyVariable("analog_camera_invert_mode");
|
general_model_handle.DirtyVariable("analog_camera_invert_mode");
|
||||||
|
@ -417,43 +417,43 @@ struct SoundOptionsContext {
|
||||||
|
|
||||||
SoundOptionsContext sound_options_context;
|
SoundOptionsContext sound_options_context;
|
||||||
|
|
||||||
void recomp::reset_sound_settings() {
|
void zelda64::reset_sound_settings() {
|
||||||
sound_options_context.reset();
|
sound_options_context.reset();
|
||||||
if (sound_options_model_handle) {
|
if (sound_options_model_handle) {
|
||||||
sound_options_model_handle.DirtyAllVariables();
|
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);
|
sound_options_context.main_volume.store(volume);
|
||||||
if (sound_options_model_handle) {
|
if (sound_options_model_handle) {
|
||||||
sound_options_model_handle.DirtyVariable("main_volume");
|
sound_options_model_handle.DirtyVariable("main_volume");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int recomp::get_main_volume() {
|
int zelda64::get_main_volume() {
|
||||||
return sound_options_context.main_volume.load();
|
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);
|
sound_options_context.bgm_volume.store(volume);
|
||||||
if (sound_options_model_handle) {
|
if (sound_options_model_handle) {
|
||||||
sound_options_model_handle.DirtyVariable("bgm_volume");
|
sound_options_model_handle.DirtyVariable("bgm_volume");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int recomp::get_bgm_volume() {
|
int zelda64::get_bgm_volume() {
|
||||||
return sound_options_context.bgm_volume.load();
|
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);
|
sound_options_context.low_health_beeps_enabled.store((int)enabled);
|
||||||
if (sound_options_model_handle) {
|
if (sound_options_model_handle) {
|
||||||
sound_options_model_handle.DirtyVariable("low_health_beeps_enabled");
|
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();
|
return (bool)sound_options_context.low_health_beeps_enabled.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,7 +471,7 @@ struct DebugContext {
|
||||||
bool debug_enabled = false;
|
bool debug_enabled = false;
|
||||||
|
|
||||||
DebugContext() {
|
DebugContext() {
|
||||||
for (const auto& area : recomp::game_warps) {
|
for (const auto& area : zelda64::game_warps) {
|
||||||
area_names.emplace_back(area.name);
|
area_names.emplace_back(area.name);
|
||||||
}
|
}
|
||||||
update_warp_names();
|
update_warp_names();
|
||||||
|
@ -479,15 +479,15 @@ struct DebugContext {
|
||||||
|
|
||||||
void update_warp_names() {
|
void update_warp_names() {
|
||||||
scene_names.clear();
|
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);
|
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;
|
static uint32_t lastRate = 0;
|
||||||
if (!graphics_model_handle) return;
|
if (!graphics_model_handle) return;
|
||||||
|
|
||||||
|
@ -500,7 +500,7 @@ void recomp::update_rml_display_refresh_rate() {
|
||||||
|
|
||||||
DebugContext debug_context;
|
DebugContext debug_context;
|
||||||
|
|
||||||
class ConfigMenu : public recomp::MenuController {
|
class ConfigMenu : public recompui::MenuController {
|
||||||
public:
|
public:
|
||||||
ConfigMenu() {
|
ConfigMenu() {
|
||||||
|
|
||||||
|
@ -511,13 +511,13 @@ public:
|
||||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||||
return context->LoadDocument("assets/config_menu.rml");
|
return context->LoadDocument("assets/config_menu.rml");
|
||||||
}
|
}
|
||||||
void register_events(recomp::UiEventListenerInstancer& listener) override {
|
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||||
recomp::register_event(listener, "apply_options",
|
recompui::register_event(listener, "apply_options",
|
||||||
[](const std::string& param, Rml::Event& event) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
graphics_model_handle.DirtyVariable("options_changed");
|
graphics_model_handle.DirtyVariable("options_changed");
|
||||||
apply_graphics_config();
|
apply_graphics_config();
|
||||||
});
|
});
|
||||||
recomp::register_event(listener, "config_keydown",
|
recompui::register_event(listener, "config_keydown",
|
||||||
[](const std::string& param, Rml::Event& event) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
if (!prompt_context.open && event.GetId() == Rml::EventId::Keydown) {
|
if (!prompt_context.open && event.GetId() == Rml::EventId::Keydown) {
|
||||||
auto key = event.GetParameter<Rml::Input::KeyIdentifier>("key_identifier", Rml::Input::KeyIdentifier::KI_UNKNOWN);
|
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
|
// 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) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
if (event.GetPhase() == Rml::EventPhase::Target) {
|
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||||
close_config_menu();
|
close_config_menu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
recomp::register_event(listener, "close_config_menu",
|
recompui::register_event(listener, "close_config_menu",
|
||||||
[](const std::string& param, Rml::Event& event) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
close_config_menu();
|
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) {
|
[](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) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
cur_device = cur_device == recomp::InputDevice::Controller
|
cur_device = cur_device == recomp::InputDevice::Controller
|
||||||
? recomp::InputDevice::Keyboard
|
? recomp::InputDevice::Keyboard
|
||||||
|
@ -558,7 +558,7 @@ public:
|
||||||
controls_model_handle.DirtyVariable("inputs");
|
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) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
debug_context.area_index = event.GetParameter<int>("value", 0);
|
debug_context.area_index = event.GetParameter<int>("value", 0);
|
||||||
debug_context.scene_index = 0;
|
debug_context.scene_index = 0;
|
||||||
|
@ -570,7 +570,7 @@ public:
|
||||||
debug_context.model_handle.DirtyVariable("entrance_names");
|
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) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
debug_context.scene_index = event.GetParameter<int>("value", 0);
|
debug_context.scene_index = event.GetParameter<int>("value", 0);
|
||||||
debug_context.entrance_index = 0;
|
debug_context.entrance_index = 0;
|
||||||
|
@ -579,14 +579,14 @@ public:
|
||||||
debug_context.model_handle.DirtyVariable("entrance_names");
|
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) {
|
[](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) {
|
[](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) {
|
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||||
scanned_input_index = inputs.at(0).Get<size_t>();
|
scanned_input_index = inputs.at(0).Get<size_t>();
|
||||||
scanned_binding_index = inputs.at(1).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_input");
|
||||||
model_handle.DirtyVariable("active_binding_slot");
|
model_handle.DirtyVariable("active_binding_slot");
|
||||||
});
|
});
|
||||||
|
@ -730,18 +730,18 @@ public:
|
||||||
constructor.BindEventCallback("reset_input_bindings_to_defaults",
|
constructor.BindEventCallback("reset_input_bindings_to_defaults",
|
||||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||||
if (cur_device == recomp::InputDevice::Controller) {
|
if (cur_device == recomp::InputDevice::Controller) {
|
||||||
recomp::reset_cont_input_bindings();
|
zelda64::reset_cont_input_bindings();
|
||||||
} else {
|
} else {
|
||||||
recomp::reset_kb_input_bindings();
|
zelda64::reset_kb_input_bindings();
|
||||||
}
|
}
|
||||||
model_handle.DirtyAllVariables();
|
model_handle.DirtyAllVariables();
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor.BindEventCallback("clear_input_bindings",
|
constructor.BindEventCallback("clear_input_bindings",
|
||||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
[](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++) {
|
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");
|
model_handle.DirtyVariable("inputs");
|
||||||
});
|
});
|
||||||
|
@ -776,7 +776,7 @@ public:
|
||||||
|
|
||||||
virtual int Size(void* ptr) override { return recomp::bindings_per_input; }
|
virtual int Size(void* ptr) override { return recomp::bindings_per_input; }
|
||||||
virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override {
|
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)};
|
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);
|
return Rml::DataVariable(&binding_array_var_instance, nullptr);
|
||||||
}
|
}
|
||||||
else {
|
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) {
|
if (input != recomp::GameInput::COUNT) {
|
||||||
return Rml::DataVariable(&binding_container_var_instance, (void*)(uintptr_t)input);
|
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__confirmLabel", &prompt_context.confirmLabel);
|
||||||
constructor.Bind("prompt__cancelLabel", &prompt_context.cancelLabel);
|
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();
|
prompt_context.model_handle = constructor.GetModelHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void make_bindings(Rml::Context* context) override {
|
void make_bindings(Rml::Context* context) override {
|
||||||
// initially set cont state for ui help
|
// 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_nav_help_bindings(context);
|
||||||
make_general_bindings(context);
|
make_general_bindings(context);
|
||||||
make_controls_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>();
|
return std::make_unique<ConfigMenu>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool recomp::get_debug_mode_enabled() {
|
bool zelda64::get_debug_mode_enabled() {
|
||||||
return debug_context.debug_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;
|
debug_context.debug_enabled = enabled;
|
||||||
if (debug_context.model_handle) {
|
if (debug_context.model_handle) {
|
||||||
debug_context.model_handle.DirtyVariable("debug_enabled");
|
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;
|
msaa2x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||||
msaa4x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X;
|
msaa4x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||||
msaa8x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X;
|
msaa8x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||||
|
@ -1010,7 +1010,7 @@ void recomp::update_supported_options() {
|
||||||
graphics_model_handle.DirtyAllVariables();
|
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;
|
new_options.wm_option = (new_options.wm_option == ultramodern::WindowMode::Windowed) ? ultramodern::WindowMode::Fullscreen : ultramodern::WindowMode::Windowed;
|
||||||
apply_graphics_config();
|
apply_graphics_config();
|
||||||
graphics_model_handle.DirtyVariable("wm_option");
|
graphics_model_handle.DirtyVariable("wm_option");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include "recomp_ui.h"
|
#include "recomp_ui.h"
|
||||||
#include "recomp_config.h"
|
#include "zelda_config.h"
|
||||||
#include "recomp_game.h"
|
#include "librecomp/game.hpp"
|
||||||
#include "../../ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
#include "RmlUi/Core.h"
|
#include "RmlUi/Core.h"
|
||||||
#include "nfd.h"
|
#include "nfd.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
@ -11,6 +11,8 @@ std::string version_number = "v1.1.1";
|
||||||
Rml::DataModelHandle model_handle;
|
Rml::DataModelHandle model_handle;
|
||||||
bool mm_rom_valid = false;
|
bool mm_rom_valid = false;
|
||||||
|
|
||||||
|
extern std::vector<recomp::GameEntry> supported_games;
|
||||||
|
|
||||||
void select_rom() {
|
void select_rom() {
|
||||||
nfdnchar_t* native_path = nullptr;
|
nfdnchar_t* native_path = nullptr;
|
||||||
nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
|
nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
|
||||||
|
@ -21,39 +23,39 @@ void select_rom() {
|
||||||
NFD_FreePathN(native_path);
|
NFD_FreePathN(native_path);
|
||||||
native_path = nullptr;
|
native_path = nullptr;
|
||||||
|
|
||||||
recomp::RomValidationError rom_error = recomp::select_rom(path, recomp::Game::MM);
|
recomp::RomValidationError rom_error = recomp::select_rom(path, supported_games[0].game_id);
|
||||||
|
switch (rom_error) {
|
||||||
switch (rom_error) {
|
case recomp::RomValidationError::Good:
|
||||||
case recomp::RomValidationError::Good:
|
mm_rom_valid = true;
|
||||||
mm_rom_valid = true;
|
model_handle.DirtyVariable("mm_rom_valid");
|
||||||
model_handle.DirtyVariable("mm_rom_valid");
|
break;
|
||||||
break;
|
case recomp::RomValidationError::FailedToOpen:
|
||||||
case recomp::RomValidationError::FailedToOpen:
|
recompui::message_box("Failed to open ROM file.");
|
||||||
recomp::message_box("Failed to open ROM file.");
|
break;
|
||||||
break;
|
case recomp::RomValidationError::NotARom:
|
||||||
case recomp::RomValidationError::NotARom:
|
recompui::message_box("This is not a valid ROM file.");
|
||||||
recomp::message_box("This is not a valid ROM file.");
|
break;
|
||||||
break;
|
case recomp::RomValidationError::IncorrectRom:
|
||||||
case recomp::RomValidationError::IncorrectRom:
|
recompui::message_box("This ROM is not the correct game.");
|
||||||
recomp::message_box("This ROM is not the correct game.");
|
break;
|
||||||
break;
|
case recomp::RomValidationError::NotYet:
|
||||||
case recomp::RomValidationError::NotYet:
|
recompui::message_box("This game isn't supported yet.");
|
||||||
recomp::message_box("This game isn't supported yet.");
|
break;
|
||||||
break;
|
case recomp::RomValidationError::IncorrectVersion:
|
||||||
case recomp::RomValidationError::IncorrectVersion:
|
recompui::message_box(
|
||||||
recomp::message_box("This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
"This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
||||||
break;
|
break;
|
||||||
case recomp::RomValidationError::OtherError:
|
case recomp::RomValidationError::OtherError:
|
||||||
recomp::message_box("An unknown error has occurred.");
|
recompui::message_box("An unknown error has occurred.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LauncherMenu : public recomp::MenuController {
|
class LauncherMenu : public recompui::MenuController {
|
||||||
public:
|
public:
|
||||||
LauncherMenu() {
|
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 {
|
~LauncherMenu() override {
|
||||||
|
|
||||||
|
@ -61,37 +63,37 @@ public:
|
||||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||||
return context->LoadDocument("assets/launcher.rml");
|
return context->LoadDocument("assets/launcher.rml");
|
||||||
}
|
}
|
||||||
void register_events(recomp::UiEventListenerInstancer& listener) override {
|
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||||
recomp::register_event(listener, "select_rom",
|
recompui::register_event(listener, "select_rom",
|
||||||
[](const std::string& param, Rml::Event& event) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
select_rom();
|
select_rom();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
recomp::register_event(listener, "rom_selected",
|
recompui::register_event(listener, "rom_selected",
|
||||||
[](const std::string& param, Rml::Event& event) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
mm_rom_valid = true;
|
mm_rom_valid = true;
|
||||||
model_handle.DirtyVariable("mm_rom_valid");
|
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) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
recomp::start_game(recomp::Game::MM);
|
recomp::start_game(supported_games[0].game_id);
|
||||||
recomp::set_current_menu(recomp::Menu::None);
|
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) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
recomp::set_current_menu(recomp::Menu::Config);
|
recompui::set_current_menu(recompui::Menu::Config);
|
||||||
recomp::set_config_submenu(recomp::ConfigSubmenu::Controls);
|
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) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
recomp::set_current_menu(recomp::Menu::Config);
|
recompui::set_current_menu(recompui::Menu::Config);
|
||||||
recomp::set_config_submenu(recomp::ConfigSubmenu::General);
|
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) {
|
[](const std::string& param, Rml::Event& event) {
|
||||||
ultramodern::quit();
|
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>();
|
return std::make_unique<LauncherMenu>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,22 @@
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <SDL_video.h>
|
||||||
|
#else
|
||||||
|
#include <SDL2/SDL_video.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#include "recomp_ui.h"
|
#include "recomp_ui.h"
|
||||||
#include "recomp_input.h"
|
#include "recomp_input.h"
|
||||||
#include "recomp_game.h"
|
#include "librecomp/game.hpp"
|
||||||
#include "recomp_config.h"
|
#include "zelda_config.h"
|
||||||
#include "ui_rml_hacks.hpp"
|
#include "ui_rml_hacks.hpp"
|
||||||
|
|
||||||
#include "concurrentqueue.h"
|
#include "concurrentqueue.h"
|
||||||
|
|
||||||
#include "rt64_layer.h"
|
#include "ultramodern/rt64_layer.hpp"
|
||||||
#include "rt64_render_hooks.h"
|
#include "rt64_render_hooks.h"
|
||||||
#include "rt64_render_interface_builders.h"
|
#include "rt64_render_interface_builders.h"
|
||||||
|
|
||||||
|
@ -718,7 +724,7 @@ Rml::Element* get_target(Rml::ElementDocument* document, Rml::Element* element)
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace recomp {
|
namespace recompui {
|
||||||
class UiEventListener : public Rml::EventListener {
|
class UiEventListener : public Rml::EventListener {
|
||||||
event_handler_t* handler_;
|
event_handler_t* handler_;
|
||||||
Rml::String param_;
|
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);
|
listener.register_event(name, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,8 +785,8 @@ Rml::Element* find_autofocus_element(Rml::Element* start) {
|
||||||
struct UIContext {
|
struct UIContext {
|
||||||
struct UIRenderContext render;
|
struct UIRenderContext render;
|
||||||
class {
|
class {
|
||||||
std::unordered_map<recomp::Menu, std::unique_ptr<recomp::MenuController>> menus;
|
std::unordered_map<recompui::Menu, std::unique_ptr<recompui::MenuController>> menus;
|
||||||
std::unordered_map<recomp::Menu, Rml::ElementDocument*> documents;
|
std::unordered_map<recompui::Menu, Rml::ElementDocument*> documents;
|
||||||
Rml::ElementDocument* current_document;
|
Rml::ElementDocument* current_document;
|
||||||
Rml::Element* prev_focused;
|
Rml::Element* prev_focused;
|
||||||
bool mouse_is_active_changed = false;
|
bool mouse_is_active_changed = false;
|
||||||
|
@ -794,13 +800,13 @@ struct UIContext {
|
||||||
std::unique_ptr<SystemInterface_SDL> system_interface;
|
std::unique_ptr<SystemInterface_SDL> system_interface;
|
||||||
std::unique_ptr<RmlRenderInterface_RT64> render_interface;
|
std::unique_ptr<RmlRenderInterface_RT64> render_interface;
|
||||||
Rml::Context* context;
|
Rml::Context* context;
|
||||||
recomp::UiEventListenerInstancer event_listener_instancer;
|
recompui::UiEventListenerInstancer event_listener_instancer;
|
||||||
|
|
||||||
void unload() {
|
void unload() {
|
||||||
render_interface.reset();
|
render_interface.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void swap_document(recomp::Menu menu) {
|
void swap_document(recompui::Menu menu) {
|
||||||
if (current_document != nullptr) {
|
if (current_document != nullptr) {
|
||||||
Rml::Element* window_el = current_document->GetElementById("window");
|
Rml::Element* window_el = current_document->GetElementById("window");
|
||||||
if (window_el != nullptr) {
|
if (window_el != nullptr) {
|
||||||
|
@ -831,7 +837,7 @@ struct UIContext {
|
||||||
mouse_is_active_initialized = false;
|
mouse_is_active_initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void swap_config_menu(recomp::ConfigSubmenu submenu) {
|
void swap_config_menu(recompui::ConfigSubmenu submenu) {
|
||||||
if (current_document != nullptr) {
|
if (current_document != nullptr) {
|
||||||
Rml::Element* config_tabset_base = current_document->GetElementById("config_tabset");
|
Rml::Element* config_tabset_base = current_document->GetElementById("config_tabset");
|
||||||
if (config_tabset_base != nullptr) {
|
if (config_tabset_base != nullptr) {
|
||||||
|
@ -911,7 +917,7 @@ struct UIContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mouse_is_active_initialized) {
|
if (mouse_is_active_initialized) {
|
||||||
recomp::set_cursor_visible(mouse_is_active);
|
recompui::set_cursor_visible(mouse_is_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_document == nullptr) {
|
if (current_document == nullptr) {
|
||||||
|
@ -939,7 +945,6 @@ struct UIContext {
|
||||||
if (cont_is_active || non_mouse_interacted) {
|
if (cont_is_active || non_mouse_interacted) {
|
||||||
if (non_mouse_interacted) {
|
if (non_mouse_interacted) {
|
||||||
auto focusedEl = current_document->GetFocusLeafNode();
|
auto focusedEl = current_document->GetFocusLeafNode();
|
||||||
Rml::Variant* ti = focusedEl == nullptr ? nullptr : focusedEl->GetAttribute("tab-index");
|
|
||||||
if (focusedEl == nullptr || RecompRml::CanFocusElement(focusedEl) != RecompRml::CanFocus::Yes) {
|
if (focusedEl == nullptr || RecompRml::CanFocusElement(focusedEl) != RecompRml::CanFocus::Yes) {
|
||||||
Rml::Element* element = find_autofocus_element(current_document);
|
Rml::Element* element = find_autofocus_element(current_document);
|
||||||
if (element != nullptr) {
|
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));
|
menus.emplace(menu, std::move(controller));
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_config_menu_loop(bool menu_changed) {
|
void update_config_menu_loop(bool menu_changed) {
|
||||||
static int prevTab = -1;
|
static int prevTab = -1;
|
||||||
if (menu_changed) 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");
|
Rml::ElementTabSet *tabset = (Rml::ElementTabSet *)current_document->GetElementById("config_tabset");
|
||||||
if (tabset == nullptr) return;
|
if (tabset == nullptr) return;
|
||||||
|
@ -1022,7 +1027,7 @@ struct UIContext {
|
||||||
void update_prompt_loop(void) {
|
void update_prompt_loop(void) {
|
||||||
static bool wasShowingPrompt = false;
|
static bool wasShowingPrompt = false;
|
||||||
|
|
||||||
recomp::PromptContext *ctx = recomp::get_prompt_context();
|
recompui::PromptContext *ctx = recompui::get_prompt_context();
|
||||||
if (!ctx->open && wasShowingPrompt) {
|
if (!ctx->open && wasShowingPrompt) {
|
||||||
Rml::Element* focused = current_document->GetFocusLeafNode();
|
Rml::Element* focused = current_document->GetFocusLeafNode();
|
||||||
if (focused) focused->Blur();
|
if (focused) focused->Blur();
|
||||||
|
@ -1088,8 +1093,8 @@ struct UIContext {
|
||||||
|
|
||||||
Rml::Element *confirmButton = current_document->GetElementById("prompt__confirm-button");
|
Rml::Element *confirmButton = current_document->GetElementById("prompt__confirm-button");
|
||||||
Rml::Element *cancelButton = current_document->GetElementById("prompt__cancel-button");
|
Rml::Element *cancelButton = current_document->GetElementById("prompt__cancel-button");
|
||||||
if (confirmButton != nullptr) confirmButton->SetClassNames("button button--" + recomp::button_variants.at(ctx->confirmVariant));
|
if (confirmButton != nullptr) confirmButton->SetClassNames("button button--" + recompui::button_variants.at(ctx->confirmVariant));
|
||||||
if (cancelButton != nullptr) cancelButton->SetClassNames( "button button--" + recomp::button_variants.at(ctx->cancelVariant));
|
if (cancelButton != nullptr) cancelButton->SetClassNames( "button button--" + recompui::button_variants.at(ctx->cancelVariant));
|
||||||
}
|
}
|
||||||
} rml;
|
} rml;
|
||||||
};
|
};
|
||||||
|
@ -1100,7 +1105,7 @@ std::mutex ui_context_mutex{};
|
||||||
// TODO make this not be global
|
// TODO make this not be global
|
||||||
extern SDL_Window* window;
|
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);
|
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1110,8 +1115,8 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||||
#endif
|
#endif
|
||||||
ui_context = std::make_unique<UIContext>();
|
ui_context = std::make_unique<UIContext>();
|
||||||
|
|
||||||
ui_context->rml.add_menu(recomp::Menu::Config, recomp::create_config_menu());
|
ui_context->rml.add_menu(recompui::Menu::Config, recompui::create_config_menu());
|
||||||
ui_context->rml.add_menu(recomp::Menu::Launcher, recomp::create_launcher_menu());
|
ui_context->rml.add_menu(recompui::Menu::Launcher, recompui::create_launcher_menu());
|
||||||
|
|
||||||
ui_context->render.interface = interface;
|
ui_context->render.interface = interface;
|
||||||
ui_context->render.device = device;
|
ui_context->render.device = device;
|
||||||
|
@ -1129,7 +1134,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||||
Rml::Initialise();
|
Rml::Initialise();
|
||||||
|
|
||||||
// Apply the hack to replace RmlUi's default color parser with one that conforms to HTML5 alpha parsing for SASS compatibility
|
// 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;
|
int width, height;
|
||||||
SDL_GetWindowSizeInPixels(window, &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{};
|
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);
|
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);
|
return ui_event_queue.try_dequeue(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::atomic<recomp::Menu> open_menu = recomp::Menu::Launcher;
|
std::atomic<recompui::Menu> open_menu = recompui::Menu::Launcher;
|
||||||
std::atomic<recomp::ConfigSubmenu> open_config_submenu = recomp::ConfigSubmenu::Count;
|
std::atomic<recompui::ConfigSubmenu> open_config_submenu = recompui::ConfigSubmenu::Count;
|
||||||
|
|
||||||
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
||||||
switch (button.button) {
|
switch (button.button) {
|
||||||
|
@ -1236,15 +1241,15 @@ void apply_background_input_mode() {
|
||||||
last_input_mode = cur_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;
|
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;
|
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_primary_input(true, false);
|
||||||
ui_context->rml.update_focus(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;
|
bool reload_sheets = is_reload_held && !was_reload_held;
|
||||||
was_reload_held = is_reload_held;
|
was_reload_held = is_reload_held;
|
||||||
|
|
||||||
static recomp::Menu prev_menu = recomp::Menu::None;
|
static recompui::Menu prev_menu = recompui::Menu::None;
|
||||||
recomp::Menu cur_menu = open_menu.load();
|
recompui::Menu cur_menu = open_menu.load();
|
||||||
|
|
||||||
if (reload_sheets) {
|
if (reload_sheets) {
|
||||||
ui_context->rml.load_documents();
|
ui_context->rml.load_documents();
|
||||||
prev_menu = recomp::Menu::None;
|
prev_menu = recompui::Menu::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool menu_changed = cur_menu != prev_menu;
|
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);
|
ui_context->rml.swap_document(cur_menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::ConfigSubmenu config_submenu = open_config_submenu.load();
|
recompui::ConfigSubmenu config_submenu = open_config_submenu.load();
|
||||||
if (config_submenu != recomp::ConfigSubmenu::Count) {
|
if (config_submenu != recompui::ConfigSubmenu::Count) {
|
||||||
ui_context->rml.swap_config_menu(config_submenu);
|
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;
|
prev_menu = cur_menu;
|
||||||
|
@ -1296,15 +1301,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||||
bool cont_interacted = false;
|
bool cont_interacted = false;
|
||||||
bool kb_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);
|
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();
|
ui_context->rml.update_prompt_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
while (recomp::try_deque_event(cur_event)) {
|
while (recompui::try_deque_event(cur_event)) {
|
||||||
bool menu_is_open = cur_menu != recomp::Menu::None;
|
bool menu_is_open = cur_menu != recompui::Menu::None;
|
||||||
|
|
||||||
if (!recomp::all_input_disabled()) {
|
if (!recomp::all_input_disabled()) {
|
||||||
// Implement some additional behavior for specific events on top of what RmlUi normally does with them.
|
// 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;
|
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 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1401,15 +1406,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open_config) {
|
if (open_config) {
|
||||||
cur_menu = recomp::Menu::Config;
|
cur_menu = recompui::Menu::Config;
|
||||||
open_menu.store(recomp::Menu::Config);
|
open_menu.store(recompui::Menu::Config);
|
||||||
ui_context->rml.swap_document(cur_menu);
|
ui_context->rml.swap_document(cur_menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // end dequeue event loop
|
} // end dequeue event loop
|
||||||
|
|
||||||
if (cont_interacted || kb_interacted || mouse_clicked) {
|
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);
|
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_primary_input(mouse_moved, non_mouse_interacted);
|
||||||
ui_context->rml.update_focus(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 width = swap_chain_framebuffer->getWidth();
|
||||||
int height = swap_chain_framebuffer->getHeight();
|
int height = swap_chain_framebuffer->getHeight();
|
||||||
|
|
||||||
|
@ -1457,25 +1462,25 @@ void set_rt64_hooks() {
|
||||||
RT64::SetRenderHooks(init_hook, draw_hook, deinit_hook);
|
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);
|
open_menu.store(menu);
|
||||||
if (menu == recomp::Menu::None) {
|
if (menu == recompui::Menu::None) {
|
||||||
ui_context->rml.system_interface->SetMouseCursor("arrow");
|
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);
|
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();
|
return open_menu.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::message_box(const char* msg) {
|
void recompui::message_box(const char* msg) {
|
||||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, recomp::program_name.data(), msg, nullptr);
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, zelda64::program_name.data(), msg, nullptr);
|
||||||
printf("[ERROR] %s\n", msg);
|
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