From 0fdfc5f7fe5dc251d9eab2469d7106d1164d34ce Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Thu, 22 Jun 2023 18:29:55 -0400 Subject: [PATCH] Build system & dependency refactoring Switched to a makefile + clang build system Now builds as a library by default Removed SDL2 dependency Changed RT64 integration to use zilmar spec in order to pass a window handle Proper unaligned loads and stores implementation --- MMRecomp.sln | 10 ---- MMRecomp.vcxproj | 15 ++--- Makefile | 108 ++++++++++++++++++++++++++++++++++++ include/recomp.h | 75 +++++++++++++++++++------ include/rt64_layer.h | 12 +++- portultra/audio.cpp | 83 +++++++++++++-------------- portultra/events.cpp | 80 +++----------------------- portultra/multilibultra.hpp | 23 +++++++- portultra/ultrainit.cpp | 5 +- src/cont.cpp | 34 +++++++----- src/recomp.cpp | 69 +++++++++++++---------- src/rt64_layer.cpp | 13 +++-- us.rev1.toml | 84 ++++++++++++++++++++++++++++ 13 files changed, 403 insertions(+), 208 deletions(-) create mode 100644 Makefile create mode 100644 us.rev1.toml diff --git a/MMRecomp.sln b/MMRecomp.sln index 90b65d5..938537c 100644 --- a/MMRecomp.sln +++ b/MMRecomp.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.33027.164 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MMRecomp", "MMRecomp.vcxproj", "{14B47028-6A86-4660-A86D-2E69F229E110}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RecompiledFuncs", "RecompiledFuncs.vcxproj", "{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -23,14 +21,6 @@ Global {14B47028-6A86-4660-A86D-2E69F229E110}.Release|x64.Build.0 = Release|x64 {14B47028-6A86-4660-A86D-2E69F229E110}.Release|x86.ActiveCfg = Release|Win32 {14B47028-6A86-4660-A86D-2E69F229E110}.Release|x86.Build.0 = Release|Win32 - {7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Debug|x64.ActiveCfg = Debug|x64 - {7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Debug|x64.Build.0 = Debug|x64 - {7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Debug|x86.ActiveCfg = Debug|Win32 - {7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Debug|x86.Build.0 = Debug|Win32 - {7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Release|x64.ActiveCfg = Release|x64 - {7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Release|x64.Build.0 = Release|x64 - {7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Release|x86.ActiveCfg = Release|Win32 - {7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MMRecomp.vcxproj b/MMRecomp.vcxproj index d3b303c..959b67b 100644 --- a/MMRecomp.vcxproj +++ b/MMRecomp.vcxproj @@ -40,13 +40,13 @@ Unicode - Application + Makefile true v142 Unicode - Application + Makefile false v142 true @@ -78,9 +78,15 @@ true + make TARGET=$(Configuration) LIB_DIR="$(VC_LibraryPath_VC_x64_Desktop)" UCRT_DIR="$(UniversalCRT_LibraryPath_x64)" SDK_DIR="$(WindowsSDK_LibraryPath)\x64" -j$(NUMBER_OF_PROCESSORS) + make TARGET=$(Configuration) clean + $(SolutionDir)build\$(Configuration)\ false + make TARGET=$(Configuration) LIB_DIR="$(VC_LibraryPath_VC_x64_Desktop)" UCRT_DIR="$(UniversalCRT_LibraryPath_x64)" SDK_DIR="$(WindowsSDK_LibraryPath)\x64" -j$(NUMBER_OF_PROCESSORS) + make TARGET=$(Configuration) clean + $(SolutionDir)build\$(Configuration)\ @@ -225,11 +231,6 @@ XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S - - - {7bf5e3f9-c49f-4c84-ab64-7681f028cac2} - - diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..19e4c14 --- /dev/null +++ b/Makefile @@ -0,0 +1,108 @@ +CONFIG ?= Debug +LIB ?= 0 + +ifeq ($(CONFIG),Debug) +BUILD_DIR := build/debug +FUNC_OPTFLAGS := -Og -g -fno-strict-aliasing +OPTFLAGS := -Og -g -fno-strict-aliasing +# Static C runtime linking +LIBS := -Wl,/nodefaultlib:libcmt -Wl,/nodefaultlib:ucrt -Wl,/nodefaultlib:libucrt -llibcmtd -llibvcruntimed -llibucrtd +# Dynamic +# LIBS := -Wl,/nodefaultlib:libcmt -Wl,/nodefaultlib:ucrt -Wl,/nodefaultlib:libucrt -lmsvcrtd -lvcruntimed -lucrtd +else ifeq ($(CONFIG),Release) +BUILD_DIR := build/release +FUNC_OPTFLAGS := -O2 -g -fno-strict-aliasing +OPTFLAGS := -O2 -g -fno-strict-aliasing +# Static C runtime linking +LIBS := -Wl,/nodefaultlib:libcmt -Wl,/nodefaultlib:ucrt -Wl,/nodefaultlib:libucrt -llibcmt -llibvcruntime -llibucrt +# Dynamic +# LIBS := -Wl,/nodefaultlib:libcmt -Wl,/nodefaultlib:ucrt -Wl,/nodefaultlib:libucrt -lmsvcrt -lvcruntime -lucrt +else +$(error "Invalid build configuration: $(CONFIG)") +endif + +SRC_DIRS := portultra src rsp + +FUNCS_DIR := RecompiledFuncs +FUNCS_LIB := $(BUILD_DIR)/RecompiledFuncs.lib +FUNCS_C_SRCS := $(wildcard $(FUNCS_DIR)/*.c) +FUNCS_CXX_SRCS := $(wildcard $(FUNCS_DIR)/*.cpp) + +FUNC_BUILD_DIR := $(BUILD_DIR)/RecompiledFuncs +FUNCS_C_OBJS := $(addprefix $(BUILD_DIR)/,$(FUNCS_C_SRCS:.c=.o)) +FUNCS_CXX_OBJS := $(addprefix $(BUILD_DIR)/,$(FUNCS_CXX_SRCS:.cpp=.o)) +ALL_FUNC_OBJS := $(FUNCS_C_OBJS) $(FUNCS_CXX_OBJS) + +BUILD_SRC_DIRS := $(addprefix $(BUILD_DIR)/,$(SRC_DIRS)) +C_SRCS := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c)) +CXX_SRCS := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cpp)) + +C_OBJS := $(addprefix $(BUILD_DIR)/,$(C_SRCS:.c=.o)) +CXX_OBJS := $(addprefix $(BUILD_DIR)/,$(CXX_SRCS:.cpp=.o)) +ALL_OBJS := $(C_OBJS) $(CXX_OBJS) + + +CC := clang +CXX := clang++ +LIB := clang++ +LD := clang++ + +FUNC_CFLAGS := $(FUNC_OPTFLAGS) -c -Wno-unused-but-set-variable +FUNC_CXXFLAGS := $(FUNC_OPTFLAGS) -std=c++20 -c +FUNC_CPPFLAGS := -Iinclude +LIBFLAGS := $(OPTFLAGS) -fuse-ld=llvm-lib +LIB_DIR ?= C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64 +UCRT_DIR ?= C:\Program Files (x86)\Windows Kits\10\lib\10.0.22000.0\ucrt\x64; +SDK_DIR ?= C:\Program Files (x86)\Windows Kits\10\lib\10.0.22000.0\um\x64 + +WARNFLAGS := -Wall -Wextra -Wpedantic -Wno-gnu-anonymous-struct +CFLAGS := -ffunction-sections -fdata-sections $(OPTFLAGS) $(WARNFLAGS) -c +CXXFLAGS := -ffunction-sections -fdata-sections $(OPTFLAGS) $(WARNFLAGS) -std=c++20 -c +CPPFLAGS := -Iinclude -Ithirdparty +LDFLAGS := -v -Wl,/OPT:REF $(OPTFLAGS) $(LIBS) -L"$(LIB_DIR:;=)" -L"$(UCRT_DIR:;=)" -L"$(SDK_DIR:;=)" lib/RT64/$(CONFIG)/RT64.lib + +ifeq ($(LIB),1) +TARGET := $(BUILD_DIR)/MMRecomp.dll +LDFLAGS += -shared +else +TARGET := $(BUILD_DIR)/MMRecomp.exe +endif + +default: $(TARGET) + +clean: + rmdir /S /Q $(subst /,\\,$(BUILD_DIR)) + +cleanfuncs: + + +$(FUNCS_CXX_OBJS) : $(BUILD_DIR)/%.o : %.cpp | $(FUNC_BUILD_DIR) + @$(CXX) $(FUNC_CXXFLAGS) $(FUNC_CPPFLAGS) $^ -o $@ + +$(FUNCS_C_OBJS) : $(BUILD_DIR)/%.o : %.c | $(FUNC_BUILD_DIR) + @$(CC) $(FUNC_CFLAGS) $(FUNC_CPPFLAGS) $^ -o $@ + +$(FUNCS_LIB): $(ALL_FUNC_OBJS) | $(BUILD_DIR) + $(LIB) $(LIBFLAGS) $(FUNC_BUILD_DIR)/*.o -o $@ + + + +$(CXX_OBJS) : $(BUILD_DIR)/%.o : %.cpp | $(BUILD_SRC_DIRS) + $(CXX) -MMD -MF $(@:.o=.d) $(CXXFLAGS) $(CPPFLAGS) $< -o $@ + +$(C_OBJS) : $(BUILD_DIR)/%.o : %.c | $(BUILD_SRC_DIRS) + $(CC) -MMD -MF $(@:.o=.d) $(CFLAGS) $(CPPFLAGS) $< -o $@ + +$(TARGET): $(FUNCS_LIB) $(ALL_OBJS) | $(BUILD_SRC_DIRS) + $(LD) $(LDFLAGS) -o $@ $^ + +$(BUILD_SRC_DIRS) $(FUNC_BUILD_DIR) $(BUILD_DIR): + mkdir $(subst /,\\,$@) + +-include $(ALL_OBJS:.o=.d) + +MAKEFLAGS += --no-builtin-rules +.SUFFIXES: +.PHONY: default clean cleanfuncs + +print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true diff --git a/include/recomp.h b/include/recomp.h index 5768cb2..e627a8c 100644 --- a/include/recomp.h +++ b/include/recomp.h @@ -66,27 +66,68 @@ static inline uint64_t load_doubleword(uint8_t* rdram, gpr reg, gpr offset) { #define LD(offset, reg) \ load_doubleword(rdram, offset, reg) -// TODO proper lwl/lwr/swl/swr -static inline void do_swl(uint8_t* rdram, gpr offset, gpr reg, gpr val) { - uint8_t byte0 = (uint8_t)(val >> 24); - uint8_t byte1 = (uint8_t)(val >> 16); - uint8_t byte2 = (uint8_t)(val >> 8); - uint8_t byte3 = (uint8_t)(val >> 0); +static inline gpr do_lwl(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) { + // Calculate the overall address + gpr address = (offset + reg); - MEM_B(offset + 0, reg) = byte0; - MEM_B(offset + 1, reg) = byte1; - MEM_B(offset + 2, reg) = byte2; - MEM_B(offset + 3, reg) = byte3; -} + // Load the aligned word + gpr word_address = address & ~0x3; + uint32_t loaded_value = MEM_W(0, word_address); -static inline gpr do_lwl(uint8_t* rdram, gpr offset, gpr reg) { - uint8_t byte0 = MEM_B(offset + 0, reg); - uint8_t byte1 = MEM_B(offset + 1, reg); - uint8_t byte2 = MEM_B(offset + 2, reg); - uint8_t byte3 = MEM_B(offset + 3, reg); + // 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)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | (byte3 << 0)); + 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) \ diff --git a/include/rt64_layer.h b/include/rt64_layer.h index b60d667..b38d4f1 100644 --- a/include/rt64_layer.h +++ b/include/rt64_layer.h @@ -2,6 +2,11 @@ #define __RT64_LAYER_H__ typedef struct { + void* hWnd; + void* hStatusBar; + + int Reserved; + unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */ unsigned char* RDRAM; unsigned char* DMEM; @@ -35,9 +40,9 @@ typedef struct { void (*CheckInterrupts)(void); - unsigned int version; - unsigned int* SP_STATUS_REG; - const unsigned int* RDRAM_SIZE; + // unsigned int version; + // unsigned int* SP_STATUS_REG; + // const unsigned int* RDRAM_SIZE; } GFX_INFO; #define DLLEXPORT extern "C" __declspec(dllexport) @@ -56,6 +61,7 @@ DLLIMPORT void ProcessRDPList(void); DLLIMPORT void ProcessDList(void); DLLIMPORT void UpdateScreen(void); DLLIMPORT void PumpEvents(void); +DLLIMPORT void ChangeWindow(void); #endif diff --git a/portultra/audio.cpp b/portultra/audio.cpp index d553fc6..3dca8ad 100644 --- a/portultra/audio.cpp +++ b/portultra/audio.cpp @@ -1,55 +1,39 @@ #include "ultra64.h" #include "multilibultra.hpp" -#include "SDL.h" -#include "SDL_audio.h" #include -static SDL_AudioDeviceID audio_device = 0; static uint32_t sample_rate = 48000; +static Multilibultra::audio_callbacks_t audio_callbacks; + +void Multilibultra::set_audio_callbacks(const audio_callbacks_t* callbacks) { + if (callbacks != nullptr) { + audio_callbacks = *callbacks; + } +} + void Multilibultra::init_audio() { - // Initialize SDL audio. - SDL_InitSubSystem(SDL_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 Multilibultra::set_audio_frequency(uint32_t freq) { - if (audio_device != 0) { - SDL_CloseAudioDevice(audio_device); + if (audio_callbacks.set_frequency) { + audio_callbacks.set_frequency(freq); } - SDL_AudioSpec spec_desired{ - .freq = (int)freq, - .format = AUDIO_S16, - .channels = 2, - .silence = 0, // calculated - .samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering - .padding = 0, // unused - .size = 0, // calculated - .callback = nullptr,//feed_audio, // Use a callback as QueueAudio causes popping - .userdata = nullptr - }; - - audio_device = SDL_OpenAudioDevice(nullptr, false, &spec_desired, nullptr, 0); - if (audio_device == 0) { - printf("SDL Error: %s\n", SDL_GetError()); - fflush(stdout); - assert(false); - } - SDL_PauseAudioDevice(audio_device, 0); sample_rate = freq; } -void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t byte_count) { +void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(int16_t) audio_data_, uint32_t byte_count) { // Buffer for holding the output of swapping the audio channels. This is reused across // calls to reduce runtime allocations. - static std::vector swap_buffer; + static std::vector swap_buffer; // 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(s16); + uint32_t sample_count = byte_count / sizeof(int16_t); // Make sure the swap buffer is large enough to hold all the incoming audio data. if (sample_count > swap_buffer.size()) { @@ -57,34 +41,45 @@ void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t } // Swap the audio channels into the swap buffer to correct for the address xor caused by endianness handling. - s16* audio_data = TO_PTR(s16, audio_data_); + int16_t* audio_data = TO_PTR(int16_t, audio_data_); for (size_t i = 0; i < sample_count; i += 2) { swap_buffer[i + 0] = audio_data[i + 1]; swap_buffer[i + 1] = audio_data[i + 0]; } // Queue the swapped audio data. - SDL_QueueAudio(audio_device, swap_buffer.data(), byte_count); + if (audio_callbacks.queue_samples) { + audio_callbacks.queue_samples(swap_buffer.data(), sample_count); + } } -uint32_t buffer_offset_frames = 1; +// 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 Multilibultra::get_remaining_audio_bytes() { // Get the number of remaining buffered audio bytes. - uint32_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device); - - // 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 > (buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) { - buffered_byte_count -= (buffer_offset_frames * sizeof(int16_t) * samples_per_vi); - } else { - buffered_byte_count = 0; + uint32_t buffered_byte_count; + if (audio_callbacks.get_samples_remaining()) { + buffered_byte_count = audio_callbacks.get_samples_remaining() * sizeof(int16_t); } - return buffered_byte_count; + 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(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) { + buffered_byte_count -= static_cast(buffer_offset_frames * sizeof(int16_t) * samples_per_vi); + } + else { + buffered_byte_count = 0; + } + return buffered_byte_count; } diff --git a/portultra/events.cpp b/portultra/events.cpp index 988698b..f825f05 100644 --- a/portultra/events.cpp +++ b/portultra/events.cpp @@ -9,7 +9,6 @@ #include #include -#include "SDL.h" #include "blockingconcurrentqueue.h" #include "ultra64.h" @@ -154,57 +153,10 @@ void dp_complete() { osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK); } -void RT64Init(uint8_t* rom, uint8_t* rdram); +void RT64Init(uint8_t* rom, uint8_t* rdram, void* window_handle); void RT64SendDL(uint8_t* rdram, const OSTask* task); void RT64UpdateScreen(uint32_t vi_origin); - -std::unordered_map button_map{ - { SDL_Scancode::SDL_SCANCODE_LEFT, 0x0002 }, // c left - { SDL_Scancode::SDL_SCANCODE_RIGHT, 0x0001 }, // c right - { SDL_Scancode::SDL_SCANCODE_UP, 0x0008 }, // c up - { SDL_Scancode::SDL_SCANCODE_DOWN, 0x0004 }, // c down - { SDL_Scancode::SDL_SCANCODE_RETURN, 0x1000 }, // start - { SDL_Scancode::SDL_SCANCODE_SPACE, 0x8000 }, // a - { SDL_Scancode::SDL_SCANCODE_LSHIFT, 0x4000 }, // b - { SDL_Scancode::SDL_SCANCODE_Q, 0x2000 }, // z - { SDL_Scancode::SDL_SCANCODE_E, 0x0020 }, // l - { SDL_Scancode::SDL_SCANCODE_R, 0x0010 }, // r - { SDL_Scancode::SDL_SCANCODE_J, 0x0200 }, // dpad left - { SDL_Scancode::SDL_SCANCODE_L, 0x0100 }, // dpad right - { SDL_Scancode::SDL_SCANCODE_I, 0x0800 }, // dpad up - { SDL_Scancode::SDL_SCANCODE_K, 0x0400 }, // dpad down -}; - -extern int button; -extern int stick_x; -extern int stick_y; - -int sdl_event_filter(void* userdata, SDL_Event* event) { - switch (event->type) { - case SDL_EventType::SDL_KEYUP: - case SDL_EventType::SDL_KEYDOWN: - { - const Uint8* key_states = SDL_GetKeyboardState(nullptr); - int new_button = 0; - - for (const auto& mapping : button_map) { - if (key_states[mapping.first]) { - new_button |= mapping.second; - } - } - - button = new_button; - - stick_x = 85 * (key_states[SDL_Scancode::SDL_SCANCODE_D] - key_states[SDL_Scancode::SDL_SCANCODE_A]); - stick_y = 85 * (key_states[SDL_Scancode::SDL_SCANCODE_W] - key_states[SDL_Scancode::SDL_SCANCODE_S]); - } - break; - case SDL_EventType::SDL_QUIT: - std::quick_exit(ERROR_SUCCESS); - break; - } - return 1; -} +void RT64ChangeWindow(); uint8_t dmem[0x1000]; uint16_t rspReciprocals[512]; @@ -276,19 +228,10 @@ void task_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_rea } } -void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready) { +void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready, void* window_handle) { using namespace std::chrono_literals; - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { - fprintf(stderr, "Failed to initialize SDL2: %s\n", SDL_GetError()); - std::quick_exit(EXIT_FAILURE); - } - RT64Init(rom, rdram); - SDL_Window* window = SDL_GetWindowFromID(1); - // TODO set this window title in RT64, create the window here and send it to RT64, or something else entirely - // as the current window name visibly changes as RT64 is initialized - SDL_SetWindowTitle(window, "Recomp"); - //SDL_SetEventFilter(sdl_event_filter, nullptr); - + RT64Init(rom, rdram, window_handle); + rsp_constants_init(); // Notify the caller thread that this thread is ready. @@ -318,15 +261,6 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read RT64UpdateScreen(swap_action->origin); } } - - // Handle events - constexpr int max_events_per_frame = 16; - SDL_Event cur_event; - int i = 0; - while (i++ < max_events_per_frame && SDL_PollEvent(&cur_event)) { - sdl_event_filter(nullptr, &cur_event); - } - //SDL_PumpEvents(); } } @@ -483,11 +417,11 @@ void Multilibultra::send_si_message() { osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); } -void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom) { +void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom, void* window_handle) { std::atomic_flag gfx_thread_ready; std::atomic_flag task_thread_ready; events_context.rdram = rdram; - events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, rom, &gfx_thread_ready }; + events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, rom, &gfx_thread_ready, window_handle }; events_context.sp.task_thread = std::thread{ task_thread_func, rdram, rom, &task_thread_ready }; // Wait for the two sp threads to be ready before continuing to prevent the game from diff --git a/portultra/multilibultra.hpp b/portultra/multilibultra.hpp index b694a6c..2f11f3d 100644 --- a/portultra/multilibultra.hpp +++ b/portultra/multilibultra.hpp @@ -21,10 +21,10 @@ constexpr int32_t cart_handle = 0x80800000; constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash -void preinit(uint8_t* rdram, uint8_t* rom); +void preinit(uint8_t* rdram, uint8_t* rom, void* window_handle); void save_init(); void init_scheduler(); -void init_events(uint8_t* rdram, uint8_t* rom); +void init_events(uint8_t* rdram, uint8_t* rom, void* window_handle); void init_timers(RDRAM_ARG1); void set_self_paused(RDRAM_ARG1); void wait_for_resumed(RDRAM_ARG1); @@ -46,11 +46,30 @@ void send_si_message(); uint32_t get_speed_multiplier(); std::chrono::system_clock::time_point get_start(); std::chrono::system_clock::duration time_since_start(); + +// 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_samples_remaining; + set_frequency_t* set_frequency; +}; +void set_audio_callbacks(const audio_callbacks_t* callbacks); + +// Input +struct input_callbacks_t { + using get_input_t = void(uint16_t*, float*, float*); + get_input_t* get_input; +}; +void set_input_callbacks(const input_callbacks_t* callback); + class preemption_guard { public: preemption_guard(); diff --git a/portultra/ultrainit.cpp b/portultra/ultrainit.cpp index 7d23675..1abfbac 100644 --- a/portultra/ultrainit.cpp +++ b/portultra/ultrainit.cpp @@ -1,9 +1,9 @@ #include "ultra64.h" #include "multilibultra.hpp" -void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom) { +void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom, void* window_handle) { Multilibultra::set_main_thread(); - Multilibultra::init_events(rdram, rom); + Multilibultra::init_events(rdram, rom, window_handle); Multilibultra::init_timers(rdram); Multilibultra::init_audio(); Multilibultra::save_init(); @@ -11,5 +11,4 @@ void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom) { extern "C" void osInitialize() { Multilibultra::init_scheduler(); - //Multilibultra::native_init(); } diff --git a/src/cont.cpp b/src/cont.cpp index 52283a2..86ca632 100644 --- a/src/cont.cpp +++ b/src/cont.cpp @@ -1,6 +1,14 @@ #include "../portultra/multilibultra.hpp" #include "recomp.h" +static Multilibultra::input_callbacks_t input_callbacks; + +void Multilibultra::set_input_callbacks(const input_callbacks_t* callbacks) { + if (callbacks != nullptr) { + input_callbacks = *callbacks; + } +} + static int max_controllers = 0; extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) { @@ -37,28 +45,24 @@ struct OSContPad { u8 errno_; }; -int button = 0; -int stick_x = 0; -int stick_y = 0; - -void press_button(int button) { - -} - -void release_button(int button) { - -} - extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) { int32_t pad = (int32_t)ctx->r4; + 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) = button; + MEM_H(0, pad) = buttons; // stick_x - MEM_B(2, pad) = stick_x; + MEM_B(2, pad) = (int8_t)(127 * x); // stick_y - MEM_B(3, pad) = stick_y; + MEM_B(3, pad) = (int8_t)(127 * y); // errno MEM_B(4, pad) = 0; } diff --git a/src/recomp.cpp b/src/recomp.cpp index 957311c..9fc34ae 100644 --- a/src/recomp.cpp +++ b/src/recomp.cpp @@ -69,30 +69,10 @@ extern "C" void unload_overlays(int32_t ram_addr, uint32_t size); #include #endif -int main(int argc, char **argv) { - //if (argc != 2) { - // printf("Usage: %s [baserom]\n", argv[0]); - // exit(EXIT_SUCCESS); - //} - -#ifdef _WIN32 - // Set up console output to accept UTF-8 on windows - SetConsoleOutputCP(CP_UTF8); - - // Change to a font that supports Japanese characters - CONSOLE_FONT_INFOEX cfi; - cfi.cbSize = sizeof cfi; - cfi.nFont = 0; - cfi.dwFontSize.X = 0; - cfi.dwFontSize.Y = 16; - cfi.FontFamily = FF_DONTCARE; - cfi.FontWeight = FW_NORMAL; - wcscpy_s(cfi.FaceName, L"NSimSun"); - SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi); -#else - std::setlocale(LC_ALL, "en_US.UTF-8"); -#endif +std::unique_ptr rdram_buffer; +recomp_context context{}; +__declspec(dllexport) extern "C" void init() { { std::basic_ifstream rom_file{ get_rom_name(), std::ios::binary }; @@ -128,9 +108,8 @@ int main(int argc, char **argv) { load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); // Allocate rdram_buffer (16MB to give room for any extra addressable data used by recomp) - std::unique_ptr rdram_buffer = std::make_unique(16 * 1024 * 1024); + rdram_buffer = std::make_unique(16 * 1024 * 1024); std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024); - recomp_context context{}; // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000); @@ -152,14 +131,46 @@ int main(int argc, char **argv) { MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base MEM_W(osResetType, 0) = 0; // cold reset MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB +} - debug_printf("[Recomp] Starting\n"); +__declspec(dllexport) extern "C" void start(void* window_handle, const Multilibultra::audio_callbacks_t* audio_callbacks, const Multilibultra::input_callbacks_t* input_callbacks) { + Multilibultra::set_audio_callbacks(audio_callbacks); + Multilibultra::set_input_callbacks(input_callbacks); + std::thread game_thread{[](void* window_handle) { + debug_printf("[Recomp] Starting\n"); - Multilibultra::preinit(rdram_buffer.get(), rom.get()); + Multilibultra::preinit(rdram_buffer.get(), rom.get(), window_handle); - recomp_entrypoint(rdram_buffer.get(), &context); + recomp_entrypoint(rdram_buffer.get(), &context); + + debug_printf("[Recomp] Quitting\n"); + }, window_handle}; - debug_printf("[Recomp] Quitting\n"); + game_thread.detach(); +} + +int main(int argc, char **argv) { + +#ifdef _WIN32 + // Set up console output to accept UTF-8 on windows + SetConsoleOutputCP(CP_UTF8); + + // Change to a font that supports Japanese characters + CONSOLE_FONT_INFOEX cfi; + cfi.cbSize = sizeof cfi; + cfi.nFont = 0; + cfi.dwFontSize.X = 0; + cfi.dwFontSize.Y = 16; + cfi.FontFamily = FF_DONTCARE; + cfi.FontWeight = FW_NORMAL; + wcscpy_s(cfi.FaceName, L"NSimSun"); + SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi); +#else + std::setlocale(LC_ALL, "en_US.UTF-8"); +#endif + + init(); + start(nullptr, nullptr, nullptr); return EXIT_SUCCESS; } diff --git a/src/rt64_layer.cpp b/src/rt64_layer.cpp index 7acbcfe..192183b 100644 --- a/src/rt64_layer.cpp +++ b/src/rt64_layer.cpp @@ -3,7 +3,6 @@ #include "../portultra/multilibultra.hpp" #include "rt64_layer.h" -#include "SDL.h" static uint8_t DMEM[0x1000]; static uint8_t IMEM[0x1000]; @@ -44,7 +43,7 @@ void dummy_check_interrupts() { } -void RT64Init(uint8_t* rom, uint8_t* rdram) { +void RT64Init(uint8_t* rom, uint8_t* rdram, void* window_handle) { // Dynamic loading //auto RT64 = LoadLibrary("RT64.dll"); //if (RT64 == 0) { @@ -57,6 +56,9 @@ void RT64Init(uint8_t* rom, uint8_t* rdram) { //GET_FUNC(RT64, UpdateScreen); GFX_INFO gfx_info{}; + gfx_info.hWnd = window_handle; + gfx_info.hStatusBar = nullptr; + gfx_info.HEADER = rom; gfx_info.RDRAM = rdram; gfx_info.DMEM = DMEM; @@ -89,9 +91,6 @@ void RT64Init(uint8_t* rom, uint8_t* rdram) { gfx_info.VI_Y_SCALE_REG = &VI_Y_SCALE_REG; gfx_info.CheckInterrupts = dummy_check_interrupts; - gfx_info.version = 2; - gfx_info.SP_STATUS_REG = &SP_STATUS_REG; - gfx_info.RDRAM_SIZE = &RDRAM_SIZE; InitiateGFX(gfx_info); } @@ -112,3 +111,7 @@ void RT64UpdateScreen(uint32_t vi_origin) { UpdateScreen(); } + +void RT64ChangeWindow() { + ChangeWindow(); +} diff --git a/us.rev1.toml b/us.rev1.toml new file mode 100644 index 0000000..2fe8087 --- /dev/null +++ b/us.rev1.toml @@ -0,0 +1,84 @@ +# Config file for Majora's Mask NTSC 1.0 Recompilation. + +[input] +entrypoint = 0x80080000 +# Paths are relative to the location of this config file. +elf_path = "mm.us.rev1.elf" +output_func_path = "RecompiledFuncs" +relocatable_sections_path = "overlays.us.rev1.txt" + +[patches] +stubs = [ + # Stub out unused functions that directly manipulate RCP status. + "func_80084940", + "func_80084968", + # Stub out an unnecessary function that accesses kseg1 addresses. + "func_800818F4" +] + +# Hooks + +# Function definition for the overlay loading function. +[[patches.func]] +name = "load_overlays" +args = ["u32", "u32", "u32"] + +# Function hooks for overlay loading. +[[patches.hook]] +func = "Idle_InitCodeAndMemory" +calls = "load_overlays" +args = ["a2", "a1", "a3"] +after_vram = 0x800802A4 + +[[patches.hook]] +func = "Load2_LoadOverlay" +calls = "load_overlays" +# args = [ +# "a0", # $a0 contains rom start +# {operation = "load", type = "u32", base = "sp", offset = 0x10}, # sp + 10 contains the ram address +# {operation = "subtract", arguments = ["a1", "a0"]} # Calculate size from rom end - rom start +# ] +args = ["a1", "a0", "a2"] +# This vram address is an instruction in a delay slot. In that case, the recompiler will emit the +# hook call after this instruction is run and before the function is called. +after_vram = 0x80085048 + +# Single-instruction patches + +# Write audio dlists to kseg0 (cached, 0x80...) addresses instead of kseg1 (uncached, 0xA0...) ones. +# This saves the recompiler from needing to handle checking for kseg1 addresses on every memory operation. +[[patches.instruction]] +func = "AudioHeap_WritebackDCache" +vram = 0x8018B510 +value = 0x3C010000 # lui $at, 0x2000 -> lui $at, 0x0000 + +# These two may not be needed with RCP timeout detection disabled +# # Pretend the RSP has already halted so that the game doesn't attempt to directly manipulate RSP registers. +# [[patches.instruction]] +# func = "Sched_HandleAudioCancel" +# vram = 0x801763D8 +# value = 0x240F0001 # lw $t7, 0x0($s1) -> li $t7, 1 + +# # Ditto. +# [[patches.instruction]] +# func = "Sched_HandleGfxCancel" +# vram = 0x80176534 +# value = 0x240F0001 # lw $t7, 0x0($s1) -> li $t7, 1 + +# Disable RCP timeout detection (sometimes throws false positives, not needed) +[[patches.instruction]] +func = "AudioMgr_HandleRetrace" +vram = 0x80172DBC +value = 0x00000000 # jal osSetTimer -> nop + +# Ditto. +[[patches.instruction]] +func = "Graph_TaskSet00" +vram = 0x80174218 +value = 0x00000000 # jal osSetTimer -> nop + +# Prevent the minimap from drawing at a point where it can cause a crash when pausing. +[[patches.instruction]] +func = "func_80106644" +vram = 0x80106684 +value = 0x29E10003 # slti $at, $t7, 0x4 -> slti $at, $t7, 0x3