mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-10 21:00:55 -04:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c509ff7647 | |||
| 6849baac06 | |||
| f3ca94b338 | |||
| 9b3b827054 | |||
| 4de3c20431 | |||
| 2840843e3c | |||
| 312195920a | |||
| bc11bf3563 | |||
| 36635f8c62 | |||
| 6136d9bbf7 | |||
| 16c573a170 | |||
| e9e16a8ac1 | |||
| 4339e60ba6 | |||
| af5635dd42 | |||
| c876ea558f | |||
| 9b75dc5fd2 | |||
| 5c9c76cc0b | |||
| b474aafe9d | |||
| 84c5729c4e | |||
| e0ab36b7d9 | |||
| d40a30ee13 | |||
| 074b43a089 | |||
| 8e8dc305e6 | |||
| 41389f4a8e | |||
| 8145dec2a1 | |||
| 4089a80a17 | |||
| b772b6d952 | |||
| 42910ab2fd | |||
| f54892b2c2 | |||
| f32e069c4b |
+1
-1
@@ -1,6 +1,6 @@
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: C++03
|
||||
Standard: c++20
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignConsecutiveAssignments: false
|
||||
|
||||
+142
-155
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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})
|
||||
@@ -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()
|
||||
@@ -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}")
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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
@@ -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.
|
||||
Vendored
+1
-1
Submodule extern/aurora updated: 9087a409da...0a5a5d90ef
+34
-105
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
struct Z2SoundStarter;
|
||||
|
||||
class Z2SoundObjBase : public Z2SoundHandles
|
||||
#if DEBUG
|
||||
#if PARTIAL_DEBUG || DEBUG
|
||||
, public JSULink<Z2SoundObjBase>
|
||||
#endif
|
||||
{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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); }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
BASE_ROOM5,
|
||||
BASE_DEMO,
|
||||
|
||||
#if DEBUG
|
||||
#if PARTIAL_DEBUG || DEBUG
|
||||
BASE_DEBUG,
|
||||
#endif
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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, ...) \
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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, \
|
||||
}); \
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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("" 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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%);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "os_report.h"
|
||||
|
||||
Z2SoundObjBase::Z2SoundObjBase()
|
||||
#if DEBUG
|
||||
#if PARTIAL_DEBUG || DEBUG
|
||||
: JSULink<Z2SoundObjBase>(this)
|
||||
#endif
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(¤t.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();
|
||||
}
|
||||
|
||||
@@ -1191,19 +1191,6 @@ void daAlink_c::setLightningSwordEffect() {
|
||||
emitter = setEmitter(&field_0x327c[i], effName[i], ¤t.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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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, ¤t.pos, 8, 3, 0xff);
|
||||
fopAcM_delete(this);
|
||||
} else {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user