Compare commits

..

22 Commits

Author SHA1 Message Date
SuperDude88 3eeb9a19a1 Fix for Mobile Testing 2026-07-07 19:04:14 -04:00
SuperDude88 060f0eb419 Use OSGetSystemTime Extension
- Fixes desyncing issues with save file time and a couple other odd instances, particularly on mobile platforms that suspend apps
2026-07-07 18:52:16 -04:00
SuperDude88 e491d254f8 Don't Use OSCalendarTime for Speedrun Timing
- Shouldn't rely on OSTicksToCalendarTime since it will be changed to handle time zone conversion, and that doesn't make sense on elapsed time
2026-07-07 18:12:15 -04:00
TakaRikka 84c5729c4e Merge pull request #2175 from TwilitRealm/mirror-mode-fixes
Mirror Mode Map Fixes
2026-07-07 04:00:07 -07:00
Luke Street e0ab36b7d9 Stable game ABI: Use PARTIAL_DEBUG
To make sure that mods can work across debug and release build, we can utilize PARTIAL_DEBUG to enable only DEBUG changes that affect struct and vtable layout
2026-07-07 01:14:29 -06:00
Luke Street d40a30ee13 Config: renaming & add change subscriptions 2026-07-07 00:50:11 -06:00
SuperDude88 074b43a089 Merge pull request #2173 from Lypopp/Invincible-enemies-activated-with-speedrun-mode-activated
Fixed invincible enemies cheat not deactivating on game startup when speedrun mode is activated
2026-07-06 19:48:53 -04:00
SuperDude88 8e8dc305e6 Update Pane Mirroring
- Update mirroring of Link + the dungeon map icon properly when the setting changes
2026-07-05 23:33:15 -04:00
SuperDude88 41389f4a8e Remove Extra Mirror
Accidentally left this in from testing
2026-07-05 22:58:45 -04:00
SuperDude88 8145dec2a1 Remove Outdated MIrror Mode TODO
This was done!
2026-07-05 20:19:42 -04:00
SuperDude88 4089a80a17 Mirror Mode Map Fixes
Will probably need to resolve conflicts with Wii code once decomp has related PR merged + we sync things

Change to setPlusZoomCenterX is not present on Wii (but fixes controls), probably from its lack of CStick
2026-07-05 20:19:33 -04:00
hector.bonhoure@gmail.com b772b6d952 Fixed invincible enemies cheat not deactivating on game startup when speedrun mode is activated 2026-07-05 16:05:55 +02:00
Irastris 42910ab2fd Restore simultaneous gyro and joystick input for first person aiming (#2164) 2026-07-02 00:13:22 -06:00
Olivia!! f54892b2c2 do not disable regular touch input if menu touch is disabled (#2152) 2026-07-02 00:03:53 -06:00
Luke Street f32e069c4b Improve mouse hiding logic (#2163)
* Improve mouse hiding logic

* Restore ImGui logic
2026-07-02 02:03:18 -04:00
Luke Street 09f087656a Guard against null dMsgObject_getMsgObjectClass() in a few places 2026-06-30 15:19:30 -06:00
Irastris fe15366912 Make Ganondorf cape tearing deterministic when using texture replacements (#2145)
* Make Ganondorf cape tearing deterministic when using texture replacements

* GXIsTexObjReplaced -> has_replacement

* Only check for a replacement once during creation

* Update Aurora
2026-06-28 22:44:26 -06:00
jdflyer cfadf7607a Various Mirror Mode Fixes (#2149)
* Various Mirror Mode Fixes

* Avoid mutating mArrowPos2DX

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-06-28 22:42:52 -06:00
Luke Street f81d25b425 Add "Touch Targeting" option 2026-06-27 21:20:14 -06:00
qubitnano ebf6f31719 flake.nix: patchelf libvulkan (#2124)
Since aurora 7ccdae18077caa67acb0f61a525aa5a423cc3b2c, dusklight can't
find libvulkan.so on nix
2026-06-27 21:15:57 -06:00
Luke Street 590d209f76 Update README.md 2026-06-27 21:09:04 -06:00
Luke Street ed21cd4fd0 Update README.md 2026-06-27 20:35:17 -06:00
122 changed files with 1182 additions and 3533 deletions
-2
View File
@@ -53,5 +53,3 @@ compile_commands.json
pipeline_cache.bin pipeline_cache.bin
extract extract
*.dusk
+2 -2
View File
@@ -6,12 +6,12 @@
"type": "cppvsdbg", "type": "cppvsdbg",
"request": "launch", "request": "launch",
"program": "${command:cmake.launchTargetPath}", "program": "${command:cmake.launchTargetPath}",
"args": ["-l", "1", "--dvd", "${workspaceRoot}/orig/GZ2E01/GZ2E01.iso", "--console", "--mods", "${workspaceRoot}/mods"], "args": ["-l", "1", "--dvd", "${workspaceRoot}/orig/GZ2E01/GZ2E01.iso", "--console"],
"MIMode": "gdb", "MIMode": "gdb",
"miDebuggerPath": "gdb", "miDebuggerPath": "gdb",
"symbolSearchPath": "${command:cmake.launchTargetPath}", "symbolSearchPath": "${command:cmake.launchTargetPath}",
"console": "integratedTerminal", "console": "integratedTerminal",
"cwd":"${workspaceRoot}", "cwd":"${workspaceRoot}"
} }
] ]
} }
+34 -145
View File
@@ -21,26 +21,26 @@ else ()
find_package(Git) find_package(Git)
if (GIT_FOUND) if (GIT_FOUND)
# make sure version information gets re-run when the current Git HEAD changes # make sure version information gets re-run when the current Git HEAD changes
execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD
OUTPUT_VARIABLE dusk_git_head_filename OUTPUT_VARIABLE dusk_git_head_filename
OUTPUT_STRIP_TRAILING_WHITESPACE) OUTPUT_STRIP_TRAILING_WHITESPACE)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_filename}") set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_filename}")
execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD
OUTPUT_VARIABLE dusk_git_head_symbolic OUTPUT_VARIABLE dusk_git_head_symbolic
OUTPUT_STRIP_TRAILING_WHITESPACE) OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${dusk_git_head_symbolic} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${dusk_git_head_symbolic}
OUTPUT_VARIABLE dusk_git_head_symbolic_filename OUTPUT_VARIABLE dusk_git_head_symbolic_filename
OUTPUT_STRIP_TRAILING_WHITESPACE) OUTPUT_STRIP_TRAILING_WHITESPACE)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_symbolic_filename}") set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_symbolic_filename}")
# defines DUSK_WC_REVISION # defines DUSK_WC_REVISION
execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
OUTPUT_VARIABLE DUSK_WC_REVISION OUTPUT_VARIABLE DUSK_WC_REVISION
OUTPUT_STRIP_TRAILING_WHITESPACE) OUTPUT_STRIP_TRAILING_WHITESPACE)
# defines DUSK_WC_DESCRIBE # defines DUSK_WC_DESCRIBE
execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --match "v*" execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --match "v*"
OUTPUT_VARIABLE DUSK_WC_DESCRIBE OUTPUT_VARIABLE DUSK_WC_DESCRIBE
OUTPUT_STRIP_TRAILING_WHITESPACE) OUTPUT_STRIP_TRAILING_WHITESPACE)
@@ -49,11 +49,11 @@ if (GIT_FOUND)
string(REGEX REPLACE "-0$" "" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}") string(REGEX REPLACE "-0$" "" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}")
# defines DUSK_WC_BRANCH # defines DUSK_WC_BRANCH
execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
OUTPUT_VARIABLE DUSK_WC_BRANCH OUTPUT_VARIABLE DUSK_WC_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE) OUTPUT_STRIP_TRAILING_WHITESPACE)
# defines DUSK_WC_DATE # defines DUSK_WC_DATE
execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad
OUTPUT_VARIABLE DUSK_WC_DATE OUTPUT_VARIABLE DUSK_WC_DATE
OUTPUT_STRIP_TRAILING_WHITESPACE) OUTPUT_STRIP_TRAILING_WHITESPACE)
else () else ()
@@ -211,7 +211,7 @@ if (DUSK_MOVIE_SUPPORT)
-DWITH_JAVA=OFF -DWITH_JAVA=OFF
) )
if (CMAKE_TOOLCHAIN_FILE) if (CMAKE_TOOLCHAIN_FILE)
get_filename_component(_jpeg_toolchain_file "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") get_filename_component(_jpeg_toolchain_file "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}")
list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file}) list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file})
endif () endif ()
set(_jpeg_passthrough_vars set(_jpeg_passthrough_vars
@@ -292,42 +292,14 @@ FetchContent_Declare(cxxopts
URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz
URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072 URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072
DOWNLOAD_EXTRACT_TIMESTAMP FALSE DOWNLOAD_EXTRACT_TIMESTAMP FALSE
EXCLUDE_FROM_ALL
) )
message(STATUS "dusklight: Fetching nlohmann/json") message(STATUS "dusklight: Fetching nlohmann/json")
FetchContent_Declare(json FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
DOWNLOAD_EXTRACT_TIMESTAMP FALSE DOWNLOAD_EXTRACT_TIMESTAMP FALSE
EXCLUDE_FROM_ALL
) )
FetchContent_MakeAvailable(cxxopts json)
message(STATUS "dusk: Fetching miniz")
FetchContent_Declare(miniz
URL https://github.com/richgel999/miniz/releases/download/3.0.2/miniz-3.0.2.zip
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
EXCLUDE_FROM_ALL
)
message(STATUS "dusk: Fetching funchook")
# cmake/patch_funchook.cmake patches funchook's cmake/capstone.cmake.in to inject a
# PATCH_COMMAND into capstone's inner ExternalProject. That PATCH_COMMAND runs
# cmake/fix_capstone_policy.cmake after capstone is cloned, which removes the
# cmake_policy(SET CMP0048 OLD) line that CMake >= 3.27 rejects.
# This is incredibly scuffed and we should probably think of a better way to do this
set(CAPSTONE_FIX_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/fix_capstone_policy.cmake")
FetchContent_Declare(funchook
GIT_REPOSITORY https://github.com/kubo/funchook.git
GIT_TAG v1.1.3
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
PATCH_COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=<SOURCE_DIR> -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch_funchook.cmake
EXCLUDE_FROM_ALL
)
set(FUNCHOOK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(FUNCHOOK_BUILD_SHARED OFF CACHE BOOL "" FORCE)
set(FUNCHOOK_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(cxxopts json miniz funchook)
if (DUSK_ENABLE_SENTRY_NATIVE) if (DUSK_ENABLE_SENTRY_NATIVE)
message(STATUS "dusklight: Fetching sentry-native") message(STATUS "dusklight: Fetching sentry-native")
@@ -375,7 +347,7 @@ else ()
string(TOLOWER CMAKE_SYSTEM_NAME PLATFORM_NAME) string(TOLOWER CMAKE_SYSTEM_NAME PLATFORM_NAME)
endif () endif ()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_BINARY_DIR}/version.h)
include(files.cmake) include(files.cmake)
@@ -391,7 +363,8 @@ set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES}) source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
source_group("dusklight" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES}) source_group("dusklight" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES})
set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1) # PARTIAL_DEBUG makes debug and release share one struct/vtable ABI so a mod binary loads into either
set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1 PARTIAL_DEBUG=1)
set(GAME_INCLUDE_DIRS set(GAME_INCLUDE_DIRS
include include
@@ -400,20 +373,12 @@ set(GAME_INCLUDE_DIRS
libs/JSystem/include libs/JSystem/include
libs libs
extern/aurora/include/dolphin extern/aurora/include/dolphin
extern/aurora/include
extern extern
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR})
${miniz_SOURCE_DIR})
# Interface target for mods and sub-projects to inherit game headers/defines
add_library(dusklight_game_headers INTERFACE)
target_include_directories(dusklight_game_headers INTERFACE ${GAME_INCLUDE_DIRS})
target_compile_definitions(dusklight_game_headers INTERFACE TARGET_PC=1)
target_link_libraries(dusklight_game_headers INTERFACE TracyClient)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd
aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt funchook-static aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt
Threads::Threads zstd::libzstd) Threads::Threads zstd::libzstd)
if (DUSK_ENABLE_SENTRY_NATIVE) if (DUSK_ENABLE_SENTRY_NATIVE)
@@ -486,16 +451,6 @@ if(ANDROID)
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1) list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
endif () endif ()
set(DUSK_ENABLE_CODE_MODS_DEFAULT OFF)
option(DUSK_ENABLE_CODE_MODS "Enable code mods" ${DUSK_ENABLE_CODE_MODS_DEFAULT})
if (DUSK_ENABLE_CODE_MODS)
if (NOT ANDROID AND NOT IOS AND NOT TVOS)
list(APPEND GAME_COMPILE_DEFS DUSK_CODE_MODS=1)
else ()
message(FATAL_ERROR "Code mods not supported on the target platform!")
endif ()
endif ()
if (DUSK_PACKAGE_INSTALL) if (DUSK_PACKAGE_INSTALL)
include(GNUInstallDirs) include(GNUInstallDirs)
list(APPEND GAME_COMPILE_DEFS DUSK_ASSET_DIR="${CMAKE_INSTALL_FULL_DATADIR}/dusklight/") list(APPEND GAME_COMPILE_DEFS DUSK_ASSET_DIR="${CMAKE_INSTALL_FULL_DATADIR}/dusklight/")
@@ -520,9 +475,10 @@ set(GAME_DEBUG_FILES
set_source_files_properties( set_source_files_properties(
${GAME_DEBUG_FILES} ${GAME_DEBUG_FILES}
PROPERTIES PROPERTIES
COMPILE_DEFINITIONS "$<$<CONFIG:Debug>:DEBUG=1>;$<$<CONFIG:Debug>:PARTIAL_DEBUG=1>" COMPILE_DEFINITIONS "$<$<CONFIG:Debug>:DEBUG=1>;PARTIAL_DEBUG=1"
) )
# game_base is for all other game code files
set(GAME_BASE_FILES set(GAME_BASE_FILES
${DOLZEL_FILES} ${DOLZEL_FILES}
${Z2AUDIOLIB_FILES} ${Z2AUDIOLIB_FILES}
@@ -533,15 +489,14 @@ set(GAME_BASE_FILES
set_source_files_properties( set_source_files_properties(
${GAME_BASE_FILES} ${GAME_BASE_FILES}
PROPERTIES PROPERTIES
COMPILE_DEFINITIONS "NDEBUG=1;NDEBUG_DEFINED=1;DEBUG_DEFINED=0;$<$<CONFIG:Debug>:PARTIAL_DEBUG=1>" COMPILE_DEFINITIONS "NDEBUG=1;NDEBUG_DEFINED=1;DEBUG_DEFINED=0;PARTIAL_DEBUG=1"
) )
foreach(jsystem_lib IN LISTS JSYSTEM_LIBRARIES) foreach(jsystem_lib IN LISTS JSYSTEM_LIBRARIES)
target_compile_definitions(${jsystem_lib} PRIVATE target_compile_definitions(${jsystem_lib} PRIVATE
${GAME_COMPILE_DEFS} ${GAME_COMPILE_DEFS}
DUSK_BUILDING_GAME=1
$<$<CONFIG:Debug>:DEBUG=1> $<$<CONFIG:Debug>:DEBUG=1>
$<$<CONFIG:Debug>:PARTIAL_DEBUG=1> PARTIAL_DEBUG=1
) )
target_include_directories(${jsystem_lib} PRIVATE ${GAME_INCLUDE_DIRS}) target_include_directories(${jsystem_lib} PRIVATE ${GAME_INCLUDE_DIRS})
target_link_libraries(${jsystem_lib} PRIVATE ${GAME_LIBS}) target_link_libraries(${jsystem_lib} PRIVATE ${GAME_LIBS})
@@ -555,61 +510,24 @@ if (CMAKE_CXX_LINK_GROUP_USING_RESCAN_SUPPORTED OR CMAKE_LINK_GROUP_USING_RESCAN
set(JSYSTEM_LINK_LIBRARIES "$<LINK_GROUP:RESCAN,${JSYSTEM_LIBRARIES}>") set(JSYSTEM_LINK_LIBRARIES "$<LINK_GROUP:RESCAN,${JSYSTEM_LIBRARIES}>")
endif () endif ()
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES} ${miniz_SOURCE_DIR}/miniz.c) set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES})
if(ANDROID) if(ANDROID)
add_library(dusklight SHARED ${DUSK_FILES}) add_library(dusklight SHARED ${DUSK_FILES})
set_target_properties(dusklight PROPERTIES OUTPUT_NAME main) set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
set(DUSK_MAIN_TARGET dusklight)
elseif(WIN32)
add_library(dusklight_game SHARED ${DUSK_FILES})
set_target_properties(dusklight_game PROPERTIES
WINDOWS_EXPORT_ALL_SYMBOLS ${DUSK_ENABLE_CODE_MODS}
OUTPUT_NAME dusklight
PDB_NAME dusklight_game)
# rmlui_core uses its own PCH which creates a duplicate PCH marker symbol when linked
# Disabling rmlui's PCH removes the conflicting marker and lets the link succeed
if (MSVC AND TARGET rmlui_core AND DUSK_ENABLE_CODE_MODS)
set_target_properties(rmlui_core PROPERTIES DISABLE_PRECOMPILE_HEADERS ON)
endif ()
add_executable(dusklight WIN32 src/dusk/launcher_win32.cpp)
target_link_libraries(dusklight PRIVATE dusklight_game)
target_include_directories(dusklight PRIVATE include)
set(DUSK_MAIN_TARGET dusklight_game)
else () else ()
add_executable(dusklight ${DUSK_FILES}) add_executable(dusklight ${DUSK_FILES})
set(DUSK_MAIN_TARGET dusklight)
endif () endif ()
if (ENABLE_ASAN) if (ENABLE_ASAN)
target_sources(${DUSK_MAIN_TARGET} PRIVATE src/dusk/asan_options.c) target_sources(dusklight PRIVATE src/dusk/asan_options.c)
endif () endif ()
if (WIN32 AND TARGET imgui) target_compile_definitions(dusklight PRIVATE ${GAME_COMPILE_DEFS})
target_compile_definitions(imgui PRIVATE "IMGUI_API=__declspec(dllexport)") target_include_directories(dusklight PRIVATE ${GAME_INCLUDE_DIRS})
target_sources(${DUSK_MAIN_TARGET} PRIVATE $<TARGET_OBJECTS:imgui>) target_link_libraries(dusklight PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
endif () target_precompile_headers(dusklight PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
target_compile_definitions(${DUSK_MAIN_TARGET} PRIVATE ${GAME_COMPILE_DEFS} DUSK_BUILDING_GAME=1)
target_include_directories(${DUSK_MAIN_TARGET} PRIVATE ${GAME_INCLUDE_DIRS})
target_link_libraries(${DUSK_MAIN_TARGET} PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
target_precompile_headers(${DUSK_MAIN_TARGET} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_LIST_DIR}/include/dusk_pch.hpp>")
if(WIN32)
target_link_libraries(${DUSK_MAIN_TARGET} PRIVATE Psapi)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL Linux)
target_link_libraries(${DUSK_MAIN_TARGET} PRIVATE dl)
endif()
if(APPLE)
target_link_options(${DUSK_MAIN_TARGET} PRIVATE -Wl,-export_dynamic)
elseif(UNIX AND NOT ANDROID)
target_link_options(${DUSK_MAIN_TARGET} PRIVATE -rdynamic)
endif()
if (TARGET crashpad_handler) if (TARGET crashpad_handler)
add_dependencies(${DUSK_MAIN_TARGET} crashpad_handler) add_dependencies(dusklight crashpad_handler)
add_custom_command(TARGET ${DUSK_MAIN_TARGET} POST_BUILD add_custom_command(TARGET dusklight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:crashpad_handler>" "$<TARGET_FILE:crashpad_handler>"
"$<TARGET_FILE_DIR:dusklight>" "$<TARGET_FILE_DIR:dusklight>"
@@ -620,7 +538,7 @@ endif ()
if (ANDROID) if (ANDROID)
# SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static # SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static
# archive, force an undefined reference so the linker keeps the SDL_main object. # archive, force an undefined reference so the linker keeps the SDL_main object.
target_link_options(${DUSK_MAIN_TARGET} PRIVATE "-Wl,-u,SDL_main") target_link_options(dusklight PRIVATE "-Wl,-u,SDL_main")
endif () endif ()
if (CMAKE_SYSTEM_NAME STREQUAL Linux) if (CMAKE_SYSTEM_NAME STREQUAL Linux)
@@ -630,7 +548,7 @@ endif ()
if (NOT APPLE) if (NOT APPLE)
add_custom_command(TARGET dusklight POST_BUILD add_custom_command(TARGET dusklight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/res" "${CMAKE_SOURCE_DIR}/res"
"$<TARGET_FILE_DIR:dusklight>/res" "$<TARGET_FILE_DIR:dusklight>/res"
COMMENT "Copying resources" COMMENT "Copying resources"
) )
@@ -658,13 +576,13 @@ if (WIN32)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.rc.in ${DUSK_WINDOWS_RC} @ONLY) configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.rc.in ${DUSK_WINDOWS_RC} @ONLY)
target_sources(dusklight PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC}) target_sources(dusklight PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
set_target_properties(dusklight PROPERTIES WIN32_EXECUTABLE TRUE)
if (MSVC) if (MSVC)
target_link_options(dusklight PRIVATE /MANIFEST:NO) target_link_options(dusklight PRIVATE /MANIFEST:NO)
endif () endif ()
endif () endif ()
include(cmake/DuskModSDK.cmake)
if (APPLE) if (APPLE)
if (IOS) if (IOS)
set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios) set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios)
@@ -672,7 +590,6 @@ if (APPLE)
set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/tvos) set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/tvos)
else () else ()
set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos) set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos)
set(DUSK_ENTITLEMENTS ${DUSK_RESOURCE_DIR}/Dusk.entitlements)
endif () endif ()
set(DUSK_INFO_PLIST ${DUSK_RESOURCE_DIR}/Info.plist.in) set(DUSK_INFO_PLIST ${DUSK_RESOURCE_DIR}/Info.plist.in)
file(GLOB_RECURSE DUSK_RESOURCE_FILES file(GLOB_RECURSE DUSK_RESOURCE_FILES
@@ -703,8 +620,6 @@ if (APPLE)
OUTPUT_NAME Dusklight OUTPUT_NAME Dusklight
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES" XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${DUSK_ENTITLEMENTS}
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
) )
endif () endif ()
@@ -724,11 +639,7 @@ if (IOS)
endif () endif ()
include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
if(WIN32) aurora_copy_runtime_dlls(dusklight)
aurora_copy_runtime_dlls(dusklight dusklight_game)
else()
aurora_copy_runtime_dlls(dusklight)
endif()
if (DUSK_SELECTED_OPT) if (DUSK_SELECTED_OPT)
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
@@ -767,33 +678,15 @@ function(get_target_prefix target result_var)
endif () endif ()
endfunction() endfunction()
list(APPEND BINARY_TARGETS dusklight) list(APPEND BINARY_TARGETS dusklight)
if(WIN32)
list(APPEND BINARY_TARGETS dusklight_game)
endif()
set(EXTRA_TARGETS "") set(EXTRA_TARGETS "")
if (TARGET crashpad_handler) if (TARGET crashpad_handler)
list(APPEND EXTRA_TARGETS crashpad_handler) list(APPEND EXTRA_TARGETS crashpad_handler)
endif () endif ()
if (WIN32) if (DUSK_PACKAGE_INSTALL)
# Install the launcher and game DLL, but skip the DLL import library.
if (DUSK_PACKAGE_INSTALL)
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR}
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR}
)
else ()
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS}
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}
BUNDLE DESTINATION ${CMAKE_INSTALL_PREFIX}
)
endif ()
elseif (DUSK_PACKAGE_INSTALL)
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_BINDIR}) install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_BINDIR})
else () else()
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX}) install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX})
endif () endif()
aurora_install_runtime_dlls(dusklight ${CMAKE_INSTALL_PREFIX}) aurora_install_runtime_dlls(dusklight ${CMAKE_INSTALL_PREFIX})
if (NOT APPLE) if (NOT APPLE)
if (DUSK_PACKAGE_INSTALL) if (DUSK_PACKAGE_INSTALL)
@@ -851,7 +744,3 @@ foreach (target IN LISTS BINARY_TARGETS)
endif () endif ()
endforeach () endforeach ()
endforeach () endforeach ()
if (DUSK_ENABLE_CODE_MODS)
add_subdirectory(tools/mod_test)
endif ()
+4 -18
View File
@@ -20,31 +20,17 @@ It aims to be as accurate as possible to the original while also providing new o
> Dusklight does *not* provide any copyrighted assets. You must provide your own copy of the original game. > Dusklight does *not* provide any copyrighted assets. You must provide your own copy of the original game.
> [!IMPORTANT] > [!IMPORTANT]
> At a minimum, Dusklight requires a GPU with support for either D3D12, Vulkan, or Metal. Your experience with specific hardware, operating systems, and drivers may vary. In particular, older Intel iGPUs have a high likelihood of incompatibility. We are also aware of a number of issues on devices with Adreno GPUs and are working to resolve them. > At a minimum, Dusklight requires a GPU with support for D3D12, Vulkan 1.1+, or Metal. For older devices, best-effort support is provided for D3D11 and OpenGL ES (Android), but will not achieve full accuracy or performance. Your experience with specific hardware, operating systems, and drivers may vary.
### 1. Dump your game ### 1. Dump your game
You must dump your own copy of the game, please see [this article](https://wiki.dolphin-emu.org/index.php?title=Ripping_Games) for instructions. After dumping, you can use a program like [Dolphin](https://dolphin-emu.org/) or [nodtool](https://github.com/encounter/nod/releases) to convert the `.iso` to a `.rvz` to save space. You must dump your own copy of the game. Please see [this article](https://wiki.dolphin-emu.org/index.php?title=Ripping_Games) for instructions. After dumping, you can use a program like [Dolphin](https://dolphin-emu.org/) or [nodtool](https://github.com/encounter/nod/releases) to convert the `.iso` to `.rvz` to save space.
Currently, only the GameCube USA and EUR releases are supported. Support for other versions of the game is planned in the future. Currently, only the GameCube USA and EUR releases are supported. Support for other versions of the game is planned in the future.
### 2. Download [Dusklight](https://github.com/TwilitRealm/dusklight/releases) ### 2. Install Dusklight
### 3. Setup the game Visit the [official installation guide](https://twilitrealm.dev/install/) for full instructions.
**Windows / macOS / Linux**
- Extract the .zip file
- Launch Dusklight
- Press **Select Disc Image** and provide the path to your supported game dump
- Press **Play**!
**iOS**
- Follow the [iOS setup guide](docs/ios-install-altstore.md)
**Android**
- Install the Dusklight APK
- Launch Dusklight
- Press **Select Disc Image** and provide the path to your supported game dump
- Press **Play**!
# Building # Building
-49
View File
@@ -1,49 +0,0 @@
# add_dusk_mod(<target> SOURCES <file>... MOD_JSON <mod.json> [RES_DIR <res>])
set(DUSK_MODS_OUTPUT_DIR "${CMAKE_SOURCE_DIR}/mods" CACHE PATH "Directory to write .dusk packages into")
function(add_dusk_mod target_name)
cmake_parse_arguments(ARG "" "MOD_JSON;RES_DIR" "SOURCES" ${ARGN})
if(NOT ARG_MOD_JSON)
message(FATAL_ERROR "add_dusk_mod: MOD_JSON is required")
endif()
add_library(${target_name} SHARED ${ARG_SOURCES})
set_target_properties(${target_name} PROPERTIES PREFIX "" WINDOWS_EXPORT_ALL_SYMBOLS ON)
target_compile_features(${target_name} PRIVATE cxx_std_20)
target_link_libraries(${target_name} PRIVATE dusklight_game_headers)
if(APPLE)
target_link_options(${target_name} PRIVATE -undefined dynamic_lookup)
elseif(UNIX)
target_link_options(${target_name} PRIVATE -Wl,--allow-shlib-undefined)
elseif(WIN32)
target_link_libraries(${target_name} PRIVATE dusklight_game)
if(MSVC)
target_link_options(${target_name} PRIVATE /INCREMENTAL:NO)
set_target_properties(${target_name} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
endif()
endif()
set(_stage "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_stage")
set(_out "${DUSK_MODS_OUTPUT_DIR}/${target_name}.dusk")
file(MAKE_DIRECTORY "${_stage}") # must exist before POST_BUILD on Windows
set(_zip_args "$<TARGET_FILE_NAME:${target_name}>" mod.json)
set(_extra_cmds "")
if(ARG_RES_DIR)
list(APPEND _zip_args res)
set(_extra_cmds COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/${ARG_RES_DIR}" "${_stage}/res")
endif()
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${_stage}" "${DUSK_MODS_OUTPUT_DIR}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:${target_name}>" "${_stage}/$<TARGET_FILE_NAME:${target_name}>"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/${ARG_MOD_JSON}" "${_stage}/mod.json"
${_extra_cmds}
COMMAND ${CMAKE_COMMAND} -E tar cvf "${_out}" --format=zip ${_zip_args}
WORKING_DIRECTORY "${_stage}"
COMMENT "Packaging ${target_name} -> ${_out}"
)
endfunction()
-13
View File
@@ -1,13 +0,0 @@
# Patches capstone's CMakeLists.txt for compatibility with CMake >= 4.0:
# - Bumps cmake_minimum_required to 3.10 (CMake >= 4.0 dropped < 3.5 support; < 3.10 warns)
# - Removes cmake_policy(SET CMP0048 OLD) (rejected by CMake >= 3.27)
file(READ "${DIR}/CMakeLists.txt" _content)
string(REGEX REPLACE
"cmake_minimum_required[ \t]*\\([ \t]*VERSION[ \t]+[0-9]+\\.[0-9]+(\\.[0-9]+)?[ \t]*\\)"
"cmake_minimum_required(VERSION 3.10)"
_content "${_content}")
string(REGEX REPLACE
"cmake_policy[ \t]*\\([ \t]*SET[ \t]+CMP0048[ \t]+OLD[ \t]*\\)"
"# cmake_policy(SET CMP0048 OLD)"
_content "${_content}")
file(WRITE "${DIR}/CMakeLists.txt" "${_content}")
-11
View File
@@ -1,11 +0,0 @@
file(READ "${SOURCE_DIR}/cmake/capstone.cmake.in" _content)
# Insert PATCH_COMMAND before CONFIGURE_COMMAND in the ExternalProject_Add.
# Bracket args prevent cmake from substituting ${...} while writing this file.
string(REPLACE
" CONFIGURE_COMMAND \"\""
[=[ PATCH_COMMAND "${CMAKE_COMMAND}" -DDIR=${CMAKE_CURRENT_BINARY_DIR}/capstone-src -P "${CAPSTONE_FIX_SCRIPT}"
CONFIGURE_COMMAND ""]=]
_content "${_content}")
file(WRITE "${SOURCE_DIR}/cmake/capstone.cmake.in" "${_content}")
-351
View File
@@ -1,351 +0,0 @@
# Dusk Mod API
Mods are shared libraries packaged into a `.dusk` zip archive. The loader scans the `mods/` directory at startup, extracts each library, and calls your exports each frame.
## Table of Contents
1. [Getting Started](#getting-started)
2. [mod.json](#modjson)
3. [Required Exports](#required-exports)
4. [DuskModAPI Reference](#duskmodapi-reference)
5. [Logging](#logging)
6. [Loading Resources](#loading-resources)
7. [ImGui Integration](#imgui-integration)
8. [Hooking Game Functions](#hooking-game-functions)
- [Pre-hooks](#pre-hooks)
- [Post-hooks](#post-hooks)
- [Replace hooks](#replace-hooks)
- [Reading and writing arguments](#reading-and-writing-arguments)
9. [Inter-Mod Communication](#inter-mod-communication)
10. [Full Example](#full-example)
---
## Getting Started
Fork the [mod template](../tools/mod_template/), it is a self-contained CMake project that references dusk as a subdirectory.
```
my_mod/
├── CMakeLists.txt
├── mod.json
├── src/mod.cpp
└── res/ (optional bundled resources)
```
**CMakeLists.txt:**
```cmake
cmake_minimum_required(VERSION 3.25)
project(my_mod CXX)
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dusk" CACHE PATH "Path to dusk source root")
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
add_dusk_mod(my_mod
SOURCES src/mod.cpp
MOD_JSON mod.json
RES_DIR res # optional
)
```
After building, `my_mod.dusk` is placed in `mods/` next to the project root (`DUSK_MODS_OUTPUT_DIR` cache variable). Copy it to the game's `mods/` folder and launch.
- Windows: `%APPDATA%\TwilitRealm\Dusk\mods`
- Linux: `~/.local/share/TwilitRealm/Dusk/mods`
- macOS: `~/Library/Application Support/TwilitRealm/Dusk/mods`
The `.dusk` archive is a standard zip containing `mod.json`, the compiled library, and an optional `res/` tree. `add_dusk_mod()` creates it automatically.
---
## mod.json
```json
{
"name": "My Mod",
"version": "1.0.0",
"author": "Your Name",
"description": "A short description shown in the mod manager."
}
```
All fields are optional but recommended. `name` falls back to the filename, `version` to `"?"`.
---
## Required Exports
```cpp
#include "dusk/mod_api.h"
DUSK_REQUIRE_API_VERSION // declares mod_api_version; loader rejects the mod if the engine is older
extern "C" {
void mod_init (DuskModAPI* api); // required, called once at startup
void mod_tick (DuskModAPI* api); // required, called every frame
void mod_cleanup(DuskModAPI* api); // optional, called on shutdown
}
```
`DUSK_REQUIRE_API_VERSION` is optional but recommended. When present, the loader will refuse to initialize the mod if its API version doesn't exactly match the engine's.
---
## DuskModAPI Reference
The `api` pointer is valid for the lifetime of the mod. When using `hook.hpp`, call `dusk::init(api)` once and `dusk::g_api` is set for you.
| Field | Description |
|-------|-------------|
| `api_version` | ABI version, check against `DUSK_MOD_API_VERSION` if needed |
| `mod_dir` | Absolute path to the extracted mod cache directory |
| `log_info` / `log_warn` / `log_error` | `printf`-style logging, prefixed with the mod name |
| `load_resource` / `free_resource` | Load files from the `res/` tree in the `.dusk` archive |
| `register_tab_content` | Add a panel to the mod manager's per-mod tab |
| `register_menu_item` | Add an item to the quick-access menu |
| `hook_dispatch_pre` / `hook_dispatch_post` | Called by the trampoline, do not call directly |
| `service_publish` | Register a named pointer in the global service registry |
| `service_get` | Look up a named pointer registered by another mod |
---
## Logging
```cpp
api->log_info("Player health: %d", hp);
api->log_warn("Something looks wrong");
api->log_error("Fatal: %s", msg);
```
Output appears in the dusk console as `[My Mod] ...`
The format string is `printf`-compatible.
---
## Loading Resources
```cpp
size_t size = 0;
void* data = api->load_resource("config.txt", &size);
if (data) {
std::string text(static_cast<char*>(data), size);
api->free_resource(data);
}
```
- Path is relative to `res/`, pass `"config.txt"` not `"res/config.txt"`
- Always call `free_resource`, the buffer is owned by miniz
- For writable storage, write files under `api->mod_dir`
---
## ImGui Integration
**Tab content:** shown in the mod's panel in the Mods window, called every frame while visible:
```cpp
static void DrawPanel(void* userdata) {
ImGui::Text("Hello!");
}
api->register_tab_content(DrawPanel, nullptr);
```
Pass a pointer through `userdata` if your callback needs state:
```cpp
api->register_tab_content(DrawPanel, &g_state);
```
**Menu items:** added to the quick-access menu. Use `ImGui::MenuItem`, `ImGui::Separator`, etc.:
```cpp
static void DrawMenuEntry(void*) {
if (ImGui::MenuItem("Reset rotation")) { ... }
}
api->register_menu_item(DrawMenuEntry, nullptr);
```
---
## Hooking Game Functions
Call `dusk::init(api)` first.
```cpp
#include "dusk/hook.hpp"
extern "C" void mod_init(DuskModAPI* api) {
dusk::init(api);
dusk::hookAddPre<&ClassName::Method>(callback);
}
```
The trampoline is installed once per address. Multiple mods can register pre/post callbacks for the same function independently.
### Pre-hooks
Run before the original. Return `0` to let it proceed, non-zero to cancel it. Post-hooks still run either way.
```cpp
static int32_t on_posMove_pre(void* args) {
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0); // this
if (link->shape_angle.y > 10000)
return 1; // cancel
return 0;
}
dusk::hookAddPre<&daAlink_c::posMove>(on_posMove_pre);
```
### Post-hooks
Run after the original (or replace-hook).
```cpp
static void on_posMove_post(void* args) {
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
dusk::g_api->log_info("New Y angle: %d", (int)link->shape_angle.y);
}
dusk::hookAddPost<&daAlink_c::posMove>(on_posMove_post);
```
### Replace hooks
Completely substitutes the original. Only one replace-hook per function, a second install overwrites with a warning.
```cpp
static void on_posMove_replace(void* args) {
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
link->shape_angle.y += 100;
}
dusk::hookSetReplace<&daAlink_c::posMove>(on_posMove_replace);
```
To call the original from inside a replace-hook:
```cpp
using Entry = dusk::HookEntry<&daAlink_c::posMove>;
static void on_posMove_replace(void* args) {
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
link->shape_angle.y = 0;
Entry::g_orig(link);
}
```
### Reading and writing arguments
`args` is a `void*[N]` array. Index `0` is `this`, subsequent indices are parameters in declaration order.
```cpp
T value = dusk::arg <T>(args, n); // copy
T& ref = dusk::argRef<T>(args, n); // reference (read/write)
```
**Example:** halve incoming damage
```cpp
// void daEnemy_c::takeDamage(int amount, daActor_c* source)
static int32_t on_takeDamage_pre(void* args) {
dusk::argRef<int>(args, 1) /= 2;
return 0;
}
dusk::hookAddPre<&daEnemy_c::takeDamage>(on_takeDamage_pre);
```
For reference parameters (e.g. `const cXyz& pos`), use `argRef<cXyz>` to get a direct reference.
---
## Inter-Mod Communication
Mods can expose a public API to each other through a global service registry. The convention for names is `"mod_name/service_name"`.
**Mod A — publishing:**
```cpp
struct MyModAPI {
void (*do_thing)(int value);
};
static void my_do_thing(int value) { ... }
static MyModAPI g_api = { my_do_thing };
extern "C" void mod_init(DuskModAPI* api) {
api->service_publish("my_mod/api", &g_api);
}
```
**Mod B — consuming:**
```cpp
#include "my_mod_api.h"
static MyModAPI* g_my_mod = nullptr;
extern "C" void mod_init(DuskModAPI* api) {
g_my_mod = static_cast<MyModAPI*>(api->service_get("my_mod/api"));
}
```
---
## Full Example
```cpp
#include "d/actor/d_a_alink.h"
#include "dusk/hook.hpp"
#include "dusk/mod_api.h"
#include "imgui.h"
#include "m_Do/m_Do_controller_pad.h"
static int g_ticks = 0;
static int32_t on_posMove_pre(void* args) {
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
if (mDoCPd_c::getHoldR(PAD_1)) {
link->shape_angle.y -= 2048;
}
return 0;
}
static void DrawPanel(void*) {
daAlink_c* link = daAlink_getAlinkActorClass();
ImGui::Text("Ticks: %d", g_ticks);
if (link) {
ImGui::Text("Y angle: %d", (int)link->shape_angle.y);
if (ImGui::Button("Reset rotation")) {
link->shape_angle.y = 0;
}
}
}
static void DrawMenuEntry(void*) {
daAlink_c* link = daAlink_getAlinkActorClass();
if (ImGui::MenuItem("Reset rotation", nullptr, false, link != nullptr)) {
link->shape_angle.y = 0;
}
}
extern "C" {
void mod_init(DuskModAPI* api) {
dusk::init(api);
dusk::hookAddPre<&daAlink_c::posMove>(on_posMove_pre);
api->register_tab_content(DrawPanel, nullptr);
api->register_menu_item(DrawMenuEntry, nullptr);
}
void mod_tick(DuskModAPI* api) {
++g_ticks;
}
void mod_cleanup(DuskModAPI* api) {
api->log_info("Unloaded after %d ticks.", g_ticks);
}
}
```
+1 -1
-11
View File
@@ -1507,8 +1507,6 @@ set(DUSK_FILES
src/dusk/ui/pane.hpp src/dusk/ui/pane.hpp
src/dusk/ui/menu_bar.cpp src/dusk/ui/menu_bar.cpp
src/dusk/ui/menu_bar.hpp src/dusk/ui/menu_bar.hpp
src/dusk/ui/mods_window.cpp
src/dusk/ui/mods_window.hpp
src/dusk/ui/prelaunch.cpp src/dusk/ui/prelaunch.cpp
src/dusk/ui/prelaunch.hpp src/dusk/ui/prelaunch.hpp
src/dusk/ui/preset.cpp src/dusk/ui/preset.cpp
@@ -1543,15 +1541,6 @@ set(DUSK_FILES
src/dusk/OSReport.cpp src/dusk/OSReport.cpp
src/dusk/OSThread.cpp src/dusk/OSThread.cpp
src/dusk/OSMutex.cpp src/dusk/OSMutex.cpp
src/dusk/hook_system.cpp
src/dusk/modding/mod_loader.cpp
src/dusk/modding/mod_loader_api.cpp
src/dusk/modding/mod_loader_overlay.cpp
src/dusk/modding/native_module.cpp
src/dusk/modding/native_module.hpp
src/dusk/modding/bundle_disk.cpp
src/dusk/modding/bundle_zip.cpp
src/dusk/gx_helper.cpp
src/dusk/discord.cpp src/dusk/discord.cpp
src/dusk/discord.hpp src/dusk/discord.hpp
src/dusk/discord_presence.cpp src/dusk/discord_presence.cpp
+6
View File
@@ -269,6 +269,12 @@
runHook postInstall runHook postInstall
''; '';
postFixup = lib.optionalString (!isDarwin) ''
patchelf \
--add-needed "${pkgs.vulkan-loader}/lib/libvulkan.so" \
$out/bin/dusklight
'';
dontStrip = true; dontStrip = true;
meta = { meta = {
@@ -20,7 +20,7 @@ public:
/* 0x14 */ cM3dGAab mM3dGAab; /* 0x14 */ cM3dGAab mM3dGAab;
/* 0x30 */ cBgS_ShdwDraw_Callback mCallbackFun; /* 0x30 */ cBgS_ShdwDraw_Callback mCallbackFun;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x34 */ int field_0x34; /* 0x34 */ int field_0x34;
#endif #endif
}; };
+3 -1
View File
@@ -34,7 +34,7 @@ public:
bool isResetting() { return mResettingFlag; } bool isResetting() { return mResettingFlag; }
static Z2AudioMgr* getInterface() { return mAudioMgrPtr; } static Z2AudioMgr* getInterface() { return mAudioMgrPtr; }
static DUSK_GAME_DATA Z2AudioMgr* mAudioMgrPtr; static Z2AudioMgr* mAudioMgrPtr;
/* 0x0514 */ virtual bool startSound(JAISoundID soundID, JAISoundHandle* handle, const JGeometry::TVec3<f32>* posPtr); /* 0x0514 */ virtual bool startSound(JAISoundID soundID, JAISoundHandle* handle, const JGeometry::TVec3<f32>* posPtr);
/* 0x0518 */ bool mResettingFlag; /* 0x0518 */ bool mResettingFlag;
@@ -48,6 +48,8 @@ public:
/* 0x1370 */ Z2FxLineMgr mFxLineMgr; /* 0x1370 */ Z2FxLineMgr mFxLineMgr;
#if DEBUG #if DEBUG
/* 0x13BC */ Z2DebugSys mDebugSys; /* 0x13BC */ Z2DebugSys mDebugSys;
#elif PARTIAL_DEBUG
alignas(Z2DebugSys) u8 mDebugSys[sizeof(Z2DebugSys)];
#endif #endif
}; // Size: 0x138C }; // Size: 0x138C
+1 -1
View File
@@ -194,7 +194,7 @@ public:
JAISoundHandle* getMainBgmHandle() { return &mMainBgmHandle; } JAISoundHandle* getMainBgmHandle() { return &mMainBgmHandle; }
JAISoundHandle* getSubBgmHandle() { return &mSubBgmHandle; } JAISoundHandle* getSubBgmHandle() { return &mSubBgmHandle; }
#if DEBUG #if PARTIAL_DEBUG || DEBUG
f32 field_0x00_debug; f32 field_0x00_debug;
u8 field_0x04_debug; u8 field_0x04_debug;
#endif #endif
+2 -2
View File
@@ -100,13 +100,13 @@ public:
bool isForceBattle() { return forceBattle_; } bool isForceBattle() { return forceBattle_; }
JSUList<Z2CreatureEnemy>* getEnemyList() { return &field_0x0; } JSUList<Z2CreatureEnemy>* getEnemyList() { return &field_0x0; }
#if DEBUG #if PARTIAL_DEBUG || DEBUG
JSUList<Z2SoundObjBase>* getAllList() { return &allList_; } JSUList<Z2SoundObjBase>* getAllList() { return &allList_; }
#endif #endif
private: private:
/* 0x00 */ JSUList<Z2CreatureEnemy> field_0x0; /* 0x00 */ JSUList<Z2CreatureEnemy> field_0x0;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x0C */ JSUList<Z2SoundObjBase> allList_; /* 0x0C */ JSUList<Z2SoundObjBase> allList_;
#endif #endif
/* 0x0C */ Z2EnemyArea enemyArea_; /* 0x0C */ Z2EnemyArea enemyArea_;
+1 -1
View File
@@ -7,7 +7,7 @@
struct Z2SoundStarter; struct Z2SoundStarter;
class Z2SoundObjBase : public Z2SoundHandles class Z2SoundObjBase : public Z2SoundHandles
#if DEBUG #if PARTIAL_DEBUG || DEBUG
, public JSULink<Z2SoundObjBase> , public JSULink<Z2SoundObjBase>
#endif #endif
{ {
+7 -2
View File
@@ -88,9 +88,14 @@ public:
/* 0x396A */ u8 field_0x396A[0x399E - 0x396A]; /* 0x396A */ u8 field_0x396A[0x399E - 0x396A];
/* 0x399E */ s16 field_0x399e; /* 0x399E */ s16 field_0x399e;
/* 0x39A0 */ u8 field_0x39A0[0x39A4 - 0x39A0]; /* 0x39A0 */ u8 field_0x39A0[0x39A4 - 0x39A0];
#if TARGET_PC
/* 0x39A4 */ cM_rnd_c mMantRng;
#endif
}; };
#if TARGET_PC
STATIC_ASSERT(sizeof(mant_class) == 0x39ac);
#else
STATIC_ASSERT(sizeof(mant_class) == 0x39a4); STATIC_ASSERT(sizeof(mant_class) == 0x39a4);
#endif
#endif /* D_A_MANT_H */ #endif /* D_A_MANT_H */
+2 -2
View File
@@ -45,7 +45,7 @@ private:
class dAttParam_c : public JORReflexible { class dAttParam_c : public JORReflexible {
public: public:
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x04 */ s8 mHIOChildNo; /* 0x04 */ s8 mHIOChildNo;
#endif #endif
@@ -66,7 +66,7 @@ public:
/* 0x35 */ u8 mAttnCursorDisappearFrames; /* 0x35 */ u8 mAttnCursorDisappearFrames;
/* 0x38 */ f32 field_0x38; /* 0x38 */ f32 field_0x38;
/* 0x3C */ f32 field_0x3c; /* 0x3C */ f32 field_0x3c;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x44 */ s32 mDebugDispPosX; /* 0x44 */ s32 mDebugDispPosX;
/* 0x48 */ s32 mDebugDispPosY; /* 0x48 */ s32 mDebugDispPosY;
#endif #endif
+2 -2
View File
@@ -201,7 +201,7 @@ private:
/* 0x02C */ u32 m_flags; /* 0x02C */ u32 m_flags;
/* 0x030 */ cXyz* pm_pos; /* 0x030 */ cXyz* pm_pos;
/* 0x034 */ cXyz* pm_old_pos; /* 0x034 */ cXyz* pm_old_pos;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x038 */ cXyz unk_0x38; /* 0x038 */ cXyz unk_0x38;
#endif #endif
/* 0x038 */ cXyz* pm_speed; /* 0x038 */ cXyz* pm_speed;
@@ -229,7 +229,7 @@ private:
/* 0x0CC */ f32 field_0xcc; /* 0x0CC */ f32 field_0xcc;
/* 0x0D0 */ f32 m_wtr_chk_offset; /* 0x0D0 */ f32 m_wtr_chk_offset;
/* 0x0D4 */ cBgS_PolyInfo* pm_out_poly_info; /* 0x0D4 */ cBgS_PolyInfo* pm_out_poly_info;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x0E4 */ cXyz unk_0xe4; /* 0x0E4 */ cXyz unk_0xe4;
#endif #endif
/* 0x0D8 */ f32 field_0xd8; /* 0x0D8 */ f32 field_0xd8;
+1 -1
View File
@@ -79,7 +79,7 @@ public:
// /* 0x0000 */ cCcS mCCcS; // /* 0x0000 */ cCcS mCCcS;
/* 0x284C */ dCcMassS_Mng mMass_Mng; /* 0x284C */ dCcMassS_Mng mMass_Mng;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x2AD0 */ u8 m_is_mass_all_timer; /* 0x2AD0 */ u8 m_is_mass_all_timer;
#endif #endif
}; // Size = 0x2AC4 }; // Size = 0x2AC4
+6 -6
View File
@@ -1037,7 +1037,7 @@ public:
/* 0x1DE09 */ u8 field_0x1de09; /* 0x1DE09 */ u8 field_0x1de09;
/* 0x1DE0A */ u8 field_0x1de0a; /* 0x1DE0A */ u8 field_0x1de0a;
/* 0x1DE0B */ u8 mIsDebugMode; /* 0x1DE0B */ u8 mIsDebugMode;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x1DE0C */ OSStopwatch mStopwatch; /* 0x1DE0C */ OSStopwatch mStopwatch;
#endif #endif
@@ -1049,11 +1049,11 @@ public:
STATIC_ASSERT(122384 == sizeof(dComIfG_inf_c)); STATIC_ASSERT(122384 == sizeof(dComIfG_inf_c));
DUSK_GAME_EXTERN dComIfG_inf_c g_dComIfG_gameInfo; extern dComIfG_inf_c g_dComIfG_gameInfo;
DUSK_GAME_EXTERN GXColor g_blackColor; extern GXColor g_blackColor;
DUSK_GAME_EXTERN GXColor g_clearColor; extern GXColor g_clearColor;
DUSK_GAME_EXTERN GXColor g_whiteColor; extern GXColor g_whiteColor;
DUSK_GAME_EXTERN GXColor g_saftyWhiteColor; extern GXColor g_saftyWhiteColor;
int dComLbG_PhaseHandler(request_of_phase_process_class*, request_of_phase_process_fn*, int dComLbG_PhaseHandler(request_of_phase_process_class*, request_of_phase_process_fn*,
void*); void*);
+1 -1
View File
@@ -41,7 +41,7 @@ public:
BASE_ROOM5, BASE_ROOM5,
BASE_DEMO, BASE_DEMO,
#if DEBUG #if PARTIAL_DEBUG || DEBUG
BASE_DEBUG, BASE_DEBUG,
#endif #endif
+1 -1
View File
@@ -259,7 +259,7 @@ public:
/* 0x09B8 */ DUNGEON_LIGHT dungeonlight[8]; /* 0x09B8 */ DUNGEON_LIGHT dungeonlight[8];
/* 0x0C18 */ BOSS_LIGHT field_0x0c18[8]; /* 0x0C18 */ BOSS_LIGHT field_0x0c18[8];
/* 0x0D58 */ BOSS_LIGHT field_0x0d58[6]; /* 0x0D58 */ BOSS_LIGHT field_0x0d58[6];
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x0E48 */ NAVYCHAN navy; /* 0x0E48 */ NAVYCHAN navy;
/* 0x0E58 */ u8 field_0xe58[0xE68 - 0xE58]; // part of NAVYCHAN? /* 0x0E58 */ u8 field_0xe58[0xE68 - 0xE58]; // part of NAVYCHAN?
#endif #endif
+1 -4
View File
@@ -2,7 +2,6 @@
#define D_METER_D_METER2_INFO_H #define D_METER_D_METER2_INFO_H
#include "SSystem/SComponent/c_xyz.h" #include "SSystem/SComponent/c_xyz.h"
#include "global.h"
class CPaneMgr; class CPaneMgr;
class J2DTextBox; class J2DTextBox;
@@ -302,7 +301,7 @@ public:
/* 0xF3 */ u8 unk_0xf3[5]; /* 0xF3 */ u8 unk_0xf3[5];
}; };
DUSK_GAME_EXTERN dMeter2Info_c g_meter2_info; extern dMeter2Info_c g_meter2_info;
void dMeter2Info_setSword(u8 i_itemId, bool i_offItemBit); void dMeter2Info_setSword(u8 i_itemId, bool i_offItemBit);
void dMeter2Info_setCloth(u8 i_clothId, bool i_offItemBit); void dMeter2Info_setCloth(u8 i_clothId, bool i_offItemBit);
@@ -850,8 +849,6 @@ inline void dMeter2Info_setFloatingMessage(u16 i_msgID, s16 i_msgTimer, bool i_w
g_meter2_info.setFloatingMessage(i_msgID, i_msgTimer, i_wakuVisible); g_meter2_info.setFloatingMessage(i_msgID, i_msgTimer, i_wakuVisible);
} }
// Show a custom text notification using the floating-message HUD display.
inline void dMeter2Info_setMiniGameCount(s8 i_count) { inline void dMeter2Info_setMiniGameCount(s8 i_count) {
g_meter2_info.setMiniGameCount(i_count); g_meter2_info.setMiniGameCount(i_count);
} }
+10
View File
@@ -360,7 +360,12 @@ inline void dMsgObject_demoMessageGroup() {
} }
inline bool dMsgObject_isTalkNowCheck() { inline bool dMsgObject_isTalkNowCheck() {
#if TARGET_PC
dMsgObject_c* msgObject = dMsgObject_getMsgObjectClass();
return msgObject != NULL && msgObject->getStatus() != 1;
#else
return dMsgObject_getMsgObjectClass()->getStatus() == 1 ? false : true; return dMsgObject_getMsgObjectClass()->getStatus() == 1 ? false : true;
#endif
} }
inline bool dMsgObject_isKillMessageFlag() { inline bool dMsgObject_isKillMessageFlag() {
@@ -497,7 +502,12 @@ inline void dMsgObject_onMsgSend() {
} }
inline bool dMsgObject_isFukidashiCheck() { inline bool dMsgObject_isFukidashiCheck() {
#if TARGET_PC
dMsgObject_c* msgObject = dMsgObject_getMsgObjectClass();
return msgObject != NULL && msgObject->getScrnDrawPtr() != NULL;
#else
return dMsgObject_getMsgObjectClass()->getScrnDrawPtr() == NULL ? false : true; return dMsgObject_getMsgObjectClass()->getScrnDrawPtr() == NULL ? false : true;
#endif
} }
inline void* dMsgObject_getTalkHeap() { inline void* dMsgObject_getTalkHeap() {
+2 -2
View File
@@ -521,13 +521,13 @@ private:
/* 0x019 */ u8 field_0x19; /* 0x019 */ u8 field_0x19;
/* 0x01A */ u8 field_0x1a; /* 0x01A */ u8 field_0x1a;
/* 0x01B */ u8 field_0x1b; /* 0x01B */ u8 field_0x1b;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x01C */ dPa_simpleEcallBack field_0x1c[48]; /* 0x01C */ dPa_simpleEcallBack field_0x1c[48];
#else #else
/* 0x01C */ dPa_simpleEcallBack field_0x1c[25]; /* 0x01C */ dPa_simpleEcallBack field_0x1c[25];
#endif #endif
/* 0x210 */ level_c field_0x210; /* 0x210 */ level_c field_0x210;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
u8 mSceneCount; u8 mSceneCount;
#endif #endif
}; };
+1 -1
View File
@@ -59,7 +59,7 @@ private:
/* 0x18 */ JKRHeap* heap; /* 0x18 */ JKRHeap* heap;
/* 0x1C */ JKRSolidHeap* mDataHeap; /* 0x1C */ JKRSolidHeap* mDataHeap;
/* 0x20 */ void** mRes; /* 0x20 */ void** mRes;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x24 */ int mSize; /* 0x24 */ int mSize;
#endif #endif
}; // Size: 0x24 }; // Size: 0x24
+4 -1
View File
@@ -1008,7 +1008,7 @@ public:
static const int ZONE_MAX = 0x20; static const int ZONE_MAX = 0x20;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x000 */ u8 unk_0x0; /* 0x000 */ u8 unk_0x0;
/* 0x001 */ char unk_0x1; /* 0x001 */ char unk_0x1;
/* 0x000 */ u8 unk_0x2[0x48 - 0x2]; /* 0x000 */ u8 unk_0x2[0x48 - 0x2];
@@ -1029,6 +1029,9 @@ public:
/* 0xF30 */ s64 mSaveTotalTime; /* 0xF30 */ s64 mSaveTotalTime;
#if DEBUG #if DEBUG
/* 0xF80 */ flagFile_c mFlagFile; /* 0xF80 */ flagFile_c mFlagFile;
#elif PARTIAL_DEBUG
// flagFile_c's ctor/virtuals are only defined under #if DEBUG (d_save.cpp)
alignas(flagFile_c) u8 mFlagFile[sizeof(flagFile_c)];
#endif #endif
}; // Size: 0xF38 }; // Size: 0xF38
+4 -4
View File
@@ -538,7 +538,7 @@ public:
/* vt[86] */ virtual stage_tgsc_class* getDrTg(void) const = 0; /* vt[86] */ virtual stage_tgsc_class* getDrTg(void) const = 0;
/* vt[87] */ virtual void setDoor(stage_tgsc_class*) = 0; /* vt[87] */ virtual void setDoor(stage_tgsc_class*) = 0;
/* vt[88] */ virtual stage_tgsc_class* getDoor(void) const = 0; /* vt[88] */ virtual stage_tgsc_class* getDoor(void) const = 0;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
virtual void setUnit(void*) = 0; virtual void setUnit(void*) = 0;
virtual void* getUnit() = 0; virtual void* getUnit() = 0;
#endif #endif
@@ -796,7 +796,7 @@ public:
virtual stage_tgsc_class* getDrTg(void) const { return mDrTg; } virtual stage_tgsc_class* getDrTg(void) const { return mDrTg; }
virtual void setDoor(stage_tgsc_class* i_Door) { mDoor = i_Door; } virtual void setDoor(stage_tgsc_class* i_Door) { mDoor = i_Door; }
virtual stage_tgsc_class* getDoor(void) const { return mDoor; } virtual stage_tgsc_class* getDoor(void) const { return mDoor; }
#if DEBUG #if PARTIAL_DEBUG || DEBUG
virtual void setUnit(void* i_Unit) { mUnit = i_Unit; } virtual void setUnit(void* i_Unit) { mUnit = i_Unit; }
virtual void* getUnit() { return mUnit; } virtual void* getUnit() { return mUnit; }
#endif #endif
@@ -845,7 +845,7 @@ public:
/* 0x54 */ stage_tgsc_class* mDrTg; /* 0x54 */ stage_tgsc_class* mDrTg;
/* 0x58 */ stage_tgsc_class* mDoor; /* 0x58 */ stage_tgsc_class* mDoor;
/* 0x5C */ dStage_FloorInfo_c* mFloorInfo; /* 0x5C */ dStage_FloorInfo_c* mFloorInfo;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x60 */ void* mUnit; /* 0x60 */ void* mUnit;
#endif #endif
/* 0x60 */ u16 mPlayerNum; /* 0x60 */ u16 mPlayerNum;
@@ -990,7 +990,7 @@ public:
/* vt[86] */ virtual stage_tgsc_class* getDrTg(void) const { return mDrTg; } /* vt[86] */ virtual stage_tgsc_class* getDrTg(void) const { return mDrTg; }
/* vt[87] */ virtual void setDoor(stage_tgsc_class* i_Door) { mDoor = i_Door; } /* vt[87] */ virtual void setDoor(stage_tgsc_class* i_Door) { mDoor = i_Door; }
/* vt[88] */ virtual stage_tgsc_class* getDoor(void) const { return mDoor; } /* vt[88] */ virtual stage_tgsc_class* getDoor(void) const { return mDoor; }
#if DEBUG #if PARTIAL_DEBUG || DEBUG
virtual void setUnit(void* i_Unit) { virtual void setUnit(void* i_Unit) {
UNUSED(i_Unit); UNUSED(i_Unit);
OSReport("stage non unit list data !!\n"); OSReport("stage non unit list data !!\n");
+3
View File
@@ -97,6 +97,9 @@ public:
private: private:
#if DEBUG #if DEBUG
/* 0x00 */ dVibTest_c mVibTest; /* 0x00 */ dVibTest_c mVibTest;
#elif PARTIAL_DEBUG
// dVibTest_c's ctor/virtuals are only defined under #if DEBUG (d_vibration.cpp)
alignas(dVibTest_c) u8 mVibTest[sizeof(dVibTest_c)];
#endif #endif
class { class {
+71 -7
View File
@@ -1,9 +1,11 @@
#ifndef DUSK_CONFIG_HPP #ifndef DUSK_CONFIG_HPP
#define DUSK_CONFIG_HPP #define DUSK_CONFIG_HPP
#include <concepts>
#include <functional> #include <functional>
#include <nlohmann/json.hpp>
#include <stdexcept> #include <stdexcept>
#include "nlohmann/json.hpp"
#include "config_var.hpp" #include "config_var.hpp"
namespace dusk::config { namespace dusk::config {
@@ -40,7 +42,7 @@ public:
[[nodiscard]] virtual nlohmann::json dumpToJson(const ConfigVarBase& cVar) const = 0; [[nodiscard]] virtual nlohmann::json dumpToJson(const ConfigVarBase& cVar) const = 0;
}; };
template<ConfigValue T> template <ConfigValue T>
class ConfigImpl : public ConfigImplBase { class ConfigImpl : public ConfigImplBase {
// Just downcasting the references... // Just downcasting the references...
void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const final { void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const final {
@@ -89,20 +91,29 @@ public:
*/ */
void Register(ConfigVarBase& configVar); void Register(ConfigVarBase& configVar);
/**
* \brief Unregister a CVar, detaching it from the config system.
*
* If the CVar carries a user-set value (Value or Speedrun layer), it is stashed as an
* unregistered key: Save() keeps writing it, and a later Register() of the same name restores
* it through the normal back-fill path. The CVar may be destroyed after this returns.
*/
void unregister(ConfigVarBase& configVar);
/** /**
* \brief Load config from the standard user preferences location. * \brief Load config from the standard user preferences location.
*/ */
void LoadFromUserPreferences(); void load_from_user_preferences();
void LoadFromFileName(const char* path); void load_from_file_name(const char* path);
void LoadArgOverride(std::string_view name, std::string_view value); void load_arg_override(std::string_view name, std::string_view value);
void Shutdown(); void shutdown();
/** /**
* \brief Save the config to file. * \brief Save the config to file.
*/ */
void Save(); void save();
/** /**
* \brief Get a registered CVar by name. * \brief Get a registered CVar by name.
@@ -123,6 +134,59 @@ void ClearAllActionBindings(int port);
*/ */
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback); void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback);
/**
* \brief Type-erased change callback. previousValue points at the value before the mutation
* (a `const T*` for a `ConfigVar<T>`) and is valid only for the duration of the call.
*/
using ChangeCallback = std::function<void(ConfigVarBase& cVar, const void* previousValue)>;
/**
* \brief Token identifying a change subscription. 0 is never a valid token.
*/
using Subscription = u64;
/**
* \brief Subscribe to changes of the named CVar (registered or not yet registered).
*
* Fired synchronously on the mutating thread (in practice the game thread) whenever the CVar's
* effective value changes at runtime: setValue, override/speedrun setters and clears. Values
* applied by config load or launch arguments do *not* notify: loads happen during startup
* before the subsystems callbacks push values into are initialized, and each subsystem reads
* its initial value itself at its own init. Callbacks may mutate other CVars; a nested
* mutation of the same CVar applies but does not re-notify.
*/
Subscription subscribe(std::string_view name, ChangeCallback callback);
/**
* \brief Typed convenience overload: the callback receives the current and previous values.
*/
template <ConfigValue T, typename Callback>
requires std::invocable<Callback, const T&, const T&> Subscription subscribe(
ConfigVar<T>& cVar, Callback&& callback) {
return subscribe(cVar.getName(),
[&cVar, cb = std::forward<Callback>(callback)](ConfigVarBase&, const void* previousValue) {
cb(cVar.getValue(), *static_cast<const T*>(previousValue));
});
}
void unsubscribe(Subscription token);
/**
* \brief Register a CVar and attach a change callback in one step.
*
* Useful for pushing settings into external systems (e.g. aurora) from one place instead of
* every UI setter. The callback fires only for runtime changes (see subscribe); the value
* loaded from config or launch arguments does not fire it the external system reads its
* initial value itself at its own initialization.
*/
template <ConfigValue T, typename Callback>
requires std::invocable<Callback, const T&, const T&> Subscription Register(
ConfigVar<T>& cVar, Callback&& onChange) {
auto subscription = subscribe(cVar, std::forward<Callback>(onChange));
Register(static_cast<ConfigVarBase&>(cVar));
return subscription;
}
template <ConfigValue T> template <ConfigValue T>
const ConfigImplBase* GetConfigImpl() { const ConfigImplBase* GetConfigImpl() {
static ConfigImpl<T> config; static ConfigImpl<T> config;
+72 -3
View File
@@ -2,10 +2,12 @@
#define DUSK_CONFIG_VAR_HPP #define DUSK_CONFIG_VAR_HPP
#include "dolphin/types.h" #include "dolphin/types.h"
#include <type_traits> #include <concepts>
#include <cstdlib> #include <cstdlib>
#include <limits> #include <limits>
#include <optional>
#include <string> #include <string>
#include <type_traits>
/** /**
* The configuration system. * The configuration system.
@@ -87,8 +89,6 @@ protected:
*/ */
const ConfigImplBase* impl; const ConfigImplBase* impl;
// The configuration system stores a direct pointer to the ConfigVar instance.
// It is not legal to move or copy it.
ConfigVarBase(const ConfigVarBase&) = delete; ConfigVarBase(const ConfigVarBase&) = delete;
ConfigVarBase(std::string name, const ConfigImplBase* impl); ConfigVarBase(std::string name, const ConfigImplBase* impl);
@@ -100,6 +100,19 @@ protected:
abort(); abort();
} }
/**
* Whether any change subscriber (see config::subscribe) is attached to this CVar's name.
*/
[[nodiscard]] bool has_subscribers() const;
/**
* Notify change subscribers (see config::subscribe) that the effective value of this CVar
* changed. Called by mutators after the change has been applied; previousValue points at
* the old value (a `const T*` for a `ConfigVar<T>`), valid only for the duration of the
* call.
*/
void notify_changed(const void* previousValue);
public: public:
virtual ~ConfigVarBase(); virtual ~ConfigVarBase();
@@ -160,6 +173,7 @@ template <typename T>
concept ConfigValue = concept ConfigValue =
!std::is_const_v<T> !std::is_const_v<T>
&& !std::is_volatile_v<T> && !std::is_volatile_v<T>
&& std::equality_comparable<T>
&& (std::is_same_v<T, bool> && (std::is_same_v<T, bool>
|| ConfigValueInteger<T> || ConfigValueInteger<T>
|| std::is_same_v<T, f32> || std::is_same_v<T, f32>
@@ -171,6 +185,9 @@ concept ConfigValue =
template <ConfigValue T> template <ConfigValue T>
const ConfigImplBase* GetConfigImpl(); const ConfigImplBase* GetConfigImpl();
template <ConfigValue T>
class ConfigImpl;
template <typename T> template <typename T>
struct ConfigEnumRange { struct ConfigEnumRange {
static constexpr auto min = std::numeric_limits<std::underlying_type_t<T>>::min(); static constexpr auto min = std::numeric_limits<std::underlying_type_t<T>>::min();
@@ -241,6 +258,7 @@ public:
*/ */
void setValue(T newValue, bool replaceOverride = true) { void setValue(T newValue, bool replaceOverride = true) {
checkRegistered(); checkRegistered();
const auto previous = previous_for_notify();
value = std::move(newValue); value = std::move(newValue);
if (replaceOverride) { if (replaceOverride) {
@@ -249,6 +267,7 @@ public:
} else if (layer != ConfigVarLayer::Override) { } else if (layer != ConfigVarLayer::Override) {
layer = ConfigVarLayer::Value; layer = ConfigVarLayer::Value;
} }
notify_if_changed(previous);
} }
operator const T&() { operator const T&() {
@@ -265,8 +284,10 @@ public:
*/ */
void setOverrideValue(T newValue) { void setOverrideValue(T newValue) {
checkRegistered(); checkRegistered();
const auto previous = previous_for_notify();
overrideValue = std::move(newValue); overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override; layer = ConfigVarLayer::Override;
notify_if_changed(previous);
} }
/** /**
@@ -280,25 +301,31 @@ public:
void setSpeedrunValue(T newValue) { void setSpeedrunValue(T newValue) {
checkRegistered(); checkRegistered();
if (layer != ConfigVarLayer::Override) { if (layer != ConfigVarLayer::Override) {
const auto previous = previous_for_notify();
priorLayer = layer; priorLayer = layer;
overrideValue = std::move(newValue); overrideValue = std::move(newValue);
layer = ConfigVarLayer::Speedrun; layer = ConfigVarLayer::Speedrun;
notify_if_changed(previous);
} }
} }
void clearOverride() { void clearOverride() {
checkRegistered(); checkRegistered();
if (layer == ConfigVarLayer::Override) { if (layer == ConfigVarLayer::Override) {
const auto previous = previous_for_notify();
overrideValue = {}; overrideValue = {};
layer = ConfigVarLayer::Value; layer = ConfigVarLayer::Value;
notify_if_changed(previous);
} }
} }
void clearSpeedrunOverride() override { void clearSpeedrunOverride() override {
checkRegistered(); checkRegistered();
if (layer == ConfigVarLayer::Speedrun) { if (layer == ConfigVarLayer::Speedrun) {
const auto previous = previous_for_notify();
overrideValue = {}; overrideValue = {};
layer = priorLayer; layer = priorLayer;
notify_if_changed(previous);
} }
} }
@@ -312,6 +339,48 @@ public:
const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer; const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer;
return effectiveLayer == ConfigVarLayer::Default ? defaultValue : value; return effectiveLayer == ConfigVarLayer::Default ? defaultValue : value;
} }
private:
// The config loader applies values through the silent load_* methods below.
friend class ConfigImpl<T>;
/**
* Copy of the effective value before a mutation, taken only when someone is subscribed.
*/
[[nodiscard]] std::optional<T> previous_for_notify() const {
return has_subscribers() ? std::optional<T>{getValue()} : std::nullopt;
}
/**
* Notify subscribers if the effective value actually changed across a mutation.
*/
void notify_if_changed(const std::optional<T>& previous) {
if (previous.has_value() && !(getValue() == *previous)) {
notify_changed(&*previous);
}
}
/**
* setValue(newValue, false) without notifying change subscribers. Used when loading config:
* loads happen during startup before the subsystems change callbacks push values into are
* initialized, and each subsystem applies the loaded value itself at its own init.
*/
void load_value(T newValue) {
checkRegistered();
value = std::move(newValue);
if (layer != ConfigVarLayer::Override) {
layer = ConfigVarLayer::Value;
}
}
/**
* setOverrideValue without notifying change subscribers (see load_value).
*/
void load_override_value(T newValue) {
checkRegistered();
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override;
}
}; };
using ActionBindConfigVar = ConfigVar<int>; using ActionBindConfigVar = ConfigVar<int>;
+13 -4
View File
@@ -22,8 +22,9 @@
class GXTexObjRAII : public GXTexObj { class GXTexObjRAII : public GXTexObj {
public: public:
GXTexObjRAII() : GXTexObj() {} GXTexObjRAII() : GXTexObj() {}
~GXTexObjRAII(); ~GXTexObjRAII() { GXDestroyTexObj(this); }
void reset();
void reset() { GXDestroyTexObj(this); }
GXTexObjRAII(const GXTexObjRAII&) = delete; GXTexObjRAII(const GXTexObjRAII&) = delete;
GXTexObjRAII& operator=(const GXTexObjRAII&) = delete; GXTexObjRAII& operator=(const GXTexObjRAII&) = delete;
@@ -64,8 +65,16 @@ typedef GXTlutObj TGXTlutObj;
#endif #endif
struct GXScopedDebugGroup { struct GXScopedDebugGroup {
explicit GXScopedDebugGroup(const char* text); explicit GXScopedDebugGroup(const char* text) {
~GXScopedDebugGroup(); #if DUSK_GFX_DEBUG_GROUPS
GXPushDebugGroup(text);
#endif
}
~GXScopedDebugGroup() {
#if DUSK_GFX_DEBUG_GROUPS
GXPopDebugGroup();
#endif
}
}; };
#define GX_AND_TRACY_SCOPED(name) GXScopedDebugGroup scope(name); ZoneScopedN(name); #define GX_AND_TRACY_SCOPED(name) GXScopedDebugGroup scope(name); ZoneScopedN(name);
-122
View File
@@ -1,122 +0,0 @@
#pragma once
#include <cstring>
#include <memory>
#include <type_traits>
#include "dusk/mod_api.h"
namespace dusk {
inline DuskModAPI* g_api = nullptr;
inline void init(DuskModAPI* api) { g_api = api; }
template <class T>
T arg(void* args_raw, int n) noexcept {
void** a = static_cast<void**>(args_raw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T>>>(a[n]);
}
template <class T>
std::remove_reference_t<T>& argRef(void* args_raw, int n) noexcept {
void** a = static_cast<void**>(args_raw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T>>>(a[n]);
}
template <class F>
void* mfpAddr(F fn) noexcept {
void* p = nullptr;
static_assert(sizeof(fn) >= sizeof(void*), "unexpected MFP size");
std::memcpy(&p, &fn, sizeof(void*));
return p;
}
template <auto MFP, class R, class Self, class Orig, class... A>
struct HookEntryBase {
static inline Orig g_orig = nullptr;
static R trampoline(Self self, A... args) {
void* ptrs[] = {static_cast<void*>(std::addressof(self)), static_cast<void*>(std::addressof(args))...};
if constexpr (std::is_void_v<R>) {
const bool cancel = g_api->hook_dispatch_pre(mfpAddr(MFP), static_cast<void*>(ptrs), nullptr);
if (!cancel) g_orig(self, args...);
g_api->hook_dispatch_post(mfpAddr(MFP), static_cast<void*>(ptrs), nullptr);
} else {
R result{};
const bool cancel = g_api->hook_dispatch_pre(mfpAddr(MFP), static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
if (!cancel) result = g_orig(self, args...);
g_api->hook_dispatch_post(mfpAddr(MFP), static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
return result;
}
}
};
template <auto FP, class R, class Orig, class... A>
struct HookEntryFreeBase {
static inline Orig g_orig = nullptr;
static R trampoline(A... args) {
if constexpr (sizeof...(A) == 0) {
if constexpr (std::is_void_v<R>) {
const bool cancel = g_api->hook_dispatch_pre(mfpAddr(FP), nullptr, nullptr);
if (!cancel) g_orig(args...);
g_api->hook_dispatch_post(mfpAddr(FP), nullptr, nullptr);
} else {
R result{};
const bool cancel = g_api->hook_dispatch_pre(mfpAddr(FP), nullptr, static_cast<void*>(std::addressof(result)));
if (!cancel) result = g_orig(args...);
g_api->hook_dispatch_post(mfpAddr(FP), nullptr, static_cast<void*>(std::addressof(result)));
return result;
}
} else {
void* ptrs[] = {static_cast<void*>(std::addressof(args))...};
if constexpr (std::is_void_v<R>) {
const bool cancel = g_api->hook_dispatch_pre(mfpAddr(FP), static_cast<void*>(ptrs), nullptr);
if (!cancel) g_orig(args...);
g_api->hook_dispatch_post(mfpAddr(FP), static_cast<void*>(ptrs), nullptr);
} else {
R result{};
const bool cancel = g_api->hook_dispatch_pre(mfpAddr(FP), static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
if (!cancel) result = g_orig(args...);
g_api->hook_dispatch_post(mfpAddr(FP), static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
return result;
}
}
}
};
template <auto MFP>
struct HookEntry;
template <class C, class R, class... A, R (C::*MFP)(A...)>
struct HookEntry<MFP> : HookEntryBase<MFP, R, C*, R(*)(C*, A...), A...> {};
template <class C, class R, class... A, R (C::*MFP)(A...) const>
struct HookEntry<MFP> : HookEntryBase<MFP, R, const C*, R(*)(const C*, A...), A...> {};
template <class R, class... A, R (*FP)(A...)>
struct HookEntry<FP> : HookEntryFreeBase<FP, R, R(*)(A...), A...> {};
template <auto MFP>
void hookAddPre(int32_t (*fn)(void* args)) {
using E = HookEntry<MFP>;
g_api->hook_install(mfpAddr(MFP), reinterpret_cast<void*>(E::trampoline),
reinterpret_cast<void**>(&E::g_orig));
g_api->hook_pre(mfpAddr(MFP), fn);
}
template <auto MFP>
void hookAddPost(void (*fn)(void* args, void* retval)) {
using E = HookEntry<MFP>;
g_api->hook_install(mfpAddr(MFP), reinterpret_cast<void*>(E::trampoline),
reinterpret_cast<void**>(&E::g_orig));
g_api->hook_post(mfpAddr(MFP), fn);
}
template <auto MFP>
void hookSetReplace(void (*fn)(void* args, void* retval)) {
using E = HookEntry<MFP>;
g_api->hook_install(mfpAddr(MFP), reinterpret_cast<void*>(E::trampoline),
reinterpret_cast<void**>(&E::g_orig));
g_api->hook_replace(mfpAddr(MFP), fn);
}
} // namespace dusk
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include <cstdint>
namespace dusk {
void hookInstallByAddr(void* fn_addr, void* tramp_fn, void** orig_store);
void hookRegisterPre (void* fn_addr, void* mod, int32_t (*fn)(void* args));
void hookRegisterPost(void* fn_addr, void* mod, const char* mod_name, void (*fn)(void* args, void* retval));
bool hookSetReplace (void* fn_addr, void* mod, const char* mod_name, void (*fn)(void* args, void* retval));
bool hookDispatchPre (void* fn_addr, void* args, void* retval);
void hookDispatchPost(void* fn_addr, void* args, void* retval);
void hookClearMod(void* mod);
} // namespace dusk
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include <cstddef>
#include <cstdint>
#if defined(_WIN32)
#define DUSK_MOD_EXPORT __declspec(dllexport)
#else
#define DUSK_MOD_EXPORT __attribute__((visibility("default")))
#endif
#define DUSK_MOD_API_VERSION 1
typedef void* DuskPanelHandle;
typedef void* DuskElemHandle;
// Place this once at file scope in your mod to declare the minimum API version required.
// The loader will refuse to initialize the mod if the engine's API version is older.
#define DUSK_REQUIRE_API_VERSION \
extern "C" DUSK_MOD_EXPORT uint32_t mod_api_version = DUSK_MOD_API_VERSION;
struct DuskModAPIv1 {
uint32_t api_version;
const char* mod_dir;
void (*log_info)(const char* fmt, ...);
void (*log_warn)(const char* fmt, ...);
void (*log_error)(const char* fmt, ...);
void* (*load_resource)(const char* relative_path, size_t* out_size);
void (*free_resource)(void* data);
void (*register_tab_content)(
void (*build_fn)(DuskPanelHandle panel, void* userdata), void* userdata);
void (*register_tab_update)(void (*update_fn)(void* userdata), void* userdata);
void (*panel_add_section)(DuskPanelHandle panel, const char* text);
void (*panel_add_button)(
DuskPanelHandle panel, const char* label, void (*cb)(void* userdata), void* userdata);
DuskElemHandle (*panel_add_badge_row)(DuskPanelHandle panel, const char* label, int ok);
DuskElemHandle (*panel_add_dyn_text)(DuskPanelHandle panel, const char* text);
DuskElemHandle (*panel_add_progress)(DuskPanelHandle panel, float value);
void (*elem_set_badge)(DuskElemHandle elem, int ok);
void (*elem_set_text)(DuskElemHandle elem, const char* text);
void (*elem_set_progress)(DuskElemHandle elem, float value);
void (*hook_install)(void* fn_addr, void* tramp_fn, void** orig_store);
void (*hook_pre)(void* fn_addr, int32_t (*fn)(void* args));
void (*hook_post)(void* fn_addr, void (*fn)(void* args, void* retval));
void (*hook_replace)(void* fn_addr, void (*fn)(void* args, void* retval));
bool (*hook_dispatch_pre)(void* fn_addr, void* args, void* retval);
void (*hook_dispatch_post)(void* fn_addr, void* args, void* retval);
void (*service_publish)(const char* name, void* ptr);
void* (*service_get)(const char* name);
};
using DuskModAPI = DuskModAPIv1;
extern "C" {
void mod_init(DuskModAPI* api);
void mod_tick(DuskModAPI* api);
}
-134
View File
@@ -1,134 +0,0 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include <ranges>
#include "dusk/mod_api.h"
#include "dusk/config_var.hpp"
namespace dusk::modding {
class ModBundle;
class NativeModule;
}
namespace dusk {
struct RmlTabContentCallback {
void (*build_fn)(void* panel, void* userdata);
void* userdata;
};
struct RmlTabUpdateCallback {
void (*update_fn)(void* userdata);
void* userdata;
};
struct ModMetadata {
std::string id;
std::string name;
std::string version;
std::string author;
std::string description;
bool hasCode;
};
struct NativeMod {
std::unique_ptr<modding::NativeModule> handle;
DuskModAPI api{};
using FnInit = void (*)(DuskModAPI*);
using FnTick = void (*)(DuskModAPI*);
using FnCleanup = void (*)(DuskModAPI*);
FnInit fn_init = nullptr;
FnTick fn_tick = nullptr;
FnCleanup fn_cleanup = nullptr;
};
enum class NativeModStatus : u8 {
/**
* Mod does not have native code included.
*/
None,
/**
* Native code mod loaded successfully.
*
* Note that this only indicates load status of the native library. If the native lib throws in
* its init function, it will still be disabled!
*/
Loaded,
/**
* This build was compiled without native mod support!
*/
BuildDisabled,
/**
* Mod does not have a native library suitable for this build's platform.
*/
ModMissingPlatform,
/**
* Mod is built for a different API version than this build of the game.
*/
ApiVersionMismatch,
/**
* Unknown error loading the native mod.
*/
Unknown,
};
struct LoadedMod {
ModMetadata metadata;
std::string mod_path;
std::string dir;
std::unique_ptr<ConfigVar<bool>> cvarIsEnabled;
bool active = false;
bool load_failed = false;
NativeModStatus native_status = NativeModStatus::None;
std::unique_ptr<NativeMod> native;
std::unique_ptr<modding::ModBundle> bundle;
std::vector<RmlTabContentCallback> tab_content;
std::vector<RmlTabUpdateCallback> tab_updates;
};
class ModLoader {
public:
static ModLoader& instance();
void setModsDir(std::filesystem::path dir) { m_modsDir = std::move(dir); }
void init();
void tick();
void shutdown();
[[nodiscard]] auto mods() const {
return m_mods | std::views::transform([](const auto& m) -> LoadedMod& { return *m; });
}
[[nodiscard]] auto active_mods() const {
return mods() | std::views::filter([](const auto& m) { return m.active; });
}
private:
std::vector<std::unique_ptr<LoadedMod>> m_mods;
std::filesystem::path m_modsDir;
bool m_initialized = false;
void tryLoadDusk(const std::filesystem::path& modPath, bool fromDir);
void tryLoadNativeMod(LoadedMod& mod);
void buildAPI(LoadedMod& mod);
void initOverlayFiles();
};
using ModIndex = std::ranges::range_difference_t<decltype(std::declval<ModLoader>().mods())>;
} // namespace dusk
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include "f_op/f_op_actor_mng.h"
#include "f_pc/f_pc_layer.h"
#include "f_pc/f_pc_manager.h"
#include "f_pc/f_pc_node.h"
#include "m_Do/m_Do_controller_pad.h"
// Remove a button from this frame's trigger state so the game won't see it
// Call after detecting a combo in mod_tick to prevent double-processing
inline void consumeInput(u32 pad, u32 buttonMask) {
mDoCPd_c::getCpadInfo(pad).mPressedButtonFlags &= ~buttonMask;
}
// Spawn an actor in the play scene layer
// calling fopAcM_create directly outside game simulation context creates the actor in the wrong
// layer, corrupting its first-frame rendering setup
inline fpc_ProcID fopAcM_createInPlayScene(s16 proc_name, u32 params, const cXyz* pos, int room_no,
const csXyz* angle, const cXyz* scale, s8 argument) {
layer_class* savedLayer = fpcLy_CurrentLayer();
base_process_class* playScene = fpcM_SearchByName(fpcNm_PLAY_SCENE_e);
if (playScene != nullptr) {
fpcLy_SetCurrentLayer(&((process_node_class*)playScene)->layer);
}
fpc_ProcID result = fopAcM_create(proc_name, params, pos, room_no, angle, scale, argument);
fpcLy_SetCurrentLayer(savedLayer);
return result;
}
+4 -4
View File
@@ -4,9 +4,9 @@
namespace dusk::mouse { namespace dusk::mouse {
void read(); void read();
void getAimDeltas(float& out_yaw, float& out_pitch); void get_aim_deltas(float& out_yaw, float& out_pitch);
void getCameraDeltas(float& out_yaw, float& out_pitch); void get_camera_deltas(float& out_yaw, float& out_pitch);
void handle_event(const SDL_Event& event) noexcept; void handle_event(const SDL_Event& event) noexcept;
void onFocusLost(); void on_focus_lost();
void onFocusGained(); void on_focus_gained();
} // namespace dusk::mouse } // namespace dusk::mouse
+15 -1
View File
@@ -7,7 +7,8 @@
namespace dusk { namespace dusk {
using namespace config; using config::ConfigVar;
using config::ActionBindConfigVar;
enum class BloomMode : int { enum class BloomMode : int {
Off = 0, Off = 0,
@@ -46,6 +47,12 @@ enum class FrameInterpMode : u8 {
Unlimited = 2, Unlimited = 2,
}; };
enum class TouchTargeting : u8 {
Hybrid = 0,
Hold = 1,
Switch = 2,
};
enum class MenuScaling : u8 { enum class MenuScaling : u8 {
GameCube = 0, GameCube = 0,
Wii = 1, Wii = 1,
@@ -97,6 +104,12 @@ struct ConfigEnumRange<FrameInterpMode> {
static constexpr auto max = FrameInterpMode::Unlimited; static constexpr auto max = FrameInterpMode::Unlimited;
}; };
template <>
struct ConfigEnumRange<TouchTargeting> {
static constexpr auto min = TouchTargeting::Hybrid;
static constexpr auto max = TouchTargeting::Switch;
};
template <> template <>
struct ConfigEnumRange<MenuScaling> { struct ConfigEnumRange<MenuScaling> {
static constexpr auto min = MenuScaling::GameCube; static constexpr auto min = MenuScaling::GameCube;
@@ -216,6 +229,7 @@ struct UserSettings {
ConfigVar<bool> invertMouseY; ConfigVar<bool> invertMouseY;
ConfigVar<bool> freeCamera; ConfigVar<bool> freeCamera;
ConfigVar<bool> enableTouchControls; ConfigVar<bool> enableTouchControls;
ConfigVar<TouchTargeting> touchTargeting;
ConfigVar<bool> enableMenuPointer; ConfigVar<bool> enableMenuPointer;
ConfigVar<ui::ControlLayout> touchControlsLayout; ConfigVar<ui::ControlLayout> touchControlsLayout;
ConfigVar<bool> invertCameraXAxis; ConfigVar<bool> invertCameraXAxis;
+1 -1
View File
@@ -36,7 +36,7 @@ typedef struct node_create_request {
/* 0x58 */ s16 name; /* 0x58 */ s16 name;
/* 0x5C */ void* data; /* 0x5C */ void* data;
/* 0x60 */ s16 unk_0x60; /* 0x60 */ s16 unk_0x60;
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x64 */ int unk_0x64; /* 0x64 */ int unk_0x64;
/* 0x68 */ int unk_0x68; /* 0x68 */ int unk_0x68;
#endif #endif
-13
View File
@@ -114,19 +114,6 @@ inline int __builtin_clz(unsigned int v) {
#endif #endif
// Data symbols in dusk.dll need dllimport on the mod side
// DUSK_BUILDING_GAME is defined for the game build so the same headers work in both.
#if defined(TARGET_PC) && defined(_WIN32) && !defined(DUSK_BUILDING_GAME)
# define DUSK_GAME_EXTERN extern __declspec(dllimport)
# define DUSK_GAME_DATA __declspec(dllimport)
#elif defined(TARGET_PC) && defined(_WIN32) && defined(DUSK_BUILDING_GAME)
# define DUSK_GAME_EXTERN extern __declspec(dllexport)
# define DUSK_GAME_DATA __declspec(dllexport)
#else
# define DUSK_GAME_EXTERN extern
# define DUSK_GAME_DATA
#endif
#define FAST_DIV(x, n) (x >> (n / 2)) #define FAST_DIV(x, n) (x >> (n / 2))
#define SQUARE(x) ((x) * (x)) #define SQUARE(x) ((x) * (x))
+3 -3
View File
@@ -11,13 +11,13 @@ class mDoAud_zelAudio_c : public Z2AudioMgr {
public: public:
void reset(); void reset();
mDoAud_zelAudio_c() { mDoAud_zelAudio_c() {
#if DEBUG #if PARTIAL_DEBUG || DEBUG
setMode(2); setMode(2);
#endif #endif
} }
~mDoAud_zelAudio_c() {} ~mDoAud_zelAudio_c() {}
#if DEBUG #if PARTIAL_DEBUG || DEBUG
u8 getMode() { return field_0x13bd; } u8 getMode() { return field_0x13bd; }
void setMode(u8 mode) { field_0x13bd = mode; } void setMode(u8 mode) { field_0x13bd = mode; }
@@ -33,7 +33,7 @@ public:
static void onBgmSet() { mBgmSet = true; } static void onBgmSet() { mBgmSet = true; }
static void offBgmSet() { mBgmSet = false; } static void offBgmSet() { mBgmSet = false; }
static DUSK_GAME_DATA u8 mInitFlag; static u8 mInitFlag;
static u8 mResetFlag; static u8 mResetFlag;
static u8 mBgmSet; static u8 mBgmSet;
}; };
+1 -2
View File
@@ -4,7 +4,6 @@
#include "JSystem/JUtility/JUTGamePad.h" #include "JSystem/JUtility/JUTGamePad.h"
#include "SSystem/SComponent/c_API_controller_pad.h" #include "SSystem/SComponent/c_API_controller_pad.h"
#include "dusk/settings.h" #include "dusk/settings.h"
#include "global.h"
// Controller Ports 1 - 4 // Controller Ports 1 - 4
enum { PAD_1, PAD_2, PAD_3, PAD_4 }; enum { PAD_1, PAD_2, PAD_3, PAD_4 };
@@ -95,7 +94,7 @@ public:
static void stopMotorWaveHard(u32 pad) { return m_gamePad[pad]->stopMotorWaveHard(); } static void stopMotorWaveHard(u32 pad) { return m_gamePad[pad]->stopMotorWaveHard(); }
static JUTGamePad* m_gamePad[4]; static JUTGamePad* m_gamePad[4];
static DUSK_GAME_DATA interface_of_controller_pad m_cpadInfo[4]; static interface_of_controller_pad m_cpadInfo[4];
static interface_of_controller_pad m_debugCpadInfo[4]; static interface_of_controller_pad m_debugCpadInfo[4];
}; };
+1 -1
View File
@@ -371,7 +371,6 @@ public:
static int m_height; static int m_height;
static f32 m_heightF; static f32 m_heightF;
static f32 m_widthF; static f32 m_widthF;
#endif
#if TARGET_PC #if TARGET_PC
static f32 m_safeMinXF; static f32 m_safeMinXF;
@@ -381,6 +380,7 @@ public:
static f32 m_safeWidthF; static f32 m_safeWidthF;
static f32 m_safeHeightF; static f32 m_safeHeightF;
#endif #endif
#endif
}; };
#endif /* M_DO_M_DO_GRAPHIC_H */ #endif /* M_DO_M_DO_GRAPHIC_H */
+5
View File
@@ -35,6 +35,11 @@ public:
/* 0x4 */ s8 mNo; /* 0x4 */ s8 mNo;
/* 0x5 */ u8 mCount; /* 0x5 */ u8 mCount;
#else #else
#if PARTIAL_DEBUG
// Initialized here since the DEBUG ctor doesn't run.
/* 0x4 */ s8 mNo = -1;
/* 0x5 */ u8 mCount = 0;
#endif
virtual ~mDoHIO_entry_c() {} virtual ~mDoHIO_entry_c() {}
#endif #endif
}; };
@@ -12,12 +12,23 @@ struct JORNodeEvent;
class JORMContext; class JORMContext;
class JORServer; class JORServer;
// NOTE (stable game ABI): these classes stay non-polymorphic outside DEBUG
// on purpose. Making them polymorphic under PARTIAL_DEBUG would give every one of the ~250
// derived HIO classes a vptr and turn their plain `void genMessage(JORMContext*);`
// declarations into implicit virtual overrides whose definitions are #if DEBUG-gated; every
// instantiated one then fails to link (missing vtable). Closure types shared with DEBUG TUs
// either declare their own unconditional virtuals (vptr in all TUs anyway) or add a
// PARTIAL_DEBUG-only virtual dtor for vptr parity (see dAttParam_c).
class JOREventListener { class JOREventListener {
public: public:
#if DEBUG #if DEBUG
JOREventListener() {} JOREventListener() {}
#if TARGET_PC
virtual void listenPropertyEvent(const JORPropertyEvent*) {}
#else
virtual void listenPropertyEvent(const JORPropertyEvent*) = 0; virtual void listenPropertyEvent(const JORPropertyEvent*) = 0;
#endif #endif
#endif
}; };
class JORReflexible : public JOREventListener { class JORReflexible : public JOREventListener {
@@ -30,7 +41,11 @@ public:
virtual void listenPropertyEvent(const JORPropertyEvent*); virtual void listenPropertyEvent(const JORPropertyEvent*);
virtual void listen(u32, const JOREvent*); virtual void listen(u32, const JOREvent*);
virtual void genObjectInfo(const JORGenEvent*); virtual void genObjectInfo(const JORGenEvent*);
#if TARGET_PC
virtual void genMessage(JORMContext*) {}
#else
virtual void genMessage(JORMContext*) = 0; virtual void genMessage(JORMContext*) = 0;
#endif
virtual void listenNodeEvent(const JORNodeEvent*); virtual void listenNodeEvent(const JORNodeEvent*);
#endif #endif
}; };
@@ -4,7 +4,6 @@
#include <types.h> #include <types.h>
#include <cmath> #include <cmath>
#include <utility> #include <utility>
#include "global.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -142,9 +141,9 @@ struct TAsinAcosTable {
} }
}; };
DUSK_GAME_EXTERN TSinCosTable<13, f32> sincosTable_; extern TSinCosTable<13, f32> sincosTable_;
DUSK_GAME_EXTERN TAtanTable<1024, f32> atanTable_; extern TAtanTable<1024, f32> atanTable_;
DUSK_GAME_EXTERN TAsinAcosTable<1024, f32> asinAcosTable_; extern TAsinAcosTable<1024, f32> asinAcosTable_;
inline f32 acosDegree(f32 x) { inline f32 acosDegree(f32 x) {
return asinAcosTable_.acosDegree(x); return asinAcosTable_.acosDegree(x);
@@ -314,7 +314,7 @@ public:
> >
{ {
TIterator_data_(const TFunctionValue_list_parameter& rParent, const f32* value) { TIterator_data_(const TFunctionValue_list_parameter& rParent, const f32* value) {
#if DEBUG #if PARTIAL_DEBUG || DEBUG
pOwn_ = &rParent; pOwn_ = &rParent;
#endif #endif
pf_ = value; pf_ = value;
@@ -372,7 +372,7 @@ public:
return (r1.pf_ - r2.pf_) / suData_size; return (r1.pf_ - r2.pf_) / suData_size;
} }
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x00 */ const TFunctionValue_list_parameter* pOwn_; /* 0x00 */ const TFunctionValue_list_parameter* pOwn_;
#endif #endif
/* 0x00 */ const f32* pf_; /* 0x00 */ const f32* pf_;
@@ -425,7 +425,7 @@ public:
> >
{ {
TIterator_data_(const TFunctionValue_hermite& rParent, const f32* value) { TIterator_data_(const TFunctionValue_hermite& rParent, const f32* value) {
#if DEBUG #if PARTIAL_DEBUG || DEBUG
pOwn_ = &rParent; pOwn_ = &rParent;
#endif #endif
pf_ = value; pf_ = value;
@@ -491,7 +491,7 @@ public:
return (r1.pf_ - r2.pf_) / r1.uSize_; return (r1.pf_ - r2.pf_) / r1.uSize_;
} }
#if DEBUG #if PARTIAL_DEBUG || DEBUG
/* 0x00 */ const TFunctionValue_hermite* pOwn_; /* 0x00 */ const TFunctionValue_hermite* pOwn_;
/* 0x04 */ const f32* pf_; /* 0x04 */ const f32* pf_;
/* 0x08 */ u32 uSize_; /* 0x08 */ u32 uSize_;
+3 -3
View File
@@ -16,10 +16,10 @@ inline f64 getConst2() {
return 9.765625E-4; return 9.765625E-4;
} }
DUSK_GAME_DATA TSinCosTable<13, f32> sincosTable_ ATTRIBUTE_ALIGN(32); TSinCosTable<13, f32> sincosTable_ ATTRIBUTE_ALIGN(32);
DUSK_GAME_DATA TAtanTable<1024, f32> atanTable_ ATTRIBUTE_ALIGN(32); TAtanTable<1024, f32> atanTable_ ATTRIBUTE_ALIGN(32);
DUSK_GAME_DATA TAsinAcosTable<1024, f32> asinAcosTable_ ATTRIBUTE_ALIGN(32); TAsinAcosTable<1024, f32> asinAcosTable_ ATTRIBUTE_ALIGN(32);
} // namespace JMath } // namespace JMath
@@ -22,7 +22,7 @@
It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience. It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
</p> </p>
</description> </description>
<provides> <provides>
<binary>dusklight</binary> <binary>dusklight</binary>
<id>dev.twilitrealm.dusk.desktop</id> <id>dev.twilitrealm.dusk.desktop</id>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
-4
View File
@@ -362,10 +362,6 @@ body.animate-in .intro-item {
transition: opacity transform 0.3s 0.6s cubic-in-out; transition: opacity transform 0.3s 0.6s cubic-in-out;
} }
.delay-6 {
transition: opacity transform 0.3s 0.7s cubic-in-out;
}
/* Mobile layout */ /* Mobile layout */
@media (max-height: 640dp) { @media (max-height: 640dp) {
.gradient { .gradient {
-29
View File
@@ -395,11 +395,6 @@ progress.progress-ongoing fill {
border-radius: 3dp; border-radius: 3dp;
} }
progress.progress-health fill {
background-color: #cc3322;
border-radius: 3dp;
}
button.achievement-clear { button.achievement-clear {
flex: 0 0 auto; flex: 0 0 auto;
align-self: center; align-self: center;
@@ -514,30 +509,6 @@ progress.verification-progress-bar {
color: rgba(224, 219, 200, 65%); color: rgba(224, 219, 200, 65%);
} }
.mod-info-row {
display: flex;
align-items: center;
gap: 12dp;
padding: 4dp 0;
}
.mod-info-label {
font-family: "Fira Sans Condensed";
font-weight: bold;
opacity: 0.55;
flex: 0 0 80dp;
}
.mod-info-value {
flex: 1 1 0;
}
.mod-path {
font-size: 14dp;
word-break: break-all;
opacity: 0.7;
}
.modal-actions { .modal-actions {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
-6
View File
@@ -142,12 +142,6 @@ DynamicModuleControl::DynamicModuleControl(char const* name) {
} }
#endif #endif
#if TARGET_PC
// dump() is declared but its definition is inside #if !TARGET_PC above; stub it out.
void DynamicModuleControlBase::dump() {}
void DynamicModuleControlBase::dump(char*) {}
#endif
u32 DynamicModuleControl::sAllocBytes; u32 DynamicModuleControl::sAllocBytes;
JKRArchive* DynamicModuleControl::sArchive; JKRArchive* DynamicModuleControl::sArchive;
+1 -1
View File
@@ -19,7 +19,7 @@
#include "Z2AudioCS/Z2AudioCS.h" #include "Z2AudioCS/Z2AudioCS.h"
#endif #endif
DUSK_GAME_DATA Z2AudioMgr* Z2AudioMgr::mAudioMgrPtr; Z2AudioMgr* Z2AudioMgr::mAudioMgrPtr;
u8 gMuffleOutOfRangeMic = false; u8 gMuffleOutOfRangeMic = false;
Z2AudioMgr::Z2AudioMgr() : mSoundStarter(true) { Z2AudioMgr::Z2AudioMgr() : mSoundStarter(true) {
+1 -1
View File
@@ -22,7 +22,7 @@
#include "os_report.h" #include "os_report.h"
Z2SoundObjBase::Z2SoundObjBase() Z2SoundObjBase::Z2SoundObjBase()
#if DEBUG #if PARTIAL_DEBUG || DEBUG
: JSULink<Z2SoundObjBase>(this) : JSULink<Z2SoundObjBase>(this)
#endif #endif
{ {
+5 -12
View File
@@ -122,18 +122,11 @@ BOOL daAlink_c::setBodyAngleToCamera() {
var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c); var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c);
} }
#if TARGET_PC shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f)));
if (dusk::getSettings().game.enableMouseAim && checkAimInputContext()) { sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f)));
sp8 = mBodyAngle.x;
} else
#endif
{
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f)));
sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f)));
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) { if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x; sp8 = mBodyAngle.x;
}
} }
} else { } else {
sp8 = mBodyAngle.x; sp8 = mBodyAngle.x;
@@ -156,7 +149,7 @@ BOOL daAlink_c::setBodyAngleToCamera() {
f32 final_yaw = 0.f; f32 final_yaw = 0.f;
f32 final_pitch = 0.f; f32 final_pitch = 0.f;
if (dusk::getSettings().game.enableMouseAim) { if (dusk::getSettings().game.enableMouseAim) {
dusk::mouse::getAimDeltas(final_yaw, final_pitch); dusk::mouse::get_aim_deltas(final_yaw, final_pitch);
} }
if (dusk::getSettings().game.enableGyroAim) { if (dusk::getSettings().game.enableGyroAim) {
f32 gyro_yaw = 0.f; f32 gyro_yaw = 0.f;
+22 -3
View File
@@ -11,6 +11,7 @@
#include "d/d_com_inf_game.h" #include "d/d_com_inf_game.h"
#if TARGET_PC #if TARGET_PC
#include <aurora/texture.hpp>
#include "dusk/dvd_asset.hpp" #include "dusk/dvd_asset.hpp"
#include "dusk/frame_interpolation.h" #include "dusk/frame_interpolation.h"
@@ -40,6 +41,8 @@ static f32* l_texCoord_get() { alignas(32) static f32 buf[338]; static bool _
//#define l_pos (l_pos_get()) //#define l_pos (l_pos_get())
#define l_normal (l_normal_get()) #define l_normal (l_normal_get())
#define l_texCoord (l_texCoord_get()) #define l_texCoord (l_texCoord_get())
static bool l_Egnd_mantTEX_hasReplacement = false;
#else #else
#include "assets/l_Egnd_mantTEX.h" #include "assets/l_Egnd_mantTEX.h"
@@ -223,6 +226,7 @@ void daMant_packet_c::draw() {
GXInitTexObjCI( GXInitTexObjCI(
&undersideTexObj, l_Egnd_mantTEX_U, 0x80, 0x80, GX_TF_C8, GX_CLAMP, GX_CLAMP, 0, 0); &undersideTexObj, l_Egnd_mantTEX_U, 0x80, 0x80, GX_TF_C8, GX_CLAMP, GX_CLAMP, 0, 0);
GXInitTexObjLOD(&undersideTexObj, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, 0, 0, GX_ANISO_1); GXInitTexObjLOD(&undersideTexObj, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, 0, 0, GX_ANISO_1);
l_Egnd_mantTEX_hasReplacement = aurora::texture::has_replacement(&mainTexObj, &tlutObj);
textureObjsInitialized = true; textureObjsInitialized = true;
} }
#else #else
@@ -636,7 +640,11 @@ static int daMant_Execute(mant_class* i_this) {
iVar8 = 0; iVar8 = 0;
if (i_this->field_0x3967 != 0) { if (i_this->field_0x3967 != 0) {
#if TARGET_PC
mant_cut_type = l_Egnd_mantTEX_hasReplacement ? 1 : i_this->field_0x3967;
#else
mant_cut_type = i_this->field_0x3967; mant_cut_type = i_this->field_0x3967;
#endif
if (i_this->field_0x3968 < 15) { if (i_this->field_0x3968 < 15) {
i_this->field_0x3968++; i_this->field_0x3968++;
@@ -648,9 +656,18 @@ static int daMant_Execute(mant_class* i_this) {
iVar8 = 20; iVar8 = 20;
} }
unaff_r29 = cM_rndF(65536.0f); #if TARGET_PC
var_f31 = cM_rndFX(32.0f); if (l_Egnd_mantTEX_hasReplacement) {
var_f30 = cM_rndFX(32.0f); unaff_r29 = i_this->mMantRng.getF(65536.0f);
var_f31 = i_this->mMantRng.getFX(32.0f);
var_f30 = i_this->mMantRng.getFX(32.0f);
} else
#endif
{
unaff_r29 = cM_rndF(65536.0f);
var_f31 = cM_rndFX(32.0f);
var_f30 = cM_rndFX(32.0f);
}
} }
i_this->field_0x3967 = 0; i_this->field_0x3967 = 0;
@@ -760,6 +777,8 @@ static int daMant_Create(fopAc_ac_c* i_this) {
if(textureObjsInitialized) { if(textureObjsInitialized) {
GXInitTlutObjData(&tlutObj, l_Egnd_mantPAL); // make sure the cached textures are updated GXInitTlutObjData(&tlutObj, l_Egnd_mantPAL); // make sure the cached textures are updated
} }
m_this->mMantRng.init(66, 16983, 855);
#endif #endif
lbl_277_bss_0 = 0; lbl_277_bss_0 = 0;
+5 -1
View File
@@ -7602,6 +7602,10 @@ bool dCamera_c::executeDebugFlyCam() {
sFlyCamLastMousePos = mouseValid ? io.MousePos : ImVec2{-1.0f, -1.0f}; sFlyCamLastMousePos = mouseValid ? io.MousePos : ImVec2{-1.0f, -1.0f};
} }
if (dusk::getSettings().game.enableMirrorMode) {
stickX *= -1.0f;
}
f32 verticalDisp = 0.0f; f32 verticalDisp = 0.0f;
if (trigR >= FLYCAM_TRIGGER_DEADZONE) { if (trigR >= FLYCAM_TRIGGER_DEADZONE) {
verticalDisp += trigR; verticalDisp += trigR;
@@ -7712,7 +7716,7 @@ bool dCamera_c::freeCamera() {
f32 yaw_rad = 0.0f; f32 yaw_rad = 0.0f;
f32 pitch_rad = 0.0f; f32 pitch_rad = 0.0f;
dusk::mouse::getCameraDeltas(yaw_rad, pitch_rad); dusk::mouse::get_camera_deltas(yaw_rad, pitch_rad);
if (dusk::getSettings().game.enableMouseCamera && (yaw_rad != 0.0f || pitch_rad != 0.0f) && if (dusk::getSettings().game.enableMouseCamera && (yaw_rad != 0.0f || pitch_rad != 0.0f) &&
!dComIfGp_checkCameraAttentionStatus(dComIfGp_getPlayerCameraID(0), 0x8)) !dComIfGp_checkCameraAttentionStatus(dComIfGp_getPlayerCameraID(0), 0x8))
{ {
+1 -1
View File
@@ -118,7 +118,7 @@ static int dKyeff_Create(kankyo_class* i_this) {
if (strcmp(dComIfGp_getStartStageName(), "Name") == 0) { if (strcmp(dComIfGp_getStartStageName(), "Name") == 0) {
camera_process_class* camera = dComIfGp_getCamera(0); camera_process_class* camera = dComIfGp_getCamera(0);
OSTime time = OSGetTime(); OSTime time = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime());
OSTicksToCalendarTime(time, &calendar); OSTicksToCalendarTime(time, &calendar);
g_env_light.global_wind_influence.vec.x = 1.0f; g_env_light.global_wind_influence.vec.x = 1.0f;
+6 -1
View File
@@ -1640,7 +1640,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) mPackX; mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) mPackX;
mCenterZ -= mPackZ; mCenterZ -= mPackZ;
mCenterX += field_0x64; mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -field_0x64 : ) field_0x64;
mCenterZ += mPackPlusZ; mCenterZ += mPackPlusZ;
} }
@@ -1649,6 +1649,8 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
{ {
#if DEBUG #if DEBUG
field_0x64 = 33830.0f; field_0x64 = 33830.0f;
#elif TARGET_PC
field_0x64 = dusk::getSettings().game.enableMirrorMode ? 33830.0f : 0.0f;
#else #else
field_0x64 = 0.0f; field_0x64 = 0.0f;
#endif #endif
@@ -1657,6 +1659,9 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
f32 temp = (field_0x58 * (f32)(field_0x74 + 4)) * 0.5f; f32 temp = (field_0x58 * (f32)(field_0x74 + 4)) * 0.5f;
#if DEBUG #if DEBUG
mRightEdgePlus = -(((dMpath_c::getMinZ() - (-127103.67f)) - temp) / field_0x58); mRightEdgePlus = -(((dMpath_c::getMinZ() - (-127103.67f)) - temp) / field_0x58);
#elif TARGET_PC
mRightEdgePlus = dusk::getSettings().game.enableMirrorMode ? -(((dMpath_c::getMinX() - (-127103.67f)) - temp) / field_0x58) : 0.0f;
#else #else
mRightEdgePlus = 0.0f; mRightEdgePlus = 0.0f;
#endif #endif
+24 -1
View File
@@ -914,6 +914,20 @@ void dMenu_DmapBg_c::dMapBgWide() {
void dMenu_DmapBg_c::draw() { void dMenu_DmapBg_c::draw() {
#if TARGET_PC #if TARGET_PC
dMapBgWide(); dMapBgWide();
static bool prevMirror = false; // default state of panes is not mirrored
if(prevMirror != dusk::getSettings().game.enableMirrorMode) {
if(dusk::getSettings().game.enableMirrorMode) {
static_cast<J2DPicture*>(mFloorScreen->search(MULTI_CHAR('rink')))->setMirror(J2DMirror_X);
static_cast<J2DPicture*>(mBaseScreen->search(MULTI_CHAR('map000')))->setMirror(J2DMirror_X);
}
else {
static_cast<J2DPicture*>(mFloorScreen->search(MULTI_CHAR('rink')))->setMirror(MIRROR0);
static_cast<J2DPicture*>(mBaseScreen->search(MULTI_CHAR('map000')))->setMirror(MIRROR0);
}
prevMirror = dusk::getSettings().game.enableMirrorMode;
}
#endif #endif
u32 scissor_left; u32 scissor_left;
@@ -960,6 +974,15 @@ void dMenu_DmapBg_c::draw() {
mpBackTexture->setAlpha(dVar17 * (field_0xdbc * field_0xd9c)); mpBackTexture->setAlpha(dVar17 * (field_0xdbc * field_0xd9c));
f32 local_28c = mpBackTexture->getBounds().i.x; f32 local_28c = mpBackTexture->getBounds().i.x;
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
CPaneMgr mgr;
Vec local_94 = mgr.getGlobalVtxCenter(mMapPane, true, 0);
local_28c = (local_94.x * 2.0f) - (local_28c + 0.5f * mpBackTexture->getWidth()) - 0.5f * mpBackTexture->getWidth();
}
#endif
mpBackTexture->setBlackWhite(color_black, color_white); mpBackTexture->setBlackWhite(color_black, color_white);
mpBackTexture->draw(local_28c, field_0xd94 + mpBackTexture->getBounds().i.y, mpBackTexture->getWidth(), mpBackTexture->draw(local_28c, field_0xd94 + mpBackTexture->getBounds().i.y, mpBackTexture->getWidth(),
mpBackTexture->getHeight(), mpBackTexture->getHeight(),
@@ -1981,7 +2004,7 @@ void dMenu_Dmap_c::mapControl() {
f32 temp_f28 = (var_f29 / 100.0f) * var_f31; f32 temp_f28 = (var_f29 / 100.0f) * var_f31;
f32 sp18 = temp_f28 * cM_ssin(stick_angle); f32 sp18 = temp_f28 * cM_ssin(stick_angle);
f32 sp14 = temp_f28 * cM_scos(stick_angle); f32 sp14 = temp_f28 * cM_scos(stick_angle);
mMapCtrl->setPlusZoomCenterX(sp18); mMapCtrl->setPlusZoomCenterX(IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -sp18 :) sp18);
mMapCtrl->setPlusZoomCenterZ(sp14); mMapCtrl->setPlusZoomCenterZ(sp14);
} }
+1 -20
View File
@@ -359,14 +359,7 @@ f32 dMenu_StageMapCtrl_c::getPixelStageSizeZ() const {
f32 dMenu_StageMapCtrl_c::getPixelCenterX() const { f32 dMenu_StageMapCtrl_c::getPixelCenterX() const {
f32 var_f31 = dMpath_c::getCenterX(); f32 var_f31 = dMpath_c::getCenterX();
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
return (1.0f / field_0xbc) * (field_0x9c + var_f31);
}
else return (1.0f / field_0xbc) * (field_0x9c - var_f31);
#else
return (1.0f / field_0xbc) * (field_0x9c - var_f31); return (1.0f / field_0xbc) * (field_0x9c - var_f31);
#endif
} }
f32 dMenu_StageMapCtrl_c::getPixelCenterZ() const { f32 dMenu_StageMapCtrl_c::getPixelCenterZ() const {
@@ -425,18 +418,7 @@ inline static f32 rightModeCnvPos(f32 param_0) {
void dMenu_StageMapCtrl_c::cnvPosTo2Dpos(f32 param_0, f32 param_1, f32* param_2, void dMenu_StageMapCtrl_c::cnvPosTo2Dpos(f32 param_0, f32 param_1, f32* param_2,
f32* param_3) const { f32* param_3) const {
if (param_2 != NULL) { if (param_2 != NULL) {
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
*param_2 =
(0.5f * field_0x94) + rightModeCnvPos((1.0f / field_0xbc) * (field_0x9c + param_0));
} else {
*param_2 =
(0.5f * field_0x94) + rightModeCnvPos((1.0f / field_0xbc) * (param_0 - field_0x9c));
}
#else
*param_2 = (0.5f * field_0x94) + rightModeCnvPos((1.0f / field_0xbc) * (param_0 - field_0x9c)); *param_2 = (0.5f * field_0x94) + rightModeCnvPos((1.0f / field_0xbc) * (param_0 - field_0x9c));
#endif
} }
if (param_3 != NULL) { if (param_3 != NULL) {
@@ -933,8 +915,7 @@ void dMenu_StageMapCtrl_c::move() {
void dMenu_DmapMapCtrl_c::draw() { void dMenu_DmapMapCtrl_c::draw() {
if (field_0xef != 0) { if (field_0xef != 0) {
setPos(field_0xeb, field_0xec, setPos(field_0xeb, field_0xec, field_0x9c,
IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -field_0x9c :) field_0x9c,
field_0xa0, field_0xbc, true, field_0xd8); field_0xa0, field_0xbc, true, field_0xd8);
} }
} }
+10 -15
View File
@@ -931,17 +931,8 @@ void dMenu_Fmap_c::region_map_proc() {
mpDraw2DBack->regionMapMove(mpStick); mpDraw2DBack->regionMapMove(mpStick);
int stage_no, room_no; int stage_no, room_no;
#if TARGET_PC
f32 arrow_pos_x = mpDraw2DBack->getArrowPos2DX();
if (dusk::getSettings().game.enableMirrorMode) {
arrow_pos_x = mpDraw2DBack->getMirrorPosX(arrow_pos_x, 0.0f);
}
f32 pos_x = arrow_pos_x - mDoGph_gInf_c::getMinXF() - mDoGph_gInf_c::getWidthF() * 0.5f;
#else
f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF() f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF()
- mDoGph_gInf_c::getWidthF() * 0.5f; - mDoGph_gInf_c::getWidthF() * 0.5f;
#endif
f32 pos_y = mpDraw2DBack->getArrowPos2DY() - mDoGph_gInf_c::getHeightF() * 0.5f; f32 pos_y = mpDraw2DBack->getArrowPos2DY() - mDoGph_gInf_c::getHeightF() * 0.5f;
mpMenuFmapMap->getPointStagePathInnerNo(getNowFmapRegionData(), pos_x, pos_y, mpMenuFmapMap->getPointStagePathInnerNo(getNowFmapRegionData(), pos_x, pos_y,
@@ -2486,12 +2477,6 @@ void dMenu_Fmap_c::portalWarpMapMove(STControl* i_stick) {
f32 arrow_y = mpDraw2DBack->getArrowPos2DY(); f32 arrow_y = mpDraw2DBack->getArrowPos2DY();
u8 uVar6 = 0xff; u8 uVar6 = 0xff;
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
arrow_x = mpDraw2DBack->getMirrorPosX(arrow_x, 0.0f);
}
#endif
for (int i = 0; i < portal_dat->mCount; i++) { for (int i = 0; i < portal_dat->mCount; i++) {
if (portals[i].mRegionNo == mpDraw2DBack->getRegionCursor() + 1 if (portals[i].mRegionNo == mpDraw2DBack->getRegionCursor() + 1
@@ -2561,6 +2546,11 @@ void dMenu_Fmap_c::drawIcon(f32 param_0, bool param_1) {
if (mProcess == PROC_PORTAL_DEMO1) { if (mProcess == PROC_PORTAL_DEMO1) {
is_portal_demo1 = 1; is_portal_demo1 = 1;
} }
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
angle = 0x10000 - angle;
}
#endif
mpDraw2DBack->setIcon2DPos(0x11, stage_name, pos.x, pos.z, cM_sht2d(angle), mpDraw2DBack->setIcon2DPos(0x11, stage_name, pos.x, pos.z, cM_sht2d(angle),
is_portal_demo1, param_1); is_portal_demo1, param_1);
@@ -2649,6 +2639,11 @@ void dMenu_Fmap_c::drawPlayEnterIcon() {
angle = dComIfGs_getPlayerFieldLastStayAngleY(); angle = dComIfGs_getPlayerFieldLastStayAngleY();
SAFE_STRCPY(stage_name, dComIfGs_getPlayerFieldLastStayName()); SAFE_STRCPY(stage_name, dComIfGs_getPlayerFieldLastStayName());
} }
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
angle = 0x10000 - angle;
}
#endif
mpDraw2DBack->setIcon2DPos(0x15, stage_name, pos.x, pos.z, cM_sht2d(angle), 0, false); mpDraw2DBack->setIcon2DPos(0x15, stage_name, pos.x, pos.z, cM_sht2d(angle), 0, false);
} }
} }
+73 -19
View File
@@ -426,7 +426,15 @@ void dMenu_Fmap2DBack_c::draw() {
} }
mpPointParent->setAlphaRate(mArrowAlpha * mSpotTextureFadeAlpha); mpPointParent->setAlphaRate(mArrowAlpha * mSpotTextureFadeAlpha);
mpPointParent->translate(mArrowPos2DX + mTransX, mArrowPos2DY + mTransZ);
f32 drawX = mArrowPos2DX + mTransX;
#ifdef TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
drawX = getMirrorPosX(drawX, 0.0f);
}
#endif
mpPointParent->translate(drawX, mArrowPos2DY + mTransZ);
mpPointScreen->draw(0.0f, 0.0f, grafPort); mpPointScreen->draw(0.0f, 0.0f, grafPort);
} }
@@ -745,7 +753,7 @@ void dMenu_Fmap2DBack_c::zoomMapCalc(f32 i_zoom) {
f32 tmp2 = (dVar12 + (i_zoom * (centerX - dVar12))); f32 tmp2 = (dVar12 + (i_zoom * (centerX - dVar12)));
f32 tmp2_ = (dVar11 + (i_zoom * (centerY - dVar11))); f32 tmp2_ = (dVar11 + (i_zoom * (centerY - dVar11)));
field_0xf0c[mRegionCursor] = field_0xf0c[mRegionCursor] =
((tmp2 + (tmp3 * mZoom)) - mRegionMapSizeX[mRegionCursor] * mZoom * 0.5f) - ((tmp2 + (tmp3 * mZoom)) - mRegionMapSizeX[mRegionCursor] * mZoom * 0.5f) -
mRegionMinMapX[mRegionCursor]; mRegionMinMapX[mRegionCursor];
@@ -1018,6 +1026,11 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) {
f32 delta_y = speed * cM_ssin(angle); f32 delta_y = speed * cM_ssin(angle);
f32 delta_x = speed * cM_scos(angle); f32 delta_x = speed * cM_scos(angle);
#ifdef TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
delta_y = -delta_y;
}
#endif
control_xpos = control_xpos + delta_y; control_xpos = control_xpos + delta_y;
control_ypos = control_ypos + delta_x; control_ypos = control_ypos + delta_x;
} }
@@ -1046,11 +1059,6 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) {
calcAllMapPos2D((mArrowPos3DX + control_xpos) - mStageTransX, calcAllMapPos2D((mArrowPos3DX + control_xpos) - mStageTransX,
(mArrowPos3DZ + control_ypos) - mStageTransZ, &sp14, &sp10); (mArrowPos3DZ + control_ypos) - mStageTransZ, &sp14, &sp10);
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
sp14 = getMirrorPosX(sp14, 0.0f);
}
#endif
mSelectRegion = 0xff; mSelectRegion = 0xff;
for (int i = 7; i >= 0; i--) { for (int i = 7; i >= 0; i--) {
@@ -1465,6 +1473,12 @@ void dMenu_Fmap2DBack_c::worldGridDraw() {
f32 dVar8 = -mStageTransZ; f32 dVar8 = -mStageTransZ;
calcAllMapPos2D(dVar9, dVar8, &local_74, &local_78); calcAllMapPos2D(dVar9, dVar8, &local_74, &local_78);
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
local_74 = getMirrorPosX(local_74, 0.0f);
}
#endif
J2DDrawLine(local_74, mDoGph_gInf_c::getMinYF(), local_74, J2DDrawLine(local_74, mDoGph_gInf_c::getMinYF(), local_74,
mDoGph_gInf_c::getMinYF() + mDoGph_gInf_c::getHeightF(), mDoGph_gInf_c::getMinYF() + mDoGph_gInf_c::getHeightF(),
JUtility::TColor(255, 255, 255, 255), 6); JUtility::TColor(255, 255, 255, 255), 6);
@@ -1473,6 +1487,11 @@ void dMenu_Fmap2DBack_c::worldGridDraw() {
while (true) { while (true) {
calcAllMapPos2D(xPos, dVar8, &local_74, &local_78); calcAllMapPos2D(xPos, dVar8, &local_74, &local_78);
if (local_74 >= getMapScissorAreaLX()) { if (local_74 >= getMapScissorAreaLX()) {
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
local_74 = getMirrorPosX(local_74, 0.0f);
}
#endif
J2DDrawLine(local_74, mDoGph_gInf_c::getMinYF(), local_74, J2DDrawLine(local_74, mDoGph_gInf_c::getMinYF(), local_74,
mDoGph_gInf_c::getMinYF() + mDoGph_gInf_c::getHeightF(), mDoGph_gInf_c::getMinYF() + mDoGph_gInf_c::getHeightF(),
JUtility::TColor(255, 255, 255, 255), 6); JUtility::TColor(255, 255, 255, 255), 6);
@@ -1534,6 +1553,12 @@ void dMenu_Fmap2DBack_c::regionGridDraw() {
f32 dVar8 = mRegionOriginZ[mRegionCursor] - mStageTransZ; f32 dVar8 = mRegionOriginZ[mRegionCursor] - mStageTransZ;
calcAllMapPos2D(dVar9, dVar8, &local_74, &local_78); calcAllMapPos2D(dVar9, dVar8, &local_74, &local_78);
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
local_74 = getMirrorPosX(local_74, 0.0f);
}
#endif
J2DDrawLine(local_74, mDoGph_gInf_c::getMinYF(), local_74, J2DDrawLine(local_74, mDoGph_gInf_c::getMinYF(), local_74,
mDoGph_gInf_c::getMinYF() + mDoGph_gInf_c::getHeightF(), mDoGph_gInf_c::getMinYF() + mDoGph_gInf_c::getHeightF(),
JUtility::TColor(180, 0, 0, 255), 6); JUtility::TColor(180, 0, 0, 255), 6);
@@ -1542,6 +1567,12 @@ void dMenu_Fmap2DBack_c::regionGridDraw() {
while (true) { while (true) {
calcAllMapPos2D(xPos, dVar8, &local_74, &local_78); calcAllMapPos2D(xPos, dVar8, &local_74, &local_78);
if (local_74 >= getMapScissorAreaLX()) { if (local_74 >= getMapScissorAreaLX()) {
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
local_74 = getMirrorPosX(local_74, 0.0f);
}
#endif
J2DDrawLine(local_74, mDoGph_gInf_c::getMinYF(), local_74, J2DDrawLine(local_74, mDoGph_gInf_c::getMinYF(), local_74,
mDoGph_gInf_c::getMinYF() + mDoGph_gInf_c::getHeightF(), mDoGph_gInf_c::getMinYF() + mDoGph_gInf_c::getHeightF(),
JUtility::TColor(180, 0, 0, 255), 6); JUtility::TColor(180, 0, 0, 255), 6);
@@ -1604,6 +1635,12 @@ void dMenu_Fmap2DBack_c::worldOriginDraw() {
f32 local_44, local_48; f32 local_44, local_48;
calcAllMapPos2D(-mStageTransX, -mStageTransZ, &local_44, &local_48); calcAllMapPos2D(-mStageTransX, -mStageTransZ, &local_44, &local_48);
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
local_44 = getMirrorPosX(local_44, 0.0f);
}
#endif
J2DDrawLine(mDoGph_gInf_c::getMinXF(), local_48 - local_44 + mDoGph_gInf_c::getMinXF(), J2DDrawLine(mDoGph_gInf_c::getMinXF(), local_48 - local_44 + mDoGph_gInf_c::getMinXF(),
mDoGph_gInf_c::getMinXF() + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getMinXF() + mDoGph_gInf_c::getWidthF(),
local_48 - local_44 + (mDoGph_gInf_c::getMinXF() + mDoGph_gInf_c::getWidthF()), local_48 - local_44 + (mDoGph_gInf_c::getMinXF() + mDoGph_gInf_c::getWidthF()),
@@ -1638,6 +1675,13 @@ void dMenu_Fmap2DBack_c::scrollAreaDraw() {
calcAllMapPos2D(x_min - mStageTransX, z_min - mStageTransZ, &local_4c, &local_50); calcAllMapPos2D(x_min - mStageTransX, z_min - mStageTransZ, &local_4c, &local_50);
calcAllMapPos2D(x_max - mStageTransX, z_max - mStageTransZ, &local_54, &local_58); calcAllMapPos2D(x_max - mStageTransX, z_max - mStageTransZ, &local_54, &local_58);
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
local_4c = getMirrorPosX(local_4c, 0.0f);
local_54 = getMirrorPosX(local_54, 0.0f);
}
#endif
J2DDrawLine(local_4c, local_50, local_4c, local_58, J2DDrawLine(local_4c, local_50, local_4c, local_58,
JUtility::TColor(255, 255, 255, 255), 6); JUtility::TColor(255, 255, 255, 255), 6);
J2DDrawLine(local_54, local_50, local_54, local_58, J2DDrawLine(local_54, local_50, local_54, local_58,
@@ -1658,6 +1702,11 @@ void dMenu_Fmap2DBack_c::regionOriginDraw() {
f32 center_x, center_y; f32 center_x, center_y;
calcAllMapPos2D(mRegionOriginX[i] - mStageTransX, mRegionOriginZ[i] - mStageTransZ, calcAllMapPos2D(mRegionOriginX[i] - mStageTransX, mRegionOriginZ[i] - mStageTransZ,
&center_x, &center_y); &center_x, &center_y);
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
center_x = getMirrorPosX(center_x - 3.0f, 3.0f);
}
#endif
J2DFillBox(center_x - 3.0f, center_y - 3.0f, 6.0f, 6.0f, JUtility::TColor(255, 0, 0, 255)); J2DFillBox(center_x - 3.0f, center_y - 3.0f, 6.0f, 6.0f, JUtility::TColor(255, 0, 0, 255));
} }
} }
@@ -1675,6 +1724,11 @@ void dMenu_Fmap2DBack_c::stageOriginDraw() {
f32 v1 = mRegionOriginX[mRegionCursor] + stage_data[i].mOffsetX - mStageTransX; f32 v1 = mRegionOriginX[mRegionCursor] + stage_data[i].mOffsetX - mStageTransX;
f32 v2 = mRegionOriginZ[mRegionCursor] + stage_data[i].mOffsetZ - mStageTransZ; f32 v2 = mRegionOriginZ[mRegionCursor] + stage_data[i].mOffsetZ - mStageTransZ;
calcAllMapPos2D(v1, v2, &center_x, &center_y); calcAllMapPos2D(v1, v2, &center_x, &center_y);
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
center_x = getMirrorPosX(center_x - 3.0f, 3.0f);
}
#endif
J2DFillBox(center_x - 3.0f, center_y - 3.0f, 6.0f, 6.0f, J2DFillBox(center_x - 3.0f, center_y - 3.0f, 6.0f, 6.0f,
JUtility::TColor(0, 0, 255, 255)); JUtility::TColor(0, 0, 255, 255));
} }
@@ -1921,7 +1975,7 @@ void dMenu_Fmap2DBack_c::regionMapMove(STControl* i_stick) {
f32 speed = base_speed / 100.0f * local_78; f32 speed = base_speed / 100.0f * local_78;
f32 speed_y = speed * cM_ssin(angle); f32 speed_y = speed * cM_ssin(angle);
f32 speed_x = speed * cM_scos(angle); f32 speed_x = speed * cM_scos(angle);
control_xpos += speed_y; control_xpos += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -speed_y :) speed_y;
control_ypos += speed_x; control_ypos += speed_x;
} }
} }
@@ -1946,11 +2000,6 @@ void dMenu_Fmap2DBack_c::regionMapMove(STControl* i_stick) {
calcAllMapPos2D(mArrowPos3DX + control_xpos - mStageTransX, calcAllMapPos2D(mArrowPos3DX + control_xpos - mStageTransX,
mArrowPos3DZ + control_ypos - mStageTransZ, &pos_x, &pos_y); mArrowPos3DZ + control_ypos - mStageTransZ, &pos_x, &pos_y);
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorPosX(pos_x, 0.0f);
}
#endif
mSelectRegion = 0xff; mSelectRegion = 0xff;
int region = mRegionCursor; int region = mRegionCursor;
@@ -1982,11 +2031,6 @@ void dMenu_Fmap2DBack_c::stageMapMove(STControl* i_stick, u8 param_1, bool param
if (stick_value >= slow_bound && param_2 && field_0x1238 != 2) { if (stick_value >= slow_bound && param_2 && field_0x1238 != 2) {
bVar6 = true; bVar6 = true;
s16 angle = i_stick->getAngleStick(); s16 angle = i_stick->getAngleStick();
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
angle = -angle;
}
#endif
f32 local_68 = mTexMaxX - mTexMinX; f32 local_68 = mTexMaxX - mTexMinX;
f32 spot_zoom = getSpotMapZoomRate(); f32 spot_zoom = getSpotMapZoomRate();
f32 region_zoom = getRegionMapZoomRate(mRegionCursor); f32 region_zoom = getRegionMapZoomRate(mRegionCursor);
@@ -2001,7 +2045,7 @@ void dMenu_Fmap2DBack_c::stageMapMove(STControl* i_stick, u8 param_1, bool param
f32 speed = base_speed / 100.0f * local_78; f32 speed = base_speed / 100.0f * local_78;
f32 speed_x = speed * cM_ssin(angle); f32 speed_x = speed * cM_ssin(angle);
f32 speed_z = speed * cM_scos(angle); f32 speed_z = speed * cM_scos(angle);
mStageTransX += speed_x; mStageTransX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -speed_x :) speed_x;
mStageTransZ += speed_z; mStageTransZ += speed_z;
} else if (!param_2) { } else if (!param_2) {
return; return;
@@ -2095,6 +2139,11 @@ void dMenu_Fmap2DBack_c::drawDebugStageArea() {
if (stage_no >= 0) { if (stage_no >= 0) {
f32 v = i + mDoGph_gInf_c::getMinXF(); f32 v = i + mDoGph_gInf_c::getMinXF();
f32 v2 = j; f32 v2 = j;
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
v = getMirrorPosX(v - 3.0f, 3.0f);
}
#endif
J2DFillBox(v - 3.0f, v2 - 3.0f, 6.0f, 6.0f, colors[stage_no % 6]); J2DFillBox(v - 3.0f, v2 - 3.0f, 6.0f, 6.0f, colors[stage_no % 6]);
} }
} }
@@ -2130,6 +2179,11 @@ void dMenu_Fmap2DBack_c::drawDebugRegionArea() {
mRegionMapSizeX[region] * mZoom, mRegionMapSizeY[region] * mZoom, mRegionMapSizeX[region] * mZoom, mRegionMapSizeY[region] * mZoom,
mpAreaTex[region]->getTexture(0)->getTexInfo()); mpAreaTex[region]->getTexture(0)->getTexInfo());
if (u) { if (u) {
#if TARGET_PC
if(dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorPosX(pos_x - 3.0f, 3.0f);
}
#endif
J2DFillBox(pos_x - 3.0f, pos_y - 3.0f, 6.0f, 6.0f, colors[region]); J2DFillBox(pos_x - 3.0f, pos_y - 3.0f, 6.0f, 6.0f, colors[region]);
break; break;
} }
+7 -15
View File
@@ -394,21 +394,8 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
icon_size_y *= _c7c; icon_size_y *= _c7c;
} }
#if TARGET_PC
f32 rotation = mIconInfo[info_idx].rotation;
if (dusk::getSettings().game.enableMirrorMode &&
(mIconInfo[info_idx].icon_no == ICON_LINK_e ||
mIconInfo[info_idx].icon_no == ICON_LINK_ENTER_e))
{
rotation = -rotation;
}
mPictures[mIconInfo[info_idx].icon_no]->rotate(icon_size_x / 2, icon_size_y / 2, ROTATE_Z,
rotation);
#else
mPictures[mIconInfo[info_idx].icon_no]->rotate(icon_size_x / 2, icon_size_y / 2, ROTATE_Z, mPictures[mIconInfo[info_idx].icon_no]->rotate(icon_size_x / 2, icon_size_y / 2, ROTATE_Z,
mIconInfo[info_idx].rotation); mIconInfo[info_idx].rotation);
#endif
if (mIconInfo[info_idx].icon_no == ICON_LIGHT_DROP_e) { if (mIconInfo[info_idx].icon_no == ICON_LIGHT_DROP_e) {
mPictures[mIconInfo[info_idx].icon_no]->setAlpha((180.0f * _c80) / 255.0f); mPictures[mIconInfo[info_idx].icon_no]->setAlpha((180.0f * _c80) / 255.0f);
@@ -423,10 +410,15 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
} }
f32 pos_x = i_posX + (icon_pos_x - (icon_size_x / 2)); f32 pos_x = i_posX + (icon_pos_x - (icon_size_x / 2));
bool r4 = false;
#if TARGET_PC #if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) { if (dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorCenterPosX(i_posX + (icon_pos_x - (icon_size_x / 2)), icon_size_x / 2); pos_x = getMirrorCenterPosX(i_posX + (icon_pos_x - (icon_size_x / 2)), icon_size_x / 2);
} }
if(mIconInfo[info_idx].icon_no == ICON_GOLD_WOLF_e) {
r4 = true;
}
#endif #endif
mPictures[mIconInfo[info_idx].icon_no]->draw(pos_x, (i_posY + (icon_pos_y - icon_size_y / 2)), mPictures[mIconInfo[info_idx].icon_no]->draw(pos_x, (i_posY + (icon_pos_y - icon_size_y / 2)),
@@ -435,7 +427,7 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
if (mIconInfo[info_idx].icon_no == ICON_LIGHT_DROP_e) { if (mIconInfo[info_idx].icon_no == ICON_LIGHT_DROP_e) {
mLightDropPic->draw((pos_x + (icon_size_x / 2)) - (var_f29 / 2), mLightDropPic->draw((pos_x + (icon_size_x / 2)) - (var_f29 / 2),
((icon_size_y / 2) + (i_posY + (icon_pos_y - icon_size_y / 2))) - (var_f28 / 2), ((icon_size_y / 2) + (i_posY + (icon_pos_y - icon_size_y / 2))) - (var_f28 / 2),
var_f29, var_f28, false, false, false); var_f29, var_f28, r4, false, false);
} }
} }
} }
@@ -869,4 +861,4 @@ void dMenuMapCommon_c::getFmapPoeCount(const int regionNo, int& nowCount, int& t
} }
} }
} }
#endif #endif
+11
View File
@@ -437,7 +437,12 @@ void dMeter2_c::checkStatus() {
field_0x128 = daPy_py_c::checkNowWolf(); field_0x128 = daPy_py_c::checkNowWolf();
#if TARGET_PC
dMsgObject_c* msgObject = dMsgObject_getMsgObjectClass();
if (!dComIfGp_2dShowCheck() || (msgObject != NULL && msgObject->isPlaceMessage())) {
#else
if (!dComIfGp_2dShowCheck() || dMsgObject_getMsgObjectClass()->isPlaceMessage()) { if (!dComIfGp_2dShowCheck() || dMsgObject_getMsgObjectClass()->isPlaceMessage()) {
#endif
mStatus |= 0x4000; mStatus |= 0x4000;
} else if (dComIfGp_checkPlayerStatus1(0, 1) && dComIfGp_getAStatus() == 0x12) { } else if (dComIfGp_checkPlayerStatus1(0, 1) && dComIfGp_getAStatus() == 0x12) {
mStatus |= 0x200000; mStatus |= 0x200000;
@@ -2870,8 +2875,14 @@ void dMeter2_c::alphaAnimeButton() {
u8 var_31; u8 var_31;
var_31 = 0; var_31 = 0;
#if TARGET_PC
dMsgObject_c* msgObject = dMsgObject_getMsgObjectClass();
if ((mStatus & 0x4000) ||
((mStatus & 0x100) && (msgObject != NULL && msgObject->isAutoMessageFlag())) ||
#else
if ((mStatus & 0x4000) || if ((mStatus & 0x4000) ||
((mStatus & 0x100) && dMsgObject_getMsgObjectClass()->isAutoMessageFlag()) || ((mStatus & 0x100) && dMsgObject_getMsgObjectClass()->isAutoMessageFlag()) ||
#endif
((mStatus & 0x40000000) && !(mStatus & 0x100)) || (mStatus & 0x80000000) || (mStatus & 8) || ((mStatus & 0x40000000) && !(mStatus & 0x100)) || (mStatus & 0x80000000) || (mStatus & 8) ||
(mStatus & 0x10) || (mStatus & 0x20) || (mStatus & 0x04000000) || (mStatus & 0x10000000)) (mStatus & 0x10) || (mStatus & 0x20) || (mStatus & 0x04000000) || (mStatus & 0x10000000))
{ {
+1 -1
View File
@@ -594,7 +594,7 @@ BOOL dMeter2Info_c::isDirectUseItem(int param_0) {
return (mDirectUseItem & (u8)(1 << param_0)) ? TRUE : FALSE; return (mDirectUseItem & (u8)(1 << param_0)) ? TRUE : FALSE;
} }
DUSK_GAME_DATA dMeter2Info_c g_meter2_info; dMeter2Info_c g_meter2_info;
int dMeter2Info_c::setMeterString(s32 i_string) { int dMeter2Info_c::setMeterString(s32 i_string) {
if (mMeterString != 0) { if (mMeterString != 0) {
-11
View File
@@ -428,17 +428,6 @@ static void dummyStrings() {
dMsgObject_HIO_c g_MsgObject_HIO_c; dMsgObject_HIO_c g_MsgObject_HIO_c;
int dMsgObject_c::_execute() { int dMsgObject_c::_execute() {
// TODO: enabling wii message overrides fixes direction text, but gives wrong item control text
/*#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
// enable wii message index override
g_MsgObject_HIO_c.mMessageDisplay = 1;
} else if (!dusk::getSettings().game.enableMirrorMode && g_MsgObject_HIO_c.mMessageDisplay == 1) {
g_MsgObject_HIO_c.mMessageDisplay = 0;
}
#endif*/
field_0x4c7 = 0; field_0x4c7 = 0;
if (mpTalkHeap != NULL) { if (mpTalkHeap != NULL) {
field_0x148 = mDoExt_setCurrentHeap(mpTalkHeap); field_0x148 = mDoExt_setCurrentHeap(mpTalkHeap);
+1 -1
View File
@@ -1823,7 +1823,7 @@ int dSv_info_c::memory_to_card(char* card_ptr, int dataNum) {
savedata->getPlayer().getPlayerInfo().setTotalTime(play_time); savedata->getPlayer().getPlayerInfo().setTotalTime(play_time);
} }
savedata->getPlayer().getPlayerStatusB().setDateIpl(OSGetTime()); savedata->getPlayer().getPlayerStatusB().setDateIpl(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()));
memcpy(card_ptr, savedata, sizeof(dSv_save_c)); memcpy(card_ptr, savedata, sizeof(dSv_save_c));
card_ptr += 0x958; card_ptr += 0x958;
+222 -152
View File
@@ -7,6 +7,7 @@
#include "dusk/io.hpp" #include "dusk/io.hpp"
#include "dusk/settings.h" #include "dusk/settings.h"
#include <algorithm>
#include <cmath> #include <cmath>
#include <filesystem> #include <filesystem>
#include <limits> #include <limits>
@@ -15,79 +16,90 @@
#include <string_view> #include <string_view>
#include <system_error> #include <system_error>
#include <utility> #include <utility>
#include <vector>
#include "dusk/action_bindings.h" #include "dusk/action_bindings.h"
#include "dusk/logging.h" #include "dusk/logging.h"
#include "dusk/main.h" #include "dusk/main.h"
using namespace dusk::config; namespace dusk::config {
namespace {
constexpr auto ConfigFileName = "config.json"; constexpr auto ConfigFileName = "config.json";
using json = nlohmann::json; using json = nlohmann::json;
aurora::Module DuskConfigLog("dusk::config"); aurora::Module DuskConfigLog("dusk::config");
static absl::flat_hash_map<std::string, ConfigVarBase*> RegisteredConfigVars; absl::flat_hash_map<std::string, ConfigVarBase*> RegisteredConfigVars;
static absl::flat_hash_map<std::string, nlohmann::json> UnregisteredConfigVars; absl::flat_hash_map<std::string, nlohmann::json> UnregisteredConfigVars;
static absl::flat_hash_map<std::string, std::string> UnregisteredConfigVarOverrides; absl::flat_hash_map<std::string, std::string> UnregisteredConfigVarOverrides;
static std::optional<dusk::ui::ControlAnchor> parse_control_anchor(std::string_view value) { struct ChangeSubscription {
Subscription token;
ChangeCallback callback;
};
absl::flat_hash_map<std::string, std::vector<ChangeSubscription> > s_changeSubscriptions;
absl::flat_hash_map<Subscription, std::string> s_changeTokenNames;
Subscription s_nextChangeToken = 1;
// Names currently being notified; guards against a callback re-notifying its own CVar.
std::vector<std::string> s_activeChangeNotifications;
std::optional<ui::ControlAnchor> parse_control_anchor(std::string_view value) {
if (value == "none") { if (value == "none") {
return dusk::ui::ControlAnchor::None; return ui::ControlAnchor::None;
} }
if (value == "top") { if (value == "top") {
return dusk::ui::ControlAnchor::Top; return ui::ControlAnchor::Top;
} }
if (value == "left") { if (value == "left") {
return dusk::ui::ControlAnchor::Left; return ui::ControlAnchor::Left;
} }
if (value == "bottom") { if (value == "bottom") {
return dusk::ui::ControlAnchor::Bottom; return ui::ControlAnchor::Bottom;
} }
if (value == "right") { if (value == "right") {
return dusk::ui::ControlAnchor::Right; return ui::ControlAnchor::Right;
} }
if (value == "topLeft") { if (value == "topLeft") {
return dusk::ui::ControlAnchor::TopLeft; return ui::ControlAnchor::TopLeft;
} }
if (value == "topRight") { if (value == "topRight") {
return dusk::ui::ControlAnchor::TopRight; return ui::ControlAnchor::TopRight;
} }
if (value == "bottomLeft") { if (value == "bottomLeft") {
return dusk::ui::ControlAnchor::BottomLeft; return ui::ControlAnchor::BottomLeft;
} }
if (value == "bottomRight") { if (value == "bottomRight") {
return dusk::ui::ControlAnchor::BottomRight; return ui::ControlAnchor::BottomRight;
} }
return std::nullopt; return std::nullopt;
} }
static const char* control_anchor_value(dusk::ui::ControlAnchor anchor) { const char* control_anchor_value(ui::ControlAnchor anchor) {
switch (anchor) { switch (anchor) {
case dusk::ui::ControlAnchor::None: case ui::ControlAnchor::None:
return "none"; return "none";
case dusk::ui::ControlAnchor::Top: case ui::ControlAnchor::Top:
return "top"; return "top";
case dusk::ui::ControlAnchor::Left: case ui::ControlAnchor::Left:
return "left"; return "left";
case dusk::ui::ControlAnchor::Bottom: case ui::ControlAnchor::Bottom:
return "bottom"; return "bottom";
case dusk::ui::ControlAnchor::Right: case ui::ControlAnchor::Right:
return "right"; return "right";
case dusk::ui::ControlAnchor::TopLeft: case ui::ControlAnchor::TopLeft:
return "topLeft"; return "topLeft";
case dusk::ui::ControlAnchor::TopRight: case ui::ControlAnchor::TopRight:
return "topRight"; return "topRight";
case dusk::ui::ControlAnchor::BottomLeft: case ui::ControlAnchor::BottomLeft:
return "bottomLeft"; return "bottomLeft";
case dusk::ui::ControlAnchor::BottomRight: case ui::ControlAnchor::BottomRight:
return "bottomRight"; return "bottomRight";
} }
return "none"; return "none";
} }
static std::optional<float> json_finite_float(const json& object, const char* key) { std::optional<float> json_finite_float(const json& object, const char* key) {
const auto iter = object.find(key); const auto iter = object.find(key);
if (iter == object.end() || !iter->is_number()) { if (iter == object.end() || !iter->is_number()) {
return std::nullopt; return std::nullopt;
@@ -101,7 +113,7 @@ static std::optional<float> json_finite_float(const json& object, const char* ke
return value; return value;
} }
static std::optional<dusk::ui::ControlProps> parse_control_props(const json& value) { std::optional<ui::ControlProps> parse_control_props(const json& value) {
if (!value.is_object()) { if (!value.is_object()) {
return std::nullopt; return std::nullopt;
} }
@@ -120,7 +132,7 @@ static std::optional<dusk::ui::ControlProps> parse_control_props(const json& val
if (!anchor || *w <= 0.0f || *h <= 0.0f || *scale <= 0.0f) { if (!anchor || *w <= 0.0f || *h <= 0.0f || *scale <= 0.0f) {
return std::nullopt; return std::nullopt;
} }
return dusk::ui::ControlProps{ return ui::ControlProps{
.x = *x, .x = *x,
.y = *y, .y = *y,
.w = *w, .w = *w,
@@ -130,17 +142,17 @@ static std::optional<dusk::ui::ControlProps> parse_control_props(const json& val
}; };
} }
static std::filesystem::path GetConfigJsonPath() { std::filesystem::path GetConfigJsonPath() {
return dusk::ConfigPath / ConfigFileName; return ConfigPath / ConfigFileName;
} }
static std::filesystem::path GetTempConfigJsonPath(const std::filesystem::path& configJsonPath) { std::filesystem::path GetTempConfigJsonPath(const std::filesystem::path& configJsonPath) {
auto tempPath = configJsonPath; auto tempPath = configJsonPath;
tempPath.replace_filename(fmt::format(".{}.tmp", configJsonPath.filename().string())); tempPath.replace_filename(fmt::format(".{}.tmp", configJsonPath.filename().string()));
return tempPath; return tempPath;
} }
static void ReplaceFile(const std::filesystem::path& source, const std::filesystem::path& target) { void ReplaceFile(const std::filesystem::path& source, const std::filesystem::path& target) {
std::error_code ec; std::error_code ec;
std::filesystem::rename(source, target, ec); std::filesystem::rename(source, target, ec);
if (ec) { if (ec) {
@@ -150,9 +162,84 @@ static void ReplaceFile(const std::filesystem::path& source, const std::filesyst
} }
} }
ConfigVarBase::ConfigVarBase(std::string name, const ConfigImplBase* impl) : name(std::move(name)), registered(false), layer(ConfigVarLayer::Default), impl(impl) { template <typename T>
T sanitizeEnumValue(const ConfigVar<T>& cVar, T value) {
if constexpr (std::is_enum_v<T>) {
using Underlying = std::underlying_type_t<T>;
const Underlying raw = static_cast<Underlying>(value);
const Underlying min = static_cast<Underlying>(ConfigEnumRange<T>::min);
const Underlying max = static_cast<Underlying>(ConfigEnumRange<T>::max);
if (raw < min || raw > max) {
return cVar.getDefaultValue();
}
}
return value;
} }
template <ConfigValue T>
requires std::is_integral_v<T>&& std::is_signed_v<T> T parse_arg_value(
const ConfigVar<T>&, const std::string_view stringValue) {
const std::string str(stringValue);
const auto result = std::stoll(str);
if (result >= std::numeric_limits<T>::min() && result <= std::numeric_limits<T>::max()) {
return static_cast<T>(result);
}
throw std::out_of_range("Value is too large");
}
template <ConfigValue T>
requires std::is_integral_v<T>&& std::is_unsigned_v<T> T parse_arg_value(
const ConfigVar<T>&, const std::string_view stringValue) {
const std::string str(stringValue);
const auto result = std::stoull(str);
if (result <= std::numeric_limits<T>::max()) {
return static_cast<T>(result);
}
throw std::out_of_range("Value is too large");
}
f32 parse_arg_value(const ConfigVar<f32>&, const std::string_view stringValue) {
const std::string str(stringValue);
return std::stof(str);
}
f64 parse_arg_value(const ConfigVar<f64>&, const std::string_view stringValue) {
const std::string str(stringValue);
return std::stod(str);
}
std::string parse_arg_value(const ConfigVar<std::string>&, const std::string_view stringValue) {
return std::string(stringValue);
}
template <ConfigValue T>
requires std::is_enum_v<T> T parse_arg_value(
const ConfigVar<T>& cVar, const std::string_view stringValue) {
using Underlying = std::underlying_type_t<T>;
const std::string str(stringValue);
if constexpr (std::is_signed_v<Underlying>) {
const auto result = std::stoll(str);
if (result >= std::numeric_limits<Underlying>::min() &&
result <= std::numeric_limits<Underlying>::max())
{
return sanitizeEnumValue(cVar, static_cast<T>(result));
}
throw std::out_of_range("Value is too large");
} else {
const auto result = std::stoull(str);
if (result <= std::numeric_limits<Underlying>::max()) {
return sanitizeEnumValue(cVar, static_cast<T>(result));
}
throw std::out_of_range("Value is too large");
}
}
} // namespace
ConfigVarBase::ConfigVarBase(std::string name, const ConfigImplBase* impl)
: name(std::move(name)), registered(false), layer(ConfigVarLayer::Default), impl(impl) {}
const char* ConfigVarBase::getName() const noexcept { const char* ConfigVarBase::getName() const noexcept {
return name.c_str(); return name.c_str();
} }
@@ -167,21 +254,6 @@ ConfigVarBase::~ConfigVarBase() {
} }
} }
template <typename T>
static T sanitizeEnumValue(const ConfigVar<T>& cVar, T value) {
if constexpr (std::is_enum_v<T>) {
using Underlying = std::underlying_type_t<T>;
const Underlying raw = static_cast<Underlying>(value);
const Underlying min = static_cast<Underlying>(ConfigEnumRange<T>::min);
const Underlying max = static_cast<Underlying>(ConfigEnumRange<T>::max);
if (raw < min || raw > max) {
return cVar.getDefaultValue();
}
}
return value;
}
template <ConfigValue T> template <ConfigValue T>
void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) { void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
if constexpr (std::is_enum_v<T>) { if constexpr (std::is_enum_v<T>) {
@@ -195,12 +267,12 @@ void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
const Underlying raw = b ? static_cast<Underlying>(1) : static_cast<Underlying>(0); const Underlying raw = b ? static_cast<Underlying>(1) : static_cast<Underlying>(0);
cVar.setValue(sanitizeEnumValue(cVar, static_cast<T>(raw)), false); cVar.load_value(sanitizeEnumValue(cVar, static_cast<T>(raw)));
return; return;
} }
} }
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<T>()), false); cVar.load_value(sanitizeEnumValue(cVar, jsonValue.get<T>()));
} }
template <ConfigValue T> template <ConfigValue T>
@@ -208,74 +280,9 @@ nlohmann::json ConfigImpl<T>::dumpToJson(const ConfigVar<T>& cVar) {
return cVar.getValueForSave(); return cVar.getValueForSave();
} }
template <ConfigValue T>
requires std::is_integral_v<T>&& std::is_signed_v<T> static void loadFromArgImpl(
ConfigVar<T>& cVar, const std::string_view stringValue) {
const std::string str(stringValue);
const auto result = std::stoll(str);
if (result >= std::numeric_limits<T>::min() && result <= std::numeric_limits<T>::max()) {
cVar.setOverrideValue(result);
} else {
throw std::out_of_range("Value is too large");
}
}
template <ConfigValue T>
requires std::is_integral_v<T>&& std::is_unsigned_v<T> static void loadFromArgImpl(
ConfigVar<T>& cVar, const std::string_view stringValue) {
const std::string str(stringValue);
const auto result = std::stoull(str);
if (result <= std::numeric_limits<T>::max()) {
cVar.setOverrideValue(result);
} else {
throw std::out_of_range("Value is too large");
}
}
static void loadFromArgImpl(ConfigVar<f32>& cVar, const std::string_view stringValue) {
const std::string str(stringValue);
const auto result = std::stof(str);
cVar.setOverrideValue(result);
}
static void loadFromArgImpl(ConfigVar<f64>& cVar, const std::string_view stringValue) {
const std::string str(stringValue);
const auto result = std::stod(str);
cVar.setOverrideValue(result);
}
static void loadFromArgImpl(ConfigVar<std::string>& cVar, const std::string_view stringValue) {
cVar.setOverrideValue(std::string(stringValue));
}
template <ConfigValue T>
requires std::is_enum_v<T> static void loadFromArgImpl(
ConfigVar<T>& cVar, const std::string_view stringValue) {
using Underlying = std::underlying_type_t<T>;
const std::string str(stringValue);
if constexpr (std::is_signed_v<Underlying>) {
const auto result = std::stoll(str);
if (result >= std::numeric_limits<Underlying>::min() &&
result <= std::numeric_limits<Underlying>::max())
{
cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast<T>(result)));
} else {
throw std::out_of_range("Value is too large");
}
} else {
const auto result = std::stoull(str);
if (result <= std::numeric_limits<Underlying>::max()) {
cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast<T>(result)));
} else {
throw std::out_of_range("Value is too large");
}
}
}
template <ConfigValue T> template <ConfigValue T>
void ConfigImpl<T>::loadFromArg(ConfigVar<T>& cVar, const std::string_view stringValue) { void ConfigImpl<T>::loadFromArg(ConfigVar<T>& cVar, const std::string_view stringValue) {
loadFromArgImpl(cVar, stringValue); cVar.load_override_value(parse_arg_value(cVar, stringValue));
} }
template <> template <>
@@ -283,18 +290,16 @@ void ConfigImpl<bool>::loadFromArg(ConfigVar<bool>& cVar, const std::string_view
if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" || if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" ||
stringValue == "True") stringValue == "True")
{ {
cVar.setOverrideValue(true); cVar.load_override_value(true);
} else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" || } else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" ||
stringValue == "False") stringValue == "False")
{ {
cVar.setOverrideValue(false); cVar.load_override_value(false);
} else { } else {
throw InvalidConfigError("Value cannot be parsed as boolean"); throw InvalidConfigError("Value cannot be parsed as boolean");
} }
} }
// My IDE is convinced this namespace is necessary. It shouldn't be AFAICT?
namespace dusk::config {
template class ConfigImpl<bool>; template class ConfigImpl<bool>;
template class ConfigImpl<s8>; template class ConfigImpl<s8>;
template class ConfigImpl<u8>; template class ConfigImpl<u8>;
@@ -307,10 +312,10 @@ template class ConfigImpl<u64>;
template class ConfigImpl<f32>; template class ConfigImpl<f32>;
template class ConfigImpl<f64>; template class ConfigImpl<f64>;
template class ConfigImpl<std::string>; template class ConfigImpl<std::string>;
template class ConfigImpl<dusk::BloomMode>; template class ConfigImpl<BloomMode>;
template class ConfigImpl<dusk::DepthOfFieldMode>; template class ConfigImpl<DepthOfFieldMode>;
template class ConfigImpl<dusk::DiscVerificationState>; template class ConfigImpl<DiscVerificationState>;
template class ConfigImpl<dusk::GameLanguage>; template class ConfigImpl<GameLanguage>;
template <> template <>
void ConfigImpl<FrameInterpMode>::loadFromJson( void ConfigImpl<FrameInterpMode>::loadFromJson(
@@ -320,11 +325,11 @@ void ConfigImpl<FrameInterpMode>::loadFromJson(
const FrameInterpMode mode = b ? FrameInterpMode::Unlimited : FrameInterpMode::Off; const FrameInterpMode mode = b ? FrameInterpMode::Unlimited : FrameInterpMode::Off;
cVar.setValue(sanitizeEnumValue(cVar, mode), false); cVar.load_value(sanitizeEnumValue(cVar, mode));
return; return;
} }
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<FrameInterpMode>()), false); cVar.load_value(sanitizeEnumValue(cVar, jsonValue.get<FrameInterpMode>()));
} }
template <> template <>
@@ -355,7 +360,7 @@ void ConfigImpl<ui::ControlLayout>::loadFromJson(
} }
} }
cVar.setValue(std::move(layout), false); cVar.load_value(std::move(layout));
} }
template <> template <>
@@ -385,14 +390,14 @@ nlohmann::json ConfigImpl<ui::ControlLayout>::dumpToJson(const ConfigVar<ui::Con
}; };
} }
template class ConfigImpl<dusk::FrameInterpMode>; template class ConfigImpl<FrameInterpMode>;
template class ConfigImpl<dusk::MenuScaling>; template class ConfigImpl<TouchTargeting>;
template class ConfigImpl<dusk::Resampler>; template class ConfigImpl<MenuScaling>;
template class ConfigImpl<dusk::MagicArmorMode>; template class ConfigImpl<Resampler>;
template class ConfigImpl<dusk::ui::ControlLayout>; template class ConfigImpl<MagicArmorMode>;
} // namespace dusk::config template class ConfigImpl<ui::ControlLayout>;
void dusk::config::Register(ConfigVarBase& configVar) { void Register(ConfigVarBase& configVar) {
const std::string_view name = configVar.getName(); const std::string_view name = configVar.getName();
if (RegisteredConfigVars.contains(name)) { if (RegisteredConfigVars.contains(name)) {
DuskConfigLog.fatal("Tried to register CVar {} twice!", name); DuskConfigLog.fatal("Tried to register CVar {} twice!", name);
@@ -415,17 +420,31 @@ void dusk::config::Register(ConfigVarBase& configVar) {
const auto overridePair = UnregisteredConfigVarOverrides.find(name); const auto overridePair = UnregisteredConfigVarOverrides.find(name);
if (overridePair != UnregisteredConfigVarOverrides.end()) { if (overridePair != UnregisteredConfigVarOverrides.end()) {
const auto value = std::move(overridePair->second);
UnregisteredConfigVars.erase(name);
try { try {
configVar.getImpl()->loadFromArg(configVar, value); configVar.getImpl()->loadFromArg(configVar, overridePair->second);
} catch (std::exception& e) { } catch (std::exception& e) {
DuskConfigLog.error("Failed to load key '{}' from override arg: {}", name, e.what()); DuskConfigLog.error("Failed to load key '{}' from override arg: {}", name, e.what());
} }
} }
} }
void unregister(ConfigVarBase& configVar) {
const std::string_view name = configVar.getName();
const auto it = RegisteredConfigVars.find(name);
if (it == RegisteredConfigVars.end() || it->second != &configVar) {
DuskConfigLog.fatal("Tried to unregister CVar '{}' that is not registered!", name);
}
const auto layer = configVar.getLayer();
if (layer == ConfigVarLayer::Value || layer == ConfigVarLayer::Speedrun) {
UnregisteredConfigVars.insert_or_assign(
std::string{name}, configVar.getImpl()->dumpToJson(configVar));
}
RegisteredConfigVars.erase(it);
configVar.unmarkRegistered();
}
void ConfigVarBase::markRegistered() { void ConfigVarBase::markRegistered() {
if (registered) if (registered)
abort(); abort();
@@ -440,17 +459,17 @@ void ConfigVarBase::unmarkRegistered() {
registered = false; registered = false;
} }
void dusk::config::LoadFromUserPreferences() { void load_from_user_preferences() {
const auto configJsonPath = GetConfigJsonPath(); const auto configJsonPath = GetConfigJsonPath();
if (configJsonPath.empty()) { if (configJsonPath.empty()) {
return; return;
} }
const auto configPathString = io::fs_path_to_string(configJsonPath); const auto configPathString = io::fs_path_to_string(configJsonPath);
LoadFromFileName(configPathString.c_str()); load_from_file_name(configPathString.c_str());
} }
static void LoadFromPath(const char* path) { static void LoadFromPath(const char* path) {
auto data = dusk::io::FileStream::ReadAllBytes(path); auto data = io::FileStream::ReadAllBytes(path);
json j = json::parse(data); json j = json::parse(data);
if (!j.is_object()) { if (!j.is_object()) {
@@ -464,8 +483,8 @@ static void LoadFromPath(const char* path) {
const auto& key = el.key(); const auto& key = el.key();
auto configVar = RegisteredConfigVars.find(key); auto configVar = RegisteredConfigVars.find(key);
if (configVar == RegisteredConfigVars.end()) { if (configVar == RegisteredConfigVars.end()) {
DuskConfigLog.debug( DuskConfigLog.debug("Unknown key '{}' found in config! If this gets registered later, "
"Unknown key '{}' found in config! If this gets registered later, that's acceptable!", "that's acceptable!",
key); key);
UnregisteredConfigVars.emplace(key, el.value()); UnregisteredConfigVars.emplace(key, el.value());
continue; continue;
@@ -479,7 +498,7 @@ static void LoadFromPath(const char* path) {
} }
} }
void dusk::config::LoadFromFileName(const char* path) { void load_from_file_name(const char* path) {
DuskConfigLog.info("Loading config from '{}'", path); DuskConfigLog.info("Loading config from '{}'", path);
try { try {
@@ -497,10 +516,10 @@ void dusk::config::LoadFromFileName(const char* path) {
} }
} }
void dusk::config::LoadArgOverride(std::string_view name, std::string_view value) { void load_arg_override(std::string_view name, std::string_view value) {
const auto cVar = GetConfigVar(name); const auto cVar = GetConfigVar(name);
if (!cVar) { if (!cVar) {
UnregisteredConfigVarOverrides.emplace(name, name); UnregisteredConfigVarOverrides.emplace(name, value);
return; return;
} }
@@ -511,7 +530,7 @@ void dusk::config::LoadArgOverride(std::string_view name, std::string_view value
} }
} }
void dusk::config::Save() { void save() {
const auto configJsonPath = GetConfigJsonPath(); const auto configJsonPath = GetConfigJsonPath();
if (configJsonPath.empty()) { if (configJsonPath.empty()) {
return; return;
@@ -542,14 +561,14 @@ void dusk::config::Save() {
} }
} }
void dusk::config::ClearAllActionBindings(int port) { void ClearAllActionBindings(int port) {
for (auto& actionBinding : getActionBinds() | std::views::values) { for (auto& actionBinding : getActionBinds() | std::views::values) {
actionBinding.configVars->at(port).setValue(PAD_NATIVE_BUTTON_INVALID); actionBinding.configVars->at(port).setValue(PAD_NATIVE_BUTTON_INVALID);
} }
Save(); save();
} }
ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { ConfigVarBase* GetConfigVar(std::string_view name) {
const auto configVar = RegisteredConfigVars.find(name); const auto configVar = RegisteredConfigVars.find(name);
if (configVar != RegisteredConfigVars.end()) { if (configVar != RegisteredConfigVars.end()) {
return configVar->second; return configVar->second;
@@ -558,13 +577,59 @@ ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
return nullptr; return nullptr;
} }
void dusk::config::EnumerateRegistered(std::function<void(ConfigVarBase&)> callback) { void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback) {
for (auto& pair : RegisteredConfigVars) { for (auto& pair : RegisteredConfigVars) {
callback(*pair.second); callback(*pair.second);
} }
} }
void dusk::config::Shutdown() { Subscription subscribe(std::string_view name, ChangeCallback callback) {
const auto token = s_nextChangeToken++;
s_changeSubscriptions[std::string{name}].push_back({token, std::move(callback)});
s_changeTokenNames.emplace(token, std::string{name});
return token;
}
void unsubscribe(Subscription token) {
const auto nameIt = s_changeTokenNames.find(token);
if (nameIt == s_changeTokenNames.end()) {
DuskConfigLog.fatal("Tried to unsubscribe unknown change token {}!", token);
}
const auto subsIt = s_changeSubscriptions.find(nameIt->second);
auto& subscriptions = subsIt->second;
std::erase_if(
subscriptions, [token](const ChangeSubscription& sub) { return sub.token == token; });
if (subscriptions.empty()) {
s_changeSubscriptions.erase(subsIt);
}
s_changeTokenNames.erase(nameIt);
}
bool ConfigVarBase::has_subscribers() const {
return s_changeSubscriptions.contains(name);
}
void ConfigVarBase::notify_changed(const void* previousValue) {
const auto subsIt = s_changeSubscriptions.find(name);
if (subsIt == s_changeSubscriptions.end()) {
return;
}
if (std::ranges::find(s_activeChangeNotifications, name) != s_activeChangeNotifications.end()) {
DuskConfigLog.error("Recursive change notification for CVar '{}' suppressed", name);
return;
}
s_activeChangeNotifications.push_back(name);
// Copied so callbacks can subscribe/unsubscribe safely.
const auto subscriptions = subsIt->second;
for (const auto& sub : subscriptions) {
sub.callback(*this, previousValue);
}
s_activeChangeNotifications.pop_back();
}
void shutdown() {
for (auto& pair : RegisteredConfigVars) { for (auto& pair : RegisteredConfigVars) {
pair.second->unmarkRegistered(); pair.second->unmarkRegistered();
} }
@@ -572,4 +637,9 @@ void dusk::config::Shutdown() {
RegisteredConfigVars.clear(); RegisteredConfigVars.clear();
UnregisteredConfigVars.clear(); UnregisteredConfigVars.clear();
UnregisteredConfigVarOverrides.clear(); UnregisteredConfigVarOverrides.clear();
s_changeSubscriptions.clear();
s_changeTokenNames.clear();
s_activeChangeNotifications.clear();
} }
} // namespace dusk::config
-17
View File
@@ -1,17 +0,0 @@
#include "dusk/gx_helper.h"
GXTexObjRAII::~GXTexObjRAII() { GXDestroyTexObj(this); }
void GXTexObjRAII::reset() { GXDestroyTexObj(this); }
GXScopedDebugGroup::GXScopedDebugGroup(const char* text) {
#if DUSK_GFX_DEBUG_GROUPS
GXPushDebugGroup(text);
#else
(void)text;
#endif
}
GXScopedDebugGroup::~GXScopedDebugGroup() {
#if DUSK_GFX_DEBUG_GROUPS
GXPopDebugGroup();
#endif
}
-156
View File
@@ -1,156 +0,0 @@
#include "dusk/hook_system.hpp"
#include "dusk/logging.h"
#include <cstdint>
#include <cstring>
#include <funchook.h>
#include <string>
#include <unordered_map>
#include <vector>
namespace dusk {
namespace modding {
extern thread_local void* g_dusk_hook_current_mod;
}
struct PreHookFn {
void* mod;
int32_t (*fn)(void* args);
};
struct VoidHookFn {
void* mod;
const char* mod_name;
void (*fn)(void* args, void* retval);
};
struct HookSlot {
std::vector<PreHookFn> pre;
VoidHookFn replace = {};
std::vector<VoidHookFn> post;
};
static std::unordered_map<uintptr_t, HookSlot> s_registry;
static std::unordered_map<uintptr_t, void*> s_installed;
// Follow E9/FF25 chains to skip MSVC incremental-link and import stubs
static void* resolveImportThunk(void* addr) {
#if defined(_WIN32) && (defined(_M_X64) || defined(__x86_64__))
for (int i = 0; i < 8; ++i) {
const auto* p = static_cast<const uint8_t*>(addr);
if (p[0] == 0xFF && p[1] == 0x25) {
int32_t offset;
std::memcpy(&offset, p + 2, 4);
addr = const_cast<void*>(*reinterpret_cast<const void* const*>(p + 6 + offset));
break;
} else if (p[0] == 0xE9) {
int32_t offset;
std::memcpy(&offset, p + 1, 4);
addr = const_cast<uint8_t*>(p) + 5 + offset;
} else {
break;
}
}
#endif
return addr;
}
struct ModGuard {
void* prev;
explicit ModGuard(void* mod) : prev(modding::g_dusk_hook_current_mod) { modding::g_dusk_hook_current_mod = mod; }
~ModGuard() { modding::g_dusk_hook_current_mod = prev; }
};
void hookInstallByAddr(void* fn_addr, void* tramp_fn, void** orig_store) {
fn_addr = resolveImportThunk(fn_addr);
auto key = reinterpret_cast<uintptr_t>(fn_addr);
auto it = s_installed.find(key);
if (it != s_installed.end()) {
*orig_store = it->second;
return;
}
funchook_t* fh = funchook_create();
void* fn = fn_addr;
int prep = funchook_prepare(fh, &fn, tramp_fn);
int inst = (prep == 0) ? funchook_install(fh, 0) : -1;
if (prep != 0 || inst != 0) {
DuskLog.warn(
"HookSystem: funchook failed for {:p} (prepare={} install={})", fn_addr, prep, inst);
funchook_destroy(fh);
return;
}
funchook_destroy(fh);
s_installed[key] = fn;
*orig_store = fn;
}
bool hookDispatchPre(void* fn_addr, void* args, void* retval) {
auto it = s_registry.find(reinterpret_cast<uintptr_t>(fn_addr));
if (it == s_registry.end()) {
return false;
}
auto& slot = it->second;
for (auto& h : slot.pre) {
ModGuard g(h.mod);
if (h.fn(args) != 0) {
return true;
}
}
if (slot.replace.fn) {
ModGuard g(slot.replace.mod);
slot.replace.fn(args, retval);
return true;
}
return false;
}
void hookDispatchPost(void* fn_addr, void* args, void* retval) {
auto it = s_registry.find(reinterpret_cast<uintptr_t>(fn_addr));
if (it == s_registry.end()) {
return;
}
for (auto& h : it->second.post) {
if (h.fn) {
ModGuard g(h.mod);
h.fn(args, retval);
}
}
}
void hookRegisterPre(void* fn_addr, void* mod, int32_t (*fn)(void* args)) {
s_registry[reinterpret_cast<uintptr_t>(fn_addr)].pre.push_back({mod, fn});
}
void hookRegisterPost(void* fn_addr, void* mod, const char* mod_name, void (*fn)(void* args, void* retval)) {
s_registry[reinterpret_cast<uintptr_t>(fn_addr)].post.push_back({mod, mod_name, fn});
}
bool hookSetReplace(void* fn_addr, void* mod, const char* mod_name, void (*fn)(void* args, void* retval)) {
auto& slot = s_registry[reinterpret_cast<uintptr_t>(fn_addr)];
if (slot.replace.fn) {
DuskLog.error("HookSystem: '{}' conflicts with '{}', both replace the same function",
mod_name, slot.replace.mod_name);
return false;
}
slot.replace = {mod, mod_name, fn};
return true;
}
void hookClearMod(void* mod) {
for (auto& [addr, slot] : s_registry) {
auto erase = [&](auto& v) {
v.erase(
std::remove_if(v.begin(), v.end(), [mod](const auto& h) { return h.mod == mod; }),
v.end());
};
erase(slot.pre);
erase(slot.post);
if (slot.replace.mod == mod) {
slot.replace = {};
}
}
}
} // namespace dusk
+4 -4
View File
@@ -9,7 +9,7 @@ namespace dusk::config {
bool copy = var.getValue(); bool copy = var.getValue();
if (ImGui::Checkbox(title, &copy)) { if (ImGui::Checkbox(title, &copy)) {
var.setValue(copy); var.setValue(copy);
Save(); save();
return true; return true;
} }
@@ -20,7 +20,7 @@ namespace dusk::config {
float val = var; float val = var;
if (ImGui::SliderFloat(label, &val, v_min, v_max, format, flags)) { if (ImGui::SliderFloat(label, &val, v_min, v_max, format, flags)) {
var.setValue(val); var.setValue(val);
Save(); save();
return true; return true;
} }
@@ -31,7 +31,7 @@ namespace dusk::config {
int val = var; int val = var;
if (ImGui::SliderInt(label, &val, v_min, v_max, format, flags)) { if (ImGui::SliderInt(label, &val, v_min, v_max, format, flags)) {
var.setValue(val); var.setValue(val);
Save(); save();
return true; return true;
} }
@@ -42,7 +42,7 @@ namespace dusk::config {
bool copy = p_selected.getValue(); bool copy = p_selected.getValue();
if (ImGui::MenuItem(label, shortcut, &copy, enabled)) { if (ImGui::MenuItem(label, shortcut, &copy, enabled)) {
p_selected.setValue(copy); p_selected.setValue(copy);
Save(); save();
return true; return true;
} }
+2 -2
View File
@@ -254,7 +254,7 @@ namespace dusk {
if (ImGui::IsKeyPressed(ImGuiKey_F11)) { if (ImGui::IsKeyPressed(ImGuiKey_F11)) {
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen); getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
VISetWindowFullscreen(getSettings().video.enableFullscreen); VISetWindowFullscreen(getSettings().video.enableFullscreen);
config::Save(); config::save();
} }
if (getSettings().game.enableResetKeybind && ImGui::GetIO().KeyCtrl && if (getSettings().game.enableResetKeybind && ImGui::GetIO().KeyCtrl &&
@@ -336,7 +336,7 @@ namespace dusk {
if constexpr (SupportsProcessRestart) { if constexpr (SupportsProcessRestart) {
if (ImGui::Button("Retry (Auto backend)")) { if (ImGui::Button("Retry (Auto backend)")) {
getSettings().backend.graphicsBackend.setValue("auto"); getSettings().backend.graphicsBackend.setValue("auto");
config::Save(); config::save();
RestartRequested = true; RestartRequested = true;
IsRunning = false; IsRunning = false;
} }
+1 -1
View File
@@ -73,7 +73,7 @@ namespace dusk {
bool disableWaterRefraction = getSettings().game.disableWaterRefraction; bool disableWaterRefraction = getSettings().game.disableWaterRefraction;
if (ImGui::Checkbox("Disable Water Refraction", &disableWaterRefraction)) { if (ImGui::Checkbox("Disable Water Refraction", &disableWaterRefraction)) {
getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction); getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction);
config::Save(); config::save();
} }
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias); ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
ImGui::EndMenu(); ImGui::EndMenu();
-15
View File
@@ -1,15 +0,0 @@
/**
* Thin Windows launcher EXE. The game lives in dusk.dll, this just forwards
* the Windows entry point to it. Keeping the game as a DLL lets mod .dll
* files link against dusk.lib and resolve all game symbols at load time.
*/
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// see src/dusk/main.cpp
int dusk_WinMain(HINSTANCE hInst, HINSTANCE hPrev, PWSTR cmd, int show);
int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrev, PWSTR cmd, int show) {
return dusk_WinMain(hInst, hPrev, cmd, show);
}
+2 -3
View File
@@ -1,5 +1,5 @@
#if _WIN32 #if _WIN32
#define WIN32_LEAN_AND_MEAN #define WINDOWS_LEAN_AND_MEAN
#include <Windows.h> #include <Windows.h>
#include <shellapi.h> #include <shellapi.h>
#endif #endif
@@ -224,8 +224,7 @@ int main(int argc, char* argv[]) {
} }
#if _WIN32 #if _WIN32
// Entry point called by the launcher executable. int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) {
int __declspec(dllexport) dusk_WinMain(HINSTANCE, HINSTANCE, PWSTR, int) {
return RunWindowsGuiEntryPoint(); return RunWindowsGuiEntryPoint();
} }
#endif #endif
+1 -1
View File
@@ -314,7 +314,7 @@ void end_game_frame() noexcept {
} }
void begin_context(Context context) noexcept { void begin_context(Context context) noexcept {
if (context == Context::None) { if (context == Context::None || !enabled()) {
return; return;
} }
-61
View File
@@ -1,61 +0,0 @@
#include <utility>
#include "dusk/io.hpp"
#include "mod_loader.hpp"
namespace fs = std::filesystem;
namespace dusk::modding {
ModBundleDisk::ModBundleDisk(fs::path root) : root_path(std::move(root)) {}
std::vector<u8> ModBundleDisk::readFile(const std::string& fileName) {
return io::FileStream::ReadAllBytes(toRealPath(fileName));
}
std::vector<std::string> ModBundleDisk::getFileNames() {
std::vector<std::string> files;
std::error_code ec;
for (fs::recursive_directory_iterator it(root_path,
fs::directory_options::skip_permission_denied |
fs::directory_options::follow_directory_symlink,
ec);
it != fs::recursive_directory_iterator(); it.increment(ec))
{
if (ec) {
break;
}
if (!it->is_regular_file()) {
continue;
}
const auto& path = it->path();
const auto relPath = fs::relative(path, root_path);
auto string = io::fs_path_to_string(relPath);
if constexpr (fs::path::preferred_separator != '/') {
// Convert \ to / on Windows
for (auto& chr : string) {
if (chr == fs::path::preferred_separator) {
chr = '/';
}
}
}
files.emplace_back(std::move(string));
}
return files;
}
size_t ModBundleDisk::getFileSize(const std::string& fileName) {
return std::filesystem::file_size(toRealPath(fileName));
}
std::filesystem::path ModBundleDisk::toRealPath(const std::string& fileName) const {
const fs::path filePath = reinterpret_cast<const char8_t*>(fileName.c_str());
return root_path / filePath;
}
} // namespace dusk::modding
-65
View File
@@ -1,65 +0,0 @@
#include "fmt/format.h"
#include "mod_loader.hpp"
#include <span>
namespace dusk::modding {
ModBundleZip::ModBundleZip(std::vector<u8>&& data) : zip_data(std::move(data)) {
if (!mz_zip_reader_init_mem(&res_zip, zip_data.data(), zip_data.size(), 0)) {
const auto error = mz_zip_get_last_error(&res_zip);
throw std::runtime_error(
fmt::format("Opening zip failed: {}", mz_zip_get_error_string(error)));
}
}
ModBundleZip::~ModBundleZip() {
mz_zip_reader_end(&res_zip);
}
std::vector<u8> ModBundleZip::readFile(const std::string& fileName) {
size_t size;
const auto ptr = mz_zip_reader_extract_file_to_heap(&res_zip, fileName.c_str(), &size, 0);
if (!ptr) {
throw std::runtime_error(fmt::format("File does not exist: {}", fileName));
}
std::span data(static_cast<u8*>(ptr), size);
std::vector vec(data.begin(), data.end());
mz_free(ptr);
return vec;
}
std::vector<std::string> ModBundleZip::getFileNames() {
std::vector<std::string> results;
for (mz_uint i = 0, n = mz_zip_reader_get_num_files(&res_zip); i < n; ++i) {
mz_zip_archive_file_stat stat{};
if (!mz_zip_reader_file_stat(&res_zip, i, &stat)) {
continue;
}
if (mz_zip_reader_is_file_a_directory(&res_zip, i)) {
continue;
}
results.emplace_back(stat.m_filename);
}
return results;
}
size_t ModBundleZip::getFileSize(const std::string& fileName) {
const auto idx = mz_zip_reader_locate_file(&res_zip, fileName.c_str(), nullptr, 0);
if (idx < 0) {
throw std::runtime_error(fmt::format("Unable to locate file in zip: {}", fileName));
}
mz_zip_archive_file_stat stat{};
mz_zip_reader_file_stat(&res_zip, idx, &stat);
return stat.m_uncomp_size;
}
} // namespace dusk::modding
-445
View File
@@ -1,445 +0,0 @@
#include "dusk/mod_loader.hpp"
#include "dusk/hook_system.hpp"
#include "dusk/logging.h"
#include "mod_loader.hpp"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <string>
#include "aurora/dvd.h"
#include "dusk/config.hpp"
#include "dusk/io.hpp"
#include "miniz.h"
#include "native_module.hpp"
#include "nlohmann/json.hpp"
static aurora::Module Log("dusk::modLoader");
using namespace dusk::modding;
using namespace std::string_literals;
using namespace std::string_view_literals;
#if defined(_M_ARM64) || defined(__aarch64__)
static constexpr std::string_view k_archSuffix = "_arm64"sv;
#elif defined(_M_X64) || defined(__x86_64__)
static constexpr std::string_view k_archSuffix = "_x64"sv;
#elif defined(_M_IX86) || defined(__i386__)
static constexpr std::string_view k_archSuffix = "_x86"sv;
#else
static constexpr std::string_view k_archSuffix = ""sv;
#endif
static dusk::ModLoader g_modLoader;
// We cannot delete config vars registered by mods until the game shuts down fully.
// Therefore, orphan them during shutdown.
static std::vector<std::unique_ptr<dusk::ConfigVarBase>> OrphanedConfigVars;
namespace dusk {
ModLoader& ModLoader::instance() {
return g_modLoader;
}
static std::unique_ptr<ModBundle> loadBundle(const std::filesystem::path& modPath, bool fromDir) {
if (fromDir) {
return std::make_unique<ModBundleDisk>(modPath);
} else {
std::vector<u8> data = io::FileStream::ReadAllBytes(modPath);
return std::make_unique<ModBundleZip>(std::move(data));
}
}
struct DllLocateResult {
std::string primary;
std::string fallback;
};
static std::string_view getFileNameWithoutExtension(const std::string_view fileName) {
return fileName.substr(0, fileName.find_last_of('.'));
}
static DllLocateResult LocateDllInBundle(ModBundle& bundle) {
std::string dllEntry, dllFallback;
for (const auto& name : bundle.getFileNames()) {
if (!name.ends_with(".dll"sv) && !name.ends_with(".dylib"sv) && !name.ends_with(".so"sv)) {
continue;
}
if (!k_archSuffix.empty() && getFileNameWithoutExtension(name).ends_with(k_archSuffix)) {
dllEntry = name;
} else if (dllFallback.empty()) {
dllFallback = name;
}
}
return DllLocateResult{dllEntry, dllFallback};
}
class InvalidModDataException : public std::runtime_error {
public:
explicit InvalidModDataException(const std::string& msg) : runtime_error(msg) {}
explicit InvalidModDataException(const char* msg) : runtime_error(msg) {}
};
static void validateModId(std::string_view const str) {
if (str.empty()) {
throw InvalidModDataException("Missing ID value in mod metadata!");
}
bool lastWasPeriod = false;
for (auto const chr : str) {
if (chr == '.') {
if (lastWasPeriod) {
throw InvalidModDataException("Cannot have two consecutive periods in mod ID!");
}
lastWasPeriod = true;
continue;
}
lastWasPeriod = false;
if (chr == '_')
continue;
if (chr >= '0' && chr <= '9')
continue;
if (chr >= 'a' && chr <= 'z')
continue;
if (chr >= 'A' && chr <= 'Z')
continue;
throw InvalidModDataException(fmt::format("Invalid character '{}' in mod ID. Valid characters are period, underscore, and alphanumerics.", chr));
}
}
static ModMetadata loadMetadata(const std::filesystem::path& modPath, ModBundle& bundle) {
const auto metaJson = bundle.readFile("mod.json");
auto j = nlohmann::json::parse(metaJson);
std::string metaId = j.value("id", "");
std::string metaName = j.value("name", "");
std::string metaVersion = j.value("version", "");
std::string metaAuthor = j.value("author", "");
std::string metaDescription = j.value("description", "");
const bool hasCode = j.value("has_code", false);
validateModId(metaId);
if (metaName.empty()) {
metaName = io::fs_path_to_string(modPath.stem());
}
if (metaVersion.empty()) {
metaVersion = "?"s;
}
if (metaAuthor.empty()) {
metaAuthor = "unknown"s;
}
return ModMetadata{
std::move(metaId),
std::move(metaName),
std::move(metaVersion),
std::move(metaAuthor),
std::move(metaDescription),
hasCode,
};
}
template <std::ranges::input_range TIter>
bool checkDuplicateMod(
const ModMetadata& metadata, TIter mods) {
return std::ranges::any_of(mods,
[&](const LoadedMod& mod) { return mod.metadata.id == metadata.id; });
}
void ModLoader::tryLoadNativeMod(LoadedMod& mod) {
if (!EnableCodeMods) {
Log.error("Code mods are not available in this build");
mod.native_status = NativeModStatus::BuildDisabled;
return;
}
namespace fs = std::filesystem;
auto [dllEntry, dllFallback] = LocateDllInBundle(*mod.bundle);
if (dllEntry.empty()) {
dllEntry = dllFallback;
}
if (dllEntry.empty()) {
Log.error(
"no *{} found in {} — skipping", NativeModule::LibraryExtension, mod.metadata.id);
mod.native_status = NativeModStatus::ModMissingPlatform;
return;
}
const fs::path cacheDir = m_modsDir / ".cache" / mod.metadata.id;
std::error_code ec;
fs::create_directories(cacheDir, ec);
const fs::path dllCachePath = cacheDir / fs::path(dllEntry).filename();
std::vector<u8> dllData;
try {
dllData = mod.bundle->readFile(dllEntry);
} catch (const std::runtime_error& e) {
Log.error(
"failed to extract {} from {}", dllEntry, mod.metadata.id);
return;
}
{
std::ofstream out(dllCachePath, std::ios::binary | std::ios::out);
if (!out) {
Log.error("failed to write {}", io::fs_path_to_string(dllCachePath));
return;
}
out.write(
reinterpret_cast<const char*>(dllData.data()),
static_cast<std::streamsize>(dllData.size()));
}
auto nativeMod = std::make_unique<NativeMod>();
try {
nativeMod->handle = std::make_unique<NativeModule>(dllCachePath);
} catch (const std::runtime_error& e) {
Log.error("failed to open {}: {}", io::fs_path_to_string(dllCachePath), e.what());
return;
}
const auto mod_api_ver = nativeMod->handle->LookupSymbol<uint32_t*>("mod_api_version");
if (mod_api_ver && *mod_api_ver != DUSK_MOD_API_VERSION) {
Log.error("{} expects API v{} but engine is v{}, skipping",
io::fs_path_to_string(fs::path(dllEntry).filename()), *mod_api_ver, DUSK_MOD_API_VERSION);
mod.native_status = NativeModStatus::ApiVersionMismatch;
return;
}
nativeMod->fn_init = nativeMod->handle->LookupSymbol<NativeMod::FnInit>("mod_init");
nativeMod->fn_tick = nativeMod->handle->LookupSymbol<NativeMod::FnTick>("mod_tick");
nativeMod->fn_cleanup = nativeMod->handle->LookupSymbol<NativeMod::FnCleanup>("mod_cleanup");
if (!nativeMod->fn_init || !nativeMod->fn_tick) {
Log.error("{} missing mod_init or mod_tick — skipping",
io::fs_path_to_string(fs::path(dllEntry).filename()));
return;
}
mod.dir = io::fs_path_to_string(fs::absolute(cacheDir));
mod.native = std::move(nativeMod);
mod.native_status = NativeModStatus::Loaded;
}
static std::string escapeModIdForConfig(std::string_view const id) {
std::string buf;
// Simple escaping. All characters in mod IDs literal, except for '.' and '_'.
// '.' -> '_', '_' -> '__'
for (char const chr : id) {
if (chr == '.') {
buf.push_back('_');
} else if (chr == '_') {
buf.push_back('_');
buf.push_back('_');
} else {
buf.push_back(chr);
}
}
return buf;
}
static std::string modEnabledCVarName(std::string_view const id) {
return fmt::format("mod.{}.enabled", escapeModIdForConfig(id));
}
void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir) {
namespace fs = std::filesystem;
std::unique_ptr<ModBundle> bundle;
try {
bundle = loadBundle(modPath, fromDir);
} catch (const std::runtime_error& e) {
Log.error("Failed to open {} bundle: {}", io::fs_path_to_string(modPath.filename()), e.what());
return;
}
ModMetadata metadata;
try
{
metadata = loadMetadata(modPath, *bundle);
}
catch (const std::runtime_error& e) {
Log.error(
"bad mod.json in {}: {}", io::fs_path_to_string(modPath.filename()), e.what());
return;
}
if (checkDuplicateMod(metadata, mods())) {
Log.error(
"mod with id '{}' already exists, not loading {}",
metadata.id,
io::fs_path_to_string(modPath.filename()));
return;
}
const auto& inserted = m_mods.emplace_back(std::make_unique<LoadedMod>());
auto& mod = *inserted;
mod.active = true;
mod.mod_path = io::fs_path_to_string(fs::absolute(modPath));
mod.metadata = std::move(metadata);
mod.bundle = std::move(bundle);
mod.cvarIsEnabled = std::make_unique<ConfigVar<bool>>(modEnabledCVarName(mod.metadata.id), true);
if (mod.metadata.hasCode) {
mod.native_status = NativeModStatus::Unknown;
tryLoadNativeMod(mod);
// Native mod lod failure DOES NOT block insertion into m_mods.
// We still want to be able to present the failed load in the UI!
if (mod.native_status != NativeModStatus::Loaded) {
Log.error("Native mod '{}' failed to load, disabling", metadata.id);
mod.active = false;
}
}
Log.info(
"found '{}' ('{}') v{} by {} ({})",
mod.metadata.name,
mod.metadata.id,
mod.metadata.version,
mod.metadata.author,
io::fs_path_to_string(modPath.filename()));
}
void ModLoader::init() {
if (m_initialized) {
return;
}
m_initialized = true;
namespace fs = std::filesystem;
if (!fs::is_directory(m_modsDir)) {
Log.info(
"mods directory '{}' not found — mod loading skipped", io::fs_path_to_string(m_modsDir));
return;
}
std::error_code ec;
std::vector<fs::directory_entry> entries;
for (auto& e : fs::directory_iterator(m_modsDir, ec)) {
if (e.is_directory() && std::filesystem::exists(e.path() / "mod.json")) {
entries.push_back(e);
} else if (e.is_regular_file() && e.path().extension() == ".dusk") {
entries.push_back(e);
}
}
std::sort(entries.begin(), entries.end(),
[](const fs::directory_entry& a, const fs::directory_entry& b) {
return a.path().filename() < b.path().filename();
});
m_mods.reserve(entries.size());
for (auto& entry : entries) {
tryLoadDusk(entry.path(), entry.is_directory());
}
if (m_mods.empty()) {
Log.info("no mods found");
return;
}
Log.info("initializing {} mod(s)...", m_mods.size());
for (auto& mod : mods()) {
Register(*mod.cvarIsEnabled);
if (!mod.cvarIsEnabled->getValue()) {
Log.info("Mod '{}' is disabled by config", mod.metadata.id);
mod.active = false;
}
}
for (auto& mod : active_mods()) {
if (mod.native) {
buildAPI(mod);
}
}
for (auto& mod : active_mods()) {
if (!mod.native) {
continue;
}
Log.debug("Initializing '{}'", mod.metadata.id);
ModGuard guard(&mod);
try {
mod.native->fn_init(&mod.native->api);
if (!mod.load_failed) {
Log.info("'{}' initialized", mod.metadata.id);
} else {
mod.active = false;
Log.error("'{}' failed to load due to hook conflicts", mod.metadata.id);
}
} catch (const std::exception& e) {
mod.active = false;
Log.error("exception in {}.mod_init(): {}", mod.metadata.id, e.what());
} catch (...) {
mod.active = false;
Log.error("unknown exception in {}.mod_init()", mod.metadata.id);
}
}
initOverlayFiles();
auto active = std::ranges::count_if(mods(), [](const LoadedMod& m) { return m.active; });
Log.info("{}/{} mod(s) active", active, m_mods.size());
}
void ModLoader::tick() {
for (auto& mod : active_mods()) {
if (!mod.native) {
continue;
}
ModGuard guard(&mod);
try {
mod.native->fn_tick(&mod.native->api);
} catch (const std::exception& e) {
Log.error("exception in {}.mod_tick(): {} — disabling", mod.metadata.id, e.what());
mod.active = false;
} catch (...) {
Log.error("unknown exception in {}.mod_tick() — disabling", mod.metadata.id);
mod.active = false;
}
}
}
void ModLoader::shutdown() {
for (auto& mod : mods()) {
hookClearMod(&mod);
if (mod.native && mod.native->fn_cleanup) {
ModGuard guard(&mod);
try {
mod.native->fn_cleanup(&mod.native->api);
} catch (...) {
}
}
OrphanedConfigVars.emplace_back(std::move(mod.cvarIsEnabled));
}
m_mods.clear();
g_services.clear();
Log.info("all mods unloaded");
}
} // namespace dusk
-73
View File
@@ -1,73 +0,0 @@
#pragma once
#include <filesystem>
#include "miniz.h"
#include "dusk/mod_loader.hpp"
namespace dusk::modding {
#if DUSK_CODE_MODS
constexpr bool EnableCodeMods = true;
#else
constexpr bool EnableCodeMods = false;
#endif
class ModBundle {
public:
virtual ~ModBundle() = default;
virtual std::vector<u8> readFile(const std::string& fileName) = 0;
virtual std::vector<std::string> getFileNames() = 0;
virtual size_t getFileSize(const std::string& fileName) = 0;
};
class ModBundleZip final : public ModBundle {
public:
explicit ModBundleZip(std::vector<u8>&& data);
~ModBundleZip() override;
std::vector<u8> readFile(const std::string& fileName) override;
std::vector<std::string> getFileNames() override;
size_t getFileSize(const std::string& fileName) override;
private:
std::vector<uint8_t> zip_data;
mz_zip_archive res_zip{};
bool res_zip_open = false;
};
class ModBundleDisk final : public ModBundle {
public:
explicit ModBundleDisk(std::filesystem::path root);
~ModBundleDisk() override = default;
std::vector<u8> readFile(const std::string& fileName) override;
std::vector<std::string> getFileNames() override;
size_t getFileSize(const std::string& fileName) override;
private:
[[nodiscard]] std::filesystem::path toRealPath(const std::string& fileName) const;
std::filesystem::path root_path;
};
extern thread_local LoadedMod* g_currentMod;
extern std::unordered_map<std::string, void*> g_services;
extern thread_local void* g_dusk_hook_current_mod;
struct ModGuard {
explicit ModGuard(dusk::LoadedMod* m) {
g_currentMod = m;
g_dusk_hook_current_mod = m;
}
~ModGuard() {
g_currentMod = nullptr;
g_dusk_hook_current_mod = nullptr;
}
};
inline const char* modName() {
return g_currentMod ? g_currentMod->metadata.id.c_str() : "mod";
}
} // namespace dusk::modding
-285
View File
@@ -1,285 +0,0 @@
#include <RmlUi/Core.h>
#include "dusk/hook_system.hpp"
#include "dusk/logging.h"
#include "dusk/mod_api.h"
#include "dusk/mod_loader.hpp"
#include "mod_loader.hpp"
using namespace dusk::modding;
namespace dusk::modding {
thread_local LoadedMod* g_currentMod = nullptr;
std::unordered_map<std::string, void*> g_services;
thread_local void* g_dusk_hook_current_mod = nullptr;
}
namespace {
void cb_log_info(const char* fmt, ...) {
va_list ap, ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
std::string s(vsnprintf(nullptr, 0, fmt, ap2), '\0');
va_end(ap2);
vsnprintf(s.data(), s.size() + 1, fmt, ap);
va_end(ap);
DuskLog.info("[{}] {}", modName(), s);
}
void cb_log_warn(const char* fmt, ...) {
va_list ap, ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
std::string s(vsnprintf(nullptr, 0, fmt, ap2), '\0');
va_end(ap2);
vsnprintf(s.data(), s.size() + 1, fmt, ap);
va_end(ap);
DuskLog.warn("[{}] {}", modName(), s);
}
void cb_log_error(const char* fmt, ...) {
va_list ap, ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
std::string s(vsnprintf(nullptr, 0, fmt, ap2), '\0');
va_end(ap2);
vsnprintf(s.data(), s.size() + 1, fmt, ap);
va_end(ap);
DuskLog.error("[{}] {}", modName(), s);
}
void* cb_load_resource(const char* relative_path, size_t* out_size) {
if (out_size) {
*out_size = 0;
}
if (!g_currentMod || !relative_path) {
DuskLog.error("load_resource: called outside mod context or with null path");
return nullptr;
}
std::string entry = std::string("res/") + relative_path;
std::vector<u8> data;
try {
data = g_currentMod->bundle->readFile(entry);
} catch (const std::runtime_error& e) {
DuskLog.error("[{}] load_resource: '{}' failed: {}", g_currentMod->metadata.id, entry, e.what());
return nullptr;
}
const auto retPtr = std::malloc(data.size());
std::memcpy(retPtr, data.data(), data.size());
if (out_size) {
*out_size = data.size();
}
return retPtr;
}
void cb_free_resource(void* data) {
std::free(data);
}
class ModClickListener : public Rml::EventListener {
public:
ModClickListener(void (*cb)(void*), void* ud) : m_cb(cb), m_ud(ud) {}
void ProcessEvent(Rml::Event&) override { m_cb(m_ud); }
void OnDetach(Rml::Element*) override { delete this; }
private:
void (*m_cb)(void*);
void* m_ud;
};
std::string escape_rml(const char* text) {
std::string out;
for (const char* p = text; *p; ++p) {
switch (*p) {
case '&': out += "&amp;"; break;
case '<': out += "&lt;"; break;
case '>': out += "&gt;"; break;
default: out += *p; break;
}
}
return out;
}
void cb_panel_add_section(DuskPanelHandle panel, const char* text) {
auto* pane = static_cast<Rml::Element*>(panel);
if (!pane || !text) {
return;
}
auto el = pane->GetOwnerDocument()->CreateElement("div");
el->SetClass("section-heading", true);
el->SetInnerRML(escape_rml(text));
pane->AppendChild(std::move(el));
}
void cb_panel_add_button(DuskPanelHandle panel, const char* label,
void (*cb)(void*), void* userdata) {
auto* pane = static_cast<Rml::Element*>(panel);
if (!pane || !label || !cb) {
return;
}
auto btn = pane->GetOwnerDocument()->CreateElement("button");
btn->SetInnerRML(escape_rml(label));
btn->AddEventListener(Rml::EventId::Click, new ModClickListener(cb, userdata));
pane->AppendChild(std::move(btn));
}
DuskElemHandle cb_panel_add_badge_row(DuskPanelHandle panel, const char* label, int ok) {
auto* pane = static_cast<Rml::Element*>(panel);
if (!pane || !label) {
return nullptr;
}
auto* doc = pane->GetOwnerDocument();
auto row = doc->CreateElement("div");
row->SetClass("mod-info-row", true);
auto badge = doc->CreateElement("span");
badge->SetClass("achievement-badge", true);
badge->SetClass(ok ? "unlocked" : "locked", true);
badge->SetInnerRML(ok ? "PASS" : "WAIT");
Rml::Element* badgePtr = row->AppendChild(std::move(badge));
auto lbl = doc->CreateElement("span");
lbl->SetClass("mod-info-value", true);
lbl->SetInnerRML(escape_rml(label));
row->AppendChild(std::move(lbl));
pane->AppendChild(std::move(row));
return static_cast<DuskElemHandle>(badgePtr);
}
DuskElemHandle cb_panel_add_dyn_text(DuskPanelHandle panel, const char* text) {
auto* pane = static_cast<Rml::Element*>(panel);
if (!pane) {
return nullptr;
}
auto el = pane->GetOwnerDocument()->CreateElement("div");
el->SetInnerRML(text ? escape_rml(text) : std::string{});
Rml::Element* ptr = pane->AppendChild(std::move(el));
return static_cast<DuskElemHandle>(ptr);
}
void cb_elem_set_badge(DuskElemHandle elem, int ok) {
auto* el = static_cast<Rml::Element*>(elem);
if (!el) {
return;
}
el->SetClass("unlocked", ok != 0);
el->SetClass("locked", ok == 0);
el->SetInnerRML(ok ? "PASS" : "WAIT");
}
void cb_elem_set_text(DuskElemHandle elem, const char* text) {
auto* el = static_cast<Rml::Element*>(elem);
if (!el || !text) {
return;
}
el->SetInnerRML(escape_rml(text));
}
DuskElemHandle cb_panel_add_progress(DuskPanelHandle panel, float value) {
auto* pane = static_cast<Rml::Element*>(panel);
if (!pane) {
return nullptr;
}
auto el = pane->GetOwnerDocument()->CreateElement("progress");
el->SetClass("progress-health", true);
el->SetAttribute("value", value);
Rml::Element* ptr = pane->AppendChild(std::move(el));
return static_cast<DuskElemHandle>(ptr);
}
void cb_elem_set_progress(DuskElemHandle elem, float value) {
auto* el = static_cast<Rml::Element*>(elem);
if (!el) {
return;
}
el->SetAttribute("value", value);
}
void cb_register_tab_content(void (*build_fn)(void*, void*), void* userdata) {
if (g_currentMod && build_fn) {
g_currentMod->tab_content.push_back({build_fn, userdata});
}
}
void cb_register_tab_update(void (*update_fn)(void*), void* userdata) {
if (g_currentMod && update_fn) {
g_currentMod->tab_updates.push_back({update_fn, userdata});
}
}
void cb_service_publish(const char* name, void* ptr) {
if (!name) {
return;
}
if (g_services.count(name)) {
DuskLog.error(
"[{}] service_publish: '{}' already published by another mod", modName(), name);
}
g_services[name] = ptr;
}
void* cb_service_get(const char* name) {
if (!name) {
return nullptr;
}
auto it = g_services.find(name);
return it != g_services.end() ? it->second : nullptr;
}
void api_hook_pre(void* addr, int32_t (*fn)(void* args)) {
dusk::hookRegisterPre(addr, g_currentMod, fn);
}
void api_hook_post(void* addr, void (*fn)(void* args, void* retval)) {
dusk::hookRegisterPost(addr, g_currentMod, modName(), fn);
}
void api_hook_replace(void* addr, void (*fn)(void* args, void* retval)) {
if (!dusk::hookSetReplace(addr, g_currentMod, modName(), fn)) {
if (g_currentMod) {
g_currentMod->load_failed = true;
}
}
}
}
namespace dusk {
void ModLoader::buildAPI(LoadedMod& mod) {
auto& native = *mod.native;
native.api.api_version = DUSK_MOD_API_VERSION;
native.api.mod_dir = mod.dir.c_str();
native.api.log_info = cb_log_info;
native.api.log_warn = cb_log_warn;
native.api.log_error = cb_log_error;
native.api.load_resource = cb_load_resource;
native.api.free_resource = cb_free_resource;
native.api.register_tab_content = cb_register_tab_content;
native.api.register_tab_update = cb_register_tab_update;
native.api.panel_add_section = cb_panel_add_section;
native.api.panel_add_button = cb_panel_add_button;
native.api.panel_add_badge_row = cb_panel_add_badge_row;
native.api.panel_add_dyn_text = cb_panel_add_dyn_text;
native.api.elem_set_badge = cb_elem_set_badge;
native.api.elem_set_text = cb_elem_set_text;
native.api.panel_add_progress = cb_panel_add_progress;
native.api.elem_set_progress = cb_elem_set_progress;
native.api.hook_install = hookInstallByAddr;
native.api.hook_pre = api_hook_pre;
native.api.hook_post = api_hook_post;
native.api.hook_replace = api_hook_replace;
native.api.hook_dispatch_pre = hookDispatchPre;
native.api.hook_dispatch_post = hookDispatchPost;
native.api.service_publish = cb_service_publish;
native.api.service_get = cb_service_get;
}
}
-111
View File
@@ -1,111 +0,0 @@
#include "aurora/dvd.h"
#include "aurora/lib/logging.hpp"
#include "dusk/mod_loader.hpp"
#include "mod_loader.hpp"
#include <cstring>
using namespace std::string_literals;
namespace {
aurora::Module Log("dusk::modLoader::overlay");
struct OverlayFileData {
std::string bundlePath;
dusk::LoadedMod* mod; // TODO: is using a raw pointer a bad idea here?
};
std::vector<OverlayFileData> s_overlayFiles;
void findOverlayFiles(std::vector<AuroraOverlayFile>& files, dusk::LoadedMod& mod) {
for (const auto& file : mod.bundle->getFileNames()) {
if (!file.starts_with("overlay/")) {
continue;
}
auto overlayPath = file.substr("overlay/"s.size());
assert(!overlayPath.starts_with('/'));
overlayPath.insert(0, "/");
const auto size = mod.bundle->getFileSize(file);
const auto index = s_overlayFiles.size();
s_overlayFiles.emplace_back(file, &mod);
files.emplace_back(
strdup(overlayPath.c_str()),
reinterpret_cast<void*>(index),
size);
}
}
struct OpenOverlayFile {
std::vector<u8> data;
size_t pos;
};
void* cbOpen(void* userdata) {
const auto index = reinterpret_cast<size_t>(userdata);
const auto& fileData = s_overlayFiles[index];
auto fileContents = fileData.mod->bundle->readFile(fileData.bundlePath);
return new OpenOverlayFile(std::move(fileContents), 0);
}
void cbClose(void* handle) {
const auto openFile = static_cast<OpenOverlayFile*>(handle);
delete openFile;
}
int64_t cbRead(void* handle, uint8_t *buf, const size_t len) {
auto& openFile = *static_cast<OpenOverlayFile*>(handle);
const auto remainingSpace = openFile.data.size() - openFile.pos;
const auto toRead = std::min(remainingSpace, len);
std::memcpy(buf, openFile.data.data() + openFile.pos, toRead);
openFile.pos += toRead;
return static_cast<int64_t>(toRead);
}
int64_t cbSeek(void* handle, int64_t offset, int32_t whence) {
if (whence != 0) {
Log.fatal("Invalid seek mode from aurora: {}", whence);
}
auto& openFile = *static_cast<OpenOverlayFile*>(handle);
const auto posSigned = std::clamp(offset, static_cast<int64_t>(0), static_cast<int64_t>(openFile.data.size()));
openFile.pos = static_cast<size_t>(posSigned);
return posSigned;
}
constexpr AuroraOverlayCallbacks s_overlayCallbacks = {
.open = cbOpen,
.close = cbClose,
.read = cbRead,
.seek = cbSeek,
};
}
namespace dusk {
void ModLoader::initOverlayFiles() {
Log.debug("Initializing overlay files...");
aurora_dvd_overlay_callbacks(&s_overlayCallbacks);
std::vector<AuroraOverlayFile> files;
for (auto& mod : active_mods()) {
findOverlayFiles(files, mod);
}
Log.debug("Found {} overlay files.", files.size());
aurora_dvd_overlay_files(files.data(), files.size(), nullptr);
for (const auto& file : files) {
std::free(const_cast<char*>(file.fileName));
}
}
} // namespace dusk
-83
View File
@@ -1,83 +0,0 @@
#include "native_module.hpp"
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#endif
namespace {
#if defined(_WIN32)
void* pl_dlopen(const std::filesystem::path& p) {
return LoadLibraryW(p.wstring().c_str());
}
void* pl_dlsym(void* h, const char* name) {
return reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(h), name));
}
void pl_dlclose(void* h) {
FreeLibrary(static_cast<HMODULE>(h));
}
std::string pl_dlerror() {
char buf[256]{};
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
GetLastError(), 0, buf, sizeof(buf), nullptr);
std::string s = buf;
while (!s.empty() && (s.back() == '\r' || s.back() == '\n')) {
s.pop_back();
}
return s;
}
#else
#include <dlfcn.h>
static void* pl_dlopen(const std::filesystem::path& p) {
#if defined(__linux__)
return dlopen(p.c_str(), RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
#else
return dlopen(p.c_str(), RTLD_LAZY | RTLD_LOCAL);
#endif
}
static void* pl_dlsym(void* h, const char* name) {
return dlsym(h, name);
}
static void pl_dlclose(void* h) {
dlclose(h);
}
static std::string pl_dlerror() {
const char* e = dlerror();
return e ? e : "(unknown error)";
}
#endif
}
namespace dusk::modding {
NativeModule::NativeModule() noexcept : handle(nullptr) {
}
NativeModule::NativeModule(NativeModule&& other) noexcept {
handle = other.handle;
other.handle = nullptr;
}
NativeModule& NativeModule::operator=(NativeModule&& other) noexcept {
handle = other.handle;
other.handle = nullptr;
return *this;
}
NativeModule::NativeModule(const std::filesystem::path& path) {
handle = pl_dlopen(path);
if (!handle) {
throw std::runtime_error(pl_dlerror());
}
}
NativeModule::~NativeModule() {
if (handle) {
pl_dlclose(handle);
}
}
void* NativeModule::LookupSymbol(const char* name) const {
return pl_dlsym(handle, name);
}
} // namespace dusk::modding
-35
View File
@@ -1,35 +0,0 @@
#pragma once
#include <filesystem>
namespace dusk::modding {
class NativeModule final {
public:
NativeModule() noexcept;
NativeModule(const NativeModule& other) = delete;
NativeModule(NativeModule&& other) noexcept;
explicit NativeModule(const std::filesystem::path& path);
~NativeModule();
void* LookupSymbol(const char* name) const;
template<typename T>
T LookupSymbol(const char* name) const {
return reinterpret_cast<T>(LookupSymbol(name));
}
NativeModule& operator=(NativeModule&& other) noexcept;
#if defined(_WIN32)
static constexpr auto LibraryExtension = ".dll";
#elif defined(__APPLE__)
static constexpr auto LibraryExtension = ".dylib";
#else
static constexpr auto LibraryExtension = ".so";
#endif
private:
void* handle;
};
}
+74 -52
View File
@@ -1,54 +1,69 @@
#include "dusk/mouse.h" #include "dusk/mouse.h"
#include "d/actor/d_a_alink.h"
#include "d/d_com_inf_game.h"
#include "dusk/menu_pointer.h" #include "dusk/menu_pointer.h"
#include "dusk/settings.h" #include "dusk/settings.h"
#include "dusk/ui/ui.hpp" #include "dusk/ui/ui.hpp"
#include "d/actor/d_a_alink.h"
#include "d/d_com_inf_game.h"
#include <aurora/lib/window.hpp>
#include <imgui.h>
#include <SDL3/SDL_mouse.h> #include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_video.h> #include <SDL3/SDL_video.h>
#include <aurora/lib/window.hpp>
#include <imgui.h>
#include <chrono>
namespace dusk::mouse { namespace dusk::mouse {
namespace { namespace {
constexpr float kMousePixelToRad = 0.0025f; using Clock = std::chrono::steady_clock;
constexpr int kIdleHideFrames = 99; // Approx. 3 seconds with 33ms ticks
float s_aim_yaw_rad = 0.0f; constexpr float kMousePixelToRad = 0.0025f;
float s_aim_pitch_rad = 0.0f; constexpr auto kCursorIdleDuration = std::chrono::seconds(1);
float s_camera_yaw_rad = 0.0f;
float s_aim_yaw_rad = 0.0f;
float s_aim_pitch_rad = 0.0f;
float s_camera_yaw_rad = 0.0f;
float s_camera_pitch_rad = 0.0f; float s_camera_pitch_rad = 0.0f;
int s_idle_frames = 0; Clock::time_point s_last_cursor_motion = Clock::now();
void reset_deltas() { void reset_deltas() {
s_aim_yaw_rad = s_aim_pitch_rad = 0.0f; s_aim_yaw_rad = s_aim_pitch_rad = 0.0f;
s_camera_yaw_rad = s_camera_pitch_rad = 0.0f; s_camera_yaw_rad = s_camera_pitch_rad = 0.0f;
} }
bool queryMouseAimContext() { bool query_mouse_aim_context() {
return getSettings().game.enableMouseAim.getValue() && dCamera_c::isAimActive(); return getSettings().game.enableMouseAim.getValue() && dCamera_c::isAimActive();
} }
bool wantMouseCapture() { bool want_mouse_capture() {
return getSettings().game.enableMouseCamera.getValue() || queryMouseAimContext(); return getSettings().game.enableMouseCamera.getValue() || query_mouse_aim_context();
} }
bool isWindowFocused(SDL_Window* window) { bool mouse_input_enabled() {
const auto& game = getSettings().game;
return game.enableMouseAim.getValue() || game.enableMouseCamera.getValue();
}
bool is_window_focused(SDL_Window* window) {
if (window == nullptr) { if (window == nullptr) {
return false; return false;
} }
return (SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS) != 0; return (SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS) != 0;
} }
bool shouldCaptureMouse(SDL_Window* window) { bool imgui_windows_visible() {
if (window == nullptr || ui::any_document_visible() || menu_pointer::active()) { return ImGui::GetIO().MetricsRenderWindows > 0;
return false;
}
return wantMouseCapture() && isWindowFocused(window);
} }
bool syncCaptureState(SDL_Window* window, bool should_capture) { bool should_capture_mouse(SDL_Window* window) {
if (window == nullptr || ui::any_document_visible() || imgui_windows_visible() ||
menu_pointer::active())
{
return false;
}
return want_mouse_capture() && is_window_focused(window);
}
bool sync_capture_state(SDL_Window* window, bool should_capture) {
if (window == nullptr) { if (window == nullptr) {
reset_deltas(); reset_deltas();
return false; return false;
@@ -78,7 +93,7 @@ bool syncCaptureState(SDL_Window* window, bool should_capture) {
return is_captured; return is_captured;
} }
void accumulateDeltas(float mx_rel, float my_rel, bool camera_active, bool aim_active) { void accumulate_deltas(float mx_rel, float my_rel, bool camera_active, bool aim_active) {
const auto& game = getSettings().game; const auto& game = getSettings().game;
const bool mirror_mode = game.enableMirrorMode.getValue(); const bool mirror_mode = game.enableMirrorMode.getValue();
const bool invert_y = game.invertMouseY.getValue(); const bool invert_y = game.invertMouseY.getValue();
@@ -114,57 +129,62 @@ void set_cursor_visible(bool visible) {
} }
} }
void update_cursor_visibility(SDL_Window* window, bool captured) { bool cursor_idle() {
if (window == nullptr || !isWindowFocused(window)) { return Clock::now() - s_last_cursor_motion >= kCursorIdleDuration;
return; }
}
bool should_show_cursor(bool captured) {
if (captured) { if (captured) {
s_idle_frames = 0; return false;
set_cursor_visible(false); }
if (ui::any_document_visible()) {
return true;
}
if (imgui_windows_visible()) {
return true;
}
if (menu_pointer::enabled() && menu_pointer::active()) {
return true;
}
if (mouse_input_enabled()) {
return false;
}
return !cursor_idle();
}
void update_cursor_visibility(SDL_Window* window, bool captured) {
if (window == nullptr || !is_window_focused(window)) {
return; return;
} }
const ImGuiIO& io = ImGui::GetIO(); set_cursor_visible(should_show_cursor(captured));
if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) {
s_idle_frames = 0;
set_cursor_visible(true);
return;
}
if (s_idle_frames < kIdleHideFrames) {
++s_idle_frames;
set_cursor_visible(true);
} else {
set_cursor_visible(false);
}
} }
} // namespace } // namespace
void read() { void read() {
SDL_Window* window = aurora::window::get_sdl_window(); SDL_Window* window = aurora::window::get_sdl_window();
const bool capture_active = syncCaptureState(window, shouldCaptureMouse(window)); const bool capture_active = sync_capture_state(window, should_capture_mouse(window));
update_cursor_visibility(window, capture_active); update_cursor_visibility(window, capture_active);
if (!capture_active) { if (!capture_active) {
return; return;
} }
const bool aim_active = capture_active && queryMouseAimContext(); const bool aim_active = capture_active && query_mouse_aim_context();
const bool camera_active = capture_active && getSettings().game.enableMouseCamera; const bool camera_active = capture_active && getSettings().game.enableMouseCamera;
float mx_rel = 0.0f; float mx_rel = 0.0f;
float my_rel = 0.0f; float my_rel = 0.0f;
SDL_GetRelativeMouseState(&mx_rel, &my_rel); SDL_GetRelativeMouseState(&mx_rel, &my_rel);
accumulateDeltas(mx_rel, my_rel, camera_active, aim_active); accumulate_deltas(mx_rel, my_rel, camera_active, aim_active);
} }
void getAimDeltas(float& out_yaw, float& out_pitch) { void get_aim_deltas(float& out_yaw, float& out_pitch) {
out_yaw = s_aim_yaw_rad; out_yaw = s_aim_yaw_rad;
out_pitch = s_aim_pitch_rad; out_pitch = s_aim_pitch_rad;
} }
void getCameraDeltas(float& out_yaw, float& out_pitch) { void get_camera_deltas(float& out_yaw, float& out_pitch) {
out_yaw = 0.0f; out_yaw = 0.0f;
out_pitch = 0.0f; out_pitch = 0.0f;
@@ -178,26 +198,28 @@ void getCameraDeltas(float& out_yaw, float& out_pitch) {
void handle_event(const SDL_Event& event) noexcept { void handle_event(const SDL_Event& event) noexcept {
switch (event.type) { switch (event.type) {
case SDL_EVENT_MOUSE_MOTION:
s_last_cursor_motion = Clock::now();
break;
case SDL_EVENT_WINDOW_FOCUS_LOST: case SDL_EVENT_WINDOW_FOCUS_LOST:
onFocusLost(); on_focus_lost();
break; break;
case SDL_EVENT_WINDOW_FOCUS_GAINED: case SDL_EVENT_WINDOW_FOCUS_GAINED:
onFocusGained(); on_focus_gained();
break; break;
} }
} }
void onFocusLost() { void on_focus_lost() {
SDL_Window* window = aurora::window::get_sdl_window(); SDL_Window* window = aurora::window::get_sdl_window();
if (window != nullptr) { if (window != nullptr) {
syncCaptureState(window, false); sync_capture_state(window, false);
} }
s_idle_frames = 0;
set_cursor_visible(true); set_cursor_visible(true);
} }
void onFocusGained() { void on_focus_gained() {
SDL_Window* window = aurora::window::get_sdl_window(); SDL_Window* window = aurora::window::get_sdl_window();
syncCaptureState(window, shouldCaptureMouse(window)); sync_capture_state(window, should_capture_mouse(window));
} }
} // namespace dusk::mouse } // namespace dusk::mouse
+5 -1
View File
@@ -1,5 +1,6 @@
#include "dusk/settings.h" #include "dusk/settings.h"
#include "dusk/config.hpp" #include "dusk/config.hpp"
#include <aurora/aurora.h>
namespace dusk { namespace dusk {
@@ -96,6 +97,7 @@ UserSettings g_userSettings = {
.invertMouseY {"game.invertMouseY", false}, .invertMouseY {"game.invertMouseY", false},
.freeCamera {"game.freeCamera", false}, .freeCamera {"game.freeCamera", false},
.enableTouchControls {"game.enableTouchControls", false}, .enableTouchControls {"game.enableTouchControls", false},
.touchTargeting {"game.touchTargeting", TouchTargeting::Hybrid},
.enableMenuPointer {"game.enableMenuPointer", true}, .enableMenuPointer {"game.enableMenuPointer", true},
.touchControlsLayout {"game.touchControlsLayout", ui::ControlLayout{}}, .touchControlsLayout {"game.touchControlsLayout", ui::ControlLayout{}},
.invertCameraXAxis {"game.invertCameraXAxis", false}, .invertCameraXAxis {"game.invertCameraXAxis", false},
@@ -266,7 +268,8 @@ void registerSettings() {
Register(g_userSettings.game.touchCameraYSensitivity); Register(g_userSettings.game.touchCameraYSensitivity);
Register(g_userSettings.game.minimalHUD); Register(g_userSettings.game.minimalHUD);
Register(g_userSettings.game.hudScale); Register(g_userSettings.game.hudScale);
Register(g_userSettings.game.pauseOnFocusLost); Register(g_userSettings.game.pauseOnFocusLost,
[](const bool& value, const bool&) { aurora_set_pause_on_focus_lost(value); });
Register(g_userSettings.game.enableDiscordPresence); Register(g_userSettings.game.enableDiscordPresence);
Register(g_userSettings.game.bloomMode); Register(g_userSettings.game.bloomMode);
Register(g_userSettings.game.bloomMultiplier); Register(g_userSettings.game.bloomMultiplier);
@@ -329,6 +332,7 @@ void registerSettings() {
Register(g_userSettings.game.invertMouseY); Register(g_userSettings.game.invertMouseY);
Register(g_userSettings.game.freeCamera); Register(g_userSettings.game.freeCamera);
Register(g_userSettings.game.enableTouchControls); Register(g_userSettings.game.enableTouchControls);
Register(g_userSettings.game.touchTargeting);
Register(g_userSettings.game.enableMenuPointer); Register(g_userSettings.game.enableMenuPointer);
Register(g_userSettings.game.touchControlsLayout); Register(g_userSettings.game.touchControlsLayout);
Register(g_userSettings.game.debugFlyCam); Register(g_userSettings.game.debugFlyCam);
+1 -1
View File
@@ -34,9 +34,9 @@ void resetForSpeedrunMode() {
getSettings().game.fastRoll.setSpeedrunValue(false); getSettings().game.fastRoll.setSpeedrunValue(false);
getSettings().game.fastSpinner.setSpeedrunValue(false); getSettings().game.fastSpinner.setSpeedrunValue(false);
getSettings().game.armorRupeeDrain.setSpeedrunValue(MagicArmorMode::NORMAL); getSettings().game.armorRupeeDrain.setSpeedrunValue(MagicArmorMode::NORMAL);
getSettings().game.invincibleEnemies.setSpeedrunValue(false);
getSettings().game.pauseOnFocusLost.setSpeedrunValue(false); getSettings().game.pauseOnFocusLost.setSpeedrunValue(false);
aurora_set_pause_on_focus_lost(false);
getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false); getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false);
getSettings().game.recordingMode.setSpeedrunValue(false); getSettings().game.recordingMode.setSpeedrunValue(false);
+3
View File
@@ -7,6 +7,9 @@ float s_pitch_dp = 0.0f;
} // namespace } // namespace
void add_delta(float yaw_dp, float pitch_dp) noexcept { void add_delta(float yaw_dp, float pitch_dp) noexcept {
if (getSettings().game.enableMirrorMode) {
yaw_dp *= -1.0;
}
s_yaw_dp += yaw_dp; s_yaw_dp += yaw_dp;
s_pitch_dp += pitch_dp; s_pitch_dp += pitch_dp;
} }
+5 -5
View File
@@ -278,7 +278,7 @@ ControllerConfigWindow::ControllerConfigWindow(bool prelaunch) {
void ControllerConfigWindow::hide(bool close) { void ControllerConfigWindow::hide(bool close) {
stop_rumble_test(); stop_rumble_test();
cancel_pending_binding(); cancel_pending_binding();
config::Save(); config::save();
Window::hide(close); Window::hide(close);
} }
@@ -403,7 +403,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADClearPort(port); PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), FALSE); PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSerializeMappings(); PADSerializeMappings();
ClearAllActionBindings(port); config::ClearAllActionBindings(port);
refresh_controller_page(); refresh_controller_page();
}); });
@@ -417,7 +417,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADClearPort(port); PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), TRUE); PADSetKeyboardActive(static_cast<u32>(port), TRUE);
PADSerializeMappings(); PADSerializeMappings();
ClearAllActionBindings(port); config::ClearAllActionBindings(port);
}); });
const u32 controllerCount = PADCount(); const u32 controllerCount = PADCount();
@@ -439,7 +439,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADSetKeyboardActive(static_cast<u32>(port), FALSE); PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSetPortForIndex(i, port); PADSetPortForIndex(i, port);
PADSerializeMappings(); PADSerializeMappings();
ClearAllActionBindings(port); config::ClearAllActionBindings(port);
}); });
} }
break; break;
@@ -1125,7 +1125,7 @@ void ControllerConfigWindow::poll_pending_binding() {
return; return;
} }
mPendingActionBinding->setValue(button); mPendingActionBinding->setValue(button);
config::Save(); config::save();
finish_pending_binding(completedPort); finish_pending_binding(completedPort);
} }
return; return;
+4
View File
@@ -51,6 +51,8 @@ struct ControlProps {
float h = 0.0f; float h = 0.0f;
float scale = 1.0f; float scale = 1.0f;
ControlAnchor anchor = ControlAnchor::None; ControlAnchor anchor = ControlAnchor::None;
bool operator==(const ControlProps&) const = default;
}; };
struct ControlRect { struct ControlRect {
@@ -76,6 +78,8 @@ struct ControlLayout {
int version = Version; int version = Version;
std::map<std::string, ControlProps, std::less<> > controls; std::map<std::string, ControlProps, std::less<> > controls;
bool operator==(const ControlLayout&) const = default;
}; };
constexpr std::array<std::string_view, 9> kControlLayoutIds = { constexpr std::array<std::string_view, 9> kControlLayoutIds = {
+1 -1
View File
@@ -300,7 +300,7 @@ void GraphicsTuner::show() {
} }
void GraphicsTuner::hide(bool close) { void GraphicsTuner::hide(bool close) {
config::Save(); config::save();
mRoot->RemoveAttribute("open"); mRoot->RemoveAttribute("open");
if (close) { if (close) {
mPendingClose = true; mPendingClose = true;
+2 -2
View File
@@ -16,7 +16,6 @@
#include "f_pc/f_pc_name.h" #include "f_pc/f_pc_name.h"
#include "imgui.h" #include "imgui.h"
#include "modal.hpp" #include "modal.hpp"
#include "mods_window.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "ui.hpp" #include "ui.hpp"
#include "warp.hpp" #include "warp.hpp"
@@ -59,7 +58,8 @@ MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById(
} }
mTabBar->add_tab("Achievements", [this] { push(std::make_unique<AchievementsWindow>()); }); mTabBar->add_tab("Achievements", [this] { push(std::make_unique<AchievementsWindow>()); });
mTabBar->add_tab("Mods", [this] { push(std::make_unique<ModsWindow>()); });
mTabBar->add_tab("Reset", [this] { mTabBar->add_tab("Reset", [this] {
mTabBar->set_active_tab(-1); mTabBar->set_active_tab(-1);
const auto dismiss = [](Modal& modal) { modal.pop(); }; const auto dismiss = [](Modal& modal) { modal.pop(); };
-125
View File
@@ -1,125 +0,0 @@
#include "mods_window.hpp"
#include "dusk/mod_loader.hpp"
#include "fmt/format.h"
#include "pane.hpp"
namespace dusk::ui {
namespace {
Rml::String build_mod_detail_rml(const dusk::LoadedMod& mod) {
const char* statusClass;
const char* statusText;
if (mod.load_failed) {
statusClass = "locked";
statusText = "Failed";
} else if (mod.active) {
statusClass = "unlocked";
statusText = "Active";
} else {
statusClass = "";
statusText = "Disabled";
}
return fmt::format(
R"(<div class="mod-info-row">)"
R"(<span class="mod-info-label">Version</span>)"
R"(<span class="mod-info-value">{}</span>)"
R"(</div>)"
R"(<div class="mod-info-row">)"
R"(<span class="mod-info-label">Author</span>)"
R"(<span class="mod-info-value">{}</span>)"
R"(</div>)"
R"(<div class="mod-info-row">)"
R"(<span class="mod-info-label">Status</span>)"
R"(<span class="achievement-badge {}">{}</span>)"
R"(</div>)"
R"(<div class="mod-info-row">)"
R"(<span class="mod-info-label">Path</span>)"
R"(<span class="mod-info-value mod-path">{}</span>)"
R"(</div>)",
mod.metadata.version,
mod.metadata.author,
statusClass, statusText,
mod.mod_path
);
}
} // namespace
ModsWindow::ModsWindow() {
const auto& mods = dusk::ModLoader::instance().mods();
if (mods.empty()) {
add_tab("Mods", [this](Rml::Element* content) {
auto& pane = add_child<Pane>(content, Pane::Type::Uncontrolled);
pane.add_text("No mods installed.");
pane.finalize();
});
return;
}
for (ModIndex i = 0; i < mods.size(); ++i) {
mSnapshot.push_back({mods[i].active, mods[i].load_failed});
add_tab(mods[i].metadata.name, [this, i](Rml::Element* content) {
mActiveModIndex = static_cast<int>(i);
const auto& curMods = dusk::ModLoader::instance().mods();
if (i >= curMods.size()) {
return;
}
const auto& mod = curMods[i];
auto& pane = add_child<Pane>(content, Pane::Type::Uncontrolled);
pane.add_section("Details");
pane.add_rml(build_mod_detail_rml(mod));
if (!mod.metadata.description.empty()) {
pane.add_section("Description");
pane.add_text(mod.metadata.description);
}
for (const auto& cb : mod.tab_content) {
cb.build_fn(static_cast<void*>(pane.root()), cb.userdata);
}
pane.finalize();
});
}
}
void ModsWindow::update() {
const auto& mods = dusk::ModLoader::instance().mods();
bool dirty = mods.size() != mSnapshot.size();
if (!dirty) {
for (ModIndex i = 0; i < mods.size(); ++i) {
if (mods[i].active != mSnapshot[i].active ||
mods[i].load_failed != mSnapshot[i].load_failed)
{
dirty = true;
break;
}
}
}
if (dirty) {
mSnapshot.clear();
for (const auto& mod : mods) {
mSnapshot.push_back({mod.active, mod.load_failed});
}
refresh_active_tab();
}
if (mActiveModIndex >= 0 && static_cast<size_t>(mActiveModIndex) < mods.size()) {
for (const auto& cb : mods[mActiveModIndex].tab_updates) {
cb.update_fn(cb.userdata);
}
}
Window::update();
}
} // namespace dusk::ui
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include "window.hpp"
#include <vector>
#include "dusk/mod_loader.hpp"
namespace dusk::ui {
class ModsWindow : public Window {
public:
ModsWindow();
void update() override;
private:
struct ModSnapshot {
bool active;
bool load_failed;
};
std::vector<ModSnapshot> mSnapshot;
ModIndex mActiveModIndex = 0;
};
} // namespace dusk::ui
+15 -6
View File
@@ -197,10 +197,19 @@ void remove_element(Rml::Element*& elem) noexcept {
} // namespace } // namespace
static std::string FormatTime(OSTime ticks) { static std::string FormatElapsedTime(OSTime ticksElapsed) {
OSCalendarTime t; using namespace std::chrono;
OSTicksToCalendarTime(ticks, &t);
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", t.hour, t.min, t.sec, t.msec); milliseconds ms{OSTicksToMilliseconds(ticksElapsed)};
const hours hr = duration_cast<hours>(ms);
ms -= hr;
const minutes min = duration_cast<minutes>(ms);
ms -= min;
const seconds sec = duration_cast<seconds>(ms);
ms -= sec;
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", hr.count(), min.count(), sec.count(), ms.count());
} }
Overlay::Overlay() : Document(kDocumentSource, true) { Overlay::Overlay() : Document(kDocumentSource, true) {
@@ -317,13 +326,13 @@ void Overlay::update() {
if (getSettings().game.showSpeedrunRTATimer) { if (getSettings().game.showSpeedrunRTATimer) {
mSpeedrunRta->SetAttribute("open", ""); mSpeedrunRta->SetAttribute("open", "");
mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatTime(elapsedTime)))); mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatElapsedTime(elapsedTime))));
} else { } else {
mSpeedrunRta->RemoveAttribute("open"); mSpeedrunRta->RemoveAttribute("open");
} }
mSpeedrunIgt->SetInnerRML( mSpeedrunIgt->SetInnerRML(
escape(fmt::format("IGT {}", FormatTime(m_speedrunInfo.m_igtTimer)))); escape(fmt::format("IGT {}", FormatElapsedTime(m_speedrunInfo.m_igtTimer))));
} else { } else {
mSpeedrunTimer->RemoveAttribute("open"); mSpeedrunTimer->RemoveAttribute("open");
} }
+4 -12
View File
@@ -28,7 +28,6 @@
#include <thread> #include <thread>
#include "m_Do/m_Do_MemCard.h" #include "m_Do/m_Do_MemCard.h"
#include "mods_window.hpp"
namespace dusk::ui { namespace dusk::ui {
namespace { namespace {
@@ -50,14 +49,14 @@ const Rml::String kDocumentSource = R"RML(
</hero> </hero>
<div id="menu-list" /> <div id="menu-list" />
</menu> </menu>
<disc-info class="intro-item delay-5"> <disc-info class="intro-item delay-4">
<div id="disc-status"> <div id="disc-status">
<icon /> <icon />
<span id="disc-status-label" /> <span id="disc-status-label" />
</div> </div>
<span id="disc-version" class="detail" /> <span id="disc-version" class="detail" />
</disc-info> </disc-info>
<version-info class="intro-item delay-6"> <version-info class="intro-item delay-5">
<div class="version">Version <span id="version-text"></span></div> <div class="version">Version <span id="version-text"></span></div>
<div id="update-status" class="update"> <div id="update-status" class="update">
<span id="update-message"></span> <span id="update-message"></span>
@@ -310,7 +309,7 @@ void persist_disc_choice(const std::string& path, iso::ValidationError validatio
getSettings().backend.isoPath.setValue(path); getSettings().backend.isoPath.setValue(path);
getSettings().backend.isoVerification.setValue(verification); getSettings().backend.isoVerification.setValue(verification);
config::Save(); config::save();
if (previousPath != path || previousVerification != verification) { if (previousPath != path || previousVerification != verification) {
iso::log_verification_state(path, verification); iso::log_verification_state(path, verification);
@@ -727,16 +726,9 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
}); });
apply_intro_animation(mMenuButtons.back()->root(), "delay-2"); apply_intro_animation(mMenuButtons.back()->root(), "delay-2");
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Mods"));
mMenuButtons.back()->on_pressed([this] {
mRestartSuppressed = false;
push(std::make_unique<ModsWindow>());
});
apply_intro_animation(mMenuButtons.back()->root(), "delay-3");
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit")); mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit"));
mMenuButtons.back()->on_pressed([] { IsRunning = false; }); mMenuButtons.back()->on_pressed([] { IsRunning = false; });
apply_intro_animation(mMenuButtons.back()->root(), "delay-4"); apply_intro_animation(mMenuButtons.back()->root(), "delay-3");
} }
mDiscStatus = mDocument->GetElementById("disc-status"); mDiscStatus = mDocument->GetElementById("disc-status");

Some files were not shown because too many files have changed in this diff Show More