Compare commits

..

30 Commits

Author SHA1 Message Date
MelonSpeedruns c509ff7647 Easy Quick Spin Cheat 2026-07-10 09:22:17 -04:00
Luke Street 6849baac06 Mod API: UI service (#2189) 2026-07-09 10:31:34 -06:00
Luke Street f3ca94b338 Merge pull request #2185 from encounter/mods-core
Dusklight mod API core
2026-07-08 22:39:18 -06:00
Luke Street 9b3b827054 Error handling fixes 2026-07-08 19:40:27 -06:00
Luke Street 4de3c20431 Fix dependency resolution on startup & unload failed startup mods 2026-07-08 18:28:26 -06:00
Luke Street 2840843e3c Catch exceptions from hook callbacks & fail mod 2026-07-08 18:27:09 -06:00
Luke Street 312195920a Remove unused HookFlags 2026-07-08 18:26:46 -06:00
Luke Street bc11bf3563 Mods manager UI & logs viewer 2026-07-08 17:51:13 -06:00
Luke Street 36635f8c62 Fix multi-configuration builds with CMake 2026-07-08 17:50:03 -06:00
Luke Street 6136d9bbf7 Set .clang-format to C++20 2026-07-08 17:49:48 -06:00
Luke Street 16c573a170 Doc updates 2026-07-08 01:47:52 -06:00
Luke Street e9e16a8ac1 Remove redundant OSInitAlloc 2026-07-08 01:47:52 -06:00
Luke Street 4339e60ba6 Hook service, symgen manifest & data linking 2026-07-08 01:47:49 -06:00
Luke Street af5635dd42 Resource, texture and overlay services 2026-07-08 01:47:35 -06:00
Luke Street c876ea558f Config service 2026-07-08 01:47:35 -06:00
Luke Street 9b75dc5fd2 Service lifecycle events 2026-07-08 01:47:35 -06:00
Luke Street 5c9c76cc0b Dusklight mod API core
Co-authored-by: qwertyquerty <qwertytrogi@gmail.com>
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2026-07-08 01:47:31 -06:00
Luke Street b474aafe9d Fix compile error & cleanup 2026-07-07 23:22:38 -06: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
454 changed files with 13272 additions and 65170 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
---
Language: Cpp
Standard: C++03
Standard: c++20
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
+142 -155
View File
@@ -5,96 +5,8 @@ if (NOT CMAKE_BUILD_TYPE)
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
endif ()
set(DUSK_VERSION_OVERRIDE "" CACHE STRING "Override version string (skips git detection and format validation)")
if (DUSK_VERSION_OVERRIDE)
set(DUSK_WC_DESCRIBE "${DUSK_VERSION_OVERRIDE}")
set(DUSK_VERSION_STRING "0.0.0.0")
set(DUSK_SHORT_VERSION_STRING "0.0.0")
set(DUSK_VERSION_CODE "1")
set(DUSK_WC_REVISION "")
set(DUSK_WC_BRANCH "")
set(DUSK_WC_DATE "")
message(STATUS "Dusklight version overridden to ${DUSK_WC_DESCRIBE}")
else ()
# obtain revision info from git
find_package(Git)
if (GIT_FOUND)
# make sure version information gets re-run when the current Git HEAD changes
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD
OUTPUT_VARIABLE dusk_git_head_filename
OUTPUT_STRIP_TRAILING_WHITESPACE)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_filename}")
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD
OUTPUT_VARIABLE dusk_git_head_symbolic
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${dusk_git_head_symbolic}
OUTPUT_VARIABLE dusk_git_head_symbolic_filename
OUTPUT_STRIP_TRAILING_WHITESPACE)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_symbolic_filename}")
# defines DUSK_WC_REVISION
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
OUTPUT_VARIABLE DUSK_WC_REVISION
OUTPUT_STRIP_TRAILING_WHITESPACE)
# defines DUSK_WC_DESCRIBE
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --match "v*"
OUTPUT_VARIABLE DUSK_WC_DESCRIBE
OUTPUT_STRIP_TRAILING_WHITESPACE)
# remove the git hash, then collapse a clean "-0" suffix only
string(REGEX REPLACE "-[^-]+(-dirty|)$" "\\1" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}")
string(REGEX REPLACE "-0$" "" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}")
# defines DUSK_WC_BRANCH
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
OUTPUT_VARIABLE DUSK_WC_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE)
# defines DUSK_WC_DATE
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad
OUTPUT_VARIABLE DUSK_WC_DATE
OUTPUT_STRIP_TRAILING_WHITESPACE)
else ()
message(STATUS "Unable to find git, commit information will not be available")
endif ()
if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)([-+].*)?$")
set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
set(_ver_major ${CMAKE_MATCH_1})
set(_ver_minor ${CMAKE_MATCH_2})
set(_ver_patch ${CMAKE_MATCH_3})
set(DUSK_VERSION_TWEAK "0")
if (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+)(-dirty)?$")
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
elseif (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-[0-9A-Za-z.-]+-([0-9]+)(-dirty)?$")
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
endif ()
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${DUSK_VERSION_TWEAK}")
if(DUSK_VERSION_TWEAK GREATER 999)
set(_tweak 999)
else()
set(_tweak ${DUSK_VERSION_TWEAK})
endif()
# encoding: major*1e7 + minor*1e5 + patch*1e3 + tweak; collision-free for major<210, minor<100, patch<100, tweak<=999
math(EXPR DUSK_VERSION_CODE
"${_ver_major} * 10000000 + ${_ver_minor} * 100000 + ${_ver_patch} * 1000 + ${_tweak}")
else ()
set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION")
set(DUSK_VERSION_STRING "0.0.0.0")
set(DUSK_SHORT_VERSION_STRING "0.0.0")
set(DUSK_VERSION_CODE "1")
endif ()
endif ()
# Add version information to CI environment variables
if(DEFINED ENV{GITHUB_ENV})
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n")
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION_CODE=${DUSK_VERSION_CODE}\n")
endif()
message(STATUS "Dusklight version set to ${DUSK_WC_DESCRIBE}")
include(cmake/DetectVersion.cmake)
detect_version()
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
project(dusklight LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
if (APPLE)
@@ -177,12 +89,12 @@ option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON)
option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
option(DUSK_PACKAGE_INSTALL "Install Dusklight with a Linux-native file structure" OFF)
option(DUSK_GFX_DEBUG_GROUPS "Report debug groups to the native graphics API" ${DUSK_GFX_DEBUG_GROUPS_DEFAULT})
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
option(DUSK_ENABLE_CODE_MODS "Enable code mods" OFF)
# Edit & Continue
if (MSVC)
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug"
AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
endif ()
if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
@@ -299,7 +211,47 @@ FetchContent_Declare(json
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
)
FetchContent_MakeAvailable(cxxopts json)
message(STATUS "dusklight: 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
)
set(_fetch_content_deps cxxopts json miniz)
if (DUSK_ENABLE_CODE_MODS)
message(STATUS "dusklight: Fetching funchook")
# cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a
# PATCH_COMMAND into capstone's inner ExternalProject. That PATCH_COMMAND runs
# cmake/PatchCapstone.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/PatchCapstone.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/PatchFunchook.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)
if (APPLE AND CMAKE_OSX_ARCHITECTURES)
list(LENGTH CMAKE_OSX_ARCHITECTURES _osx_arch_count)
if (_osx_arch_count EQUAL 1)
list(GET CMAKE_OSX_ARCHITECTURES 0 _osx_arch)
if (_osx_arch MATCHES "^(arm64|aarch64|ARM64)$")
set(FUNCHOOK_CPU arm64 CACHE STRING "" FORCE)
elseif (_osx_arch MATCHES "^(x86_64|AMD64|amd64|i[3-6]86|x86)$")
set(FUNCHOOK_CPU x86 CACHE STRING "" FORCE)
endif ()
endif ()
endif ()
list(APPEND _fetch_content_deps funchook)
endif ()
FetchContent_MakeAvailable(${_fetch_content_deps})
if (DUSK_ENABLE_SENTRY_NATIVE)
message(STATUS "dusklight: Fetching sentry-native")
@@ -333,21 +285,7 @@ if(_arch MATCHES "^(arm|aarch64)" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQU
add_compile_options(-fsigned-char)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL Windows)
set(PLATFORM_NAME win32)
elseif (CMAKE_SYSTEM_NAME STREQUAL Darwin)
if (IOS)
set(PLATFORM_NAME ios)
elseif (TVOS)
set(PLATFORM_NAME tvos)
else ()
set(PLATFORM_NAME macos)
endif ()
else ()
string(TOLOWER CMAKE_SYSTEM_NAME PLATFORM_NAME)
endif ()
configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_BINARY_DIR}/version.h)
configure_version_header()
include(files.cmake)
@@ -363,22 +301,15 @@ set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_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)
set(GAME_INCLUDE_DIRS
include
src
assets/GZ2E01 # TODO: make this dynamic if needed?
libs/JSystem/include
libs
extern/aurora/include/dolphin
extern
${CMAKE_BINARY_DIR})
include(cmake/GameABIConfig.cmake)
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
aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt
Threads::Threads zstd::libzstd)
Threads::Threads zstd::libzstd dusklight_game_headers)
if (DUSK_ENABLE_CODE_MODS)
list(APPEND GAME_LIBS funchook-static)
endif ()
if (DUSK_ENABLE_SENTRY_NATIVE)
list(APPEND GAME_LIBS sentry)
@@ -437,8 +368,6 @@ if (DUSK_MOVIE_SUPPORT)
list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1)
endif ()
include(src/dusk/randomizer/randomizer.cmake)
set(DUSK_ENABLE_DISCORD_DEFAULT ON)
if (DEFINED DUSK_ENABLE_DISCORD_RPC AND NOT DEFINED DUSK_ENABLE_DISCORD)
set(DUSK_ENABLE_DISCORD_DEFAULT ${DUSK_ENABLE_DISCORD_RPC})
@@ -448,8 +377,8 @@ if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
endif ()
if(ANDROID)
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
if (DUSK_ENABLE_CODE_MODS)
list(APPEND GAME_COMPILE_DEFS DUSK_CODE_MODS=1)
endif ()
if (DUSK_PACKAGE_INSTALL)
@@ -476,7 +405,7 @@ set(GAME_DEBUG_FILES
set_source_files_properties(
${GAME_DEBUG_FILES}
PROPERTIES
COMPILE_DEFINITIONS "$<$<CONFIG:Debug>:DEBUG=1>;$<$<CONFIG:Debug>:PARTIAL_DEBUG=1>"
COMPILE_DEFINITIONS "$<$<CONFIG:Debug>:DEBUG=1>"
)
# game_base is for all other game code files
@@ -490,16 +419,11 @@ set(GAME_BASE_FILES
set_source_files_properties(
${GAME_BASE_FILES}
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"
)
foreach(jsystem_lib IN LISTS JSYSTEM_LIBRARIES)
target_compile_definitions(${jsystem_lib} PRIVATE
${GAME_COMPILE_DEFS}
$<$<CONFIG:Debug>:DEBUG=1>
$<$<CONFIG:Debug>:PARTIAL_DEBUG=1>
)
target_include_directories(${jsystem_lib} PRIVATE ${GAME_INCLUDE_DIRS})
target_compile_definitions(${jsystem_lib} PRIVATE ${GAME_COMPILE_DEFS} $<$<CONFIG:Debug>:DEBUG=1>)
target_link_libraries(${jsystem_lib} PRIVATE ${GAME_LIBS})
set_target_properties(${jsystem_lib} PROPERTIES FOLDER "JSystem")
endforeach()
@@ -511,7 +435,7 @@ if (CMAKE_CXX_LINK_GROUP_USING_RESCAN_SUPPORTED OR CMAKE_LINK_GROUP_USING_RESCAN
set(JSYSTEM_LINK_LIBRARIES "$<LINK_GROUP:RESCAN,${JSYSTEM_LIBRARIES}>")
endif ()
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES})
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES} ${miniz_SOURCE_DIR}/miniz.c)
if(ANDROID)
add_library(dusklight SHARED ${DUSK_FILES})
set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
@@ -522,28 +446,56 @@ if (ENABLE_ASAN)
target_sources(dusklight PRIVATE src/dusk/asan_options.c)
endif ()
# Add embedded data to target
file(GLOB_RECURSE RANDOMIZER_DATA "src/dusk/randomizer/generator/data/*")
file(GLOB_RECURSE RANDOMIZER_ASSETS "src/dusk/randomizer/assets/*")
list(APPEND RANDOMIZER_DATA ${RANDOMIZER_ASSETS})
set(RANDO_ROOT_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR})
foreach (RANDOMIZER_FILE IN LISTS RANDOMIZER_DATA)
file(RELATIVE_PATH REL_PATH ${RANDO_ROOT_DATA_DIR} ${RANDOMIZER_FILE})
if(REL_PATH MATCHES "^src/dusk/randomizer/generator/data/tests")
message(STATUS "Skipping Embed: ${REL_PATH}")
continue()
endif ()
message(STATUS "Embedding File: ${REL_PATH}")
b_embed(dusklight ${REL_PATH})
endforeach ()
target_compile_definitions(dusklight PRIVATE ${GAME_COMPILE_DEFS})
target_include_directories(dusklight PRIVATE ${GAME_INCLUDE_DIRS})
target_include_directories(dusklight PRIVATE ${miniz_SOURCE_DIR})
target_link_libraries(dusklight PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
target_precompile_headers(dusklight PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
if (DUSK_ENABLE_CODE_MODS)
include(cmake/SymbolManifest.cmake)
if (WIN32)
# Game ABI exports & import library for mod linking
include(cmake/WindowsExports.cmake)
setup_windows_exports(dusklight)
endif ()
# Post-link symbol manifest: hookable-surface name->address map keyed to the build.
setup_symbol_manifest(dusklight)
endif ()
# Hook reliability: guaranteed patchable entries on the game ABI surface, and no identical-code folding.
if (MSVC)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(DUSK_PATCHABLE_ENTRY_FLAG $<$<COMPILE_LANGUAGE:C,CXX>:/hotpatch>)
endif ()
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64")
target_link_options(dusklight PRIVATE /FUNCTIONPADMIN:16 /OPT:NOICF)
else ()
target_link_options(dusklight PRIVATE /FUNCTIONPADMIN /OPT:NOICF)
endif ()
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" OR CMAKE_OSX_ARCHITECTURES MATCHES "arm64")
set(DUSK_PATCHABLE_ENTRY_FLAG $<$<COMPILE_LANGUAGE:C,CXX>:-fpatchable-function-entry=2,1>)
else ()
set(DUSK_PATCHABLE_ENTRY_FLAG $<$<COMPILE_LANGUAGE:C,CXX>:-fpatchable-function-entry=10,5>)
endif ()
endif ()
if (DEFINED DUSK_PATCHABLE_ENTRY_FLAG)
target_compile_options(dusklight PRIVATE ${DUSK_PATCHABLE_ENTRY_FLAG})
foreach(jsystem_lib IN LISTS JSYSTEM_LIBRARIES)
target_compile_options(${jsystem_lib} PRIVATE ${DUSK_PATCHABLE_ENTRY_FLAG})
endforeach()
endif ()
if (WIN32)
target_link_libraries(dusklight PRIVATE Psapi)
endif ()
if (APPLE)
# Mods resolve game symbols from the executable at dlopen time.
target_link_options(dusklight PRIVATE -Wl,-export_dynamic)
elseif (UNIX AND NOT ANDROID)
target_link_options(dusklight PRIVATE -rdynamic)
endif ()
if (TARGET crashpad_handler)
add_dependencies(dusklight crashpad_handler)
add_custom_command(TARGET dusklight POST_BUILD
@@ -562,6 +514,7 @@ endif ()
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1")
target_link_libraries(dusklight PRIVATE dl)
endif ()
if (NOT APPLE)
@@ -602,6 +555,13 @@ if (WIN32)
endif ()
endif ()
include(cmake/ModSDK.cmake)
if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
add_custom_target(dusklight_mods) # Aggregate target for all in-tree mods
add_subdirectory(tools/mod_template)
endif ()
if (APPLE)
if (IOS)
set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios)
@@ -609,6 +569,7 @@ if (APPLE)
set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/tvos)
else ()
set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos)
set(DUSK_ENTITLEMENTS ${DUSK_RESOURCE_DIR}/Dusklight.entitlements)
endif ()
set(DUSK_INFO_PLIST ${DUSK_RESOURCE_DIR}/Info.plist.in)
file(GLOB_RECURSE DUSK_RESOURCE_FILES
@@ -628,8 +589,7 @@ if (APPLE)
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}")
endforeach ()
set_target_properties(
dusklight PROPERTIES
set(_apple_bundle_properties
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
@@ -640,6 +600,31 @@ if (APPLE)
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES"
)
if (DUSK_ENTITLEMENTS)
list(APPEND _apple_bundle_properties
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${DUSK_ENTITLEMENTS})
endif ()
if (NOT IOS AND NOT TVOS)
list(APPEND _apple_bundle_properties
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES")
endif ()
set_target_properties(dusklight PROPERTIES ${_apple_bundle_properties})
if (NOT IOS AND NOT TVOS AND NOT "${CMAKE_GENERATOR}" STREQUAL "Xcode")
set(_sign_nested_commands)
if (TARGET crashpad_handler)
list(APPEND _sign_nested_commands
COMMAND /usr/bin/codesign --force --sign -
"$<TARGET_FILE_DIR:dusklight>/crashpad_handler")
endif ()
add_custom_command(TARGET dusklight POST_BUILD
${_sign_nested_commands}
COMMAND /usr/bin/codesign --force --sign - --entitlements
"${DUSK_ENTITLEMENTS}" "$<TARGET_BUNDLE_DIR:dusklight>"
COMMENT "Signing Dusklight.app with entitlements"
VERBATIM
)
endif ()
endif ()
if (APPLE AND NOT IOS AND NOT TVOS)
@@ -763,3 +748,5 @@ foreach (target IN LISTS BINARY_TARGETS)
endif ()
endforeach ()
endforeach ()
install_bundled_mods()
+18 -2
View File
@@ -72,7 +72,11 @@
"type": "BOOL",
"value": false
},
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install"
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
"DUSK_ENABLE_CODE_MODS": {
"type": "BOOL",
"value": true
}
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
@@ -159,6 +163,10 @@
"CMAKE_C_COMPILER": "cl",
"CMAKE_CXX_COMPILER": "cl",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
"DUSK_ENABLE_CODE_MODS": {
"type": "BOOL",
"value": true
},
"CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": {
"type": "BOOL",
"value": true
@@ -262,7 +270,11 @@
"type": "BOOL",
"value": false
},
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install"
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
"DUSK_ENABLE_CODE_MODS": {
"type": "BOOL",
"value": true
}
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
@@ -394,6 +406,10 @@
"type": "BOOL",
"value": false
},
"DUSK_ENABLE_CODE_MODS": {
"type": "BOOL",
"value": true
},
"CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": {
"type": "BOOL",
"value": true
+121
View File
@@ -0,0 +1,121 @@
# Version detection shared by the main build and the mod SDK (sdk/CMakeLists.txt)
include_guard(GLOBAL)
get_filename_component(_DUSK_VERSION_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
set(DUSK_VERSION_OVERRIDE "" CACHE STRING "Override version string (skips git detection and format validation)")
macro(detect_version)
if (DUSK_VERSION_OVERRIDE)
set(DUSK_WC_DESCRIBE "${DUSK_VERSION_OVERRIDE}")
set(DUSK_VERSION_STRING "0.0.0.0")
set(DUSK_SHORT_VERSION_STRING "0.0.0")
set(DUSK_VERSION_CODE "1")
set(DUSK_WC_REVISION "")
set(DUSK_WC_BRANCH "")
set(DUSK_WC_DATE "")
message(STATUS "Dusklight version overridden to ${DUSK_WC_DESCRIBE}")
else ()
# obtain revision info from git
find_package(Git)
if (GIT_FOUND)
# make sure version information gets re-run when the current Git HEAD changes
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD
OUTPUT_VARIABLE dusk_git_head_filename
OUTPUT_STRIP_TRAILING_WHITESPACE)
get_filename_component(dusk_git_head_filename "${dusk_git_head_filename}" ABSOLUTE BASE_DIR "${_DUSK_VERSION_ROOT}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_filename}")
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD
OUTPUT_VARIABLE dusk_git_head_symbolic
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT}
COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${dusk_git_head_symbolic}
OUTPUT_VARIABLE dusk_git_head_symbolic_filename
OUTPUT_STRIP_TRAILING_WHITESPACE)
get_filename_component(dusk_git_head_symbolic_filename "${dusk_git_head_symbolic_filename}" ABSOLUTE BASE_DIR "${_DUSK_VERSION_ROOT}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_symbolic_filename}")
# defines DUSK_WC_REVISION
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
OUTPUT_VARIABLE DUSK_WC_REVISION
OUTPUT_STRIP_TRAILING_WHITESPACE)
# defines DUSK_WC_DESCRIBE
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --match "v*"
OUTPUT_VARIABLE DUSK_WC_DESCRIBE
OUTPUT_STRIP_TRAILING_WHITESPACE)
# remove the git hash, then collapse a clean "-0" suffix only
string(REGEX REPLACE "-[^-]+(-dirty|)$" "\\1" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}")
string(REGEX REPLACE "-0$" "" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}")
# defines DUSK_WC_BRANCH
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
OUTPUT_VARIABLE DUSK_WC_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE)
# defines DUSK_WC_DATE
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad
OUTPUT_VARIABLE DUSK_WC_DATE
OUTPUT_STRIP_TRAILING_WHITESPACE)
else ()
message(STATUS "Unable to find git, commit information will not be available")
endif ()
if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)([-+].*)?$")
set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
set(_ver_major ${CMAKE_MATCH_1})
set(_ver_minor ${CMAKE_MATCH_2})
set(_ver_patch ${CMAKE_MATCH_3})
set(DUSK_VERSION_TWEAK "0")
if (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+)(-dirty)?$")
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
elseif (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-[0-9A-Za-z.-]+-([0-9]+)(-dirty)?$")
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
endif ()
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${DUSK_VERSION_TWEAK}")
if (DUSK_VERSION_TWEAK GREATER 999)
set(_tweak 999)
else ()
set(_tweak ${DUSK_VERSION_TWEAK})
endif ()
# encoding: major*1e7 + minor*1e5 + patch*1e3 + tweak; collision-free for major<210, minor<100, patch<100, tweak<=999
math(EXPR DUSK_VERSION_CODE
"${_ver_major} * 10000000 + ${_ver_minor} * 100000 + ${_ver_patch} * 1000 + ${_tweak}")
else ()
set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION")
set(DUSK_VERSION_STRING "0.0.0.0")
set(DUSK_SHORT_VERSION_STRING "0.0.0")
set(DUSK_VERSION_CODE "1")
endif ()
endif ()
# Add version information to CI environment variables
if (DEFINED ENV{GITHUB_ENV})
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n")
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION_CODE=${DUSK_VERSION_CODE}\n")
endif ()
message(STATUS "Dusklight version set to ${DUSK_WC_DESCRIBE}")
endmacro()
# Sets PLATFORM_NAME and configures version.h into the caller's binary dir.
macro(configure_version_header)
if (CMAKE_SYSTEM_NAME STREQUAL Windows)
set(PLATFORM_NAME win32)
elseif (CMAKE_SYSTEM_NAME STREQUAL Darwin)
if (IOS)
set(PLATFORM_NAME ios)
elseif (TVOS)
set(PLATFORM_NAME tvos)
else ()
set(PLATFORM_NAME macos)
endif ()
else ()
string(TOLOWER CMAKE_SYSTEM_NAME PLATFORM_NAME)
endif ()
configure_file(${_DUSK_VERSION_ROOT}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
endmacro()
+27
View File
@@ -0,0 +1,27 @@
# The game ABI surface shared by the main build and the mod SDK (sdk/CMakeLists.txt)
include_guard(GLOBAL)
get_filename_component(_game_root "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
# PARTIAL_DEBUG makes debug and release share one struct/vtable ABI so a mod binary loads into either
set(_game_compile_defs TARGET_PC=1 WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1 PARTIAL_DEBUG=1)
if (ANDROID)
list(APPEND _game_compile_defs TARGET_ANDROID=1)
endif ()
set(_game_include_dirs
${_game_root}/include
${_game_root}/src
${_game_root}/assets/GZ2E01 # TODO: make this dynamic if needed?
${_game_root}/libs/JSystem/include
${_game_root}/libs
${_game_root}/extern/aurora/include/dolphin
${_game_root}/extern/aurora/include
${_game_root}/extern
${CMAKE_CURRENT_BINARY_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 ${_game_compile_defs})
+279
View File
@@ -0,0 +1,279 @@
# add_mod(<target> SOURCES <file>... MOD_JSON <mod.json> [RES_DIR <res>] [OVERLAY_DIR <overlay>]
# [TEXTURES_DIR <textures>] [OUTPUT_DIR <dir>] [BUNDLE])
set(DUSK_MODS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/mods" CACHE PATH "Directory to write mod packages into")
function(_mod_lib_name out_var)
set(_arch "${CMAKE_SYSTEM_PROCESSOR}")
if (APPLE AND CMAKE_OSX_ARCHITECTURES)
list(LENGTH CMAKE_OSX_ARCHITECTURES _count)
if (_count GREATER 1)
message(FATAL_ERROR "add_mod: universal binaries are not supported")
endif ()
set(_arch "${CMAKE_OSX_ARCHITECTURES}")
endif ()
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _platform)
string(TOLOWER "${_arch}" _arch)
if (_arch MATCHES "^(i[3-6]86|x86)$")
set(_arch "x86")
endif ()
if (WIN32)
set(_ext ".dll")
elseif (APPLE)
set(_ext ".dylib")
else ()
set(_ext ".so")
endif ()
set(${out_var} "${_platform}-${_arch}${_ext}" PARENT_SCOPE)
endfunction()
function(_mod_resolve_source_path out_var path)
if (IS_ABSOLUTE "${path}")
set(_path "${path}")
else ()
set(_path "${CMAKE_CURRENT_SOURCE_DIR}/${path}")
endif ()
set(${out_var} "${_path}" PARENT_SCOPE)
endfunction()
function(_mod_collect_assets out_var dir)
if (NOT IS_DIRECTORY "${dir}")
message(FATAL_ERROR "add_mod: asset directory does not exist: ${dir}")
endif ()
file(GLOB_RECURSE _files CONFIGURE_DEPENDS LIST_DIRECTORIES false "${dir}/*")
set(${out_var} ${_files} PARENT_SCOPE)
endfunction()
function(add_mod target_name)
cmake_parse_arguments(ARG "BUNDLE" "MOD_JSON;RES_DIR;OVERLAY_DIR;TEXTURES_DIR;OUTPUT_DIR" "SOURCES" ${ARGN})
if (NOT ARG_MOD_JSON)
message(FATAL_ERROR "add_mod: MOD_JSON is required")
endif ()
_mod_resolve_source_path(_mod_json "${ARG_MOD_JSON}")
if (NOT EXISTS "${_mod_json}")
message(FATAL_ERROR "add_mod: MOD_JSON does not exist: ${_mod_json}")
endif ()
add_library(${target_name} SHARED ${ARG_SOURCES})
_mod_lib_name(_lib_name)
set_target_properties(${target_name} PROPERTIES
PREFIX ""
C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
WINDOWS_EXPORT_ALL_SYMBOLS OFF)
target_compile_features(${target_name} PRIVATE cxx_std_20)
target_link_libraries(${target_name} PRIVATE dusklight_game_headers)
if (NOT TARGET dusklight)
# Apply global compile options for out-of-tree mod builds
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
target_compile_options(${target_name} PRIVATE
-Wno-multichar -Wno-trigraphs -Wno-deprecated-declarations)
elseif (APPLE)
target_compile_options(${target_name} PRIVATE
-Wno-declaration-after-statement -Wno-non-pod-varargs)
elseif (MSVC)
target_compile_options(${target_name} PRIVATE
"$<$<COMPILE_LANGUAGE:C,CXX>:/bigobj>"
"$<$<COMPILE_LANGUAGE:C,CXX>:/utf-8>")
endif ()
# Use signed char on ARM to match the original game (and x86)
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _mod_arch)
if (_mod_arch MATCHES "^(arm|aarch64)" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
target_compile_options(${target_name} PRIVATE -fsigned-char)
endif ()
endif ()
if (APPLE)
# Game symbols resolve against the host executable at dlopen time.
target_link_options(${target_name} PRIVATE -undefined dynamic_lookup)
elseif (ANDROID)
if (TARGET dusklight)
target_link_libraries(${target_name} PRIVATE dusklight)
elseif (DUSK_GAME_SOLIB)
target_link_libraries(${target_name} PRIVATE "${DUSK_GAME_SOLIB}")
else ()
message(FATAL_ERROR "add_mod: DUSK_GAME_SOLIB is not set (libmain.so)")
endif ()
elseif (UNIX)
target_link_options(${target_name} PRIVATE -Wl,--allow-shlib-undefined)
elseif (WIN32)
# Link against the generated import library (game ABI surface). Function calls
# resolve through import thunks. Data is toolchain dependent:
# - clang-cl: lld's mingw mode auto-imports data references, fixed up at load by
# the mod SDK's pseudo-relocation runtime (pseudo_reloc.cpp).
# - cl (MSVC): only DUSK_GAME_DATA-annotated data is reachable. Un-annotated
# references fail to link.
if (NOT DUSK_GAME_IMPLIB)
message(FATAL_ERROR "add_mod: DUSK_GAME_IMPLIB is not set.")
endif ()
target_link_libraries(${target_name} PRIVATE "${DUSK_GAME_IMPLIB}")
set_target_properties(${target_name} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
target_compile_definitions(${target_name} PRIVATE _ITERATOR_DEBUG_LEVEL=0)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(${target_name} PRIVATE "$<$<COMPILE_LANGUAGE:C,CXX>:/clang:-mcmodel=large>")
target_sources(${target_name} PRIVATE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../sdk/pseudo_reloc.cpp")
# lld mingw mode rewrites /DEFAULTLIB directives to -l style and skips %LIB%, so
# the CRT libraries and search paths are spelled out explicitly.
target_link_options(${target_name} PRIVATE -lldmingw /nodefaultlib /INCREMENTAL:NO)
target_link_libraries(${target_name} PRIVATE
msvcrt.lib msvcprt.lib vcruntime.lib ucrt.lib
oldnames.lib uuid.lib kernel32.lib user32.lib)
set(_lib_dirs "$ENV{LIB}")
if ("${_lib_dirs}" STREQUAL "")
message(FATAL_ERROR "add_mod: %LIB% is empty; configure from a VS dev shell")
endif ()
foreach (_libdir IN LISTS _lib_dirs)
target_link_options(${target_name} PRIVATE "/libpath:${_libdir}")
endforeach ()
endif ()
endif ()
set(_output_dir "${DUSK_MODS_OUTPUT_DIR}")
if (ARG_OUTPUT_DIR)
set(_output_dir "${ARG_OUTPUT_DIR}")
endif ()
set(_stage "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_stage")
set(_out "${_output_dir}/${target_name}.dusk")
set(_zip_args "${_lib_name}" mod.json)
set(_package_deps "${_mod_json}")
set(_package_inputs "${_mod_json}")
set(_extra_cmds "")
if (ARG_RES_DIR)
_mod_resolve_source_path(_res_dir "${ARG_RES_DIR}")
_mod_collect_assets(_res_deps "${_res_dir}")
list(APPEND _package_deps ${_res_deps})
list(APPEND _package_inputs "${_res_dir}" ${_res_deps})
list(APPEND _zip_args res)
list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} -E copy_directory
"${_res_dir}" "${_stage}/res")
endif ()
if (ARG_OVERLAY_DIR)
_mod_resolve_source_path(_overlay_dir "${ARG_OVERLAY_DIR}")
_mod_collect_assets(_overlay_deps "${_overlay_dir}")
list(APPEND _package_deps ${_overlay_deps})
list(APPEND _package_inputs "${_overlay_dir}" ${_overlay_deps})
list(APPEND _zip_args overlay)
list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} -E copy_directory
"${_overlay_dir}" "${_stage}/overlay")
endif ()
if (ARG_TEXTURES_DIR)
_mod_resolve_source_path(_textures_dir "${ARG_TEXTURES_DIR}")
_mod_collect_assets(_textures_deps "${_textures_dir}")
list(APPEND _package_deps ${_textures_deps})
list(APPEND _package_inputs "${_textures_dir}" ${_textures_deps})
list(APPEND _zip_args textures)
list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} -E copy_directory
"${_textures_dir}" "${_stage}/textures")
endif ()
set(_bundle_cmds "")
if (ARG_BUNDLE AND TARGET dusklight)
file(READ "${_mod_json}" _mod_json_text)
string(JSON _mod_id GET "${_mod_json_text}" id)
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_TARGETS "${target_name}")
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_IDS "${_mod_id}")
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_STAGES "${_stage}")
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_PACKAGES "${_out}")
set_property(GLOBAL APPEND PROPERTY DUSK_BUNDLED_MOD_LIB_NAMES "${_lib_name}")
set(_bundle_cmds
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundled_mods"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_out}" "${CMAKE_BINARY_DIR}/bundled_mods/${target_name}.dusk")
endif ()
set(_package_target "${target_name}_package")
set(_package_inputs_file "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_package_inputs.txt")
list(SORT _package_inputs)
set(_package_inputs_text "")
foreach (_package_input IN LISTS _package_inputs)
string(APPEND _package_inputs_text "${_package_input}\n")
endforeach ()
file(GENERATE OUTPUT "${_package_inputs_file}" CONTENT "${_package_inputs_text}")
add_custom_command(OUTPUT "${_out}"
COMMAND ${CMAKE_COMMAND} -E rm -rf "${_stage}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${_stage}" "${_output_dir}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:${target_name}>" "${_stage}/${_lib_name}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_mod_json}" "${_stage}/mod.json"
${_extra_cmds}
COMMAND ${CMAKE_COMMAND} -E chdir "${_stage}" ${CMAKE_COMMAND} -E tar cvf "${_out}" --format=zip ${_zip_args}
${_bundle_cmds}
DEPENDS ${target_name} ${_package_deps} "${_package_inputs_file}"
COMMENT "Packaging ${target_name} -> ${_out}"
COMMAND_EXPAND_LISTS
VERBATIM
)
add_custom_target(${_package_target} ALL DEPENDS "${_out}")
if (TARGET dusklight_mods)
add_dependencies(dusklight_mods ${_package_target})
endif ()
endfunction()
# Install rules for BUNDLE mods.
# - Windows: the .dusk archives into <install>/mods (the loader extracts native libs to the
# user cache).
# - Linux: pre-extracted stage dirs into <install>/mods so native libs dlopen in place from
# read-only installs.
# - macOS: pre-extracted stage dirs into the installed app's Contents/Resources/mods, dylibs
# ad-hoc signed in place, then the whole bundle re-signed.
# - iOS/tvOS: assets into <app>/mods/<id> and the dylib into Frameworks/<id>.dylib.
# - Android: nothing here; gradle packs ${CMAKE_BINARY_DIR}/bundled_mods into APK assets.
function(install_bundled_mods)
get_property(_targets GLOBAL PROPERTY DUSK_BUNDLED_MOD_TARGETS)
if (NOT _targets OR ANDROID)
return ()
endif ()
get_property(_ids GLOBAL PROPERTY DUSK_BUNDLED_MOD_IDS)
get_property(_stages GLOBAL PROPERTY DUSK_BUNDLED_MOD_STAGES)
get_property(_lib_names GLOBAL PROPERTY DUSK_BUNDLED_MOD_LIB_NAMES)
list(LENGTH _targets _count)
math(EXPR _last "${_count} - 1")
if (APPLE)
get_target_property(_app_name dusklight OUTPUT_NAME)
if (NOT _app_name)
set(_app_name dusklight)
endif ()
set(_bundle_dir "${CMAKE_INSTALL_PREFIX}/${_app_name}.app")
if (IOS OR TVOS)
foreach (_i RANGE ${_last})
list(GET _targets ${_i} _target)
list(GET _ids ${_i} _id)
list(GET _stages ${_i} _stage)
list(GET _lib_names ${_i} _lib_name)
install(DIRECTORY "${_stage}/" DESTINATION "${_bundle_dir}/mods/${_id}"
PATTERN "${_lib_name}" EXCLUDE)
install(PROGRAMS "$<TARGET_FILE:${_target}>"
DESTINATION "${_bundle_dir}/Frameworks" RENAME "${_id}.dylib")
endforeach ()
else ()
foreach (_i RANGE ${_last})
list(GET _ids ${_i} _id)
list(GET _stages ${_i} _stage)
list(GET _lib_names ${_i} _lib_name)
install(DIRECTORY "${_stage}/" DESTINATION "${_bundle_dir}/Contents/Resources/mods/${_id}")
install(CODE "execute_process(COMMAND /usr/bin/codesign --force --sign - \"${_bundle_dir}/Contents/Resources/mods/${_id}/${_lib_name}\" COMMAND_ERROR_IS_FATAL ANY)")
endforeach ()
install(CODE "execute_process(COMMAND /usr/bin/codesign --force --sign - --entitlements \"${DUSK_ENTITLEMENTS}\" \"${_bundle_dir}\" COMMAND_ERROR_IS_FATAL ANY)")
endif ()
return ()
endif ()
if (DUSK_PACKAGE_INSTALL)
set(_mods_dest "${CMAKE_INSTALL_DATAROOTDIR}/dusklight/mods")
else ()
set(_mods_dest "${CMAKE_INSTALL_PREFIX}/mods")
endif ()
if (WIN32)
foreach (_target IN LISTS _targets)
install(FILES "${CMAKE_BINARY_DIR}/bundled_mods/${_target}.dusk" DESTINATION "${_mods_dest}")
endforeach ()
else ()
foreach (_i RANGE ${_last})
list(GET _ids ${_i} _id)
list(GET _stages ${_i} _stage)
install(DIRECTORY "${_stage}/" DESTINATION "${_mods_dest}/${_id}")
endforeach ()
endif ()
endfunction()
+13
View File
@@ -0,0 +1,13 @@
# 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}")
+60
View File
@@ -0,0 +1,60 @@
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}")
file(READ "${SOURCE_DIR}/src/funchook_unix.c" _unix_content)
# macOS rejects the POSIX mprotect RWX/RW transition for executable image pages on arm64.
# Use Mach VM_PROT_COPY for the short patch window, then restore RX permissions.
if (NOT _unix_content MATCHES "VM_PROT_READ \\| VM_PROT_WRITE \\| VM_PROT_COPY")
string(REPLACE
[=[ rv = mprotect(mstate->addr, mstate->size, prot);]=]
[=[#ifdef __APPLE__
kern_return_t kr = vm_protect(mach_task_self(), (vm_address_t)mstate->addr,
(vm_size_t)mstate->size, FALSE,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
if (kr == KERN_SUCCESS) {
funchook_log(funchook, " unprotect memory %p (size=%"PRIuPTR", prot=read,write,copy) <- %p (size=%"PRIuPTR")\n",
mstate->addr, mstate->size, start, len);
return 0;
}
funchook_set_error_message(funchook, "Failed to unprotect memory %p (size=%"PRIuPTR", prot=read,write,copy) <- %p (size=%"PRIuPTR", error=%s)",
mstate->addr, mstate->size, start, len,
mach_error_string(kr));
return FUNCHOOK_ERROR_MEMORY_FUNCTION;
#endif
rv = mprotect(mstate->addr, mstate->size, prot);]=]
_unix_content "${_unix_content}")
string(REPLACE
[=[ char errbuf[128];
int rv = mprotect(mstate->addr, mstate->size, PROT_READ | PROT_EXEC);]=]
[=[ char errbuf[128];
#ifdef __APPLE__
kern_return_t kr = vm_protect(mach_task_self(), (vm_address_t)mstate->addr,
(vm_size_t)mstate->size, FALSE,
VM_PROT_READ | VM_PROT_EXECUTE);
if (kr == KERN_SUCCESS) {
funchook_log(funchook, " protect memory %p (size=%"PRIuPTR", prot=read,exec)\n",
mstate->addr, mstate->size);
return 0;
}
funchook_set_error_message(funchook, "Failed to protect memory %p (size=%"PRIuPTR", prot=read,exec, error=%s)",
mstate->addr, mstate->size,
mach_error_string(kr));
return FUNCHOOK_ERROR_MEMORY_FUNCTION;
#endif
int rv = mprotect(mstate->addr, mstate->size, PROT_READ | PROT_EXEC);]=]
_unix_content "${_unix_content}")
endif ()
file(WRITE "${SOURCE_DIR}/src/funchook_unix.c" "${_unix_content}")
+125
View File
@@ -0,0 +1,125 @@
include_guard(GLOBAL)
get_filename_component(_SYMBOL_MANIFEST_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
set(_SYMGEN_VERSION "1.1.0")
set(_SYMGEN_RELEASE_BASE_URL "https://github.com/encounter/symgen/releases/download/v${_SYMGEN_VERSION}")
set(SYMGEN_PATH "" CACHE FILEPATH "Path to a symgen executable; empty downloads the pinned release")
mark_as_advanced(SYMGEN_PATH)
function(symgen_host_asset out_name)
string(TOLOWER "${CMAKE_HOST_SYSTEM_PROCESSOR}" _host_processor)
set(_asset "")
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
if (_host_processor MATCHES "^(arm64|aarch64)$")
set(_asset "symgen-macos-arm64")
elseif (_host_processor MATCHES "^(x86_64|amd64)$")
set(_asset "symgen-macos-x86_64")
endif ()
elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (_host_processor MATCHES "^(aarch64|arm64)$")
set(_asset "symgen-linux-aarch64")
elseif (_host_processor MATCHES "^(x86_64|amd64)$")
set(_asset "symgen-linux-x86_64")
elseif (_host_processor MATCHES "^(i[3-6]86|x86)$")
set(_asset "symgen-linux-i686")
endif ()
elseif (CMAKE_HOST_WIN32)
if (_host_processor MATCHES "^(arm64|aarch64)$")
set(_asset "symgen-windows-arm64.exe")
elseif (_host_processor MATCHES "^(x86_64|amd64)$")
set(_asset "symgen-windows-x86_64.exe")
elseif (_host_processor MATCHES "^(i[3-6]86|x86)$")
set(_asset "symgen-windows-x86.exe")
endif ()
endif ()
set(${out_name} "${_asset}" PARENT_SCOPE)
endfunction()
function(ensure_symgen required)
if (TARGET symgen)
return()
endif ()
if (SYMGEN_PATH)
get_filename_component(_symgen "${SYMGEN_PATH}" ABSOLUTE)
if (NOT EXISTS "${_symgen}")
if (required)
message(FATAL_ERROR "symgen: SYMGEN_PATH does not exist: ${_symgen}")
endif ()
message(STATUS "symgen: SYMGEN_PATH does not exist, symbol manifest generation "
"skipped (by-name hook resolution will be unavailable)")
return()
endif ()
else ()
symgen_host_asset(_asset)
if (_asset STREQUAL "")
if (required)
message(FATAL_ERROR "symgen: no prebuilt binary for host "
"${CMAKE_HOST_SYSTEM_NAME}/${CMAKE_HOST_SYSTEM_PROCESSOR} "
"(configure with -DDUSK_ENABLE_CODE_MODS=OFF)")
endif ()
message(STATUS "symgen: no prebuilt binary for host "
"${CMAKE_HOST_SYSTEM_NAME}/${CMAKE_HOST_SYSTEM_PROCESSOR}; "
"symbol manifest generation skipped (by-name hook resolution will be unavailable)")
return()
endif ()
set(_symgen_dir "${CMAKE_BINARY_DIR}/_deps/symgen")
set(_symgen "${_symgen_dir}/${_asset}")
set(_url "${_SYMGEN_RELEASE_BASE_URL}/${_asset}")
message(STATUS "dusklight: Fetching symgen ${_SYMGEN_VERSION} (${_asset})")
file(MAKE_DIRECTORY "${_symgen_dir}")
file(DOWNLOAD "${_url}" "${_symgen}"
TLS_VERIFY ON
STATUS _download_status
SHOW_PROGRESS)
list(GET _download_status 0 _download_code)
if (NOT _download_code EQUAL 0)
list(GET _download_status 1 _download_message)
file(REMOVE "${_symgen}")
if (required)
message(FATAL_ERROR "symgen: failed to download ${_url}: ${_download_message}")
endif ()
message(STATUS "symgen: failed to download ${_url}: ${_download_message}; "
"symbol manifest generation skipped (by-name hook resolution will be unavailable)")
return()
endif ()
if (NOT CMAKE_HOST_WIN32)
file(CHMOD "${_symgen}" PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
endif ()
endif ()
add_custom_target(symgen DEPENDS "${_symgen}")
set(SYMGEN_EXE "${_symgen}" CACHE INTERNAL "symgen executable" FORCE)
endfunction()
function(setup_symbol_manifest target)
ensure_symgen(TRUE)
if (NOT TARGET symgen)
return()
endif ()
add_dependencies(${target} symgen)
if (WIN32)
set(_input --pdb "$<TARGET_PDB_FILE:${target}>")
set(_out "$<TARGET_FILE_DIR:${target}>/dusklight.symdb")
else ()
set(_input --binary "$<TARGET_FILE:${target}>")
if (APPLE)
set(_out "$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Resources/dusklight.symdb")
else ()
set(_out "$<TARGET_FILE_DIR:${target}>/dusklight.symdb")
endif ()
endif ()
add_custom_command(TARGET ${target} POST_BUILD
COMMAND "${SYMGEN_EXE}" manifest ${_input} --out "${_out}"
COMMENT "Generating symbol manifest"
VERBATIM)
endfunction()
+79
View File
@@ -0,0 +1,79 @@
include_guard(GLOBAL)
get_filename_component(_DUSK_WINDOWS_EXPORTS_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
# Windows mod linking: generate the curated export surface for the game executable and the
# import library mods link against. symgen scans the built objects, filters by source, and
# writes a .def used by the main link and import library generation.
function(setup_windows_exports target)
if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(WARNING "dusklight: Windows code-mod exports are x64-only for now; skipping")
return()
endif ()
include("${_DUSK_WINDOWS_EXPORTS_CMAKE_DIR}/SymbolManifest.cmake")
ensure_symgen(TRUE)
set(_symgen "${SYMGEN_EXE}")
add_dependencies(${target} symgen)
set(_config_subdir "")
if (CMAKE_CONFIGURATION_TYPES)
set(_config_subdir "$<CONFIG>/")
endif ()
set(_rsp_lines "$<TARGET_OBJECTS:${target}>")
foreach (_lib IN LISTS JSYSTEM_LIBRARIES)
list(APPEND _rsp_lines "$<TARGET_FILE:${_lib}>")
endforeach ()
list(JOIN _rsp_lines "\n" _rsp_content)
set(_rsp "${CMAKE_BINARY_DIR}/${_config_subdir}dusklight_exports_input.rsp")
file(GENERATE OUTPUT "${_rsp}" CONTENT "${_rsp_content}")
set(_sdk_args)
foreach (_lib aurora_card aurora_core aurora_dvd aurora_gd aurora_gx aurora_mtx
aurora_os aurora_pad aurora_si aurora_vi)
if (TARGET ${_lib})
list(APPEND _sdk_args --sdk-lib "$<TARGET_FILE:${_lib}>")
endif ()
endforeach ()
# Generate curated exports list from the main binary
set(_def "${CMAKE_BINARY_DIR}/${_config_subdir}dusklight_exports.def")
add_custom_command(TARGET ${target} PRE_LINK
# TODO: src/dusk/ is NOT excluded: inline code in game headers
# currently call into it (e.g. dusk::frame_interp::lookup_replacement).
COMMAND "${_symgen}" def
--rsp "${_rsp}"
--out "${_def}"
--exclude cmake_pch
--exclude miniz
--exclude asan_options
--max-exports 58000
${_sdk_args}
COMMENT "Generating dusklight exports"
VERBATIM)
target_link_options(${target} PRIVATE "/DEF:${_def}")
# Generate import library for mods to link against.
set(_implib "${CMAKE_BINARY_DIR}/${_config_subdir}dusklight_imports.lib")
get_filename_component(_compiler_dir "${CMAKE_CXX_COMPILER}" DIRECTORY)
find_program(DUSK_LLVM_DLLTOOL llvm-dlltool HINTS "${_compiler_dir}")
if (DUSK_LLVM_DLLTOOL)
set(_implib_cmd "${DUSK_LLVM_DLLTOOL}" -d "${_def}" -D dusklight.exe -m i386:x86-64
-l "${_implib}")
else ()
set(_implib_cmd "${CMAKE_AR}" /nologo "/def:${_def}" /machine:x64 /name:dusklight.exe
"/out:${_implib}")
endif ()
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${_implib_cmd}
BYPRODUCTS "${_implib}"
COMMENT "Generating dusklight import library"
VERBATIM)
set(DUSK_GAME_IMPLIB "${_implib}" CACHE INTERNAL "Import library for Windows mod linking")
set(DUSK_GAME_DEF "${_def}" CACHE INTERNAL "Curated export .def for the game executable")
# Ship the import library as sdk/dusklight.lib in the install tree: mods may use it to
# compile against Dusklight without having to build the whole game. (See DUSK_GAME_IMPLIB)
install(FILES "${_implib}" DESTINATION sdk RENAME dusklight.lib)
endfunction()
+716
View File
@@ -0,0 +1,716 @@
# Dusklight Mod API
Mods are `.dusk` bundles: zip archives that can contain code (in the form of native libraries), resources, DVD overlay
files, and texture replacements. Mods may be enabled, disabled and reloaded at runtime.
When code mods are loaded, they get dynamically linked by the operating system to the running game process. The mod
exports lifecycle functions that Dusklight calls into (`mod_initialize`, `mod_update`, `mod_shutdown`), and the mod
communicates with the host via **services**: plain C APIs, individually versioned. Dusklight exports several built-in
services, and mods may export services of their own, permitting framework mods and cross-mod integration.
Beyond services, mods have full access to the original game's code: include game headers, call directly into any public
function, read and write data fields, and hook the vast majority of game functions.
## Table of Contents
1. [Getting Started](#getting-started)
2. [mod.json](#modjson)
3. [Anatomy of a Code Mod](#anatomy-of-a-code-mod)
4. [Services](#services)
5. [Built-in Services](#built-in-services)
6. [Hooking Game Functions](#hooking-game-functions)
7. [Asset Overlays](#asset-overlays)
8. [Runtime Lifecycle](#runtime-lifecycle)
9. [Error Handling](#error-handling)
10. [Advanced: Exporting Services](#advanced-exporting-services)
---
## Getting Started
Fork the [mod template](../tools/mod_template/), a self-contained CMake project that uses the Dusklight mod SDK.
```
my_mod/
├── CMakeLists.txt
├── mod.json
├── src/mod.cpp
├── res/ (optional bundled resources)
├── overlay/ (optional game file overrides)
└── textures/ (optional texture replacements)
```
**CMakeLists.txt:**
```cmake
cmake_minimum_required(VERSION 3.25)
project(my_mod CXX)
set(DUSKLIGHT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dusklight" CACHE PATH "Path to dusklight source root")
add_subdirectory("${DUSKLIGHT_DIR}/sdk" dusklight-sdk EXCLUDE_FROM_ALL)
add_mod(my_mod
SOURCES src/mod.cpp
MOD_JSON mod.json
RES_DIR res # optional
OVERLAY_DIR overlay # optional
TEXTURES_DIR textures # optional
)
```
Building produces `my_mod.dusk` in `build/<preset>/mods/` (configurable via the `DUSK_MODS_OUTPUT_DIR` cache variable).
Dusklight searches a `mods/` directory next to the app in addition to the user directory, so a dev build launched from
`build/<preset>/` picks these up automatically: rebuild, relaunch (or click Reload), done.
For a regular game install, copy the `.dusk` into the user mods folder:
- Windows: `%APPDATA%\TwilitRealm\Dusklight\mods`
- Linux: `~/.local/share/TwilitRealm/Dusklight/mods`
- macOS: `~/Library/Application Support/TwilitRealm/Dusklight/mods`
Passing `--mods <dir>` on the command line replaces the user directory with one of your choosing.
---
## mod.json
```json
{
"id": "com.example.my_mod",
"name": "My Mod",
"version": "1.0.0",
"author": "Your Name",
"description": "A short description shown in the mod manager.",
"icon": "res/my_icon.png",
"banner": "res/my_banner.png"
}
```
`id` is required: a unique, stable identifier (reverse-DNS style; periods, underscores, and alphanumerics). Everything
else is optional but recommended.
`icon` and `banner` are bundle-relative paths to PNG images for the in-game mod manager: the square icon (e.g.
512x512), the banner (~3.5:1). If omitted, `res/icon.png` and `res/banner.png` are used automatically when present.
---
## Anatomy of a Code Mod
```cpp
#include "mods/service.hpp"
#include "mods/svc/log.h"
DEFINE_MOD(); // once, in exactly one translation unit
IMPORT_SERVICE(LogService, svc_log); // resolved by the loader before mod_initialize
extern "C" {
MOD_EXPORT ModResult mod_initialize(ModError* error) {
svc_log->info(mod_ctx, "hello from my_mod");
return MOD_OK;
}
MOD_EXPORT ModResult mod_update(ModError* error) { // called every frame
return MOD_OK;
}
MOD_EXPORT ModResult mod_shutdown(ModError* error) {
return MOD_OK;
}
}
```
All three lifecycle exports are required. `mod_ctx` is your mod's identity token, set by the loader before
`mod_initialize` runs. Pass it as the first argument to every service call.
---
## Services
A service is a struct of C function pointers with a version header. You declare what you use at file scope, and the
loader resolves it before your mod initializes:
```cpp
IMPORT_SERVICE(LogService, svc_log); // required, any minor version
IMPORT_SERVICE_VERSION(LogService, svc_log, 2); // required, minor version >= 2
IMPORT_OPTIONAL_SERVICE(SomeService, svc_maybe); // may be null
```
Each service is individually versioned, and there may be multiple major versions of a service provided at once,
allowing backwards compatibility with older mods while still changing services fundamentally if necessary. A **major**
bump is a breaking change, treated as a different service entirely. For **additive** changes, a service appends new
functions to the end of the struct without breaking existing callers and simply bumps the minor version. Mods that
want the newer functions may use `IMPORT_SERVICE_VERSION` to require that minor at **load time**, or `SERVICE_HAS` to
check at **runtime** whether a specific function is available.
The contract (see `include/mods/api.h` for the full version):
- **A required import is guaranteed valid.** If the service is missing or too old, the mod fails to load with a clear
error. No need to null check at call sites.
- **Anything at or below the minor version you imported can be called unconditionally.**
- Optional imports may be null; check once in `mod_initialize`.
- Fields newer than your imported minor must be gated behind `SERVICE_HAS(service, ServiceType, field)` plus a null
check.
---
## Built-in Services
### LogService (`mods/svc/log.h`)
```cpp
IMPORT_SERVICE(LogService, svc_log);
svc_log->info(mod_ctx, "spawned the thing");
svc_log->warn(mod_ctx, "that looks wrong");
svc_log->error(mod_ctx, "very bad");
svc_log->write(mod_ctx, LOG_LEVEL_DEBUG, "verbose details");
```
Messages appear in the console prefixed with your mod ID. Messages are plain UTF-8 strings and are copied before the
call returns; use `snprintf` or `fmt::format` for formatting.
### ResourceService (`mods/svc/resource.h`)
Loads files from the `res/` tree of your `.dusk` archive. Paths are relative to `res/` (pass `"config.txt"`, not
`"res/config.txt"`); absolute paths and `..` are rejected.
```cpp
IMPORT_SERVICE(ResourceService, svc_resource);
ResourceBuffer buf = RESOURCE_BUFFER_INIT;
if (svc_resource->load(mod_ctx, "config.txt", &buf) == MOD_OK) {
// buf.data / buf.size
svc_resource->free(mod_ctx, &buf);
}
```
Missing files return `MOD_UNAVAILABLE`. Always `free` what you `load`. Note that the bundle is read-only; for writable
storage, use the directory from `svc_host->mod_dir(mod_ctx)`.
### HostService (`mods/svc/host.h`)
Mod metadata and runtime interaction with the loader:
```cpp
IMPORT_SERVICE(HostService, svc_host);
const char* id = svc_host->mod_id(mod_ctx);
const char* dir = svc_host->mod_dir(mod_ctx); // writable per-mod directory
svc_host->fail(mod_ctx, MOD_ERROR, "something unrecoverable happened"); // disables the mod
```
`get_service`/`publish_service` provide dynamic service lookup; see [Advanced](#advanced-exporting-services).
**Lifecycle watches.** If your mod provides a service that hands out per-caller state (registrations, callbacks,
handles), watch other mods' lifecycle and drop what you hold for a mod when it detaches.
```cpp
IMPORT_SERVICE_VERSION(HostService, svc_host, 1);
void on_mod_lifecycle(ModContext* ctx, ModContext* subject, const char* subject_id,
ModLifecycleEvent event, void* user_data) {
if (event == MOD_LIFECYCLE_DETACHED) {
drop_state_for(subject); // same ModContext* the subject passed into your service
}
}
uint64_t watch = 0;
svc_host->watch_mod_lifecycle(mod_ctx, on_mod_lifecycle, nullptr, &watch);
```
`MOD_LIFECYCLE_DETACHED` fires on the game thread at a lifecycle safe point, after the subject's `mod_shutdown` ran and
every service dropped its state. For your own mod's teardown, use `mod_shutdown` instead.
### HookService (`mods/svc/hook.h`)
Installs hooks on game functions and resolves symbols by name. You'll rarely call it directly; use the typed helpers in
`mods/hook.hpp` described in [Hooking Game Functions](#hooking-game-functions).
### OverlayService (`mods/svc/overlay.h`)
Registers DVD file overlays at runtime: the dynamic counterpart to the static `overlay/` directory (see
[Asset Overlays](#asset-overlays)). Overlay a disc path with a file from your bundle, or with a caller-owned buffer
(copied on registration):
```cpp
IMPORT_SERVICE(OverlayService, svc_overlay);
OverlayHandle handle = 0;
svc_overlay->add_file(mod_ctx, "/res/Msgus.arc", "res/replacement.arc", &handle);
svc_overlay->add_buffer(mod_ctx, "/generated.txt", data, size, nullptr);
svc_overlay->remove(mod_ctx, handle);
```
`disc_path` must be absolute (leading `/`) and is matched against the disc case-insensitively. Paths that don't exist
on the disc are added as new files. Changes are applied at the next frame boundary, and data the game already read
stays in memory until the file is re-read: sometimes a scene reload, and in the worst case, a full restart.
See [Asset Overlays](#asset-overlays) for priority and conflict handling.
### TextureService (`mods/svc/texture.h`)
Registers texture replacements at runtime: the dynamic counterpart to the static `textures/` directory (see
[Asset Overlays](#asset-overlays)). Two forms: raw texel data with an explicit key, or an encoded `.dds`/`.png` from
your bundle whose filename encodes the key:
```cpp
IMPORT_SERVICE(TextureService, svc_texture);
// Encoded file; filename follows the replacement naming convention.
TextureReplacementHandle handle = 0;
svc_texture->register_file(mod_ctx, "res/tex1_32x32_$_6.png", &handle);
// Raw data: match by texel-data pointer or by content hash (TEXTURE_KEY_SOURCE).
TextureKey key = TEXTURE_KEY_INIT;
key.kind = TEXTURE_KEY_POINTER;
key.pointer = someTexObj.data;
TextureData data = TEXTURE_DATA_INIT;
data.data = pixels; data.size = pixelsSize;
data.width = 32; data.height = 32; data.gx_format = GX_TF_RGBA8_PC;
svc_texture->register_data(mod_ctx, &key, &data, nullptr);
svc_texture->unregister(mod_ctx, handle);
```
Filenames use the same Dolphin-style convention as the user's `texture_replacements` directory:
`tex1_{w}x{h}_{texhash}[_{tluthash}]_{fmt}.dds|.png`, where hashes may be `$` (wildcard). `_mipN` sidecar files next to
a registered file are picked up automatically. Files are decoded lazily on first use by the renderer; raw data is copied
at registration. Registrations follow your mod's lifecycle.
See [Asset Overlays](#asset-overlays) for priority and conflict handling.
### ConfigService (`mods/svc/config.h`)
Persistent, mod-scoped configuration variables. Each var is stored in the user's `config.json` under
`mod.<escaped mod id>.<name>` (escaping: `.``_`, `_``__`, so `com.example.my_mod` becomes `com_example_my__mod`),
next to the host's own settings:
```cpp
IMPORT_SERVICE(ConfigService, svc_config);
ConfigVarDesc desc = CONFIG_VAR_DESC_INIT;
desc.name = "speedMultiplier"; // 1-64 chars from [A-Za-z0-9_-]; "enabled" is reserved
desc.type = CONFIG_VAR_FLOAT;
desc.default_float = 1.0;
ConfigVarHandle var = 0;
svc_config->register_var(mod_ctx, &desc, &var);
double speed = 1.0;
svc_config->get_float(mod_ctx, var, &speed);
svc_config->set_float(mod_ctx, var, 2.0);
// Optional: get notified when the value changes.
void on_speed_changed(ModContext* ctx, ConfigVarHandle var, const ConfigVarValue* value,
const ConfigVarValue* previous, void* user_data) {
/* value->float_value is the new value, previous->float_value the old one */
}
svc_config->subscribe(mod_ctx, var, on_speed_changed, nullptr, nullptr);
```
Types: `CONFIG_VAR_BOOL` (`bool`), `CONFIG_VAR_INT` (`int64_t`), `CONFIG_VAR_FLOAT` (`double`), `CONFIG_VAR_STRING`
(UTF-8; `get_string` copies into a caller buffer, pass a `NULL` buffer with size 0 to query the length). Accessors are
typed and must match the registration.
Change callbacks fire on the game thread whenever the value changes at runtime (your own `set_*` calls included).
Writes that store the same value are silent. Values applied from `config.json` or `--cvar` at registration do
**not** fire callbacks; read the value after `register_var` for the starting state.
### UiService (`mods/svc/ui.h`)
Integrate seamlessly with Dusklight's UI system: add controls and buttons to your mod's detail pane in the Mods window,
create custom windows and modal dialogs, apply custom RCSS stylesheets (anywhere!), and add menu bar tabs.
**Mod panel:** Registers or replaces the panel rendered in your mod's detail pane; `build` runs every time the detail
content is rebuilt, and `update` runs every frame while that mod is selected. While your mod is selected, the detail
pane carries your mod's id as a `mod-id` attribute (like custom window roots), so scoped RCSS can target it (e.g.
`[mod-id="com.example.mod"]`).
```cpp
IMPORT_SERVICE(UiService, svc_ui);
UiElementHandle statusText = 0;
ModResult build(ModContext*, UiElementHandle panel, void*, ModError*) {
svc_ui->pane_add_section(mod_ctx, panel, "Status");
svc_ui->pane_add_text(mod_ctx, panel, "starting...", &statusText);
svc_ui->pane_add_progress(mod_ctx, panel, 0.5f, nullptr);
return MOD_OK;
}
ModResult update(ModContext*, void*, ModError*) {
svc_ui->elem_set_text(mod_ctx, statusText, "running");
return MOD_OK;
}
UiModsPanelDesc panel = UI_MODS_PANEL_DESC_INIT;
panel.build = build;
panel.update = update;
svc_ui->register_mods_panel(mod_ctx, &panel);
```
Element setters must match the element kind: `elem_set_text`/`elem_set_rml` on text rows, and `elem_set_progress` on
progress bars. `elem_set_class` sets or clears an RCSS class on any element handle, for styling via scoped or
per-window RCSS. A non-`MOD_OK` result from `build`/`update` fails your mod, as do exceptions thrown from any UI
callback.
**Controls:** `pane_add_control` adds an input row described by a `UiControlDesc`: `UI_CONTROL_BUTTON`,
`UI_CONTROL_TOGGLE`, `UI_CONTROL_NUMBER`, `UI_CONTROL_STRING`, or `UI_CONTROL_SELECT`. Values bind with callbacks or
directly to a config var.
```cpp
UiControlDesc control = UI_CONTROL_DESC_INIT;
control.kind = UI_CONTROL_TOGGLE;
control.label = "Enable rainbows";
control.help_rml = "Shown in the help pane while focused.";
control.binding = UI_BINDING_CONFIG_VAR;
control.config_var = myBoolVar; // from svc_config->register_var
svc_ui->pane_add_control(mod_ctx, leftPane, &control, nullptr);
```
`UI_BINDING_CONFIG_VAR` wires persistence, change notifications, and the modified indicator automatically. The var
type must match the control: `TOGGLE` = bool, `NUMBER` and `SELECT` = int, `STRING` = string. Float vars are not
bindable; use callbacks and convert. `help_rml` and `SELECT` option lists render in a help pane, so `SELECT` controls
are only available inside window tabs.
**Windows:** `window_push` pushes a tabbed two-pane window onto the document stack and shows it. Each tab's `build`
receives the window handle plus fresh left and right pane handles on every activation. The optional per-tab `update`
runs each frame while that tab is active. `on_closed` fires when the window is destroyed. `desc.rcss` optionally styles
that window's document only; custom windows carry the owning mod's id as a `mod-id` attribute on the window root, so
scoped RCSS can target your specific mod's windows (e.g. `window[mod-id="com.example.mod"]`).
```cpp
UiTabDesc tabs[1] = {UI_TAB_DESC_INIT};
tabs[0].title = "Options";
tabs[0].build = build_options_tab;
UiWindowDesc desc = UI_WINDOW_DESC_INIT;
desc.tabs = tabs;
desc.tab_count = 1;
desc.on_closed = options_window_closed;
UiWindowHandle window = 0;
svc_ui->window_push(mod_ctx, &desc, &window);
```
**Dialogs:** `dialog_push` shows a modal dialog. `variant` picks the style, `icon` optionally overrides the variant's
default icon, and actions become buttons. After an action's `on_pressed` returns, the dialog closes unless the action
sets `keep_open`. A `keep_open` action can close it later (or immediately) with `dialog_close`. Cancel fires
`on_dismiss` if present and always closes. `dialog_set_body`, `dialog_set_icon`, and `dialog_add_action` mutate a live
dialog.
**Menu bar tabs:** `register_menu_tab` adds a tab to the in-game menu bar. `on_selected` fires when the user activates
the tab: typically you'd push a window from it. The tab is removed by `unregister_menu_tab`, or automatically when the
mod is disabled.
**Custom styles:** `register_styles(scope, rcss, &handle)` applies an RCSS stylesheet to every document of a scope:
existing documents restyle immediately, and future ones pick it up when created. `register_styles_file(scope, path,
&handle)` reads the sheet from your bundle's `res/` directory. Scopes are `UI_SCOPE_PRELAUNCH`, `UI_SCOPE_WINDOW`,
`UI_SCOPE_MENU_BAR`, `UI_SCOPE_OVERLAY`, `UI_SCOPE_TOUCH_CONTROLS`, and `UI_SCOPE_GRAPHICS_TUNER`. Sheets apply after
host styles and may override them. Scope selectors tightly (use `[mod-id="..."]`!), especially for `UI_SCOPE_WINDOW`,
unless changing host UI is intentional.
---
## Hooking Game Functions
Mods may hook the vast majority of game functions, including file-local static, private and virtual functions.
`mods/hook.hpp` provides typed helpers over the hook service:
```cpp
#include "mods/hook.hpp"
#include "mods/svc/hook.h"
IMPORT_SERVICE(HookService, svc_hook);
```
### Pre-hooks
Run before the original. Return `HOOK_SKIP_ORIGINAL` to cancel it (post-hooks still run).
```cpp
HookAction on_pos_move_pre(ModContext*, void* args, void* retval, void* userdata) {
daAlink_c* link = dusk::mods::arg<daAlink_c*>(args, 0); // arg 0 is `this`
if (link->shape_angle.y > 10000) {
return HOOK_SKIP_ORIGINAL;
}
return HOOK_CONTINUE;
}
dusk::mods::hook_add_pre<&daAlink_c::posMove>(svc_hook, on_pos_move_pre);
```
### Post-hooks
Run after the original (or after a replace-hook, or after a cancelled original). `retval` points to the return value,
if any.
```cpp
void on_pos_move_post(ModContext*, void* args, void* retval, void* userdata) { ... }
dusk::mods::hook_add_post<&daAlink_c::posMove>(svc_hook, on_pos_move_post);
```
### Replace-hooks
Substitute the original entirely. Call through to it via `Hook<...>::g_orig` if needed:
```cpp
using ExecuteEntry = dusk::mods::Hook<&daAlink_c::execute>;
void on_execute_replace(ModContext*, void* args, void* retval, void*) {
int result = ExecuteEntry::g_orig(dusk::mods::arg<daAlink_c*>(args, 0));
if (retval != nullptr) {
*static_cast<int*>(retval) = result;
}
}
dusk::mods::hook_replace<&daAlink_c::execute>(svc_hook, on_execute_replace);
```
By default a second replace-hook on the same function is a conflict; `HookOptions` (`replace_policy`, `priority`,
`userdata`) controls this and callback ordering. Multiple mods can attach pre/post hooks to the same function
independently.
### Hooking by name
Functions you can't name in C++ (file-local statics, private class members, anything not in a header) can be hooked by
symbol name instead. You must supply the signature along with the name.
```cpp
// static callback used by Link's hookshot collider in d_a_alink_hook.inc
using HookshotHit = dusk::mods::NamedHook<
"daAlink_hookshotAtHitCallBack",
void(fopAc_ac_c*, dCcD_GObjInf*, fopAc_ac_c*, dCcD_GObjInf*)>;
dusk::mods::hook_add_pre<HookshotHit>(svc_hook, on_hookshot_hit_pre);
...
HookshotHit::g_orig(link, atObjInf, target, tgObjInf); // call through to the original
```
Class member functions must include `Class*` as the first argument.
The install fails with the resolve error when the name is missing (`MOD_UNAVAILABLE`), ambiguous (`MOD_CONFLICT`),
or the manifest is absent (`MOD_UNSUPPORTED`). Unlike `Hook<&Class::method>`, the signature is **not**
compiler-checked: a mismatched signature will corrupt the call.
### Reading and writing arguments
`args` is an array of pointers to the arguments. For member functions, index 0 is `this`; parameters follow in
declaration order.
```cpp
T value = dusk::mods::arg<T>(args, n); // copy
T& ref = dusk::mods::arg_ref<T>(args, n); // read/write reference
```
```cpp
// fpc_ProcID fopAcM_createItem(..., int itemNo, ...): turn heart drops into green rupees
HookAction on_create_item_pre(ModContext*, void* args, void*, void*) {
int& itemNo = dusk::mods::arg_ref<int>(args, 1);
if (itemNo == dItemNo_HEART_e) {
itemNo = dItemNo_GREEN_RUPEE_e;
}
return HOOK_CONTINUE;
}
dusk::mods::hook_add_pre<&fopAcM_createItem>(svc_hook, on_create_item_pre);
```
For reference parameters (e.g. `const cXyz& pos`), `arg_ref<cXyz>` yields a direct reference.
### Resolving symbols by name
`resolve` looks a symbol up in the **symbol manifest**: a name→address map generated alongside every game build and
keyed to that exact binary. It covers the whole image, including functions that aren't exported (file-local statics),
which makes them hookable:
```cpp
IMPORT_SERVICE(HookService, svc_hook);
void* addr = nullptr;
uint32_t flags = 0;
if (svc_hook->resolve(mod_ctx, "GXSetProjection", &addr, &flags) == MOD_OK) {
// addr is the function's real address in the running game; hook or call it.
}
```
Two spellings work on every platform:
- **Display names** (`daAlink_c::posMove`, `fapGm_Before`): the qualified name with no parameter list. They carry no
signature, so overload sets (and file-local statics sharing a name) return `MOD_CONFLICT`.
- **Decorated names** (`_ZN9daAlink_c7posMoveEv` / `?posMove@daAlink_c@@...`): the platform's mangled spelling in
dlsym convention (no Mach-O leading underscore). The escape hatch for overloads.
`MOD_UNSUPPORTED` means the manifest is missing or was built for a different game binary.
### Game code ABI contract
A primary consideration when letting mods link against the game is maintaining ABI stability across Dusklight
versions. If your mod calls or hooks game code directly (anything beyond the service APIs), import `GameService`
(`mods/svc/game.h`):
```cpp
IMPORT_SERVICE(GameService, svc_game);
```
Its major version is the game code ABI epoch: it's bumped when game struct or vtable layouts change incompatibly, and
the ordinary service version check then rejects your mod with a clear error instead of letting it corrupt memory in a
version it wasn't built for. Service-only and asset-only mods should *not* import it and will continue to work across
game ABI changes.
The more you can do through services, the better: a mod that avoids touching game code directly sidesteps future ABI
breaks entirely and plays nicer with other enabled mods.
---
## Asset Overlays
Files placed under `overlay/` in the `.dusk` archive override game files at the corresponding path, equivalent to
replacing files in the .iso. This requires no code: an archive with just `mod.json` and `overlay/` is a complete mod.
Files placed under `textures/` register as texture replacements, and act just like the user's general
`texture_replacements/` directory: Dolphin-style naming, matched by texture hash
(`tex1_{w}x{h}_{texhash}[_{tluthash}]_{fmt}.dds|.png`, `$` as a hash wildcard). Subdirectories are scanned recursively;
only the filename needs to match.
Both mechanisms are tied to the mod's lifecycle: disabling the mod removes its overrides (files revert to the disc
contents on their next open; added files stop existing), and reloading serves the new bundle's content. However, game
data the engine already read stays as-is until it is loaded again, which may require a scene change or, in the worst
case, a full restart. Texture replacements usually take effect immediately.
If multiple sources replace the same file or texture, the last one wins: runtime registrations override static
`textures/` or `overlay/` files, and later-loaded mods override earlier ones. Cross-mod conflicts log warnings.
**All** mod-provided texture replacements override the user's `texture_replacements/`.
To configure overlays and texture replacements at runtime instead, see [OverlayService](#overlayservice-modssvcoverlayh)
and [TextureService](#textureservice-modssvctextureh).
---
## Runtime Lifecycle
Mods can be disabled, re-enabled, and reloaded at runtime without restarting the game (the enabled state persists as the
`mod.<escaped id>.enabled` config var). Write your mod assuming this happens:
- **Disable** calls `mod_shutdown`, removes your hooks, services, overlays, and texture replacements (both static and
runtime-registered), and unloads your library.
- **Enable** and **Reload** load a *fresh copy* of your library, imports are re-resolved, and `mod_initialize` runs
again. You never see a second `mod_initialize` on the same image, so just make `mod_shutdown` release anything the
loader doesn't manage for you (threads, files, game-side state you mutated).
- **Reload** additionally re-reads the `.dusk` from disk, picking up a rebuilt library and changed assets. This is the
fast iteration loop during development: rebuild, click Reload.
**Dependents restart too.** Disabling or reloading a mod that exports services shuts down the mods importing them
first (in reverse dependency order) and brings them back afterward. A mod whose *required* provider is disabled stays
suspended and resumes automatically when the provider returns. Mods with an *optional* import of a disabled provider
restart with that import null.
**One caution for hooks:** lifecycle changes are applied between frames, which is safe for hooks on functions
that return every frame (effectively everything you'd normally hook). Avoid hooking a function that stays on
the stack for the whole session (e.g. the outermost main loop); a mod that does cannot be safely unloaded.
---
## Error Handling
Service calls report failure through `ModResult` return values (`MOD_OK`, `MOD_UNAVAILABLE`,
`MOD_INVALID_ARGUMENT`, ...). Lifecycle exports additionally receive a `ModError*`: fill it (e.g. with
`dusk::mods::set_error(error, code, "message")`) and return the code, and the loader disables the mod and shows the
message to the user.
```cpp
MOD_EXPORT ModResult mod_initialize(ModError* error) {
if (!load_my_data()) {
return dusk::mods::set_error(error, MOD_ERROR, "failed to load data");
}
return MOD_OK;
}
```
Throwing exceptions out of lifecycle functions also disables the mod (they are caught by the loader), but prefer
explicit results.
---
## Advanced: Exporting Services
Mods may export services of their own, permitting framework mods and cross-mod integration. Define the interface in a
header both mods share:
```cpp
// my_mod_api.h
#include "mods/api.h"
#define MY_MOD_SERVICE_ID "com.example.my_mod.api"
#define MY_MOD_SERVICE_MAJOR 1u
#define MY_MOD_SERVICE_MINOR 0u
typedef struct MyModService {
ServiceHeader header;
ModResult (*do_thing)(ModContext* ctx, int value);
} MyModService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<MyModService> {
static constexpr const char* id = MY_MOD_SERVICE_ID;
static constexpr uint16_t major_version = MY_MOD_SERVICE_MAJOR;
};
#endif
```
**Provider:**
```cpp
ModResult do_thing(ModContext* ctx, int value) { ... }
constexpr MyModService g_service{
.header = SERVICE_HEADER(MyModService, MY_MOD_SERVICE_MAJOR, MY_MOD_SERVICE_MINOR),
.do_thing = do_thing,
};
EXPORT_SERVICE(g_service);
```
**Consumer:**
```cpp
IMPORT_SERVICE(MyModService, svc_my_mod);
// or IMPORT_OPTIONAL_SERVICE if the dependency is optional
svc_my_mod->do_thing(mod_ctx, 42);
```
The loader registers all exports before resolving any imports, so declaration order between mods doesn't matter. Note
that the `ctx` a provider receives identifies the *calling* mod.
### Dependencies between mods
Service imports are also dependency declarations: the loader initializes mods in dependency order, so by the time your
`mod_initialize` runs, every mod you import services from (required *or* optional) has already finished its own
`mod_initialize`. This includes deferred services: a service the provider publishes during its initialization resolves
into your import slot just like a static export.
Consequences of that contract:
- If a provider fails to load, every mod that *requires* one of its services is disabled too, with an error naming the
provider. Optional imports of a failed provider simply resolve to `NULL`.
- Mods whose **required** imports form a cycle all fail to load. If the cycle runs through an **optional** import, the
loader breaks it there: the optional import still resolves, but its provider may not be initialized yet when you run.
- `svc_host->get_service(...)` is outside this system. It sees whatever is published at call time and gives no
initialization-order guarantee, which also makes it the escape hatch for intentionally cyclic designs.
Mods shut down in reverse initialization order, so services you import remain safe to call from `mod_shutdown`.
Rules for providers:
- Service IDs are global and use reverse-DNS names (e.g. `com.mydomain.mod.service`)
- Every function pointer covered by your declared minor version must be populated.
- Within a major version, only append fields; never reorder, remove, or repurpose them. Breaking changes require a major
bump (which is, in effect, a new service).
- Only one provider per `(id, major)` pair may be registered; duplicates are load errors.
For services whose construction can't happen at static-init time, declare the export with `EXPORT_DEFERRED_SERVICE(...)`
and publish the pointer later via `svc_host->publish_service(...)`. Consumers can fetch services dynamically with
`svc_host->get_service(...)`; prefer manifest imports whenever possible, since they give the loader dependency
information and fail fast with good errors.
+1 -1
+34 -105
View File
@@ -1464,9 +1464,6 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiBloomWindow.hpp
src/dusk/imgui/ImGuiMenuTools.cpp
src/dusk/imgui/ImGuiMenuTools.hpp
src/dusk/imgui/ImGuiMenuRandomizer.cpp
src/dusk/imgui/ImGuiMenuRandomizer.hpp
src/dusk/imgui/ImGuiArchipelagoDebug.cpp
src/dusk/imgui/ImGuiActorSpawner.cpp
src/dusk/imgui/ImGuiProcessOverlay.cpp
src/dusk/imgui/ImGuiCameraOverlay.cpp
@@ -1487,8 +1484,6 @@ set(DUSK_FILES
src/dusk/ui/controls.hpp
src/dusk/ui/controller_config.cpp
src/dusk/ui/controller_config.hpp
src/dusk/ui/cosmetics.hpp
src/dusk/ui/cosmetics.cpp
src/dusk/ui/document.cpp
src/dusk/ui/document.hpp
src/dusk/ui/editor.cpp
@@ -1501,6 +1496,12 @@ set(DUSK_FILES
src/dusk/ui/input.hpp
src/dusk/ui/icon_provider.cpp
src/dusk/ui/icon_provider.hpp
src/dusk/ui/logs_window.cpp
src/dusk/ui/logs_window.hpp
src/dusk/ui/mod_texture_provider.cpp
src/dusk/ui/mod_texture_provider.hpp
src/dusk/ui/mod_window.cpp
src/dusk/ui/mod_window.hpp
src/dusk/ui/modal.cpp
src/dusk/ui/modal.hpp
src/dusk/ui/nav_types.hpp
@@ -1512,6 +1513,8 @@ set(DUSK_FILES
src/dusk/ui/pane.hpp
src/dusk/ui/menu_bar.cpp
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.hpp
src/dusk/ui/preset.cpp
@@ -1538,12 +1541,6 @@ set(DUSK_FILES
src/dusk/ui/warp.hpp
src/dusk/ui/window.cpp
src/dusk/ui/window.hpp
src/dusk/ui/rando_config.cpp
src/dusk/ui/rando_config.hpp
src/dusk/ui/rando_seed_generation.cpp
src/dusk/ui/rando_seed_generation.hpp
src/dusk/ui/archi_connect_modal.cpp
src/dusk/ui/archi_connect_modal.hpp
src/dusk/achievements.cpp
src/dusk/iso_validate.cpp
src/dusk/livesplit.cpp
@@ -1552,105 +1549,37 @@ set(DUSK_FILES
src/dusk/OSReport.cpp
src/dusk/OSThread.cpp
src/dusk/OSMutex.cpp
src/dusk/mods/log_buffer.cpp
src/dusk/mods/log_buffer.hpp
src/dusk/mods/manifest.cpp
src/dusk/mods/manifest.hpp
src/dusk/mods/loader/bundle_disk.cpp
src/dusk/mods/loader/bundle_zip.cpp
src/dusk/mods/loader/context.cpp
src/dusk/mods/loader/depgraph.cpp
src/dusk/mods/loader/depgraph.hpp
src/dusk/mods/loader/loader.cpp
src/dusk/mods/loader/loader.hpp
src/dusk/mods/loader/native_module.cpp
src/dusk/mods/loader/native_module.hpp
src/dusk/mods/svc/config.cpp
src/dusk/mods/svc/config.hpp
src/dusk/mods/svc/game.cpp
src/dusk/mods/svc/hook.cpp
src/dusk/mods/svc/host.cpp
src/dusk/mods/svc/log.cpp
src/dusk/mods/svc/overlay.cpp
src/dusk/mods/svc/resource.cpp
src/dusk/mods/svc/texture.cpp
src/dusk/mods/svc/ui.cpp
src/dusk/mods/svc/ui.hpp
src/dusk/mods/svc/registry.cpp
src/dusk/mods/svc/registry.hpp
src/dusk/discord.cpp
src/dusk/discord.hpp
src/dusk/discord_presence.cpp
src/dusk/version.cpp
src/dusk/action_bindings.cpp
src/dusk/cosmetics/color_utils.hpp
src/dusk/cosmetics/color_utils.cpp
src/dusk/cosmetics/texture_utils.hpp
src/dusk/cosmetics/texture_utils.cpp
# Randomizer files
src/dusk/randomizer/game/flags.cpp
src/dusk/randomizer/game/flags.h
src/dusk/randomizer/game/messages.cpp
src/dusk/randomizer/game/messages.hpp
src/dusk/randomizer/game/stages.cpp
src/dusk/randomizer/game/stages.h
src/dusk/randomizer/game/tools.cpp
src/dusk/randomizer/game/tools.h
src/dusk/randomizer/game/verify_item_functions.cpp
src/dusk/randomizer/game/verify_item_functions.h
src/dusk/randomizer/game/randomizer_context.cpp
src/dusk/randomizer/game/randomizer_context.hpp
src/dusk/randomizer/generator/logic/area.cpp
src/dusk/randomizer/generator/logic/area.hpp
src/dusk/randomizer/generator/logic/dungeon.cpp
src/dusk/randomizer/generator/logic/dungeon.hpp
src/dusk/randomizer/generator/logic/entrance.cpp
src/dusk/randomizer/generator/logic/entrance.hpp
src/dusk/randomizer/generator/logic/entrance_shuffle.cpp
src/dusk/randomizer/generator/logic/entrance_shuffle.hpp
src/dusk/randomizer/generator/logic/fill.cpp
src/dusk/randomizer/generator/logic/fill.hpp
src/dusk/randomizer/generator/logic/flatten/bits.cpp
src/dusk/randomizer/generator/logic/flatten/bits.hpp
src/dusk/randomizer/generator/logic/flatten/flatten.cpp
src/dusk/randomizer/generator/logic/flatten/flatten.hpp
src/dusk/randomizer/generator/logic/flatten/simplify_algebraic.cpp
src/dusk/randomizer/generator/logic/flatten/simplify_algebraic.hpp
src/dusk/randomizer/generator/logic/hints.cpp
src/dusk/randomizer/generator/logic/hints.hpp
src/dusk/randomizer/generator/logic/item.cpp
src/dusk/randomizer/generator/logic/item.hpp
src/dusk/randomizer/generator/logic/item_pool.cpp
src/dusk/randomizer/generator/logic/item_pool.hpp
src/dusk/randomizer/generator/logic/location.cpp
src/dusk/randomizer/generator/logic/location.hpp
src/dusk/randomizer/generator/logic/plandomizer.cpp
src/dusk/randomizer/generator/logic/plandomizer.hpp
src/dusk/randomizer/generator/logic/requirement.cpp
src/dusk/randomizer/generator/logic/requirement.hpp
src/dusk/randomizer/generator/logic/search.cpp
src/dusk/randomizer/generator/logic/search.hpp
src/dusk/randomizer/generator/logic/spoiler_log.cpp
src/dusk/randomizer/generator/logic/spoiler_log.hpp
src/dusk/randomizer/generator/logic/world.cpp
src/dusk/randomizer/generator/logic/world.hpp
src/dusk/randomizer/generator/randomizer.cpp
src/dusk/randomizer/generator/randomizer.hpp
src/dusk/randomizer/generator/seedgen/config.cpp
src/dusk/randomizer/generator/seedgen/config.hpp
src/dusk/randomizer/generator/seedgen/packed_bits.hpp
src/dusk/randomizer/generator/seedgen/seed.cpp
src/dusk/randomizer/generator/seedgen/seed.hpp
src/dusk/randomizer/generator/seedgen/settings.cpp
src/dusk/randomizer/generator/seedgen/settings.hpp
src/dusk/randomizer/generator/test/test.cpp
src/dusk/randomizer/generator/test/test.hpp
src/dusk/randomizer/generator/utility/base64pp.hpp
src/dusk/randomizer/generator/utility/color.cpp
src/dusk/randomizer/generator/utility/color.hpp
src/dusk/randomizer/generator/utility/crc32.hpp
src/dusk/randomizer/generator/utility/common.cpp
src/dusk/randomizer/generator/utility/common.hpp
src/dusk/randomizer/generator/utility/container.hpp
src/dusk/randomizer/generator/utility/endian.cpp
src/dusk/randomizer/generator/utility/endian.hpp
src/dusk/randomizer/generator/utility/exception.hpp
src/dusk/randomizer/generator/utility/file.cpp
src/dusk/randomizer/generator/utility/file.hpp
src/dusk/randomizer/generator/utility/general.hpp
src/dusk/randomizer/generator/utility/log.cpp
src/dusk/randomizer/generator/utility/log.hpp
src/dusk/randomizer/generator/utility/math.hpp
src/dusk/randomizer/generator/utility/path.cpp
src/dusk/randomizer/generator/utility/path.hpp
src/dusk/randomizer/generator/utility/platform.cpp
src/dusk/randomizer/generator/utility/platform.hpp
src/dusk/randomizer/generator/utility/random.cpp
src/dusk/randomizer/generator/utility/random.hpp
src/dusk/randomizer/generator/utility/string.cpp
src/dusk/randomizer/generator/utility/string.hpp
src/dusk/randomizer/generator/utility/text.cpp
src/dusk/randomizer/generator/utility/text.hpp
src/dusk/randomizer/generator/utility/thread_local.hpp
src/dusk/randomizer/generator/utility/time.cpp
src/dusk/randomizer/generator/utility/time.hpp
src/dusk/randomizer/generator/utility/yaml.hpp
# Archipelago Files
src/dusk/archipelago/archipelago_context.cpp
)
set(DUSK_HTTP_BACKEND_FILES
@@ -20,7 +20,7 @@ public:
/* 0x14 */ cM3dGAab mM3dGAab;
/* 0x30 */ cBgS_ShdwDraw_Callback mCallbackFun;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x34 */ int field_0x34;
#endif
};
+2
View File
@@ -48,6 +48,8 @@ public:
/* 0x1370 */ Z2FxLineMgr mFxLineMgr;
#if DEBUG
/* 0x13BC */ Z2DebugSys mDebugSys;
#elif PARTIAL_DEBUG
alignas(Z2DebugSys) u8 mDebugSys[sizeof(Z2DebugSys)];
#endif
}; // Size: 0x138C
-5
View File
@@ -46,11 +46,6 @@ public:
s32 getSeLoadStatus(u32 wave) { return getWaveLoadStatus(wave, 0); }
s32 getBgmLoadStatus(u32 wave) { return getWaveLoadStatus(wave, 1); }
u8 getDemoSeWaveNum() { return loadedDemoWave; }
#if TARGET_PC
u8 getLoadedSeWave_1() { return loadedSeWave_1;}
u8 getLoadedSeWave_2() { return loadedSeWave_2;}
#endif
private:
/* 0x00 */ JAISoundID BGM_ID;
+1 -1
View File
@@ -194,7 +194,7 @@ public:
JAISoundHandle* getMainBgmHandle() { return &mMainBgmHandle; }
JAISoundHandle* getSubBgmHandle() { return &mSubBgmHandle; }
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
f32 field_0x00_debug;
u8 field_0x04_debug;
#endif
+2 -2
View File
@@ -100,13 +100,13 @@ public:
bool isForceBattle() { return forceBattle_; }
JSUList<Z2CreatureEnemy>* getEnemyList() { return &field_0x0; }
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
JSUList<Z2SoundObjBase>* getAllList() { return &allList_; }
#endif
private:
/* 0x00 */ JSUList<Z2CreatureEnemy> field_0x0;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x0C */ JSUList<Z2SoundObjBase> allList_;
#endif
/* 0x0C */ Z2EnemyArea enemyArea_;
+1 -1
View File
@@ -7,7 +7,7 @@
struct Z2SoundStarter;
class Z2SoundObjBase : public Z2SoundHandles
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
, public JSULink<Z2SoundObjBase>
#endif
{
+2 -10
View File
@@ -3912,12 +3912,6 @@ public:
u16 getReadyItem() { return dComIfGp_getSelectItem(mSelectItemId); }
static u32 getOtherHeapSize() { return 0xF0A60; }
#if TARGET_PC
u16 getEventId() { return mMsgFlow.getEventId(); }
bool checkSwimming() {return checkModeFlg(MODE_SWIMMING);}
#endif
static daAlink_BckData const m_mainBckShield[20];
static daAlink_BckData const m_mainBckSword[5];
@@ -6397,8 +6391,7 @@ public:
class daAlinkHIO_huLight_c0 {
public:
static daAlinkHIO_huLight_c1 IF_NOT_DUSK(const) m;
IF_DUSK(static daAlinkHIO_huLight_c1 const original;)
static daAlinkHIO_huLight_c1 const m;
};
class daAlinkHIO_wlLight_c1 {
@@ -6472,8 +6465,7 @@ public:
class daAlinkHIO_kandelaar_c0 {
public:
static daAlinkHIO_kandelaar_c1 IF_NOT_DUSK(const) m;
IF_DUSK(static daAlinkHIO_kandelaar_c1 const original;)
static daAlinkHIO_kandelaar_c1 const m;
};
class daAlinkHIO_kandelaar_c : public daAlinkHIO_data_c {
+1 -4
View File
@@ -120,10 +120,7 @@ public:
BOOL chkAction(actionFunc action) { return action == mAction; }
static eventFunc mEvtSeqList[1];
#if TARGET_PC
// Keep track of last given bug for rando
static u8 mGivenInsectId;
#endif
private:
/* 0xB48 */ Z2Creature mSound;
/* 0xBD8 */ J3DModel* mpUmbrellaModel;
-3
View File
@@ -64,9 +64,6 @@ public:
virtual int Delete();
virtual BOOL checkSmallTbox() { return true; }
#if TARGET_PC
u8 getTboxNo() { return fopAcM_GetParamBit(this, 16, 8);}
#endif
u8 getModelType() { return fopAcM_GetParamBit(this, 8, 4); }
u8 getItemNo() { return fopAcM_GetParamBit(this, 0, 8); }
-4
View File
@@ -114,10 +114,6 @@ public:
bool checkBoomWindTgTimer() { return mBoomWindTgTimer == 0; }
#if TARGET_PC
void setRandomizerItem(bool setFoolishItemModel = false);
#endif
static procFunc mFuncPtr[];
static const dCcD_SrcCyl m_cyl_src;
static s32 m_timer_max;
-5
View File
@@ -7,9 +7,4 @@ class fopAc_ac_c;
int CheckFieldItemCreateHeap(fopAc_ac_c* actor);
int CheckItemCreateHeap(fopAc_ac_c* i_this);
#if TARGET_PC
// Used for foolish items in rando. Foolish item model id is saved to home.angle.z
#define M_ITEMNO_MODEL_ITEM_ID (IF_DUSK(randomizer_IsActive() && m_itemNo == dItemNo_Randomizer_FOOLISH_ITEM_e && home.angle.z != 0 ? home.angle.z :) m_itemNo)
#endif
#endif /* D_A_D_A_ITEMBASE_STATIC_H */
-10
View File
@@ -74,17 +74,7 @@ public:
s16 getAngleY() const { return mAngleY; }
void setAngleY(s16 angle) { mAngleY = angle;}
#if TARGET_PC
BOOL isRandomized() const;
#endif
static ResourceData const mData[23];
#if TARGET_PC
static ResourceData mRandoData[23];
#define M_SHOP_DATA (isRandomized() ? mRandoData : mData)
#else
#define M_SHOP_DATA mData
#endif
static f32 const m_cullfar_max;
enum {
+2 -2
View File
@@ -45,7 +45,7 @@ private:
class dAttParam_c : public JORReflexible {
public:
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x04 */ s8 mHIOChildNo;
#endif
@@ -66,7 +66,7 @@ public:
/* 0x35 */ u8 mAttnCursorDisappearFrames;
/* 0x38 */ f32 field_0x38;
/* 0x3C */ f32 field_0x3c;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x44 */ s32 mDebugDispPosX;
/* 0x48 */ s32 mDebugDispPosY;
#endif
+2 -2
View File
@@ -201,7 +201,7 @@ private:
/* 0x02C */ u32 m_flags;
/* 0x030 */ cXyz* pm_pos;
/* 0x034 */ cXyz* pm_old_pos;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x038 */ cXyz unk_0x38;
#endif
/* 0x038 */ cXyz* pm_speed;
@@ -229,7 +229,7 @@ private:
/* 0x0CC */ f32 field_0xcc;
/* 0x0D0 */ f32 m_wtr_chk_offset;
/* 0x0D4 */ cBgS_PolyInfo* pm_out_poly_info;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x0E4 */ cXyz unk_0xe4;
#endif
/* 0x0D8 */ f32 field_0xd8;
+1 -1
View File
@@ -79,7 +79,7 @@ public:
// /* 0x0000 */ cCcS mCCcS;
/* 0x284C */ dCcMassS_Mng mMass_Mng;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x2AD0 */ u8 m_is_mass_all_timer;
#endif
}; // Size = 0x2AC4
+2 -106
View File
@@ -1,10 +1,6 @@
#ifndef D_COM_D_COM_INF_GAME_H
#define D_COM_D_COM_INF_GAME_H
#if TARGET_PC
#include "dusk/randomizer/game/randomizer_context.hpp"
#endif
#include "d/d_attention.h"
#include "d/d_cc_s.h"
#include "d/d_event.h"
@@ -21,7 +17,7 @@
#include "m_Do/m_Do_graphic.h"
#include <cstring>
#include "tracy/Tracy.hpp"
#include "dusk/profiling.hpp"
enum dComIfG_ButtonStatus {
/* 0x00 */ BUTTON_STATUS_NONE,
@@ -470,9 +466,6 @@ public:
s8 getNextStageRoomNo() { return mNextStage.getRoomNo(); }
s8 getNextStageLayer() { return mNextStage.getLayer(); }
BOOL isEnableNextStage() { return mNextStage.isEnable(); }
#if TARGET_PC
void setEnableNextStage() { return mNextStage.setEnable(); }
#endif
void offEnableNextStage() { mNextStage.offEnable(); }
s8 getNextStageWipe() { return mNextStage.getWipe(); }
u8 getNextStageWipeSpeed() { return mNextStage.getWipeSpeed(); }
@@ -1044,7 +1037,7 @@ public:
/* 0x1DE09 */ u8 field_0x1de09;
/* 0x1DE0A */ u8 field_0x1de0a;
/* 0x1DE0B */ u8 mIsDebugMode;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x1DE0C */ OSStopwatch mStopwatch;
#endif
@@ -1128,15 +1121,6 @@ void dComIfGs_setWarpItemData(char const* stage, cXyz pos, s16 angle, s8 roomNo,
u8 param_5);
BOOL dComIfGs_isStageSwitch(int i_stageNo, int i_no);
BOOL dComIfGs_isStageTbox(int i_stageNo, int i_no);
#if TARGET_PC
void dComIfGs_onStageTbox(int i_stageNo, int i_no);
void dComIfGs_offStageTbox(int i_stageNo, int i_no);
void dComIfGs_onStageItem(int i_stageNo, int i_no);
void dComIfGs_offStageItem(int i_stageNo, int i_no);
#endif
void dComIfGs_onStageSwitch(int i_stageNo, int i_no);
void dComIfGs_offStageSwitch(int i_stageNo, int i_no);
BOOL dComIfGs_isStageSwitch(int i_stageNo, int i_no);
@@ -1284,10 +1268,6 @@ int dComIfGd_setShadow(u32 param_0, s8 param_1, J3DModel* param_2, cXyz* param_3
f32 param_5, f32 param_6, f32 param_7, cBgS_PolyInfo& param_8,
dKy_tevstr_c* param_9, s16 param_10, f32 param_11, TGXTexObj* param_12);
#if TARGET_PC
void dComIfGs_setupRandomizerSave();
#endif
inline dSv_info_c* dComIfGs_getSaveInfo() {
return &g_dComIfG_gameInfo.info;
}
@@ -1348,13 +1328,9 @@ inline u8 dComIfGs_getSelectEquipShield() {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerStatusA().getSelectEquip(COLLECT_SHIELD);
}
#if TARGET_PC
u8 dComIfGs_getCollectSmell();
#else
inline u8 dComIfGs_getCollectSmell() {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerStatusA().getSelectEquip(COLLECT_SMELL);
}
#endif
inline void dComIfGs_setCollectSmell(u8 smell) {
g_dComIfG_gameInfo.info.getPlayer().getPlayerStatusA().setSelectEquip(COLLECT_SMELL, smell);
@@ -1444,12 +1420,6 @@ inline BOOL dComIfGs_isDarkClearLV(int i_no) {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerStatusB().isDarkClearLV(i_no);
}
#if TARGET_PC
inline u8 dComIfGs_getDarkClearLV() {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerStatusB().getDarkClearLV();
}
#endif
inline void dComIfGs_onTransformLV(int i_no) {
g_dComIfG_gameInfo.info.getPlayer().getPlayerStatusB().onTransformLV(i_no);
}
@@ -1527,12 +1497,6 @@ inline void dComIfGs_onRegionBit(int i_region) {
g_dComIfG_gameInfo.info.getPlayer().getPlayerFieldLastStayInfo().onRegionBit(i_region);
}
#if TARGET_PC
inline void dComIfGs_setRegionBit(u8 i_region) {
g_dComIfG_gameInfo.info.getPlayer().getPlayerFieldLastStayInfo().setRegionBit(i_region);
}
#endif
inline void dComIfGs_setPlayerFieldLastStayInfo(const char* i_stage, cXyz& i_pos, s16 i_angle,
s8 i_point, u8 i_region) {
g_dComIfG_gameInfo.info.getPlayer().getPlayerFieldLastStayInfo().set(i_stage, i_pos, i_angle,
@@ -1684,16 +1648,6 @@ inline void dComIfGs_setPachinkoNum(u8 i_num) {
g_dComIfG_gameInfo.info.getPlayer().getItemRecord().setPachinkoNum(i_num);
}
#if TARGET_PC
inline u8 dComIfGs_getAncientDocumentNum() {
return g_dComIfG_gameInfo.info.getPlayer().getItemRecord().getAncientDocumentNum();
}
inline void dComIfGs_setAncientDocumentNum(u8 i_num) {
g_dComIfG_gameInfo.info.getPlayer().getItemRecord().setAncientDocumentNum(i_num);
}
#endif
inline u8 dComIfGs_getPachinkoMax() {
return 50;
}
@@ -1866,16 +1820,7 @@ inline u8 dComIfGs_getGetNumber(int i_no) {
inline void dComIfGs_setGetNumber(int i_no, u8 i_value) {
g_dComIfG_gameInfo.info.getPlayer().getLetterInfo().setGetNumber(i_no, i_value);
}
#if TARGET_PC
// For rando
inline void dComIfGs_setAllLetterGet() {
g_dComIfG_gameInfo.info.getPlayer().getLetterInfo().setAllLetterGet();
}
inline void dComIfGs_setAllLetterRead() {
g_dComIfG_gameInfo.info.getPlayer().getLetterInfo().setAllLetterRead();
}
#endif
inline void dComIfGs_addFishNum(u8 param_0) {
g_dComIfG_gameInfo.info.getPlayer().getFishingInfo().addFishCount(param_0);
}
@@ -1991,33 +1936,6 @@ inline u8 dComIfGs_getPalLanguage() {
return g_dComIfG_gameInfo.info.getPlayer().getConfig().getPalLanguage();
}
#if TARGET_PC
// Kinda hacky, but will do for now
inline void dComIfGs_onRegionFlag(int i_stageNo, int i_no) {
auto regionFlags = reinterpret_cast<u8*>(&g_dComIfG_gameInfo.info.getSavedata().getSave(i_stageNo).getBit());
const int offset = i_no / 8;
const int shift = i_no % 8;
regionFlags[offset] |= (0x80 >> shift);
}
inline void dComIfGs_onSaveTbox(int i_stageNo, int i_no) {
g_dComIfG_gameInfo.info.getSavedata().getSave(i_stageNo).getBit().onTbox(i_no);
}
inline void dComIfGs_offSaveTbox(int i_stageNo, int i_no) {
g_dComIfG_gameInfo.info.getSavedata().getSave(i_stageNo).getBit().offTbox(i_no);
}
inline void dComIfGs_onSaveItem(int i_no) {
g_dComIfG_gameInfo.info.getMemory().getBit().onItem(i_no);
}
inline void dComIfGs_offSaveItem(int i_no) {
g_dComIfG_gameInfo.info.getMemory().getBit().offItem(i_no);
}
#endif
inline BOOL dComIfGs_isSaveTbox(int i_stageNo, int i_no) {
return g_dComIfG_gameInfo.info.getSavedata().getSave(i_stageNo).getBit().isTbox(i_no);
}
@@ -2102,10 +2020,6 @@ inline void dComIfGs_setKeyNum(u8 i_keyNum) {
g_dComIfG_gameInfo.info.getMemory().getBit().setKeyNum(i_keyNum);
}
#if TARGET_PC
u8 dComIfGs_getKeyNum(int i_stageNo);
#endif
inline void dComIfGs_onDungeonItemMap() {
g_dComIfG_gameInfo.info.getMemory().getBit().onDungeonItemMap();
}
@@ -2482,12 +2396,6 @@ inline void dComIfGs_onItem(int i_bitNo, int i_roomNo) {
g_dComIfG_gameInfo.info.onItem(i_bitNo, i_roomNo);
}
#if TARGET_PC
inline void dComIfGs_offItem(int i_bitNo, int i_roomNo) {
g_dComIfG_gameInfo.info.offItem(i_bitNo, i_roomNo);
}
#endif
inline bool dComIfGs_isItem(int i_bitNo, int i_roomNo) {
return g_dComIfG_gameInfo.info.isItem(i_bitNo, i_roomNo);
}
@@ -2544,12 +2452,6 @@ inline s8 dComIfGp_getStartStageRoomNo() {
return g_dComIfG_gameInfo.play.getStartStageRoomNo();
}
#if TARGET_PC
inline s8 dComIfGp_getLayerNo() {
return g_dComIfG_gameInfo.play.getLayerNo(0);
}
#endif
inline s8 dComIfGp_getStartStageLayer() {
return g_dComIfG_gameInfo.play.getStartStageLayer();
}
@@ -2594,12 +2496,6 @@ inline void dComIfGp_offEnableNextStage() {
g_dComIfG_gameInfo.play.offEnableNextStage();
}
#if TARGET_PC
inline void dComIfGp_setEnableNextStage() {
g_dComIfG_gameInfo.play.setEnableNextStage();
}
#endif
inline s8 dComIfGp_getNextStageWipe() {
return g_dComIfG_gameInfo.play.getNextStageWipe();
}
+1 -1
View File
@@ -41,7 +41,7 @@ public:
BASE_ROOM5,
BASE_DEMO,
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
BASE_DEBUG,
#endif
+2 -17
View File
@@ -203,10 +203,6 @@ public:
DATASELPROC_DATA_SELECT_MOVE_ANIME,
DATASELPROC_SELECT_DATA_OPEN_MOVE,
DATASELPROC_SELECT_DATA_NAME_MOVE,
#if TARGET_PC
DATASELPROC_SELECT_DATA_PLAY_MOVE, // Select between vanilla or randomizer play
DATASELPROC_MENU_ARCHIPELAGO_CONNECT, // Wait for archipelago to connect
#endif
DATASELPROC_SELECT_DATA_OPENERASE_MOVE,
DATASELPROC_MENU_SELECT,
DATASELPROC_MENU_SELECT_MOVE_ANM,
@@ -335,10 +331,6 @@ public:
void makeRecInfo(u8);
void selectDataOpenMove();
void selectDataNameMove();
#if TARGET_PC
void selectDataPlayTypeMove();
void menuArchipelagoConnect();
#endif
void selectDataOpenEraseMove();
void menuSelect();
void menuSelectStart();
@@ -740,14 +732,7 @@ public:
/* 0x2378 */ J2DPicture* mpFadePict;
#endif
#ifdef TARGET_PC
struct mDusk {
dDlst_FileSelFade_c mFadeDlst;
bool mStartNameAnm;
bool mBackToFileSelect;
bool mArchipelagoBeginConnect;
bool mArchiStartCloseFile;
int mPendingRmlCloseFrames{0};
} mDusk;
dDlst_FileSelFade_c mFadeDlst;
#endif
#if PLATFORM_WII || PLATFORM_SHIELD
@@ -758,7 +743,7 @@ public:
};
#ifdef TARGET_PC
STATIC_ASSERT(sizeof(dFile_select_c) == 0x237C + sizeof(dFile_select_c::mDusk));
STATIC_ASSERT(sizeof(dFile_select_c) == 0x237C + sizeof(dDlst_FileSelFade_c));
#else
STATIC_ASSERT(sizeof(dFile_select_c) == 0x237C);
#endif
-135
View File
@@ -32,11 +32,6 @@ void item_func_ARROW_20();
void item_func_ARROW_30();
void item_func_ARROW_1();
void item_func_PACHINKO_SHOT();
#if TARGET_PC
void item_func_FOOLISH_ITEM();
void item_func_ORDON_PORTAL();
void item_func_SOUTH_FARON_PORTAL();
#endif
void item_func_WATER_BOMB_5();
void item_func_WATER_BOMB_10();
void item_func_WATER_BOMB_20();
@@ -70,12 +65,6 @@ void item_func_DUNGEON_EXIT_2();
void item_func_WALLET_LV1();
void item_func_WALLET_LV2();
void item_func_WALLET_LV3();
#if TARGET_PC
void item_func_UPPER_ZORAS_RIVER_PORTAL();
void item_func_CASTLE_TOWN_PORTAL();
void item_func_GERUDO_DESERT_PORTAL();
void item_func_NORTH_FARON_PORTAL();
#endif
void item_func_ZORAS_JEWEL();
void item_func_HAWK_EYE();
void item_func_WOOD_STICK();
@@ -92,23 +81,13 @@ void item_func_LIGHT_SWORD();
void item_func_FISHING_ROD_1();
void item_func_PACHINKO();
void item_func_COPY_ROD_2();
#if TARGET_PC
void item_func_KAKARIKO_GORGE_PORTAL();
void item_func_KAKARIKO_VILLAGE_PORTAL();
#endif
void item_func_BOMB_BAG_LV2();
void item_func_BOMB_BAG_LV1();
void item_func_BOMB_IN_BAG();
#if TARGET_PC
void item_func_DEATH_MOUNTAIN_PORTAL();
#endif
void item_func_LIGHT_ARROW();
void item_func_ARROW_LV1();
void item_func_ARROW_LV2();
void item_func_ARROW_LV3();
#if TARGET_PC
void item_func_ZORAS_DOMAIN_PORTAL();
#endif
void item_func_LURE_ROD();
void item_func_BOMB_ARROW();
void item_func_HAWK_ARROW();
@@ -154,33 +133,8 @@ void item_func_BILL();
void item_func_WOOD_STATUE();
void item_func_IRIAS_PENDANT();
void item_func_HORSE_FLUTE();
#if TARGET_PC
void item_func_FOREST_SMALL_KEY();
void item_func_MINES_SMALL_KEY();
void item_func_LAKEBED_SMALL_KEY();
void item_func_ARBITERS_SMALL_KEY();
void item_func_SNOWPEAK_SMALL_KEY();
void item_func_TEMPLE_OF_TIME_SMALL_KEY();
void item_func_CITY_SMALL_KEY();
void item_func_PALACE_SMALL_KEY();
void item_func_HYRULE_SMALL_KEY();
void item_func_CAMP_SMALL_KEY();
void item_func_LAKE_HYLIA_PORTAL();
#endif
void item_func_RAFRELS_MEMO();
void item_func_ASHS_SCRIBBLING();
#if TARGET_PC
void item_func_FOREST_BOSS_KEY();
void item_func_LAKEBED_BOSS_KEY();
void item_func_ARBITERS_BOSS_KEY();
void item_func_TEMPLE_OF_TIME_BOSS_KEY();
void item_func_CITY_BOSS_KEY();
void item_func_PALACE_BOSS_KEY();
void item_func_HYRULE_BOSS_KEY();
void item_func_FOREST_COMPASS();
void item_func_MINES_COMPASS();
void item_func_LAKEBED_COMPASS();
#endif
void item_func_CHUCHU_YELLOW2();
void item_func_OIL_BOTTLE3();
void item_func_SHOP_BEE_CHILD();
@@ -193,34 +147,12 @@ void item_func_FILLED_CONTAINER();
void item_func_MIRROR_PIECE_2();
void item_func_MIRROR_PIECE_3();
void item_func_MIRROR_PIECE_4();
#if TARGET_PC
void item_func_ARBITERS_COMPASS();
void item_func_SNOWPEAK_COMPASS();
void item_func_TEMPLE_OF_TIME_COMPASS();
void item_func_CITY_COMPASS();
void item_func_PALACE_COMPASS();
void item_func_HYRULE_COMPASS();
void item_func_MIRROR_CHAMBER_PORTAL();
void item_func_SNOWPEAK_PORTAL();
#endif
void item_func_SMELL_YELIA_POUCH();
void item_func_SMELL_PUMPKIN();
void item_func_SMELL_POH();
void item_func_SMELL_FISH();
void item_func_SMELL_CHILDREN();
void item_func_SMELL_MEDICINE();
#if TARGET_PC
void item_func_FOREST_MAP();
void item_func_MINES_MAP();
void item_func_LAKEBED_MAP();
void item_func_ARBITERS_MAP();
void item_func_SNOWPEAK_MAP();
void item_func_TEMPLE_OF_TIME_MAP();
void item_func_CITY_MAP();
void item_func_PALACE_MAP();
void item_func_HYRULE_MAP();
void item_func_SACRED_GROVE_PORTAL();
#endif
void item_func_M_BEETLE();
void item_func_F_BEETLE();
void item_func_M_BUTTERFLY();
@@ -245,24 +177,7 @@ void item_func_M_ANT();
void item_func_F_ANT();
void item_func_M_MAYFLY();
void item_func_F_MAYFLY();
#if TARGET_PC
void item_func_FUSED_SHADOW_1();
void item_func_FUSED_SHADOW_2();
void item_func_FUSED_SHADOW_3();
void item_func_MIRROR_PIECE_1();
void item_func_ARCHIPELAGO_ITEM();
#endif
void item_func_POU_SPIRIT();
#if TARGET_PC
void item_func_ENDING_BLOW();
void item_func_SHIELD_ATTACK();
void item_func_BACK_SLICE();
void item_func_HELM_SPLITTER();
void item_func_MORTAL_DRAW();
void item_func_JUMP_STRIKE();
void item_func_GREAT_SPIN();
void item_func_ELDIN_BRIDGE_PORTAL();
#endif
void item_func_ANCIENT_DOCUMENT();
void item_func_AIR_LETTER();
void item_func_ANCIENT_DOCUMENT2();
@@ -306,10 +221,6 @@ int item_getcheck_func_ARROW_20();
int item_getcheck_func_ARROW_30();
int item_getcheck_func_ARROW_1();
int item_getcheck_func_PACHINKO_SHOT();
#if TARGET_PC
int item_getcheck_func_ORDON_PORTAL();
int item_getcheck_func_SOUTH_FARON_PORTAL();
#endif
int item_getcheck_func_WATER_BOMB_5();
int item_getcheck_func_WATER_BOMB_10();
int item_getcheck_func_WATER_BOMB_20();
@@ -343,12 +254,6 @@ int item_getcheck_func_DUNGEON_EXIT_2();
int item_getcheck_func_WALLET_LV1();
int item_getcheck_func_WALLET_LV2();
int item_getcheck_func_WALLET_LV3();
#if TARGET_PC
int item_getcheck_func_UPPER_ZORAS_RIVER_PORTAL();
int item_getcheck_func_CASTLE_TOWN_PORTAL();
int item_getcheck_func_GERUDO_DESERT_PORTAL();
int item_getcheck_func_NORTH_FARON_PORTAL();
#endif
int item_getcheck_func_ZORAS_JEWEL();
int item_getcheck_func_HAWK_EYE();
int item_getcheck_func_WOOD_STICK();
@@ -365,23 +270,13 @@ int item_getcheck_func_LIGHT_SWORD();
int item_getcheck_func_FISHING_ROD_1();
int item_getcheck_func_PACHINKO();
int item_getcheck_func_COPY_ROD_2();
#if TARGET_PC
int item_getcheck_func_KAKARIKO_GORGE_PORTAL();
int item_getcheck_func_KAKARIKO_VILLAGE_PORTAL();
#endif
int item_getcheck_func_BOMB_BAG_LV2();
int item_getcheck_func_BOMB_BAG_LV1();
int item_getcheck_func_BOMB_IN_BAG();
#if TARGET_PC
int item_getcheck_func_DEATH_MOUNTAIN_PORTAL();
#endif
int item_getcheck_func_LIGHT_ARROW();
int item_getcheck_func_ARROW_LV1();
int item_getcheck_func_ARROW_LV2();
int item_getcheck_func_ARROW_LV3();
#if TARGET_PC
int item_getcheck_func_ZORAS_DOMAIN_PORTAL();
#endif
int item_getcheck_func_LURE_ROD();
int item_getcheck_func_BOMB_ARROW();
int item_getcheck_func_HAWK_ARROW();
@@ -427,10 +322,6 @@ int item_getcheck_func_BILL();
int item_getcheck_func_WOOD_STATUE();
int item_getcheck_func_IRIAS_PENDANT();
int item_getcheck_func_HORSE_FLUTE();
#if TARGET_PC
int item_getcheck_func_CAMP_SMALL_KEY();
int item_getcheck_func_LAKE_HYLIA_PORTAL();
#endif
int item_getcheck_func_RAFRELS_MEMO();
int item_getcheck_func_ASHS_SCRIBBLING();
int item_getcheck_func_CHUCHU_YELLOW2();
@@ -445,19 +336,12 @@ int item_getcheck_func_FILLED_CONTAINER();
int item_getcheck_func_MIRROR_PIECE_2();
int item_getcheck_func_MIRROR_PIECE_3();
int item_getcheck_func_MIRROR_PIECE_4();
#if TARGET_PC
int item_getcheck_func_MIRROR_CHAMBER_PORTAL();
int item_getcheck_func_SNOWPEAK_PORTAL();
#endif
int item_getcheck_func_SMELL_YELIA_POUCH();
int item_getcheck_func_SMELL_PUMPKIN();
int item_getcheck_func_SMELL_POH();
int item_getcheck_func_SMELL_FISH();
int item_getcheck_func_SMELL_CHILDREN();
int item_getcheck_func_SMELL_MEDICINE();
#if TARGET_PC
int item_getcheck_func_SACRED_GROVE_PORTAL();
#endif
int item_getcheck_func_M_BEETLE();
int item_getcheck_func_F_BEETLE();
int item_getcheck_func_M_BUTTERFLY();
@@ -482,23 +366,7 @@ int item_getcheck_func_M_ANT();
int item_getcheck_func_F_ANT();
int item_getcheck_func_M_MAYFLY();
int item_getcheck_func_F_MAYFLY();
#if TARGET_PC
int item_getcheck_func_FUSED_SHADOW_1();
int item_getcheck_func_FUSED_SHADOW_2();
int item_getcheck_func_FUSED_SHADOW_3();
int item_getcheck_func_MIRROR_PIECE_1();
#endif
int item_getcheck_func_POU_SPIRIT();
#if TARGET_PC
int item_getcheck_func_ENDING_BLOW();
int item_getcheck_func_SHIELD_ATTACK();
int item_getcheck_func_BACK_SLICE();
int item_getcheck_func_HELM_SPLITTER();
int item_getcheck_func_MORTAL_DRAW();
int item_getcheck_func_JUMP_STRIKE();
int item_getcheck_func_GREAT_SPIN();
int item_getcheck_func_ELDIN_BRIDGE_PORTAL();
#endif
int item_getcheck_func_ANCIENT_DOCUMENT();
int item_getcheck_func_AIR_LETTER();
int item_getcheck_func_ANCIENT_DOCUMENT2();
@@ -527,9 +395,6 @@ int checkItemGet(u8, int);
BOOL isHeart(u8 item_no);
int isBomb(u8);
int isArrow(u8);
#if TARGET_PC
int isRupee(u8);
#endif
int addBombCount(u8, u8);
BOOL isBottleItem(u8 item_no);
u8 check_itemno(int i_itemNo);
-327
View File
@@ -35,65 +35,7 @@ struct dItem_fieldItemResource {
/* 0xC */ u16 mHeapSize;
}; // Size: 0x10
#if TARGET_PC
// Delcared helper functions since we can't include dComIfg_isRandomizer here
dItem_itemResource* dItem_data_getItemResource();
dItem_fieldItemResource* dItem_data_getFieldItemResource();
dItem_itemInfo* dItem_data_getItemInfo();
#endif
struct dItem_data {
#if TARGET_PC
static const char* getArcName(u8 index) { return dItem_data_getItemResource()[index].mArcName; }
static s16 getBtpName(u8 index) { return dItem_data_getItemResource()[index].mBtpName; }
static s16 getBrkName(u8 index) { return dItem_data_getItemResource()[index].mBrkName; }
static s16 getBxaName(u8 index) { return -1; }
static s16 getBckName(u8 index) { return dItem_data_getItemResource()[index].mBckName; }
static s16 getBpkName(u8 index) { return -1; }
static s16 getBtkName(u8 index) { return dItem_data_getItemResource()[index].mBtkName; }
static s16 getBmdName(u8 index) { return dItem_data_getItemResource()[index].mBmdName; }
static s8 getTevFrm(u8 index) { return dItem_data_getItemResource()[index].mTevFrm; }
static s8 getBtpFrm(u8 index) { return dItem_data_getItemResource()[index].mBtpFrm; }
static s16 getTexture(u8 index) { return dItem_data_getItemResource()[index].mTexture; }
static u8 getTexScale (u8 index) { return dItem_data_getItemResource()[index].mTexScale; }
static const char* getFieldArc(u8 index) { return dItem_data_getFieldItemResource()[index].mFieldArc; }
static s16 getItemBmdName(u8 index) { return dItem_data_getFieldItemResource()[index].mItemBmdName; }
static s16 getItemBtkName(u8 index) { return -1; }
static s16 getItemBpkName(u8 index) { return -1; }
static s16 getItemBckName(u8 index) { return dItem_data_getFieldItemResource()[index].mItemBckName; }
static s16 getItemBxaName(u8 index) { return -1; }
static s16 getItemBrkName(u8 index) { return dItem_data_getFieldItemResource()[index].mItemBrkName; }
static s16 getItemBtpName(u8 index) { return -1; }
static BOOL chkFlag(u8 index, int flag) { return dItem_data_getItemInfo()[index].mFlag & flag; }
static u8 getShadowSize(u8 index) { return dItem_data_getItemInfo()[index].mShadowSize; }
static const u8 getH(u8 index) { return dItem_data_getItemInfo()[index].mH; }
static const u8 getR(u8 index) { return dItem_data_getItemInfo()[index].mR; }
static u16 getFieldHeapSize(u8 index) { return dItem_data_getFieldItemResource()[index].mHeapSize; }
#else
static const char* getArcName(u8 index) { return item_resource[index].mArcName; }
static s16 getBtpName(u8 index) { return item_resource[index].mBtpName; }
@@ -143,16 +85,10 @@ struct dItem_data {
static const u8 getR(u8 index) { return item_info[index].mR; }
static u16 getFieldHeapSize(u8 index) { return field_item_res[index].mHeapSize; }
#endif
static dItem_itemResource item_resource[255];
static dItem_fieldItemResource field_item_res[255];
static dItem_itemInfo item_info[255];
#if TARGET_PC
static dItem_itemResource item_resource_randomizer[255];
static dItem_fieldItemResource field_item_res_randomizer[255];
static dItem_itemInfo item_info_randomizer[255];
#endif
};
enum {
@@ -414,267 +350,4 @@ enum {
/* 0xFF */ dItemNo_NONE_e,
};
#if TARGET_PC
// Randomizer item ids. Mostly the same, but we use most unused
// entries for custom portals and keys
enum {
/* 0x00 */ dItemNo_Randomizer_HEART_e,
/* 0x01 */ dItemNo_Randomizer_GREEN_RUPEE_e,
/* 0x02 */ dItemNo_Randomizer_BLUE_RUPEE_e,
/* 0x03 */ dItemNo_Randomizer_YELLOW_RUPEE_e,
/* 0x04 */ dItemNo_Randomizer_RED_RUPEE_e,
/* 0x05 */ dItemNo_Randomizer_PURPLE_RUPEE_e,
/* 0x06 */ dItemNo_Randomizer_ORANGE_RUPEE_e,
/* 0x07 */ dItemNo_Randomizer_SILVER_RUPEE_e,
/* 0x08 */ dItemNo_Randomizer_S_MAGIC_e,
/* 0x09 */ dItemNo_Randomizer_L_MAGIC_e,
/* 0x0A */ dItemNo_Randomizer_BOMB_5_e,
/* 0x0B */ dItemNo_Randomizer_BOMB_10_e,
/* 0x0C */ dItemNo_Randomizer_BOMB_20_e,
/* 0x0D */ dItemNo_Randomizer_BOMB_30_e,
/* 0x0E */ dItemNo_Randomizer_ARROW_10_e,
/* 0x0F */ dItemNo_Randomizer_ARROW_20_e,
/* 0x10 */ dItemNo_Randomizer_ARROW_30_e,
/* 0x11 */ dItemNo_Randomizer_ARROW_1_e,
/* 0x12 */ dItemNo_Randomizer_PACHINKO_SHOT_e,
/* 0x13 */ dItemNo_Randomizer_FOOLISH_ITEM_e,
/* 0x14 */ dItemNo_Randomizer_ORDON_PORTAL_e,
/* 0x15 */ dItemNo_Randomizer_SOUTH_FARON_PORTAL_e,
/* 0x16 */ dItemNo_Randomizer_WATER_BOMB_5_e,
/* 0x17 */ dItemNo_Randomizer_WATER_BOMB_10_e,
/* 0x18 */ dItemNo_Randomizer_WATER_BOMB_20_e,
/* 0x19 */ dItemNo_Randomizer_WATER_BOMB_30_e,
/* 0x1A */ dItemNo_Randomizer_BOMB_INSECT_5_e,
/* 0x1B */ dItemNo_Randomizer_BOMB_INSECT_10_e,
/* 0x1C */ dItemNo_Randomizer_BOMB_INSECT_20_e,
/* 0x1D */ dItemNo_Randomizer_BOMB_INSECT_30_e,
/* 0x1E */ dItemNo_Randomizer_RECOVERY_FAILY_e,
/* 0x1F */ dItemNo_Randomizer_TRIPLE_HEART_e,
/* 0x20 */ dItemNo_Randomizer_SMALL_KEY_e,
/* 0x21 */ dItemNo_Randomizer_KAKERA_HEART_e,
/* 0x22 */ dItemNo_Randomizer_UTAWA_HEART_e,
/* 0x23 */ dItemNo_Randomizer_MAP_e,
/* 0x24 */ dItemNo_Randomizer_COMPUS_e,
/* 0x25 */ dItemNo_Randomizer_DUNGEON_EXIT_e,
/* 0x26 */ dItemNo_Randomizer_BOSS_KEY_e,
/* 0x27 */ dItemNo_Randomizer_DUNGEON_BACK_e,
/* 0x28 */ dItemNo_Randomizer_SWORD_e,
/* 0x29 */ dItemNo_Randomizer_MASTER_SWORD_e,
/* 0x2A */ dItemNo_Randomizer_WOOD_SHIELD_e,
/* 0x2B */ dItemNo_Randomizer_SHIELD_e,
/* 0x2C */ dItemNo_Randomizer_HYLIA_SHIELD_e,
/* 0x2D */ dItemNo_Randomizer_TKS_LETTER_e,
/* 0x2E */ dItemNo_Randomizer_WEAR_CASUAL_e,
/* 0x2F */ dItemNo_Randomizer_WEAR_KOKIRI_e,
/* 0x30 */ dItemNo_Randomizer_ARMOR_e,
/* 0x31 */ dItemNo_Randomizer_WEAR_ZORA_e,
/* 0x32 */ dItemNo_Randomizer_MAGIC_LV1_e,
/* 0x33 */ dItemNo_Randomizer_DUNGEON_EXIT_2_e,
/* 0x34 */ dItemNo_Randomizer_WALLET_LV1_e,
/* 0x35 */ dItemNo_Randomizer_WALLET_LV2_e,
/* 0x36 */ dItemNo_Randomizer_WALLET_LV3_e,
/* 0x37 */ dItemNo_Randomizer_NOENTRY_55_e,
/* 0x38 */ dItemNo_Randomizer_NOENTRY_56_e,
/* 0x39 */ dItemNo_Randomizer_UPPER_ZORAS_RIVER_PORTAL_e,
/* 0x3A */ dItemNo_Randomizer_CASTLE_TOWN_PORTAL_e,
/* 0x3B */ dItemNo_Randomizer_GERUDO_DESERT_PORTAL_e,
/* 0x3C */ dItemNo_Randomizer_NORTH_FARON_PORTAL_e,
/* 0x3D */ dItemNo_Randomizer_ZORAS_JEWEL_e,
/* 0x3E */ dItemNo_Randomizer_HAWK_EYE_e,
/* 0x3F */ dItemNo_Randomizer_WOOD_STICK_e,
/* 0x40 */ dItemNo_Randomizer_BOOMERANG_e,
/* 0x41 */ dItemNo_Randomizer_SPINNER_e,
/* 0x42 */ dItemNo_Randomizer_IRONBALL_e,
/* 0x43 */ dItemNo_Randomizer_BOW_e,
/* 0x44 */ dItemNo_Randomizer_HOOKSHOT_e,
/* 0x45 */ dItemNo_Randomizer_HVY_BOOTS_e,
/* 0x46 */ dItemNo_Randomizer_COPY_ROD_e,
/* 0x47 */ dItemNo_Randomizer_W_HOOKSHOT_e,
/* 0x48 */ dItemNo_Randomizer_KANTERA_e,
/* 0x49 */ dItemNo_Randomizer_LIGHT_SWORD_e,
/* 0x4A */ dItemNo_Randomizer_FISHING_ROD_1_e,
/* 0x4B */ dItemNo_Randomizer_PACHINKO_e,
/* 0x4C */ dItemNo_Randomizer_COPY_ROD_2_e,
/* 0x4D */ dItemNo_Randomizer_KAKARIKO_GORGE_PORTAL_e,
/* 0x4E */ dItemNo_Randomizer_KAKARIKO_VILLAGE_PORTAL_e,
/* 0x4F */ dItemNo_Randomizer_BOMB_BAG_LV2_e,
/* 0x50 */ dItemNo_Randomizer_BOMB_BAG_LV1_e,
/* 0x51 */ dItemNo_Randomizer_BOMB_IN_BAG_e,
/* 0x52 */ dItemNo_Randomizer_DEATH_MOUNTAIN_PORTAL_e,
/* 0x53 */ dItemNo_Randomizer_LIGHT_ARROW_e,
/* 0x54 */ dItemNo_Randomizer_ARROW_LV1_e,
/* 0x55 */ dItemNo_Randomizer_ARROW_LV2_e,
/* 0x56 */ dItemNo_Randomizer_ARROW_LV3_e,
/* 0x57 */ dItemNo_Randomizer_ZORAS_DOMAIN_PORTAL_e,
/* 0x58 */ dItemNo_Randomizer_LURE_ROD_e,
/* 0x59 */ dItemNo_Randomizer_BOMB_ARROW_e,
/* 0x5A */ dItemNo_Randomizer_HAWK_ARROW_e,
/* 0x5B */ dItemNo_Randomizer_BEE_ROD_e,
/* 0x5C */ dItemNo_Randomizer_JEWEL_ROD_e,
/* 0x5D */ dItemNo_Randomizer_WORM_ROD_e,
/* 0x5E */ dItemNo_Randomizer_JEWEL_BEE_ROD_e,
/* 0x5F */ dItemNo_Randomizer_JEWEL_WORM_ROD_e,
/* 0x60 */ dItemNo_Randomizer_EMPTY_BOTTLE_e,
/* 0x61 */ dItemNo_Randomizer_RED_BOTTLE_e,
/* 0x62 */ dItemNo_Randomizer_GREEN_BOTTLE_e,
/* 0x63 */ dItemNo_Randomizer_BLUE_BOTTLE_e,
/* 0x64 */ dItemNo_Randomizer_MILK_BOTTLE_e,
/* 0x65 */ dItemNo_Randomizer_HALF_MILK_BOTTLE_e,
/* 0x66 */ dItemNo_Randomizer_OIL_BOTTLE_e,
/* 0x67 */ dItemNo_Randomizer_WATER_BOTTLE_e,
/* 0x68 */ dItemNo_Randomizer_OIL_BOTTLE_2_e,
/* 0x69 */ dItemNo_Randomizer_RED_BOTTLE_2_e,
/* 0x6A */ dItemNo_Randomizer_UGLY_SOUP_e,
/* 0x6B */ dItemNo_Randomizer_HOT_SPRING_e,
/* 0x6C */ dItemNo_Randomizer_FAIRY_e,
/* 0x6D */ dItemNo_Randomizer_HOT_SPRING_2_e,
/* 0x6E */ dItemNo_Randomizer_OIL2_e,
/* 0x6F */ dItemNo_Randomizer_OIL_e,
/* 0x70 */ dItemNo_Randomizer_NORMAL_BOMB_e,
/* 0x71 */ dItemNo_Randomizer_WATER_BOMB_e,
/* 0x72 */ dItemNo_Randomizer_POKE_BOMB_e,
/* 0x73 */ dItemNo_Randomizer_FAIRY_DROP_e,
/* 0x74 */ dItemNo_Randomizer_WORM_e,
/* 0x75 */ dItemNo_Randomizer_DROP_BOTTLE_e,
/* 0x76 */ dItemNo_Randomizer_BEE_CHILD_e,
/* 0x77 */ dItemNo_Randomizer_CHUCHU_RARE_e,
/* 0x78 */ dItemNo_Randomizer_CHUCHU_RED_e,
/* 0x79 */ dItemNo_Randomizer_CHUCHU_BLUE_e,
/* 0x7A */ dItemNo_Randomizer_CHUCHU_GREEN_e,
/* 0x7B */ dItemNo_Randomizer_CHUCHU_YELLOW_e,
/* 0x7C */ dItemNo_Randomizer_CHUCHU_PURPLE_e,
/* 0x7D */ dItemNo_Randomizer_LV1_SOUP_e,
/* 0x7E */ dItemNo_Randomizer_LV2_SOUP_e,
/* 0x7F */ dItemNo_Randomizer_LV3_SOUP_e,
/* 0x80 */ dItemNo_Randomizer_LETTER_e,
/* 0x81 */ dItemNo_Randomizer_BILL_e,
/* 0x82 */ dItemNo_Randomizer_WOOD_STATUE_e,
/* 0x83 */ dItemNo_Randomizer_IRIAS_PENDANT_e,
/* 0x84 */ dItemNo_Randomizer_HORSE_FLUTE_e,
/* 0x85 */ dItemNo_Randomizer_FOREST_SMALL_KEY_e,
/* 0x86 */ dItemNo_Randomizer_MINES_SMALL_KEY_e,
/* 0x87 */ dItemNo_Randomizer_LAKEBED_SMALL_KEY_e,
/* 0x88 */ dItemNo_Randomizer_ARBITERS_SMALL_KEY_e,
/* 0x89 */ dItemNo_Randomizer_SNOWPEAK_SMALL_KEY_e,
/* 0x8A */ dItemNo_Randomizer_TEMPLE_OF_TIME_SMALL_KEY_e,
/* 0x8B */ dItemNo_Randomizer_CITY_SMALL_KEY_e,
/* 0x8C */ dItemNo_Randomizer_PALACE_SMALL_KEY_e,
/* 0x8D */ dItemNo_Randomizer_HYRULE_SMALL_KEY_e,
/* 0x8E */ dItemNo_Randomizer_CAMP_SMALL_KEY_e,
/* 0x8F */ dItemNo_Randomizer_LAKE_HYLIA_PORTAL_e,
/* 0x90 */ dItemNo_Randomizer_RAFRELS_MEMO_e,
/* 0x91 */ dItemNo_Randomizer_ASHS_SCRIBBLING_e,
/* 0x92 */ dItemNo_Randomizer_FOREST_BOSS_KEY_e,
/* 0x93 */ dItemNo_Randomizer_LAKEBED_BOSS_KEY_e,
/* 0x94 */ dItemNo_Randomizer_ARBITERS_BOSS_KEY_e,
/* 0x95 */ dItemNo_Randomizer_TEMPLE_OF_TIME_BOSS_KEY_e,
/* 0x96 */ dItemNo_Randomizer_CITY_BOSS_KEY_e,
/* 0x97 */ dItemNo_Randomizer_PALACE_BOSS_KEY_e,
/* 0x98 */ dItemNo_Randomizer_HYRULE_BOSS_KEY_e,
/* 0x99 */ dItemNo_Randomizer_FOREST_COMPASS_e,
/* 0x9A */ dItemNo_Randomizer_MINES_COMPASS_e,
/* 0x9B */ dItemNo_Randomizer_LAKEBED_COMPASS_e,
/* 0x9C */ dItemNo_Randomizer_CHUCHU_YELLOW2_e,
/* 0x9D */ dItemNo_Randomizer_OIL_BOTTLE3_e,
/* 0x9E */ dItemNo_Randomizer_SHOP_BEE_CHILD_e,
/* 0x9F */ dItemNo_Randomizer_CHUCHU_BLACK_e,
/* 0xA0 */ dItemNo_Randomizer_LIGHT_DROP_e,
/* 0xA1 */ dItemNo_Randomizer_DROP_CONTAINER_e,
/* 0xA2 */ dItemNo_Randomizer_DROP_CONTAINER02_e,
/* 0xA3 */ dItemNo_Randomizer_DROP_CONTAINER03_e,
/* 0xA4 */ dItemNo_Randomizer_FILLED_CONTAINER_e,
/* 0xA5 */ dItemNo_Randomizer_MIRROR_PIECE_2_e,
/* 0xA6 */ dItemNo_Randomizer_MIRROR_PIECE_3_e,
/* 0xA7 */ dItemNo_Randomizer_MIRROR_PIECE_4_e,
/* 0xA8 */ dItemNo_Randomizer_ARBITERS_COMPASS_e,
/* 0xA9 */ dItemNo_Randomizer_SNOWPEAK_COMPASS_e,
/* 0xAA */ dItemNo_Randomizer_TEMPLE_OF_TIME_COMPASS_e,
/* 0xAB */ dItemNo_Randomizer_CITY_COMPASS_e,
/* 0xAC */ dItemNo_Randomizer_PALACE_COMPASS_e,
/* 0xAD */ dItemNo_Randomizer_HYRULE_COMPASS_e,
/* 0xAE */ dItemNo_Randomizer_MIRROR_CHAMBER_PORTAL_e,
/* 0xAF */ dItemNo_Randomizer_SNOWPEAK_PORTAL_e,
/* 0xB0 */ dItemNo_Randomizer_SMELL_YELIA_POUCH_e,
/* 0xB1 */ dItemNo_Randomizer_SMELL_PUMPKIN_e,
/* 0xB2 */ dItemNo_Randomizer_SMELL_POH_e,
/* 0xB3 */ dItemNo_Randomizer_SMELL_FISH_e,
/* 0xB4 */ dItemNo_Randomizer_SMELL_CHILDREN_e,
/* 0xB5 */ dItemNo_Randomizer_SMELL_MEDICINE_e,
/* 0xB6 */ dItemNo_Randomizer_FOREST_MAP_e,
/* 0xB7 */ dItemNo_Randomizer_MINES_MAP_e,
/* 0xB8 */ dItemNo_Randomizer_LAKEBED_MAP_e,
/* 0xB9 */ dItemNo_Randomizer_ARBITERS_MAP_e,
/* 0xBA */ dItemNo_Randomizer_SNOWPEAK_MAP_e,
/* 0xBB */ dItemNo_Randomizer_TEMPLE_OF_TIME_MAP_e,
/* 0xBC */ dItemNo_Randomizer_CITY_MAP_e,
/* 0xBD */ dItemNo_Randomizer_PALACE_MAP_e,
/* 0xBE */ dItemNo_Randomizer_HYRULE_MAP_e,
/* 0xBF */ dItemNo_Randomizer_SACRED_GROVE_PORTAL_e,
/* 0xC0 */ dItemNo_Randomizer_M_BEETLE_e,
/* 0xC1 */ dItemNo_Randomizer_F_BEETLE_e,
/* 0xC2 */ dItemNo_Randomizer_M_BUTTERFLY_e,
/* 0xC3 */ dItemNo_Randomizer_F_BUTTERFLY_e,
/* 0xC4 */ dItemNo_Randomizer_M_STAG_BEETLE_e,
/* 0xC5 */ dItemNo_Randomizer_F_STAG_BEETLE_e,
/* 0xC6 */ dItemNo_Randomizer_M_GRASSHOPPER_e,
/* 0xC7 */ dItemNo_Randomizer_F_GRASSHOPPER_e,
/* 0xC8 */ dItemNo_Randomizer_M_NANAFUSHI_e,
/* 0xC9 */ dItemNo_Randomizer_F_NANAFUSHI_e,
/* 0xCA */ dItemNo_Randomizer_M_DANGOMUSHI_e,
/* 0xCB */ dItemNo_Randomizer_F_DANGOMUSHI_e,
/* 0xCC */ dItemNo_Randomizer_M_MANTIS_e,
/* 0xCD */ dItemNo_Randomizer_F_MANTIS_e,
/* 0xCE */ dItemNo_Randomizer_M_LADYBUG_e,
/* 0xCF */ dItemNo_Randomizer_F_LADYBUG_e,
/* 0xD0 */ dItemNo_Randomizer_M_SNAIL_e,
/* 0xD1 */ dItemNo_Randomizer_F_SNAIL_e,
/* 0xD2 */ dItemNo_Randomizer_M_DRAGONFLY_e,
/* 0xD3 */ dItemNo_Randomizer_F_DRAGONFLY_e,
/* 0xD4 */ dItemNo_Randomizer_M_ANT_e,
/* 0xD5 */ dItemNo_Randomizer_F_ANT_e,
/* 0xD6 */ dItemNo_Randomizer_M_MAYFLY_e,
/* 0xD7 */ dItemNo_Randomizer_F_MAYFLY_e,
/* 0xD8 */ dItemNo_Randomizer_FUSED_SHADOW_1_e,
/* 0xD9 */ dItemNo_Randomizer_FUSED_SHADOW_2_e,
/* 0xDA */ dItemNo_Randomizer_FUSED_SHADOW_3_e,
/* 0xDB */ dItemNo_Randomizer_MIRROR_PIECE_1_e,
/* 0xDC */ dItemNo_Randomizer_ARCHIPELAGO_ITEM_e,
/* 0xDD */ dItemNo_Randomizer_NOENTRY_221_e,
/* 0xDE */ dItemNo_Randomizer_NOENTRY_222_e,
/* 0xDF */ dItemNo_Randomizer_NOENTRY_223_e,
/* 0xE0 */ dItemNo_Randomizer_POU_SPIRIT_e,
/* 0xE1 */ dItemNo_Randomizer_ENDING_BLOW_e,
/* 0xE2 */ dItemNo_Randomizer_SHIELD_ATTACK_e,
/* 0xE3 */ dItemNo_Randomizer_BACK_SLICE_e,
/* 0xE4 */ dItemNo_Randomizer_HELM_SPLITTER_e,
/* 0xE5 */ dItemNo_Randomizer_MORTAL_DRAW_e,
/* 0xE6 */ dItemNo_Randomizer_JUMP_STRIKE_e,
/* 0xE7 */ dItemNo_Randomizer_GREAT_SPIN_e,
/* 0xE8 */ dItemNo_Randomizer_ELDIN_BRIDGE_PORTAL_e,
/* 0xE9 */ dItemNo_Randomizer_ANCIENT_DOCUMENT_e,
/* 0xEA */ dItemNo_Randomizer_AIR_LETTER_e,
/* 0xEB */ dItemNo_Randomizer_ANCIENT_DOCUMENT2_e,
/* 0xEC */ dItemNo_Randomizer_LV7_DUNGEON_EXIT_e,
/* 0xED */ dItemNo_Randomizer_LINKS_SAVINGS_e,
/* 0xEE */ dItemNo_Randomizer_SMALL_KEY2_e,
/* 0xEF */ dItemNo_Randomizer_POU_FIRE1_e,
/* 0xF0 */ dItemNo_Randomizer_POU_FIRE2_e,
/* 0xF1 */ dItemNo_Randomizer_POU_FIRE3_e,
/* 0xF2 */ dItemNo_Randomizer_POU_FIRE4_e,
/* 0xF3 */ dItemNo_Randomizer_BOSSRIDER_KEY_e,
/* 0xF4 */ dItemNo_Randomizer_TOMATO_PUREE_e,
/* 0xF5 */ dItemNo_Randomizer_TASTE_e,
/* 0xF6 */ dItemNo_Randomizer_LV5_BOSS_KEY_e,
/* 0xF7 */ dItemNo_Randomizer_SURFBOARD_e,
/* 0xF8 */ dItemNo_Randomizer_KANTERA2_e,
/* 0xF9 */ dItemNo_Randomizer_L2_KEY_PIECES1_e,
/* 0xFA */ dItemNo_Randomizer_L2_KEY_PIECES2_e,
/* 0xFB */ dItemNo_Randomizer_L2_KEY_PIECES3_e,
/* 0xFC */ dItemNo_Randomizer_KEY_OF_CARAVAN_e,
/* 0xFD */ dItemNo_Randomizer_LV2_BOSS_KEY_e,
/* 0xFE */ dItemNo_Randomizer_KEY_OF_FILONE_e,
/* 0xFF */ dItemNo_Randomizer_NONE_e,
};
#endif
#endif /* D_D_ITEM_DATA_H */
+1 -1
View File
@@ -259,7 +259,7 @@ public:
/* 0x09B8 */ DUNGEON_LIGHT dungeonlight[8];
/* 0x0C18 */ BOSS_LIGHT field_0x0c18[8];
/* 0x0D58 */ BOSS_LIGHT field_0x0d58[6];
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x0E48 */ NAVYCHAN navy;
/* 0x0E58 */ u8 field_0xe58[0xE68 - 0xE58]; // part of NAVYCHAN?
#endif
-2
View File
@@ -75,7 +75,6 @@ public:
bool dpdMove();
u8 openExplain(u8);
#if TARGET_PC
void updateSlotImage(u8 slot);
bool pointerMove();
#endif
@@ -220,7 +219,6 @@ private:
bool mCursorInterpCurrAngular;
bool mCursorInterpInit;
bool mPointerTouchPressHoveredCurrent;
J2DPicture* mDpadIcon;
#endif
};
+1 -3
View File
@@ -129,9 +129,7 @@ public:
bool getPlayerSubject();
bool isBButtonShow(bool);
s16 getButtonTimer();
#if TARGET_PC
f32 getZButtonAlpha() { return mButtonZAlpha;}
#endif
virtual ~dMeter2Draw_c();
J2DScreen* getMainScreenPtr() { return mpScreen; }
+2 -20
View File
@@ -58,9 +58,6 @@ public:
int checkEventRender(int*, int*, int*, int*);
void remove();
u16 getEventId(int*);
#if TARGET_PC
u16 getEventId();
#endif
u32 getMsgNo();
u32 getNowMsgNo();
msg_class* getMsg();
@@ -131,9 +128,6 @@ public:
u16 query051(mesg_flow_node_branch*, fopAc_ac_c*, int);
u16 query052(mesg_flow_node_branch*, fopAc_ac_c*, int);
u16 query053(mesg_flow_node_branch*, fopAc_ac_c*, int);
#if TARGET_PC
u16 query054(mesg_flow_node_branch*, fopAc_ac_c*, int);
#endif
int event000(mesg_flow_node_event*, fopAc_ac_c*);
int event001(mesg_flow_node_event*, fopAc_ac_c*);
int event002(mesg_flow_node_event*, fopAc_ac_c*);
@@ -177,11 +171,6 @@ public:
int event040(mesg_flow_node_event*, fopAc_ac_c*);
int event041(mesg_flow_node_event*, fopAc_ac_c*);
int event042(mesg_flow_node_event*, fopAc_ac_c*);
#if TARGET_PC
// events for rando
int event043(mesg_flow_node_event*, fopAc_ac_c*);
int event044(mesg_flow_node_event*, fopAc_ac_c*);
#endif
void initWord(fopAc_ac_c*, const char*, u8, int, fopAc_ac_c**);
@@ -196,15 +185,8 @@ public:
void setMsg(u32 msg) { mMsg = msg; }
bool checkEndFlow() { return (u32)field_0x26 == 1; }
static queryFunc mQueryList[DUSK_IF_ELSE(54, 53)];
static eventFunc mEventList[DUSK_IF_ELSE(45, 43)];
#if TARGET_PC
// patch funcs for rando
void randoPatchNodeType(u8& type, u16 nodeIdx);
void randoPatchBranchNode(mesg_flow_node_branch*& branch_node, u16 nodeIdx);
void randoPatchEventNode(mesg_flow_node_event*& event_node, u16 nodeIdx);
#endif
static queryFunc mQueryList[53];
static eventFunc mEventList[43];
private:
/* 0x04 */ u8* mFlow_p;
+2 -2
View File
@@ -521,13 +521,13 @@ private:
/* 0x019 */ u8 field_0x19;
/* 0x01A */ u8 field_0x1a;
/* 0x01B */ u8 field_0x1b;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x01C */ dPa_simpleEcallBack field_0x1c[48];
#else
/* 0x01C */ dPa_simpleEcallBack field_0x1c[25];
#endif
/* 0x210 */ level_c field_0x210;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
u8 mSceneCount;
#endif
};
+1 -1
View File
@@ -59,7 +59,7 @@ private:
/* 0x18 */ JKRHeap* heap;
/* 0x1C */ JKRSolidHeap* mDataHeap;
/* 0x20 */ void** mRes;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x24 */ int mSize;
#endif
}; // Size: 0x24
+4 -37
View File
@@ -10,10 +10,6 @@
#include "JSystem/JHostIO/JORReflexible.h"
#include "dusk/endian.h"
#if TARGET_PC
#include <unordered_map>
#endif
static const int DEFAULT_SELECT_ITEM_INDEX = 0;
static const int MAX_SELECT_ITEM = 4;
static const int SELECT_ITEM_NUM = 2;
@@ -203,9 +199,6 @@ public:
void setTime(f32 i_time) { mTime = i_time; }
u16 getDate() const { return mDate; }
void setDate(u16 i_date) { mDate = i_date; }
#if TARGET_PC
u8 getDarkClearLV() const { return mDarkClearLevelFlag; }
#endif
/* 0x00 */ BE(OSTime) mDateIpl;
/* 0x08 */ u8 mTransformLevelFlag;
@@ -279,9 +272,6 @@ public:
bool isFieldDataExistFlag() const { return mFieldDataExistFlag ? true : false; }
void offFieldDataExistFlag() { mFieldDataExistFlag = false; }
void onFieldDataExistFlag() { mFieldDataExistFlag = true; }
#if TARGET_PC
void setRegionBit(u8 region) { mRegion |= region;}
#endif
#if TARGET_PC
/* 0x00 */ BE(Vec) mPos;
@@ -386,21 +376,12 @@ public:
void setArrowNum(u8 i_num) { mArrowNum = i_num; }
u8 getPachinkoNum() const { return mPachinkoNum; }
void setPachinkoNum(u8 i_num) { mPachinkoNum = i_num; }
#if TARGET_PC
u8 getAncientDocumentNum() const { return mAncientDocumentNum; }
void setAncientDocumentNum(u8 i_num) { mAncientDocumentNum = i_num; }
#endif
/* 0x0 */ u8 mArrowNum;
/* 0x1 */ u8 mBombNum[3];
/* 0x4 */ u8 mBottleNum[4];
/* 0x8 */ u8 mPachinkoNum;
#if TARGET_PC
/* 0x9 */ u8 mAncientDocumentNum; // Custom Field for Randomizer
/* 0x9 */ u8 unk5[2];
#else
/* 0x9 */ u8 unk5[3];
#endif
}; // Size: 0xC
class dSv_player_item_max_c {
@@ -432,11 +413,7 @@ public:
u8 getPohNum() const { return mPohNum; }
void addPohNum() {
#if TARGET_PC
if (mPohNum < 0x3D) {
#else
if (mPohNum < 0xFF) {
#endif
mPohNum++;
}
}
@@ -481,11 +458,6 @@ public:
int isLetterReadFlag(int i_no) const;
u8 getGetNumber(int i_no) { return mGetNumber[i_no]; }
void setGetNumber(int i_no, u8 i_value) { mGetNumber[i_no] = i_value; }
#if TARGET_PC
// For rando
void setAllLetterGet() { mLetterGetFlags[0] |= 0xFFFF;}
void setAllLetterRead() { mLetterReadFlags[0] |= 0xFFFF;}
#endif
/* 0x00 */ BE(u32) mLetterGetFlags[2];
/* 0x08 */ BE(u32) mLetterReadFlags[2];
@@ -692,14 +664,10 @@ public:
void onDungeonItemBossKey() { onDungeonItem(BOSS_KEY); }
void offDungeonItemBossKey() { offDungeonItem(BOSS_KEY); }
s32 isDungeonItemBossKey() const { return isDungeonItem(BOSS_KEY); }
#if TARGET_PC
void onStageBossEnemy();
#else
void onStageBossEnemy() {
onDungeonItem(STAGE_BOSS_ENEMY);
onDungeonItem(OOCCOO_NOTE);
}
#endif
void offStageBossEnemy() { offDungeonItem(STAGE_BOSS_ENEMY); }
s32 isStageBossEnemy() const { return isDungeonItem(STAGE_BOSS_ENEMY); }
void onStageLife() { onDungeonItem(STAGE_LIFE); }
@@ -980,7 +948,6 @@ public:
/* 0x8 */ s8 m_no;
};
class dSv_info_c {
public:
void init();
@@ -993,9 +960,6 @@ public:
BOOL isSwitch(int i_no, int i_roomNo) const;
BOOL revSwitch(int i_no, int i_roomNo);
void onItem(int i_no, int i_roomNo);
#if TARGET_PC
void offItem(int i_no, int i_roomNo);
#endif
BOOL isItem(int i_no, int i_roomNo) const;
void onActor(int i_no, int i_roomNo);
void offActor(int i_no, int i_roomNo);
@@ -1044,7 +1008,7 @@ public:
static const int ZONE_MAX = 0x20;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x000 */ u8 unk_0x0;
/* 0x001 */ char unk_0x1;
/* 0x000 */ u8 unk_0x2[0x48 - 0x2];
@@ -1065,6 +1029,9 @@ public:
/* 0xF30 */ s64 mSaveTotalTime;
#if DEBUG
/* 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
}; // Size: 0xF38
+4 -7
View File
@@ -538,7 +538,7 @@ public:
/* vt[86] */ virtual stage_tgsc_class* getDrTg(void) const = 0;
/* vt[87] */ virtual void setDoor(stage_tgsc_class*) = 0;
/* vt[88] */ virtual stage_tgsc_class* getDoor(void) const = 0;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
virtual void setUnit(void*) = 0;
virtual void* getUnit() = 0;
#endif
@@ -796,7 +796,7 @@ public:
virtual stage_tgsc_class* getDrTg(void) const { return mDrTg; }
virtual void setDoor(stage_tgsc_class* i_Door) { mDoor = i_Door; }
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* getUnit() { return mUnit; }
#endif
@@ -845,7 +845,7 @@ public:
/* 0x54 */ stage_tgsc_class* mDrTg;
/* 0x58 */ stage_tgsc_class* mDoor;
/* 0x5C */ dStage_FloorInfo_c* mFloorInfo;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x60 */ void* mUnit;
#endif
/* 0x60 */ u16 mPlayerNum;
@@ -990,7 +990,7 @@ public:
/* 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[88] */ virtual stage_tgsc_class* getDoor(void) const { return mDoor; }
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
virtual void setUnit(void* i_Unit) {
UNUSED(i_Unit);
OSReport("stage non unit list data !!\n");
@@ -1292,9 +1292,6 @@ public:
void set(const char*, s8, s16, s8, s8, u8);
void offEnable() { enabled = 0; }
BOOL isEnable() const { return enabled; }
#if TARGET_PC
void setEnable() { enabled |= 0x1; }
#endif
s8 getWipe() const { return wipe; }
u8 getWipeSpeed() const { return wipe_speed; }
dStage_startStage_c* getStartStage() { return this; }
+3
View File
@@ -97,6 +97,9 @@ public:
private:
#if DEBUG
/* 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
class {
+69 -7
View File
@@ -1,9 +1,11 @@
#ifndef DUSK_CONFIG_HPP
#define DUSK_CONFIG_HPP
#include <concepts>
#include <functional>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include "nlohmann/json.hpp"
#include "config_var.hpp"
namespace dusk::config {
@@ -40,7 +42,7 @@ public:
[[nodiscard]] virtual nlohmann::json dumpToJson(const ConfigVarBase& cVar) const = 0;
};
template<ConfigValue T>
template <ConfigValue T>
class ConfigImpl : public ConfigImplBase {
// Just downcasting the references...
void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const final {
@@ -90,20 +92,28 @@ public:
void Register(ConfigVarBase& configVar);
/**
* \brief Indicate that all registrations have happened and everything should lock in.
* \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 FinishRegistration();
void unregister(ConfigVarBase& configVar);
/**
* \brief Load config from the standard user preferences location.
*/
void LoadFromUserPreferences();
void LoadFromFileName(const char* path);
void load_from_user_preferences();
void load_from_file_name(const char* path);
void load_arg_override(std::string_view name, std::string_view value);
void shutdown();
/**
* \brief Save the config to file.
*/
void Save();
void save();
/**
* \brief Get a registered CVar by name.
@@ -124,6 +134,58 @@ void ClearAllActionBindings(int port);
*/
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); not when
* loaded from config or launch arguments.
*/
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>
const ConfigImplBase* GetConfigImpl() {
static ConfigImpl<T> config;
+82 -6
View File
@@ -2,10 +2,12 @@
#define DUSK_CONFIG_VAR_HPP
#include "dolphin/types.h"
#include <type_traits>
#include <concepts>
#include <cstdlib>
#include <limits>
#include <optional>
#include <string>
#include <type_traits>
/**
* The configuration system.
@@ -69,7 +71,7 @@ protected:
/**
* The name of this CVar, used in the configuration file.
*/
const char* name;
std::string name;
/**
* Whether this CVar has been registered with the global managing logic.
@@ -87,8 +89,8 @@ protected:
*/
const ConfigImplBase* impl;
ConfigVarBase(const char* name, const ConfigImplBase* impl);
virtual ~ConfigVarBase() = default;
ConfigVarBase(const ConfigVarBase&) = delete;
ConfigVarBase(std::string name, const ConfigImplBase* impl);
/**
* Check that the CVar is registered, aborting if this is not the case.
@@ -98,7 +100,22 @@ protected:
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:
virtual ~ConfigVarBase();
/**
* Get the name of this CVar, used in the configuration file.
*/
@@ -121,6 +138,7 @@ public:
* This is necessary to make it legal to access.
*/
void markRegistered();
void unmarkRegistered();
/**
* Clear a speedrun-mode override if one is active on this CVar.
@@ -155,6 +173,7 @@ template <typename T>
concept ConfigValue =
!std::is_const_v<T>
&& !std::is_volatile_v<T>
&& std::equality_comparable<T>
&& (std::is_same_v<T, bool>
|| ConfigValueInteger<T>
|| std::is_same_v<T, f32>
@@ -166,6 +185,9 @@ concept ConfigValue =
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl();
template <ConfigValue T>
class ConfigImpl;
template <typename T>
struct ConfigEnumRange {
static constexpr auto min = std::numeric_limits<std::underlying_type_t<T>>::min();
@@ -192,10 +214,12 @@ public:
* @param arg Arguments to forward to construct the default value.
*/
template <typename... Args>
ConfigVar(const char* name, Args&&... arg)
: ConfigVarBase(name, GetConfigImpl<T>()), defaultValue(std::forward<Args>(arg)...),
ConfigVar(std::string name, Args&&... arg)
: ConfigVarBase(std::move(name), GetConfigImpl<T>()), defaultValue(std::forward<Args>(arg)...),
value(), overrideValue() {}
ConfigVar(ConfigVar const&) = delete;
/**
* \brief Get the current value of the CVar.
*
@@ -234,6 +258,7 @@ public:
*/
void setValue(T newValue, bool replaceOverride = true) {
checkRegistered();
const auto previous = previous_for_notify();
value = std::move(newValue);
if (replaceOverride) {
@@ -242,6 +267,7 @@ public:
} else if (layer != ConfigVarLayer::Override) {
layer = ConfigVarLayer::Value;
}
notify_if_changed(previous);
}
operator const T&() {
@@ -258,8 +284,10 @@ public:
*/
void setOverrideValue(T newValue) {
checkRegistered();
const auto previous = previous_for_notify();
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override;
notify_if_changed(previous);
}
/**
@@ -273,25 +301,31 @@ public:
void setSpeedrunValue(T newValue) {
checkRegistered();
if (layer != ConfigVarLayer::Override) {
const auto previous = previous_for_notify();
priorLayer = layer;
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Speedrun;
notify_if_changed(previous);
}
}
void clearOverride() {
checkRegistered();
if (layer == ConfigVarLayer::Override) {
const auto previous = previous_for_notify();
overrideValue = {};
layer = ConfigVarLayer::Value;
notify_if_changed(previous);
}
}
void clearSpeedrunOverride() override {
checkRegistered();
if (layer == ConfigVarLayer::Speedrun) {
const auto previous = previous_for_notify();
overrideValue = {};
layer = priorLayer;
notify_if_changed(previous);
}
}
@@ -305,6 +339,48 @@ public:
const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer;
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>;
+2 -1
View File
@@ -5,7 +5,8 @@
#include <dolphin/gx/GXAurora.h>
#include <dolphin/gx/GXExtra.h>
#include "tracy/Tracy.hpp"
#include "profiling.hpp"
#if DUSK_GFX_DEBUG_GROUPS
#define GX_DEBUG_GROUP(name, ...) \
+245
View File
@@ -0,0 +1,245 @@
#pragma once
#include <filesystem>
#include <memory>
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
#include "dusk/config.hpp"
#include "dusk/config_var.hpp"
#include "mods/api.h"
namespace dusk::mods {
struct LoadedMod;
class ModBundle;
} // namespace dusk::mods
struct ModContext {
dusk::mods::LoadedMod* mod = nullptr;
};
namespace dusk::mods::loader {
class NativeModule;
} // namespace dusk::mods::loader
namespace dusk::mods {
struct ModDependencyEdge {
LoadedMod* mod = nullptr;
bool required = false;
};
struct ModManifestInfo {
struct Import {
std::string id;
uint16_t major = 0;
bool required = false;
bool operator==(const Import&) const = default;
};
struct Export {
std::string id;
uint16_t major = 0;
bool operator==(const Export&) const = default;
};
std::vector<Import> imports;
std::vector<Export> exports;
bool operator==(const ModManifestInfo&) const = default;
};
struct ModMetadata {
std::string id;
std::string name;
std::string version;
std::string author;
std::string description;
std::string iconPath;
std::string bannerPath;
};
struct ModSearchDir {
std::filesystem::path path;
// Directory bundles dlopen their native lib in place instead of extracting it to the cache.
// Required where extracted code cannot run (iOS), desirable for signed/read-only installs.
bool inPlaceNative = false;
// Native library location for platforms that restrict placement (e.g. iOS/tvOS Frameworks/)
std::filesystem::path nativeLibDir;
};
struct NativeMod {
std::unique_ptr<loader::NativeModule> handle;
const ModManifest* manifest = nullptr;
ModContext** contextSymbol = nullptr;
ModInitializeFn fn_initialize = nullptr;
ModUpdateFn fn_update = nullptr;
ModShutdownFn fn_shutdown = 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 ships native libraries, but none matches this build's platform and architecture.
*/
ModMissingPlatform,
/**
* Mod is built for a different ABI version than this build of the game.
*/
ApiVersionMismatch,
/**
* Mod is missing a required native API export.
*/
MissingExport,
/**
* Unknown error loading the native mod.
*/
Unknown,
};
struct LoadedMod {
ModMetadata metadata;
std::string modPath;
std::string dir;
uint32_t searchDirIndex = 0;
// Native lib is dlopen'd in place and stays resident for the session. Reload is unsupported.
bool inPlace = false;
std::unique_ptr<ConfigVar<bool> > cvarIsEnabled;
config::Subscription enabledSubscription = 0;
bool active = false;
bool loadFailed = false;
std::string failureReason;
// mod_initialize succeeded; a mod_shutdown is owed on deactivation.
bool initialized = false;
// Static service exports are currently present in the registry.
bool servicesRegistered = false;
// Lifecycle state last applied by the loader; diffed against cvarIsEnabled to pick up
// runtime enable/disable requests.
bool enabledApplied = false;
// Deactivated because a provider it imports from was disabled, not by its own cvar.
bool suspendedByProvider = false;
// Bumped per native lib extraction so every dlopen sees a fresh path (and thus a fresh
// image with fresh statics; a previous dlclose may not fully unmap). Also bumped by
// asset-only reloads, so it doubles as a generation for anything caching per-mod content.
uint32_t cacheGeneration = 0;
// Currently extracted native library, empty if none.
std::string nativePath;
NativeModStatus nativeStatus = NativeModStatus::None;
std::unique_ptr<NativeMod> native;
std::unique_ptr<ModContext> context;
// Shared with overlay file registrations so in-flight DVD reads survive disable/reload.
std::shared_ptr<ModBundle> bundle;
ModManifestInfo manifestInfo;
// Mods this mod imports services from, and mods importing services from this mod.
std::vector<ModDependencyEdge> dependencies;
std::vector<ModDependencyEdge> dependents;
};
class ModLoader {
public:
static ModLoader& instance();
void set_search_dirs(std::vector<ModSearchDir> dirs) { m_searchDirs = std::move(dirs); }
void set_cache_dir(std::filesystem::path dir) { m_cacheDir = std::move(dir); }
void init();
void tick();
void shutdown();
void request_enable(std::string_view id);
void request_disable(std::string_view id);
void request_reload(std::string_view id);
void notify_mod_failure(LoadedMod& mod, bool firstFailure);
[[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:
enum class RequestKind : u8 { Enable, Disable, Reload };
struct Request {
std::string modId;
RequestKind kind;
};
// ModLoader::tick runs inside fapGm_Execute, so code from an unloading mod can still be
// live on the stack (its frame unwinds after the tick). dlclose is therefore deferred to
// the next tick, by which point every per-frame entry into the mod should have returned.
struct RetiredNative {
std::unique_ptr<NativeMod> native;
std::string path;
};
std::vector<std::unique_ptr<LoadedMod> > m_mods;
std::vector<ModSearchDir> m_searchDirs;
std::filesystem::path m_cacheDir;
std::vector<Request> m_pendingRequests;
std::vector<std::string> m_pendingFailures;
std::vector<RetiredNative> m_retiredNatives;
bool m_initialized = false;
bool m_startupComplete = false;
void try_load_mod(const std::filesystem::path& modPath, bool fromDir, uint32_t searchDirIndex);
void load_native(LoadedMod& mod, const std::string& dllEntry);
// Resolved <nativeLibDir>/<mod id><ext> if it exists on disk, empty otherwise.
[[nodiscard]] std::filesystem::path external_native_lib_path(const LoadedMod& mod) const;
void unload_native(LoadedMod& mod);
// Registers exports (if needed), resolves imports and runs mod_initialize.
// Returns whether the mod ended up active; failures go through fail_mod.
bool activate_mod(LoadedMod& mod);
// Runs mod_shutdown (if needed), detaches the mod from every service, and unloads the
// native lib. Must only run with no mod code on the stack (startup, shutdown, or top of tick).
void deactivate_mod(LoadedMod& mod);
void init_services();
bool register_static_service_exports(LoadedMod& mod);
bool resolve_service_imports(LoadedMod& mod);
[[nodiscard]] std::string describe_missing_import(
const char* serviceId, uint16_t majorVersion, uint16_t minMinorVersion) const;
LoadedMod* find_mod(std::string_view id) const;
void drain_retired_natives();
void apply_pending_requests();
void flush_toasts();
void on_enabled_changed(LoadedMod& mod);
// Deactivates `target` (if needed) and its transitive dependents, optionally re-reads the
// bundle from disk, then reactivates whatever the current cvar/provider state allows.
void apply_lifecycle_change(LoadedMod& target, bool reload);
// `target` plus transitive active/suspended dependents, in m_mods (init) order.
std::vector<LoadedMod*> collect_lifecycle_set(LoadedMod& target);
bool reload_bundle(LoadedMod& mod);
bool ensure_native_loaded(LoadedMod& mod);
};
using ModIndex = std::ranges::range_difference_t<decltype(std::declval<ModLoader>().mods())>;
} // namespace dusk::mods
+4 -4
View File
@@ -4,9 +4,9 @@
namespace dusk::mouse {
void read();
void getAimDeltas(float& out_yaw, float& out_pitch);
void getCameraDeltas(float& out_yaw, float& out_pitch);
void get_aim_deltas(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 onFocusLost();
void onFocusGained();
void on_focus_lost();
void on_focus_gained();
} // namespace dusk::mouse
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#if defined(__has_include)
#if __has_include(<tracy/Tracy.hpp>)
#include <tracy/Tracy.hpp>
#endif
#endif
#ifndef ZoneScoped
#define ZoneScoped
#define ZoneScopedN(name)
#endif
+3 -38
View File
@@ -7,7 +7,8 @@
namespace dusk {
using namespace config;
using config::ConfigVar;
using config::ActionBindConfigVar;
enum class BloomMode : int {
Off = 0,
@@ -265,6 +266,7 @@ struct UserSettings {
ConfigVar<bool> fastSpinner;
ConfigVar<MagicArmorMode> armorRupeeDrain;
ConfigVar<bool> invincibleEnemies;
ConfigVar<bool> easyQuickSpin;
// Technical
ConfigVar<bool> restoreWiiGlitches;
@@ -278,8 +280,6 @@ struct UserSettings {
ConfigVar<bool> liveSplitEnabled;
ConfigVar<bool> showSpeedrunRTATimer;
ConfigVar<bool> recordingMode;
// Misc
ConfigVar<bool> removeQuestMapMarkers;
ConfigVar<bool> showInputViewer;
ConfigVar<bool> showInputViewerGyro;
@@ -305,41 +305,6 @@ struct UserSettings {
std::array<ActionBindConfigVar, 4> openDusklightMenu;
std::array<ActionBindConfigVar, 4> turboSpeedButton;
} actionBindings;
// Randomizer seed hashes, 1 per file
struct {
std::array<ConfigVar<std::string>, 3> seedHashes;
} randomizer;
// Archipelago Settings
struct {
std::array<ConfigVar<std::string>, 3> savesServerIP;
std::array<ConfigVar<std::string>, 3> savesServerPass;
std::array<ConfigVar<std::string>, 3> savesSlotName;
} archipelago;
// Cosmetics
struct {
ConfigVar<std::string> herosTunicCapColor;
ConfigVar<std::string> herosTunicTorsoColor;
ConfigVar<std::string> herosTunicSkirtColor;
ConfigVar<std::string> zoraArmorCapColor;
ConfigVar<std::string> zoraArmorHelmetColor;
ConfigVar<std::string> zoraArmorTorsoColor;
ConfigVar<std::string> zoraArmorScalesColor;
ConfigVar<std::string> zoraArmorFlippersColor;
ConfigVar<std::string> lanternGlowColor;
ConfigVar<std::string> woodenSwordColor;
ConfigVar<std::string> msBladeColor;
ConfigVar<std::string> msHandleColor;
ConfigVar<std::string> lightSwordGlowColor;
ConfigVar<std::string> boomerangColor;
ConfigVar<std::string> ironBootsColor;
ConfigVar<std::string> spinnerColor;
ConfigVar<std::string> linkHairColor;
ConfigVar<std::string> wolfLinkColor;
ConfigVar<std::string> eponaColor;
} cosmetics;
};
UserSettings& getSettings();
+5
View File
@@ -1,8 +1,13 @@
#ifndef DUSK_TEXTURE_REPLACEMENTS_HPP
#define DUSK_TEXTURE_REPLACEMENTS_HPP
#include <cstdint>
namespace dusk::texture_replacements {
// Mod replacements are prioritized *over* user replacements (<data folder>/texture_replacements/)
inline constexpr int32_t kUserTextureReplacementPriority = -1'000'000;
void reload();
void set_enabled(bool enabled);
void shutdown();
+1 -1
View File
@@ -36,7 +36,7 @@ typedef struct node_create_request {
/* 0x58 */ s16 name;
/* 0x5C */ void* data;
/* 0x60 */ s16 unk_0x60;
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x64 */ int unk_0x64;
/* 0x68 */ int unk_0x68;
#endif
+13
View File
@@ -114,6 +114,19 @@ inline int __builtin_clz(unsigned int v) {
#endif
// Data symbols exported from the main exe 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 SQUARE(x) ((x) * (x))
+2 -5
View File
@@ -11,13 +11,13 @@ class mDoAud_zelAudio_c : public Z2AudioMgr {
public:
void reset();
mDoAud_zelAudio_c() {
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
setMode(2);
#endif
}
~mDoAud_zelAudio_c() {}
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
u8 getMode() { return field_0x13bd; }
void setMode(u8 mode) { field_0x13bd = mode; }
@@ -398,7 +398,4 @@ inline int mDoAud_monsSeStart(u32 i_soundId, const Vec* i_pos, u32 i_actorId, u3
0);
}
#if TARGET_PC
mDoAud_zelAudio_c& mDoAud_getZelAudio();
#endif
#endif /* M_DO_M_DO_AUDIO_H */
-4
View File
@@ -67,10 +67,6 @@ public:
JKRMemArchive* getArchive() const { return mArchive; }
JKRHeap* getHeap() const { return mHeap; }
#if TARGET_PC
s32 getEntryNumber() const { return mEntryNumber; }
#endif
private:
/* 0x14 */ u8 mMountDirection;
+5
View File
@@ -35,6 +35,11 @@ public:
/* 0x4 */ s8 mNo;
/* 0x5 */ u8 mCount;
#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() {}
#endif
};
+152
View File
@@ -0,0 +1,152 @@
#pragma once
#ifdef __cplusplus
#include <cstddef>
#include <cstdint>
extern "C" {
#else
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#endif
#if defined(_WIN32)
#define MOD_EXPORT __declspec(dllexport)
#else
#define MOD_EXPORT __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
#define MOD_EXTERN_C extern "C"
#else
#define MOD_EXTERN_C
#endif
#define MOD_ABI_VERSION 5u
#define MOD_ERROR_MESSAGE_SIZE 512u
typedef struct ModContext ModContext;
typedef enum ModResult {
MOD_OK = 0,
MOD_ERROR = 1,
MOD_UNAVAILABLE = 2,
MOD_UNSUPPORTED = 3,
MOD_CONFLICT = 4,
MOD_INVALID_ARGUMENT = 5,
} ModResult;
static_assert(sizeof(ModResult) == 4, "mod SDK enums must be int-sized; do not build mods with -fshort-enums");
typedef struct ModError {
uint32_t struct_size;
ModResult code;
char message[MOD_ERROR_MESSAGE_SIZE];
} ModError;
#define MOD_ERROR_INIT {sizeof(ModError), MOD_OK, {0}}
/*
* Opaque per-mod context, populated by the host before mod_initialize is called.
* Pass it as the first argument to every service call; it identifies the calling
* mod for attribution (logging, resource lookup, hook ownership, etc.).
*/
MOD_EXPORT extern ModContext* mod_ctx;
/*
* Service versioning contract:
*
* A service is a struct of function pointers, beginning with a ServiceHeader.
* Compatibility is tracked with a major/minor version pair:
*
* - A major version bump is a breaking change. Different majors are distinct
* services; the registry never matches an import against a different major.
* - A minor version bump may only append fields to the end of the struct.
* Existing fields must keep their offsets and semantics.
*
* Providers: exporting minor N means every function pointer introduced at or
* below N is populated (non-NULL). struct_size reflects the compiled struct.
*
* Importers: importing with min_minor_version N guarantees (enforced at load
* time) that the resolved service is at least minor N, so any field introduced
* at or below N may be used unconditionally, with no availability checks.
* Fields newer than the declared min_minor_version must be gated behind
* SERVICE_HAS plus a NULL check on the pointer itself.
*
* Load ordering: a manifest import of another mod's service (required or
* optional) guarantees that the provider's mod_initialize completed before the
* importer's runs, and deferred services published during the provider's
* initialization resolve into import slots just like static exports. If a
* provider fails to load, mods that required its services fail in turn. Mods
* whose required imports form a cycle all fail to load; a cycle involving an
* optional import is broken by dropping the ordering guarantee (not the
* resolution) of that optional import. Dynamic lookups via
* HostService::get_service carry no ordering guarantee: they see whatever has
* been published at call time.
*/
typedef struct ServiceHeader {
uint32_t struct_size;
uint16_t major_version;
uint16_t minor_version;
} ServiceHeader;
#define SERVICE_HEADER(service_type, major, minor) {sizeof(service_type), (major), (minor)}
#define SERVICE_HAS(service, service_type, field) \
((service) != NULL && \
(service)->header.struct_size >= \
(uint32_t)(offsetof(service_type, field) + sizeof(((service_type*)0)->field)))
typedef enum ServiceImportFlags {
SERVICE_IMPORT_REQUIRED = 0u,
SERVICE_IMPORT_OPTIONAL = 1u << 0u,
} ServiceImportFlags;
typedef enum ServiceExportFlags {
SERVICE_EXPORT_STATIC = 0u,
SERVICE_EXPORT_DEFERRED = 1u << 0u,
} ServiceExportFlags;
typedef struct ServiceImport {
uint32_t struct_size;
const char* service_id;
uint16_t major_version;
uint16_t min_minor_version;
uint32_t flags;
void* slot;
} ServiceImport;
typedef struct ServiceExport {
uint32_t struct_size;
const char* service_id;
uint16_t major_version;
uint16_t minor_version;
uint32_t flags;
const void* service;
} ServiceExport;
typedef struct ModManifest {
uint32_t struct_size;
uint32_t abi_version;
const ServiceImport* imports;
size_t import_count;
const ServiceExport* exports;
size_t export_count;
} ModManifest;
typedef const ModManifest* (*ModGetManifestFn)(void);
typedef ModResult (*ModInitializeFn)(ModError* out_error);
typedef ModResult (*ModUpdateFn)(ModError* out_error);
typedef ModResult (*ModShutdownFn)(ModError* out_error);
MOD_EXPORT const ModManifest* mod_get_manifest(void);
MOD_EXPORT ModResult mod_initialize(ModError* out_error);
MOD_EXPORT ModResult mod_update(ModError* out_error);
MOD_EXPORT ModResult mod_shutdown(ModError* out_error);
#ifdef __cplusplus
}
#endif
+464
View File
@@ -0,0 +1,464 @@
#pragma once
#include "mods/svc/hook.h"
#include <array>
#include <cstddef>
#include <cstring>
#include <memory>
#include <string_view>
#include <type_traits>
namespace dusk::mods {
template <class T>
T arg(void* argsRaw, int n) noexcept {
void** args = static_cast<void**>(argsRaw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T> > >(args[n]);
}
template <class T>
std::remove_reference_t<T>& arg_ref(void* argsRaw, int n) noexcept {
void** args = static_cast<void**>(argsRaw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T> > >(args[n]);
}
template <class F>
void* mfp_addr(F fn) noexcept {
void* p = nullptr;
static_assert(sizeof(fn) >= sizeof(void*), "unexpected function pointer size");
std::memcpy(&p, &fn, sizeof(void*));
return p;
}
/* A string usable as a template argument: carries the hook target's symbol name and
* makes each NamedHook instantiation's static state unique. */
template <size_t N>
struct FixedString {
char chars[N]{};
constexpr FixedString(const char (&s)[N]) noexcept {
for (size_t i = 0; i < N; ++i) {
chars[i] = s[i];
}
}
};
namespace detail {
template <class T>
constexpr std::string_view class_name() {
#if defined(__clang__) || defined(__GNUC__)
// "... class_name() [T = daAlink_c]" / "... [with T = daAlink_c; ...]"
constexpr std::string_view fn = __PRETTY_FUNCTION__;
constexpr size_t start = fn.find("T = ") + 4;
return fn.substr(start, fn.find_first_of(";]", start) - start);
#elif defined(_MSC_VER)
// "... class_name<class daAlink_c>(void)"
constexpr std::string_view fn = __FUNCSIG__;
constexpr size_t start = fn.find("class_name<") + 11;
constexpr std::string_view name = fn.substr(start, fn.rfind(">(") - start);
if constexpr (name.starts_with("class ")) {
return name.substr(6);
} else if constexpr (name.starts_with("struct ")) {
return name.substr(7);
} else {
return name;
}
#else
#error "unsupported compiler"
#endif
}
/* The manifest name of C's vtable. Only unscoped, non-template class names are
* supported (an empty result fails resolution and the install reports it). */
template <class C>
constexpr auto vtable_symbol() {
constexpr std::string_view name = class_name<C>();
constexpr bool simple = name.find_first_of(":<> ") == std::string_view::npos;
// "_ZTV" + decimal length + name / "??_7" + name + "@@6B@", NUL-terminated
std::array<char, name.size() + 12> out{};
if constexpr (!simple) {
return out;
}
size_t n = 0;
#if defined(_WIN32)
for (char c : {'?', '?', '_', '7'}) {
out[n++] = c;
}
for (char c : name) {
out[n++] = c;
}
for (char c : {'@', '@', '6', 'B', '@'}) {
out[n++] = c;
}
#else
for (char c : {'_', 'Z', 'T', 'V'}) {
out[n++] = c;
}
size_t len = name.size();
char digits[8]{};
size_t d = 0;
while (len != 0) {
digits[d++] = static_cast<char>('0' + len % 10);
len /= 10;
}
while (d != 0) {
out[n++] = digits[--d];
}
for (char c : name) {
out[n++] = c;
}
#endif
return out;
}
#if defined(_WIN32)
/* Follow jump stubs, then match the MSVC vcall thunk a virtual mfp points at.
* Returns the vtable slot's byte offset, or npos when fn is not a vcall thunk. */
inline size_t vcall_slot_offset(const void*& fn) noexcept {
constexpr size_t npos = static_cast<size_t>(-1);
#if defined(_M_X64) || defined(__x86_64__)
const auto* p = static_cast<const uint8_t*>(fn);
for (int i = 0; i < 8 && p[0] == 0xE9; ++i) { // incremental-link stubs
int32_t rel;
std::memcpy(&rel, p + 1, 4);
p += 5 + rel;
}
fn = p;
// The vptr load. Unoptimized clang-cl thunks spill/reload rcx first
// (push rax; mov [rsp], rcx; mov rcx, [rsp]), so scan a short window.
const uint8_t* q = nullptr;
for (int i = 0; i <= 12; ++i) {
if (p[i] == 0x48 && p[i + 1] == 0x8B && p[i + 2] == 0x01) { // mov rax, [rcx]
q = p + i + 3;
break;
}
}
if (q == nullptr) {
return npos;
}
if (q[0] == 0xFF && q[1] == 0x20) { // jmp [rax] (MSVC)
return 0;
}
if (q[0] == 0xFF && q[1] == 0x60) { // jmp [rax + imm8]
return static_cast<int8_t>(q[2]);
}
if (q[0] == 0xFF && q[1] == 0xA0) { // jmp [rax + imm32]
int32_t off;
std::memcpy(&off, q + 2, 4);
return off;
}
// clang-cl: mov rax, [rax + off]; (pop r10;) jmp rax. Requiring the jmp rax
// distinguishes the thunk from an ordinary getter that begins the same way.
if (q[0] == 0x48 && q[1] == 0x8B && (q[2] == 0x00 || q[2] == 0x40 || q[2] == 0x80)) {
size_t off = 0;
const uint8_t* r = q + 3;
if (q[2] == 0x40) {
off = static_cast<int8_t>(q[3]);
r = q + 4;
} else if (q[2] == 0x80) {
int32_t off32;
std::memcpy(&off32, q + 3, 4);
off = off32;
r = q + 7;
}
for (int i = 0; i <= 8; ++i) {
if (r[i] == 0xFF && r[i + 1] == 0xE0) { // jmp rax (48 REX optional)
return off;
}
}
}
return npos;
#elif defined(_M_ARM64) || defined(__aarch64__)
const auto* p = static_cast<const uint8_t*>(fn);
uint32_t insn[3];
for (int i = 0; i < 8; ++i) { // incremental-link `b` stubs
std::memcpy(insn, p, 4);
if ((insn[0] & 0xFC000000u) != 0x14000000u) {
break;
}
const auto imm26 = static_cast<int32_t>(insn[0] << 6) >> 6;
p += static_cast<intptr_t>(imm26) * 4;
}
fn = p;
std::memcpy(insn, p, 12);
// ldr Xt, [x0]; ldr Xs, [Xt, #imm12*8]; br Xs
if ((insn[0] & 0xFFFFFFE0u) != 0xF9400000u) {
return npos;
}
const uint32_t t = insn[0] & 0x1Fu;
if ((insn[1] & 0xFFC003E0u) != (0xF9400000u | (t << 5))) {
return npos;
}
const uint32_t s = insn[1] & 0x1Fu;
if (insn[2] != (0xD61F0000u | (s << 5))) {
return npos;
}
return ((insn[1] >> 10) & 0xFFFu) * 8;
#else
(void)fn;
return npos;
#endif
}
#endif
/* Code address of the member function a mfp designates. Virtual mfps don't carry
* one; recover it from the class's vtable (resolved from the symbol manifest), so
* Hook works uniformly on virtual and non-virtual members. */
template <class C, class F>
ModResult member_target(const HookService* hooks, F mfp, void** out) {
*out = nullptr;
uintptr_t words[sizeof(F) > sizeof(uintptr_t) ? 2 : 1] = {};
std::memcpy(words, &mfp, sizeof(words) < sizeof(F) ? sizeof(words) : sizeof(F));
#if defined(_WIN32)
const void* fn = reinterpret_cast<const void*>(words[0]);
const size_t slot = vcall_slot_offset(fn);
if (slot == static_cast<size_t>(-1)) { // not a vcall thunk: direct address
*out = const_cast<void*>(fn);
return MOD_OK;
}
void* vtable = nullptr;
const ModResult resolved = hooks->resolve(mod_ctx, vtable_symbol<C>().data(), &vtable, nullptr);
if (resolved != MOD_OK) {
return resolved;
}
// ??_7 points at the first slot.
*out = *reinterpret_cast<void**>(static_cast<char*>(vtable) + slot);
#else
#if defined(__aarch64__) || defined(__arm__)
// AAPCS C++ ABI: the virtual flag is bit 0 of the adjustment word (function
// addresses can't spare their low bit), and ptr holds the slot offset directly.
const bool isVirtual = (words[1] & 1) != 0;
const uintptr_t thisAdjust = words[1] >> 1;
const uintptr_t slotOffset = words[0];
#else
// Itanium C++ ABI: virtual mfps set bit 0 of ptr; the slot offset is ptr - 1.
const bool isVirtual = (words[0] & 1) != 0;
const uintptr_t thisAdjust = words[1];
const uintptr_t slotOffset = words[0] - 1;
#endif
if (!isVirtual) { // non-virtual: the address itself
*out = reinterpret_cast<void*>(words[0]);
return MOD_OK;
}
if (thisAdjust != 0) {
// this-adjusting mfp (member of a secondary base): the slot offset is
// relative to a vtable we can't locate. Hook the overrider by name instead.
return MOD_UNSUPPORTED;
}
void* vtable = nullptr;
const ModResult resolved = hooks->resolve(mod_ctx, vtable_symbol<C>().data(), &vtable, nullptr);
if (resolved != MOD_OK) {
return resolved;
}
// _ZTV points at the offset-to-top slot; the address point mfps index from is
// two pointers in (past offset-to-top and the typeinfo pointer).
*out = *reinterpret_cast<void**>(static_cast<char*>(vtable) + 2 * sizeof(void*) + slotOffset);
#endif
return *out != nullptr ? MOD_OK : MOD_UNAVAILABLE;
}
} // namespace detail
/* Trampoline generator + per-target state shared by Hook and NamedHook. Tag makes
* each hooked target's statics distinct; the target address is filled in at install. */
template <class Tag, class R, class... A>
struct HookImpl {
static inline R (*g_orig)(A...) = nullptr;
static inline const HookService* hooks = nullptr;
static inline void* target = nullptr;
static bool dispatch_pre(void* args, void* retval) {
if (hooks == nullptr) {
return false;
}
int skipOriginal = 0;
const ModResult result = hooks->dispatch_pre(mod_ctx, target, args, retval, &skipOriginal);
return result == MOD_OK && skipOriginal != 0;
}
static void dispatch_post(void* args, void* retval) {
if (hooks != nullptr) {
hooks->dispatch_post(mod_ctx, target, args, retval);
}
}
static R trampoline(A... args) {
if constexpr (sizeof...(A) == 0) {
if constexpr (std::is_void_v<R>) {
const bool skipOriginal = dispatch_pre(nullptr, nullptr);
if (!skipOriginal) {
g_orig(args...);
}
dispatch_post(nullptr, nullptr);
} else {
R result{};
const bool skipOriginal =
dispatch_pre(nullptr, static_cast<void*>(std::addressof(result)));
if (!skipOriginal) {
result = g_orig(args...);
}
dispatch_post(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 skipOriginal = dispatch_pre(static_cast<void*>(ptrs), nullptr);
if (!skipOriginal) {
g_orig(args...);
}
dispatch_post(static_cast<void*>(ptrs), nullptr);
} else {
R result{};
const bool skipOriginal = dispatch_pre(
static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
if (!skipOriginal) {
result = g_orig(args...);
}
dispatch_post(static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
return result;
}
}
}
};
namespace detail {
template <auto Target>
using TargetTag = std::integral_constant<decltype(Target), Target>;
template <FixedString Name>
struct NameTag {};
} // namespace detail
/*
* Typed hook on a function named at compile time (&daAlink_c::execute, &free_fn).
* Member functions may be virtual: the install decodes the member function pointer and hooks the
* class's own overrider.
*/
template <auto Target>
struct Hook;
template <class C, class R, class... A, R (C::*Target)(A...)>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, C*, A...> {
static ModResult resolve_target(const HookService* hooks, void** out) {
return detail::member_target<C>(hooks, Target, out);
}
};
template <class C, class R, class... A, R (C::*Target)(A...) const>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, const C*, A...> {
static ModResult resolve_target(const HookService* hooks, void** out) {
return detail::member_target<C>(hooks, Target, out);
}
};
template <class R, class... A, R (*Target)(A...)>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, A...> {
static ModResult resolve_target(const HookService*, void** out) {
*out = mfp_addr(Target);
return MOD_OK;
}
};
/*
* Typed hook on a function by its symbol name, for targets you can't name in C++: file-local
* statics, private members, or symbols without a header. The signature is written free-style with
* the receiver first and is *not* compiler-checked.
*
* using HookshotHit = dusk::mods::NamedHook<
* "daAlink_hookshotAtHitCallBack",
* void(fopAc_ac_c*, dCcD_GObjInf*, fopAc_ac_c*, dCcD_GObjInf*)>;
* dusk::mods::hook_add_pre<HookshotHit>(svc_hook, on_hookshot_hit);
*/
template <FixedString Name, class Sig>
struct NamedHook;
template <FixedString Name, class R, class... A>
struct NamedHook<Name, R(A...)> : HookImpl<detail::NameTag<Name>, R, A...> {
static ModResult resolve_target(const HookService* hooks, void** out) {
HookSymbolFlags flags{};
const ModResult resolved = hooks->resolve(mod_ctx, Name.chars, out, &flags);
if (resolved == MOD_OK && (flags & HOOK_SYMBOL_CODE) == 0) {
*out = nullptr;
return MOD_INVALID_ARGUMENT;
}
return resolved;
}
};
template <class Entry>
ModResult hook_install(const HookService* hooks) {
if (hooks == nullptr) {
return MOD_UNAVAILABLE;
}
Entry::hooks = hooks;
if (Entry::target == nullptr) {
const ModResult resolved = Entry::resolve_target(hooks, &Entry::target);
if (resolved != MOD_OK) {
return resolved;
}
}
return hooks->install(mod_ctx, Entry::target, reinterpret_cast<void*>(Entry::trampoline),
reinterpret_cast<void**>(&Entry::g_orig));
}
template <auto Target>
ModResult hook_install(const HookService* hooks) {
return hook_install<Hook<Target> >(hooks);
}
template <class Entry>
ModResult hook_add_pre(
const HookService* hooks, HookPreFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->add_pre(mod_ctx, Entry::target, callback, options);
}
template <auto Target>
ModResult hook_add_pre(
const HookService* hooks, HookPreFn callback, const HookOptions* options = nullptr) {
return hook_add_pre<Hook<Target> >(hooks, callback, options);
}
template <class Entry>
ModResult hook_add_post(
const HookService* hooks, HookPostFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->add_post(mod_ctx, Entry::target, callback, options);
}
template <auto Target>
ModResult hook_add_post(
const HookService* hooks, HookPostFn callback, const HookOptions* options = nullptr) {
return hook_add_post<Hook<Target> >(hooks, callback, options);
}
template <class Entry>
ModResult hook_replace(
const HookService* hooks, HookReplaceFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->replace(mod_ctx, Entry::target, callback, options);
}
template <auto Target>
ModResult hook_replace(
const HookService* hooks, HookReplaceFn callback, const HookOptions* options = nullptr) {
return hook_replace<Hook<Target> >(hooks, callback, options);
}
} // namespace dusk::mods
+138
View File
@@ -0,0 +1,138 @@
#pragma once
#include "mods/api.h"
#include <cstdint>
#include <cstdio>
#include <type_traits>
#include <vector>
namespace dusk::mods {
template <class Service>
struct ServiceTraits;
namespace detail {
inline std::vector<ServiceImport>& imports() {
static std::vector<ServiceImport> entries;
return entries;
}
inline std::vector<ServiceExport>& exports() {
static std::vector<ServiceExport> entries;
return entries;
}
inline int register_import(ServiceImport entry) {
imports().push_back(entry);
return 0;
}
inline int register_export(ServiceExport entry) {
exports().push_back(entry);
return 0;
}
inline const ModManifest* manifest() {
static ModManifest manifest{
sizeof(ModManifest),
MOD_ABI_VERSION,
nullptr,
0,
nullptr,
0,
};
auto& importEntries = imports();
auto& exportEntries = exports();
manifest.imports = importEntries.data();
manifest.import_count = importEntries.size();
manifest.exports = exportEntries.data();
manifest.export_count = exportEntries.size();
return &manifest;
}
} // namespace detail
inline ModResult set_error(ModError* outError, ModResult code, const char* message) {
if (outError != nullptr && outError->struct_size >= sizeof(ModError)) {
outError->code = code;
outError->message[0] = '\0';
if (message != nullptr) {
std::snprintf(outError->message, sizeof(outError->message), "%s", message);
}
}
return code;
}
} // namespace dusk::mods
#define DEFINE_MOD() \
extern "C" { \
MOD_EXPORT ModContext* mod_ctx = nullptr; \
MOD_EXPORT const ModManifest* mod_get_manifest(void) { \
return ::dusk::mods::detail::manifest(); \
} \
}
// Declares `static const service_type* variable`, filled in by the host before mod_initialize.
// Required imports are guaranteed non-null (the mod fails to load otherwise); optional imports
// must be checked against nullptr before use.
#define IMPORT_SERVICE_EX( \
service_type, variable, service_id_value, major_value, min_minor_value, flags_value) \
static const service_type* variable = nullptr; \
[[maybe_unused]] static const int mod_import_registration_##variable = \
::dusk::mods::detail::register_import(ServiceImport{ \
sizeof(ServiceImport), \
(service_id_value), \
static_cast<uint16_t>(major_value), \
static_cast<uint16_t>(min_minor_value), \
static_cast<uint32_t>(flags_value), \
&(variable), \
})
#define IMPORT_SERVICE_VERSION(service_type, variable, min_minor_value) \
IMPORT_SERVICE_EX(service_type, variable, ::dusk::mods::ServiceTraits<service_type>::id, \
::dusk::mods::ServiceTraits<service_type>::major_version, min_minor_value, \
SERVICE_IMPORT_REQUIRED)
#define IMPORT_SERVICE(service_type, variable) IMPORT_SERVICE_VERSION(service_type, variable, 0)
#define IMPORT_OPTIONAL_SERVICE_VERSION(service_type, variable, min_minor_value) \
IMPORT_SERVICE_EX(service_type, variable, ::dusk::mods::ServiceTraits<service_type>::id, \
::dusk::mods::ServiceTraits<service_type>::major_version, min_minor_value, \
SERVICE_IMPORT_OPTIONAL)
#define IMPORT_OPTIONAL_SERVICE(service_type, variable) \
IMPORT_OPTIONAL_SERVICE_VERSION(service_type, variable, 0)
#define EXPORT_SERVICE_AS(instance, service_id_value) \
namespace { \
const int mod_export_registration_##instance = \
::dusk::mods::detail::register_export(ServiceExport{ \
sizeof(ServiceExport), \
(service_id_value), \
(instance).header.major_version, \
(instance).header.minor_version, \
SERVICE_EXPORT_STATIC, \
&(instance), \
}); \
}
#define EXPORT_SERVICE(instance) \
EXPORT_SERVICE_AS( \
instance, ::dusk::mods::ServiceTraits<std::remove_cv_t<decltype(instance)> >::id)
#define EXPORT_DEFERRED_SERVICE(token, service_id_value, major_value, minor_value) \
namespace { \
const int mod_deferred_export_registration_##token = \
::dusk::mods::detail::register_export(ServiceExport{ \
sizeof(ServiceExport), \
(service_id_value), \
static_cast<uint16_t>(major_value), \
static_cast<uint16_t>(minor_value), \
SERVICE_EXPORT_DEFERRED, \
nullptr, \
}); \
}
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include "mods/api.h"
#define CONFIG_SERVICE_ID "dev.twilitrealm.dusklight.config"
#define CONFIG_SERVICE_MAJOR 1u
#define CONFIG_SERVICE_MINOR 0u
/* Handle for a config var registered by the calling mod. 0 is never a valid handle. */
typedef uint64_t ConfigVarHandle;
/* Handle for a change subscription. 0 is never a valid handle. */
typedef uint64_t ConfigSubscriptionHandle;
typedef enum ConfigVarType {
CONFIG_VAR_BOOL = 0, /* bool */
CONFIG_VAR_INT = 1, /* int64_t */
CONFIG_VAR_FLOAT = 2, /* double */
CONFIG_VAR_STRING = 3, /* UTF-8 */
} ConfigVarType;
typedef struct ConfigVarDesc {
uint32_t struct_size;
/* Name fragment: 1-64 characters from [A-Za-z0-9_-]. The full config key is
* "mod.<escaped mod id>.<name>", persisted in config.json alongside host settings.
* "enabled" is reserved by the loader. */
const char* name;
ConfigVarType type;
/* Default value; only the field matching `type` is read. */
bool default_bool;
int64_t default_int;
double default_float;
const char* default_string; /* NULL means "" */
} ConfigVarDesc;
#define CONFIG_VAR_DESC_INIT {sizeof(ConfigVarDesc), NULL, CONFIG_VAR_BOOL, false, 0, 0.0, NULL}
/* Snapshot of a var's value; only the field matching `type` is meaningful. */
typedef struct ConfigVarValue {
uint32_t struct_size;
ConfigVarType type;
bool bool_value;
int64_t int_value;
double float_value;
const char* string_value; /* NUL-terminated; NULL for non-string vars */
size_t string_length; /* excludes the NUL */
} ConfigVarValue;
/*
* Fired on the game thread whenever the var's effective value changes at runtime: the calling mod's
* own set_* calls and any other runtime writer. Writes that leave the value unchanged do not fire,
* and neither do values applied from config.json or --cvar during registration. `value` holds the
* new (current) value and `previous` the one it replaced; both snapshots are valid only for the
* duration of the call (copy string_value if you need to keep it). Setting the same var from inside
* its own callback applies the write but is not re-notified.
*/
typedef void (*ConfigChangedFn)(ModContext* ctx, ConfigVarHandle var, const ConfigVarValue* value,
const ConfigVarValue* previous, void* user_data);
/*
* Scoped configuration variables.
*
* Registrations are owned by the calling mod and removed automatically (subscriptions included)
* when it is disabled, reloaded, or fails. Values are saved to config.json. Writes are debounced,
* not flushed per set.
*/
typedef struct ConfigService {
ServiceHeader header;
/* Register a config var. If a value for the full key was saved earlier (or set via --cvar),
* it takes effect immediately; otherwise the var starts at the default. Registering a name
* that is already live is MOD_CONFLICT. */
ModResult (*register_var)(
ModContext* ctx, const ConfigVarDesc* desc, ConfigVarHandle* out_handle);
/* Unregister a var previously registered by the calling mod. Its persisted value is kept. */
ModResult (*unregister_var)(ModContext* ctx, ConfigVarHandle var);
/* Typed accessors; the type must match the registration (MOD_INVALID_ARGUMENT otherwise). */
ModResult (*get_bool)(ModContext* ctx, ConfigVarHandle var, bool* out_value);
ModResult (*set_bool)(ModContext* ctx, ConfigVarHandle var, bool value);
ModResult (*get_int)(ModContext* ctx, ConfigVarHandle var, int64_t* out_value);
ModResult (*set_int)(ModContext* ctx, ConfigVarHandle var, int64_t value);
ModResult (*get_float)(ModContext* ctx, ConfigVarHandle var, double* out_value);
ModResult (*set_float)(ModContext* ctx, ConfigVarHandle var, double value);
/* Copies the NUL-terminated value into buffer. out_length (optional) receives the full
* length excluding the NUL regardless of buffer size; call with buffer == NULL and
* buffer_size == 0 to query the length. A non-NULL buffer that is too small fails with
* MOD_INVALID_ARGUMENT and writes nothing. */
ModResult (*get_string)(
ModContext* ctx, ConfigVarHandle var, char* buffer, size_t buffer_size, size_t* out_length);
ModResult (*set_string)(ModContext* ctx, ConfigVarHandle var, const char* value);
/* Subscribe to changes of a var registered by the calling mod. out_handle may be NULL if
* the subscription is never removed manually (cleanup on mod teardown is automatic). */
ModResult (*subscribe)(ModContext* ctx, ConfigVarHandle var, ConfigChangedFn callback,
void* user_data, ConfigSubscriptionHandle* out_handle);
ModResult (*unsubscribe)(ModContext* ctx, ConfigSubscriptionHandle handle);
} ConfigService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<ConfigService> {
static constexpr const char* id = CONFIG_SERVICE_ID;
static constexpr uint16_t major_version = CONFIG_SERVICE_MAJOR;
};
#endif
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "mods/api.h"
/*
* Mods that link or hook game code directly must import this service; service-only and asset-only
* mods must not.
*
* Major version is the game-code ABI epoch: it is bumped when game-visible struct or vtable layouts
* change incompatibly (e.g. a TARGET_PC field added to an existing game struct). The loader's
* ordinary version check then fails mods built against the old epoch with a clear message instead
* of letting them corrupt memory.
*/
#define GAME_SERVICE_ID "dev.twilitrealm.dusklight.game"
#define GAME_SERVICE_MAJOR 1u
#define GAME_SERVICE_MINOR 0u
typedef struct GameService {
ServiceHeader header;
} GameService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<GameService> {
static constexpr const char* id = GAME_SERVICE_ID;
static constexpr uint16_t major_version = GAME_SERVICE_MAJOR;
};
#endif
+125
View File
@@ -0,0 +1,125 @@
#pragma once
#include "mods/api.h"
/*
* Intercept game functions by address. Prefer the typed helpers in mods/hook.hpp
* (hook_add_pre/hook_add_post/hook_replace over a &Class::method): they generate the
* trampoline and hide install/dispatch, which are the low-level primitives those helpers
* build. resolve() maps a symbol name to an address for targets you can't name at compile time
* (file-local statics included).
*
* Every call is game-thread-only. Install and removal must run with no hooked function on the
* stack; the loader guarantees this by applying mod lifecycle changes between frames, which is
* why hooking a function that never returns (the outermost loop) makes a mod un-unloadable.
*/
#define HOOK_SERVICE_ID "dev.twilitrealm.dusklight.hook"
#define HOOK_SERVICE_MAJOR 1u
#define HOOK_SERVICE_MINOR 0u
/* Symbol flags reported by resolve() */
typedef enum HookSymbolFlags {
HOOK_SYMBOL_CODE = 1u << 0u,
HOOK_SYMBOL_DATA = 1u << 1u,
/* Not exported/dynamically visible: hookable, but never linkable. */
HOOK_SYMBOL_LOCAL = 1u << 2u,
/* Other names share this address (ICF fold/alias): a hook intercepts them all. */
HOOK_SYMBOL_MULTI_NAME = 1u << 3u,
/* Resolved through a demangled display-name alias rather than the real symbol. */
HOOK_SYMBOL_DISPLAY = 1u << 6u,
} HookSymbolFlags;
/* A pre-hook's return value: whether to run the original function. */
typedef enum HookAction {
HOOK_CONTINUE = 0, /* run the original (and any lower-priority pre-hooks) */
HOOK_SKIP_ORIGINAL = 1, /* cancel the original and remaining pre-hooks; post-hooks still run */
} HookAction;
/* How replace resolves a second replace-hook on a target that already has one. */
typedef enum HookReplacePolicy {
HOOK_REPLACE_CONFLICT = 0, /* refuse with MOD_CONFLICT (the default) */
HOOK_REPLACE_PRIORITY = 1, /* take over only if this options.priority is strictly higher */
HOOK_REPLACE_OVERRIDE = 2, /* take over unconditionally */
} HookReplacePolicy;
/*
* Hook callbacks. `args` is an array of pointers to the call's arguments (index 0 is `this`
* for member functions); `retval` points at the return slot (NULL for void). Read and write
* them through dusk::mods::arg<T> / arg_ref<T> from mods/hook.hpp. `userdata` is the pointer
* from HookOptions. All run on the game thread, in the hooked call's own stack frame.
*/
typedef HookAction (*HookPreFn)(ModContext* ctx, void* args, void* retval, void* userdata);
typedef void (*HookPostFn)(ModContext* ctx, void* args, void* retval, void* userdata);
typedef void (*HookReplaceFn)(ModContext* ctx, void* args, void* retval, void* userdata);
typedef struct HookOptions {
uint32_t struct_size;
/* Higher runs first; ties break by registration order. Applies to pre/post ordering and,
* with HOOK_REPLACE_PRIORITY, to replace-hook takeover. */
int32_t priority;
HookReplacePolicy replace_policy;
void* userdata; /* passed back to the callback */
} HookOptions;
#define HOOK_OPTIONS_INIT {sizeof(HookOptions), 0, HOOK_REPLACE_CONFLICT, NULL}
typedef struct HookService {
ServiceHeader header;
/*
* Install a trampoline detour on fn_addr and return the address to call the original through in
* *out_original_fn. The typed helpers generate the trampoline and call this; mods normally
* don't. The first mod to install a given target owns the live detour; later mods register as
* candidates so a hook survives the owner unloading (the detour is handed off and every
* original pointer is rewritten). Idempotent per (mod, out slot).
*/
ModResult (*install)(
ModContext* ctx, void* fn_addr, void* trampoline_fn, void** out_original_fn);
/*
* Register a callback on an already-installed target. Pre runs before the original (and can
* cancel it), post runs after (even if cancelled). Any number of mods may add pre/post to the
* same target; they run in priority then registration order. replace installs a single
* substitute for the original, managed by options.replace_policy, MOD_CONFLICT if refused.
*/
ModResult (*add_pre)(
ModContext* ctx, void* fn_addr, HookPreFn callback, const HookOptions* options);
ModResult (*add_post)(
ModContext* ctx, void* fn_addr, HookPostFn callback, const HookOptions* options);
ModResult (*replace)(
ModContext* ctx, void* fn_addr, HookReplaceFn callback, const HookOptions* options);
/*
* Run the registered callbacks for a target. The generated trampoline calls these; they
* are not a mod-facing entry point. dispatch_pre reports through *out_skip_original
* whether the original should be skipped (a pre-hook returned HOOK_SKIP_ORIGINAL, or a
* replace-hook ran).
*/
ModResult (*dispatch_pre)(
ModContext* ctx, void* fn_addr, void* args, void* retval, int* out_skip_original);
ModResult (*dispatch_post)(ModContext* ctx, void* fn_addr, void* args, void* retval);
/*
* Resolve a game symbol by name from the symbol manifest, including non-exported (static)
* functions. Names can be either the platform's mangled name (i.e. the name passed to dlopen;
* no Mach-O leading underscore) or the qualified function name without parameters (e.g.
* "daAlink_c::execute"). out_flags (optional) receives HookSymbolFlags.
*
* Results: MOD_OK; MOD_UNSUPPORTED (no manifest for this build, missing or stale);
* MOD_UNAVAILABLE (symbol not found); MOD_CONFLICT (name maps to more than one address: C++
* overloads or per-TU statics; use the mangled name).
*/
ModResult (*resolve)(
ModContext* ctx, const char* symbol, void** out_addr, HookSymbolFlags* out_flags);
} HookService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<HookService> {
static constexpr const char* id = HOOK_SERVICE_ID;
static constexpr uint16_t major_version = HOOK_SERVICE_MAJOR;
};
#endif
+104
View File
@@ -0,0 +1,104 @@
#pragma once
#include "mods/api.h"
/*
* The host service: the calling mod's identity and its runtime interface to the loader.
* Always available; every other service can be reached from it.
*/
#define HOST_SERVICE_ID "dev.twilitrealm.dusklight.host"
#define HOST_SERVICE_MAJOR 2u
#define HOST_SERVICE_MINOR 0u
/*
* Ignore unknown values: later service minors may add events.
*/
typedef enum ModLifecycleEvent {
/*
* The subject mod is gone: its mod_shutdown has run (when it initialized at all) and
* every service has already dropped the state it held for it. The subject's library is
* still mapped, so pointers into it are valid to compare against, but they must not be
* called or dereferenced after the callback returns. Drop everything keyed to the
* mod: callbacks it registered, its ModContext*, state indexed by it.
*/
MOD_LIFECYCLE_DETACHED = 0,
} ModLifecycleEvent;
/*
* ctx is the watching mod's own context; subject identifies the mod the event is about.
* subject_id is valid only for the duration of the call.
*/
typedef void (*ModLifecycleFn)(ModContext* ctx, ModContext* subject, const char* subject_id,
ModLifecycleEvent event, void* user_data);
typedef struct HostService {
ServiceHeader header;
/* Version string of the current Dusklight build. (e.g. "1.4.2") */
const char* version;
/* Build id of the running game binary: PDB GUID+age on Windows, LC_UUID on macOS, GNU build-id
* on Linux. May be empty (len 0) if the identity could not be determined. */
const uint8_t* build_id;
uint32_t build_id_len;
/*
* Look up a service by id at call time. Unlike a manifest import, this sees whatever is
* currently published and carries no initialization-order guarantee (see mods/api.h).
* MOD_UNAVAILABLE if no matching service is published; *out_service is null on failure.
*/
ModResult (*get_service)(ModContext* ctx, const char* service_id, uint16_t major_version,
uint16_t min_minor_version, const void** out_service);
/*
* Publish a service the calling mod declared as a deferred export in its manifest.
* Must happen during mod_initialize so importers can resolve it; `service` must stay
* valid until the mod shuts down.
*/
ModResult (*publish_service)(
ModContext* ctx, const char* service_id, uint16_t major_version, const void* service);
/*
* Report an unrecoverable failure. The calling mod's services stop resolving immediately
* and the loader fully disables it at the next safe point; `message` is shown to the user.
* Safe to call from any mod callback.
*/
void (*fail)(ModContext* ctx, ModResult code, const char* message);
/*
* The calling mod's manifest metadata. Returned strings remain valid while the mod is
* loaded.
*/
const char* (*mod_id)(ModContext* ctx);
const char* (*mod_name)(ModContext* ctx);
const char* (*mod_version)(ModContext* ctx);
/*
* A writable scratch directory reserved for the calling mod. Contents survive disable
* and reload within a session, but the directory is wiped at game startup.
*/
const char* (*mod_dir)(ModContext* ctx);
/*
* Observe other mods' lifecycle events. Any mod whose service hands out per-caller state
* (registrations, callbacks, handles) should watch for MOD_LIFECYCLE_DETACHED and drop what it
* holds for the subject.
*
* Callbacks fire on the game thread at a lifecycle safe point (never mid-frame), for
* every mod but the watcher itself (use mod_shutdown for self-cleanup).
*/
ModResult (*watch_mod_lifecycle)(
ModContext* ctx, ModLifecycleFn fn, void* user_data, uint64_t* out_handle);
ModResult (*unwatch_mod_lifecycle)(ModContext* ctx, uint64_t handle);
} HostService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<HostService> {
static constexpr const char* id = HOST_SERVICE_ID;
static constexpr uint16_t major_version = HOST_SERVICE_MAJOR;
};
#endif
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include "mods/api.h"
/*
* Logging into the game's console and log files. Messages are attributed to the calling mod
* (prefixed with its ID).
*/
#define LOG_SERVICE_ID "dev.twilitrealm.dusklight.log"
#define LOG_SERVICE_MAJOR 1u
#define LOG_SERVICE_MINOR 0u
typedef enum LogLevel {
LOG_LEVEL_TRACE = 0,
LOG_LEVEL_DEBUG = 1,
LOG_LEVEL_INFO = 2,
LOG_LEVEL_WARN = 3,
LOG_LEVEL_ERROR = 4,
} LogLevel;
typedef struct LogService {
ServiceHeader header;
/*
* Write a log message at the given level.
* `message` is a plain UTF-8 string and is copied before returning.
*/
void (*write)(ModContext* ctx, LogLevel level, const char* message);
/* Per-level shorthands for write. */
void (*trace)(ModContext* ctx, const char* message);
void (*debug)(ModContext* ctx, const char* message);
void (*info)(ModContext* ctx, const char* message);
void (*warn)(ModContext* ctx, const char* message);
void (*error)(ModContext* ctx, const char* message);
} LogService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<LogService> {
static constexpr const char* id = LOG_SERVICE_ID;
static constexpr uint16_t major_version = LOG_SERVICE_MAJOR;
};
#endif
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include "mods/api.h"
#define OVERLAY_SERVICE_ID "dev.twilitrealm.dusklight.overlay"
#define OVERLAY_SERVICE_MAJOR 1u
#define OVERLAY_SERVICE_MINOR 0u
/* Handle for a runtime overlay registration. 0 is never a valid handle. */
typedef uint64_t OverlayHandle;
/*
* Runtime DVD file overlays.
*
* Registrations are owned by the calling mod and removed automatically when it is disabled,
* reloaded, or fails. Changes are applied at the next frame boundary; data the game has already
* read stays in memory until it re-reads the file (sometimes on scene reload, sometimes on
* restart).
*
* disc_path names the file to overlay: absolute with a leading '/', matched against the disc
* case-insensitively (e.g. "/res/Stage/R04_00.arc"). Paths that do not exist on the disc are added
* as new files.
*
* If multiple sources overlay the same path, the last one wins: a mod's runtime registrations beat
* its static overlay/ files, and later-loaded mods beat earlier ones.
*/
typedef struct OverlayService {
ServiceHeader header;
/*
* Overlay disc_path with a file from the calling mod's bundle (bundle-relative path, e.g.
* "res/replacement.arc"). The file's contents are read lazily on each open, so the bundle
* file must not change size while registered.
*/
ModResult (*add_file)(
ModContext* ctx, const char* disc_path, const char* bundle_path, OverlayHandle* out_handle);
/*
* Overlay disc_path with a caller-owned buffer. The data is copied; the caller may free it
* as soon as this returns.
*/
ModResult (*add_buffer)(ModContext* ctx, const char* disc_path, const void* data, size_t size,
OverlayHandle* out_handle);
/* Remove a runtime overlay previously added by the calling mod. */
ModResult (*remove)(ModContext* ctx, OverlayHandle handle);
} OverlayService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<OverlayService> {
static constexpr const char* id = OVERLAY_SERVICE_ID;
static constexpr uint16_t major_version = OVERLAY_SERVICE_MAJOR;
};
#endif
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include "mods/api.h"
/*
* Read-only access to the res/ tree of the calling mod's own bundle. Reload serves the new
* bundle's contents. For writable storage, use HostService::mod_dir.
*/
#define RESOURCE_SERVICE_ID "dev.twilitrealm.dusklight.resource"
#define RESOURCE_SERVICE_MAJOR 1u
#define RESOURCE_SERVICE_MINOR 0u
/*
* A loaded resource, allocated by the service. Return every successful load with free;
* buffers still live when the mod is disabled or reloaded are reclaimed with a warning.
*/
typedef struct ResourceBuffer {
uint32_t struct_size;
void* data;
size_t size;
} ResourceBuffer;
#define RESOURCE_BUFFER_INIT {sizeof(ResourceBuffer), NULL, 0u}
typedef struct ResourceService {
ServiceHeader header;
/*
* Load a file into a fresh allocation. `relative_path` is resolved against the bundle's
* res/ directory. Absolute paths and ".." are rejected. MOD_UNAVAILABLE if the file does not
* exist. An empty file loads as data == NULL with size 0. Previous contents of `out_buffer` are
* overwritten, not freed.
*/
ModResult (*load)(ModContext* ctx, const char* relative_path, ResourceBuffer* out_buffer);
/*
* Release a loaded buffer and reset it to the empty state. Safe to call on an empty or
* already-freed buffer.
*/
void (*free)(ModContext* ctx, ResourceBuffer* buffer);
} ResourceService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<ResourceService> {
static constexpr const char* id = RESOURCE_SERVICE_ID;
static constexpr uint16_t major_version = RESOURCE_SERVICE_MAJOR;
};
#endif
+88
View File
@@ -0,0 +1,88 @@
#pragma once
#include "mods/api.h"
#define TEXTURE_SERVICE_ID "dev.twilitrealm.dusklight.texture"
#define TEXTURE_SERVICE_MAJOR 1u
#define TEXTURE_SERVICE_MINOR 0u
/* Handle for a runtime texture replacement registration. 0 is never a valid handle. */
typedef uint64_t TextureReplacementHandle;
typedef enum TextureKeyKind {
/* Match a texture by the address of its in-memory GX texel data. */
TEXTURE_KEY_POINTER = 0,
/* Match by content: XXH64 of the base mip level (and of the referenced TLUT range for
* palette formats), as encoded in replacement filenames / texture dumps. */
TEXTURE_KEY_SOURCE = 1,
} TextureKeyKind;
/* Wildcard values for TEXTURE_KEY_SOURCE hashes ("$" in the filename convention). */
#define TEXTURE_HASH_WILDCARD UINT64_C(0xFFFFFFFFFFFFFFFF)
#define TEXTURE_TLUT_WILDCARD UINT64_C(0xFFFFFFFFFFFFFFFE)
typedef struct TextureKey {
uint32_t struct_size;
TextureKeyKind kind;
const void* pointer; /* TEXTURE_KEY_POINTER only */
uint64_t texture_hash; /* TEXTURE_KEY_SOURCE */
uint64_t tlut_hash; /* TEXTURE_KEY_SOURCE, palette formats only */
uint32_t width;
uint32_t height;
uint32_t gx_format;
bool has_tlut;
} TextureKey;
#define TEXTURE_KEY_INIT {sizeof(TextureKey), TEXTURE_KEY_POINTER, NULL, 0u, 0u, 0u, 0u, 0u, false}
typedef struct TextureData {
uint32_t struct_size;
const void* data; /* texel data laid out in gx_format; copied by the service */
size_t size;
uint32_t width;
uint32_t height;
uint32_t mip_count;
uint32_t gx_format; /* any GX texture format supported by Aurora's converter */
} TextureData;
#define TEXTURE_DATA_INIT {sizeof(TextureData), NULL, 0u, 0u, 0u, 1u, 0u}
/*
* Runtime texture replacements.
*
* Registrations are owned by the calling mod and removed automatically when it is disabled,
* reloaded, or fails. When multiple sources replace the same texture, the highest priority wins:
* later-loaded mods beat earlier ones, and any mod beats the user's texture_replacements config
* directory. Files shipped in a mod's textures/ directory register automatically with the same
* ownership and priority; this service is for replacements decided at runtime.
*/
typedef struct TextureService {
ServiceHeader header;
/* Register a replacement from raw texel data. The data is copied; the caller may free it as
* soon as this returns. */
ModResult (*register_data)(ModContext* ctx, const TextureKey* key, const TextureData* data,
TextureReplacementHandle* out_handle);
/*
* Register a replacement from an encoded .dds/.png inside the calling mod's bundle. The
* filename encodes the key (same convention as the texture_replacements directory, e.g.
* "tex1_{w}x{h}_{hash}_{fmt}.dds"); "_mipN" sidecars next to it are picked up automatically.
* The file is decoded lazily on first use by the renderer.
*/
ModResult (*register_file)(ModContext* ctx, const char* bundle_path,
TextureReplacementHandle* out_handle);
/* Remove a replacement previously registered by the calling mod. */
ModResult (*unregister)(ModContext* ctx, TextureReplacementHandle handle);
} TextureService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<TextureService> {
static constexpr const char* id = TEXTURE_SERVICE_ID;
static constexpr uint16_t major_version = TEXTURE_SERVICE_MAJOR;
};
#endif
+284
View File
@@ -0,0 +1,284 @@
#pragma once
#include "mods/api.h"
#include "mods/svc/config.h"
#define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui"
#define UI_SERVICE_MAJOR 1u
#define UI_SERVICE_MINOR 0u
/*
* UI primitives: a panel inside the host Mods window, mod-owned windows, dialogs, scoped
* RCSS stylesheets and menu bar tabs.
*
* All calls must be made on the game thread from mod callbacks (initialize, update, hooks, or UI
* callbacks). Handles are opaque, generation-checked ids; a stale or unknown handle fails with
* MOD_INVALID_ARGUMENT. Element handles die with the content that owns them: a panel or tab rebuild
* destroys the previous build's elements, so re-acquire handles inside the build callback rather
* than caching them. Strings are UTF-8 and, in both directions, only valid for the duration of the
* call.
*/
/* 0 is never a valid handle. */
typedef uint64_t UiWindowHandle;
typedef uint64_t UiDialogHandle;
typedef uint64_t UiElementHandle;
typedef uint64_t UiStyleHandle;
typedef uint64_t UiMenuTabHandle;
typedef enum UiStyleScope {
UI_SCOPE_PRELAUNCH = 0, /* the pre-launch menu */
UI_SCOPE_WINDOW = 1, /* every tabbed/small window, host and mod alike */
UI_SCOPE_MENU_BAR = 2, /* the in-game menu bar */
UI_SCOPE_OVERLAY = 3, /* the passive overlay (toasts, FPS counter, timers) */
UI_SCOPE_TOUCH_CONTROLS = 4, /* touch controls and their editor */
UI_SCOPE_GRAPHICS_TUNER = 5, /* the graphics tuner overlay window */
} UiStyleScope;
typedef enum UiDialogVariant {
UI_DIALOG_NORMAL = 0,
UI_DIALOG_WARNING = 1, /* warning icon by default */
UI_DIALOG_DANGER = 2, /* red styling, error icon by default */
} UiDialogVariant;
typedef enum UiControlKind {
UI_CONTROL_BUTTON = 0, /* action button (on_pressed) */
UI_CONTROL_TOGGLE = 1, /* boolean on/off */
UI_CONTROL_NUMBER = 2, /* integer stepper with min/max/step */
UI_CONTROL_STRING = 3, /* text input */
UI_CONTROL_SELECT = 4, /* one of `options`; the value is the option index */
} UiControlKind;
typedef enum UiControlBinding {
/* Values flow through the `get`/`set` callbacks. Getters are polled every frame while the
control is visible and must be cheap. */
UI_BINDING_CALLBACKS = 0,
/* The control reads and writes `config_var` (a ConfigService handle owned by the calling mod)
* directly: persistence, change notifications and the modified indicator (value != default) are
* wired automatically. The var type must match the control kind: TOGGLE = bool, NUMBER and
* SELECT = int, STRING = string. Float vars are not bindable; use callbacks. */
UI_BINDING_CONFIG_VAR = 1,
} UiControlBinding;
/* Tagged by the control's kind: TOGGLE reads bool_value, NUMBER and SELECT read int_value, STRING
* reads string_value. string_value passed to a setter is only valid during the call; a getter
* should point it at storage owned by the mod (e.g. a static buffer) that stays valid until the
* next call into the mod the host copies it right after the getter returns. */
typedef struct UiControlValue {
uint32_t struct_size;
bool bool_value;
int64_t int_value;
const char* string_value;
} UiControlValue;
#define UI_CONTROL_VALUE_INIT {sizeof(UiControlValue), false, 0, NULL}
typedef void (*UiControlGetFn)(ModContext* ctx, void* user_data, UiControlValue* out_value);
typedef void (*UiControlSetFn)(ModContext* ctx, void* user_data, const UiControlValue* value);
/* Polled every frame while the control is visible. */
typedef bool (*UiPredicateFn)(ModContext* ctx, void* user_data);
typedef void (*UiPressedFn)(ModContext* ctx, void* user_data);
typedef struct UiControlDesc {
uint32_t struct_size;
UiControlKind kind;
/* Row label (plain text). Required. */
const char* label;
/* Optional RML shown as contextual help when the control is focused or hovered. Only rendered
* where a help pane exists (mod window tabs). */
const char* help_rml;
UiControlBinding binding; /* ignored for BUTTON */
ConfigVarHandle config_var; /* UI_BINDING_CONFIG_VAR */
UiControlGetFn get; /* UI_BINDING_CALLBACKS (all kinds but BUTTON) */
UiControlSetFn set; /* UI_BINDING_CALLBACKS (all kinds but BUTTON) */
UiPressedFn on_pressed; /* BUTTON only. Required for BUTTON. */
UiPredicateFn is_disabled; /* optional */
/* Optional override for the modified indicator. CONFIG_VAR controls derive it from value !=
* default when this is NULL. */
UiPredicateFn is_modified;
/* Passed to every callback above. */
void* user_data;
/* NUMBER: inclusive clamp range and step. min == max means the defaults (0 .. INT32_MAX); step
* < 1 means 1. */
int64_t min;
int64_t max;
int64_t step;
const char* prefix; /* NUMBER: optional text before the value */
const char* suffix; /* NUMBER: optional text after the value */
/* SELECT: option labels (plain text). Required for SELECT. SELECT controls
* present their options in the help pane, so they are only available where
* one exists (mod window tabs); MOD_UNSUPPORTED elsewhere. */
const char* const* options;
size_t option_count;
int32_t max_length; /* STRING: maximum input length; < 1 means unlimited */
} UiControlDesc;
#define UI_CONTROL_DESC_INIT \
{sizeof(UiControlDesc), UI_CONTROL_BUTTON, NULL, NULL, UI_BINDING_CALLBACKS, 0u, NULL, NULL, \
NULL, NULL, NULL, NULL, 0, 0, 1, NULL, NULL, NULL, 0u, 0}
/* Build the panel contents. `panel` accepts the pane_add_* functions; it and
* every element created in it are destroyed (handles invalidated) whenever the
* panel is rebuilt, e.g. on tab switches. A non-MOD_OK result fails the mod. */
typedef ModResult (*UiPanelBuildFn)(
ModContext* ctx, UiElementHandle panel, void* user_data, ModError* out_error);
/* Called every frame while the panel is the visible tab. */
typedef ModResult (*UiPanelUpdateFn)(ModContext* ctx, void* user_data, ModError* out_error);
/* A panel rendered inside the calling mod's tab of the host Mods window. */
typedef struct UiModsPanelDesc {
uint32_t struct_size;
UiPanelBuildFn build; /* required */
UiPanelUpdateFn update;
void* user_data;
} UiModsPanelDesc;
#define UI_MODS_PANEL_DESC_INIT {sizeof(UiModsPanelDesc), NULL, NULL, NULL}
/* Build one tab of a mod window. `left_pane` is the interactive column,
* `right_pane` shows contextual help (controls' help_rml and SELECT options
* render there). Rebuilt on every tab activation. */
typedef ModResult (*UiTabBuildFn)(ModContext* ctx, UiWindowHandle window, UiElementHandle left_pane,
UiElementHandle right_pane, void* user_data, ModError* out_error);
typedef struct UiTabDesc {
uint32_t struct_size;
const char* title; /* required */
UiTabBuildFn build; /* required */
UiPanelUpdateFn update;
void* user_data;
} UiTabDesc;
#define UI_TAB_DESC_INIT {sizeof(UiTabDesc), NULL, NULL, NULL, NULL}
typedef void (*UiWindowClosedFn)(ModContext* ctx, UiWindowHandle window, void* user_data);
typedef struct UiWindowDesc {
uint32_t struct_size;
const UiTabDesc* tabs; /* at least one */
size_t tab_count;
/* Optional RCSS applied to this window's document only (on top of any
* UI_SCOPE_WINDOW sheets, which apply automatically). */
const char* rcss;
/* Fired when the window is destroyed by user close or window_close. Not
* fired during owning-mod teardown/shutdown. The handle is already invalid. */
UiWindowClosedFn on_closed;
void* user_data;
} UiWindowDesc;
#define UI_WINDOW_DESC_INIT {sizeof(UiWindowDesc), NULL, 0u, NULL, NULL, NULL}
typedef void (*UiDialogActionFn)(ModContext* ctx, UiDialogHandle dialog, void* user_data);
/* Note: array element without struct_size; a future change requires appending
* a v2 desc struct rather than growing this one. */
typedef struct UiDialogAction {
const char* label; /* required */
UiDialogActionFn on_pressed;
void* user_data;
bool keep_open; /* false = the dialog closes after on_pressed returns */
} UiDialogAction;
typedef struct UiDialogDesc {
uint32_t struct_size;
const char* title; /* plain text; required */
const char* body_rml; /* RML body; required */
UiDialogVariant variant;
/* Optional icon name overriding the variant default: "warning", "error",
* "question-mark", "verifying", "celebration". */
const char* icon;
const UiDialogAction* actions; /* at least one */
size_t action_count;
/* Fired on cancel (B/Escape) before the dialog closes; the dialog always
* closes on dismiss. */
UiDialogActionFn on_dismiss;
void* user_data; /* passed to on_dismiss */
} UiDialogDesc;
#define UI_DIALOG_DESC_INIT \
{sizeof(UiDialogDesc), NULL, NULL, UI_DIALOG_NORMAL, NULL, NULL, 0u, NULL, NULL}
/* A tab added to the in-game menu bar. */
typedef struct UiMenuTabDesc {
uint32_t struct_size;
const char* label; /* plain text; required */
UiPressedFn on_selected; /* fired when the user activates the tab; required */
void* user_data;
} UiMenuTabDesc;
#define UI_MENU_TAB_DESC_INIT {sizeof(UiMenuTabDesc), NULL, NULL, NULL}
typedef struct UiService {
ServiceHeader header;
/* Register or replace the panel shown in the calling mod's Mods-window tab. */
ModResult (*register_mods_panel)(ModContext* ctx, const UiModsPanelDesc* desc);
/* Content builders. `pane` is a panel or tab pane handle; out_elem (where
* present, optional) receives a handle for later elem_set_* updates. */
ModResult (*pane_add_section)(ModContext* ctx, UiElementHandle pane, const char* title);
ModResult (*pane_add_text)(
ModContext* ctx, UiElementHandle pane, const char* text, UiElementHandle* out_elem);
ModResult (*pane_add_rml)(
ModContext* ctx, UiElementHandle pane, const char* rml, UiElementHandle* out_elem);
ModResult (*pane_add_progress)(
ModContext* ctx, UiElementHandle pane, float value, UiElementHandle* out_elem);
ModResult (*pane_add_control)(ModContext* ctx, UiElementHandle pane, const UiControlDesc* desc,
UiElementHandle* out_elem);
/* Element updates. The handle kind must match the setter (text/rml on text
* rows, progress on progress bars). */
ModResult (*elem_set_text)(ModContext* ctx, UiElementHandle elem, const char* text);
ModResult (*elem_set_rml)(ModContext* ctx, UiElementHandle elem, const char* rml);
ModResult (*elem_set_progress)(ModContext* ctx, UiElementHandle elem, float value);
/* Set or clear an RCSS class on any element handle (rows, progress bars,
* controls, panes), for styling via scoped or window RCSS. */
ModResult (*elem_set_class)(
ModContext* ctx, UiElementHandle elem, const char* name, bool active);
/* Push a tabbed two-pane window onto the document stack and show it. */
ModResult (*window_push)(ModContext* ctx, const UiWindowDesc* desc, UiWindowHandle* out_window);
ModResult (*window_close)(ModContext* ctx, UiWindowHandle window);
/* Push a modal dialog onto the document stack and show it. */
ModResult (*dialog_push)(ModContext* ctx, const UiDialogDesc* desc, UiDialogHandle* out_dialog);
ModResult (*dialog_close)(ModContext* ctx, UiDialogHandle dialog);
ModResult (*dialog_set_body)(ModContext* ctx, UiDialogHandle dialog, const char* body_rml);
/* Replace the dialog icon ("" removes it; names as in UiDialogDesc.icon). */
ModResult (*dialog_set_icon)(ModContext* ctx, UiDialogHandle dialog, const char* icon);
/* Append one action button (same callback rules as at push). */
ModResult (*dialog_add_action)(
ModContext* ctx, UiDialogHandle dialog, const UiDialogAction* action);
/* Whether any focus-stack document is currently visible (visible documents block gamepad
* input). */
ModResult (*is_any_document_visible)(ModContext* ctx, bool* out_visible);
/* Register an RCSS sheet applied to every document of `scope`, now and in the future, until
* unregistered or the calling mod is torn down. Sheets apply in registration order after the
* host styles. Fails with MOD_INVALID_ARGUMENT if the RCSS does not parse. */
ModResult (*register_styles)(
ModContext* ctx, UiStyleScope scope, const char* rcss, UiStyleHandle* out_style);
/* Like register_styles, but reads the RCSS from the mod bundle's res/ directory (same path
* rules as ResourceService::load). MOD_UNAVAILABLE if the file does not exist. */
ModResult (*register_styles_file)(
ModContext* ctx, UiStyleScope scope, const char* path, UiStyleHandle* out_style);
ModResult (*unregister_styles)(ModContext* ctx, UiStyleHandle style);
/* Add a tab to the in-game menu bar, on_selected runs with the menu open; pushing a window from
* it stacks the window over the menu like the host tabs do. The tab is removed when
* unregistered or the mod is torn down. */
ModResult (*register_menu_tab)(
ModContext* ctx, const UiMenuTabDesc* desc, UiMenuTabHandle* out_tab);
ModResult (*unregister_menu_tab)(ModContext* ctx, UiMenuTabHandle tab);
} UiService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<UiService> {
static constexpr const char* id = UI_SERVICE_ID;
static constexpr uint16_t major_version = UI_SERVICE_MAJOR;
};
#endif
@@ -154,18 +154,6 @@ public:
return (J2DTextBoxHBinding)((mFlags >> 2) & 3);
}
#if TARGET_PC
void setVBinding(J2DTextBoxVBinding vBinding) {
mFlags &= 0b1100;
mFlags |= (vBinding & 3);
}
void setHBinding(J2DTextBoxHBinding hBinding) {
mFlags &= 0b0011;
mFlags |= ((hBinding & 3) << 2);
}
#endif
JUtility::TColor getCharColor() { return mCharColor; }
JUtility::TColor getGradColor() { return mGradientColor; }
u16 getStringAllocByte() const { return mStringLength; }
@@ -12,12 +12,23 @@ struct JORNodeEvent;
class JORMContext;
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 {
public:
#if DEBUG
JOREventListener() {}
#if TARGET_PC
virtual void listenPropertyEvent(const JORPropertyEvent*) {}
#else
virtual void listenPropertyEvent(const JORPropertyEvent*) = 0;
#endif
#endif
};
class JORReflexible : public JOREventListener {
@@ -30,7 +41,11 @@ public:
virtual void listenPropertyEvent(const JORPropertyEvent*);
virtual void listen(u32, const JOREvent*);
virtual void genObjectInfo(const JORGenEvent*);
#if TARGET_PC
virtual void genMessage(JORMContext*) {}
#else
virtual void genMessage(JORMContext*) = 0;
#endif
virtual void listenNodeEvent(const JORNodeEvent*);
#endif
};
@@ -314,7 +314,7 @@ public:
>
{
TIterator_data_(const TFunctionValue_list_parameter& rParent, const f32* value) {
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
pOwn_ = &rParent;
#endif
pf_ = value;
@@ -372,7 +372,7 @@ public:
return (r1.pf_ - r2.pf_) / suData_size;
}
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x00 */ const TFunctionValue_list_parameter* pOwn_;
#endif
/* 0x00 */ const f32* pf_;
@@ -425,7 +425,7 @@ public:
>
{
TIterator_data_(const TFunctionValue_hermite& rParent, const f32* value) {
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
pOwn_ = &rParent;
#endif
pf_ = value;
@@ -491,7 +491,7 @@ public:
return (r1.pf_ - r2.pf_) / r1.uSize_;
}
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
/* 0x00 */ const TFunctionValue_hermite* pOwn_;
/* 0x04 */ const f32* pf_;
/* 0x08 */ u32 uSize_;
-8
View File
@@ -7,10 +7,6 @@
#include "JSystem/JMessage/control.h"
#if TARGET_PC
#include "dusk/randomizer/game/messages.hpp"
#endif
JMessage::TControl::TControl()
: pSequenceProcessor_(NULL),
pRenderingProcessor_(NULL),
@@ -94,10 +90,6 @@ bool JMessage::TControl::setMessageCode_inSequence_(JMessage::TProcessor const*
JUT_ASSERT(155, pResourceCache_!=NULL);
pMessageText_begin_ = pResourceCache_->getMessageText_messageEntry(pEntry_);
#if TARGET_PC
// Feels kinda hacky to have to hijack this deep into JSystem, but works for now
HandleTextOverrides(this, pProcessor, uMessageGroupID_, uMessageID_);
#endif
pMessageText_current_ = pMessageText_begin_;
oStack_renderingProcessor_.clear();
return true;
+8
View File
@@ -0,0 +1,8 @@
<?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>
+97
View File
@@ -0,0 +1,97 @@
window.logs content {
flex-flow: column;
}
window.logs .log-toolbar {
display: flex;
flex-flow: row;
flex: 0 0 64dp;
height: 64dp;
align-items: center;
gap: 8dp;
padding-right: 72dp;
background-color: rgba(217, 217, 217, 10%);
border-bottom: 2dp #92875B;
font-family: "Fira Sans Condensed";
font-weight: bold;
font-size: 18dp;
}
window.logs > close {
top: 8dp;
right: 8dp;
}
window.logs .log-title {
align-self: stretch;
flex: 0 0 auto;
padding: 0 24dp;
line-height: 64dp;
text-transform: uppercase;
border-bottom: 4dp #C2A42D;
font-effect: glow(0dp 4dp 0dp 4dp black);
}
window.logs .log-title-mod {
flex: 0 1 auto;
min-width: 0;
white-space: nowrap;
overflow: hidden;
font-family: "Fira Sans";
font-weight: normal;
font-size: 15dp;
color: rgba(224, 219, 200, 55%);
}
.log-toolbar-spacer {
flex: 1 1 0;
}
.log-toolbar button {
flex: 0 0 auto;
font-family: "Fira Sans";
font-weight: normal;
font-size: 15dp;
padding: 5dp 12dp;
}
window.logs content pane.log-view {
flex: 1 1 0;
padding: 12dp 16dp;
padding-bottom: 0dp;
gap: 0dp;
}
.log-lines {
display: block;
}
.log-line {
display: block;
font-family: "Noto Mono";
font-size: 13dp;
line-height: 1.5;
word-break: break-word;
white-space: pre-wrap;
}
.log-line .log-time {
color: rgba(224, 219, 200, 45%);
}
.log-line .log-mod {
color: rgba(194, 164, 45, 80%);
}
.log-line.lvl-trace,
.log-line.lvl-debug {
opacity: 0.55;
}
.log-line.lvl-warn .log-msg {
color: #ffa826;
}
.log-line.lvl-error .log-msg {
color: #cc4444;
}
+218
View File
@@ -0,0 +1,218 @@
window.mods content pane.mod-list {
flex: 0 0 360dp;
padding: 16dp;
padding-bottom: 0dp;
gap: 4dp;
}
@media (max-height: 640dp) {
window.mods content pane.mod-list {
flex: 1 1 0;
}
}
window.mods content pane.mod-detail {
gap: 12dp;
}
.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 auto;
}
.mod-info-value {
flex: 1 1 0;
}
.mod-path {
font-size: 14dp;
word-break: break-all;
opacity: 0.7;
}
mod-entry {
display: flex;
flex-flow: row;
gap: 12dp;
padding: 10dp;
border-radius: 10dp;
decorator: vertical-gradient(#c2a42d00 #c2a42d00);
transition: decorator 0.1s linear-in-out;
cursor: pointer;
focus: auto;
}
mod-entry.current {
box-shadow: rgba(146, 135, 91, 40%) 0 0 0 1dp;
}
mod-entry:hover,
mod-entry:focus-visible {
decorator: vertical-gradient(#c2a42d00 #c2a42d26);
}
mod-entry:selected {
decorator: vertical-gradient(#c2a42d10 #c2a42d40);
}
mod-entry .mod-icon {
flex: 0 0 auto;
width: 56dp;
height: 56dp;
border-radius: 8dp;
}
mod-entry icon.mod-icon {
font-size: 36dp;
background-color: rgba(17, 16, 10, 20%);
color: rgba(224, 219, 200, 45%);
decorator: text("&#xe87b;" center center);
}
mod-entry .mod-entry-info {
display: flex;
flex-flow: column;
flex: 1 1 0;
min-width: 0;
gap: 2dp;
}
mod-entry .mod-entry-name {
display: flex;
flex-flow: row;
align-items: baseline;
gap: 6dp;
}
mod-entry .mod-entry-name-text {
flex: 0 1 auto;
min-width: 0;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
}
mod-entry .mod-entry-version {
flex: 0 0 auto;
font-size: 13dp;
color: rgba(224, 219, 200, 50%);
}
mod-entry .mod-entry-status.active {
color: #44cc55;
}
mod-entry .mod-entry-status.failed {
color: #cc4444;
}
mod-entry .mod-entry-desc {
font-size: 14dp;
line-height: 1.3;
color: rgba(224, 219, 200, 65%);
max-height: 2.6em;
overflow: hidden;
}
mod-entry .mod-entry-sub {
font-size: 13dp;
color: rgba(224, 219, 200, 50%);
}
mod-entry.inactive .mod-icon {
filter: grayscale(1);
}
mod-entry.inactive .mod-entry-info {
opacity: 0.5;
}
mod-header {
position: relative;
flex: 0 0 auto;
}
mod-header.has-banner {
height: 180dp;
margin: -24dp -24dp 0dp -24dp;
}
mod-header .mod-actions {
position: absolute;
top: 24dp;
left: 24dp;
display: flex;
flex-flow: row;
gap: 8dp;
}
mod-header .mod-actions button {
font-size: 16dp;
padding: 6dp 14dp;
background-color: rgba(21, 22, 16, 80%);
box-shadow: rgba(146, 135, 91, 60%) 0 0 0 1dp;
}
mod-header.no-banner {
display: flex;
flex-flow: row;
align-items: center;
}
mod-header.no-banner .mod-actions {
position: static;
}
window.mods .mod-title {
display: block;
font-size: 28dp;
font-weight: bold;
}
window.mods .mod-title .mod-title-version {
font-weight: normal;
font-size: 16dp;
color: rgba(224, 219, 200, 55%);
}
window.mods .mod-author {
display: block;
font-size: 15dp;
color: rgba(224, 219, 200, 55%);
}
window.mods .mod-restart-note {
font-size: 15dp;
color: #ffa826;
opacity: 0.85;
}
window.mods .mod-description {
line-height: 1.5;
}
.status-badge {
font-size: 14dp;
opacity: 0.7;
}
.status-badge.active,
.mod-info-label.active {
color: #44cc55;
opacity: 1;
}
.status-badge.failed,
.mod-info-label.failed {
color: #cc4444;
opacity: 1;
}
+8
View File
@@ -159,6 +159,14 @@ toast.achievement heading {
color: #C2A42D;
}
toast.warning {
border: 1dp #C2A42D;
}
toast.warning heading {
color: #C2A42D;
}
toast.controller-warning {
top: auto;
right: auto;
+4
View File
@@ -362,6 +362,10 @@ body.animate-in .intro-item {
transition: opacity transform 0.3s 0.6s cubic-in-out;
}
.delay-6 {
transition: opacity transform 0.3s 0.7s cubic-in-out;
}
/* Mobile layout */
@media (max-height: 640dp) {
.gradient {
+12 -3
View File
@@ -50,7 +50,8 @@ tab-bar[closable] tab-end-spacer {
pointer-events: none;
}
tab-bar[closable] close {
tab-bar[closable] close,
window > close {
display: block;
position: fixed;
top: 8dp;
@@ -70,12 +71,20 @@ tab-bar[closable] close {
}
tab-bar[closable] close:hover,
tab-bar[closable] close:focus-visible {
tab-bar[closable] close:focus-visible,
window > close:hover,
window > close:focus-visible {
color: #fff;
background-color: rgba(194, 164, 45, 24%);
}
tab-bar[closable] close:active {
window > close {
top: 16dp;
right: 16dp;
}
tab-bar[closable] close:active,
window > close:active {
color: #fff;
background-color: rgba(194, 164, 45, 40%);
}
-25
View File
@@ -105,18 +105,6 @@ window content pane > * {
flex: 0 0 auto;
}
pane.excluded-locations-pane {
display: flex;
flex-flow: column;
flex: 1 1 0;
min-width: 0;
min-height: 0;
gap: 8dp;
overflow-y: scroll;
font-size: 20dp;
border-top: 1dp #92875B;
}
window content pane:last-of-type > div {
line-height: 1.625;
}
@@ -490,7 +478,6 @@ window.modal.danger .modal-header icon {
font-size: 20dp;
color: #FFFFFF;
font-weight: normal;
word-break: break-word;
}
.modal-body span.tip {
@@ -532,15 +519,3 @@ progress.verification-progress-bar {
flex: 0 0 auto;
padding-top: 4dp;
}
.current-option-text {
padding-left: 5dp;
border: 2dp #C2A42D;
border-radius: 14dp;
}
.not-current-option-text {
padding-left: 5dp;
margin: 2dp; /*Used to mimic border*/
opacity: 0.5;
}
+43
View File
@@ -0,0 +1,43 @@
# Dusklight Mod SDK entry point
#
# Provides game/service headers, compile definitions and version.h without
# configuring the full game tree.
#
# Usage (from a mod project):
# add_subdirectory(<dusk>/sdk dusk-sdk EXCLUDE_FROM_ALL)
# add_mod(my_mod SOURCES ... MOD_JSON mod.json)
#
# On Windows, pass -DDUSK_GAME_IMPLIB=<path to sdk/dusklight.lib> from the
# matching game release. TODO: auto-download from tag
cmake_minimum_required(VERSION 3.25)
if (TARGET dusklight_game_headers)
message(FATAL_ERROR "Mod SDK already configured")
endif ()
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/WindowsTargetProcessor.cmake")
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
endif ()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Version detection & version.h
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/DetectVersion.cmake")
detect_version()
configure_version_header()
# Game ABI headers & compile definitions
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GameABIConfig.cmake")
if (WIN32)
set(DUSK_GAME_IMPLIB "" CACHE FILEPATH "Path to the dusklight import library (sdk/dusklight.lib)")
if (DUSK_GAME_IMPLIB AND NOT EXISTS "${DUSK_GAME_IMPLIB}")
message(FATAL_ERROR "Mod SDK: DUSK_GAME_IMPLIB does not exist: ${DUSK_GAME_IMPLIB}")
endif ()
endif ()
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ModSDK.cmake")
+139
View File
@@ -0,0 +1,139 @@
// Mod SDK runtime (Windows): applies lld's MinGW-style runtime pseudo-relocations on
// the plain MSVC CRT. This is what lets mods reference game data (`extern` globals) with no
// __declspec(dllimport) annotations: `lld-link -lldmingw` auto-imports the data through IAT
// slots and records fixups, and this module applies them at load time.
//
// The fixup pass runs as an early CRT initializer, and the IAT slots it reads were already
// bound by the OS loader, so even mod static initializers observe patched references.
#include <windows.h>
#include <cstdint>
#include <cstdio>
#include <cstring>
extern "C" char __RUNTIME_PSEUDO_RELOC_LIST__;
extern "C" char __RUNTIME_PSEUDO_RELOC_LIST_END__;
extern "C" IMAGE_DOS_HEADER __ImageBase;
namespace {
struct HdrV2 {
uint32_t magic1;
uint32_t magic2;
uint32_t version;
};
struct ItemV2 {
uint32_t sym; // RVA of the __imp_ slot (OS-bound IAT entry)
uint32_t target; // RVA of the reference to patch
uint32_t flags; // low 8 bits: bit width of the reference
};
bool g_relocsFailed = false;
void report(const char* fmt, ...) {
char buf[512];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
fprintf(stderr, "%s\n", buf);
OutputDebugStringA(buf);
}
bool compute_fixup(const ItemV2& item, char* base, intptr_t* out) {
char* impSlot = base + item.sym;
const intptr_t real = *reinterpret_cast<intptr_t*>(impSlot);
char* target = base + item.target;
const int bits = static_cast<int>(item.flags & 0xff);
intptr_t reldata;
switch (bits) {
case 8:
reldata = *reinterpret_cast<int8_t*>(target);
break;
case 16:
reldata = *reinterpret_cast<int16_t*>(target);
break;
case 32:
reldata = *reinterpret_cast<int32_t*>(target);
break;
case 64:
reldata = *reinterpret_cast<int64_t*>(target);
break;
default:
report("unsupported %d-bit pseudo-relocation at RVA 0x%x", bits, item.target);
return false;
}
reldata -= reinterpret_cast<intptr_t>(impSlot);
reldata += real;
if (bits < 64) {
const intptr_t maxUnsigned = (intptr_t{1} << bits) - 1;
const intptr_t minSigned = -(intptr_t{1} << (bits - 1));
if (reldata > maxUnsigned || reldata < minSigned) {
report("%d-bit data fixup at RVA 0x%x is out of range (delta %+lld)", bits, item.target,
static_cast<long long>(reldata));
return false;
}
}
*out = reldata;
return true;
}
} // namespace
// lld refuses to emit runtime pseudo-relocs unless a function with exactly this (mingw CRT)
// name exists in the image. It is also our actual entry point.
extern "C" void _pei386_runtime_relocator() {
char* base = reinterpret_cast<char*>(&__ImageBase);
char* start = &__RUNTIME_PSEUDO_RELOC_LIST__;
char* end = &__RUNTIME_PSEUDO_RELOC_LIST_END__;
if (end - start < static_cast<ptrdiff_t>(sizeof(HdrV2))) {
return; // no data auto-imports in this mod
}
const HdrV2* hdr = reinterpret_cast<const HdrV2*>(start);
if (hdr->magic1 != 0 || hdr->magic2 != 0 || hdr->version != 1) {
report("unexpected pseudo-relocation list format");
g_relocsFailed = true;
return;
}
const ItemV2* items = reinterpret_cast<const ItemV2*>(hdr + 1);
const ItemV2* itemsEnd = reinterpret_cast<const ItemV2*>(end);
// Validate everything before writing anything, so a bad fixup can't leave the image
// half-patched.
intptr_t scratch;
for (const ItemV2* it = items; it < itemsEnd; ++it) {
if (!compute_fixup(*it, base, &scratch)) {
g_relocsFailed = true;
return;
}
}
for (const ItemV2* it = items; it < itemsEnd; ++it) {
intptr_t reldata;
compute_fixup(*it, base, &reldata);
char* target = base + it->target;
const size_t len = static_cast<size_t>(it->flags & 0xff) / 8;
DWORD old = 0;
if (!VirtualProtect(target, len, PAGE_EXECUTE_READWRITE, &old)) {
report("VirtualProtect failed at RVA 0x%x", it->target);
g_relocsFailed = true;
return;
}
std::memcpy(target, &reldata, len);
VirtualProtect(target, len, old, &old);
}
}
using PVFV = void (*)();
#pragma section(".CRT$XCB", read)
extern "C" __declspec(allocate(".CRT$XCB")) PVFV dusk_mod_pseudo_reloc_init =
_pei386_runtime_relocator;
BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH && g_relocsFailed) {
return FALSE;
}
return TRUE;
}
+7 -10
View File
@@ -1230,8 +1230,7 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) {
bgm_id = Z2BGM_DUNGEON_FOREST;
bgm_wave1 = 0xa;
/* dSv_event_flag_c::M_022 - Forest Temple - Forest Temple clear (Midna creates warp hole) */
// In rando, check for boss defeated instead
} else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[55]) IF_DUSK(&& (!randomizer_IsActive() || dComIfGs_isStageBossEnemy()))) {
} else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[55])) {
bgm_id = 0x200005b;
} else {
bgm_wave1 = 0xc;
@@ -1291,13 +1290,11 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) {
break;
case Z2SCENE_LAKEBED_TEMPLE_BOSS:
se_wave1 = 9;
// In rando, check for boss defeated instead
if (dComIfGs_isStageSwitch(4, 0xe) IF_DUSK(&& (!randomizer_IsActive() || dComIfGs_isStageBossEnemy()))) {
if (dComIfGs_isStageSwitch(4, 0xe)) {
bgm_id = Z2BGM_DUNGEON_LV3;
bgm_wave1 = 0x15;
/* dSv_event_flag_c::M_045 - Lakebed Temple - Lakebed Temple clear */
// In rando, check for boss defeated instead
} else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[78]) IF_DUSK(&& (!randomizer_IsActive() || dComIfGs_isStageBossEnemy()))) {
} else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[78])) {
bgm_id = 0x200005b;
} else {
bgm_wave1 = 0x1e;
@@ -1327,11 +1324,11 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) {
break;
case Z2SCENE_ARBITERS_GROUNDS_BOSS:
se_wave1 = 0xd;
if (dComIfGs_isStageSwitch(0xa, 0xa) IF_DUSK(&& (!randomizer_IsActive() || dComIfGs_isStageBossEnemy()))) {
if (dComIfGs_isStageSwitch(0xa, 0xa)) {
bgm_id = Z2BGM_DUNGEON_LV4;
bgm_wave1 = 0x1a;
/* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */
} else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[265]) IF_DUSK(&& (!randomizer_IsActive() || dComIfGs_isStageBossEnemy()))) {
} else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[265])) {
bgm_id = 0x200005b;
} else {
bgm_wave1 = 0x4c;
@@ -1392,11 +1389,11 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) {
break;
case Z2SCENE_TEMPLE_OF_TIME_BOSS:
se_wave1 = 0x15;
if (dComIfGs_isStageSwitch(7, 0x18) IF_DUSK(&& (!randomizer_IsActive() || dComIfGs_isStageBossEnemy()))) {
if (dComIfGs_isStageSwitch(7, 0x18)) {
bgm_id = Z2BGM_DUNGEON_LV6;
bgm_wave1 = 0x26;
/* dSv_event_flag_c::F_0267 - Temple of Time - Temple of Time clear */
} else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[267]) IF_DUSK(&& (!randomizer_IsActive() || dComIfGs_isStageBossEnemy()))) {
} else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[267])) {
bgm_id = 0x200005b;
} else {
bgm_wave1 = 0x4e;
+1 -1
View File
@@ -22,7 +22,7 @@
#include "os_report.h"
Z2SoundObjBase::Z2SoundObjBase()
#if DEBUG
#if PARTIAL_DEBUG || DEBUG
: JSULink<Z2SoundObjBase>(this)
#endif
{
+1 -47
View File
@@ -58,10 +58,6 @@
#include "res/Object/Alink.h"
#include <cstring>
#include <dusk/string.hpp>
#include "dusk/cosmetics/color_utils.hpp"
#include "dusk/randomizer/game/flags.h"
#include "dusk/randomizer/game/stages.h"
#include "dusk/randomizer/game/tools.h"
#endif
static int daAlink_Create(fopAc_ac_c* i_this);
@@ -4235,25 +4231,6 @@ int daAlink_c::createHeap() {
if (mpHIO == NULL) {
return 0;
}
#if TARGET_PC
const auto& lanternColor = dusk::getSettings().cosmetics.lanternGlowColor.getValue();
if (dusk::cosmetics::is_valid_hex_color_str(lanternColor)) {
u8 r = std::stoi(lanternColor.substr(0, 2), nullptr, 16);
u8 g = std::stoi(lanternColor.substr(2, 2), nullptr, 16);
u8 b = std::stoi(lanternColor.substr(4, 2), nullptr, 16);
auto& lanternAmbience = mpHIO->mItem.mLanternPL.m;
auto& lanternSphere = mpHIO->mItem.mLantern.m;
lanternAmbience.mColorR = r;
lanternAmbience.mColorG = g;
lanternAmbience.mColorB = b;
lanternSphere.mColorReg1R = r;
lanternSphere.mColorReg1G = g;
lanternSphere.mColorReg1B = b;
lanternSphere.mColorReg2R = r;
lanternSphere.mColorReg2G = g;
lanternSphere.mColorReg2B = b;
}
#endif
if (!(mpWlChangeModel = initModel(dRes_ID_ALINK_BMD_WL_CHANGE_e, 0))) {
return 0;
@@ -10596,16 +10573,9 @@ void daAlink_c::decideDoStatus() {
(actor_name == fpcNm_TAG_KMSG_e &&
static_cast<daTag_KMsg_c*>(field_0x27f4)->getType() == 3))
{
// Don't check vanilla condition in randomizer
if (!checkEquipAnime() && checkMasterSwordEquip() IF_DUSK(&& !randomizer_IsActive())) {
if (!checkEquipAnime() && checkMasterSwordEquip()) {
setDoStatus(BUTTON_STATUS_STRIKE);
}
#if TARGET_PC
// Separate check for striking sword into the pedestal for randomizer
if (!checkEquipAnime() && randomizer_IsActive() && randomizer_checkTempleOfTimeRequirement()) {
setDoStatus(BUTTON_STATUS_STRIKE);
}
#endif
}
} else if (mTargetedActor != NULL && checkGoatCatchActor(mTargetedActor) &&
mAttention->getActionBtnB() != NULL &&
@@ -11513,14 +11483,6 @@ int daAlink_c::orderTalk(int i_checkZTalk) {
static void* daAlink_searchBouDoor(fopAc_ac_c* i_actor, void* i_data) {
UNUSED(i_data);
#if TARGET_PC
// In randomizer, we don't want Bo preventing us from entering his house on Day 2
if (randomizer_IsActive() && daAlink_c::checkStageName("F_SP103"))
{
return NULL;
}
#endif
if (fopAcM_GetName(i_actor) == fpcNm_NPC_BOU_e && ((daNpc_Bou_c*)i_actor)->speakTo()) {
return i_actor;
}
@@ -12301,14 +12263,6 @@ BOOL daAlink_c::checkGroundSpecialMode() {
if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn() &&
checkEndResetFlg0(ERFLG0_FORCE_WOLF_CHANGE))
{
#if TARGET_PC
u8 stage = getStageID();
// In rando, don't transform in twilight fog unless we have shadow crystal
if (randomizer_IsActive() && !dComIfGs_isEventBit(TRANSFORMING_UNLOCKED) &&
(stage == Palace_of_Twilight || stage == Phantom_Zant_1 || stage == Phantom_Zant_2)) {
return 0;
}
#endif
return procCoMetamorphoseInit();
}
+2 -55
View File
@@ -1624,7 +1624,7 @@ const daAlinkHIO_bomb_c1 daAlinkHIO_bomb_c0::m = {
#pragma push
#pragma force_active on
IF_NOT_DUSK(const) daAlinkHIO_huLight_c1 daAlinkHIO_huLight_c0::m = {
const daAlinkHIO_huLight_c1 daAlinkHIO_huLight_c0::m = {
0,
3,
0,
@@ -1638,25 +1638,8 @@ IF_NOT_DUSK(const) daAlinkHIO_huLight_c1 daAlinkHIO_huLight_c0::m = {
0.0f,
};
#pragma pop
#if TARGET_PC
// Save original lantern colors incase player reverts to default
const daAlinkHIO_huLight_c1 daAlinkHIO_huLight_c0::original = {
0,
3,
0,
181,
112,
40,
-70,
1.0f,
50.0f,
350.0f,
0.0f,
};
#endif
IF_NOT_DUSK(const) daAlinkHIO_kandelaar_c1 daAlinkHIO_kandelaar_c0::m = {
const daAlinkHIO_kandelaar_c1 daAlinkHIO_kandelaar_c0::m = {
{
30,
1.1f,
@@ -1689,42 +1672,6 @@ IF_NOT_DUSK(const) daAlinkHIO_kandelaar_c1 daAlinkHIO_kandelaar_c0::m = {
0.5f,
};
#if TARGET_PC
// Save original lantern colors incase player reverts to default
const daAlinkHIO_kandelaar_c1 daAlinkHIO_kandelaar_c0::original = {
{
30,
1.1f,
2.0f,
3.0f,
17.0f,
},
{
11,
1.0f,
0.0f,
3.0f,
12.0f,
},
{
17,
1.0f,
0.0f,
3.0f,
18.0f,
},
80,
40,
20,
40,
30,
10,
3,
200,
0.5f,
};
#endif
const daAlinkHIO_fmChain_c1 daAlinkHIO_fmChain_c0::m = {
{
20,
+6
View File
@@ -165,6 +165,12 @@ BOOL daAlink_c::checkCutTypeNoBlur() const {
}
bool daAlink_c::checkCutTurnInput() const {
#if TARGET_PC
if (mDoCPd_c::getHoldR(PAD_1) && dusk::getSettings().game.easyQuickSpin) {
return true;
}
#endif
return 0xF800 < abs(field_0x3180);
}
+2 -321
View File
@@ -23,14 +23,9 @@
#include "d/actor/d_a_npc_tkc.h"
#include <cstring>
#include "dusk/archipelago/archipelago_context.hpp"
#if TARGET_PC
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h"
#include "dusk/speedrun.h"
#include "dusk/randomizer/game/randomizer_context.hpp"
#endif
BOOL daAlink_c::checkEventRun() const {
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
@@ -1028,26 +1023,13 @@ void daAlink_c::setGetItemFace(u16 i_itemNo) {
|| i_itemNo == dItemNo_ANCIENT_DOCUMENT2_e
|| i_itemNo == dItemNo_DROP_CONTAINER_e
|| i_itemNo == dItemNo_DROP_CONTAINER02_e
#if TARGET_PC
// Rando items for facial expressions
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_WOOD_STICK_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_SWORD_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_SHIELD_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_MASTER_SWORD_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_LIGHT_SWORD_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_MAGIC_LV1_e)
#endif
|| i_itemNo == dItemNo_DROP_CONTAINER03_e)
{
setFaceBasicBck(dRes_ID_ALANM_BCK_FI_e);
} else if (i_itemNo == dItemNo_DUNGEON_EXIT_e || i_itemNo == dItemNo_LV7_DUNGEON_EXIT_e) {
setFaceBasicTexture(FTANM_K_A);
setFaceBasicBck(dRes_ID_ALANM_BCK_FK_e);
#if TARGET_PC
} else if (i_itemNo == dItemNo_TOMATO_PUREE_e || i_itemNo == dItemNo_TASTE_e || (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_FOOLISH_ITEM_e)) {
#else
} else if (i_itemNo == dItemNo_TOMATO_PUREE_e || i_itemNo == dItemNo_TASTE_e) {
#endif
setFaceBasicBck(dRes_ID_ALANM_BCK_FJ_e);
}
}
@@ -2234,283 +2216,18 @@ void daAlink_c::setGetSubBgm(int i_itemNo) {
/* dItemNo_KEY_OF_FILONE_e */ SETYPE_ITEM_GET_MINI,
};
#if TARGET_PC
// Randomizer SeType changes
static const u8 getSeTypeRandomizer[255] = {
/* dItemNo_Randomizer_HEART_e */ SETYPE_NONE,
/* dItemNo_Randomizer_GREEN_RUPEE_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_BLUE_RUPEE_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_YELLOW_RUPEE_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_RED_RUPEE_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_PURPLE_RUPEE_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_ORANGE_RUPEE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_SILVER_RUPEE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_S_MAGIC_e */ SETYPE_NONE,
/* dItemNo_Randomizer_L_MAGIC_e */ SETYPE_NONE,
/* dItemNo_Randomizer_BOMB_5_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_BOMB_10_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_BOMB_20_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_BOMB_30_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_ARROW_10_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_ARROW_20_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_ARROW_30_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_ARROW_1_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_PACHINKO_SHOT_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_FOOLISH_ITEM_e_e */ SETYPE_NONE,
/* dItemNo_Randomizer_NOENTRY_20_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_21_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WATER_BOMB_5_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_WATER_BOMB_10_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_WATER_BOMB_20_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_WATER_BOMB_30_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_BOMB_INSECT_5_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_BOMB_INSECT_10_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_BOMB_INSECT_20_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_BOMB_INSECT_30_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_RECOVERY_FAILY_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_TRIPLE_HEART_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_SMALL_KEY_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_KAKERA_HEART_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_UTAWA_HEART_e */ SETYPE_HEART,
/* dItemNo_Randomizer_MAP_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_COMPUS_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_DUNGEON_EXIT_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_BOSS_KEY_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_DUNGEON_BACK_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_SWORD_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_MASTER_SWORD_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_WOOD_SHIELD_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_SHIELD_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_HYLIA_SHIELD_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_TKS_LETTER_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WEAR_CASUAL_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WEAR_KOKIRI_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_ARMOR_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_WEAR_ZORA_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_MAGIC_LV1_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_DUNGEON_EXIT_2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WALLET_LV1_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WALLET_LV2_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_WALLET_LV3_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_55_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_56_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_57_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_58_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_59_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_60_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_ZORAS_JEWEL_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_HAWK_EYE_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_WOOD_STICK_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_BOOMERANG_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_SPINNER_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_IRONBALL_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_BOW_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_HOOKSHOT_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_HVY_BOOTS_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_COPY_ROD_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_W_HOOKSHOT_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_KANTERA_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_LIGHT_SWORD_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_FISHING_ROD_1_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_PACHINKO_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_COPY_ROD_2_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_77_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_78_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_BOMB_BAG_LV2_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_BOMB_BAG_LV1_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_BOMB_IN_BAG_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_82_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LIGHT_ARROW_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_ARROW_LV1_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_ARROW_LV2_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_ARROW_LV3_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_87_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LURE_ROD_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_BOMB_ARROW_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_HAWK_ARROW_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_BEE_ROD_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_JEWEL_ROD_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WORM_ROD_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_JEWEL_BEE_ROD_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_JEWEL_WORM_ROD_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_EMPTY_BOTTLE_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_RED_BOTTLE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_GREEN_BOTTLE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_BLUE_BOTTLE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_MILK_BOTTLE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_HALF_MILK_BOTTLE_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_OIL_BOTTLE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WATER_BOTTLE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_OIL_BOTTLE_2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_RED_BOTTLE_2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_UGLY_SOUP_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_HOT_SPRING_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_FAIRY_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_HOT_SPRING_2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_OIL2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_OIL_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NORMAL_BOMB_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WATER_BOMB_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_POKE_BOMB_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_FAIRY_DROP_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_WORM_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_DROP_BOTTLE_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_BEE_CHILD_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_CHUCHU_RARE_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_CHUCHU_RED_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_CHUCHU_BLUE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_CHUCHU_GREEN_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_CHUCHU_YELLOW_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_CHUCHU_PURPLE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LV1_SOUP_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LV2_SOUP_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LV3_SOUP_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LETTER_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_BILL_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_WOOD_STATUE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_IRIAS_PENDANT_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_HORSE_FLUTE_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_133_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_134_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_135_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_136_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_137_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_138_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_139_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_140_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_141_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_142_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_143_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_RAFRELS_MEMO_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_ASHS_SCRIBBLING_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_146_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_147_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_148_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_149_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_150_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_151_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_152_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_153_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_154_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_155_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_CHUCHU_YELLOW2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_OIL_BOTTLE3_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_SHOP_BEE_CHILD_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_CHUCHU_BLACK_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LIGHT_DROP_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_DROP_CONTAINER_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_DROP_CONTAINER02_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_DROP_CONTAINER03_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_FILLED_CONTAINER_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_MIRROR_PIECE_2_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_MIRROR_PIECE_3_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_MIRROR_PIECE_4_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_168_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_169_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_170_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_171_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_172_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_173_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_174_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_175_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_SMELL_YELIA_POUCH_e */ SETYPE_ITEM_GET_SMELL,
/* dItemNo_Randomizer_SMELL_PUMPKIN_e */ SETYPE_ITEM_GET_SMELL,
/* dItemNo_Randomizer_SMELL_POH_e */ SETYPE_ITEM_GET_SMELL,
/* dItemNo_Randomizer_SMELL_FISH_e */ SETYPE_ITEM_GET_SMELL,
/* dItemNo_Randomizer_SMELL_CHILDREN_e */ SETYPE_ITEM_GET_SMELL,
/* dItemNo_Randomizer_SMELL_MEDICINE_e */ SETYPE_ITEM_GET_SMELL,
/* dItemNo_Randomizer_NOENTRY_182_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_183_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_184_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_185_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_186_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_187_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_188_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_189_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_190_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_NOENTRY_191_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_M_BEETLE_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_BEETLE_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_BUTTERFLY_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_BUTTERFLY_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_STAG_BEETLE_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_STAG_BEETLE_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_GRASSHOPPER_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_GRASSHOPPER_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_NANAFUSHI_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_NANAFUSHI_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_DANGOMUSHI_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_DANGOMUSHI_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_MANTIS_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_MANTIS_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_LADYBUG_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_LADYBUG_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_SNAIL_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_SNAIL_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_DRAGONFLY_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_DRAGONFLY_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_ANT_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_ANT_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_M_MAYFLY_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_F_MAYFLY_e */ SETYPE_ITEM_GET_INSECT,
/* dItemNo_Randomizer_NOENTRY_216_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_217_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_218_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_219_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_220_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_221_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_222_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_223_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_POU_SPIRIT_e */ SETYPE_ITEM_GET_POU,
/* dItemNo_Randomizer_NOENTRY_225_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_226_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_227_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_228_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_229_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_230_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_231_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_NOENTRY_232_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_ANCIENT_DOCUMENT_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_AIR_LETTER_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_ANCIENT_DOCUMENT2_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_LV7_DUNGEON_EXIT_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LINKS_SAVINGS_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_SMALL_KEY2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_POU_FIRE1_e */ SETYPE_NONE,
/* dItemNo_Randomizer_POU_FIRE2_e */ SETYPE_NONE,
/* dItemNo_Randomizer_POU_FIRE3_e */ SETYPE_NONE,
/* dItemNo_Randomizer_POU_FIRE4_e */ SETYPE_NONE,
/* dItemNo_Randomizer_BOSSRIDER_KEY_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_TOMATO_PUREE_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_TASTE_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_LV5_BOSS_KEY_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_SURFBOARD_e */ SETYPE_NONE,
/* dItemNo_Randomizer_KANTERA2_e */ SETYPE_ITEM_GET_ME,
/* dItemNo_Randomizer_L2_KEY_PIECES1_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_L2_KEY_PIECES2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_L2_KEY_PIECES3_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_KEY_OF_CARAVAN_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_Randomizer_LV2_BOSS_KEY_e */ SETYPE_ITEM_GET,
/* dItemNo_Randomizer_KEY_OF_FILONE_e */ SETYPE_ITEM_GET_MINI,
};
#endif
static u32 const bgmLabel[8] = {
Z2BGM_HEART_GET, Z2BGM_ITEM_GET, Z2BGM_ITEM_GET_MINI, Z2BGM_ITEM_GET_ME,
Z2BGM_ITEM_GET_INSECT, Z2BGM_ITEM_GET_SMELL, Z2BGM_ITEM_GET_POU, Z2BGM_ITEM_GET_ME_S,
};
#if TARGET_PC
u32 se_type = randomizer_IsActive() ? getSeTypeRandomizer[i_itemNo] : getSeType[i_itemNo];
#else
u32 se_type = getSeType[i_itemNo];
#endif
if (se_type == SETYPE_ITEM_GET_ME && mProcVar4.field_0x3010 == 0) {
se_type = SETYPE_ITEM_GET_ME_S;
}
if (se_type != SETYPE_NONE IF_DUSK(|| i_itemNo == dItemNo_Randomizer_FOOLISH_ITEM_e)) {
if (se_type != SETYPE_NONE) {
mDoAud_subBgmStart(bgmLabel[se_type]);
dComIfGp_setMesgBgmOn();
}
@@ -2542,15 +2259,6 @@ int daAlink_c::procCoGetItemInit() {
s16 var_r22 = 0;
BOOL var_r31 = FALSE;
BOOL var_r30 = FALSE;
#if TARGET_PC
if (randomizer_IsActive()) {
// If we are giving a custom item, we want to set mParam0 to 0x100 so that instead of trying to search for an item
// actor that doesnt exist we want the game to create one using the item id in mGtItm.
if (g_randomizerState.getGiveItemToPlayerStatus() == RandomizerState::ITEM_IN_QUEUE) {
mDemo.setParam0(0x100);
}
}
#endif
if (mProcID == PROC_GET_ITEM || mProcID == PROC_INSECT_CATCH ||
(mProcID == PROC_PREACTION_UNEQUIP && !checkNoUpperAnime()))
{
@@ -2588,11 +2296,6 @@ int daAlink_c::procCoGetItemInit() {
} else {
item_no = dComIfGp_event_getGtItm();
}
#if TARGET_PC
if (randomizer_IsActive() && g_randomizerState.getGiveItemToPlayerStatus() == RandomizerState::ITEM_IN_QUEUE) {
g_randomizerState.setGiveItemToPlayerStatus(RandomizerState::CLEAR_QUEUE);
}
#endif
fpc_ProcID item_partner_id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1,
fopAcM_GetRoomNo(this), NULL, NULL);
@@ -2820,10 +2523,8 @@ int daAlink_c::procCoGetItem() {
} else if (mProcVar2.field_0x300c == 0x23 && checkStageName("D_MN11")) {
field_0x32cc = 0x5C0;
} else if (mProcVar2.field_0x300c == 0xE0) {
// Don't show special text in rando
if (dComIfGs_getPohSpiritNum() == 20 IF_DUSK(&& !randomizer_IsActive())) {
if (dComIfGs_getPohSpiritNum() == 20) {
field_0x32cc = 0x4CF;
// Rando gives poe soul after the text, so it'll never hit this
} else if (dComIfGs_getPohSpiritNum() == 60) {
field_0x32cc = 0x4D0;
} else {
@@ -3304,15 +3005,6 @@ int daAlink_c::procCoDeadInit(int param_0) {
field_0x3080 = 0;
mProcVar5.field_0x3012 = 0x3F;
field_0x3198 = -1;
#if TARGET_PC
// trigger archipelago death link if applicable
if (dusk::archi::ArchipelagoContext::IsConnected()) {
dusk::archi::ArchipelagoContext::TryHandleDeathLink();
}
#endif
return 1;
}
@@ -4322,10 +4014,6 @@ int daAlink_c::procGanonFinishInit() {
dusk::m_speedrunInfo.stopRun();
}
}
if (dusk::archi::ArchipelagoContext::IsConnected()) {
dusk::archi::ArchipelagoContext::TryHandleGameComplete();
}
#endif
return 1;
@@ -4860,13 +4548,6 @@ int daAlink_c::procCoWarpInit(int param_0, int param_1) {
(checkItemGet(dItemNo_DUNGEON_BACK_e, 1) &&
strcmp(stageName, dComIfGs_getWarpStageName()) == 0))
{
#if TARGET_PC
// In rando, only clear the Ooccoo slot if Ooccoo is in it
u8 ooccooSlot = dComIfGs_getItem(SLOT_18, false);
if (!randomizer_IsActive() || ooccooSlot == dItemNo_Randomizer_DUNGEON_EXIT_e ||
ooccooSlot == dItemNo_Randomizer_DUNGEON_EXIT_2_e ||
ooccooSlot == dItemNo_Randomizer_LV7_DUNGEON_EXIT_e)
#endif
dComIfGs_setItem(SLOT_18, dItemNo_NONE_e);
dComIfGs_resetLastWarpAcceptStage();
}
-13
View File
@@ -1191,19 +1191,6 @@ void daAlink_c::setLightningSwordEffect() {
emitter = setEmitter(&field_0x327c[i], effName[i], &current.pos, &shape_angle);
if (emitter != NULL) {
emitter->setGlobalRTMatrix(mSwordModel->getBaseTRMtx());
#if TARGET_PC
// Apply custom light sword glow if applicable
const auto& lightSwordGlowColor = dusk::getSettings().cosmetics.lightSwordGlowColor.getValue();
if (dusk::cosmetics::is_valid_hex_color_str(lightSwordGlowColor)) {
GXColor color = dusk::cosmetics::hex_color_str_to_gx_color(lightSwordGlowColor);
emitter->setGlobalEnvColor(color.r, color.g, color.b);
emitter->setGlobalPrmColor(color.r, color.g, color.b);
} else if (lightSwordGlowColor == "Rainbow") {
GXColor color = dusk::cosmetics::get_rainbow_rgb(127.5f);
emitter->setGlobalEnvColor(color.r, color.g, color.b);
emitter->setGlobalPrmColor(color.r, color.g, color.b);
}
#endif
}
}
} else {
+5 -12
View File
@@ -122,18 +122,11 @@ BOOL daAlink_c::setBodyAngleToCamera() {
var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c);
}
#if TARGET_PC
if (dusk::getSettings().game.enableMouseAim && checkAimInputContext()) {
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)));
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) {
sp8 = mBodyAngle.x;
}
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x;
}
} else {
sp8 = mBodyAngle.x;
@@ -156,7 +149,7 @@ BOOL daAlink_c::setBodyAngleToCamera() {
f32 final_yaw = 0.f;
f32 final_pitch = 0.f;
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) {
f32 gyro_yaw = 0.f;
-9
View File
@@ -18,10 +18,6 @@
#include "c/c_damagereaction.h"
#include <cmath>
#if TARGET_PC
#include "dusk/randomizer/game/tools.h"
#endif
enum B_bq_RES_File_ID {
/* BCK */
/* 0x07 */ BCK_BQ_APPEAR = 0x7,
@@ -900,11 +896,6 @@ static void b_bq_end(b_bq_class* i_this) {
i_this->mMode = 1;
int sw = fopAcM_GetParam(a_this) >> 0x18;
#if TARGET_PC
if (randomizer_IsActive()) {
checkTransformFromWolf(); // If the player is wolf, they will softlock after the defeat cutscene is completed.
}
#endif
dComIfGs_onSwitch(sw, fopAcM_GetRoomNo(a_this));
// fallthrough
}
-9
View File
@@ -4084,15 +4084,6 @@ void daB_DS_c::executeBattle2Dead() {
camera->mCamera.SetTrimSize(0);
dComIfGp_event_reset();
dComIfGs_onStageBossEnemy(0x13);
#if TARGET_PC
if (randomizer_IsActive()) {
// Give the boss item
u8 agDungeonReward = randomizer_getItemAtLocation("Arbiters Grounds Dungeon Reward");
g_randomizerState.addItemToEventQueue(agDungeonReward);
// Set custom item flag
dComIfGs_onItem(0x9E, -1);
}
#endif
/* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */
dComIfGs_onEventBit(0x2010);
fopAcM_delete(this);
-27
View File
@@ -530,33 +530,6 @@ int daDitem_c::execute() {
}
mSound.framework(0, dComIfGp_getReverb(fopAcM_GetRoomNo(this)));
#if TARGET_PC
// Certain items use field models that are too big to fit in link's hands so we want to scale them down to fit.
if (randomizer_IsActive()) {
switch (m_itemNo)
{
case dItemNo_Randomizer_MIRROR_PIECE_1_e:
case dItemNo_Randomizer_MIRROR_PIECE_2_e:
case dItemNo_Randomizer_MIRROR_PIECE_3_e:
case dItemNo_Randomizer_MIRROR_PIECE_4_e:
{
scale.x = 0.05f;
break;
}
case dItemNo_Randomizer_MASTER_SWORD_e:
case dItemNo_Randomizer_LIGHT_SWORD_e:
{
scale.x = 0.001f;
break;
}
default:
{
break;
}
}
}
#endif
return 1;
}
-13
View File
@@ -10,9 +10,6 @@
#include "d/actor/d_a_midna.h"
#include "d/d_door_param2.h"
#include "d/actor/d_a_player.h"
#if TARGET_PC
#include "d/actor/d_a_alink.h"
#endif
#include "d/d_com_inf_game.h"
#include "d/d_msg_object.h"
#include "d/d_map_path_dmap.h"
@@ -316,17 +313,7 @@ int daDoor20_c::checkOpenMsgDoor(int* param_1) {
return 1;
}
field_0x624.init(NULL, msgNo, 0, NULL);
#if TARGET_PC
int rv = 1;
// If we are in SPR, we don't want Yeta's msg flow to prevent us from opening the door if we haven't talked to her.
if (randomizer_IsActive() && !daAlink_c::checkStageName("D_MN11"))
{
rv = field_0x624.checkOpenDoor(this, param_1);
}
#else
int rv = field_0x624.checkOpenDoor(this, param_1);
#endif
dMsgObject_endFlowGroup();
return rv;
}
+3 -13
View File
@@ -11,11 +11,6 @@
#include "f_op/f_op_actor_enemy.h"
#include <cstring>
#if TARGET_PC
#include "dusk/randomizer/game/randomizer_context.hpp"
#endif
class daE_HP_HIO_c : public JORReflexible {
public:
daE_HP_HIO_c();
@@ -731,16 +726,11 @@ void daE_HP_c::executeDead() {
fopAcM_onSwitch(this, bitSw);
}
#if TARGET_PC
if (randomizer_IsActive()) {
g_randomizerState.handlePoeItem(bitSw);
} else
#endif
dComIfGs_addPohSpiritNum();
dComIfGs_addPohSpiritNum();
field_0x784 = -1;
if (dComIfGs_getPohSpiritNum() == 20 IF_DUSK(&& !randomizer_IsActive())) {
if (dComIfGs_getPohSpiritNum() == 20) {
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[0x1c9]);
}
@@ -762,7 +752,7 @@ void daE_HP_c::executeDead() {
field_0x788 = 1;
}
}
} else if (field_0x788 != 0 IF_DUSK(|| randomizer_IsActive())) {
} else if (field_0x788 != 0) {
fopAcM_createDisappear(this, &current.pos, 8, 3, 0xff);
fopAcM_delete(this);
} else {
+1 -4
View File
@@ -396,10 +396,7 @@ inline int daE_MD_c::create() {
int phase_state = dComIfG_resLoad(&mPhase, "E_MD");
if (phase_state == cPhs_COMPLEATE_e) {
OS_REPORT("E_MD PARAM %x\n", fopAcM_GetParam(this));
// Always create the armor in rando (otherwise ball and chain won't spawn
// if the player leaves and re-enters without getting it)
if (cDmr_SkipInfo != 0 && current.pos.z > -1500.0f IF_DUSK(&& !randomizer_IsActive())) {
if (cDmr_SkipInfo != 0 && current.pos.z > -1500.0f) {
return cPhs_ERROR_e;
}
-9
View File
@@ -21,10 +21,6 @@
#include "Z2AudioLib/Z2Instances.h"
#include <cstring>
#if TARGET_PC
#include "dusk/randomizer/game/tools.h"
#endif
class daE_MK_HIO_c : public JORReflexible {
public:
virtual ~daE_MK_HIO_c() {}
@@ -1730,11 +1726,6 @@ static void demo_camera_end(e_mk_class* i_this) {
work.z = AREG_F(2) + -20.0f;
MtxPosition(&work, &pos);
pos += i_this->crownPos;
#if TARGET_PC
if (randomizer_IsActive()) {
checkTransformFromWolf(); // If the player is wolf, they will void and lose the boomerang check.
}
#endif
fopAcM_createDisappear(actor, &pos, 5, 0, 0xff);
}
+4 -22
View File
@@ -14,10 +14,6 @@
#include "Z2AudioLib/Z2Instances.h"
#include <cstring>
#if TARGET_PC
#include "dusk/randomizer/game/randomizer_context.hpp"
#include "dusk/randomizer/game/tools.h"
#endif
static s16 mAttackNo = 3;
@@ -1136,17 +1132,11 @@ static void e_po_dead(e_po_class* i_this) {
camera_player->mCamera.Start();
camera_player->mCamera.SetTrimSize(0);
dComIfGp_event_reset();
#if TARGET_PC
if (!randomizer_IsActive()) {
#endif
dComIfGs_addPohSpiritNum();
dComIfGs_addPohSpiritNum();
#if !PLATFORM_SHIELD
if (dComIfGs_getPohSpiritNum() == 0x14) {
/* dSv_event_flag_c::F_0457 - Castle Town - Revived cat */
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[457]);
}
#endif
#if TARGET_PC
if (dComIfGs_getPohSpiritNum() == 0x14) {
/* dSv_event_flag_c::F_0457 - Castle Town - Revived cat */
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[457]);
}
#endif
daPy_getPlayerActorClass()->cancelOriginalDemo();
@@ -1275,14 +1265,6 @@ static void e_po_dead(e_po_class* i_this) {
}
} else {
if (i_this->field_0x75C == -1) {
#if TARGET_PC
if (randomizer_IsActive()) {
u16 key = getStageID() << 8 | i_this->BitSW;
u8 itemId = randomizer_GetContext().mPoeOverrides[key];
i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, itemId, 0,
-1, -1, NULL, NULL);
} else
#endif
i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, 0xE0, 0,
-1, -1, NULL, NULL);
}
-4
View File
@@ -204,11 +204,7 @@ static void e_rb_move(e_rb_class* i_this) {
spC.x = i_this->field_0xa10.x - enemy->current.pos.x;
spC.z = i_this->field_0xa10.z - enemy->current.pos.z;
#if AVOID_UB
f32 speed_target{};
#else
f32 speed_target;
#endif
switch (i_this->mode) {
case 0:
i_this->mode = 1;
-10
View File
@@ -16,10 +16,6 @@
#include "f_op/f_op_camera_mng.h"
#include <cstring>
#if TARGET_PC
#include "dusk/randomizer/game/verify_item_functions.h"
#endif
class daE_RDB_HIO_c : public JORReflexible {
public:
daE_RDB_HIO_c();
@@ -1254,12 +1250,6 @@ static void demo_camera(e_rdb_class* i_this) {
}
if (iVar1 != 0) {
#if TARGET_PC
if (randomizer_IsActive()) {
u8 itemId = verifyProgressiveItem(randomizer_getItemAtLocation("Hyrule Castle King Bulblin Key"));
daPy_getPlayerActorClass()->changeDemoMode(11, itemId, 0, 0);
} else // do the next line
#endif
daPy_getPlayerActorClass()->changeDemoMode(11, 32, 0, 0);
i_this->mDemoMode = 12;
i_this->field_0x10aa = 0;
-15
View File
@@ -9,9 +9,6 @@
#include "SSystem/SComponent/c_math.h"
#include "Z2AudioLib/Z2Instances.h"
#include "d/actor/d_a_player.h"
#if TARGET_PC
#include "d/actor/d_a_alink.h"
#endif
#include "d/d_com_inf_game.h"
#include "d/d_path.h"
#include "d/d_s_play.h"
@@ -1690,18 +1687,6 @@ static void demo_camera(e_s1_class* i_this) {
if (i_this->mDemoTimer == 137) {
if (!dComIfGs_isSwitch(i_this->mSwBit, fopAcM_GetRoomNo(a_this))) {
dComIfGs_onSwitch(i_this->mSwBit, fopAcM_GetRoomNo(a_this));
#if TARGET_PC
if (randomizer_IsActive() && daAlink_c::checkStageName("F_SP126")) {
// We check to see if the flag being set is for the UZR portal as a safety precaution.
if (i_this->mSwBit == 0x15 && g_dComIfG_gameInfo.info.getSavedata().getPlayer().getPlayerStatusA().getTransformStatus())
{
// Set the flag to make Iza 1 available and set the memory bit for having talked to her after opening the portal as human.
dComIfGs_onEventBit(0xB02);
dComIfGs_onSwitch(0x37, fopAcM_GetRoomNo(a_this));
}
// Note for the above stuff. This works for now. Eventually would like to adjust this to a FLW patch since I think we could accomplish similar results by having the conversation continue as normal regardless of form, but I haven't looked into it that much.
}
#endif
OS_REPORT("S! BITSW %d\n", i_this->mSwBit);
OS_REPORT("S! BITSW %d\n", dComIfGs_isSwitch(i_this->mSwBit, fopAcM_GetRoomNo(a_this)));
}
+1 -9
View File
@@ -10,9 +10,7 @@
#include "d/d_com_inf_game.h"
#include "d/actor/d_a_player.h"
#include "d/d_s_play.h"
#if TARGET_PC
#include "dusk/randomizer/game/verify_item_functions.h"
#endif
enum daE_TH_ACTION {
ACTION_STOP,
ACTION_SPIN,
@@ -933,12 +931,6 @@ static void get_demo(e_th_ball_class* i_this) {
case 0:
break;
case 1:
#if TARGET_PC
if (randomizer_IsActive()) {
u8 itemId = verifyProgressiveItem(randomizer_getItemAtLocation("Snowpeak Ruins Ball and Chain"));
demo_id = fopAcM_createItemForTrBoxDemo(&i_this->current.pos, itemId, -1, fopAcM_GetRoomNo(i_this), NULL, NULL);
} else
#endif
demo_id = fopAcM_createItemForTrBoxDemo(&i_this->current.pos, dItemNo_IRONBALL_e, -1, fopAcM_GetRoomNo(i_this), NULL, NULL);
JUT_ASSERT(1670, demo_id != fpcM_ERROR_PROCESS_ID_e);
i_this->mDemoMode = 2;

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