diff --git a/CMakeLists.txt b/CMakeLists.txt index aac9644..921cb3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,10 @@ cmake_minimum_required(VERSION 3.20) -project(BanjoRecompiled) + +if (APPLE) + set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum OS X deployment version") +endif() + +project(BanjoRecompiled LANGUAGES C CXX) set(CMAKE_C_STANDARD 17) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -16,6 +21,14 @@ if (WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR") endif() +if (APPLE) + enable_language(OBJC OBJCXX) +endif() + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + option(RECOMP_FLATPAK "Configure the build for Flatpak compatibility." OFF) +endif() + # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") cmake_policy(SET CMP0135 NEW) @@ -32,6 +45,10 @@ set(RT64_STATIC TRUE) set(RT64_SDL_WINDOW_VULKAN TRUE) add_compile_definitions(HLSL_CPU) +if (RECOMP_FLATPAK) + add_compile_definitions(RECOMP_FLATPAK) +endif() + add_subdirectory(${CMAKE_SOURCE_DIR}/lib/rt64 ${CMAKE_BINARY_DIR}/rt64) # set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}") @@ -42,6 +59,7 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/lib/lunasvg) SET(RMLUI_SVG_PLUGIN ON CACHE BOOL "" FORCE) SET(RMLUI_TESTS_ENABLED OFF CACHE BOOL "" FORCE) add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi) +target_compile_definitions(rmlui_core PRIVATE LUNASVG_BUILD_STATIC) add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime) @@ -110,7 +128,7 @@ add_custom_target(PatchesBin # Generate patches_bin.c from patches.bin add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches_bin.c - COMMAND file_to_c ${CMAKE_SOURCE_DIR}/patches/patches.bin bk_patches_bin ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches_bin.c ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches_bin.h + COMMAND file_to_c ${CMAKE_SOURCE_DIR}/patches/patches.bin bk_patches_bin ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches_bin.c ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches_bin.h DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.bin ) @@ -126,22 +144,12 @@ add_custom_command(OUTPUT DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.elf ) -# Download controller db file for controller support via SDL2 -set(GAMECONTROLLERDB_COMMIT "b1e4090b3d4266e55feb0793efa35792e05faf66") -set(GAMECONTROLLERDB_URL "https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/${GAMECONTROLLERDB_COMMIT}/gamecontrollerdb.txt") - -file(DOWNLOAD ${GAMECONTROLLERDB_URL} ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt - TLS_VERIFY ON) - -add_custom_target(DownloadGameControllerDB - DEPENDS ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt) - # Main executable add_executable(BanjoRecompiled) -add_dependencies(BanjoRecompiled DownloadGameControllerDB) set (SOURCES ${CMAKE_SOURCE_DIR}/src/main/main.cpp + ${CMAKE_SOURCE_DIR}/src/main/support.cpp ${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp ${CMAKE_SOURCE_DIR}/src/main/register_patches.cpp ${CMAKE_SOURCE_DIR}/src/main/rt64_render_context.cpp @@ -151,20 +159,26 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/game/config.cpp ${CMAKE_SOURCE_DIR}/src/game/debug.cpp ${CMAKE_SOURCE_DIR}/src/game/recomp_api.cpp - ${CMAKE_SOURCE_DIR}/src/game/recomp_mem_api.cpp + ${CMAKE_SOURCE_DIR}/src/game/recomp_actor_api.cpp + ${CMAKE_SOURCE_DIR}/src/game/recomp_data_api.cpp ${CMAKE_SOURCE_DIR}/src/game/rom_decompression.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_state.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp + ${CMAKE_SOURCE_DIR}/src/ui/ui_prompt.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_elements.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_details_panel.cpp + ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_installer.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_menu.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_api.cpp + ${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp + ${CMAKE_SOURCE_DIR}/src/ui/ui_api_images.cpp + ${CMAKE_SOURCE_DIR}/src/ui/ui_utils.cpp ${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp ${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp @@ -176,6 +190,7 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_radio.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_scroll_container.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp + ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_span.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_style.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_text_input.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp @@ -185,6 +200,10 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp ) +if (APPLE) + list(APPEND SOURCES ${CMAKE_SOURCE_DIR}/src/main/support_apple.mm) +endif() + target_include_directories(BanjoRecompiled PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/N64Recomp/include @@ -221,6 +240,12 @@ endif() if (MSVC) # Disable identical code folding, since this breaks mod function patching as multiple functions can get merged into one. target_link_options(BanjoRecompiled PRIVATE /OPT:NOICF) +elseif (APPLE) + # Use a wrapper around ld64 that respects segprot's `max_prot` value in order + # to make our executable memory writable (required for mod function patching) + target_link_options(BanjoRecompiled PRIVATE + "-fuse-ld=${CMAKE_SOURCE_DIR}/.github/macos/ld64" + ) endif() if (WIN32) @@ -263,6 +288,20 @@ if (WIN32) ) # target_sources(BanjoRecompiled PRIVATE ${CMAKE_SOURCE_DIR}/icons/app.rc) + target_link_libraries(BanjoRecompiled PRIVATE SDL2) +endif() + +if (APPLE) + find_package(SDL2 REQUIRED) + target_include_directories(BanjoRecompiled PRIVATE ${SDL2_INCLUDE_DIRS}) + + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + set(THREADS_PREFER_PTHREAD_FLAG TRUE) + find_package(Threads REQUIRED) + + target_link_libraries(BanjoRecompiled PRIVATE ${CMAKE_DL_LIBS} Threads::Threads SDL2::SDL2) + + include(${CMAKE_SOURCE_DIR}/.github/macos/apple_bundle.cmake) endif() if (CMAKE_SYSTEM_NAME MATCHES "Linux") @@ -273,7 +312,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux") # 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 - 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 DEPENDS ${CMAKE_SOURCE_DIR}/icons/512.png ) target_sources(BanjoRecompiled PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c) @@ -298,7 +337,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux") message(STATUS "FREETYPE_LIBRARIES = ${FREETYPE_LIBRARIES}") include_directories(${FREETYPE_LIBRARIES}) - target_link_libraries(BanjoRecompiled PRIVATE ${FREETYPE_LIBRARIES}) + target_link_libraries(BanjoRecompiled PRIVATE ${FREETYPE_LIBRARIES} SDL2::SDL2) set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) @@ -310,7 +349,6 @@ endif() target_link_libraries(BanjoRecompiled PRIVATE PatchesLib RecompiledFuncs - SDL2 librecomp ultramodern rt64 @@ -338,9 +376,14 @@ else() if (APPLE) # Apple's binary is universal, so it'll work on both x86_64 and arm64 set (DXC "DYLD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/arm64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/arm64/dxc-macos") + if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") + set(SPIRVCROSS "DYLD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/spirv-cross/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64//src/contrib/spirv-cross/bin/x64/spirv-cross") + else() + set(SPIRVCROSS "DYLD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/spirv-cross/lib/arm64" "${PROJECT_SOURCE_DIR}/lib/rt64//src/contrib/spirv-cross/bin/x64/spirv-cross") + endif() else() if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") - set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc") + set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc-linux") else() set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/arm64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/arm64/dxc-linux") endif() @@ -350,6 +393,27 @@ endif() build_vertex_shader(BanjoRecompiled "shaders/InterfaceVS.hlsl" "shaders/InterfaceVS.hlsl") build_pixel_shader(BanjoRecompiled "shaders/InterfacePS.hlsl" "shaders/InterfacePS.hlsl") +# Embed all .nrm files in the "mods" directory +file(GLOB NRM_FILES "${CMAKE_SOURCE_DIR}/mods/*.nrm") + +set(GENERATED_NRM_SOURCES "") + +foreach(NRM_FILE ${NRM_FILES}) + get_filename_component(NRM_NAME ${NRM_FILE} NAME_WE) + set(OUT_C "${CMAKE_CURRENT_BINARY_DIR}/mods/${NRM_NAME}.c") + set(OUT_H "${CMAKE_CURRENT_BINARY_DIR}/mods/${NRM_NAME}.h") + + add_custom_command( + OUTPUT ${OUT_C} ${OUT_H} + COMMAND file_to_c ${NRM_FILE} ${NRM_NAME} ${OUT_C} ${OUT_H} + DEPENDS ${NRM_FILE} + ) + + list(APPEND GENERATED_NRM_SOURCES ${OUT_C}) +endforeach() + +target_sources(BanjoRecompiled PRIVATE ${GENERATED_NRM_SOURCES}) + target_sources(BanjoRecompiled PRIVATE ${SOURCES}) set_property(TARGET BanjoRecompiled PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") diff --git a/include/banjo_render.h b/include/banjo_render.h index c4b0553..b73b48b 100644 --- a/include/banjo_render.h +++ b/include/banjo_render.h @@ -14,6 +14,8 @@ namespace RT64 { namespace banjo { namespace renderer { + inline const std::string special_option_texture_pack_enabled = "_recomp_texture_pack_enabled"; + class RT64Context final : public ultramodern::renderer::RendererContext { public: ~RT64Context() override; @@ -30,9 +32,12 @@ namespace banjo { uint32_t get_display_framerate() const override; float get_resolution_scale() const override; - protected: + private: std::unique_ptr app; - std::unordered_set enabled_texture_packs; + std::unordered_set enabled_texture_packs; + std::unordered_set secondary_disabled_texture_packs; + + void check_texture_pack_actions(); }; std::unique_ptr create_render_context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode); @@ -41,8 +46,15 @@ namespace banjo { bool RT64SamplePositionsSupported(); bool RT64HighPrecisionFBEnabled(); - void enable_texture_pack(const recomp::mods::ModHandle& mod); + void trigger_texture_pack_update(); + void enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod); void disable_texture_pack(const recomp::mods::ModHandle& mod); + void secondary_enable_texture_pack(const std::string& mod_id); + void secondary_disable_texture_pack(const std::string& mod_id); + + // Texture pack enable option. Must be an enum with two options. + // The first option is treated as disabled and the second option is treated as enabled. + bool is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors); } } diff --git a/include/banjo_support.h b/include/banjo_support.h new file mode 100644 index 0000000..a4e5a38 --- /dev/null +++ b/include/banjo_support.h @@ -0,0 +1,26 @@ +#ifndef __BANJO_SUPPORT_H__ +#define __BANJO_SUPPORT_H__ + +#include +#include +#include +#include +#include + +namespace banjo { + std::filesystem::path get_program_path(); + std::filesystem::path get_asset_path(const char* asset); + void open_file_dialog(std::function callback); + void open_file_dialog_multiple(std::function& paths)> callback); + void show_error_message_box(const char *title, const char *message); + +// Apple specific methods that usually require Objective-C. Implemented in support_apple.mm. +#ifdef __APPLE__ + void dispatch_on_ui_thread(std::function func); + std::optional get_application_support_directory(); + std::filesystem::path get_bundle_resource_directory(); + std::filesystem::path get_bundle_directory(); +#endif +} + +#endif diff --git a/include/overloaded.h b/include/overloaded.h new file mode 100644 index 0000000..2ead7ad --- /dev/null +++ b/include/overloaded.h @@ -0,0 +1,10 @@ +#ifndef __OVERLOADED_H__ +#define __OVERLOADED_H__ + +// Helper for std::visit +template +struct overloaded : Ts... { using Ts::operator()...; }; +template +overloaded(Ts...) -> overloaded; + +#endif diff --git a/include/recomp_data.h b/include/recomp_data.h index 87693c7..6891d85 100644 --- a/include/recomp_data.h +++ b/include/recomp_data.h @@ -1,9 +1,11 @@ #ifndef __RECOMP_DATA_H__ #define __RECOMP_DATA_H__ -namespace recomp { +namespace recomputil { void init_extended_actor_data(); void reset_actor_data(); + + void register_data_api_exports(); } #endif diff --git a/include/recomp_ui.h b/include/recomp_ui.h index b8e4f85..f609a52 100644 --- a/include/recomp_ui.h +++ b/include/recomp_ui.h @@ -4,6 +4,7 @@ #include #include #include +#include // TODO move this file into src/ui @@ -29,7 +30,7 @@ namespace recompui { class MenuController { public: virtual ~MenuController() {} - virtual Rml::ElementDocument* load_document(Rml::Context* context) = 0; + virtual void load_document() = 0; virtual void register_events(UiEventListenerInstancer& listener) = 0; virtual void make_bindings(Rml::Context* context) = 0; }; @@ -49,13 +50,14 @@ namespace recompui { void hide_context(ContextId context); void hide_all_contexts(); bool is_context_shown(ContextId context); - bool is_context_taking_input(); + bool is_context_capturing_input(); + bool is_context_capturing_mouse(); bool is_any_context_shown(); + ContextId try_close_current_context(); ContextId get_launcher_context_id(); ContextId get_config_context_id(); ContextId get_config_sub_menu_context_id(); - ContextId get_close_prompt_context_id(); enum class ConfigTab { General, @@ -67,6 +69,11 @@ namespace recompui { }; void set_config_tab(ConfigTab tab); + int config_tab_to_index(ConfigTab tab); + Rml::ElementTabSet* get_config_tabset(); + Rml::Element* get_mod_tab(); + void set_config_tabset_mod_nav(); + void focus_mod_configure_button(); enum class ButtonVariant { Primary, @@ -78,19 +85,37 @@ namespace recompui { NumVariants, }; - void open_prompt( - const std::string& headerText, - const std::string& contentText, - const std::string& confirmLabelText, - const std::string& cancelLabelText, - std::function confirmCb, - std::function cancelCb, - ButtonVariant _confirmVariant = ButtonVariant::Success, - ButtonVariant _cancelVariant = ButtonVariant::Error, - bool _focusOnCancel = true, - const std::string& _returnElementId = "" + void init_styling(const std::filesystem::path& rcss_file); + void init_prompt_context(); + void open_choice_prompt( + const std::string& header_text, + const std::string& content_text, + const std::string& confirm_label_text, + const std::string& cancel_label_text, + std::function confirm_action, + std::function cancel_action, + ButtonVariant confirm_variant = ButtonVariant::Success, + ButtonVariant cancel_variant = ButtonVariant::Error, + bool focus_on_cancel = true, + const std::string& return_element_id = "" ); + void open_info_prompt( + const std::string& header_text, + const std::string& content_text, + const std::string& okay_label_text, + std::function okay_action, + ButtonVariant okay_variant = ButtonVariant::Error, + const std::string& return_element_id = "" + ); + void open_notification( + const std::string& header_text, + const std::string& content_text, + const std::string& return_element_id = "" + ); + void close_prompt(); bool is_prompt_open(); + void update_mod_list(bool scan_mods = true); + void process_game_started(); void apply_color_hack(); void get_window_size(int& width, int& height); @@ -109,8 +134,13 @@ namespace recompui { Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag); Rml::ElementDocument* load_document(const std::filesystem::path& path); Rml::ElementDocument* create_empty_document(); - void queue_image_from_bytes(const std::string &src, const std::vector &bytes); + Rml::Element* get_child_by_tag(Rml::Element* parent, const std::string& tag); + + void queue_image_from_bytes_rgba32(const std::string &src, const std::vector &bytes, uint32_t width, uint32_t height); + void queue_image_from_bytes_file(const std::string &src, const std::vector &bytes); void release_image(const std::string &src); + + void drop_files(const std::list &file_list); } #endif diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index 6ffd6b8..c5e268a 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit 6ffd6b8856c6c613ba01cbccda8985691c05d70a +Subproject commit c5e268aa0f71cf06a10a001da981dc3e02e7dff0 diff --git a/lib/lunasvg b/lib/lunasvg index 610b8bf..4166d0c 160000 --- a/lib/lunasvg +++ b/lib/lunasvg @@ -1 +1 @@ -Subproject commit 610b8bf5148a27489b4e3344b4f5617b81be38c7 +Subproject commit 4166d0cccfc059b39d5ecfc372524375e59448f9 diff --git a/lib/rt64 b/lib/rt64 index 1db8c34..cf75b17 160000 --- a/lib/rt64 +++ b/lib/rt64 @@ -1 +1 @@ -Subproject commit 1db8c347caa9dd356050777ac79a81f1ccfa462b +Subproject commit cf75b17fc263a45269d5b9449eaa0dadcb918528 diff --git a/patches/actor_funcs.h b/patches/actor_funcs.h new file mode 100644 index 0000000..6c091e1 --- /dev/null +++ b/patches/actor_funcs.h @@ -0,0 +1,14 @@ +#ifndef __MEM_FUNCS_H__ +#define __MEM_FUNCS_H__ + +#include "patch_helpers.h" + +DECLARE_FUNC(u32, recomp_register_actor_extension, u32 actor_type, u32 size); +DECLARE_FUNC(u32, recomp_register_actor_extension_generic, u32 size); +DECLARE_FUNC(void, recomp_clear_all_actor_data); +DECLARE_FUNC(u32, recomp_create_actor_data, u32 actor_type); +DECLARE_FUNC(void, recomp_destroy_actor_data, u32 actor_handle); +DECLARE_FUNC(void*, recomp_get_actor_data, u32 actor_handle, u32 extension_handle, u32 actor_type); +DECLARE_FUNC(u32, recomp_get_actor_spawn_index, u32 actor_handle); + +#endif diff --git a/patches/culling_patches.c b/patches/culling_patches.c index 9b7c727..6cfa071 100644 --- a/patches/culling_patches.c +++ b/patches/culling_patches.c @@ -1,60 +1,5 @@ #include "patches.h" -typedef struct Cube_s Cube; - -RECOMP_PATCH bool viewport_cube_isInFrustum(Cube *cube) { - // f32 sp24[3]; - // f32 sp18[3]; - - // sp24[0] = (f32) ((cube->x * 1000) - 150); - // sp24[1] = (f32) ((cube->y * 1000) - 150); - // sp24[2] = (f32) ((cube->z * 1000) - 150); - // sp18[0] = sp24[0] + 1300.0f; - // sp18[1] = sp24[1] + 1300.0f; - // sp18[2] = sp24[2] + 1300.0f; - // return func_8024D374(sp24, sp18); - return TRUE; -} - -RECOMP_PATCH bool viewport_cube_isInFrustum2(Cube *cube) { - // f32 sp34[3]; - // f32 sp28[3]; - // f32 sp1C[3]; - - // if (cube->x == -0x10) { - // return TRUE; - // } - // sp1C[0] = (f32) ((cube->x * 1000) + 500) - viewportPosition[0]; - // sp1C[1] = (f32) ((cube->y * 1000) + 500) - viewportPosition[1]; - // sp1C[2] = (f32) ((cube->z * 1000) + 500) - viewportPosition[2]; - // if (LENGTH_SQ_VEC3F(sp1C) > 1.6e7f) { - // return FALSE; - // } - // sp34[0] = (f32) ((cube->x * 1000) - 150); - // sp34[1] = (f32) ((cube->y * 1000) - 150); - // sp34[2] = (f32) ((cube->z * 1000) - 150); - // sp28[0] = sp34[0] + 1300.0f; - // sp28[1] = sp34[1] + 1300.0f; - // sp28[2] = sp34[2] + 1300.0f; - // return func_8024D374(sp34, sp28); - return TRUE; -} - -RECOMP_PATCH bool viewport_func_8024DB50(f32 arg0[3], f32 arg1) { - // f32 sp3C[3]; - // s32 i; - - // sp3C[0] = arg0[0] - viewportPosition[0]; - // sp3C[1] = arg0[1] - viewportPosition[1]; - // sp3C[2] = arg0[2] - viewportPosition[2]; - // for(i = 0; i < 4; i++){ - // if(arg1 <= ml_dotProduct_vec3f(sp3C, D_80280ED0[i])){ - // return FALSE; - // } - // } - return TRUE; -} - RECOMP_PATCH bool viewport_isBoundingBoxInFrustum(f32 arg0[3], f32 arg1[3]) { return TRUE; } diff --git a/patches/input.h b/patches/input.h index e505bfc..d4dd09f 100644 --- a/patches/input.h +++ b/patches/input.h @@ -19,7 +19,6 @@ extern RecompAimingOverideMode recomp_aiming_override_mode; DECLARE_FUNC(void, recomp_get_gyro_deltas, float* x, float* y); DECLARE_FUNC(void, recomp_get_mouse_deltas, float* x, float* y); -DECLARE_FUNC(s32, recomp_get_targeting_mode); DECLARE_FUNC(void, recomp_get_inverted_axes, s32* x, s32* y); DECLARE_FUNC(s32, recomp_get_analog_cam_enabled); DECLARE_FUNC(void, recomp_get_analog_inverted_axes, s32* x, s32* y); diff --git a/patches/recompui_event_structs.h b/patches/recompui_event_structs.h new file mode 100644 index 0000000..914160d --- /dev/null +++ b/patches/recompui_event_structs.h @@ -0,0 +1,52 @@ +#ifndef __UI_FUNCS_H__ +#define __UI_FUNCS_H__ + +// These two enums must be kept in sync with src/ui/elements/ui_types.h! +typedef enum { + UI_EVENT_NONE, + UI_EVENT_CLICK, + UI_EVENT_FOCUS, + UI_EVENT_HOVER, + UI_EVENT_ENABLE, + UI_EVENT_DRAG, + UI_EVENT_RESERVED1, // Would be UI_EVENT_TEXT but text events aren't usable in mods currently + UI_EVENT_UPDATE, + UI_EVENT_COUNT +} RecompuiEventType; + +typedef enum { + UI_DRAG_NONE, + UI_DRAG_START, + UI_DRAG_MOVE, + UI_DRAG_END +} RecompuiDragPhase; + +typedef struct { + RecompuiEventType type; + union { + struct { + float x; + float y; + } click; + + struct { + bool active; + } focus; + + struct { + bool active; + } hover; + + struct { + bool active; + } enable; + + struct { + float x; + float y; + RecompuiDragPhase phase; + } drag; + } data; +} RecompuiEventData; + +#endif diff --git a/patches/transform_ids.h b/patches/transform_ids.h new file mode 100644 index 0000000..a288f61 --- /dev/null +++ b/patches/transform_ids.h @@ -0,0 +1,6 @@ +#ifndef __TRANSFORM_IDS_H__ +#define __TRANSFORM_IDS_H__ + + + +#endif diff --git a/patches/transform_tagging.c b/patches/transform_tagging.c new file mode 100644 index 0000000..95318ee --- /dev/null +++ b/patches/transform_tagging.c @@ -0,0 +1,3 @@ +#include "patches.h" +#include "transform_ids.h" + diff --git a/patches/ui_funcs.h b/patches/ui_funcs.h new file mode 100644 index 0000000..ad6e572 --- /dev/null +++ b/patches/ui_funcs.h @@ -0,0 +1,9 @@ +#ifndef __UI_FUNCS_INTERNAL_H__ +#define __UI_FUNCS_INTERNAL_H__ + +#include "patch_helpers.h" +#include "recompui_event_structs.h" + +DECLARE_FUNC(void, recomp_run_ui_callbacks); + +#endif diff --git a/src/game/config.cpp b/src/game/config.cpp index 6b464bc..e361b74 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -2,6 +2,7 @@ #include "recomp_input.h" #include "banjo_sound.h" #include "banjo_render.h" +#include "banjo_support.h" #include "ultramodern/config.hpp" #include "librecomp/files.hpp" #include @@ -13,6 +14,8 @@ #elif defined(__linux__) #include #include +#elif defined(__APPLE__) +#include "apple/rt64_apple.h" #endif constexpr std::u8string_view general_filename = u8"general.json"; @@ -71,7 +74,7 @@ T from_or_default(const json& j, const std::string& key, T default_value) { else { ret = default_value; } - + return ret; } @@ -129,11 +132,19 @@ namespace recomp { } std::filesystem::path banjo::get_app_folder_path() { - // directly check for portable.txt (windows and native linux binary) + // directly check for portable.txt (windows and native linux binary) if (std::filesystem::exists("portable.txt")) { return std::filesystem::current_path(); } +#if defined(__APPLE__) + // Check for portable file in the directory containing the app bundle. + const auto app_bundle_path = banjo::get_bundle_directory().parent_path(); + if (std::filesystem::exists(app_bundle_path / "portable.txt")) { + return app_bundle_path; + } +#endif + std::filesystem::path recomp_dir{}; #if defined(_WIN32) @@ -145,16 +156,27 @@ std::filesystem::path banjo::get_app_folder_path() { } CoTaskMemFree(known_path); -#elif defined(__linux__) - // check for APP_FOLDER_PATH env var used by AppImage +#elif defined(__linux__) || defined(__APPLE__) + // check for APP_FOLDER_PATH env var if (getenv("APP_FOLDER_PATH") != nullptr) { return std::filesystem::path{getenv("APP_FOLDER_PATH")}; } +#if defined(__APPLE__) + const auto supportdir = banjo::get_application_support_directory(); + if (supportdir) { + return *supportdir / banjo::program_id; + } +#endif + const char *homedir; if ((homedir = getenv("HOME")) == nullptr) { + #if defined(__linux__) homedir = getpwuid(getuid())->pw_dir; + #elif defined(__APPLE__) + homedir = GetHomeDirectory(); + #endif } if (homedir != nullptr) { @@ -206,7 +228,7 @@ bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::j return recomp::finalize_output_file_with_backup(path); } -bool save_general_config(const std::filesystem::path& path) { +bool save_general_config(const std::filesystem::path& path) { nlohmann::json config_json{}; recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode()); @@ -218,7 +240,7 @@ bool save_general_config(const std::filesystem::path& path) { config_json["analog_cam_mode"] = banjo::get_analog_cam_mode(); config_json["analog_camera_invert_mode"] = banjo::get_analog_camera_invert_mode(); config_json["debug_mode"] = banjo::get_debug_mode_enabled(); - + return save_json_with_backups(path, config_json); } @@ -433,7 +455,7 @@ bool save_sound_config(const std::filesystem::path& path) { config_json["main_volume"] = banjo::get_main_volume(); config_json["bgm_volume"] = banjo::get_bgm_volume(); - + return save_json_with_backups(path, config_json); } @@ -494,7 +516,7 @@ void banjo::save_config() { } std::filesystem::create_directories(recomp_dir); - + // TODO error handling for failing to save config files. save_general_config(recomp_dir / general_filename); diff --git a/src/game/input.cpp b/src/game/input.cpp index 54936fa..6e4bccc 100644 --- a/src/game/input.cpp +++ b/src/game/input.cpp @@ -31,7 +31,7 @@ static struct { std::mutex cur_controllers_mutex; std::vector cur_controllers{}; std::unordered_map controller_states; - + std::array rotation_delta{}; std::array mouse_delta{}; std::mutex pending_input_mutex; @@ -42,6 +42,10 @@ static struct { bool rumble_active; } InputState; +static struct { + std::list files_dropped; +} DropState; + std::atomic scanning_device = recomp::InputDevice::COUNT; std::atomic scanned_input; @@ -93,85 +97,82 @@ bool should_override_keystate(SDL_Scancode key, SDL_Keymod mod) { } } - return false; + return false; } bool sdl_event_filter(void* userdata, SDL_Event* event) { switch (event->type) { case SDL_EventType::SDL_KEYDOWN: - { - SDL_KeyboardEvent* keyevent = &event->key; + { + SDL_KeyboardEvent* keyevent = &event->key; - // Skip repeated events when not in the menu - if (!recompui::is_context_taking_input() && - event->key.repeat) { - break; - } + // Skip repeated events when not in the menu + if (!recompui::is_context_capturing_input() && + event->key.repeat) { + break; + } - if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) || - keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11 + if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) || + keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11 ) { - recompui::toggle_fullscreen(); + recompui::toggle_fullscreen(); + } + if (scanning_device != recomp::InputDevice::COUNT) { + if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) { + recomp::cancel_scanning_input(); } - if (scanning_device != recomp::InputDevice::COUNT) { - if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) { - recomp::cancel_scanning_input(); - } else if (scanning_device == recomp::InputDevice::Keyboard) { - set_scanned_input({(uint32_t)InputType::Keyboard, keyevent->keysym.scancode}); - } - } else { - if (!should_override_keystate(keyevent->keysym.scancode, static_cast(keyevent->keysym.mod))) { - queue_if_enabled(event); - } + else if (scanning_device == recomp::InputDevice::Keyboard) { + set_scanned_input({ (uint32_t)InputType::Keyboard, keyevent->keysym.scancode }); } } - break; + else { + if (!should_override_keystate(keyevent->keysym.scancode, static_cast(keyevent->keysym.mod))) { + queue_if_enabled(event); + } + } + } + break; case SDL_EventType::SDL_CONTROLLERDEVICEADDED: - { - SDL_ControllerDeviceEvent* controller_event = &event->cdevice; - SDL_GameController* controller = SDL_GameControllerOpen(controller_event->which); - printf("Controller added: %d\n", controller_event->which); - if (controller != nullptr) { - printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))); - ControllerState& state = InputState.controller_states[SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))]; - state.controller = controller; + { + SDL_ControllerDeviceEvent* controller_event = &event->cdevice; + SDL_GameController* controller = SDL_GameControllerOpen(controller_event->which); + printf("Controller added: %d\n", controller_event->which); + if (controller != nullptr) { + printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))); + ControllerState& state = InputState.controller_states[SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))]; + state.controller = controller; - if (SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_GYRO) && SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_ACCEL)) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_GYRO, SDL_TRUE); - SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_ACCEL, SDL_TRUE); - } + if (SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_GYRO) && SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_ACCEL)) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_GYRO, SDL_TRUE); + SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_ACCEL, SDL_TRUE); } } - break; + } + break; case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED: - { - SDL_ControllerDeviceEvent* controller_event = &event->cdevice; - printf("Controller removed: %d\n", controller_event->which); - InputState.controller_states.erase(controller_event->which); - } - break; + { + SDL_ControllerDeviceEvent* controller_event = &event->cdevice; + printf("Controller removed: %d\n", controller_event->which); + InputState.controller_states.erase(controller_event->which); + } + break; case SDL_EventType::SDL_QUIT: { if (!ultramodern::is_game_started()) { ultramodern::quit(); return true; } - recompui::ContextId config_context_id = recompui::get_config_context_id(); - if (!recompui::is_context_shown(config_context_id)) { - recompui::show_context(config_context_id, ""); - } - banjo::open_quit_game_prompt(); recompui::activate_mouse(); break; } case SDL_EventType::SDL_MOUSEWHEEL: - { - SDL_MouseWheelEvent* wheel_event = &event->wheel; - InputState.mouse_wheel_pos.fetch_add(wheel_event->y * (wheel_event->direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1)); - } - queue_if_enabled(event); - break; + { + SDL_MouseWheelEvent* wheel_event = &event->wheel; + InputState.mouse_wheel_pos.fetch_add(wheel_event->y * (wheel_event->direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1)); + } + queue_if_enabled(event); + break; case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: if (scanning_device != recomp::InputDevice::COUNT) { auto menuToggleBinding0 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller); @@ -180,22 +181,24 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) { if ((menuToggleBinding0.input_type != 0 && event->cbutton.button == menuToggleBinding0.input_id) || (menuToggleBinding1.input_type != 0 && event->cbutton.button == menuToggleBinding1.input_id)) { recomp::cancel_scanning_input(); - } else if (scanning_device == recomp::InputDevice::Controller) { + } + else if (scanning_device == recomp::InputDevice::Controller) { SDL_ControllerButtonEvent* button_event = &event->cbutton; auto scanned_input_index = recomp::get_scanned_input_index(); if ((scanned_input_index == static_cast(recomp::GameInput::TOGGLE_MENU) || - scanned_input_index == static_cast(recomp::GameInput::ACCEPT_MENU) || - scanned_input_index == static_cast(recomp::GameInput::APPLY_MENU)) && ( - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) { + scanned_input_index == static_cast(recomp::GameInput::ACCEPT_MENU) || + scanned_input_index == static_cast(recomp::GameInput::APPLY_MENU)) && ( + button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP || + button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN || + button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT || + button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) { break; } - set_scanned_input({(uint32_t)InputType::ControllerDigital, button_event->button}); + set_scanned_input({ (uint32_t)InputType::ControllerDigital, button_event->button }); } - } else { + } + else { queue_if_enabled(event); } break; @@ -217,8 +220,8 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) { set_stick_return_event.user.data1 = nullptr; set_stick_return_event.user.data2 = nullptr; recompui::queue_event(set_stick_return_event); - - set_scanned_input({(uint32_t)InputType::ControllerAnalog, axis_event->axis + 1}); + + set_scanned_input({ (uint32_t)InputType::ControllerAnalog, axis_event->axis + 1 }); } else if (axis_value < -axis_threshold) { SDL_Event set_stick_return_event; @@ -228,9 +231,10 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) { set_stick_return_event.user.data2 = nullptr; recompui::queue_event(set_stick_return_event); - set_scanned_input({(uint32_t)InputType::ControllerAnalog, -axis_event->axis - 1}); + set_scanned_input({ (uint32_t)InputType::ControllerAnalog, -axis_event->axis - 1 }); } - } else { + } + else { queue_if_enabled(event); } break; @@ -276,6 +280,22 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) { InputState.pending_mouse_delta[0] += motion_event->xrel; InputState.pending_mouse_delta[1] += motion_event->yrel; } + queue_if_enabled(event); + break; + case SDL_EventType::SDL_DROPBEGIN: + DropState.files_dropped.clear(); + break; + case SDL_EventType::SDL_DROPFILE: + DropState.files_dropped.emplace_back(std::filesystem::path(std::u8string_view((const char8_t*)(event->drop.file)))); + SDL_free(event->drop.file); + break; + case SDL_EventType::SDL_DROPCOMPLETE: + recompui::drop_files(DropState.files_dropped); + break; + case SDL_EventType::SDL_CONTROLLERBUTTONUP: + // Always queue button up events to avoid missing them during binding. + recompui::queue_event(*event); + break; default: queue_if_enabled(event); break; @@ -285,6 +305,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) { void recomp::handle_events() { SDL_Event cur_event; + static bool started = false; static bool exited = false; while (SDL_PollEvent(&cur_event) && !exited) { exited = sdl_event_filter(nullptr, &cur_event); @@ -301,6 +322,11 @@ void recomp::handle_events() { SDL_ShowCursor(cursor_visible ? SDL_ENABLE : SDL_DISABLE); SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE); } + + if (!started && ultramodern::is_game_started()) { + started = true; + recompui::process_game_started(); + } } constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A; @@ -465,7 +491,7 @@ void recomp::poll_inputs() { // Read the deltas while resetting them to zero. { std::lock_guard lock{ InputState.pending_input_mutex }; - + InputState.rotation_delta = InputState.pending_rotation_delta; InputState.pending_rotation_delta = { 0.0f, 0.0f }; @@ -482,14 +508,14 @@ void recomp::set_rumble(int controller_num, bool on) { ultramodern::input::connected_device_info_t recomp::get_connected_device_info(int controller_num) { switch (controller_num) { - case 0: - return ultramodern::input::connected_device_info_t { - .connected_device = ultramodern::input::Device::Controller, - .connected_pak = ultramodern::input::Pak::RumblePak, - }; + case 0: + return ultramodern::input::connected_device_info_t{ + .connected_device = ultramodern::input::Device::Controller, + .connected_pak = ultramodern::input::Pak::RumblePak, + }; } - return ultramodern::input::connected_device_info_t { + return ultramodern::input::connected_device_info_t{ .connected_device = ultramodern::input::Device::None, .connected_pak = ultramodern::input::Pak::None, }; @@ -506,7 +532,8 @@ void recomp::update_rumble() { if (InputState.rumble_active) { InputState.cur_rumble += 0.17f; if (InputState.cur_rumble > 1) InputState.cur_rumble = 1; - } else { + } + else { InputState.cur_rumble *= 0.92f; InputState.cur_rumble -= 0.01f; if (InputState.cur_rumble < 0) InputState.cur_rumble = 0; @@ -647,13 +674,13 @@ void recomp::get_mouse_deltas(float* x, float* y) { void recomp::apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out) { float joystick_deadzone = (float)recomp::get_joystick_deadzone() / 100.0f; - if(fabsf(x_in) < joystick_deadzone) { + if (fabsf(x_in) < joystick_deadzone) { x_in = 0.0f; } else { - if(x_in > 0.0f) { + if (x_in > 0.0f) { x_in -= joystick_deadzone; - } + } else { x_in += joystick_deadzone; } @@ -661,13 +688,13 @@ void recomp::apply_joystick_deadzone(float x_in, float y_in, float* x_out, float x_in /= (1.0f - joystick_deadzone); } - if(fabsf(y_in) < joystick_deadzone) { + if (fabsf(y_in) < joystick_deadzone) { y_in = 0.0f; } else { - if(y_in > 0.0f) { + if (y_in > 0.0f) { y_in -= joystick_deadzone; - } + } else { y_in += joystick_deadzone; } @@ -695,7 +722,7 @@ void recomp::set_right_analog_suppressed(bool suppressed) { bool recomp::game_input_disabled() { // Disable input if any menu that blocks input is open. - return recompui::is_context_taking_input(); + return recompui::is_context_capturing_input(); } bool recomp::all_input_disabled() { @@ -735,16 +762,16 @@ std::string controller_button_to_string(SDL_GameControllerButton button) { return PF_DPAD_LEFT; case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return PF_DPAD_RIGHT; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1: - // return ""; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1: - // return ""; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2: - // return ""; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3: - // return ""; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4: - // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1: + // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1: + // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2: + // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3: + // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4: + // return ""; case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_TOUCHPAD: return PF_SONY_TOUCHPAD; default: @@ -752,7 +779,7 @@ std::string controller_button_to_string(SDL_GameControllerButton button) { } } -std::unordered_map scancode_codepoints { +std::unordered_map scancode_codepoints{ {SDL_SCANCODE_LEFT, PF_KEYBOARD_LEFT}, // NOTE: UP and RIGHT are swapped with promptfont. {SDL_SCANCODE_UP, PF_KEYBOARD_RIGHT}, @@ -856,15 +883,15 @@ std::string controller_axis_to_string(int axis) { std::string recomp::InputField::to_string() const { switch ((InputType)input_type) { - case InputType::None: - return ""; - case InputType::ControllerDigital: - return controller_button_to_string((SDL_GameControllerButton)input_id); - case InputType::ControllerAnalog: - return controller_axis_to_string(input_id); - case InputType::Keyboard: - return keyboard_input_to_string((SDL_Scancode)input_id); - default: - return std::to_string(input_type) + "," + std::to_string(input_id); + case InputType::None: + return ""; + case InputType::ControllerDigital: + return controller_button_to_string((SDL_GameControllerButton)input_id); + case InputType::ControllerAnalog: + return controller_axis_to_string(input_id); + case InputType::Keyboard: + return keyboard_input_to_string((SDL_Scancode)input_id); + default: + return std::to_string(input_type) + "," + std::to_string(input_id); } } diff --git a/src/game/recomp_mem_api.cpp b/src/game/recomp_actor_api.cpp similarity index 98% rename from src/game/recomp_mem_api.cpp rename to src/game/recomp_actor_api.cpp index 6ee6a05..8cbe5a6 100644 --- a/src/game/recomp_mem_api.cpp +++ b/src/game/recomp_actor_api.cpp @@ -8,7 +8,7 @@ #include "ultramodern/error_handling.hpp" #include "recomp_ui.h" #include "recomp_data.h" -#include "../patches/mem_funcs.h" +#include "../patches/actor_funcs.h" struct ExtensionInfo { // Either the actor's type ID, or 0xFFFFFFFF if this is for generic data. @@ -41,7 +41,7 @@ bool can_register = false; size_t alloc_count = 0; size_t free_count = 0; -void recomp::init_extended_actor_data() { +void recomputil::init_extended_actor_data() { std::lock_guard lock{ actor_data_mutex }; actor_data_sizes.clear(); @@ -54,7 +54,7 @@ void recomp::init_extended_actor_data() { actor_extensions.push_back({}); } -void recomp::reset_actor_data() { +void recomputil::reset_actor_data() { std::lock_guard lock{ actor_data_mutex }; actor_data.reset(); actor_spawn_count = 0; @@ -113,7 +113,7 @@ extern "C" void recomp_register_actor_extension_generic(uint8_t* rdram, recomp_c extern "C" void recomp_clear_all_actor_data(uint8_t* rdram, recomp_context* ctx) { (void)rdram; (void)ctx; - recomp::reset_actor_data(); + recomputil::reset_actor_data(); } extern "C" void recomp_create_actor_data(uint8_t* rdram, recomp_context* ctx) { diff --git a/src/game/recomp_data_api.cpp b/src/game/recomp_data_api.cpp new file mode 100644 index 0000000..2557abf --- /dev/null +++ b/src/game/recomp_data_api.cpp @@ -0,0 +1,727 @@ +#include +#include +#include +#include + +#include "slot_map.h" +#include "recomp_data.h" +#include "recomp_ui.h" +#include "librecomp/helpers.hpp" +#include "librecomp/overlays.hpp" +#include "librecomp/addresses.hpp" +#include "ultramodern/error_handling.hpp" + +template +class LockedMap { +private: + std::mutex mutex{}; + std::unordered_map map{}; +public: + bool get(const KeyType& key, ValueType& out) { + std::lock_guard lock{mutex}; + auto find_it = map.find(key); + if (find_it == map.end()) { + return false; + } + out = find_it->second; + return true; + } + + bool insert(const KeyType& key, ValueType val) { + std::lock_guard lock{mutex}; + auto ret = map.insert_or_assign(key, val); + return ret.second; + } + + bool erase(const KeyType& key) { + std::lock_guard lock{mutex}; + size_t num_erased = map.erase(key); + return num_erased != 0; + } + + void clear() { + std::lock_guard lock{mutex}; + map.clear(); + } + + bool erase_first(ValueType& out) { + std::lock_guard lock{mutex}; + auto it = map.begin(); + + if (it == map.end()) { + return false; + } + + out = it->second; + map.erase(it); + return true; + } + + bool contains(const KeyType& key) { + std::lock_guard lock{mutex}; + + return map.contains(key); + } + + size_t size() { + std::lock_guard lock{mutex}; + + return map.size(); + } +}; + +template +class LockedSet { +private: + std::mutex mutex{}; + std::unordered_set set{}; +public: + bool contains(const KeyType& key) { + std::lock_guard lock{mutex}; + return set.contains(key); + } + + bool insert(const KeyType& key) { + std::lock_guard lock{mutex}; + auto it = set.insert(key); + return it.second; + } + + bool erase(const KeyType& key) { + std::lock_guard lock{mutex}; + size_t num_erased = set.erase(key); + return num_erased != 0; + } + + void clear() { + std::lock_guard lock{mutex}; + set.clear(); + } + + size_t size() { + std::lock_guard lock{mutex}; + return set.size(); + } +}; + +template +class LockedSlotmap { +private: + std::mutex mutex{}; + dod::slot_map32 map{}; + using key_t = typename dod::slot_map32::key; +public: + bool get(uint32_t key, ValueType** out) { + std::lock_guard lock{mutex}; + ValueType* ret = map.get(key_t{key}); + *out = ret; + return ret != nullptr; + } + + uint32_t create() { + std::lock_guard lock{mutex}; + return map.emplace().raw; + } + + bool erase(uint32_t key) { + std::lock_guard lock{mutex}; + if (!map.has_key(key_t{ key })) { + return false; + } + + map.erase(key_t{ key }); + return true; + } + + void clear() { + std::lock_guard lock{mutex}; + map.clear(); + } + + bool erase_first(ValueType& out) { + std::lock_guard lock{mutex}; + auto it = map.items().begin(); + + if (it == map.items().end()) { + return false; + } + + out = it->second; + map.erase(it->first); + return true; + } + + size_t size() { + std::lock_guard lock{mutex}; + + return map.size(); + } +}; + +using U32ValueMap = LockedMap; +using U32MemoryMap = std::pair, u32>; +using U32HashSet = LockedSet; +using U32Slotmap = LockedSlotmap; +using MemorySlotmap = std::pair, u32>; + +LockedSlotmap u32_value_hashmaps{}; +LockedSlotmap u32_memory_hashmaps{}; +LockedSlotmap u32_hashsets{}; +LockedSlotmap u32_slotmaps{}; +LockedSlotmap memory_slotmaps{}; + +#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name) + +static void show_fatal_error_message_box(const char* funcname, const char* errstr) { + std::string message = std::string{"Fatal error in mod - "} + funcname + " : " + errstr; + recompui::message_box(message.c_str()); +} + +#define HANDLE_INVALID_ERROR() \ + show_fatal_error_message_box(__FUNCTION__, "handle is invalid"); \ + assert(false); \ + ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); + +#define SLOTMAP_KEY_INVALID_ERROR() \ + show_fatal_error_message_box(__FUNCTION__, "slotmap key is invalid"); \ + assert(false); \ + ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); + +// u32 -> 32-bit value hashmap. + +void recomputil_create_u32_value_hashmap(uint8_t* rdram, recomp_context* ctx) { + (void)rdram; + _return(ctx, u32_value_hashmaps.create()); +} + +void recomputil_destroy_u32_value_hashmap(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + if (!u32_value_hashmaps.erase(mapkey)) { + HANDLE_INVALID_ERROR(); + } +} + +void recomputil_u32_value_hashmap_contains(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32ValueMap* map; + if (!u32_value_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, map->contains(key)); +} + +void recomputil_u32_value_hashmap_insert(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + uint32_t value = _arg<2, uint32_t>(rdram, ctx); + + U32ValueMap* map; + if (!u32_value_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, map->insert(key, value)); +} + +void recomputil_u32_value_hashmap_get(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx); + + U32ValueMap* map; + if (!u32_value_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + uint32_t ret; + if (map->get(key, ret)) { + MEM_W(0, val_out) = ret; + _return(ctx, 1); + return; + } + else { + _return(ctx, 0); + return; + } +} + +void recomputil_u32_value_hashmap_erase(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32ValueMap* map; + if (!u32_value_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, map->erase(key)); +} + +void recomputil_u32_value_hashmap_size(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + U32ValueMap* map; + if (!u32_value_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, static_cast(map->size())); +} + +// u32 -> memory hashmap. + +void recomputil_create_u32_memory_hashmap(uint8_t* rdram, recomp_context* ctx) { + uint32_t element_size = _arg<0, uint32_t>(rdram, ctx); + + // Create the map. + uint32_t map_key = u32_memory_hashmaps.create(); + + // Retrieve the map and set its element size to the provided value. + U32MemoryMap* map; + u32_memory_hashmaps.get(map_key, &map); + map->second = element_size; + + // Return the created map's key. + _return(ctx, map_key); +} + +void recomputil_destroy_u32_memory_hashmap(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + // Retrieve the map. + U32MemoryMap* map; + if (!u32_memory_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + // Free all of the entries in the map. + PTR(void) cur_mem; + while (map->first.erase_first(cur_mem)) { + recomp::free(rdram, TO_PTR(void, cur_mem)); + } + + // Destroy the map itself. + u32_memory_hashmaps.erase(mapkey); +} + +void recomputil_u32_memory_hashmap_contains(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32MemoryMap* map; + if (!u32_memory_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, map->first.contains(key)); +} + +void recomputil_u32_memory_hashmap_create(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32MemoryMap* map; + if (!u32_memory_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + // Check if the map contains the key already to prevent inserting it twice. + PTR(void) dummy; + if (map->first.get(key, dummy)) { + _return(ctx, 0); + return; + } + + // Allocate the map's size and return the pointer. + void* mem = recomp::alloc(rdram, map->second); + gpr addr = reinterpret_cast(mem) - rdram + 0xFFFFFFFF80000000ULL; + + // Zero the memory. + for (size_t i = 0; i < map->second; i++) { + MEM_B(i, addr) = 0; + } + + PTR(void) ret = static_cast(addr); + map->first.insert(key, ret); + _return(ctx, 1); +} + +void recomputil_u32_memory_hashmap_get(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32MemoryMap* map; + if (!u32_memory_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + PTR(void) ret; + if (map->first.get(key, ret)) { + _return(ctx, ret); + return; + } + else { + _return(ctx, NULLPTR); + return; + } +} + +void recomputil_u32_memory_hashmap_erase(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32MemoryMap* map; + if (!u32_memory_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + // Free the memory for this key if the key exists. + PTR(void) addr; + bool has_value = map->first.get(key, addr); + if (has_value) { + void* mem = TO_PTR(void, addr); + recomp::free(rdram, mem); + } + + _return(ctx, map->first.erase(key)); +} + +void recomputil_u32_memory_hashmap_size(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + U32MemoryMap* map; + if (!u32_memory_hashmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, static_cast(map->first.size())); +} + +// u32 hashset. + +void recomputil_create_u32_hashset(uint8_t* rdram, recomp_context* ctx) { + (void)rdram; + _return(ctx, u32_hashsets.create()); +} + +void recomputil_destroy_u32_hashset(uint8_t* rdram, recomp_context* ctx) { + uint32_t setkey = _arg<0, uint32_t>(rdram, ctx); + + if (!u32_hashsets.erase(setkey)) { + HANDLE_INVALID_ERROR(); + } +} + +void recomputil_u32_hashset_contains(uint8_t* rdram, recomp_context* ctx) { + uint32_t setkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32HashSet* set; + if (!u32_hashsets.get(setkey, &set)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, set->contains(key)); +} + +void recomputil_u32_hashset_insert(uint8_t* rdram, recomp_context* ctx) { + uint32_t setkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32HashSet* set; + if (!u32_hashsets.get(setkey, &set)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, set->insert(key)); +} + +void recomputil_u32_hashset_erase(uint8_t* rdram, recomp_context* ctx) { + uint32_t setkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32HashSet* set; + if (!u32_hashsets.get(setkey, &set)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, set->erase(key)); +} + +void recomputil_u32_hashset_size(uint8_t* rdram, recomp_context* ctx) { + uint32_t setkey = _arg<0, uint32_t>(rdram, ctx); + + U32HashSet* set; + if (!u32_hashsets.get(setkey, &set)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, static_cast(set->size())); +} + +// u32 value slotmap. + +void recomputil_create_u32_slotmap(uint8_t* rdram, recomp_context* ctx) { + (void)rdram; + _return(ctx, u32_slotmaps.create()); +} + +void recomputil_destroy_u32_slotmap(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + if (!u32_slotmaps.erase(mapkey)) { + HANDLE_INVALID_ERROR(); + } +} + +void recomputil_u32_slotmap_contains(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32Slotmap* map; + if (!u32_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + uint32_t* dummy_ptr; + _return(ctx, map->get(key, &dummy_ptr)); +} + +void recomputil_u32_slotmap_create(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + U32Slotmap* map; + if (!u32_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, map->create()); +} + +void recomputil_u32_slotmap_get(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx); + + U32Slotmap* map; + if (!u32_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + uint32_t* ret; + if (!map->get(key, &ret)) { + _return(ctx, 0); + } + MEM_W(0, val_out) = *ret; + _return(ctx, 1); +} + +void recomputil_u32_slotmap_set(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + uint32_t value = _arg<2, uint32_t>(rdram, ctx); + + U32Slotmap* map; + if (!u32_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + uint32_t* value_ptr; + if (!map->get(key, &value_ptr)) { + _return(ctx, 0); + } + + *value_ptr = value; + _return(ctx, 1); +} + +void recomputil_u32_slotmap_erase(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + U32Slotmap* map; + if (!u32_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + if (!map->erase(key)) { + _return(ctx, 0); + } + + _return(ctx, 1); +} + +void recomputil_u32_slotmap_size(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + U32Slotmap* map; + if (!u32_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, static_cast(map->size())); +} + +// memory slotmap. + +void recomputil_create_memory_slotmap(uint8_t* rdram, recomp_context* ctx) { + (void)rdram; + _return(ctx, memory_slotmaps.create()); +} + +void recomputil_destroy_memory_slotmap(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + // Retrieve the map. + MemorySlotmap* map; + if (!memory_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + // Free all of the entries in the map. + PTR(void) cur_mem; + while (map->first.erase_first(cur_mem)) { + recomp::free(rdram, TO_PTR(void, cur_mem)); + } + + // Destroy the map itself. + memory_slotmaps.erase(mapkey); +} + +void recomputil_memory_slotmap_contains(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + MemorySlotmap* map; + if (!memory_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + PTR(void)* dummy_ptr; + _return(ctx, map->first.get(key, &dummy_ptr)); +} + +void recomputil_memory_slotmap_create(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + MemorySlotmap* map; + if (!memory_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + // Create the slotmap element. + u32 key = map->first.create(); + + // Allocate the map's element size. + void* mem = recomp::alloc(rdram, map->second); + gpr addr = reinterpret_cast(mem) - rdram + 0xFFFFFFFF80000000ULL; + + // Zero the memory. + for (size_t i = 0; i < map->second; i++) { + MEM_B(i, addr) = 0; + } + + // Store the allocated pointer. + PTR(void)* value_ptr; + map->first.get(key, &value_ptr); + MEM_W(0, *value_ptr) = addr; + + // Return the key. + _return(ctx, key); +} + +void recomputil_memory_slotmap_get(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx); + + MemorySlotmap* map; + if (!memory_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + PTR(void)* ret; + if (!map->first.get(key, &ret)) { + SLOTMAP_KEY_INVALID_ERROR(); + } + MEM_W(0, val_out) = *ret; +} + +void recomputil_memory_slotmap_erase(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + uint32_t key = _arg<1, uint32_t>(rdram, ctx); + + MemorySlotmap* map; + if (!memory_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + // Free the memory for this key if the key exists. + PTR(void)* addr; + bool has_value = map->first.get(key, &addr); + if (has_value) { + void* mem = TO_PTR(void, addr); + recomp::free(rdram, mem); + } + + _return(ctx, map->first.erase(key)); +} + +void recomputil_memory_slotmap_size(uint8_t* rdram, recomp_context* ctx) { + uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx); + + MemorySlotmap* map; + if (!memory_slotmaps.get(mapkey, &map)) { + HANDLE_INVALID_ERROR(); + } + + _return(ctx, static_cast(map->first.size())); +} + +// Exports. + +void recomputil::register_data_api_exports() { + REGISTER_FUNC(recomputil_create_u32_value_hashmap); + REGISTER_FUNC(recomputil_destroy_u32_value_hashmap); + REGISTER_FUNC(recomputil_u32_value_hashmap_contains); + REGISTER_FUNC(recomputil_u32_value_hashmap_insert); + REGISTER_FUNC(recomputil_u32_value_hashmap_get); + REGISTER_FUNC(recomputil_u32_value_hashmap_erase); + REGISTER_FUNC(recomputil_u32_value_hashmap_size); + + REGISTER_FUNC(recomputil_create_u32_memory_hashmap); + REGISTER_FUNC(recomputil_destroy_u32_memory_hashmap); + REGISTER_FUNC(recomputil_u32_memory_hashmap_contains); + REGISTER_FUNC(recomputil_u32_memory_hashmap_create); + REGISTER_FUNC(recomputil_u32_memory_hashmap_get); + REGISTER_FUNC(recomputil_u32_memory_hashmap_erase); + REGISTER_FUNC(recomputil_u32_memory_hashmap_size); + + REGISTER_FUNC(recomputil_create_u32_hashset); + REGISTER_FUNC(recomputil_destroy_u32_hashset); + REGISTER_FUNC(recomputil_u32_hashset_contains); + REGISTER_FUNC(recomputil_u32_hashset_insert); + REGISTER_FUNC(recomputil_u32_hashset_erase); + REGISTER_FUNC(recomputil_u32_hashset_size); + + REGISTER_FUNC(recomputil_create_u32_slotmap); + REGISTER_FUNC(recomputil_destroy_u32_slotmap); + REGISTER_FUNC(recomputil_u32_slotmap_contains); + REGISTER_FUNC(recomputil_u32_slotmap_create); + REGISTER_FUNC(recomputil_u32_slotmap_get); + REGISTER_FUNC(recomputil_u32_slotmap_set); + REGISTER_FUNC(recomputil_u32_slotmap_erase); + REGISTER_FUNC(recomputil_u32_slotmap_size); + + REGISTER_FUNC(recomputil_create_memory_slotmap); + REGISTER_FUNC(recomputil_destroy_memory_slotmap); + REGISTER_FUNC(recomputil_memory_slotmap_contains); + REGISTER_FUNC(recomputil_memory_slotmap_create); + REGISTER_FUNC(recomputil_memory_slotmap_get); + REGISTER_FUNC(recomputil_memory_slotmap_erase); + REGISTER_FUNC(recomputil_memory_slotmap_size); +} diff --git a/src/main/main.cpp b/src/main/main.cpp index 6752be8..7829b87 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -25,6 +25,7 @@ #include "banjo_config.h" #include "banjo_sound.h" #include "banjo_render.h" +#include "banjo_support.h" #include "banjo_game.h" #include "recomp_data.h" #include "ovl_patches.hpp" @@ -45,14 +46,15 @@ #include "../../lib/rt64/src/contrib/stb/stb_image.h" -const std::string version_string = "0.0.1"; +const std::string version_string = "0.1.0"; template void exit_error(const char* str, Ts ...args) { // TODO pop up an error ((void)fprintf(stderr, str, args), ...); assert(false); - std::quick_exit(EXIT_FAILURE); + + ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); } ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() { @@ -126,11 +128,13 @@ SDL_Window* window; ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) { uint32_t flags = SDL_WINDOW_RESIZABLE; -#if defined(RT64_SDL_WINDOW_VULKAN) +#if defined(__APPLE__) + flags |= SDL_WINDOW_METAL; +#elif defined(RT64_SDL_WINDOW_VULKAN) flags |= SDL_WINDOW_VULKAN; #endif - window = SDL_CreateWindow("Banjo: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, flags); + window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, flags); #if defined(__linux__) SetImageAsIcon("icons/512.png",window); if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux @@ -152,6 +156,9 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t:: return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() }; #elif defined(__linux__) || defined(__ANDROID__) return ultramodern::renderer::WindowHandle{ window }; +#elif defined(__APPLE__) + SDL_MetalView view = SDL_Metal_CreateView(window); + return ultramodern::renderer::WindowHandle{ wmInfo.info.cocoa.window, SDL_Metal_GetLayer(view) }; #else static_assert(false && "Unimplemented"); #endif @@ -503,15 +510,17 @@ void release_preload(PreloadContext& context) { #endif void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) { - (void)context; - banjo::renderer::enable_texture_pack(mod); + banjo::renderer::enable_texture_pack(context, mod); } -void disable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) { - (void)context; +void disable_texture_pack(recomp::mods::ModContext&, const recomp::mods::ModHandle& mod) { banjo::renderer::disable_texture_pack(mod); } +void reorder_texture_pack(recomp::mods::ModContext&) { + banjo::renderer::trigger_texture_pack_update(); +} + #define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name) int main(int argc, char** argv) { @@ -555,14 +564,22 @@ int main(int argc, char** argv) { // Force wasapi on Windows, as there seems to be some issue with sample queueing with directsound currently. SDL_setenv("SDL_AUDIODRIVER", "wasapi", true); #endif - //printf("Current dir: %ls\n", std::filesystem::current_path().c_str()); + +#if defined(__linux__) && defined(RECOMP_FLATPAK) + // When using Flatpak, applications tend to launch from the home directory by default. + // Mods might use the current working directory to store the data, so we switch it to a directory + // with persistent data storage and write permissions under Flatpak to ensure it works. + std::error_code ec; + std::filesystem::current_path("/var/data", ec); +#endif // Initialize SDL audio and set the output frequency. SDL_InitSubSystem(SDL_INIT_AUDIO); reset_audio(48000); // Source controller mappings file - if (SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt") < 0) { + std::u8string controller_db_path = (banjo::get_program_path() / "recompcontrollerdb.txt").u8string(); + if (SDL_GameControllerAddMappingsFromFile(reinterpret_cast(controller_db_path.c_str())) < 0) { fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError()); } @@ -584,10 +601,11 @@ int main(int argc, char** argv) { REGISTER_FUNC(recomp_get_inverted_axes); REGISTER_FUNC(recomp_get_analog_inverted_axes); recompui::register_ui_exports(); + recomputil::register_data_api_exports(); banjo::register_bk_overlays(); banjo::register_bk_patches(); - recomp::init_extended_actor_data(); + recomputil::init_extended_actor_data(); banjo::load_config(); recomp::rsp::callbacks_t rsp_callbacks{ @@ -636,6 +654,7 @@ int main(int argc, char** argv) { .allow_runtime_toggle = true, .on_enabled = enable_texture_pack, .on_disabled = disable_texture_pack, + .on_reordered = reorder_texture_pack, }; auto texture_pack_content_type_id = recomp::mods::register_mod_content_type(texture_pack_content_type); diff --git a/src/main/rt64_render_context.cpp b/src/main/rt64_render_context.cpp index ac2b719..1dcf2d5 100644 --- a/src/main/rt64_render_context.cpp +++ b/src/main/rt64_render_context.cpp @@ -1,10 +1,12 @@ #include #include #include +#include #define HLSL_CPU #include "hle/rt64_application.h" #include "rt64_render_hooks.h" +#include "overloaded.h" #include "ultramodern/ultramodern.hpp" #include "ultramodern/config.hpp" @@ -13,12 +15,6 @@ #include "recomp_ui.h" #include "concurrentqueue.h" -// Helper class for variant visiting. -template -struct overloaded : Ts... { using Ts::operator()...; }; -template -overloaded(Ts...) -> overloaded; - static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None; static bool sample_positions_supported = false; static bool high_precision_fb_enabled = false; @@ -27,14 +23,25 @@ static uint8_t DMEM[0x1000]; static uint8_t IMEM[0x1000]; struct TexturePackEnableAction { - std::filesystem::path path; + std::string mod_id; }; struct TexturePackDisableAction { - std::filesystem::path path; + std::string mod_id; }; -using TexturePackAction = std::variant; +struct TexturePackSecondaryEnableAction { + std::string mod_id; +}; + +struct TexturePackSecondaryDisableAction { + std::string mod_id; +}; + +struct TexturePackUpdateAction { +}; + +using TexturePackAction = std::variant; static moodycamel::ConcurrentQueue texture_pack_action_queue; @@ -171,6 +178,7 @@ void set_application_user_config(RT64::Application* application, const ultramode application->userConfig.refreshRate = to_rt64(config.rr_option); application->userConfig.refreshRateTarget = config.rr_manual_value; application->userConfig.internalColorFormat = to_rt64(config.hpfb_option); + application->userConfig.displayBuffering = RT64::UserConfiguration::DisplayBuffering::Triple; } ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) { @@ -192,6 +200,23 @@ ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResu std::exit(EXIT_FAILURE); } +ultramodern::renderer::GraphicsApi map_graphics_api(RT64::UserConfiguration::GraphicsAPI api) { + switch (api) { + case RT64::UserConfiguration::GraphicsAPI::D3D12: + return ultramodern::renderer::GraphicsApi::D3D12; + case RT64::UserConfiguration::GraphicsAPI::Vulkan: + return ultramodern::renderer::GraphicsApi::Vulkan; + case RT64::UserConfiguration::GraphicsAPI::Metal: + return ultramodern::renderer::GraphicsApi::Metal; + case RT64::UserConfiguration::GraphicsAPI::Automatic: + return ultramodern::renderer::GraphicsApi::Auto; + } + + fprintf(stderr, "Unhandled `RT64::UserConfiguration::GraphicsAPI` ?\n"); + assert(false); + std::exit(EXIT_FAILURE); +} + banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) { static unsigned char dummy_rom_header[0x40]; recompui::set_render_hooks(); @@ -263,9 +288,11 @@ banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer: case ultramodern::renderer::GraphicsApi::Vulkan: app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan; break; - default: + case ultramodern::renderer::GraphicsApi::Metal: + app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Metal; + break; case ultramodern::renderer::GraphicsApi::Auto: - // Don't override if auto is selected. + app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Automatic; break; } @@ -275,6 +302,8 @@ banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer: thread_id = window_handle.thread_id; #endif setup_result = map_setup_result(app->setup(thread_id)); + // Get the API that RT64 chose. + chosen_api = map_graphics_api(app->chosenGraphicsAPI); if (setup_result != ultramodern::renderer::SetupResult::Success) { app = nullptr; return; @@ -303,32 +332,7 @@ banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer: banjo::renderer::RT64Context::~RT64Context() = default; void banjo::renderer::RT64Context::send_dl(const OSTask* task) { - bool packs_disabled = false; - TexturePackAction cur_action; - while (texture_pack_action_queue.try_dequeue(cur_action)) { - std::visit(overloaded{ - [&](TexturePackDisableAction& to_disable) { - enabled_texture_packs.erase(to_disable.path); - packs_disabled = true; - }, - [&](TexturePackEnableAction& to_enable) { - enabled_texture_packs.insert(to_enable.path); - // Load the pack now if no packs have been disabled. - if (!packs_disabled) { - app->textureCache->loadReplacementDirectory(to_enable.path); - } - } - }, cur_action); - } - - // If any packs were disabled, unload all packs and load all the active ones. - if (packs_disabled) { - app->textureCache->clearReplacementDirectories(); - for (const std::filesystem::path& cur_pack_path : enabled_texture_packs) { - app->textureCache->loadReplacementDirectory(cur_pack_path); - } - } - + check_texture_pack_actions(); 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); @@ -394,6 +398,66 @@ float banjo::renderer::RT64Context::get_resolution_scale() const { } } +void banjo::renderer::RT64Context::check_texture_pack_actions() { + bool packs_changed = false; + TexturePackAction cur_action; + while (texture_pack_action_queue.try_dequeue(cur_action)) { + std::visit(overloaded{ + [&](TexturePackDisableAction &to_disable) { + enabled_texture_packs.erase(to_disable.mod_id); + packs_changed = true; + }, + [&](TexturePackEnableAction &to_enable) { + enabled_texture_packs.insert(to_enable.mod_id); + packs_changed = true; + }, + [&](TexturePackSecondaryDisableAction &to_override_disable) { + secondary_disabled_texture_packs.insert(to_override_disable.mod_id); + packs_changed = true; + }, + [&](TexturePackSecondaryEnableAction &to_override_enable) { + secondary_disabled_texture_packs.erase(to_override_enable.mod_id); + packs_changed = true; + }, + [&](TexturePackUpdateAction &) { + packs_changed = true; + } + }, cur_action); + } + + // If any packs were disabled, unload all packs and load all the active ones. + if (packs_changed) { + // Sort the enabled texture packs in reverse order so that earlier ones override later ones. + std::vector sorted_texture_packs{}; + sorted_texture_packs.reserve(enabled_texture_packs.size()); + for (const std::string& mod : enabled_texture_packs) { + if (!secondary_disabled_texture_packs.contains(mod)) { + sorted_texture_packs.emplace_back(mod); + } + } + + std::sort(sorted_texture_packs.begin(), sorted_texture_packs.end(), + [](const std::string& lhs, const std::string& rhs) { + return recomp::mods::get_mod_order_index(lhs) > recomp::mods::get_mod_order_index(rhs); + } + ); + + // Build the path list from the sorted mod list. + std::vector replacement_directories; + replacement_directories.reserve(enabled_texture_packs.size()); + for (const std::string &mod_id : sorted_texture_packs) { + replacement_directories.emplace_back(RT64::ReplacementDirectory(recomp::mods::get_mod_filename(mod_id))); + } + + if (!replacement_directories.empty()) { + app->textureCache->loadReplacementDirectories(replacement_directories); + } + else { + app->textureCache->clearReplacementDirectories(); + } + } +} + RT64::UserConfiguration::Antialiasing banjo::renderer::RT64MaxMSAA() { return device_max_msaa; } @@ -410,10 +474,72 @@ bool banjo::renderer::RT64HighPrecisionFBEnabled() { return high_precision_fb_enabled; } -void banjo::renderer::enable_texture_pack(const recomp::mods::ModHandle& mod) { - texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_root_path}); +void banjo::renderer::trigger_texture_pack_update() { + texture_pack_action_queue.enqueue(TexturePackUpdateAction{}); +} + +void banjo::renderer::enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) { + texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_id}); + + // Check for the texture pack enabled config option. + const recomp::mods::ConfigSchema& config_schema = context.get_mod_config_schema(mod.manifest.mod_id); + auto find_it = config_schema.options_by_id.find(banjo::renderer::special_option_texture_pack_enabled); + if (find_it != config_schema.options_by_id.end()) { + const recomp::mods::ConfigOption& config_option = config_schema.options[find_it->second]; + + if (is_texture_pack_enable_config_option(config_option, false)) { + recomp::mods::ConfigValueVariant value_variant = context.get_mod_config_value(mod.manifest.mod_id, config_option.id); + uint32_t value; + if (uint32_t* value_ptr = std::get_if(&value_variant)) { + value = *value_ptr; + } + else { + value = 0; + } + + if (value) { + banjo::renderer::secondary_enable_texture_pack(mod.manifest.mod_id); + } + else { + banjo::renderer::secondary_disable_texture_pack(mod.manifest.mod_id); + } + } + } } void banjo::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) { - texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_root_path}); + texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_id}); +} + +void banjo::renderer::secondary_enable_texture_pack(const std::string& mod_id) { + texture_pack_action_queue.enqueue(TexturePackSecondaryEnableAction{mod_id}); +} + +void banjo::renderer::secondary_disable_texture_pack(const std::string& mod_id) { + texture_pack_action_queue.enqueue(TexturePackSecondaryDisableAction{mod_id}); +} + + +// HD texture enable option. Must be an enum with two options. +// The first option is treated as disabled and the second option is treated as enabled. +bool banjo::renderer::is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors) { + if (option.id == banjo::renderer::special_option_texture_pack_enabled) { + if (option.type != recomp::mods::ConfigOptionType::Enum) { + if (show_errors) { + recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + banjo::renderer::special_option_texture_pack_enabled + "\"), but the config option is not an enum.").c_str()); + } + return false; + } + + const recomp::mods::ConfigOptionEnum &option_enum = std::get(option.variant); + if (option_enum.options.size() != 2) { + if (show_errors) { + recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + banjo::renderer::special_option_texture_pack_enabled + "\"), but the config option doesn't have exactly 2 values.").c_str()); + } + return false; + } + + return true; + } + return false; } diff --git a/src/main/support.cpp b/src/main/support.cpp new file mode 100644 index 0000000..fb4649a --- /dev/null +++ b/src/main/support.cpp @@ -0,0 +1,94 @@ +#include "banjo_support.h" +#include +#include "nfd.h" +#include "RmlUi/Core.h" + +namespace banjo { + // MARK: - Internal Helpers + void perform_file_dialog_operation(const std::function& callback) { + nfdnchar_t* native_path = nullptr; + nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr); + + bool success = (result == NFD_OKAY); + std::filesystem::path path; + + if (success) { + path = std::filesystem::path{native_path}; + NFD_FreePathN(native_path); + } + + callback(success, path); + } + + void perform_file_dialog_operation_multiple(const std::function&)>& callback) { + const nfdpathset_t* native_paths = nullptr; + nfdresult_t result = NFD_OpenDialogMultipleN(&native_paths, nullptr, 0, nullptr); + + bool success = (result == NFD_OKAY); + std::list paths; + nfdpathsetsize_t count = 0; + + if (success) { + NFD_PathSet_GetCount(native_paths, &count); + for (nfdpathsetsize_t i = 0; i < count; i++) { + nfdnchar_t* cur_path = nullptr; + nfdresult_t cur_result = NFD_PathSet_GetPathN(native_paths, i, &cur_path); + if (cur_result == NFD_OKAY) { + paths.emplace_back(std::filesystem::path{cur_path}); + } + } + NFD_PathSet_Free(native_paths); + } + + callback(success, paths); + } + + // MARK: - Public API + + std::filesystem::path get_program_path() { +#if defined(__APPLE__) + return get_bundle_resource_directory(); +#elif defined(__linux__) && defined(RECOMP_FLATPAK) + return "/app/bin"; +#else + return ""; +#endif + } + + std::filesystem::path get_asset_path(const char* asset) { + return get_program_path() / "assets" / asset; + } + + void open_file_dialog(std::function callback) { +#ifdef __APPLE__ + dispatch_on_ui_thread([callback]() { + perform_file_dialog_operation(callback); + }); +#else + perform_file_dialog_operation(callback); +#endif + } + + void open_file_dialog_multiple(std::function& paths)> callback) { +#ifdef __APPLE__ + dispatch_on_ui_thread([callback]() { + perform_file_dialog_operation_multiple(callback); + }); +#else + perform_file_dialog_operation_multiple(callback); +#endif + } + + void show_error_message_box(const char *title, const char *message) { +#ifdef __APPLE__ + std::string title_copy(title); + std::string message_copy(message); + + dispatch_on_ui_thread([title_copy, message_copy] { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title_copy.c_str(), message_copy.c_str(), nullptr); + }); +#else + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, message, nullptr); +#endif + } +} diff --git a/src/ui/core/ui_context.cpp b/src/ui/core/ui_context.cpp index b78a0b4..cff69fc 100644 --- a/src/ui/core/ui_context.cpp +++ b/src/ui/core/ui_context.cpp @@ -1,8 +1,10 @@ #include #include #include +#include #include "slot_map.h" +#include "RmlUi/Core/StreamMemory.h" #include "ultramodern/error_handling.hpp" #include "recomp_ui.h" @@ -32,8 +34,12 @@ namespace recompui { resource_slotmap resources; Rml::ElementDocument* document; Element root_element; + Element* autofocus_element = nullptr; std::vector loose_elements; std::unordered_set to_update; + std::vector> to_set_text; + bool captures_input = true; + bool captures_mouse = true; Context(Rml::ElementDocument* document) : document(document), root_element(document) {} }; } // namespace recompui @@ -41,10 +47,11 @@ namespace recompui { using context_slotmap = dod::slot_map32; static struct { - std::mutex all_contexts_lock; + std::recursive_mutex all_contexts_lock; context_slotmap all_contexts; std::unordered_set opened_contexts; std::unordered_map documents_to_contexts; + Rml::SharedPtr style_sheet; } context_state; thread_local recompui::Context* opened_context = nullptr; @@ -61,12 +68,16 @@ enum class ContextErrorType { AddResourceToWrongContext, UpdateElementWithoutContext, UpdateElementInWrongContext, + SetTextElementWithoutContext, + SetTextElementInWrongContext, GetResourceWithoutOpen, GetResourceFailed, DestroyResourceWithoutOpen, DestroyResourceInWrongContext, DestroyResourceNotFound, GetDocumentInvalidContext, + GetAutofocusInvalidContext, + SetAutofocusInvalidContext, InternalError, }; @@ -111,6 +122,12 @@ void context_error(recompui::ContextId id, ContextErrorType type) { case ContextErrorType::UpdateElementInWrongContext: error_message = "Attempted to update a UI element in a different UI context than the one that's open"; break; + case ContextErrorType::SetTextElementWithoutContext: + error_message = "Attempted to set the text of a UI element with no open UI context"; + break; + case ContextErrorType::SetTextElementInWrongContext: + error_message = "Attempted to set the text of a UI element in a different UI context than the one that's open"; + break; case ContextErrorType::GetResourceWithoutOpen: error_message = "Attempted to get a UI resource with no open UI context"; break; @@ -129,6 +146,12 @@ void context_error(recompui::ContextId id, ContextErrorType type) { case ContextErrorType::GetDocumentInvalidContext: error_message = "Attempted to get the document of an invalid UI context"; break; + case ContextErrorType::GetAutofocusInvalidContext: + error_message = "Attempted to get the autofocus element of an invalid UI context"; + break; + case ContextErrorType::SetAutofocusInvalidContext: + error_message = "Attempted to set the autofocus element of an invalid UI context"; + break; case ContextErrorType::InternalError: error_message = "Internal error in UI context"; break; @@ -166,6 +189,21 @@ recompui::ContextId create_context_impl(Rml::ElementDocument* document) { return ret; } +void recompui::init_styling(const std::filesystem::path& rcss_file) { + std::string style{}; + { + std::ifstream style_stream{rcss_file}; + style_stream.seekg(0, std::ios::end); + style.resize(style_stream.tellg()); + style_stream.seekg(0, std::ios::beg); + + style_stream.read(style.data(), style.size()); + } + std::unique_ptr rml_stream = std::make_unique(reinterpret_cast(style.data()), style.size()); + rml_stream->SetSourceURL(rcss_file.filename().string()); + context_state.style_sheet = Rml::Factory::InstanceStyleSheetStream(rml_stream.get()); +} + recompui::ContextId recompui::create_context(const std::filesystem::path& path) { ContextId new_context = create_context_impl(nullptr); @@ -193,29 +231,20 @@ recompui::ContextId recompui::create_context(Rml::ElementDocument* document) { recompui::ContextId recompui::create_context() { Rml::ElementDocument* doc = create_empty_document(); + doc->SetStyleSheetContainer(context_state.style_sheet); ContextId ret = create_context_impl(doc); Element* root = ret.get_root_element(); // Mark the root element as not being a shim, as that's only needed for elements that were parented to Rml ones manually. root->shim = false; - // TODO move these defaults elsewhere. Copied from the existing rcss. ret.open(); root->set_width(100.0f, Unit::Percent); root->set_height(100.0f, Unit::Percent); root->set_display(Display::Flex); - root->set_opacity(1.0f); - root->set_font_family("LatoLatin"); - root->set_font_style(FontStyle::Normal); - root->set_font_weight(400); - - float sz = 16.0f; - float spacing = 0.0f; - float sz_add = sz + 4; - root->set_font_size(sz_add, Unit::Dp); - root->set_letter_spacing(sz_add * spacing, Unit::Dp); - root->set_line_height(sz_add, Unit::Dp); ret.close(); + doc->Hide(); + return ret; } @@ -307,6 +336,15 @@ void recompui::ContextId::open() { opened_context_id = *this; } +bool recompui::ContextId::open_if_not_already() { + if (opened_context_id == *this) { + return false; + } + + open(); + return true; +} + void recompui::ContextId::close() { // Ensure a context is currently opened by this thread. if (opened_context_id == ContextId::null()) { @@ -330,6 +368,15 @@ void recompui::ContextId::close() { } } +recompui::ContextId recompui::try_close_current_context() { + if (opened_context_id != ContextId::null()) { + ContextId prev_context = opened_context_id; + opened_context_id.close(); + return prev_context; + } + return ContextId::null(); +} + void recompui::ContextId::process_updates() { // Ensure a context is currently opened by this thread. if (opened_context_id == ContextId::null()) { @@ -367,8 +414,83 @@ void recompui::ContextId::process_updates() { continue; } - static_cast(cur_resource->get())->process_event(update_event); + static_cast(cur_resource->get())->handle_event(update_event); } + + std::vector> to_set_text = std::move(opened_context->to_set_text); + + // Delete the Rml elements that are pending deletion. + for (auto cur_text_update : to_set_text) { + Element* element_ptr = std::get<0>(cur_text_update); + ResourceId resource = std::get<1>(cur_text_update); + std::string& text = std::get<2>(cur_text_update); + + // If the resource ID is valid, prefer that as we can quickly validate if the resource still exists. + if (resource != ResourceId::null()) { + resource_slotmap::key cur_key{ resource.slot_id }; + std::unique_ptr