Added function patching, began reorganizing UI code, added native file dialog library

This commit is contained in:
Mr-Wiseguy 2023-11-11 17:42:07 -05:00
parent 91611c9c33
commit 398988a961
21 changed files with 622 additions and 802 deletions

2
.gitignore vendored
View File

@ -7,6 +7,7 @@
# Output C files
RecompiledFuncs/
RecompiledPatches/
# Linux build output
build/
@ -43,6 +44,7 @@ bld/
# Visual Studio 2015/2017 cache/options directory
.vs/
vcpkg_installed/
# Runtime files
imgui.ini

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "thirdparty/RmlUi"]
path = thirdparty/RmlUi
url = https://github.com/mikke89/RmlUi
[submodule "thirdparty/nativefiledialog-extended"]
path = thirdparty/nativefiledialog-extended
url = https://github.com/btzy/nativefiledialog-extended

View File

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(MMRecomp)
set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
@ -16,14 +17,13 @@ find_package(Freetype REQUIRED)
add_subdirectory(${CMAKE_SOURCE_DIR}/../mupen_rt64/mupen64plus-video-rt64 ${CMAKE_BINARY_DIR}/rt64)
add_subdirectory(${CMAKE_SOURCE_DIR}/thirdparty/RmlUi)
add_subdirectory(${CMAKE_SOURCE_DIR}/thirdparty/nativefiledialog-extended)
target_include_directories(rt64 PRIVATE ${CMAKE_BINARY_DIR}/rt64/src)
get_target_property(RT64_BASENAME rt64 OUTPUT_NAME)
set(RT64_DLL ${RT64_BASENAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
file(GLOB FUNC_C_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.c)
file(GLOB FUNC_CXX_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.cpp)
# RecompiledFuncs - Library containing the primary recompiler output
add_library(RecompiledFuncs STATIC)
target_compile_options(RecompiledFuncs PRIVATE
@ -35,8 +35,44 @@ target_include_directories(RecompiledFuncs PRIVATE
${CMAKE_SOURCE_DIR}/include
)
file(GLOB FUNC_C_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.c)
file(GLOB FUNC_CXX_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.cpp)
target_sources(RecompiledFuncs PRIVATE ${FUNC_C_SOURCES} ${FUNC_CXX_SOURCES})
# PatchesLib - Library containing the recompiled output for any custom function patches
add_library(PatchesLib STATIC)
target_compile_options(PatchesLib PRIVATE
# -Wno-unused-but-set-variable
-fno-strict-aliasing
)
target_include_directories(PatchesLib PRIVATE
${CMAKE_SOURCE_DIR}/include
)
target_sources(PatchesLib PRIVATE
${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c
)
# Build patches elf
add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/patches/patches.elf
COMMAND make
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/patches
BYPRODUCTS ${CMAKE_SOURCE_DIR}/patches/patches.bin}
)
# Recompile patches elf into patches.c
add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c
COMMAND RecompPort patches.toml
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.elf
)
# Main executable
add_executable(MMRecomp)
set (SOURCES
${CMAKE_SOURCE_DIR}/portultra/audio.cpp
${CMAKE_SOURCE_DIR}/portultra/events.cpp
@ -67,7 +103,9 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/sp.cpp
${CMAKE_SOURCE_DIR}/src/vi.cpp
${CMAKE_SOURCE_DIR}/src/main/main.cpp
${CMAKE_SOURCE_DIR}/src/ui.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_events.cpp
${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp
@ -75,8 +113,6 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/thirdparty/RmlUi/Backends/RmlUi_Platform_SDL.cpp
)
add_executable(MMRecomp)
target_include_directories(MMRecomp PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/thirdparty
@ -100,12 +136,14 @@ target_link_directories(MMRecomp PRIVATE
)
target_link_libraries(MMRecomp PRIVATE
PatchesLib
RecompiledFuncs
SDL2
rt64
Freetype::Freetype
RmlCore
RmlDebugger
nfd
)
# TODO fix the RT64 CMake script so that this doesn't need to be duplicated here

View File

@ -1,29 +0,0 @@
<rml>
<head>
<title>Demo</title>
<link type="text/template" href="window.rml" />
<style>
body
{
width: 300dp;
height: 225dp;
margin: auto;
}
div#title_bar div#icon
{
display: none;
}
div#content
{
text-align: left;
}
</style>
</head>
<body template="window">
This is a sample.<br/>
Wiseguy was here
</body>
</rml>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,134 @@
@spritesheet theme
{
src: invader.tga;
/* For high dpi screens, designates the scaling it is intended to be shown at. */
resolution: 1x;
/**
The following specifies a list of sprite names and associated rectangles into the image given above.
Any sprite given here can be specified in a decorator. Their names must be globally unique.
Rectangles are specified as: x y width height. With the origin assumed to be at the top left corner.
*/
title-bar-l: 147px 0px 82px 85px;
title-bar-c: 229px 0px 1px 85px;
title-bar-r: 231px 0px 15px 85px;
/* huditems are vertically flipped titlebars */
huditem-l: 147px 55px 82px -55px;
huditem-c: 229px 55px 1px -55px;
huditem-r: 231px 55px 15px -55px;
icon-help: 128px 152px 51px 39px;
icon-invader: 179px 152px 51px 39px;
icon-game: 230px 152px 51px 39px;
icon-hiscore: 281px 152px 51px 39px;
icon-waves: 332px 152px 51px 39px;
icon-flag: 336px 191px 51px 39px;
icon-lives: 383px 152px 51px 39px;
icon-score: 434px 152px 51px 39px;
window-tl: 0px 0px 133px 140px;
window-t: 134px 0px 1px 140px;
window-tr: 136px 0px 10px 140px;
window-l: 0px 139px 10px 1px;
window-c: 11px 139px 1px 1px;
window-r: 10px 139px -10px 1px; /* mirrored left */
window-bl: 0px 140px 11px 11px;
window-b: 11px 140px 1px 11px;
window-br: 136px 140px 10px 11px;
button: 247px 0px 159px 45px;
button-hover: 247px 45px 159px 45px;
button-active: 247px 90px 159px 45px;
button-inner: 259px 19px 135px 1px;
button-inner-hover: 259px 64px 135px 1px;
button-inner-active: 259px 109px 135px 1px;
text-l: 162px 192px 14px 31px;
text-c: 176px 192px 1px 31px;
textarea: 162px 193px 145px 31px;
textarea-inner: 173px 206px 127px 10px;
selectbox-tl: 281px 275px 11px 9px;
selectbox-t: 292px 275px 1px 9px;
selectbox-tr: 294px 275px 11px 9px;
selectbox-l: 281px 283px 11px 1px;
selectbox-c: 292px 283px 1px 1px;
selectbox-bl: 281px 285px 11px 11px;
selectbox-b: 292px 285px 1px 11px;
selectbox-br: 294px 285px 11px 11px;
selectvalue: 162px 192px 145px 37px;
selectvalue-hover: 162px 230px 145px 37px;
selectarrow: 307px 192px 30px 37px;
selectarrow-hover: 307px 230px 30px 37px;
selectarrow-active: 307px 268px 30px 37px;
radio: 407px 0px 30px 30px;
radio-hover: 437px 0px 30px 30px;
radio-active: 467px 0px 30px 30px;
radio-checked: 407px 30px 30px 30px;
radio-checked-hover: 437px 30px 30px 30px;
radio-checked-active: 467px 30px 30px 30px;
checkbox: 407px 60px 30px 30px;
checkbox-hover: 437px 60px 30px 30px;
checkbox-active: 467px 60px 30px 30px;
checkbox-checked: 407px 90px 30px 30px;
checkbox-checked-hover: 437px 90px 30px 30px;
checkbox-checked-active: 467px 90px 30px 30px;
tableheader-l: 127px 192px 16px 31px;
tableheader-c: 143px 192px 2px 31px;
tableheader-r: 145px 192px 15px 31px;
expand: 3px 232px 17px 17px;
expand-hover: 21px 232px 17px 17px;
expand-active: 39px 232px 17px 17px;
expand-collapsed: 3px 250px 17px 17px;
expand-collapsed-hover: 21px 250px 17px 17px;
expand-collapsed-active: 39px 250px 17px 17px;
slidertrack-t: 70px 199px 27px 2px;
slidertrack-c: 70px 201px 27px 1px;
slidertrack-b: 70px 202px 27px 2px;
sliderbar-t: 56px 152px 23px 23px;
sliderbar-c: 56px 175px 23px 1px;
sliderbar-b: 56px 176px 23px 22px;
sliderbar-hover-t: 80px 152px 23px 23px;
sliderbar-hover-c: 80px 175px 23px 1px;
sliderbar-hover-b: 80px 176px 23px 22px;
sliderbar-active-t: 104px 152px 23px 23px;
sliderbar-active-c: 104px 175px 23px 1px;
sliderbar-active-b: 104px 176px 23px 22px;
sliderarrowdec: 0px 152px 27px 24px;
sliderarrowdec-hover: 0px 177px 27px 24px;
sliderarrowdec-active: 0px 202px 27px 24px;
sliderarrowinc: 28px 152px 27px 24px;
sliderarrowinc-hover: 28px 177px 27px 24px;
sliderarrowinc-active: 28px 202px 27px 24px;
range-track: 219px 194px 3px 32px;
range-track-inner: 220px 204px 1px 14px;
range-bar: 127px 191px 34px 32px;
range-dec: 3px 232px 17px 17px;
range-dec-hover: 21px 232px 17px 17px;
range-dec-active: 39px 232px 17px 17px;
range-inc: 3px 250px 17px 17px;
range-inc-hover: 21px 250px 17px 17px;
range-inc-active: 39px 250px 17px 17px;
progress-l: 103px 267px 13px 34px;
progress-c: 116px 267px 54px 34px;
progress-r: 170px 267px 13px 34px;
progress-fill-l: 110px 302px 6px 34px;
progress-fill-c: 140px 302px 6px 34px;
progress-fill-r: 170px 302px 6px 34px;
gauge: 0px 271px 100px 86px;
gauge-fill: 0px 356px 100px 86px;
}

22
assets/launcher.rml Normal file
View File

@ -0,0 +1,22 @@
<rml>
<head>
<title>Launcher</title>
<link type="text/rcss" href="rml.rcss"/>
<link type="text/rcss" href="invader_spritesheet.rcss"/>
<link type="text/rcss" href="invader.rcss"/>
<style>
body
{
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="window">
<button onclick="start_game" style="align-self: center;">Start Game</button>
This is a sample.<br/>
Test text
</div>
</body>
</rml>

View File

@ -1,19 +0,0 @@
<template name="window" content="content">
<head>
<link type="text/rcss" href="rml.rcss"/>
<link type="text/rcss" href="invader.rcss"/>
</head>
<body class="window">
<div id="title_bar">
<handle move_target="#document">
<div id="icon"></div>
<span id="title">Title</span>
</handle>
</div>
<div id="window">
<div id="content">
</div>
</div>
<handle size_target="#document" style="position: absolute; width: 16dp; height: 16dp; bottom: 0px; right: 0px; cursor: resize;"></handle>
</body>
</template>

View File

@ -1,9 +1,25 @@
#ifndef __RECOMP_UI__
#define __RECOMP_UI__
#include <memory>
#include "SDL.h"
void queue_event(const SDL_Event& event);
bool try_deque_event(SDL_Event& out);
namespace Rml {
class ElementDocument;
class EventListenerInstancer;
}
std::unique_ptr<Rml::EventListenerInstancer> make_event_listener_instancer();
enum class Menu {
Launcher,
None
};
void set_current_menu(Menu menu);
#endif

View File

@ -125,8 +125,8 @@ auto RSP::CFC2(r32& rt, u8 rd) -> void {
if constexpr (Accuracy::RSP::SISD) {
rt = 0;
for (u32 n = 0; n < 8; n++) {
rt |= lo.get(n) << 0 + n;
rt |= hi.get(n) << 8 + n;
rt |= lo.get(n) << (0 + n);
rt |= hi.get(n) << (8 + n);
}
rt = s16(rt);
}
@ -151,8 +151,8 @@ auto RSP::CTC2(cr32& rt, u8 rd) -> void {
if constexpr (Accuracy::RSP::SISD) {
for (u32 n = 0; n < 8; n++) {
lo->set(n, rt & 1 << 0 + n);
hi->set(n, rt & 1 << 8 + n);
lo->set(n, rt & 1 << (0 + n));
hi->set(n, rt & 1 << (8 + n));
}
}

9
patches.toml Normal file
View File

@ -0,0 +1,9 @@
# Config file for recompiling patches for the Majora's Mask NTSC 1.0 Recompilation.
[input]
# Paths are relative to the location of this config file.
elf_path = "patches/patches.elf"
output_func_path = "RecompiledPatches"
single_file_output = true
# Allow absolute symbols to be used as jump targets
use_absolute_symbols = true

5
patches/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.d
*.o
*.elf
*.bin
./funcs.h

32
patches/Makefile Normal file
View File

@ -0,0 +1,32 @@
TARGET = patches.elf
CC := clang
LD := ld.lld
OBJCOPY := llvm-objcopy
CFLAGS := -target mips -mips2 -mabi=32 -O2 -mno-odd-spreg -fomit-frame-pointer -G0 -Wall -Wextra -Wno-incompatible-library-redeclaration -Wno-unused-parameter -Wno-unknown-pragmas -Wno-unused-variable
CPPFLAGS := -nostdinc -D_LANGUAGE_C -I ../../mm/include -I ../../mm/src -I ../../mm/build -I ../../mm/assets
LDFLAGS := -nostdlib -T patches.ld -T syms.ld
BINFLAGS := -O binary
C_SRCS := $(wildcard *.c)
C_OBJS := $(C_SRCS:.c=.o)
C_DEPS := $(C_SRCS:.c=.d)
DATABIN := $(TARGET:.elf=.bin)
$(DATABIN): $(TARGET)
$(OBJCOPY) $(BINFLAGS) $(TARGET) $@
$(TARGET): $(C_OBJS) patches.ld syms.ld
$(LD) $(LDFLAGS) $(C_OBJS) -o $@
$(C_OBJS): %.o : %.c
$(CC) $(CFLAGS) $(CPPFLAGS) $< -MMD -MF $(@:.o=.d) -c -o $@
clean:
rm -rf $(C_OBJS) $(TARGET) $(DATABIN)
-include $(C_DEPS)
.PHONY: clean

139
patches/cheats.c Normal file
View File

@ -0,0 +1,139 @@
#define Audio_PlaySfx play_sound
#include "global.h"
// Infinite magic
s32 Magic_Consume(PlayState* play, s16 magicToConsume, s16 type) {
InterfaceContext* interfaceCtx = &play->interfaceCtx;
magicToConsume = 0;
// // Magic is not acquired yet
// if (!gSaveContext.save.saveInfo.playerData.isMagicAcquired) {
// return false;
// }
// Not enough magic available to consume
if ((gSaveContext.save.saveInfo.playerData.magic - magicToConsume) < 0) {
if (gSaveContext.magicCapacity != 0) {
Audio_PlaySfx(NA_SE_SY_ERROR);
}
return false;
}
switch (type) {
case MAGIC_CONSUME_NOW:
case MAGIC_CONSUME_NOW_ALT:
// Drain magic immediately e.g. Deku Bubble
if ((gSaveContext.magicState == MAGIC_STATE_IDLE) ||
(gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS)) {
if (gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS) {
play->actorCtx.lensActive = false;
}
if (CHECK_WEEKEVENTREG(WEEKEVENTREG_DRANK_CHATEAU_ROMANI)) {
magicToConsume = 0;
}
gSaveContext.magicToConsume = magicToConsume;
gSaveContext.magicState = MAGIC_STATE_CONSUME_SETUP;
return true;
} else {
Audio_PlaySfx(NA_SE_SY_ERROR);
return false;
}
case MAGIC_CONSUME_WAIT_NO_PREVIEW:
// Sets consume target but waits to consume.
// No yellow magic to preview target consumption.
if ((gSaveContext.magicState == MAGIC_STATE_IDLE) ||
(gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS)) {
if (gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS) {
play->actorCtx.lensActive = false;
}
if (CHECK_WEEKEVENTREG(WEEKEVENTREG_DRANK_CHATEAU_ROMANI)) {
magicToConsume = 0;
}
gSaveContext.magicToConsume = magicToConsume;
gSaveContext.magicState = MAGIC_STATE_METER_FLASH_3;
return true;
} else {
Audio_PlaySfx(NA_SE_SY_ERROR);
return false;
}
case MAGIC_CONSUME_LENS:
if (gSaveContext.magicState == MAGIC_STATE_IDLE) {
if (gSaveContext.save.saveInfo.playerData.magic != 0) {
interfaceCtx->magicConsumptionTimer = 80;
gSaveContext.magicState = MAGIC_STATE_CONSUME_LENS;
return true;
} else {
return false;
}
} else if (gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS) {
return true;
} else {
return false;
}
case MAGIC_CONSUME_WAIT_PREVIEW:
// Sets consume target but waits to consume.
// Preview consumption with a yellow bar. e.g. Spin Attack
if ((gSaveContext.magicState == MAGIC_STATE_IDLE) ||
(gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS)) {
if (gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS) {
play->actorCtx.lensActive = false;
}
gSaveContext.magicToConsume = magicToConsume;
gSaveContext.magicState = MAGIC_STATE_METER_FLASH_2;
return true;
} else {
Audio_PlaySfx(NA_SE_SY_ERROR);
return false;
}
case MAGIC_CONSUME_GORON_ZORA:
// Goron spiked rolling or Zora electric barrier
if (gSaveContext.save.saveInfo.playerData.magic != 0) {
interfaceCtx->magicConsumptionTimer = 10;
gSaveContext.magicState = MAGIC_STATE_CONSUME_GORON_ZORA_SETUP;
return true;
} else {
return false;
}
case MAGIC_CONSUME_GIANTS_MASK:
// Wearing Giant's Mask
if (gSaveContext.magicState == MAGIC_STATE_IDLE) {
if (gSaveContext.save.saveInfo.playerData.magic != 0) {
interfaceCtx->magicConsumptionTimer = R_MAGIC_CONSUME_TIMER_GIANTS_MASK;
gSaveContext.magicState = MAGIC_STATE_CONSUME_GIANTS_MASK;
return true;
} else {
return false;
}
}
if (gSaveContext.magicState == MAGIC_STATE_CONSUME_GIANTS_MASK) {
return true;
} else {
return false;
}
case MAGIC_CONSUME_DEITY_BEAM:
// Consumes magic immediately
if ((gSaveContext.magicState == MAGIC_STATE_IDLE) ||
(gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS)) {
if (gSaveContext.magicState == MAGIC_STATE_CONSUME_LENS) {
play->actorCtx.lensActive = false;
}
if (CHECK_WEEKEVENTREG(WEEKEVENTREG_DRANK_CHATEAU_ROMANI)) {
magicToConsume = 0;
}
gSaveContext.save.saveInfo.playerData.magic -= magicToConsume;
return true;
} else {
Audio_PlaySfx(NA_SE_SY_ERROR);
return false;
}
}
return false;
}

47
patches/culling.c Normal file
View File

@ -0,0 +1,47 @@
#include "global.h"
// Disable frustum culling for actors, but leave distance culling intact
s32 func_800BA2FC(PlayState* play, Actor* actor, Vec3f* projectedPos, f32 projectedW) {
if ((-actor->uncullZoneScale < projectedPos->z) &&
(projectedPos->z < (actor->uncullZoneForward + actor->uncullZoneScale))) {
// f32 phi_f12;
// f32 phi_f2 = CLAMP_MIN(projectedW, 1.0f);
// f32 phi_f14;
// f32 phi_f16;
// if (play->view.fovy != 60.0f) {
// phi_f12 = actor->uncullZoneScale * play->projectionMtxFDiagonal.x * 0.76980036f; // sqrt(16/27)
// phi_f14 = play->projectionMtxFDiagonal.y * 0.57735026f; // 1 / sqrt(3)
// phi_f16 = actor->uncullZoneScale * phi_f14;
// phi_f14 *= actor->uncullZoneDownward;
// } else {
// phi_f16 = phi_f12 = actor->uncullZoneScale;
// phi_f14 = actor->uncullZoneDownward;
// }
// if (((fabsf(projectedPos->x) - phi_f12) < phi_f2) && ((-phi_f2 < (projectedPos->y + phi_f14))) &&
// ((projectedPos->y - phi_f16) < phi_f2)) {
return true;
// }
}
return false;
}
// Override LOD to 0
void Player_DrawGameplay(PlayState* play, Player* this, s32 lod, Gfx* cullDList,
OverrideLimbDrawFlex overrideLimbDraw) {
OPEN_DISPS(play->state.gfxCtx);
gSPSegment(POLY_OPA_DISP++, 0x0C, cullDList);
gSPSegment(POLY_XLU_DISP++, 0x0C, cullDList);
lod = 0; // Force the closest LOD
Player_DrawImpl(play, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, lod,
this->transformation, 0, this->actor.shape.face, overrideLimbDraw, Player_PostLimbDrawGameplay,
&this->actor);
CLOSE_DISPS(play->state.gfxCtx);
}

21
patches/patches.ld Normal file
View File

@ -0,0 +1,21 @@
RAMBASE = 0x80800100; /* Used to hold any new symbols */
MEMORY {
extram : ORIGIN = RAMBASE, LENGTH = 1M
rom : ORIGIN = 0, LENGTH = 1M
}
SECTIONS {
.text : { *(.text*) } >extram AT >rom
.ctors : { *(.ctors*) *(.init_array*) } >extram AT >rom
.dtors : { *(.dtors*) } >extram AT >rom
.rodata : { *(.rodata*) } >extram AT >rom
.data : { *(.data*) } >extram AT >rom
.bss (NOLOAD) : { *(.bss*) *(COMMON) } >extram
.symtab 0 : { *(.symtab) }
.strtab 0 : { *(.strtab) }
.shstrtab 0 : { *(.shstrtab) }
/DISCARD/ : { *(*); }
}

7
patches/syms.ld Normal file
View File

@ -0,0 +1,7 @@
__start = 0x80000000;
/* TODO pull these symbols from the elf file directly */
Player_PostLimbDrawGameplay = 0x80128BD0;
Player_DrawImpl = 0x801246F4;
gRegEditor = 0x801f3f60;
play_sound = 0x8019f0c8;
gSaveContext = 0x801ef670;

View File

@ -148,6 +148,7 @@ int sdl_event_filter(void* userdata, SDL_Event* event) {
}
Multilibultra::gfx_callbacks_t::gfx_data_t create_gfx() {
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "system");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) > 0) {
exit_error("Failed to initialize SDL2: %s\n", SDL_GetError());
}
@ -158,7 +159,7 @@ Multilibultra::gfx_callbacks_t::gfx_data_t create_gfx() {
SDL_Window* window;
Multilibultra::WindowHandle create_window(Multilibultra::gfx_callbacks_t::gfx_data_t) {
window = SDL_CreateWindow("Recomp", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_RESIZABLE);
window = SDL_CreateWindow("Recomp", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_RESIZABLE );
if (window == nullptr) {
exit_error("Failed to create window: %s\n", SDL_GetError());

48
src/ui/ui_events.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "recomp_ui.h"
#include "../../portultra/multilibultra.hpp"
#include "nfd.h"
#include "RmlUi/Core.h"
using event_handler_t = void(Rml::Event&);
class UiEventListener : public Rml::EventListener {
event_handler_t* handler_;
public:
UiEventListener(event_handler_t* handler) : handler_(handler) {}
void ProcessEvent(Rml::Event& event) override {
handler_(event);
}
};
class UiEventListenerInstancer : public Rml::EventListenerInstancer {
std::unordered_map<Rml::String, UiEventListener> listener_map_;
public:
Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override {
printf("Instancing event listener for %s\n", value.c_str());
auto find_it = listener_map_.find(value);
if (find_it != listener_map_.end()) {
return &find_it->second;
}
return nullptr;
}
void register_event(const Rml::String& value, event_handler_t* handler) {
listener_map_.emplace(value, UiEventListener{ handler });
}
};
std::unique_ptr<Rml::EventListenerInstancer> make_event_listener_instancer() {
std::unique_ptr<UiEventListenerInstancer> ret = std::make_unique<UiEventListenerInstancer>();
ret->register_event("start_game",
[](Rml::Event& event) {
Multilibultra::start_game(0);
set_current_menu(Menu::None);
}
);
return ret;
}

View File

@ -5,6 +5,8 @@
#include <fstream>
#include <filesystem>
#include "recomp_ui.h"
#include "concurrentqueue.h"
#include "rt64_layer.h"
@ -124,7 +126,7 @@ class RmlRenderInterface_RT64 : public Rml::RenderInterface {
Rml::Matrix4f transform_ = Rml::Matrix4f::Identity();
Rml::Matrix4f mvp_ = Rml::Matrix4f::Identity();
std::unordered_map<Rml::TextureHandle, TextureHandle> textures_{};
Rml::TextureHandle texture_count_ = 0;
Rml::TextureHandle texture_count_ = 1; // Start at 1 to reserve texture 0 as the 1x1 pixel white texture
std::unique_ptr<RT64::RenderBuffer> upload_buffer_{};
std::unique_ptr<RT64::RenderBuffer> vertex_buffer_{};
std::unique_ptr<RT64::RenderBuffer> index_buffer_{};
@ -251,8 +253,22 @@ public:
}
uint32_t allocate_upload_data_aligned(uint32_t num_bytes, uint32_t alignment) {
// Check if there's enough remaining room in the upload buffer to allocate the requested bytes.
uint32_t total_bytes = num_bytes + upload_buffer_bytes_used_;
// Determine the amount of padding needed to meet the target alignment.
uint32_t padding_bytes = ((upload_buffer_bytes_used_ + alignment - 1) / alignment) * alignment - upload_buffer_bytes_used_;
// If there isn't enough room to allocate the required bytes plus the padding then resize the upload buffer and allocate from the start of the new one.
if (total_bytes + padding_bytes > upload_buffer_size_) {
resize_upload_buffer(total_bytes + total_bytes / 2);
upload_buffer_bytes_used_ += num_bytes;
return 0;
}
// Otherwise allocate the padding and required bytes and offset the allocated position by the padding size.
return allocate_upload_data(padding_bytes + num_bytes) + padding_bytes;
}
@ -373,8 +389,6 @@ public:
std::filesystem::path image_path{ source.c_str() };
if (image_path.extension() == ".tga") {
printf("Opening TGA image: %s\n", image_path.u8string().c_str());
std::vector<char> file_data = read_file(image_path);
if (file_data.empty()) {
@ -531,7 +545,7 @@ public:
mvp_ = projection_mtx_ * transform_;
}
void start(RT64::RenderCommandList* list, uint32_t image_width, uint32_t image_height, bool reload_style) {
void start(RT64::RenderCommandList* list, uint32_t image_width, uint32_t image_height) {
list_ = list;
list_->setPipeline(pipeline_.get());
list_->setGraphicsPipelineLayout(layout_.get());
@ -546,10 +560,6 @@ public:
// Clear out any stale buffers from the last command list.
stale_buffers_.clear();
if (reload_style) {
load_document();
}
// Reset and map the upload buffer.
upload_buffer_bytes_used_ = 0;
upload_buffer_mapped_data_ = reinterpret_cast<uint8_t*>(upload_buffer_->map());
@ -568,32 +578,60 @@ public:
struct {
struct UIRenderContext render;
struct {
class {
std::unordered_map<Menu, Rml::ElementDocument*> documents;
Rml::ElementDocument* current_document;
public:
SystemInterface_SDL system_interface;
std::unique_ptr<RmlRenderInterface_RT64> render_interface;
Rml::Context* context;
std::unique_ptr<Rml::EventListenerInstancer> event_listener_instancer;
void swap_document(Menu menu) {
if (current_document != nullptr) {
current_document->Hide();
}
auto find_it = documents.find(menu);
if (find_it != documents.end()) {
assert(find_it->second && "Document for menu not loaded!");
current_document = find_it->second;
current_document->Show();
}
else {
current_document = nullptr;
}
}
void load_documents() {
if (!documents.empty()) {
Rml::Factory::RegisterEventListenerInstancer(nullptr);
for (auto doc : documents) {
doc.second->ReloadStyleSheet();
}
Rml::ReleaseTextures();
Rml::ReleaseMemoryPools();
if (current_document != nullptr) {
current_document->Hide();
current_document->Close();
}
current_document = nullptr;
documents.clear();
Rml::Factory::RegisterEventListenerInstancer(event_listener_instancer.get());
}
documents.emplace(Menu::Launcher, context->LoadDocument("assets/launcher.rml"));
}
} rml;
} UIContext;
// TODO make this not be global
extern SDL_Window* window;
void load_document() {
if (UIContext.render.document) {
UIContext.render.document->ReloadStyleSheet();
Rml::ReleaseTextures();
Rml::ReleaseMemoryPools();
UIContext.render.document->Hide();
UIContext.render.document->Close();
// Documents are owned by RmlUi, so we don't have anything to free here.
UIContext.render.document = nullptr;
}
UIContext.render.document = UIContext.rml.context->LoadDocument("assets/demo.rml");
if (UIContext.render.document) {
UIContext.render.document->Show();
}
}
void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
printf("RT64 hook init\n");
@ -603,9 +641,11 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
// Setup RML
UIContext.rml.system_interface.SetWindow(window);
UIContext.rml.render_interface = std::make_unique<RmlRenderInterface_RT64>(&UIContext.render);
UIContext.rml.event_listener_instancer = make_event_listener_instancer();
Rml::SetSystemInterface(&UIContext.rml.system_interface);
Rml::SetRenderInterface(UIContext.rml.render_interface.get());
Rml::Factory::RegisterEventListenerInstancer(UIContext.rml.event_listener_instancer.get());
Rml::Initialise();
@ -636,7 +676,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
}
}
load_document();
UIContext.rml.load_documents();
}
moodycamel::ConcurrentQueue<SDL_Event> ui_event_queue{};
@ -649,6 +689,8 @@ bool try_deque_event(SDL_Event& out) {
return ui_event_queue.try_dequeue(out);
}
std::atomic<Menu> open_menu = Menu::Launcher;
void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_chain_texture) {
int num_keys;
const Uint8* key_state = SDL_GetKeyboardState(&num_keys);
@ -658,20 +700,19 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_
bool reload_sheets = is_reload_held && !was_reload_held;
was_reload_held = is_reload_held;
static bool menu_open = true;
static bool was_toggle_menu_held = false;
bool is_toggle_menu_held = key_state[SDL_SCANCODE_M] != 0;
if (is_toggle_menu_held && !was_toggle_menu_held) {
menu_open = !menu_open;
static Menu prev_menu = Menu::None;
Menu cur_menu = open_menu.load();
if (reload_sheets) {
UIContext.rml.load_documents();
prev_menu = Menu::None;
}
was_toggle_menu_held = is_toggle_menu_held;
static bool was_start_game_held = false;
bool is_start_game_held = key_state[SDL_SCANCODE_SPACE] != 0;
if (is_start_game_held && !was_start_game_held) {
Multilibultra::start_game(0);
if (cur_menu != prev_menu) {
UIContext.rml.swap_document(cur_menu);
}
was_start_game_held = is_start_game_held;
prev_menu = cur_menu;
SDL_Event cur_event{};
@ -679,11 +720,11 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_
RmlSDL::InputEventHandler(UIContext.rml.context, cur_event);
}
if (menu_open) {
if (cur_menu != Menu::None) {
int width, height;
SDL_GetWindowSizeInPixels(window, &width, &height);
UIContext.rml.render_interface->start(command_list, width, height, reload_sheets);
UIContext.rml.render_interface->start(command_list, width, height);
static int prev_width = 0;
static int prev_height = 0;
@ -707,3 +748,7 @@ void deinit_hook() {
void set_rt64_hooks() {
RT64::SetRenderHooks(init_hook, draw_hook, deinit_hook);
}
void set_current_menu(Menu menu) {
open_menu.store(menu);
}

@ -0,0 +1 @@
Subproject commit 75cbdf819785d9f94855987724e30a6ba0a87e29