diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41758382cc..e81135d5cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,6 +187,8 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v4 + with: + packages: platform-tools - name: Install Android SDK packages run: sdkmanager "platforms;android-36" "build-tools;36.1.0" "ndk;${ANDROID_NDK_VERSION}" diff --git a/.gitmodules b/.gitmodules index 16ad1921cc..881f8f524b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "mods/randomizer"] path = mods/randomizer url = https://github.com/TwilitRealm/dusklight-randomizer.git +[submodule "mods/cosmetics"] + path = mods/cosmetics + url = https://github.com/TwilitRealm/dusklight-cosmetics diff --git a/CMakeLists.txt b/CMakeLists.txt index b721eba8ef..c0ab5aa26e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL Linux) endif () set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE) set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE) +set(AURORA_ENABLE_THP ON CACHE BOOL "Enable THP decoder" FORCE) set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE) add_subdirectory(extern/aurora EXCLUDE_FROM_ALL) target_compile_definitions(aurora_mtx PRIVATE MTX_USE_PS=1) @@ -88,10 +89,11 @@ endif () option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)") option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.") -option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON) 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}) option(DUSK_ENABLE_CODE_MODS "Enable code mods" ON) +option(DUSK_ENABLE_OPUS "Enable loading Opus audio files for mods" OFF) + set(DUSK_HAS_FUNCHOOK OFF) if (DUSK_ENABLE_CODE_MODS AND (NOT APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Darwin")) @@ -133,69 +135,6 @@ if (MSVC) endif () endif () -if (DUSK_MOVIE_SUPPORT) - find_package(libjpeg-turbo 3.0 CONFIG QUIET) - if (libjpeg-turbo_FOUND) - message(STATUS "dusklight: Using system libjpeg-turbo") - else () - message(STATUS "dusklight: Fetching libjpeg-turbo") - include(ExternalProject) - set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install) - if (WIN32) - set(_jpeg_lib ${_jpeg_install_dir}/lib/turbojpeg-static.lib) - else () - set(_jpeg_lib ${_jpeg_install_dir}/lib/libturbojpeg.a) - endif () - set(_jpeg_cmake_args - -DCMAKE_INSTALL_PREFIX=${_jpeg_install_dir} - -DCMAKE_PROJECT_INCLUDE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/WindowsTargetProcessor.cmake - -DENABLE_SHARED=OFF - -DWITH_TURBOJPEG=ON - -DWITH_JAVA=OFF - -DCMAKE_INSTALL_LIBDIR=lib - ) - if (CMAKE_TOOLCHAIN_FILE) - get_filename_component(_jpeg_toolchain_file "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") - list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file}) - endif () - set(_jpeg_passthrough_vars - ANDROID_ABI - ANDROID_PLATFORM - CMAKE_BUILD_TYPE - CMAKE_C_COMPILER - CMAKE_C_COMPILER_LAUNCHER - CMAKE_MAKE_PROGRAM - CMAKE_MSVC_RUNTIME_LIBRARY - CMAKE_MSVC_DEBUG_INFORMATION_FORMAT - CMAKE_OSX_ARCHITECTURES - CMAKE_OSX_DEPLOYMENT_TARGET - CMAKE_OSX_SYSROOT - DEPLOYMENT_TARGET - ENABLE_ARC - ENABLE_BITCODE - PLATFORM - ) - foreach(_var IN LISTS _jpeg_passthrough_vars) - if (DEFINED ${_var}) - list(APPEND _jpeg_cmake_args -D${_var}=${${_var}}) - endif () - endforeach () - ExternalProject_Add(libjpeg-turbo-ext - URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.0.tar.gz - URL_HASH SHA256=35fec2e1ddfb05ecf6d93e50bc57c1e54bc81c16d611ddf6eff73fff266d8285 - CMAKE_ARGS ${_jpeg_cmake_args} - BUILD_BYPRODUCTS ${_jpeg_lib} - ) - file(MAKE_DIRECTORY ${_jpeg_install_dir}/include) - add_library(libjpeg-turbo::turbojpeg-static STATIC IMPORTED GLOBAL) - set_target_properties(libjpeg-turbo::turbojpeg-static PROPERTIES - IMPORTED_LOCATION ${_jpeg_lib} - INTERFACE_INCLUDE_DIRECTORIES ${_jpeg_install_dir}/include - ) - add_dependencies(libjpeg-turbo::turbojpeg-static libjpeg-turbo-ext) - endif () -endif () - if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") # -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers # (CW, GCC, Clang, MSVC) encode them identically in big-endian order. @@ -243,8 +182,34 @@ FetchContent_Declare(miniz DOWNLOAD_EXTRACT_TIMESTAMP TRUE EXCLUDE_FROM_ALL ) +message(STATUS "dusklight: Fetching PicoSHA2") +FetchContent_Declare(picosha2 + URL https://github.com/okdshin/PicoSHA2/archive/refs/tags/v1.0.1.tar.gz + URL_HASH SHA256=9983136544234e573fe07cc1a22fdf978ad7979043e7be2e9082f6d4991ff8a8 + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + EXCLUDE_FROM_ALL +) + +set(_fetch_content_deps miniz picosha2) + +if (DUSK_ENABLE_OPUS) + message(STATUS "dusklight: Fetching opusfile") + + # Opusfile options + SET(OP_DISABLE_HTTP ON CACHE BOOL "" FORCE) + SET(OP_DISABLE_DOCS ON CACHE BOOL "" FORCE) + SET(OP_DISABLE_EXAMPLES ON CACHE BOOL "" FORCE) + + message(STATUS "dusklight: Fetching opusfile") + FetchContent_Declare( + opusfile + GIT_REPOSITORY https://github.com/xiph/opusfile.git + GIT_TAG 6dfd29e7adb87f2e193575fc3fa88cbf1a0b27df + ) + + list(APPEND _fetch_content_deps opusfile) +endif () -set(_fetch_content_deps miniz) if (DUSK_HAS_FUNCHOOK) message(STATUS "dusklight: Fetching funchook") # cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a @@ -279,6 +244,14 @@ if (DUSK_HAS_FUNCHOOK) endif () FetchContent_MakeAvailable(${_fetch_content_deps}) +if (DUSK_HAS_FUNCHOOK AND APPLE) + target_sources(funchook-static PRIVATE src/dusk/mods/loader/code_patch_macos.cpp) + target_include_directories(funchook-static PRIVATE src/dusk/mods/loader) + set_source_files_properties(src/dusk/mods/loader/code_patch_macos.cpp + TARGET_DIRECTORY funchook-static PROPERTIES + COMPILE_OPTIONS "-O2;-fno-sanitize=all;-fno-stack-protector") +endif () + # Use signed char on ARM to match the original game (and x86) string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _arch) if(_arch MATCHES "^(arm|aarch64)" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") @@ -303,26 +276,21 @@ include(cmake/GameABIConfig.cmake) find_package(Threads REQUIRED) set(GAME_COMPILE_DEFS DUSK_BUILDING_GAME=1) -set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd - aurora::card borealis::cli borealis::crash borealis::data borealis::disc borealis::discord borealis::file_select borealis::io borealis::log borealis::presentation borealis::sentry borealis::update freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt - Threads::Threads zstd::libzstd dusklight_game_headers) +set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::thp + aurora::card borealis::http borealis::cli borealis::crash borealis::data borealis::disc borealis::discord borealis::file_select borealis::io borealis::log borealis::net borealis::presentation borealis::sentry borealis::update borealis::ws freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt + Threads::Threads zstd::libzstd dusklight_game_headers picosha2 PNG::PNG) if (DUSK_HAS_FUNCHOOK) list(APPEND GAME_LIBS funchook-static) endif () +if (DUSK_ENABLE_OPUS) + list(APPEND GAME_LIBS OpusFile::opusfile) + list(APPEND GAME_COMPILE_DEFS DUSK_OPUS=1) +endif () if (WIN32) list(APPEND GAME_LIBS Ws2_32) endif () -if (DUSK_MOVIE_SUPPORT) - if (TARGET libjpeg-turbo::turbojpeg-static) - list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) - else () - list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg) - endif () - list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) -endif () - if (DUSK_ENABLE_CODE_MODS) list(APPEND GAME_COMPILE_DEFS DUSK_CODE_MODS=1) endif () @@ -565,13 +533,16 @@ 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(mods/luau_runtime) add_subdirectory(mods/template_mod) add_subdirectory(mods/ao_mod) add_subdirectory(mods/shadow_mod) add_subdirectory(mods/window_demo) add_subdirectory(mods/flow_demo) - add_subdirectory(mods/basic_cosmetics_mod) + add_subdirectory(mods/custom_actor_demo) + add_subdirectory(mods/cosmetics) add_subdirectory(mods/randomizer) + add_subdirectory(mods/audio_mod) endif () if (APPLE) diff --git a/README.md b/README.md index 473efd8b68..7445e43ea9 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ Dusklight is a reverse-engineered reimplementation of Twilight Princess. It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience. +> [!IMPORTANT] +> Dusklight's official website is https://twilitrealm.dev/, any other website is not affiliated and may be promoting AI-generated misinformation. + # Setup > [!IMPORTANT] diff --git a/cmake/ModSDK.cmake b/cmake/ModSDK.cmake index ec24beba33..db3ca231d0 100644 --- a/cmake/ModSDK.cmake +++ b/cmake/ModSDK.cmake @@ -296,8 +296,6 @@ function(add_mod target_name) endif () target_link_libraries(${target_name} PRIVATE "${_game_lib}") endif () - set_target_properties(${target_name} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") - target_compile_definitions(${target_name} PRIVATE _ITERATOR_DEBUG_LEVEL=0) endif () endif () if (ARG_RUNTIME_LIBRARIES AND NOT _has_lib) diff --git a/cmake/PatchFunchook.cmake b/cmake/PatchFunchook.cmake index 6d5a6e36b4..cbd91fc38e 100644 --- a/cmake/PatchFunchook.cmake +++ b/cmake/PatchFunchook.cmake @@ -2,59 +2,55 @@ 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}" +if (NOT _content MATCHES "CAPSTONE_FIX_SCRIPT") + 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}") + _content "${_content}") + file(WRITE "${SOURCE_DIR}/cmake/capstone.cmake.in" "${_content}") endif () -file(WRITE "${SOURCE_DIR}/src/funchook_unix.c" "${_unix_content}") +file(READ "${SOURCE_DIR}/src/funchook.c" _content) +if (NOT _content MATCHES "commit_code_patch") + string(REPLACE "#include \"funchook_internal.h\"" + "#include \"funchook_internal.h\"\n#ifdef __APPLE__\n#include \"code_patch_macos.hpp\"\n#endif" + _content "${_content}") + + foreach(_operation install uninstall) + if (_operation STREQUAL "install") + set(_expected old_code) + set(_replacement new_code) + else () + set(_expected new_code) + set(_replacement old_code) + endif () + set(_original " mem_state_t mstate; + int rv = funchook_unprotect_begin(funchook, &mstate, entry->target_func, JUMP32_BYTE_SIZE); + + if (rv != 0) { + return rv; + } + memcpy(entry->target_func, entry->${_replacement}, JUMP32_BYTE_SIZE); + rv = funchook_unprotect_end(funchook, &mstate); + if (rv != 0) { + return rv; + } + flush_instruction_cache(entry->target_func, JUMP32_BYTE_SIZE);") + string(FIND "${_content}" "${_original}" _position) + if (_position EQUAL -1) + message(FATAL_ERROR "Funchook ${_operation} patch site changed") + endif () + string(REPLACE "${_original}" "#ifdef __APPLE__ + int rv = commit_code_patch(entry->target_func, entry->${_expected}, + entry->${_replacement}, JUMP32_BYTE_SIZE); + if (rv != 0) { + funchook_set_error_message(funchook, \"Code patch commit failed (Mach error %d)\", rv); + return FUNCHOOK_ERROR_MEMORY_FUNCTION; + } +#else +${_original} +#endif" _content "${_content}") + endforeach () + file(WRITE "${SOURCE_DIR}/src/funchook.c" "${_content}") +endif () diff --git a/cmake/SymbolManifest.cmake b/cmake/SymbolManifest.cmake index 7c7e09eaa1..5f4347e005 100644 --- a/cmake/SymbolManifest.cmake +++ b/cmake/SymbolManifest.cmake @@ -2,7 +2,7 @@ include_guard(GLOBAL) get_filename_component(_SYMBOL_MANIFEST_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) -set(_SYMGEN_VERSION "1.3.4") +set(_SYMGEN_VERSION "1.3.6") 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) @@ -15,32 +15,32 @@ function(symgen_host_asset out_name out_hash) if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") if (_host_processor MATCHES "^(arm64|aarch64)$") set(_asset "symgen-macos-arm64") - set(_asset_hash "SHA256=983ef6278d30bee38f240f451ca736fd294da0e5705ee15029accac5c7a33829") + set(_asset_hash "SHA256=6b836f4fd7e57fc198ca6bfb3e0161504c98f2be2622819849bfc3f265edf4af") elseif (_host_processor MATCHES "^(x86_64|amd64)$") set(_asset "symgen-macos-x86_64") - set(_asset_hash "SHA256=b6ab1720a4f04fadcf291f42d133e2ee68ef2e9a75d143e51584929e808af8a2") + set(_asset_hash "SHA256=93218f786d34d3ec58f96624e0494ff605e6102cdb31cd7c9922364cfc7885a5") endif () elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (_host_processor MATCHES "^(aarch64|arm64)$") set(_asset "symgen-linux-aarch64") - set(_asset_hash "SHA256=ba84486ff1ceb753973e6213c9bc5f195b5ff6f220f5d3322fd861966cdc791c") + set(_asset_hash "SHA256=7891446505c4dd428f4b84c7ceed1441719d910b3d5a47d3540aa890538449ae") elseif (_host_processor MATCHES "^(x86_64|amd64)$") set(_asset "symgen-linux-x86_64") - set(_asset_hash "SHA256=8b81a00a2ef8f5dd4b59adba1badfaca57e472bf272516bc12c4625cefb37718") + set(_asset_hash "SHA256=236bc5cd4bda03a8dd53a4531efff1fec3bcd19b09890893b9b6f8f5ae77f041") elseif (_host_processor MATCHES "^(i[3-6]86|x86)$") set(_asset "symgen-linux-i686") - set(_asset_hash "SHA256=01aa85d84148a6806fdf69839dea7e962e6ac46956aed63b9ecb7d596f179f93") + set(_asset_hash "SHA256=e3b4e8e978d630f07ed6aa58d387145356026116b2e8fa99801902dfac73b2ea") endif () elseif (CMAKE_HOST_WIN32) if (_host_processor MATCHES "^(arm64|aarch64)$") set(_asset "symgen-windows-arm64.exe") - set(_asset_hash "SHA256=e116099f859177d7e29fc3444d67083596330e5c4fb3a0461b21402c89249d96") + set(_asset_hash "SHA256=36e2c68bb4f9e3abea728c4cd7ddc01ff1f462be7156af18baa20621b7d39df8") elseif (_host_processor MATCHES "^(x86_64|amd64)$") set(_asset "symgen-windows-x86_64.exe") - set(_asset_hash "SHA256=57ef1c2f563e33fa4915c40194c4ced540f985c48d9cf081047c6bbcb585fa37") + set(_asset_hash "SHA256=171995a33bfd5a5271b5bb78d3f2c1d4aec58721bf3a894aaad5607ff648e2a4") elseif (_host_processor MATCHES "^(i[3-6]86|x86)$") set(_asset "symgen-windows-x86.exe") - set(_asset_hash "SHA256=cc0ba337c798d3f63d8acbb2d16d531d0c2da878fbdd2b6307702334ec880528") + set(_asset_hash "SHA256=9ed095a01e05eb05e91472bfa7b95b84dfeaeead0941a80708196199c95188db") endif () endif () @@ -116,6 +116,7 @@ function(setup_symbol_manifest target) return() endif () add_dependencies(${target} symgen) + set_property(TARGET ${target} APPEND PROPERTY LINK_DEPENDS "${SYMGEN_EXE}") # Reserve an ELF program-header entry when the linker supports it (mold). # symgen can replace the PT_NULL entry without relocating the table, keeping later post-link tools safe. @@ -148,6 +149,7 @@ function(setup_symbol_manifest target) # signature. add_custom_command(TARGET ${target} POST_BUILD COMMAND "${SYMGEN_EXE}" manifest ${_input} --embed "$" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" COMMENT "Embedding symbol manifest" VERBATIM) endfunction() diff --git a/docs/images/jaudio.mermaid b/docs/images/jaudio.mermaid new file mode 100644 index 0000000000..e5e3ee221e --- /dev/null +++ b/docs/images/jaudio.mermaid @@ -0,0 +1,58 @@ +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + subgraph BSC + bms_start[Look up BMS start] + end + Start-->|Sound ID|BSC + + BSC-->|BMS data| bms[BMS interpreter] + + subgraph IBNK + bnk[Look up instrument parameters] + end + + bms-->|Plays notes| IBNK + + subgraph WSYS + wsys[Look up wave sample] + end + + IBNK-->|Wave ID|WSYS + + +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + BST-->|File path| ast[Stream /AudioRes/Stream/xxx.ast from disc] + + +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + BST-->|Resource ID| arc[Z2SoundSeqs.arc] + arc-->|BMS file| bms[BMS interpreter] + + subgraph IBNK + bnk[Look up instrument parameters] + end + + bms-->|Plays notes| IBNK + + subgraph WSYS + wsys[Look up wave sample] + end + + IBNK-->|Wave ID|WSYS diff --git a/docs/images/jaudio_se_flow.svg b/docs/images/jaudio_se_flow.svg new file mode 100644 index 0000000000..03d4bbb29b --- /dev/null +++ b/docs/images/jaudio_se_flow.svg @@ -0,0 +1 @@ +

Sound ID

Sound ID

BMS data

Plays notes

Wave ID

WSYS

Look up wave sample

IBNK

Look up instrument parameters

BSC

Look up BMS start

BST

Look up sound parameters

Start Sound Effect

BMS interpreter

\ No newline at end of file diff --git a/docs/images/jaudio_seq_flow.svg b/docs/images/jaudio_seq_flow.svg new file mode 100644 index 0000000000..f39ee7ca5b --- /dev/null +++ b/docs/images/jaudio_seq_flow.svg @@ -0,0 +1 @@ +

BMS file

Sound ID

Resource ID

Plays notes

Wave ID

WSYS

Look up wave sample

IBNK

Look up instrument parameters

BST

Look up sound parameters

Start Sound Effect

Z2SoundSeqs.arc

BMS interpreter

\ No newline at end of file diff --git a/docs/images/jaudio_stream_flow.svg b/docs/images/jaudio_stream_flow.svg new file mode 100644 index 0000000000..47bee926eb --- /dev/null +++ b/docs/images/jaudio_stream_flow.svg @@ -0,0 +1 @@ +

Sound ID

File path

BST

Look up sound parameters

Start Sound Effect

Stream /AudioRes/Stream/xxx.ast from disc

\ No newline at end of file diff --git a/docs/imhex/jaudio/bsc.hexpat b/docs/imhex/jaudio/bsc.hexpat new file mode 100644 index 0000000000..a9f8e3ca6e --- /dev/null +++ b/docs/imhex/jaudio/bsc.hexpat @@ -0,0 +1,26 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +struct JAUSeqCollectionTable { + u32 mEntryCount; + u32 mBmsOffset[mEntryCount]; +}; + +struct JAUSeqCollectionData { + type::Magic<"SC"> mMagic; + u16 mNumSoundCategories; + u32 mSectionSize; + Offset mTableOffsets[mNumSoundCategories]; +}; + +JAUSeqCollectionData data @ 0x0; diff --git a/docs/imhex/jaudio/bst.hexpat b/docs/imhex/jaudio/bst.hexpat new file mode 100644 index 0000000000..07bf28684e --- /dev/null +++ b/docs/imhex/jaudio/bst.hexpat @@ -0,0 +1,71 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +struct ItemSoundEffect { + u8 mPriority; + u8 mVolume; + padding[2]; + u32 mSwBit; + float mPitch; +}; + +struct ItemSequence { + u8 mPriority; + u8 mVolume; + u16 mResourceId; +}; + +struct ItemStream { + u8 mPriority; + u8 mVolume; + u16 mStreamPanParameters; + char* mStreamFilePath[] : u32; +}; + +struct SoundTableGroupEntry { + u8 mTypeId; + if (mTypeId == 0x51) { + ItemSoundEffect* mOffset : u24 [[inline]]; + } else if (mTypeId == 0x60) { + ItemSequence* mOffset : u24 [[inline]]; + } else if (mTypeId == 0x70 || mTypeId == 0x71) { + ItemStream* mOffset : u24 [[inline]]; + } else { + u24 mOffset; + } +}; + +struct TGroup { + u32 mNumItems; + padding[4]; + SoundTableGroupEntry mEntries[mNumItems]; +}; + +struct TSection { + u32 mNumGroups; + Offset mGroupOffsets[mNumGroups]; +}; + +struct Root { + u32 mSectionNumber; + Offset mSectionOffsets[mSectionNumber]; +}; + +struct THeader { + type::Magic<"BST "> mMagic; + + padding[8]; + Root* mRoot : u32; +}; + +THeader header @ 0x0; \ No newline at end of file diff --git a/docs/imhex/jaudio/wsys.hexpat b/docs/imhex/jaudio/wsys.hexpat new file mode 100644 index 0000000000..aa131ea226 --- /dev/null +++ b/docs/imhex/jaudio/wsys.hexpat @@ -0,0 +1,228 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/* +WSYS file +The WSYS file contains roughly two sections: WBCT and WINF. +WBCT contains the mapping of Wave ID -> wave archives. +WINF are the wave archives themselves and their metadata. +*/ + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +/** + * Format that audio data is in.. + */ +enum WaveFormat : u8 { + /** + * 16-samples-per-9-bytes custom Nintendo ADPCM. + */ + ADPCM4 = 0, + + /** + * 16-samples-per-5-bytes custom Nintendo ADPCM. + */ + ADPCM2 = 1, + + /** + * 8-bit-per-sample PCM. + */ + PCM8 = 2, + + /** + * 16-bit-per-sample PCM. + */ + PCM16 = 3, +}; + +/** + * Defines playback info for a single audio "wave". + * This data is stored per archive, making it duplicated (except for mAWOffsetStart) + */ +struct TWave { + padding[1]; // unknown + WaveFormat mWaveFormat; + + /** + * Key (as in like, the musical term) this sample is in. + */ + u8 mBaseKey; + padding[1]; + + /** + * Sample rate of the audio in Hz. + */ + float mSampleRate; + + /** + * Position where the sample data starts. + * This is in the .aw file for the archive containing this TWave. + */ + u32 mAWOffsetStart; + + /** + * Byte length of the sample data. + */ + u32 mAWLength; + + /** + * Indicates whether the sample should loop or not. All bits appear set if so. + */ + u32 mLoopFlags; + + /** + * Audio sample at which the loop starts. + */ + u32 mLoopStartSample; + + /** + * Audio sample at which the loop ends (and goes back to mLoopStartSample). + */ + u32 mLoopEndSample; + + /** + * Total sample count in this wave. + */ + u32 mSampleCount; + + /** + * Last sample for continuing ADPCM decode after loop. + */ + s16 mpLast; + + /** + * Penult sample for continuing ADPCM decode after loop. + */ + s16 mpPenult; +}; + +/** + * A single wave archive on disk. + * These are paired 1:1 with TCtrlScene objects. + */ +struct TWaveArchive { + /** + * Filename of the raw sample data on disc. Relative to /Audiores/Waves/ + */ + char mFileName[0x70]; + + /** + * Amount of waves in this archive. + * Matches the count in the paired TCtrl object. + */ + u32 mWaveCount; + + /** + * Offsets to the wave metadata (not sample data) in the WSYS. + * These are paired 1:1 to the TCtrlWaves, and the TCtrlWave contains the actual + * "Wave ID" used by the game for lookups. + */ + Offset waveOffsets[mWaveCount]; +}; + +/** + * Header containing data for wave archives and their metadata. + */ +struct TWaveArchiveBank { + type::Magic<"WINF"> mMagic; + + /** + * Amount of archives in this wave bank. + * Matches the value in TCtrlGroup. + */ + u32 mArchiveCounts; + Offset mArchiveOffsets[mArchiveCounts]; +}; + +/** + * Definition for a single wave in a control group. + */ +struct TCtrlWave { + /** + * Group ID matches the index of the control group this item is referenced by. + */ + u16 mGroupId; + + /** + * Wave ID used by the game to look this wave up. + */ + u16 mWaveId; +}; + +/** + * Contains the actual data for a TCtrlScene. + * Why is this separate? Who knows. + */ +struct TCtrl { + // Other versions of this struct with different magic (C-EX and C-ST) also exist in the file. + // They aren't pointed to so we don't need to worry about them. + type::Magic<"C-DF"> mMagic; + + /** + * Amount of waves in this group. + * Matches the value in TWaveArchive. + */ + u32 waveCount; + Offset mWaveOffsets[waveCount]; +}; + +/** + * A single scene or "group" of waves that are loaded at once. + */ +struct TCtrlScene { + type::Magic<"SCNE"> mMagic; + + padding[8]; // unknown + TCtrl* mCtrlOffset : u32 [[inline]]; +}; + +/** + * Contains the "control" section of the WSYS. + */ +struct TCtrlGroup { + type::Magic<"WBCT"> mMagic; + + padding[4]; // unknown + u32 mGroupCount; + + Offset mCtrlSceneOffsets[mGroupCount]; +}; + +struct THeader { + type::Magic<"WSYS"> mMagic; + + /** + * Size of WSYS in bytes. + */ + u32 mSize; + + /** + * ID of wave bank. + * This matches the value passed to the BAA load command. + * The game originally does not use this value itself, but Dusklight does rely on it. + */ + u32 mId; + + /** + * Total amount of waves in this wave bank. + * (not groups! Waves!) + */ + u32 mWaveTableSize; + + TWaveArchiveBank* archiveBankOffset : u32; + TCtrlGroup* ctrlGroupOffset : u32; +}; + +THeader header @ 0x0; + +std::assert( + header.archiveBankOffset.mArchiveCounts == header.ctrlGroupOffset.mGroupCount, + "Control group and archive count does not match!"); \ No newline at end of file diff --git a/docs/jaudio.md b/docs/jaudio.md new file mode 100644 index 0000000000..84a0d9dcfa --- /dev/null +++ b/docs/jaudio.md @@ -0,0 +1,241 @@ +# JAudio + +**JAudio** is the name for the audio engine used by Twilight Princess (along with many other Nintendo games from the era). TP uses exclusively JAudio v2, while other games use v1 or a mix of v1 and v2 infrastructure. This document will primarily focus on TP's use case, but best efforts will be made to document where behavior is TP-specific. + +## Common concepts + +Audio is almost exclusively + +### `JAISoundID` + +A "sound", be that a _sound effect_ or music, is referenced in-code through the `JAISoundID` type. This is a `u32` that the game uses to look up the relevant sound. The actual value is bitpacked (BE) from the following fields: + +| Type | Field | Description | +|-------|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `u8` | Section ID | Section of the Sound Table this sound effect is located in. 0 for sound effects, 1 for sequenced music, 2 for streamed music.[^sectionids] | +| `u8` | Group ID | ID of group further used to organize inside the Sound Table's Section. Sound effects are grouped into things like "SYSTEM" and "ENEMY", other sections leave this at 0. | +| `u16` | "Wave ID" | Index inside the group to look up the sound at.
**Note** that the term "wave" in code is extremely confusing[^waveterm]; it does **not** refer to audio samples ("waves") in the wave banks directly. | + +[^sectionids]: JAudio itself can seemingly work outside this convention, however it is enforced by some TP-specific game code. + +[^waveterm]: Possibly vestigial from JAudio v1, where I believe there were less layers of indirection. + +## Disc files + +All audio data is stored in `/Audiores` on the disc. Files are as follows: + +### `/Audiores/Seqs/Z2SoundSeqs.arc` + +Contains the BMS instructions for all BMS-based music. Not all data is kept in memory at once. + +### `/Audiores/Stream/*.ast` + +Contains individual streamed music. Each file is a separate music track. See [this page](https://www.lumasworkshop.com/wiki/AST_(File_Format)) for file format description. + +### `/Audiores/Waves/*.aw` + +Contains audio sample data for sequenced music and sound effects. These files are pure meat, no bone: they are loaded directly into ARAM and all metadata is stored in the BAA WSYS sections. + +Each file contains audio samples for one "scene", with factors like the current level determining what "scenes" are made resident in memory. There is tons of duplicate data between scenes, presumably to increase simplicity and loading performance. + +### `/Audiores/Z2Sound.baa` + +Contains all remaining metadata for the audio system. This is effectively a container for a bunch of different sub-sections. The file starts with a bunch of "commands" that indicate where other data in the file is. Each command has a 4-character identifier and depending on the command will be followed by some extra arguments before the next command. + +The commands used by TP's BAA file are as follows (note that the decompiled code has support for more load commands, which are unused): + +| Command/Argument | Value | Description | +|------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------| +| Command | `AA_<` | Start of BAA commands. Must be at the start of the file and only appear once. | +| Command | `>_AA` | End of BAA commands. | +| Command | `ws `[^spaces] | Wave bank/WSYS data. Defines where audio samples are located on disc. | +| Argument | u32 | Wave bank ID, max 255. In TP, 0 is sound effects, 1 is music samples. | +| Argument | u32 | File offset for start of `WSYS` data | +| Argument | u32 | Bit field selecting which groups (= `.aw` files) to load immediately. TP leaves this at zero.[^32ws] | +| Command | `bnk `[^spaces] | Instrument bank/IBNK | +| Argument | u32 | Target wave bank ID | +| Argument | u32 | File offset for start of `IBNK` data | +| Command | `bsc `[^spaces] | Sound effect sequence collection. | +| Argument | u32 | File offset for start of `SC` data. | +| Argument | u32 | File offset for end of `SC` data. | +| Command | `bst `[^spaces] | Sound table. Defines parameters for music and sound effects. | +| Argument | u32 | File offset for start of `BST `[^spaces] data. | +| Argument | u32 | File offset for end of `BST `[^spaces] data. | +| Command | `bstn` | Sound name table. Defines names of all music and sound effects. Present on disc, but loading is disabled on release versions of the game. | +| Argument | u32 | File offset for start of `BSTN` data. | +| Argument | u32 | File offset for end of `BSTN` data. | +| Command | `bfca` | Unknown, something related to initialization of DSP FX data. | +| Argument | u32 | File offset for start of `RARC` data. | + +[^spaces]: Padded with spaces. + +[^32ws]: Both of TP's wave banks have far more than 32 groups, and seemingly this mechanism would not be able to deal with that. + +Following is data descriptions for the remaining data in the `.BAA`, as pointed to by the above commands. + +### `WSYS` / Wave banks + +`WSYS` / Wave bank data defines where a set of audio samples can be found on disc. Each wave bank is made of multiple "groups", where each group corresponds to one `.aw` file on disc. Each group has a set of "wave IDs" it contains, along with the metadata (e.g. sample rate) and data offset in the `.aw` file. + +Multiple groups can contain the same wave ID, thus meaning the raw audio samples can be duplicated on disk. + +*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.* For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat) + +### `BST ` / Sound Table + +The `BST` / Sound table defines various parameters for music and sound effect playback. + +The layout is pretty simple: it's hierarchical with sections (sound effects, music sequences, streamed music)[^sectionids], +which have groups (only sound effects use this) and each group just has a flat list of items. +These indices match directly to the fields of the `JAISoundID`. + +*Relevant classes: `JAUSoundTable`.* For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/bst.hexpat) + +Each item has a Type ID and a set of data. Types used by TP are as follows: + +#### `0x51` / sound effect + +Defines that this is a sound effect. The actual BMS code to execute (and unlike the other types, including its location) is looked up in the BSC. + +Layout: + +``` +u8 mPriority; // Priority relative to other sound effects. +u8 mVolume; // Converted to float: mVolume * (1.0/127.0) +padding[2]; +u32 mSwBit; // See below. +float mPitch; // Pitch multiplier +``` + +```cpp +// Values for mSwBit on sound effect items. (taken from JAUSoundTable.h) + +/** + * Sound is always calculated as max priority (0). + */ +#define SOUND_SW_ALWAYS_MAX_PRIORITY 0x0000'0001 + +/** + * Don't calculate volume by distance. + */ +#define SOUND_SW_IGNORE_DISTANCE_VOL 0x0000'0002 + +/** + * Don't calculate FX mix (reverb) by distance. + */ +#define SOUND_SW_IGNORE_FX_MIX 0x0000'0004 + +/** + * Mute all BGM sequences while this sound is playing. + */ +#define SOUND_SW_MUTE_BGM 0x0000'0008 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK + */ +#define SOUND_SW_RANDOM_PITCH_OFFSET 4 + +/** + * 4-bit value (0-15) to control the power of pitch randomization on sound playback. + * Code acts different for values above 8, not sure what the exact implication is. + */ +#define SOUND_SW_RANDOM_PITCH_MASK 0x0000'00F0 + +/** + * Offset to shift to access @see SOUND_SW_DOPPLER_POWER_MASK + */ +#define SOUND_SW_DOPPLER_POWER_OFFSET 8 + +/** + * 4-bit value (0-15) to scale the power of the Doppler effect for this sound. + */ +#define SOUND_SW_DOPPLER_POWER_MASK 0x0000'0F00 + +/** + * Don't calculate panning (left/right) values for this sound. + */ +#define SOUND_SW_IGNORE_PAN 0x0000'1000 + +/** + * Don't calculate Dolby (behind/front) values for this sound. + */ +#define SOUND_SW_IGNORE_DOLBY 0x0000'2000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_1 0x0000'4000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_2 0x0000'8000 + +/** + * 3-bit mask used to select a volume distance/falloff class for this sound. + */ +#define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000 +#define SOUND_SW_VOL_DIST_BIT_OFFSET 16 + +/** + * Limit minimum volume of this sound (after distance falloff) to 0.2. + */ +#define SOUND_SW_CLAMP_MIN_VOLUME 0x0008'0000 + +/** + * 3-bit mask used to select a *different* volume distance/falloff class for this sound. + * @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work. + */ +#define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000 +#define SOUND_SW_VOL_DIST_BIT_2_OFFSET 20 + +/** + * Mark sound as "far away" or "culled" when at max distance (selected by distance class). + * This affects a bunch of stuff like culling, automatic stopping, priorities, etc. + */ +#define SOUND_SW_CULL_AT_MAX_DISTANCE 0x0080'0000 + +/** + * Not sure what this does. + * Something causing volume/pan/dolby adjustment in Z2Audible::setOuterParams? + */ +#define SOUND_SW_VOL_SOMETHING_MASK 0x0F00'0000 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_VOLUME_MASK + */ +#define SOUND_SW_RANDOM_VOLUME_OFFSET 28 + +/** + * 4-bit value (0-15) to control the power of volume randomization on sound playback. + */ +#define SOUND_SW_RANDOM_VOLUME_MASK 0xF000'0000 +``` + +#### `0x60` / music sequence + +Defines background music that plays through the BMS system. + +``` +u8 mPriority; +u8 mVolume; // Converted to float: mVolume * (1.0/127.0) +u16 mResourceId; // ID to look up in Z2SoundSeqs.arc +``` + +#### `0x70` / `0x71` / music sequence + +Defines a streamed music track. Difference between `0x70` and `0x71` seems to only be that `0x71` stops automatically +on scene changes in TP's game code. + +``` +u8 mPriority; +u8 mVolume; // Converted to float: mVolume * (1.0/127.0) +u16 mStreamPanParameters; // Bitpacked, two bits per channel determining whether a channel is center (01), left (10), or right (11). +char* mStreamFilePath[] : u32; // File path to the .ast on disc. +``` + +## Further reading & credits + +* https://www.lumasworkshop.com/wiki/SMR.szs (note that details like the exact layout of the BAA are not the same as TP) +* XAYRGA for doing much RE work and making [JAMTools](https://xayr.gay/tools/SoundModdingToolkit/) +* The decompiled source code, duh. diff --git a/docs/modding.md b/docs/modding.md index 4ad29aa8da..b39000d24c 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -1,35 +1,137 @@ # Dusklight Mod API -Mods are `.dusk` bundles: zip archives that can contain code (in the form of native libraries), resources, DVD overlay +Mods are `.dusk` bundles: zip archives that can contain code (native libraries or scripts), 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 +There are three types of mods: + +- **Asset-only mods**: Mods that contain no code, and may replace files on the game disc ("overlays") and provide + replacement textures (texture packs). +- **Native mods (C++)**: Fully-featured, with the ability to interop with game code and hook functions. Must be compiled + for every supported platform. (See [mod-template](https://github.com/TwilitRealm/mod-template)) +- **Script mods (Luau)**: Simple, widely-compatible, but can only access provided services. + +## Table of Contents + +1. [mod.json](#modjson) +2. [Asset-only Mods](#asset-only-mods) +3. [Native Mods (C++)](#native-mods) +4. [Script Mods (Luau)](#script-mods) +5. [Services](#services) +6. [Built-in Services](#built-in-services) +7. [Hooking Game Functions](#hooking-game-functions) +8. [Asset Overlays](#asset-overlays) +9. [Runtime Lifecycle](#runtime-lifecycle) +10. [Error Handling](#error-handling) +11. [Advanced](#advanced) + +--- + +## mod.json + +Every mod starts with a single file, a `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/icon.png", + "banner": "res/banner.png" +} +``` + +`id` is required: a unique, stable identifier (reverse-DNS style; periods, underscores, and lowercase alphanumerics). +Everything else is optional but recommended. + +`icon` and `banner` are bundle paths to PNG images that display in the in-game mod manager and mod website. A square +icon (1:1), and a banner (~3.5:1, minimum 800px width). If omitted, `res/icon.png` and `res/banner.png` are used +automatically when present. + +Simply create a zip file with a `mod.json`, and rename it to `.dusk`. That's it! + +--- + +## Asset-only Mods + +``` +my_mod.dusk +├── mod.json +├── res/ (optional bundled resources) +├── overlay/ (optional game file overrides) +└── textures/ (optional texture replacements) +``` + +Place files in `overlay/` to replace the disc version of that file. Examples: + +- `overlay/Movie/demo_movie98_00.thp`: Replaces `Movie/demo_movie98_00.thp` on disc. +- `overlay/res/Object/Kmdl/archive/bmwr/al.bmd`: Replaces `archive/bmwr/al.bmd` _within_ `res/Object/Kmdl.arc` without + overwriting the entire `.arc`. + +Place textures in `textures/` to automatically register them as texture replacements when active. These follow the same +[Dolphin-compatible naming scheme](#textureservice-modssvctextureh) as the user `/texture_replacements/` +directory. Directories are scanned recursively. Examples: + +- `textures/tex1_256x128_e6a4c7be9bf48305_14.dds`: Replaces the Hero's Clothes texture. + +Simply zip the `mod.json` and adjacent folders, then rename to `.dusk`. Then, 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` + +--- + +## Native Mods + +Mods built with [mod-template](https://github.com/TwilitRealm/mod-template) are native C++ mods. Native mods are very +powerful and can interact with and [hook](#hooking-game-functions) game code directly. All features are available to +native mods. + +Example C++ 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; +} + +} +``` + +When native 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. +Beyond services, native 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 +### Quick Start (Native Mods) -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](#advanced) - ---- - -## Getting Started - -Fork the [mod template](https://github.com/TwilitRealm/mod-template), a self-contained CMake project that uses the -Dusklight mod SDK. +Create a repository from +the [mod-template](https://github.com/new?template_name=mod-template&template_owner=TwilitRealm), +a self-contained CMake project that uses the Dusklight mod SDK. It includes a GitHub Actions CI workflow that builds the +mod for every supported platform. ``` my_mod/ @@ -65,10 +167,12 @@ add_mod(my_mod Available features: -- `fmt`: Provides the header-only `{fmt}` library and the formatted logging helpers in `mods/svc/log.hpp`. +- `fmt`: Provides the header-only `{fmt}` library and the formatted logging helpers in + [`mods/svc/log.hpp`](../sdk/include/mods/svc/log.hpp). - `game`: Allows calling into and hooking game code. Mods that **only** use services may omit it, providing a wider range of compatibility with Dusklight versions and a slightly faster build process. -- `webgpu`: Allows importing the WebGPU API (`webgpu/webgpu.h`). Must be enabled when using +- `webgpu`: Allows importing the WebGPU API + ([`webgpu/webgpu.h`](https://github.com/webgpu-native/webgpu-headers/blob/main/webgpu.h)). Must be enabled when using [GfxService](#gfxservice-modssvcgfxh). Building produces `my_mod.dusk` in `build/mods/`. Copy the `.dusk` into the user mods folder: @@ -81,63 +185,101 @@ Passing `--mods ` on the command line replaces the user directory with one --- -## mod.json +## Script Mods + +For simpler use cases where direct game code access and hooks aren't necessary, Luau mods are also supported. +Luau mods do not need to be compiled, and are always supported on all platforms. However, they can only use services +that are provided as Luau modules. + +Mods can utilize Luau to add UI elements to their mod panel, use configuration variables, and dynamically swap out +models/textures at runtime (e.g. to switch model variants or disable certain replacements) while remaining widely +compatible. + +Example mod structure: + +``` +my_luau_mod.dusk +├── mod.json +└── res/ + ├── main.luau + └── lib/util.luau +``` + +Set the `runtime` to `dev.twilitrealm.luau@1.0` in `mod.json`: ```json { - "id": "com.example.my_mod", - "name": "My Mod", + "id": "com.example.my_luau_mod", + "name": "My Script 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" + "runtime": "dev.twilitrealm.luau@1.0" } ``` -`id` is required: a unique, stable identifier (reverse-DNS style; periods, underscores, and alphanumerics). Everything -else is optional but recommended. +`res/main.luau` runs once when the mod activates. Register optional update and shutdown callbacks through +`dusklight.host`: -`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; -} -} +```lua +local host = require("dusklight.host") +host.on_update(function() + -- Runs every frame +end) +host.on_shutdown(function() + -- Runs on mod deactivation +end) ``` -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. +Require script modules with `./` or `../` paths relative to the requiring file. The runtime appends `.luau` and rejects +paths that escape `res/`. + +The runtime provides these modules: + +- `dusklight.log`: `write`, `trace`, `debug`, `info`, `warn`, and `error`. +- `dusklight.host`: host `version`, mod metadata and directories, lifecycle callbacks, and `fail`. +- `dusklight.config`: bool, integer, float, and string variables with `get`, `set`, and subscriptions. +- `dusklight.resource`: binary-safe reads from the mod's `res/` tree. +- `dusklight.overlay`: file and copied-buffer overlays with removable handles. +- `dusklight.texture`: encoded-file and raw-data replacements with unregisterable handles. +- `dusklight.ui`: Mods panels, controls, lists, windows, dialogs, styles, menu tabs, toasts, and clipboard access. + +For completion and type checking, add `sdk/luau/dusklight.d.luau` to the `luau-lsp.types.definitionFiles` setting. + +Configurable overlay example: + +```lua +local config = require("dusklight.config") +local overlay = require("dusklight.overlay") + +local hood = config.register({ name = "hood", type = "bool", default = true }) +local current + +local function apply(enabled) + if current then current:remove() end + current = overlay.add_file("/res/Object/Alink.arc", + enabled and "res/alink_hood.arc" or "res/alink_nohood.arc") +end + +hood:subscribe(apply) +apply(hood:get()) +``` + +To create a script mod, copy `sdk/luau/template`, edit its manifest and source, then zip the `mod.json` and adjacent +folders and rename to `.dusk`. 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` + +**Restrictions:** The Luau VM has no raw filesystem, network, or game-code access. Each script mod has a 64 MiB memory +limit. Calls are interrupted after 250 ms for updates and UI callbacks or 5 seconds for lifecycle calls. An uncaught +error, timeout, or memory exhaustion fails and disables the mod. --- ## 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: +A service is a struct of C function pointers with a version header. Import services at file scope, and the loader +resolves them before initializing the mod: ```cpp IMPORT_SERVICE(LogService, svc_log); // required, latest minor version @@ -145,7 +287,7 @@ IMPORT_SERVICE_VERSION(LogService, svc_log, 0); // required, minimum minor ver IMPORT_OPTIONAL_SERVICE(SomeService, svc_maybe); // may be null ``` -A service must be imported in only **one** file (usually your `mod.cpp`). Other files may simply use `svc_log` or +A service should be imported in only **one** file (usually your `mod.cpp`). Other files may simply use `svc_log` or `mods::log::` after including the appropriate header. Each service is individually versioned, and there may be multiple major versions of a service provided at once, @@ -153,26 +295,19 @@ allowing backwards compatibility with older mods while still changing services f 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. -`IMPORT_SERVICE` and `IMPORT_OPTIONAL_SERVICE` require the latest minor version compiled against, making every field in -the service safe to call. A mod can use `IMPORT_SERVICE_VERSION` (or its optional counterpart) with an older minor -version to remain compatible with older Dusklight versions, then use `SERVICE_HAS` to check at runtime for fields added -after that explicitly requested version. - -The contract (see `sdk/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.** The default macros import - the service type's current minor version; the versioned macros explicitly override that minimum. -- Optional imports may be null; check once in `mod_initialize`. -- Fields newer than your imported minor version must be gated behind `SERVICE_HAS(service, ServiceType, field)` plus a - null check. +`IMPORT_SERVICE` and `IMPORT_OPTIONAL_SERVICE` require the latest minor version compiled against, guaranteeing that +every function is present. If a mod doesn't use (or may operate without) functions added in later minor versions, and +wants to remain compatible with older Dusklight versions, it may use `IMPORT_SERVICE_VERSION` or +`IMPORT_OPTIONAL_SERVICE_VERSION` to require an older minor version. It can then check `SERVICE_HAS` at runtime to see +if a newer function is present (i.e. running on a new enough Dusklight version). --- ## Built-in Services -### LogService (`mods/svc/log.h`) +### LogService ([`mods/svc/log.h`](../sdk/include/mods/svc/log.h)) + +**C++** ```cpp IMPORT_SERVICE(LogService, svc_log); @@ -183,9 +318,16 @@ svc_log->error(mod_ctx, "very bad"); svc_log->write(mod_ctx, LOG_LEVEL_DEBUG, "verbose details"); ``` +**Luau** + +```lua +local log = require("dusklight.log") +log.info("spawned the thing") +``` + Messages appear in the console prefixed with your mod ID. Messages are plain UTF-8 strings and are copied before the call returns. C++ mods can enable `add_mod(... FEATURES fmt)` and use the formatted logging helpers in -`mods/svc/log.hpp`: +[`mods/svc/log.hpp`](../sdk/include/mods/svc/log.hpp): ```cpp #include @@ -194,11 +336,13 @@ mods::log::info("spawned actor {} at ({}, {})", actorName, x, y); mods::log::warn("health is down to {:.1f}%", healthPercent); ``` -### ResourceService (`mods/svc/resource.h`) +### ResourceService ([`mods/svc/resource.h`](../sdk/include/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. +**C++** + ```cpp IMPORT_SERVICE(ResourceService, svc_resource); @@ -209,10 +353,17 @@ if (svc_resource->load(mod_ctx, "config.txt", &buf) == MOD_OK) { } ``` +**Luau** + +```lua +local resource = require("dusklight.resource") +local contents = resource.load("config.txt") +``` + Missing files return `MOD_UNAVAILABLE`. Always `free` what you `load`. The bundle is read-only; use `HostService::data_dir` for persistent storage. -### FileService (`mods/svc/file.h`) +### FileService ([`mods/svc/file.h`](../sdk/include/mods/svc/file.h)) Provides file and folder pickers, file I/O, exports and folder enumeration. @@ -257,9 +408,10 @@ mods::file::export_file(location, "report.txt", [](mods::file::PickResult result `export_file` copies an existing file to a user-selected destination and returns the destination location in its callback. Mod-owned persistent files belong in `HostService::data_dir`. -### HttpService (`mods/svc/http.h`) +### HttpService ([`mods/svc/http.h`](../sdk/include/mods/svc/http.h)) -Asynchronous HTTPS requests supporting HTTP/2 and TLS 1.2+. C++ mods should use the helpers in `mods/svc/http.hpp`: +Asynchronous HTTPS requests supporting HTTP/2 and TLS 1.2+. C++ mods should use the helpers in +[`mods/svc/http.hpp`](../sdk/include/mods/svc/http.hpp): ```cpp #include "mods/svc/http.hpp" @@ -299,9 +451,87 @@ For large responses, set `downloadPath` to an absolute path in the calling mod's an empty `body` and the final path in `downloadPath`. Check `Response::ok()` before using the file. `Pending::progress()` reports download progress when the server provides a total size. -### HostService (`mods/svc/host.h`) +### WebSocketService ([`mods/svc/websocket.h`](../sdk/include/mods/svc/websocket.h)) -Mod metadata and runtime interaction with the loader: +WebSocket client connections with text or binary messages. Secure `wss://` URLs are supported everywhere. Insecure +`ws://` is limited to `localhost`, `127.0.0.1`, and `[::1]`. + +```cpp +#include "mods/svc/websocket.hpp" + +IMPORT_SERVICE(WebSocketService, svc_websocket); + +mods::ws::Connection connection; + +MOD_EXPORT ModResult mod_initialize(ModError*) { + connection = mods::ws::connect({.url = "wss://example.com/events"}); + return connection ? MOD_OK : connection.result(); +} + +MOD_EXPORT ModResult mod_update(ModError*) { + mods::ws::Event event; + while (mods::ws::poll(event)) { + if (event.type == WEBSOCKET_EVENT_MESSAGE) { + consume(event.data); + } else if (event.type == WEBSOCKET_EVENT_CLOSED) { + schedule_reconnect(event.error); + } + } + return MOD_OK; +} +``` + +Keep the `Connection` alive and drain `mods::ws::poll()` regularly, usually on every `mod_update` tick. A connection +emits an (optional) `OPEN` event, zero to many `MESSAGE` events, and exactly one `CLOSED` event. + +**Restrictions:** A mod may only open four connections at once. Messages have a 1 MiB limit by default, and may request +up to 16 MiB. Unread data is limited to 16 MiB and the outbound queue to 4 MiB. `send` returns `MOD_CONFLICT` when the +outbound queue is full. Dropping the `Connection` or deactivating the mod attempts to gracefully close with code 1001, +until the close deadline expires. + +### NetService ([`mods/svc/net.h`](../sdk/include/mods/svc/net.h)) + +Asynchronous raw TCP and UDP networking. Endpoints can be `tcp://host:port` or `udp://host:port`. TCP connections and +`resolve` accept hostnames. Listeners and UDP endpoints require IP literals. + +```cpp +#include "mods/svc/net.hpp" + +IMPORT_SERVICE(NetService, svc_net); + +mods::net::BindOutcome bound; +mods::net::Socket listener; +mods::net::Socket client; + +MOD_EXPORT ModResult mod_initialize(ModError*) { + listener = mods::net::listen("tcp://127.0.0.1:0", &bound); + client = mods::net::connect(bound.local); + return listener && client ? MOD_OK : MOD_ERROR; +} + +MOD_EXPORT ModResult mod_update(ModError*) { + mods::net::Event event; + while (mods::net::poll(event)) { + if (event.type == NET_EVENT_ACCEPTED) { + remember_client(mods::net::adopt(event.accepted)); + } else if (event.type == NET_EVENT_STREAM_DATA) { + consume(event.data); + } + } + return MOD_OK; +} +``` + +`send` and `send_to` copy the payload and return `MOD_CONFLICT` when the socket's outbound queue is full. `stats` +reports queued bytes, traffic, dropped inbound datagrams, and asynchronous UDP send failures. + +**Restrictions:** A mod may only have 32 streams, 4 listeners, 4 UDP sockets, and 8 DNS resolutions active at once. + +### HostService ([`mods/svc/host.h`](../sdk/include/mods/svc/host.h)) + +Mod metadata and runtime interaction with the loader. + +**C++** ```cpp IMPORT_SERVICE(HostService, svc_host); @@ -319,6 +549,20 @@ if (svc_host->data_dir(mod_ctx, &dataDir) == MOD_OK) { svc_host->fail(mod_ctx, MOD_ERROR, "something unrecoverable happened"); ``` +**Luau** + +```lua +local host = require("dusklight.host") +local dataDir = host.data_dir() +host.on_update(function() + -- Runs every frame +end) +host.on_shutdown(function() + -- Runs on mod deactivation +end) +host.fail("something unrecoverable happened") +``` + `get_service`/`publish_service` provide dynamic service lookup; see [Exporting Services](#exporting-services). **Lifecycle watches.** If your mod provides a service that hands out per-caller state (registrations, callbacks, @@ -341,17 +585,19 @@ 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`) +### HookService ([`mods/svc/hook.h`](../sdk/include/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/svc/hook.hpp` described in [Hooking Game Functions](#hooking-game-functions). +[`mods/svc/hook.hpp`](../sdk/include/mods/svc/hook.hpp) described in +[Hooking Game Functions](#hooking-game-functions). -### OverlayService (`mods/svc/overlay.h`) +### OverlayService ([`mods/svc/overlay.h`](../sdk/include/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, a file within an archive, -or with a caller-owned buffer -(copied on registration): +or with a caller-owned buffer (copied on registration). + +**C++** ```cpp IMPORT_SERVICE(OverlayService, svc_overlay); @@ -363,6 +609,14 @@ svc_overlay->add_buffer(mod_ctx, "/generated.txt", data, size, nullptr); svc_overlay->remove(mod_ctx, handle); ``` +**Luau** + +```lua +local overlay = require("dusklight.overlay") +local replacement = overlay.add_file("/Movie/demo_movie98_00.thp", "res/replacement.thp") +replacement:remove() +``` + `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. @@ -372,11 +626,13 @@ data get refreshed without a full restart. See [Asset Overlays](#asset-overlays) for priority and conflict handling. -### TextureService (`mods/svc/texture.h`) +### TextureService ([`mods/svc/texture.h`](../sdk/include/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: +your bundle whose filename encodes the key. + +**C++** ```cpp IMPORT_SERVICE(TextureService, svc_texture); @@ -397,6 +653,14 @@ svc_texture->register_data(mod_ctx, &key, &data, nullptr); svc_texture->unregister(mod_ctx, handle); ``` +**Luau** + +```lua +local texture = require("dusklight.texture") +local replacement = texture.register_file("res/tex1_32x32_$_6.png") +replacement:unregister() +``` + 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 @@ -404,11 +668,13 @@ at registration. Registrations follow your mod's lifecycle. See [Asset Overlays](#asset-overlays) for priority and conflict handling. -### ConfigService (`mods/svc/config.h`) +### ConfigService ([`mods/svc/config.h`](../sdk/include/mods/svc/config.h)) Persistent, mod-scoped configuration variables. Each var is stored in the user's `config.json` under `mod..` (escaping: `.` → `_`, `_` → `__`, so `com.example.my_mod` becomes `com_example_my__mod`), -next to the host's own settings: +next to the host's own settings. + +**C++** ```cpp IMPORT_SERVICE(ConfigService, svc_config); @@ -432,6 +698,17 @@ void on_speed_changed(ModContext* ctx, ConfigVarHandle var, const ConfigVarValue svc_config->subscribe(mod_ctx, var, on_speed_changed, nullptr, nullptr); ``` +**Luau** + +```lua +local config = require("dusklight.config") +local speed = config.register({ name = "speedMultiplier", type = "float", default = 1.0 }) +speed:subscribe(function(value, previous) + -- React to the new value. +end) +speed:set(2.0) +``` + 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. @@ -440,10 +717,10 @@ Change callbacks fire on the game thread whenever the value changes at runtime ( 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. -### SaveService (`mods/svc/save.h`) +### SaveService ([`mods/svc/save.h`](../sdk/include/mods/svc/save.h)) -Stores named binary blobs for each save slot. Blob names are scoped to the calling mod, and each mod may store up to -`SAVE_BLOB_BUDGET_BYTES` per slot. The service copies data passed to `set_blob`. +Allows reading and writing binary blobs associated with save slots. Blobs are scoped to your mod and the active game +mode's save file. A mod may store up to `SAVE_BLOB_BUDGET_BYTES` per slot. ```cpp IMPORT_SERVICE(SaveService, svc_save); @@ -466,14 +743,14 @@ if (svc_save->get_blob(mod_ctx, "state", &loaded, &loadedSize) == MOD_OK && `set_blob`, `get_blob`, and `delete_blob` operate on the current slot, which is available after creating or loading a save and unavailable at file select. Blob changes are written with the next game save. File-select copy and erase -operations update the blob data as well. Use `peek_blob` to read the calling mod's data from any slot; it uses the same -buffer contract as `get_blob`. Pass a `NULL` buffer to either read function to query the blob size. +operations update the blob data as well. Use `peek_blob` to read the calling mod's data from any slot in the active save +file. Pass a `NULL` buffer to either read function to query the blob size. `observe_saves` registers callbacks for new, loaded, and written saves. New-save callbacks run after the slot's blobs are cleared. Observers are removed automatically when the mod is detached, so the output handle is only needed for manual unregistration. Save callbacks run on the game thread. -### StageService (`mods/svc/stage.h`) +### StageService ([`mods/svc/stage.h`](../sdk/include/mods/svc/stage.h)) Allows making changes to a stage's "stage info" (contents of .dzs/.dzr files). (Currently only supports editing actor nodes.) @@ -522,7 +799,7 @@ Stage names may contain up to 8 characters. For patches and deletions, room `0xf layer; additions require a specific room. Edits are removed when the mod is detached. If multiple mods edit the same record, the later-loaded mod wins. -### UiService (`mods/svc/ui.h`) +### UiService ([`mods/svc/ui.h`](../sdk/include/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. @@ -532,6 +809,8 @@ content is rebuilt, and `update` runs every frame while that mod is selected. Wh 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"]`). +**C++** + ```cpp IMPORT_SERVICE(UiService, svc_ui); @@ -555,14 +834,27 @@ panel.update = update; svc_ui->register_mods_panel(mod_ctx, &panel); ``` +**Luau** + +```lua +local ui = require("dusklight.ui") +ui.register_mods_panel({ + build = function(panel) + panel:add_section("Status") + panel:add_text("running") + end, +}) +``` + 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_GROUP`, `UI_CONTROL_TOGGLE`, `UI_CONTROL_NUMBER`, `UI_CONTROL_STRING`, `UI_CONTROL_SELECT`, or -`UI_CONTROL_COLOR`. Values bind with callbacks or directly to a config var. +`UI_CONTROL_GROUP`, `UI_CONTROL_TOGGLE`, `UI_CONTROL_NUMBER`, `UI_CONTROL_STRING`, `UI_CONTROL_SELECT`, +`UI_CONTROL_COLOR`, `UI_CONTROL_FILE_PICKER`, `UI_CONTROL_ICON_BUTTON`, or `UI_CONTROL_DROPDOWN`. Bind values with +callbacks or directly to a config var. ```cpp UiControlDesc control = UI_CONTROL_DESC_INIT; @@ -571,18 +863,32 @@ 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); +svc_ui->pane_add_control(mod_ctx, pane, &control, nullptr); + +UiRowDesc rowDesc = UI_ROW_DESC_INIT; +rowDesc.align = UI_ROW_ALIGN_CENTER; +UiElementHandle row = 0; +svc_ui->pane_add_row(mod_ctx, pane, &rowDesc, &row); + +UiControlDesc play = UI_CONTROL_DESC_INIT; +play.kind = UI_CONTROL_ICON_BUTTON; +play.icon = "play_arrow"; +play.label = "Play"; +play.on_pressed = play_track; +svc_ui->pane_add_control(mod_ctx, row, &play, 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` and `COLOR` = 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. +type must match the control: `TOGGLE` = bool, `NUMBER`, `SELECT`, and `DROPDOWN` = int, `STRING`, `COLOR`, and +`FILE_PICKER` = 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. `pane_add_group` adds a category button to a window tab's left pane. Focusing the button clears the paired right pane and calls the group's build callback with that pane, which is useful for organizing related controls without adding more tabs. +`pane_add_row` adds a horizontal container that other controls may be nested inside. + **Lists:** `pane_add_list` adds a scrollable virtualized list of items that can be efficiently updated and filtered. Keys must be unique and remain stable across replacements. @@ -704,7 +1010,7 @@ existing documents restyle immediately, and future ones pick it up when created. host styles and may override them. Scope selectors tightly (use `[mod-id="..."]`!), especially for `UI_SCOPE_WINDOW`, unless changing host UI is intentional. -### WindowService (`mods/svc/window.h`) +### WindowService ([`mods/svc/window.h`](../sdk/include/mods/svc/window.h)) Allows creating new windows that can be rendered to via `GfxService`. @@ -724,12 +1030,13 @@ one present target may be attached to a WindowService window at a time. New windows are hidden by default so a mod can finish attaching graphics before calling `show_window`. -### GfxService (`mods/svc/gfx.h`) +### GfxService ([`mods/svc/gfx.h`](../sdk/include/mods/svc/gfx.h)) **Requires `add_mod(... FEATURES webgpu)`** -Direct WebGPU access at various stages of the rendering pipeline. Mods use the `wgpu*` C API (via `webgpu/webgpu.h`) for -custom draws and compute dispatches. Mods must manage their own WebGPU state, including pipelines and bind groups. +Direct WebGPU access at various stages of the rendering pipeline. Mods use the `wgpu*` C API (via +[`webgpu/webgpu.h`](https://github.com/webgpu-native/webgpu-headers/blob/main/webgpu.h)) for custom draws and compute +dispatches. Mods must manage their own WebGPU state, including pipelines and bind groups. ```cpp IMPORT_SERVICE(GfxService, svc_gfx); @@ -779,7 +1086,7 @@ To create a `WGPUSurface` manually, `GfxDeviceInfo` holds the `WGPUInstance` and `push_present` must be called every frame from a GfxService stage callback. If surface was lost, `push_present` returns `MOD_ERROR`. Unregister and re-register the target before trying again. -### CameraService (`mods/svc/camera.h`) +### CameraService ([`mods/svc/camera.h`](../sdk/include/mods/svc/camera.h)) Converts a game view provided by a render callback into WebGPU-convention camera data. Matrix fields are column-major `float[16]` values using the matrix * column-vector convention (transpose of the game's row-major `Mtx`/`Mtx44` layout), @@ -801,7 +1108,7 @@ first in-game frame. Projection matrices match the renderer's WebGPU clip conven Camera operators allow overriding the main camera. When an operator callback returns true, its values replace the camera state for the current frame. Register and unregister using `register_camera_operator` / `unregister_camera_operator`. -### GameModeService (`mods/svc/game_mode.h`) +### GameModeService ([`mods/svc/game_mode.h`](../sdk/include/mods/svc/game_mode.h)) Allows a mod to register a game mode with callbacks for key gameplay and save lifecycle events. Registered game modes appear in the prelaunch menu. Game modes may use a unique set of saves by configuring `save_name`; leave it empty to use @@ -920,6 +1227,82 @@ svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc); ``` +### ActorService (`mods/svc/actor.h`) + +A service that manages registering and creating custom actors. These actors will be run by the game as if they are part of the engine. These actors can be created by the game either by its 16-bit actor name, or a 7-character long name that can +be loaded by a stage. + +```cpp +#include "mods/svc/actor.h" +IMPORT_SERVICE(ActorService, svc_actor); + +class myActor_c : public fopAc_ac_c {}; + +int myActor_Create(void* i_this) { + // Ran several times, until it returns cPhs_COMPLEATE_e to allow for async loading + return cPhs_COMPLEATE_e; +} + +int myActor_Delete(void* i_this) { + // Free resources here + return 1; +} + +int myActor_Execute(void* i_this) { + // Ran once per game tick + return 1; +} + +int myActor_IsDelete(void* i_this) { + // Returns 1 when the actor can be deleted + return 1; +} + +int myActor_Draw(void* i_this) { + // Code to draw the actor + return 1; +} + +s16 actor_name; // The process name that can be used by the game to load the actor +ActorHandle actor_handle; +ActorProfileDesc profDesc = { + .name = "AUnique", // The name used by the stage loader to load the actor with. + // It has a character limit of 7 and must be unique among active + // mod actors. Matching a game actor name overrides stage lookup. + .priority_group = 7, // When, relative to other actors _Execute should run + // See: mods/svc/actor.h + .process_size = sizeof(myActor_c), + .draw_priority = fpcDwPi_OBJ_LBOX_e, // Defines when the actor should be drawn relative + // to other actors (see f_pc_draw_priority.h) + .status = fopAcStts_CULL_e | fopAcStts_UNK_0x4000_e | fopAcStts_UNK_0x40000_e, + .group = fopAc_ACTOR_e, // Can be fopAc_ACTOR_e, fopAc_PLAYER_e, fopAc_ENEMY_e, or fopAc_NPC_e + .cull_type = fopAc_CULLBOX_CUSTOM_e, + .create_function = myActor_Create, + .delete_function = myActor_Delete, + .execute_function = myActor_Execute, + .is_delete_function = myActor_IsDelete, + .draw_function = myActor_Draw, +}; +svc_actor->register_actor(mod_ctx, &profDesc, &actor_name, &actor_handle); + +// Spawn the actor at the player's position +fopAc_ac_c* plr = dComIfGp_getPlayer(0); +if (plr) { + ActorSpawnParams spawnParams = { + .parameters = 0, + .argument = 0, + .room_num = fopAcM_GetRoomNo(plr), + .position = {plr->current.pos.x, plr->current.pos.y, plr->current.pos.z}, + .angle = {plr->current.angle.x, plr->current.angle.y, plr->current.angle.z}, + .scale = {1.0f, 1.0f, 1.0f} + }; + ActorId created_actor_id; + svc_actor->create_actor(mod_ctx, actor_name, &spawnParams, &created_actor_id); +} +``` + +See `mods/custom_actor_demo` for a more complete example. + --- ## Hooking Game Functions @@ -927,7 +1310,7 @@ svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc); **Requires `add_mod(... FEATURES game)`** Mods may hook the vast majority of game functions, including file-local static, private and virtual functions. -`mods/svc/hook.hpp` provides typed helpers over the hook service: +[`mods/svc/hook.hpp`](../sdk/include/mods/svc/hook.hpp) provides typed helpers over the hook service: ```cpp #include "mods/svc/hook.hpp" @@ -1003,12 +1386,16 @@ HookshotHit::g_orig(link, atObjInf, target, tgObjInf); // call through to the o Class member functions must include `Class*` as the first argument. -Two spellings work on every platform: +There are three ways to refer to a function by symbol: -- **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. +- **Display names** (`daAlink_c::posMove`, `fapGm_Before`): the qualified name with no parameter list. Since they have + no signature, overloads (and file-local statics sharing a name) return `MOD_CONFLICT`. +- **Decorated names** (`_ZN9daAlink_c7posMoveEv` / `?posMove@daAlink_c@@...`): the platform's mangled name, like you'd + pass to dlsym(). Useful for overloads, but must be specified separately for Windows (`#ifdef _MSVC_LANG`) and other + platforms. +- **Translation unit aliases** (`src/d/actor/d_a_b_gnd.cpp#action`): the source path relative to the repository root, + followed by `#` and then the function display name without parameters. This allows disambiguating static functions + with the same name across different source files. Installing fails with `MOD_UNAVAILABLE` when it didn't resolve (missing, ambiguous, or no symbol manifest). Unlike `DEFINE_HOOK`, the signature is **not** compiler-checked: a mismatched signature will corrupt the diff --git a/extern/aurora b/extern/aurora index f8573d34e6..011e331097 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit f8573d34e632aea81039526a728b34f373d247ef +Subproject commit 011e331097fc81d7b271237b37cbe7828fd675b9 diff --git a/extern/borealis b/extern/borealis index 4b76f9ea35..0bdba6c50a 160000 --- a/extern/borealis +++ b/extern/borealis @@ -1 +1 @@ -Subproject commit 4b76f9ea35eaa116a57fd732b1c4954b6f4d0316 +Subproject commit 0bdba6c50a46409c4862474c72b4a3a631fbe0ec diff --git a/files.cmake b/files.cmake index 38087f28b8..0834dc9f0d 100644 --- a/files.cmake +++ b/files.cmake @@ -1422,6 +1422,8 @@ set(DUSK_FILES src/dusk/achievements.cpp src/dusk/action_bindings.cpp src/dusk/action_bindings.h + src/dusk/archive.cpp + src/dusk/archive.hpp src/dusk/asserts.cpp src/dusk/autosave.cpp src/dusk/config.cpp @@ -1432,10 +1434,10 @@ set(DUSK_FILES src/dusk/dvd_asset.cpp src/dusk/dvd_asset.hpp src/dusk/extras.c - src/dusk/frame_interpolation.cpp src/dusk/commands.cpp src/dusk/commands.hpp src/dusk/game_clock.cpp + src/dusk/hash.hpp src/dusk/game_mode.cpp src/dusk/gamepad_color.cpp src/dusk/globals.cpp @@ -1462,6 +1464,13 @@ set(DUSK_FILES src/dusk/imgui/ImGuiStateShare.cpp src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStubLog.cpp + src/dusk/interp/camera.cpp + src/dusk/interp/frame_interpolation.cpp + src/dusk/interp/line.cpp + src/dusk/interp/material.cpp + src/dusk/interp/particle.cpp + src/dusk/interp/samples.cpp + src/dusk/interp/vertex.cpp src/dusk/io.cpp src/dusk/iso_validate.cpp src/dusk/language.cpp @@ -1478,10 +1487,23 @@ set(DUSK_FILES src/dusk/mods/loader/depgraph.hpp src/dusk/mods/loader/loader.cpp src/dusk/mods/loader/loader.hpp + src/dusk/mods/loader/manifest.cpp + src/dusk/mods/loader/manifest.hpp + src/dusk/mods/loader/natives.cpp + src/dusk/mods/loader/natives.hpp + src/dusk/mods/loader/packages.cpp + src/dusk/mods/loader/packages.hpp src/dusk/mods/loader/native_module.cpp src/dusk/mods/loader/native_module.hpp src/dusk/mods/loader/prepatch.cpp src/dusk/mods/loader/prepatch.hpp + src/dusk/mods/updates.cpp + src/dusk/mods/updates.hpp + src/dusk/mods/catalog.cpp + src/dusk/mods/catalog.hpp + src/dusk/mods/queue.cpp + src/dusk/mods/queue.hpp + src/dusk/mods/services.hpp src/dusk/mods/item.hpp src/dusk/mods/item_actor.cpp src/dusk/mods/item_checks.cpp @@ -1490,6 +1512,15 @@ set(DUSK_FILES src/dusk/mods/log_buffer.hpp src/dusk/mods/manifest.cpp src/dusk/mods/manifest.hpp + src/dusk/mods/svc/actor.cpp + src/dusk/mods/svc/audio_res/audio_res.hpp + src/dusk/mods/svc/audio_res/audio_res.cpp + src/dusk/mods/svc/audio_res/bst.cpp + src/dusk/mods/svc/audio_res/bst.hpp + src/dusk/mods/svc/audio_res/wsys.cpp + src/dusk/mods/svc/audio_res/wsys.hpp + src/dusk/mods/svc/audio_res/wave.cpp + src/dusk/mods/svc/audio_res/opus.cpp src/dusk/mods/svc/camera.cpp src/dusk/mods/svc/config.cpp src/dusk/mods/svc/config.hpp @@ -1500,8 +1531,13 @@ set(DUSK_FILES src/dusk/mods/svc/hook.cpp src/dusk/mods/svc/host.cpp src/dusk/mods/svc/http.cpp + src/dusk/mods/svc/net.cpp + src/dusk/mods/svc/net.hpp + src/dusk/mods/svc/websocket.cpp src/dusk/mods/svc/item.cpp src/dusk/mods/svc/item.hpp + src/dusk/mods/svc/id_allocator.cpp + src/dusk/mods/svc/id_allocator.hpp src/dusk/mods/svc/log.cpp src/dusk/mods/svc/overlay.cpp src/dusk/mods/svc/registry.cpp @@ -1521,6 +1557,8 @@ set(DUSK_FILES src/dusk/presentation.cpp src/dusk/presentation.hpp src/dusk/scope_guard.hpp + src/dusk/save_manager.cpp + src/dusk/save_manager.hpp src/dusk/settings.cpp src/dusk/speedrun.cpp src/dusk/stubs.cpp @@ -1535,6 +1573,8 @@ set(DUSK_FILES src/dusk/ui/bool_button.hpp src/dusk/ui/button.cpp src/dusk/ui/button.hpp + src/dusk/ui/clamped_text.cpp + src/dusk/ui/clamped_text.hpp src/dusk/ui/color_input.cpp src/dusk/ui/color_input.hpp src/dusk/ui/component.cpp @@ -1544,6 +1584,10 @@ set(DUSK_FILES src/dusk/ui/controls.hpp src/dusk/ui/document.cpp src/dusk/ui/document.hpp + src/dusk/ui/drop_install_modal.cpp + src/dusk/ui/drop_install_modal.hpp + src/dusk/ui/dropdown_button.cpp + src/dusk/ui/dropdown_button.hpp src/dusk/ui/editor.cpp src/dusk/ui/editor.hpp src/dusk/ui/event.cpp @@ -1564,21 +1608,41 @@ set(DUSK_FILES src/dusk/ui/list.hpp src/dusk/ui/menu_bar.cpp src/dusk/ui/menu_bar.hpp + src/dusk/ui/mod_browser.cpp + src/dusk/ui/mod_browser.hpp + src/dusk/ui/online_mods.cpp + src/dusk/ui/online_mods.hpp + src/dusk/ui/package_row.cpp + src/dusk/ui/package_row.hpp src/dusk/ui/mod_texture_provider.cpp src/dusk/ui/mod_texture_provider.hpp + src/dusk/ui/remote_texture_provider.cpp + src/dusk/ui/remote_texture_provider.hpp + src/dusk/ui/runtime_image.cpp + src/dusk/ui/runtime_image.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/mod_updates.cpp + src/dusk/ui/mod_updates.hpp src/dusk/ui/mods_window.cpp src/dusk/ui/mods_window.hpp src/dusk/ui/nav_types.hpp src/dusk/ui/nav_group.cpp src/dusk/ui/nav_group.hpp + src/dusk/ui/context_menu.cpp + src/dusk/ui/context_menu.hpp + src/dusk/ui/icon_button.cpp + src/dusk/ui/icon_button.hpp + src/dusk/ui/tooltip.cpp + src/dusk/ui/tooltip.hpp src/dusk/ui/number_button.cpp src/dusk/ui/number_button.hpp src/dusk/ui/overlay.cpp src/dusk/ui/overlay.hpp + src/dusk/ui/row.cpp + src/dusk/ui/row.hpp src/dusk/ui/pane.cpp src/dusk/ui/pane.hpp src/dusk/ui/popover.cpp @@ -1589,6 +1653,8 @@ set(DUSK_FILES src/dusk/ui/preset.hpp src/dusk/ui/reporting.cpp src/dusk/ui/reporting.hpp + src/dusk/ui/saves_window.cpp + src/dusk/ui/saves_window.hpp src/dusk/ui/select_button.cpp src/dusk/ui/select_button.hpp src/dusk/ui/settings.cpp @@ -1612,7 +1678,7 @@ set(DUSK_FILES src/dusk/version.cpp src/dusk/utilities.cpp src/helpers/batch.cpp - src/helpers/endian.cpp src/helpers/offset_ptr.cpp src/helpers/string.cpp + src/helpers/cast.cpp ) diff --git a/include/SSystem/SComponent/c_bg_s_chk.h b/include/SSystem/SComponent/c_bg_s_chk.h index 6908ef80ed..badc04679a 100644 --- a/include/SSystem/SComponent/c_bg_s_chk.h +++ b/include/SSystem/SComponent/c_bg_s_chk.h @@ -5,7 +5,6 @@ #include "f_pc/f_pc_base.h" #include "SSystem/SComponent/c_bg_s_grp_pass_chk.h" #include "SSystem/SComponent/c_bg_s_poly_pass_chk.h" -#include "helpers/endian.h" struct cBgD_Vtx_t : public Vec {}; diff --git a/include/SSystem/SComponent/c_m3d_g_tri.h b/include/SSystem/SComponent/c_m3d_g_tri.h index 9947616d54..cd535d66c3 100644 --- a/include/SSystem/SComponent/c_m3d_g_tri.h +++ b/include/SSystem/SComponent/c_m3d_g_tri.h @@ -2,7 +2,6 @@ #define C_M3D_G_TRI_H_ #include "SSystem/SComponent/c_m3d_g_pla.h" -#include "helpers/endian.h" class cM3dGCyl; diff --git a/include/Z2AudioCS/SpkMixingBuffer.h b/include/Z2AudioCS/SpkMixingBuffer.h index ccdb8902b0..5cdfcd74bb 100644 --- a/include/Z2AudioCS/SpkMixingBuffer.h +++ b/include/Z2AudioCS/SpkMixingBuffer.h @@ -13,7 +13,6 @@ public: bool update(s32 chan); void bzeroBuffer(s32 chan); -private: /* 0x00 */ s16* mBuffer[4]; }; diff --git a/include/Z2AudioCS/SpkSpeakerCtrl.h b/include/Z2AudioCS/SpkSpeakerCtrl.h index 7216eed07d..372d5b1835 100644 --- a/include/Z2AudioCS/SpkSpeakerCtrl.h +++ b/include/Z2AudioCS/SpkSpeakerCtrl.h @@ -41,7 +41,6 @@ public: static bool checkRadioSensitivity(s32 chan); static bool isSubmitPlayByRadioSensitivity(s32 chan); -private: }; #endif /* Z2AUDIOCS_SPKSPEAKERCTRL_H */ diff --git a/include/Z2AudioCS/SpkSystem.h b/include/Z2AudioCS/SpkSystem.h index 05541c72e5..03b30cda6e 100644 --- a/include/Z2AudioCS/SpkSystem.h +++ b/include/Z2AudioCS/SpkSystem.h @@ -28,7 +28,6 @@ public: inline SpkData* getData(void) const { return mData; } inline SpkMixingBuffer* getMixingBuffer(void) const { return mMixingBuffer; } -private: /* 0x00 */ JKRHeap* mHeap; /* 0x04 */ SpkData* mData; /* 0x08 */ SpkMixingBuffer* mMixingBuffer; diff --git a/include/Z2AudioCS/SpkTable.h b/include/Z2AudioCS/SpkTable.h index 862fdba3c1..f2234f5a06 100644 --- a/include/Z2AudioCS/SpkTable.h +++ b/include/Z2AudioCS/SpkTable.h @@ -31,7 +31,6 @@ public: return (SpkTableParams*)mEntryOffset + num; } - // private: /* 0x00 */ bool mIsInitialized; /* 0x04 */ s32 mNumOfSound; /* 0x08 */ u32 mEntryOffset; diff --git a/include/Z2AudioCS/SpkWave.h b/include/Z2AudioCS/SpkWave.h index 6a604d8a50..72cde1c0a0 100644 --- a/include/Z2AudioCS/SpkWave.h +++ b/include/Z2AudioCS/SpkWave.h @@ -25,7 +25,6 @@ public: inline const void* getResource(void) const { return mWaveData; } inline void* getResource(void) { return mWaveData; } -private: /* 0x00 */ void* mWaveData; }; diff --git a/include/Z2AudioLib/Z2Audience.h b/include/Z2AudioLib/Z2Audience.h index e6d2f7da3a..2f2dc359f9 100644 --- a/include/Z2AudioLib/Z2Audience.h +++ b/include/Z2AudioLib/Z2Audience.h @@ -9,6 +9,8 @@ #include "JSystem/JAudio2/JAUAudibleParam.h" #include "JSystem/TPosition3.h" +#define Z2_AUDIO_PLAYERS 1 + struct Z2Audible; struct Z2AudibleAbsPos { @@ -49,10 +51,10 @@ struct Z2AudioCamera { f32 getCamDist() const { return mCamDist; } - /* 0x00 */ JGeometry::TPosition3f32 field_0x0; + /* 0x00 */ JGeometry::TPosition3f32 mViewMatrix; /* 0x30 */ JGeometry::TVec3 mVel; /* 0x3C */ JGeometry::TVec3 mPos; - /* 0x48 */ JGeometry::TVec3 field_0x48; + /* 0x48 */ JGeometry::TVec3 mLastPos; /* 0x54 */ f32 mFovySin; /* 0x58 */ f32 mVolCenterZ; /* 0x5C */ f32 mTargetVolume; @@ -83,14 +85,14 @@ struct Z2SpotMic { /* 0x04 */ f32 field_0x4; /* 0x08 */ f32 field_0x8; /* 0x0C */ f32 field_0xc; - /* 0x10 */ Z2AudioCamera* field_0x10[1]; + /* 0x10 */ Z2AudioCamera* field_0x10[Z2_AUDIO_PLAYERS]; /* 0x14 */ Vec* mPosPtr; - /* 0x18 */ f32 field_0x18[1]; + /* 0x18 */ f32 field_0x18[Z2_AUDIO_PLAYERS]; /* 0x1C */ f32 field_0x1c; - /* 0x20 */ f32 field_0x20[1]; + /* 0x20 */ f32 field_0x20[Z2_AUDIO_PLAYERS]; /* 0x24 */ bool mIgnoreIfOut; /* 0x25 */ bool mMicOn; - /* 0x26 */ bool field_0x26[1]; + /* 0x26 */ bool field_0x26[Z2_AUDIO_PLAYERS]; }; // Size: 0x28 struct Z2Audience3DSetting { @@ -102,62 +104,99 @@ struct Z2Audience3DSetting { void updateDolbyDist(f32, f32); void calcVolumeFactorAll() { - field_0x0[1] = 1.25f * field_0x0[0]; - field_0x0[2] = 1.5f * field_0x0[0]; - field_0x0[3] = 2.0f * field_0x0[0]; - field_0x0[4] = 3.0f * field_0x0[0]; - field_0x0[5] = 4.0f * field_0x0[0]; - field_0x0[6] = 6.0f * field_0x0[0]; - field_0x0[7] = 8.0f * field_0x0[0]; - field_0x0[8] = 0.9f * field_0x0[0]; - field_0x0[9] = 0.8f * field_0x0[0]; - field_0x0[10] = 0.7f * field_0x0[0]; - field_0x0[11] = 0.6f * field_0x0[0]; - field_0x0[12] = 0.5f * field_0x0[0]; - field_0x0[13] = 0.4f * field_0x0[0]; - field_0x0[14] = 0.3f * field_0x0[0]; + mDistanceMaxes[1] = 1.25f * mDistanceMaxes[0]; + mDistanceMaxes[2] = 1.5f * mDistanceMaxes[0]; + mDistanceMaxes[3] = 2.0f * mDistanceMaxes[0]; + mDistanceMaxes[4] = 3.0f * mDistanceMaxes[0]; + mDistanceMaxes[5] = 4.0f * mDistanceMaxes[0]; + mDistanceMaxes[6] = 6.0f * mDistanceMaxes[0]; + mDistanceMaxes[7] = 8.0f * mDistanceMaxes[0]; + mDistanceMaxes[8] = 0.9f * mDistanceMaxes[0]; + mDistanceMaxes[9] = 0.8f * mDistanceMaxes[0]; + mDistanceMaxes[10] = 0.7f * mDistanceMaxes[0]; + mDistanceMaxes[11] = 0.6f * mDistanceMaxes[0]; + mDistanceMaxes[12] = 0.5f * mDistanceMaxes[0]; + mDistanceMaxes[13] = 0.4f * mDistanceMaxes[0]; + mDistanceMaxes[14] = 0.3f * mDistanceMaxes[0]; for (int i = 0; i < 15; i++) { - field_0x70[i] = (field_0x40 - 1.0f) / (field_0x0[i] - field_0x3c); + mVolumeFactor[i] = (mMinDistanceVolume - 1.0f) / (mDistanceMaxes[i] - mMaxVolumeDistance); } } void calcPriorityFactorAll() { for (int i = 0; i < 15; i++) { - field_0xac[i] = field_0x64 / (field_0x0[i] - field_0x3c); + mPriorityFactor[i] = mMaxDistancePriority / (mDistanceMaxes[i] - mMaxVolumeDistance); } } void calcFxMixFactorAll() { for (int i = 0; i < 15; i++) { - field_0xe8[i] = (field_0x54 - field_0x50) / (field_0x0[i] - field_0x3c); + mFxMixFactor[i] = (mMaxDistanceFxMix - mMinDistanceFxMix) / (mDistanceMaxes[i] - mMaxVolumeDistance); } } - /* 0x000 */ f32 field_0x0[15]; - /* 0x03C */ f32 field_0x3c; - /* 0x040 */ f32 field_0x40; - /* 0x044 */ f32 field_0x44; - /* 0x048 */ f32 field_0x48; - /* 0x04C */ f32 field_0x4c; - /* 0x050 */ f32 field_0x50; - /* 0x054 */ f32 field_0x54; - /* 0x058 */ f32 field_0x58; - /* 0x05C */ f32 field_0x5c; + /** + * Maximum distance a sound can reach before being "far away" + * Being far away affects stuff like culling, lowering its priority, forcibly stopping it, etc. + * Sounds select which entry they use based on their VolBits. + */ + /* 0x000 */ f32 mDistanceMaxes[15]; + + /** + * Distance at which the max volume of a sound is reached. + * i.e. sounds do *not* get louder if they get closer than this. + */ + /* 0x03C */ f32 mMaxVolumeDistance; + + /** + * FX Mix value at maximum distance (@ref mDistanceMaxes) + */ + /* 0x040 */ f32 mMinDistanceVolume; + /* 0x044 */ f32 mDolbyFrontDistanceMax; + /* 0x048 */ f32 mDolbyBehindDistanceMax; + /* 0x04C */ f32 mDolbyCenterValue; + + /** + * FX Mix value at minimum distance (@ref mMaxVolumeDistance) + */ + /* 0x050 */ f32 mMinDistanceFxMix; + + /** + * FX Mix value at maximum distance (@ref mDistanceMaxes) + */ + /* 0x054 */ f32 mMaxDistanceFxMix; + /* 0x058 */ f32 mPanFactor; + /* 0x05C */ f32 mSonicSpeed; // Used for doppler effect calculations. /* 0x060 */ f32 field_0x60; - /* 0x064 */ u32 field_0x64; + + /** + * Priority that sounds receive when "far away". + * @see mDistanceMaxes + */ + /* 0x064 */ u32 mMaxDistancePriority; /* 0x068 */ f32 field_0x68; /* 0x06C */ f32 field_0x6c; - /* 0x070 */ f32 field_0x70[15]; - /* 0x0AC */ f32 field_0xac[15]; - /* 0x0E8 */ f32 field_0xe8[15]; + /* 0x070 */ f32 mVolumeFactor[15]; + /* 0x0AC */ f32 mPriorityFactor[15]; + /* 0x0E8 */ f32 mFxMixFactor[15]; /* 0x124 */ bool mVolumeDistInit; /* 0x125 */ bool mDolbyDistInit; }; // Size: 0x128 struct Z2AudibleRelPos { - /* 0x00 */ JGeometry::TVec3 field_0x00; - /* 0x0C */ f32 field_0xC; - /* 0x10 */ f32 field_0x10; + /* 0x00 */ JGeometry::TVec3 mCameraRelative; + + /** + * Distance from mCameraRelative. This is from the object root and not the + * exact distance used for volume/priority calculations. + */ + /* 0x0C */ f32 mTrueDistance; + + /** + * Distance from mCameraRelative but offset by mVolCenterZ. + * This presumably means the distance is more centered on the object than mTrueDistance. + */ + /* 0x10 */ f32 mCenterDistance; }; struct Z2AudibleChannel { @@ -171,7 +210,7 @@ struct Z2AudibleChannel { } /* 0x00 */ JASSoundParams mParams; - /* 0x14 */ Z2AudibleRelPos field_0x14; + /* 0x14 */ Z2AudibleRelPos mRelPos; /* 0x28 */ f32 field_0x28; /* 0x2c */ f32 mPan; /* 0x30 */ f32 mDolby; @@ -200,8 +239,8 @@ struct Z2Audible : public JAIAudible, public JASPoolAllocObject { /* 0x10 */ JAUAudibleParam mParam; /* 0x14 */ Z2AudibleAbsPos mAbsPos; - /* 0x2C */ Z2AudibleChannel mChannel[1]; - /* 0x64 */ f32 field_0x64[1]; + /* 0x2C */ Z2AudibleChannel mChannel[Z2_AUDIO_PLAYERS]; + /* 0x64 */ f32 mMicDistances[Z2_AUDIO_PLAYERS]; }; struct Z2Audience : public JAIAudience, public JASGlobalInstance { @@ -223,7 +262,8 @@ struct Z2Audience : public JAIAudience, public JASGlobalInstance { virtual ~Z2Audience(); virtual JAIAudible* newAudible(const JGeometry::TVec3& pos, JAISoundID soundID, - const JGeometry::TVec3*, u32); + const JGeometry::TVec3*, u32 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const*)); virtual int getMaxChannels(); virtual void deleteAudible(JAIAudible* audible); virtual u32 calcPriority(JAIAudible* audible); @@ -237,7 +277,7 @@ struct Z2Audience : public JAIAudience, public JASGlobalInstance { } Z2Audience3DSetting* getSetting() { return &mSetting; } - const Z2AudioCamera* getAudioCamera(int camID) const { return &mAudioCamera[camID]; } + const Z2AudioCamera* getAudioCamera(int camID) const { return &mAudioCamera[camID]; } void setUsingOffMicVol(bool value) { mUsingOffMicVol = value; } diff --git a/include/Z2AudioLib/Z2Creature.h b/include/Z2AudioLib/Z2Creature.h index c4f96ca742..c17394b75f 100644 --- a/include/Z2AudioLib/Z2Creature.h +++ b/include/Z2AudioLib/Z2Creature.h @@ -67,7 +67,6 @@ public: void playVoice(int); virtual Z2SoundHandlePool* startCreatureVoice(JAISoundID soundID, s8 reverb); -private: /* 0xA0 */ s8 mCitizenID; /* 0xA1 */ s8 field_0xa1; /* 0xA2 */ bool mLinkSearch; diff --git a/include/Z2AudioLib/Z2LinkMgr.h b/include/Z2AudioLib/Z2LinkMgr.h index 705394df5e..e64c1729eb 100644 --- a/include/Z2AudioLib/Z2LinkMgr.h +++ b/include/Z2AudioLib/Z2LinkMgr.h @@ -63,7 +63,6 @@ public: friend class Z2LinkSoundStarter; friend struct Z2RideSoundStarter; -private: /* 0x90 Z2LinkSoundStarter */ /* 0x94 */ Z2SoundObjSimple mKantera; /* 0xB4 */ Vec field_0xb4; @@ -123,7 +122,6 @@ public: virtual void deleteObject(); -private: /* 0x90 */ // Z2RideSoundStarter mSoundStarter; /* 0x98 */ bool mLinkRiding; }; // Size: 0x9C @@ -136,7 +134,6 @@ public: Z2SoundHandlePool* startFireSound(u16); Z2SoundHandlePool* startSound(JAISoundID soundID, u32 mapinfo, s8 reverb); -private: /* 0x20 */ f32 field_0x20; }; diff --git a/include/Z2AudioLib/Z2SceneMgr.h b/include/Z2AudioLib/Z2SceneMgr.h index 27b43d71f4..739c53bb10 100644 --- a/include/Z2AudioLib/Z2SceneMgr.h +++ b/include/Z2AudioLib/Z2SceneMgr.h @@ -47,7 +47,6 @@ public: s32 getBgmLoadStatus(u32 wave) { return getWaveLoadStatus(wave, 1); } u8 getDemoSeWaveNum() { return loadedDemoWave; } -// private: /* 0x00 */ JAISoundID BGM_ID; /* 0x04 */ int sceneNum; /* 0x08 */ int timer; diff --git a/include/Z2AudioLib/Z2SeMgr.h b/include/Z2AudioLib/Z2SeMgr.h index 3f8ca03eb3..7075308ef7 100644 --- a/include/Z2AudioLib/Z2SeMgr.h +++ b/include/Z2AudioLib/Z2SeMgr.h @@ -84,7 +84,6 @@ public: void homeMenuSeCallback(s32); #endif -private: /* 0x000 */ JAISoundHandle mSoundHandle[24]; /* 0x060 */ JAISoundHandles mSoundHandles; #if VERSION >= VERSION_WII_USA_R0 diff --git a/include/Z2AudioLib/Z2SoundHandles.h b/include/Z2AudioLib/Z2SoundHandles.h index 5915e5967b..1ad344c796 100644 --- a/include/Z2AudioLib/Z2SoundHandles.h +++ b/include/Z2AudioLib/Z2SoundHandles.h @@ -44,7 +44,6 @@ public: return (Z2SoundHandlePool*)getNth(index); } -private: /* 0xC */ u8 handleNum_; }; diff --git a/include/Z2AudioLib/Z2SoundInfo.h b/include/Z2AudioLib/Z2SoundInfo.h index e2fae177a3..404e0ad5df 100644 --- a/include/Z2AudioLib/Z2SoundInfo.h +++ b/include/Z2AudioLib/Z2SoundInfo.h @@ -6,23 +6,29 @@ #include "JSystem/JAudio2/JAUSoundInfo.h" #include "JSystem/JAudio2/JAUSoundTable.h" +#define STRM_CH_SHIFT_ 2 + +#define STRM_CH_CENTER 1 +#define STRM_CH_LEFT 2 +#define STRM_CH_RIGHT 3 + class Z2SoundInfo : public JAISoundInfo, public JAUSoundInfo, public JAIStreamDataMgr, public JASGlobalInstance { public: Z2SoundInfo() : JAISoundInfo(true), JAUSoundInfo(true), JASGlobalInstance(true) {} - virtual u16 getAudibleSw(JAISoundID soundID) const; + virtual u16 getAudibleSw(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; virtual u16 getBgmSeqResourceID(JAISoundID soundID) const; - virtual s32 getStreamFileEntry(JAISoundID soundID); + virtual s32 getStreamFileEntry(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)); virtual int getSoundType(JAISoundID soundID) const; virtual int getCategory(JAISoundID soundID) const; - virtual u32 getPriority(JAISoundID soundID) const; - virtual void getSeInfo(JAISoundID soundID, JAISe* sePtr) const; + virtual u32 getPriority(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; + virtual void getSeInfo(JAISoundID soundID, JAISe* sePtr IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const; virtual void getSeqInfo(JAISoundID soundID, JAISeq* seqPtr) const; - virtual void getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const; + virtual void getStreamInfo(JAISoundID soundID, JAIStream* streamPtr IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const; virtual ~Z2SoundInfo() {} - JAUAudibleParam getAudibleSwFull(JAISoundID soundID); - const char* getStreamFilePath(JAISoundID soundID); - int getSwBit(JAISoundID soundID) const; + JAUAudibleParam getAudibleSwFull(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)); + const char* getStreamFilePath(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)); + int getSwBit(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; void getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const; BOOL isValid() const { diff --git a/include/Z2AudioLib/Z2SoundMgr.h b/include/Z2AudioLib/Z2SoundMgr.h index cd6341b2cb..24a6762e3c 100644 --- a/include/Z2AudioLib/Z2SoundMgr.h +++ b/include/Z2AudioLib/Z2SoundMgr.h @@ -38,11 +38,10 @@ public: const JAISeqMgr* getSeqMgr() const { return &seqMgr_; } JAIStreamMgr* getStreamMgr() { return &streamMgr_; } -private: /* 0x004 */ JAISeMgr seMgr_; /* 0x728 */ JAISeqMgr seqMgr_; /* 0x79C */ JAIStreamMgr streamMgr_; - /* 0x80C */ JAISoundID soundID_; + /* 0x80C */ JAISoundID bgmMuter; }; // Size: 0x810 #if VERSION != VERSION_SHIELD_DEBUG diff --git a/include/Z2AudioLib/Z2SoundObjMgr.h b/include/Z2AudioLib/Z2SoundObjMgr.h index 687f9eaf0a..64bf058ba1 100644 --- a/include/Z2AudioLib/Z2SoundObjMgr.h +++ b/include/Z2AudioLib/Z2SoundObjMgr.h @@ -104,7 +104,6 @@ public: JSUList* getAllList() { return &allList_; } #endif -private: /* 0x00 */ JSUList field_0x0; #if PARTIAL_DEBUG || DEBUG /* 0x0C */ JSUList allList_; diff --git a/include/Z2AudioLib/Z2WolfHowlMgr.h b/include/Z2AudioLib/Z2WolfHowlMgr.h index 10b746a4c1..5376499715 100644 --- a/include/Z2AudioLib/Z2WolfHowlMgr.h +++ b/include/Z2AudioLib/Z2WolfHowlMgr.h @@ -70,7 +70,6 @@ public: u8 getReleaseTimer() { return mReleaseTimer; } u8 getCorrectCurveID() { return mCorrectCurveID; } -private: /* 0x00 */ JAISoundHandle mWolfHowlHandle; /* 0x04 */ JAISoundHandle mWindStoneHandle; /* 0x08 */ JAISoundHandle mGuideHandle; diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index 384891c8d5..4d459d4597 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -77,7 +77,6 @@ public: virtual void draw(); virtual ~daAlink_blur_c() {} - // private: /* 0x010 */ ResTIMG* m_blurTex; /* 0x014 */ int field_0x14; /* 0x018 */ u8 field_0x18[4]; @@ -113,7 +112,6 @@ public: void onAppearFlg() { mAppearFlg = true; } -private: /* 0x04 */ s16 mHitFlg; /* 0x06 */ s16 mAppearFlg; /* 0x08 */ f32 mKeepMinY; @@ -126,7 +124,11 @@ public: BOOL create(); void update(); virtual void draw(); +#if TARGET_PC + virtual ~daAlink_lockCursor_c(); +#else virtual ~daAlink_lockCursor_c() {} +#endif void initFrame() { field_0x4 = 0; @@ -4558,29 +4560,6 @@ public: bool checkAimContext(); bool checkAimInputContext(); - void onIronBallChainInterpCallback(); - - static const int IRON_BALL_CHAIN_COUNT = 102; - cXyz mIBChainInterpPrevPos[IRON_BALL_CHAIN_COUNT]; - cXyz mIBChainInterpCurrPos[IRON_BALL_CHAIN_COUNT]; - csXyz mIBChainInterpPrevAngle[IRON_BALL_CHAIN_COUNT]; - csXyz mIBChainInterpCurrAngle[IRON_BALL_CHAIN_COUNT]; - cXyz mIBChainInterpPrevHandRoot; - cXyz mIBChainInterpCurrHandRoot; - bool mIBChainInterpPrevValid; - bool mIBChainInterpCurrValid; - - cXyz mHsChainInterpPrevTop; - cXyz mHsChainInterpCurrTop; - cXyz mHsChainInterpPrevRoot; - cXyz mHsChainInterpCurrRoot; - cXyz mHsChainInterpPrevSubRoot; - cXyz mHsChainInterpCurrSubRoot; - cXyz mHsChainInterpPrevSubTop; - cXyz mHsChainInterpCurrSubTop; - bool mHsChainInterpPrevValid; - bool mHsChainInterpCurrValid; - bool mIsRollstab = false; #endif }; // Size: 0x385C diff --git a/include/d/actor/d_a_arrow.h b/include/d/actor/d_a_arrow.h index 47f86eeb50..f15cf1aca8 100644 --- a/include/d/actor/d_a_arrow.h +++ b/include/d/actor/d_a_arrow.h @@ -110,7 +110,6 @@ public: void setModelMatrix(MtxP mtx) { mpModel->setBaseTRMtx(mtx); } -private: /* 0x568 */ J3DModel* mpModel; /* 0x56C */ dBgS_ArrowLinChk field_0x56c; /* 0x5DC */ dBgS_ObjLinChk field_0x5dc; diff --git a/include/d/actor/d_a_b_dr.h b/include/d/actor/d_a_b_dr.h index e15a6e7ca5..d9f72089f4 100644 --- a/include/d/actor/d_a_b_dr.h +++ b/include/d/actor/d_a_b_dr.h @@ -82,7 +82,6 @@ public: u8 onTarget() { return mTarget = true; } u8 isBack() { return arg0; } -private: /* 0x05AC */ request_of_phase_process_class mPhase; /* 0x05B4 */ mDoExt_McaMorfSO* mpModelMorf; /* 0x05B8 */ J3DModel* mpPartModel; diff --git a/include/d/actor/d_a_b_ds.h b/include/d/actor/d_a_b_ds.h index ee737a955a..7013465e93 100644 --- a/include/d/actor/d_a_b_ds.h +++ b/include/d/actor/d_a_b_ds.h @@ -189,7 +189,6 @@ public: void onDemo() { mIsDemo = true; } void offDemo() { mIsDemo = false; } -private: /* 0x05AC */ request_of_phase_process_class mPhase; /* 0x05B4 */ mDoExt_McaMorfSO* mpMorf; /* 0x05B8 */ mDoExt_McaMorfSO* mpSwordMorf; diff --git a/include/d/actor/d_a_b_gm.h b/include/d/actor/d_a_b_gm.h index f153541772..f14438da6e 100644 --- a/include/d/actor/d_a_b_gm.h +++ b/include/d/actor/d_a_b_gm.h @@ -110,6 +110,9 @@ public: /* 0x1CFC */ int field_0x1cfc; /* 0x1D00 */ f32 mKankyoBlend; /* 0x1D04 */ u8 mInitHIO; +#if TARGET_PC + s8 mDemoCamSyncTicks; +#endif }; STATIC_ASSERT(sizeof(b_gm_class) == 0x1d08); diff --git a/include/d/actor/d_a_b_gnd.h b/include/d/actor/d_a_b_gnd.h index 0827102eec..dcf6c94905 100644 --- a/include/d/actor/d_a_b_gnd.h +++ b/include/d/actor/d_a_b_gnd.h @@ -189,12 +189,6 @@ public: /* 0x2740 */ u8 field_0x2740; /* 0x2744 */ dMsgFlow_c mMsgFlow; #if TARGET_PC - cXyz mReinsInterpPrev[2][16]; - cXyz mReinsInterpCurr[2][16]; - cXyz mReinsTexInterpPrev[2]; - cXyz mReinsTexInterpCurr[2]; - bool mReinsInterpPrevValid; - bool mReinsInterpCurrValid; s8 mDemoCamSyncTicks; #endif }; diff --git a/include/d/actor/d_a_b_mgn.h b/include/d/actor/d_a_b_mgn.h index 272d02b1ef..1ad1dd5cf7 100644 --- a/include/d/actor/d_a_b_mgn.h +++ b/include/d/actor/d_a_b_mgn.h @@ -92,7 +92,6 @@ public: bool isDown() { return mDownFlag != 0; } bool isAppear() { return mAppearFlag != 0; } -private: /* 0x05AC */ request_of_phase_process_class mMgnPhase; /* 0x05B4 */ request_of_phase_process_class mMgnePhase; /* 0x05BC */ mDoExt_McaMorfSO* mpMgnModelMorf; diff --git a/include/d/actor/d_a_b_tn.h b/include/d/actor/d_a_b_tn.h index 7de1007d28..87b9cd2fae 100644 --- a/include/d/actor/d_a_b_tn.h +++ b/include/d/actor/d_a_b_tn.h @@ -131,7 +131,6 @@ public: int CreateHeap(); int create(); -private: /* 0x05AC */ request_of_phase_process_class mPhaseReq1; /* 0x05B4 */ request_of_phase_process_class mPhaseReq2; /* 0x05BC */ J3DModel* mpPartModels[16]; diff --git a/include/d/actor/d_a_b_yo.h b/include/d/actor/d_a_b_yo.h index a133d364d6..3550b95a1c 100644 --- a/include/d/actor/d_a_b_yo.h +++ b/include/d/actor/d_a_b_yo.h @@ -80,7 +80,6 @@ public: void setDrawOff() { mIsInactive = 1; } bool isEnemyDemoEnd() { return (u8)mIsEnemyDemoEnd != 0; } -private: /* 0x05AC */ request_of_phase_process_class mPhase1; /* 0x05B4 */ request_of_phase_process_class mPhase2; /* 0x05BC */ request_of_phase_process_class mPhase3; diff --git a/include/d/actor/d_a_b_yo_ice.h b/include/d/actor/d_a_b_yo_ice.h index 2ad2e5388d..e0f064fa9d 100644 --- a/include/d/actor/d_a_b_yo_ice.h +++ b/include/d/actor/d_a_b_yo_ice.h @@ -51,7 +51,6 @@ public: void setSubNumber(int i_subNumber) { mSubNumber = i_subNumber; } -private: /* 0x5AC */ request_of_phase_process_class mPhase; /* 0x5B4 */ J3DModel* mpModel; /* 0x5B8 */ Z2Creature mCreatureSound; diff --git a/include/d/actor/d_a_b_zant_magic.h b/include/d/actor/d_a_b_zant_magic.h index 0d05f9bd41..e37b055bfd 100644 --- a/include/d/actor/d_a_b_zant_magic.h +++ b/include/d/actor/d_a_b_zant_magic.h @@ -24,7 +24,6 @@ public: int _delete(); int create(); -private: /* 0x5AC */ Mtx field_0x5ac; /* 0x5DC */ cXyz field_0x5dc; /* 0x5E8 */ f32 field_0x5e8; diff --git a/include/d/actor/d_a_b_zant_mobile.h b/include/d/actor/d_a_b_zant_mobile.h index 3af85c56b1..18dcdbabc1 100644 --- a/include/d/actor/d_a_b_zant_mobile.h +++ b/include/d/actor/d_a_b_zant_mobile.h @@ -33,7 +33,6 @@ public: bool isSearchContinue() { return mSearchContinue != false; } void offSearchContinue() { mSearchContinue = false; } -private: /* 0x5AC */ request_of_phase_process_class mPhase; /* 0x5B4 */ mDoExt_McaMorfSO* mpMorf; /* 0x5B8 */ mDoExt_brkAnm* mpBrk; diff --git a/include/d/actor/d_a_balloon_2D.h b/include/d/actor/d_a_balloon_2D.h index 80aa3e3b02..04d450665c 100644 --- a/include/d/actor/d_a_balloon_2D.h +++ b/include/d/actor/d_a_balloon_2D.h @@ -72,7 +72,7 @@ public: struct ScoreCount { cXyz field_0x0; u16 field_0xc; - u8 field_0xe; + DUSK_IF_ELSE(f32, u8) field_0xe; u8 field_0xf; }; diff --git a/include/d/actor/d_a_bg.h b/include/d/actor/d_a_bg.h index 0a9859198b..51407a06c8 100644 --- a/include/d/actor/d_a_bg.h +++ b/include/d/actor/d_a_bg.h @@ -57,7 +57,6 @@ public: inline int execute(); inline int isDelete(); -private: /* 0x568 */ u8 field_0x568[0x570 - 0x568]; /* 0x570 */ daBg_Part mBgParts[6]; /* 0x5E8 */ dBgW* mpBgW; diff --git a/include/d/actor/d_a_boomerang.h b/include/d/actor/d_a_boomerang.h index fe39f226b7..5c4f6f8ede 100644 --- a/include/d/actor/d_a_boomerang.h +++ b/include/d/actor/d_a_boomerang.h @@ -17,7 +17,11 @@ public: void setSight(cXyz const*, int); virtual void draw(); +#if TARGET_PC + virtual ~daBoomerang_sight_c(); +#else virtual ~daBoomerang_sight_c() {} +#endif u8 getReserve() const { return mReserve; } u8 getAlpha(int i_index) { return m_alpha[i_index]; } @@ -25,7 +29,6 @@ public: void onReserve() { mReserve = 1; } void offReserve() { mReserve = 0; } -private: /* 0x04 */ J2DScreen* m_cursorYellowScrn; /* 0x08 */ J2DPane* m_cursorYellowAllPane; /* 0x0C */ J2DPane* m_cursorYellow0Pane; @@ -126,7 +129,6 @@ public: bool getLockCntMax() { return m_lockCnt >= BOOMERANG_LOCK_MAX; } void onLockDistanceCancel() { onStateFlg0(FLG0_LOCK_DIST_CANCEL); } -private: /* 0x568 */ J3DModel* mp_boomModel; /* 0x56C */ J3DModel* mp_shippuModel; /* 0x570 */ J3DAnmTextureSRTKey* m_windBtk; diff --git a/include/d/actor/d_a_canoe.h b/include/d/actor/d_a_canoe.h index bf34a5eee7..bcd7615e18 100644 --- a/include/d/actor/d_a_canoe.h +++ b/include/d/actor/d_a_canoe.h @@ -65,7 +65,6 @@ public: field_0x1456 = 15; } -private: /* 0x0568 */ request_of_phase_process_class mPhaseReq; /* 0x0570 */ J3DModel* mpModel; /* 0x0574 */ J3DModel* mpPaddleModel; diff --git a/include/d/actor/d_a_coach_fire.h b/include/d/actor/d_a_coach_fire.h index f669525e9c..1e8aa2d661 100644 --- a/include/d/actor/d_a_coach_fire.h +++ b/include/d/actor/d_a_coach_fire.h @@ -42,7 +42,6 @@ public: void setNoHitTimer() { noHitTimer = 20; } inline int execute(); -private: /* 0x568 */ LIGHT_INFLUENCE mLightInfluence; /* 0x588 */ dCcD_Stts mStts; /* 0x5C4 */ dCcD_Sph mSph; diff --git a/include/d/actor/d_a_cow.h b/include/d/actor/d_a_cow.h index 403dc2aef6..e215256b91 100644 --- a/include/d/actor/d_a_cow.h +++ b/include/d/actor/d_a_cow.h @@ -208,7 +208,6 @@ public: u8 getCowIn() { return mCowIn; } u8 getNoNearCheckTimer() { return mNoNearCheckTimer; } -private: /* 0x568 */ u16 mFlags; /* 0x56c */ request_of_phase_process_class mPhase; /* 0x574 */ mDoExt_McaMorfSO* mpMorf; diff --git a/include/d/actor/d_a_crod.h b/include/d/actor/d_a_crod.h index 84938d454a..02311fbeb5 100644 --- a/include/d/actor/d_a_crod.h +++ b/include/d/actor/d_a_crod.h @@ -48,7 +48,6 @@ public: static f32 getFlyInitY() { return 40.0f; } static f32 getFlyInitZ() { return 50.0f; } -private: /* 0x568 */ J3DModel* mpBallModel; /* 0x56C */ mDoExt_bckAnm mBck; /* 0x588 */ J3DAnmTevRegKey* mpBallBrk; diff --git a/include/d/actor/d_a_cstaF.h b/include/d/actor/d_a_cstaF.h index 9cb9e06ff7..982aa061a8 100644 --- a/include/d/actor/d_a_cstaF.h +++ b/include/d/actor/d_a_cstaF.h @@ -40,7 +40,6 @@ public: }; static DUSK_GAME_DATA const BckTbl m_bckIdxTable[]; -private: /* 0x5A0 */ const char* m_arcName; /* 0x5A4 */ request_of_phase_process_class m_phase; /* 0x5AC */ J3DModel* mp_model; diff --git a/include/d/actor/d_a_cstatue.h b/include/d/actor/d_a_cstatue.h index ca0b3ca616..2b74240fb2 100644 --- a/include/d/actor/d_a_cstatue.h +++ b/include/d/actor/d_a_cstatue.h @@ -101,7 +101,6 @@ public: static DUSK_GAME_DATA u16 const m_bckIdxTable[daCstatueType_N][7]; -private: /* 0x568 */ const char* mResName; /* 0x56C */ request_of_phase_process_class mPhaseReq; /* 0x574 */ J3DModel* mModel; diff --git a/include/d/actor/d_a_demo_item.h b/include/d/actor/d_a_demo_item.h index ed6d195f5e..62780f572f 100644 --- a/include/d/actor/d_a_demo_item.h +++ b/include/d/actor/d_a_demo_item.h @@ -55,7 +55,6 @@ public: void setOffsetPos(cXyz i_pos) { mOffsetPos = i_pos; } void setMaxScale(f32 i_scale) { mMaxScale = i_scale; } -private: /* 0x92C */ cXyz mOffsetPos; /* 0x938 */ f32 mMaxScale; /* 0x93C */ u8 field_0x93c; diff --git a/include/d/actor/d_a_door_boss.h b/include/d/actor/d_a_door_boss.h index afd7984bf1..722afc0a84 100644 --- a/include/d/actor/d_a_door_boss.h +++ b/include/d/actor/d_a_door_boss.h @@ -50,7 +50,6 @@ public: void setAction(u8 i_action) { mAction = i_action; } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpDoorModel; /* 0x574 */ J3DModel* mpLockModel; diff --git a/include/d/actor/d_a_door_bossL1.h b/include/d/actor/d_a_door_bossL1.h index 7e14025cd2..c0b8f2b44a 100644 --- a/include/d/actor/d_a_door_bossL1.h +++ b/include/d/actor/d_a_door_bossL1.h @@ -76,7 +76,6 @@ public: void setAction(u8 action) { mAction = action; } -private: /* 0x568 */ request_of_phase_process_class mPhase1; /* 0x570 */ request_of_phase_process_class mPhase2; /* 0x578 */ request_of_phase_process_class mPhase3; diff --git a/include/d/actor/d_a_door_bossL5.h b/include/d/actor/d_a_door_bossL5.h index 832f575ac1..d876bab9c8 100644 --- a/include/d/actor/d_a_door_bossL5.h +++ b/include/d/actor/d_a_door_bossL5.h @@ -72,7 +72,6 @@ public: return 1; } -private: /* 0x568 */ request_of_phase_process_class mPhase1; /* 0x570 */ request_of_phase_process_class mPhase2; /* 0x578 */ request_of_phase_process_class mPhase3; diff --git a/include/d/actor/d_a_door_dbdoor00.h b/include/d/actor/d_a_door_dbdoor00.h index 9b2f65e756..ec976c7f6b 100644 --- a/include/d/actor/d_a_door_dbdoor00.h +++ b/include/d/actor/d_a_door_dbdoor00.h @@ -41,7 +41,6 @@ public: void setAction(u8 i_action) { mAction = i_action; } -// private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ request_of_phase_process_class mPhaseReq2; /* 0x578 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_door_knob00.h b/include/d/actor/d_a_door_knob00.h index 577b26b58e..742b8921c9 100644 --- a/include/d/actor/d_a_door_knob00.h +++ b/include/d/actor/d_a_door_knob00.h @@ -73,7 +73,6 @@ public: void onFlag(u16 flag) { field_0x60c |= flag; } int checkFlag(u16 flag) { return field_0x60c & flag; } -private: /* 0x568 */ request_of_phase_process_class mPhase1; /* 0x570 */ request_of_phase_process_class mPhase2; /* 0x578 */ J3DModel* mModel1; diff --git a/include/d/actor/d_a_door_mbossL1.h b/include/d/actor/d_a_door_mbossL1.h index 2bd95d920b..7b76f903bc 100644 --- a/include/d/actor/d_a_door_mbossL1.h +++ b/include/d/actor/d_a_door_mbossL1.h @@ -121,7 +121,6 @@ public: mAction = action; } -private: /* 0x568 */ request_of_phase_process_class mPhase1; /* 0x570 */ request_of_phase_process_class mPhase2; /* 0x578 */ request_of_phase_process_class mPhase3; diff --git a/include/d/actor/d_a_door_push.h b/include/d/actor/d_a_door_push.h index 5beb3aa564..f648ef208b 100644 --- a/include/d/actor/d_a_door_push.h +++ b/include/d/actor/d_a_door_push.h @@ -49,7 +49,6 @@ public: void setAction(u8 i_action) { mAction = i_action; } u32 getSceneNo() { return fopAcM_GetParamBit(this, 4, 6); } -// private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel[2]; /* 0x5B0 */ dBgW* mpDoorBgW; diff --git a/include/d/actor/d_a_door_shutter.h b/include/d/actor/d_a_door_shutter.h index 34ea7f7fc9..c3b134ddb8 100644 --- a/include/d/actor/d_a_door_shutter.h +++ b/include/d/actor/d_a_door_shutter.h @@ -123,7 +123,6 @@ public: bool checkFlag(u16 flag) { return field_0x68e & flag; } void setAction(u8 action) { mAction = action; } -// private: /* 0x56C */ request_of_phase_process_class mPhase1; /* 0x574 */ request_of_phase_process_class mPhase2; /* 0x57C */ J3DModel* mModel1; diff --git a/include/d/actor/d_a_e_ai.h b/include/d/actor/d_a_e_ai.h index d2f1b6ec06..78c4174338 100644 --- a/include/d/actor/d_a_e_ai.h +++ b/include/d/actor/d_a_e_ai.h @@ -48,7 +48,6 @@ public: int CreateHeap(); int Create(); -private: /* 0x5AC */ JPABaseEmitter* mpEmitter; /* 0x5B0 */ request_of_phase_process_class m_phase; /* 0x5B8 */ u8 field_0x5b8; diff --git a/include/d/actor/d_a_e_bg.h b/include/d/actor/d_a_e_bg.h index c14b0cf533..b3b0e1d8bf 100644 --- a/include/d/actor/d_a_e_bg.h +++ b/include/d/actor/d_a_e_bg.h @@ -45,7 +45,6 @@ public: void setBgId(unsigned int i_bgId) { mBgId = i_bgId; } bool isBomb() { return mIsBomb; } -private: /* 0x5AC */ request_of_phase_process_class mPhaseReq; /* 0x5B4 */ mDoExt_McaMorfSO* mpMorfSO; /* 0x5B8 */ mDoExt_brkAnm* mpBrkAnm; diff --git a/include/d/actor/d_a_e_db.h b/include/d/actor/d_a_e_db.h index 4f10715443..a95722a6d1 100644 --- a/include/d/actor/d_a_e_db.h +++ b/include/d/actor/d_a_e_db.h @@ -80,12 +80,6 @@ public: /* 0x125C */ u32 field_0x125c; /* 0x1260 */ u8 field_0x1260[0x126C - 0x1260]; /* 0x126C */ u8 HIOInit; -#if TARGET_PC - cXyz mStalkLineInterpPrev[12]; - cXyz mStalkLineInterpCurr[12]; - bool mStalkLineInterpPrevValid; - bool mStalkLineInterpCurrValid; -#endif }; STATIC_ASSERT(sizeof(e_db_class) == 0x1270); diff --git a/include/d/actor/d_a_e_dk.h b/include/d/actor/d_a_e_dk.h index 381f8e1a62..2c7d46b84f 100644 --- a/include/d/actor/d_a_e_dk.h +++ b/include/d/actor/d_a_e_dk.h @@ -45,7 +45,6 @@ public: int create(); daE_DK_c(); -private: /* 0x5AC */ request_of_phase_process_class mPhaseReq; /* 0x5B4 */ mDoExt_McaMorfSO* mpMorfSO; /* 0x5B8 */ mDoExt_McaMorfSO* mpCoreMorfSO; diff --git a/include/d/actor/d_a_e_dt.h b/include/d/actor/d_a_e_dt.h index c5c83c76e0..0fdb13f4e5 100644 --- a/include/d/actor/d_a_e_dt.h +++ b/include/d/actor/d_a_e_dt.h @@ -83,7 +83,6 @@ public: MtxP getBackMtx() { return mpMorf->getModel()->getAnmMtx(1); } fpc_ProcID getDemoId() { return mDemoID; } -private: /* 0x05AC */ request_of_phase_process_class mPhase[3]; /* 0x05C4 */ J3DModel* mpChestModel; /* 0x05C8 */ J3DModel* mpSpitModel; diff --git a/include/d/actor/d_a_e_fb.h b/include/d/actor/d_a_e_fb.h index a4494c5e7a..ae48f77ea6 100644 --- a/include/d/actor/d_a_e_fb.h +++ b/include/d/actor/d_a_e_fb.h @@ -40,7 +40,6 @@ public: cPhs_Step create(); daE_FB_c(); -private: /* 0x5AC */ request_of_phase_process_class mPhaseReq; /* 0x5B4 */ mDoExt_McaMorfSO* mpMorf; /* 0x5B8 */ mDoExt_invisibleModel mInvisibleModel; diff --git a/include/d/actor/d_a_e_fk.h b/include/d/actor/d_a_e_fk.h index 390e4e3420..fb18f74792 100644 --- a/include/d/actor/d_a_e_fk.h +++ b/include/d/actor/d_a_e_fk.h @@ -52,7 +52,6 @@ public: void CreateChk(); int Create(); -private: /* 0x5AC */ u8 field_0x5AC[0x5B1 - 0x5AC]; /* 0x5B1 */ u8 mUpDownMode; /* 0x5B2 */ u8 field_0x5B2[0x5B6 - 0x5B2]; diff --git a/include/d/actor/d_a_e_fz.h b/include/d/actor/d_a_e_fz.h index e0b838aa51..e40232b74f 100644 --- a/include/d/actor/d_a_e_fz.h +++ b/include/d/actor/d_a_e_fz.h @@ -63,7 +63,6 @@ public: s32 CreateHeap(); s32 create(); -private: /* 0x5AC */ request_of_phase_process_class mPhaseReq; /**< Phase request used when creating the actor. */ /* 0x5B4 */ J3DModel* mpModel; /**< Pointer to the Mini Freezard's model. */ /* 0x5B8 */ mDoExt_invisibleModel mInvisibleModel; /**< Invisible model used for the Mini Freezard. */ diff --git a/include/d/actor/d_a_e_ge.h b/include/d/actor/d_a_e_ge.h index 527e5bb5fa..3c4d4ffbee 100644 --- a/include/d/actor/d_a_e_ge.h +++ b/include/d/actor/d_a_e_ge.h @@ -61,7 +61,6 @@ public: u8 getGroupNo() { return mGroupNo; } -private: /* 0x5AC */ request_of_phase_process_class mPhaseReq; /* 0x5B4 */ mDoExt_McaMorfSO* mpMorfSO; /* 0x5B8 */ Z2CreatureEnemy mSound; diff --git a/include/d/actor/d_a_e_gi.h b/include/d/actor/d_a_e_gi.h index adadfd03b8..a731797f32 100644 --- a/include/d/actor/d_a_e_gi.h +++ b/include/d/actor/d_a_e_gi.h @@ -47,7 +47,6 @@ public: bool isBattleOn() { return mIsBattleOn; } bool isAttackStart() { return mIsAttackStart; } -private: /* 0x05AC */ request_of_phase_process_class mPhase; /* 0x05B4 */ mDoExt_McaMorfSO* mpModelMorf; /* 0x05B8 */ J3DModel* mpSwordModel; diff --git a/include/d/actor/d_a_e_gm.h b/include/d/actor/d_a_e_gm.h index 370dea9150..de8c0d6acc 100644 --- a/include/d/actor/d_a_e_gm.h +++ b/include/d/actor/d_a_e_gm.h @@ -62,7 +62,6 @@ public: BOOL checkAction(actionFunc action) { return mAction == action; } BOOL checkOldAction(actionFunc action) { return mOldAction == action; } -private: /* 0x5AC */ request_of_phase_process_class mPhase; /* 0x5B4 */ dBgS_AcchCir mAcchCir; /* 0x5F4 */ dBgS_ObjAcch mObjAcch; diff --git a/include/d/actor/d_a_e_hb.h b/include/d/actor/d_a_e_hb.h index 3069bcd325..a7e0241007 100644 --- a/include/d/actor/d_a_e_hb.h +++ b/include/d/actor/d_a_e_hb.h @@ -73,12 +73,6 @@ public: /* 0x124C */ f32 field_0x124c; /* 0x1250 */ u8 field_0x1250[0x1264 - 0x1250]; /* 0x1264 */ u8 HIOInit; -#if TARGET_PC - cXyz mStalkLineInterpPrev[12]; - cXyz mStalkLineInterpCurr[12]; - bool mStalkLineInterpPrevValid; - bool mStalkLineInterpCurrValid; -#endif }; STATIC_ASSERT(sizeof(e_hb_class) == 0x1268); diff --git a/include/d/actor/d_a_e_hm.h b/include/d/actor/d_a_e_hm.h index 85f6e5422c..9be48a53f3 100644 --- a/include/d/actor/d_a_e_hm.h +++ b/include/d/actor/d_a_e_hm.h @@ -65,7 +65,6 @@ public: void CheckWall(); void CreateStyle(); int Create(); -private: /* 0x5AC */ u8 field_0x5ac; /* 0x5AD */ u8 field_0x5ad; /* 0x5AE */ u8 field_0x5ae; diff --git a/include/d/actor/d_a_e_hz.h b/include/d/actor/d_a_e_hz.h index 539d808cf5..e48a72ee69 100644 --- a/include/d/actor/d_a_e_hz.h +++ b/include/d/actor/d_a_e_hz.h @@ -64,7 +64,6 @@ public: void setInitPos(); int create(); -private: /* 0x5AC */ request_of_phase_process_class mPhaseReq; /* 0x5B4 */ request_of_phase_process_class mPhaseReq2; /* 0x5BC */ mDoExt_McaMorfSO* mpMorfSO; diff --git a/include/d/actor/d_a_e_kk.h b/include/d/actor/d_a_e_kk.h index 890da0eba2..a249cafc74 100644 --- a/include/d/actor/d_a_e_kk.h +++ b/include/d/actor/d_a_e_kk.h @@ -49,7 +49,6 @@ public: int CreateHeap(); int create(); -private: /* 0x05AC */ request_of_phase_process_class mPhaseReq; /* 0x05B4 */ mDoExt_McaMorfSO* mpMorfSO; /* 0x05B8 */ mDoExt_McaMorfSO* mpWeaponMorfSO; diff --git a/include/d/actor/d_a_e_mb.h b/include/d/actor/d_a_e_mb.h index 527cf30eac..f36c744879 100644 --- a/include/d/actor/d_a_e_mb.h +++ b/include/d/actor/d_a_e_mb.h @@ -44,12 +44,6 @@ public: /* 0x88C */ u8 field_0x88C[0x8C8 - 0x88C]; /* 0x8C8 */ s8 field_0x8c8; /* 0x8C9 */ u8 mInitHIO; -#if TARGET_PC - cXyz mRopeInterpPrev[16]; - cXyz mRopeInterpCurr[16]; - bool mRopeInterpPrevValid; - bool mRopeInterpCurrValid; -#endif }; STATIC_ASSERT(sizeof(e_mb_class) == 0x8cc); diff --git a/include/d/actor/d_a_e_oc.h b/include/d/actor/d_a_e_oc.h index 7f60afa878..2d40d9bcbc 100644 --- a/include/d/actor/d_a_e_oc.h +++ b/include/d/actor/d_a_e_oc.h @@ -87,7 +87,6 @@ public: daE_OC_c* getTalkOc() { return mpTalk; } J3DModel* getOcModel() { return mpMorf->getModel(); } -private: /* 0x5a0 */ request_of_phase_process_class mPhaseReqs[2]; /* 0x5bc */ mDoExt_McaMorfSO* mpMorf; /* 0x5c0 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_e_oct_bg.h b/include/d/actor/d_a_e_oct_bg.h index cd5b6b27cd..9033e03736 100644 --- a/include/d/actor/d_a_e_oct_bg.h +++ b/include/d/actor/d_a_e_oct_bg.h @@ -54,7 +54,6 @@ public: u32 checkAction(actionFunc func) { return field_0xb58 == func; } bool checkNormalAttk() { return checkAction(&daE_OctBg_c::normal_attack); } -private: /* 0x5AC */ request_of_phase_process_class mPhase; /* 0x5B4 */ mDoExt_McaMorfSO* field_0x5b4; /* 0x5B8 */ mDoExt_brkAnm* field_0x5b8; diff --git a/include/d/actor/d_a_e_ot.h b/include/d/actor/d_a_e_ot.h index 54e8399b7b..3505062e3c 100644 --- a/include/d/actor/d_a_e_ot.h +++ b/include/d/actor/d_a_e_ot.h @@ -54,7 +54,6 @@ public: /* 0x4 */ ACT_DAMAGE, }; -private: /* 0x5AC */ request_of_phase_process_class mPhase; /* 0x5B4 */ mDoExt_McaMorfSO* mpMorf; /* 0x5B8 */ J3DModel* mpEggModel; diff --git a/include/d/actor/d_a_e_pm.h b/include/d/actor/d_a_e_pm.h index edec93c6a2..35368eff61 100644 --- a/include/d/actor/d_a_e_pm.h +++ b/include/d/actor/d_a_e_pm.h @@ -84,7 +84,6 @@ public: u8 SwitchChk() { return mStage; } u8 DoorChk() { return mDoorAction; } -private: /* 0x5AC */ fpc_ProcID mPuppetID[4]; /* 0x5BC */ u8 field_0x5bc[0x5DC - 0x5BC]; /* 0x5DC */ u8 mDoorAction; diff --git a/include/d/actor/d_a_e_s1.h b/include/d/actor/d_a_e_s1.h index 5cbae84696..a631ba47ac 100644 --- a/include/d/actor/d_a_e_s1.h +++ b/include/d/actor/d_a_e_s1.h @@ -81,15 +81,6 @@ public: /* 0x306D */ u8 field_0x306D[0x307C - 0x306D]; /* 0x307C */ u32 mBodyEffEmtrID; /* 0x3080 */ u8 mInitHIO; - -#if TARGET_PC - static const int HAIR_STRAND_COUNT = 22; - static const int HAIR_SEGMENT_COUNT = 16; - cXyz mHairInterpPrev[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT]; - cXyz mHairInterpCurr[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT]; - bool mHairInterpPrevValid; - bool mHairInterpCurrValid; -#endif }; STATIC_ASSERT(sizeof(e_s1_class) == 0x3084); diff --git a/include/d/actor/d_a_e_sb.h b/include/d/actor/d_a_e_sb.h index e015ecc5bc..bd7d8c9bdc 100644 --- a/include/d/actor/d_a_e_sb.h +++ b/include/d/actor/d_a_e_sb.h @@ -57,7 +57,6 @@ public: int Delete(); void setBaseMtx(); cPhs_Step Create(); -private: /* 0x5AC */ int field_0x5ac; /* 0x5B0 */ int field_0x5b0; /* 0x5B4 */ int field_0x5b4; @@ -78,7 +77,6 @@ private: public: /* 0x5EC */ cXyz* field_0x5ec; /* 0x5F0 */ f32 field_0x5f0; -private: /* 0x5F4 */ cXyz field_0x5f4; /* 0x600 */ s16 field_0x600; /* 0x602 */ u8 field_0x602; diff --git a/include/d/actor/d_a_e_sw.h b/include/d/actor/d_a_e_sw.h index b073dae952..6f6a71f16d 100644 --- a/include/d/actor/d_a_e_sw.h +++ b/include/d/actor/d_a_e_sw.h @@ -82,7 +82,6 @@ public: bool d_checkOldAction(void (daE_SW_c::*action)()) { return field_0xb0c == action; } -private: /* 0x5AC */ request_of_phase_process_class mPhase; /* 0x5B4 */ mDoExt_McaMorfSO* mpModelMorf; /* 0x5B8 */ Z2CreatureEnemy mSound; diff --git a/include/d/actor/d_a_e_tt.h b/include/d/actor/d_a_e_tt.h index f3b79a69b3..38dc53ac3f 100644 --- a/include/d/actor/d_a_e_tt.h +++ b/include/d/actor/d_a_e_tt.h @@ -47,7 +47,6 @@ public: int CreateHeap(); int create(); -private: /* 0x5AC */ request_of_phase_process_class mPhaseReq; /* 0x5B4 */ request_of_phase_process_class mPhaseReq2; /* 0x5BC */ mDoExt_McaMorfSO* mpMorfSO; diff --git a/include/d/actor/d_a_e_vt.h b/include/d/actor/d_a_e_vt.h index 4b6082adae..b55bb4891a 100644 --- a/include/d/actor/d_a_e_vt.h +++ b/include/d/actor/d_a_e_vt.h @@ -146,7 +146,6 @@ public: int create(); daE_VA_c(); -private: /* 0x005AC */ request_of_phase_process_class mPhase; /* 0x005B4 */ mDoExt_McaMorfSO* mpMorf; /* 0x005B8 */ mDoExt_McaMorfSO* mpEndEfMorf; diff --git a/include/d/actor/d_a_e_wb.h b/include/d/actor/d_a_e_wb.h index aecbac96a1..770e537f2e 100644 --- a/include/d/actor/d_a_e_wb.h +++ b/include/d/actor/d_a_e_wb.h @@ -221,12 +221,6 @@ public: /* 0x17E4 */ u8 field_0x17e4[0x17e8 - 0x17e4]; /* 0x17E8 */ f32 ride_speed_max; ///< @brief Speed rate for riding calculations. #if TARGET_PC - cXyz himo_mat_interp_prev[2][16]; - cXyz himo_mat_interp_curr[2][16]; - cXyz himo_tex_interp_prev[2]; - cXyz himo_tex_interp_curr[2]; - bool himo_interp_prev_valid; - bool himo_interp_curr_valid; s8 demo_cam_sync_ticks; #endif }; diff --git a/include/d/actor/d_a_e_ww.h b/include/d/actor/d_a_e_ww.h index fc1083c34e..f1ebac248a 100644 --- a/include/d/actor/d_a_e_ww.h +++ b/include/d/actor/d_a_e_ww.h @@ -59,7 +59,6 @@ public: void setAttack() { mActionMode = 10; } f32 const getSpeedF() { return speedF; } -private: /* 0x5AC */ request_of_phase_process_class mPhase; /* 0x5B4 */ mDoExt_McaMorfSO* mpModelMorf; /* 0x5B8 */ Z2CreatureEnemy mSound; diff --git a/include/d/actor/d_a_e_yd.h b/include/d/actor/d_a_e_yd.h index 44d6036600..188e435ba5 100644 --- a/include/d/actor/d_a_e_yd.h +++ b/include/d/actor/d_a_e_yd.h @@ -74,12 +74,6 @@ public: /* 0x1250 */ f32 field_0x1250; /* 0x1254 */ u8 field_0x1254[0x1268 - 0x1254]; /* 0x1268 */ u8 field_0x1268; -#if TARGET_PC - cXyz mLineMatInterpPrev[12]; - cXyz mLineMatInterpCurr[12]; - bool mLineMatInterpPrevValid; - bool mLineMatInterpCurrValid; -#endif }; STATIC_ASSERT(sizeof(e_yd_class) == 0x126c); diff --git a/include/d/actor/d_a_e_yg.h b/include/d/actor/d_a_e_yg.h index 6be19933cb..69a85430a8 100644 --- a/include/d/actor/d_a_e_yg.h +++ b/include/d/actor/d_a_e_yg.h @@ -63,15 +63,6 @@ public: /* 0x0BB4 */ yg_ke_s mYgKes[13]; /* 0x1880 */ mDoExt_3DlineMat0_c mLineMat; /* 0x189C */ u8 mIsFirstSpawn; - -#if TARGET_PC - static const int TENTACLE_STRAND_COUNT = 13; - static const int TENTACLE_SEGMENT_COUNT = 10; - cXyz mTentacleInterpPrev[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT]; - cXyz mTentacleInterpCurr[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT]; - bool mTentacleInterpPrevValid; - bool mTentacleInterpCurrValid; -#endif }; STATIC_ASSERT(sizeof(e_yg_class) == 0x18a0); diff --git a/include/d/actor/d_a_e_yh.h b/include/d/actor/d_a_e_yh.h index 63e8ac1882..519e5e2779 100644 --- a/include/d/actor/d_a_e_yh.h +++ b/include/d/actor/d_a_e_yh.h @@ -77,12 +77,6 @@ public: /* 0x1260 */ u32 field_0x1260; /* 0x1260 */ u8 field_0x1264[0x1270 - 0x1264]; /* 0x1270 */ bool mIsHIOOwner; -#if TARGET_PC - cXyz mLineInterpPrev[12]; - cXyz mLineInterpCurr[12]; - bool mLineInterpPrevValid; - bool mLineInterpCurrValid; -#endif }; STATIC_ASSERT(sizeof(e_yh_class) == 0x1274); diff --git a/include/d/actor/d_a_e_ym.h b/include/d/actor/d_a_e_ym.h index 9638736b46..699d14d98f 100644 --- a/include/d/actor/d_a_e_ym.h +++ b/include/d/actor/d_a_e_ym.h @@ -122,7 +122,6 @@ public: u8 getSwitchBit() { return mSwitchBit; } u8 isHide() { return mIsHide; } -private: /* 0x5AC */ request_of_phase_process_class mPhase; /* 0x5B4 */ mDoExt_McaMorfSO* mpMorf; /* 0x5B8 */ mDoExt_brkAnm* mpBrk; diff --git a/include/d/actor/d_a_e_zs.h b/include/d/actor/d_a_e_zs.h index d15b384650..93dbd82d89 100644 --- a/include/d/actor/d_a_e_zs.h +++ b/include/d/actor/d_a_e_zs.h @@ -39,7 +39,6 @@ public: int CreateHeap(); int create(); -private: /* 0x5ac */ request_of_phase_process_class mPhase; /* 0x5b4 */ mDoExt_McaMorfSO* mpMorf; /* 0x5b8 */ Z2CreatureEnemy mSound; diff --git a/include/d/actor/d_a_horse.h b/include/d/actor/d_a_horse.h index bb7ce7ccb1..2074570517 100644 --- a/include/d/actor/d_a_horse.h +++ b/include/d/actor/d_a_horse.h @@ -196,9 +196,6 @@ public: void copyReinPos(); void setReinPosHandSubstance(int); void setReinPosNormalSubstance(); -#if TARGET_PC - void lerpControlPoints(f32 alpha); -#endif void bgCheck(); bool checkSpecialWallHitSubstance(cXyz const&) const; void setServiceWaitTimer(); diff --git a/include/d/actor/d_a_kago.h b/include/d/actor/d_a_kago.h index 33ec51672f..ba0e77a37c 100644 --- a/include/d/actor/d_a_kago.h +++ b/include/d/actor/d_a_kago.h @@ -140,7 +140,6 @@ public: void setEatYm() { field_0x6d8 |= (u8) 4; } MtxP getMouthMtx() { return mAnm_p->getModel()->getAnmMtx(7); } -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ mDoExt_McaMorfSO* mAnm_p; /* 0x574 */ Z2CreatureEnemy mSound; diff --git a/include/d/actor/d_a_mant.h b/include/d/actor/d_a_mant.h index 6d5b173d1a..e6f3d71a9c 100644 --- a/include/d/actor/d_a_mant.h +++ b/include/d/actor/d_a_mant.h @@ -92,10 +92,7 @@ public: /* 0x39A4 */ cM_rnd_c mMantRng; #endif }; -#if TARGET_PC -STATIC_ASSERT(sizeof(mant_class) == 0x39ac); -#else + STATIC_ASSERT(sizeof(mant_class) == 0x39a4); -#endif #endif /* D_A_MANT_H */ diff --git a/include/d/actor/d_a_mg_rod.h b/include/d/actor/d_a_mg_rod.h index 97a0bb1c72..76863f33d5 100644 --- a/include/d/actor/d_a_mg_rod.h +++ b/include/d/actor/d_a_mg_rod.h @@ -299,13 +299,6 @@ public: /* 0x168C */ u8 field_0x168c; /* 0x168D */ u8 field_0x168d; /* 0x168E */ u8 HIOInit; - -#if TARGET_PC - cXyz mLineInterpPrev[MG_ROD_LURE_LINE_LEN]; - cXyz mLineInterpCurr[MG_ROD_LURE_LINE_LEN]; - bool mLineInterpPrevValid; - bool mLineInterpCurrValid; -#endif }; #endif /* D_A_MG_ROD_H */ diff --git a/include/d/actor/d_a_midna.h b/include/d/actor/d_a_midna.h index 2b07386ca7..1371dce12c 100644 --- a/include/d/actor/d_a_midna.h +++ b/include/d/actor/d_a_midna.h @@ -423,7 +423,6 @@ public: static DUSK_GAME_DATA daMidna_texData_s const m_texDataTable[21]; static DUSK_GAME_DATA daMidna_anmData_s const m_anmDataTable[53]; -// private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ J3DModel* mpShadowModel; diff --git a/include/d/actor/d_a_movie_player.h b/include/d/actor/d_a_movie_player.h index fbd8784d40..785f9458ab 100644 --- a/include/d/actor/d_a_movie_player.h +++ b/include/d/actor/d_a_movie_player.h @@ -4,6 +4,7 @@ #if !TARGET_PC #include #else +#include #include #include #endif @@ -17,29 +18,6 @@ struct daMP_THPReadBuffer { }; #if TARGET_PC -// Copying here because thp.h is probably erroneous in the dolphin lib, -// and it's kind of a problem being there (Aurora owns the headers). -// TODO: Move this stuff in decomp? -typedef struct THPAudioRecordHeader { - BE(u32) offsetNextChannel; - BE(u32) sampleSize; - BE(s16) lCoef[8][2]; - BE(s16) rCoef[8][2]; - BE(s16) lYn1; - BE(s16) lYn2; - BE(s16) rYn1; - BE(s16) rYn2; -} THPAudioRecordHeader; - -typedef struct THPAudioDecodeInfo { - u8* encodeData; - u32 offsetNibbles; - u8 predictor; - u8 scale; - s16 yn1; - s16 yn2; -} THPAudioDecodeInfo; - typedef struct THPTextureSet { u8* ytexture; u8* utexture; @@ -86,10 +64,6 @@ typedef struct THPHeader { /* 0x2C */ BE(u32) finalFrameDataOffsets; } THPHeader; -static u32 THPAudioDecode(s16* audioBuffer, u8* audioFrame, s32 flag); -static s32 __THPAudioGetNewSample(THPAudioDecodeInfo* info); -static void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr); - #define THP_AUDIO_BUFFER_COUNT 3 #define THP_READ_BUFFER_COUNT 10 #define THP_TEXTURE_SET_COUNT 3 @@ -166,7 +140,6 @@ public: static DUSK_GAME_DATA daMP_c* m_myObj; -private: /* 0x568 */ u32 (*mpGetMovieRestFrame)(void); /* 0x56C */ void (*mpSetPercentMovieVol)(f32); /* 0x570 */ u32 (*mpTHPGetTotalFrame)(void); diff --git a/include/d/actor/d_a_npc.h b/include/d/actor/d_a_npc.h index 7b99f1e2fe..51425bb912 100644 --- a/include/d/actor/d_a_npc.h +++ b/include/d/actor/d_a_npc.h @@ -29,7 +29,7 @@ struct daNpc_GetParam3 { // name unknown }; class daNpcT_ActorMngr_c { -private: +public: /* 0x0 */ fpc_ProcID mActorID; public: @@ -44,7 +44,7 @@ public: }; class daNpcT_MatAnm_c : public J3DMaterialAnm { -private: +public: /* 0x0F4 */ mutable f32 field_0xF4; /* 0x0F8 */ mutable f32 field_0xF8; /* 0x0FC */ f32 mNowOffsetX; @@ -102,7 +102,6 @@ public: } void setOffset(int i_offset) { mOffset = i_offset; } -private: /* 0x00 */ sequenceStepData_c const* mpSeqData; /* 0x04 */ int mStepNum; /* 0x08 */ int mNo; @@ -217,7 +216,7 @@ public: }; class daNpcT_JntAnm_c { -private: +public: /* 0x000 */ daNpcT_ActorMngr_c mActrMngr; /* 0x008 */ cXyz mPos; /* 0x014 */ cXyz mAttnPos; diff --git a/include/d/actor/d_a_npc2.h b/include/d/actor/d_a_npc2.h index 0f8ab6ba20..2453ba1f30 100644 --- a/include/d/actor/d_a_npc2.h +++ b/include/d/actor/d_a_npc2.h @@ -15,7 +15,6 @@ public: fopAc_ac_c* getActor(); virtual ~daBaseNpc_acMngr_c() {} -private: /* 0x04 */ fpc_ProcID mActorId; }; diff --git a/include/d/actor/d_a_npc4.h b/include/d/actor/d_a_npc4.h index d195eb9669..1bf39f54ec 100644 --- a/include/d/actor/d_a_npc4.h +++ b/include/d/actor/d_a_npc4.h @@ -12,7 +12,7 @@ #include "JSystem/J3DGraphAnimator/J3DMaterialAnm.h" class daNpcF_ActorMngr_c { -private: +public: /* 0x0 */ fpc_ProcID mActorID; public: @@ -52,7 +52,7 @@ BOOL daNpcF_chkPassed(cXyz i_pos, dPnt* i_points, u16 i_idx, u16 i_num, BOOL i_i BOOL daNpcF_chkDoBtnEqSpeak(fopAc_ac_c* i_actor_p); class daNpcF_SPCurve_c { -private: +public: /* 0x00 */ u16 mNurbs; /* 0x02 */ u16 field_0x02; /* 0x04 */ u8 mIsReversed; @@ -119,7 +119,7 @@ public: }; // Size: 0x630 class daNpcF_MatAnm_c : public J3DMaterialAnm { -private: +public: /* 0x0F4 */ mutable f32 field_0xF4; /* 0x0F8 */ mutable f32 field_0xF8; /* 0x0FC */ f32 mNowOffsetX; @@ -138,7 +138,7 @@ public: }; class daNpcF_Lookat_c { -private: +public: /* 0x00 */ cXyz mJointPos[4]; /* 0x30 */ cXyz* mAttnPos_p; /* 0x34 */ csXyz mAngularMoveDis[4]; @@ -406,7 +406,6 @@ public: STATIC_ASSERT(sizeof(daNpcF_c) == 0xB48); class daNpcF_MoveBgActor_c : public daNpcF_c { -private: public: // these functions are inferred based on daBaseNpc_moveBgActor_c - // defining them in this order fixes weak function order for this class in d_a_npc diff --git a/include/d/actor/d_a_npc_aru.h b/include/d/actor/d_a_npc_aru.h index e822850a7e..a41e45acba 100644 --- a/include/d/actor/d_a_npc_aru.h +++ b/include/d/actor/d_a_npc_aru.h @@ -174,7 +174,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[7]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[7]; -private: /* 0xE40 */ NPC_ARU_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_ash.h b/include/d/actor/d_a_npc_ash.h index 46c850a372..e9f6e3427a 100644 --- a/include/d/actor/d_a_npc_ash.h +++ b/include/d/actor/d_a_npc_ash.h @@ -145,7 +145,6 @@ public: static DUSK_GAME_DATA EventFn mEvtSeqList[6]; -private: /* 0xB48 */ Z2Creature mCreatureSound; /* 0xBD8 */ J3DModel* mModelBow; /* 0xBDC */ J3DModel* mModelQuiver; diff --git a/include/d/actor/d_a_npc_ashB.h b/include/d/actor/d_a_npc_ashB.h index e257db2402..9b158acf2d 100644 --- a/include/d/actor/d_a_npc_ashB.h +++ b/include/d/actor/d_a_npc_ashB.h @@ -137,7 +137,6 @@ public: static DUSK_GAME_DATA EventFn DUSK_CONST mEvtSeqList[2]; -private: /* 0xB48 */ Z2Creature mCreatureSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_npc_besu.h b/include/d/actor/d_a_npc_besu.h index 646c4860bd..04579f595b 100644 --- a/include/d/actor/d_a_npc_besu.h +++ b/include/d/actor/d_a_npc_besu.h @@ -143,7 +143,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[15]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[15]; -private: /* 0x0E40 */ mDoExt_McaMorfSO* mpCupModelMorf; /* 0x0E44 */ NPC_BESU_HIO_CLASS* mpHIO; /* 0x0E48 */ J3DModel* mpClothModel[1]; diff --git a/include/d/actor/d_a_npc_bou.h b/include/d/actor/d_a_npc_bou.h index d2c1c0ef2f..d0379b9a1b 100644 --- a/include/d/actor/d_a_npc_bou.h +++ b/include/d/actor/d_a_npc_bou.h @@ -168,7 +168,6 @@ public: return 0; } -private: /* 0xE40 */ NPC_BOU_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl1; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_bouS.h b/include/d/actor/d_a_npc_bouS.h index a3431cfec7..7d6f48fb53 100644 --- a/include/d/actor/d_a_npc_bouS.h +++ b/include/d/actor/d_a_npc_bouS.h @@ -130,7 +130,6 @@ public: static DUSK_GAME_DATA eventFunc mEvtSeqList[4]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_chat.h b/include/d/actor/d_a_npc_chat.h index 5bdbee8869..88ddc9a8b3 100644 --- a/include/d/actor/d_a_npc_chat.h +++ b/include/d/actor/d_a_npc_chat.h @@ -104,7 +104,6 @@ public: static DUSK_GAME_DATA eventFunc DUSK_CONST mEvtSeqList[1]; -private: /* 0xB48 */ Z2CreatureCitizen mSound; /* 0xBEC */ u8 field_0xbec[0xbf0 - 0xbec]; /* 0xBF0 */ J3DModel* mObjModel; diff --git a/include/d/actor/d_a_npc_clerka.h b/include/d/actor/d_a_npc_clerka.h index 12ef5a3b97..6479bae503 100644 --- a/include/d/actor/d_a_npc_clerka.h +++ b/include/d/actor/d_a_npc_clerka.h @@ -127,7 +127,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0x0F7C */ NPC_CLERKA_HIO_CLASS* mpHIO; /* 0x0F80 */ dCcD_Cyl mCyl; /* 0x10BC */ u8 mType; diff --git a/include/d/actor/d_a_npc_clerkb.h b/include/d/actor/d_a_npc_clerkb.h index c6c2ef83fb..9bc2a39a3a 100644 --- a/include/d/actor/d_a_npc_clerkb.h +++ b/include/d/actor/d_a_npc_clerkb.h @@ -140,7 +140,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0x0F7C */ mDoExt_bpkAnm mBpkAnm2; /* 0x0F7C */ NPC_CLERKB_HIO_CLASS* mpHIO; /* 0x0F98 */ dCcD_Cyl mCyl; diff --git a/include/d/actor/d_a_npc_clerkt.h b/include/d/actor/d_a_npc_clerkt.h index 6acfb8970f..a476f83449 100644 --- a/include/d/actor/d_a_npc_clerkt.h +++ b/include/d/actor/d_a_npc_clerkt.h @@ -128,7 +128,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0x0F7C */ NPC_CLERKT_HIO_CLASS* mpHIO; /* 0x0F80 */ dCcD_Cyl mCyl; /* 0x10BC */ u8 mType; diff --git a/include/d/actor/d_a_npc_coach.h b/include/d/actor/d_a_npc_coach.h index f50cbd87a5..e0ecd63fc2 100644 --- a/include/d/actor/d_a_npc_coach.h +++ b/include/d/actor/d_a_npc_coach.h @@ -297,7 +297,6 @@ public: static DUSK_GAME_DATA daNpcCoach_Attr_c const M_attr; static u16 const ParticleName[10]; -private: /* 0x0568 */ daNpcChHorse_c mChHorse; /* 0x0E7C */ daNpcChHarness_c mChHarness; /* 0x15D0 */ daNpcChCoach_c mChCoach; diff --git a/include/d/actor/d_a_npc_doc.h b/include/d/actor/d_a_npc_doc.h index 417251e10b..191a2e5bf2 100644 --- a/include/d/actor/d_a_npc_doc.h +++ b/include/d/actor/d_a_npc_doc.h @@ -136,7 +136,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_DOC_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_doorboy.h b/include/d/actor/d_a_npc_doorboy.h index 31568dd2f6..3fd6ef1ce8 100644 --- a/include/d/actor/d_a_npc_doorboy.h +++ b/include/d/actor/d_a_npc_doorboy.h @@ -80,7 +80,6 @@ public: static DUSK_GAME_DATA EventFn DUSK_CONST mEvtSeqList[1]; -private: /* 0xB48 */ Z2CreatureCitizen mSound; /* 0xBEC */ u8 field_0xbec[0xbf0 - 0xbec]; /* 0xBF0 */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_fairy.h b/include/d/actor/d_a_npc_fairy.h index da9977ed37..ff2df4260b 100644 --- a/include/d/actor/d_a_npc_fairy.h +++ b/include/d/actor/d_a_npc_fairy.h @@ -296,7 +296,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[18]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[18]; -// private: /* 0x0E40 */ NPC_FAIRY_HIO_CLASS* mHIO; /* 0x0E44 */ dCcD_Cyl mCyl; /* 0x0F80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_fairy_seirei.h b/include/d/actor/d_a_npc_fairy_seirei.h index a0ad9342ed..d47b2a17eb 100644 --- a/include/d/actor/d_a_npc_fairy_seirei.h +++ b/include/d/actor/d_a_npc_fairy_seirei.h @@ -93,7 +93,6 @@ public: static DUSK_GAME_DATA DUSK_CONST char* mCutNameList[1]; static DUSK_GAME_DATA DUSK_CONST cutFunc mCutList[1]; -private: /* 0xE40 */ NPC_FAIRY_SEIREI_HIO_CLASS* mpHIO; /* 0xE44 */ u8 mType; /* 0xE48 */ dCcD_Cyl mCyl; diff --git a/include/d/actor/d_a_npc_gnd.h b/include/d/actor/d_a_npc_gnd.h index f61d7e0214..cb0289055e 100644 --- a/include/d/actor/d_a_npc_gnd.h +++ b/include/d/actor/d_a_npc_gnd.h @@ -99,7 +99,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_GND_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl1; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_gra.h b/include/d/actor/d_a_npc_gra.h index 333cf5d299..f16b445195 100644 --- a/include/d/actor/d_a_npc_gra.h +++ b/include/d/actor/d_a_npc_gra.h @@ -142,7 +142,6 @@ public: typedef BOOL (daNpc_grA_c::*cut_type)(int); static DUSK_GAME_DATA cut_type DUSK_CONST mEvtCutList[]; -private: typedef BOOL (daNpc_grA_c::*daNpc_grA_c_Action)(void*); /* 0x0B48 */ Z2Creature mCreature; diff --git a/include/d/actor/d_a_npc_grc.h b/include/d/actor/d_a_npc_grc.h index ac7dfa9869..53a483b51d 100644 --- a/include/d/actor/d_a_npc_grc.h +++ b/include/d/actor/d_a_npc_grc.h @@ -92,7 +92,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList; static DUSK_GAME_DATA EventFn DUSK_CONST mEvtCutList[1]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ int field_0xbd8; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_grd.h b/include/d/actor/d_a_npc_grd.h index 24871ab24c..64afa3ef2c 100644 --- a/include/d/actor/d_a_npc_grd.h +++ b/include/d/actor/d_a_npc_grd.h @@ -84,7 +84,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mEvtCutList[2]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_grm.h b/include/d/actor/d_a_npc_grm.h index d7c30ba90f..c820eabbc3 100644 --- a/include/d/actor/d_a_npc_grm.h +++ b/include/d/actor/d_a_npc_grm.h @@ -134,7 +134,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[2]; -private: /* 0x0F7C */ NPC_GRM_HIO_CLASS* mpHIO; /* 0x0F80 */ dCcD_Cyl mCyl; /* 0x10BC */ u8 mType; diff --git a/include/d/actor/d_a_npc_grmc.h b/include/d/actor/d_a_npc_grmc.h index 1761c82869..1d87c39a4d 100644 --- a/include/d/actor/d_a_npc_grmc.h +++ b/include/d/actor/d_a_npc_grmc.h @@ -127,7 +127,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0x0F7C */ NPC_GRMC_HIO_CLASS* mpHIO; /* 0x0F80 */ dCcD_Cyl mCyl; /* 0x10BC */ u8 mType; diff --git a/include/d/actor/d_a_npc_gro.h b/include/d/actor/d_a_npc_gro.h index b2a5595bd9..8bc6c8c93e 100644 --- a/include/d/actor/d_a_npc_gro.h +++ b/include/d/actor/d_a_npc_gro.h @@ -88,7 +88,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[3]; static DUSK_GAME_DATA cutFunc DUSK_CONST mEvtCutList[3]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ J3DModel* mpOtherModel[1]; /* 0xBDC */ daNpcF_MatAnm_c* mpMatAnm; diff --git a/include/d/actor/d_a_npc_grr.h b/include/d/actor/d_a_npc_grr.h index 6dfa8512bc..4caf927d75 100644 --- a/include/d/actor/d_a_npc_grr.h +++ b/include/d/actor/d_a_npc_grr.h @@ -86,7 +86,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mEvtCutList[1]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_grs.h b/include/d/actor/d_a_npc_grs.h index 5ec4354dd1..9c1ac788dc 100644 --- a/include/d/actor/d_a_npc_grs.h +++ b/include/d/actor/d_a_npc_grs.h @@ -87,7 +87,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mEvtCutList[2]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_npc_grz.h b/include/d/actor/d_a_npc_grz.h index f49c878ff7..249720276f 100644 --- a/include/d/actor/d_a_npc_grz.h +++ b/include/d/actor/d_a_npc_grz.h @@ -119,7 +119,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[7]; static DUSK_GAME_DATA cutFunc DUSK_CONST mEvtCutList[7]; -private: /* 0x0B48 */ Z2Creature mSound; /* 0x0BD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0x0BDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_guard.h b/include/d/actor/d_a_npc_guard.h index bceab108e5..59a6719c9d 100644 --- a/include/d/actor/d_a_npc_guard.h +++ b/include/d/actor/d_a_npc_guard.h @@ -62,7 +62,6 @@ public: static DUSK_GAME_DATA actionFunc ActionTable[7][2]; -private: /* 0xAC8 */ J3DModel* mpModel; /* 0xACC */ J3DModel* mpModel2; /* 0xAD0 */ J3DAnmTransformKey* mpAnmKey; diff --git a/include/d/actor/d_a_npc_gwolf.h b/include/d/actor/d_a_npc_gwolf.h index bd577e18e7..f7f4bc5a8e 100644 --- a/include/d/actor/d_a_npc_gwolf.h +++ b/include/d/actor/d_a_npc_gwolf.h @@ -107,7 +107,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[5]; static DUSK_GAME_DATA cutFunc DUSK_CONST mEvtCutList[5]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_hanjo.h b/include/d/actor/d_a_npc_hanjo.h index e9daf3bb0e..76d354b743 100644 --- a/include/d/actor/d_a_npc_hanjo.h +++ b/include/d/actor/d_a_npc_hanjo.h @@ -221,7 +221,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[6]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[6]; static DUSK_GAME_DATA dCcD_SrcSph DUSK_CONST mStoneCcDSph; -private: /* 0x0E40 */ NPC_HANJO_HIO_CLASS* mpHIO; /* 0x0E44 */ J3DModel* mModel1; /* 0x0E48 */ J3DModel* mModel2; diff --git a/include/d/actor/d_a_npc_henna0.h b/include/d/actor/d_a_npc_henna0.h index 112904d144..63da974cde 100644 --- a/include/d/actor/d_a_npc_henna0.h +++ b/include/d/actor/d_a_npc_henna0.h @@ -12,7 +12,7 @@ * */ class d_a_npc_henna0 : public fopAc_ac_c { - private: + public: /* 0x568 */ u8 field_0x568[0x828 - 0x568]; }; diff --git a/include/d/actor/d_a_npc_hoz.h b/include/d/actor/d_a_npc_hoz.h index aec84cf761..57a2169c78 100644 --- a/include/d/actor/d_a_npc_hoz.h +++ b/include/d/actor/d_a_npc_hoz.h @@ -128,7 +128,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[8]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[]; -private: /* 0xE40 */ NPC_HOZ_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_impal.h b/include/d/actor/d_a_npc_impal.h index f0102ab698..f6753be0e2 100644 --- a/include/d/actor/d_a_npc_impal.h +++ b/include/d/actor/d_a_npc_impal.h @@ -123,7 +123,6 @@ public: static DUSK_GAME_DATA EventFn mEvtSeqList[4]; -private: /* 0xB48 */ Z2Creature mCreatureSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_ins.h b/include/d/actor/d_a_npc_ins.h index 620358a297..fd133a7a79 100644 --- a/include/d/actor/d_a_npc_ins.h +++ b/include/d/actor/d_a_npc_ins.h @@ -121,7 +121,6 @@ public: static DUSK_GAME_DATA eventFunc mEvtSeqList[1]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ J3DModel* mpUmbrellaModel; /* 0xBDC */ J3DModel* mpKagoModel; diff --git a/include/d/actor/d_a_npc_jagar.h b/include/d/actor/d_a_npc_jagar.h index 13147762dd..e933d28bf4 100644 --- a/include/d/actor/d_a_npc_jagar.h +++ b/include/d/actor/d_a_npc_jagar.h @@ -225,7 +225,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[7]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[7]; -private: /* 0x0E40 */ NPC_JAGAR_HIO_CLASS* mpHIO; /* 0x0E44 */ dCcD_Cyl mCyl1; /* 0x0F80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_kakashi.h b/include/d/actor/d_a_npc_kakashi.h index c3f57b383a..a21117376d 100644 --- a/include/d/actor/d_a_npc_kakashi.h +++ b/include/d/actor/d_a_npc_kakashi.h @@ -116,7 +116,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[4]; static DUSK_GAME_DATA int (daNpc_Kakashi_c::* DUSK_CONST mCutList[])(int); -private: /* 0x0E40 */ NPC_KAKASHI_HIO_CLASS* mpHIO; /* 0x0E44 */ Z2SoundObjSimple mSound; /* 0x0E64 */ dCcD_Cyl mCcCyl; diff --git a/include/d/actor/d_a_npc_kasi_hana.h b/include/d/actor/d_a_npc_kasi_hana.h index 625cb31845..302c3d84ab 100644 --- a/include/d/actor/d_a_npc_kasi_hana.h +++ b/include/d/actor/d_a_npc_kasi_hana.h @@ -206,7 +206,6 @@ public: static DUSK_GAME_DATA f32 mTargetTagDist; static DUSK_GAME_DATA s16 mWolfAngle; -private: /* 0x0B48 */ Z2CreatureCitizen mSound; /* 0x0BEC */ u8 field_0xbec[0xbf0 - 0xbec]; /* 0x0BF0 */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_kasi_kyu.h b/include/d/actor/d_a_npc_kasi_kyu.h index 962a2e04cc..8ba34391c7 100644 --- a/include/d/actor/d_a_npc_kasi_kyu.h +++ b/include/d/actor/d_a_npc_kasi_kyu.h @@ -125,7 +125,6 @@ public: static DUSK_GAME_DATA f32 mTargetTagDist; static DUSK_GAME_DATA s16 mWolfAngle; -private: /* 0x0B48 */ Z2CreatureCitizen mSound; /* 0x0BEC */ u8 field_0xbec[0xbf0 - 0xbec]; /* 0x0BF0 */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_kasi_mich.h b/include/d/actor/d_a_npc_kasi_mich.h index efa1a221c6..08361fe134 100644 --- a/include/d/actor/d_a_npc_kasi_mich.h +++ b/include/d/actor/d_a_npc_kasi_mich.h @@ -126,7 +126,6 @@ public: static DUSK_GAME_DATA f32 mTargetTagDist; static DUSK_GAME_DATA s16 mWolfAngle; -private: /* 0x0B48 */ Z2CreatureCitizen mSound; /* 0x0BEC */ u8 field_0xbec[0xbf0 - 0xbec]; /* 0x0BF0 */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_kdk.h b/include/d/actor/d_a_npc_kdk.h index 6f701e33af..a647cc706d 100644 --- a/include/d/actor/d_a_npc_kdk.h +++ b/include/d/actor/d_a_npc_kdk.h @@ -12,7 +12,7 @@ * */ class daNpcKdk_c : public fopAc_ac_c { -private: +public: /* 0x568 */ u8 field_0x568[0xFA0 - 0x568]; }; diff --git a/include/d/actor/d_a_npc_kkri.h b/include/d/actor/d_a_npc_kkri.h index c6e173480d..1265f6e27f 100644 --- a/include/d/actor/d_a_npc_kkri.h +++ b/include/d/actor/d_a_npc_kkri.h @@ -121,7 +121,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[3]; static DUSK_GAME_DATA int (daNpc_Kkri_c::* DUSK_CONST mCutList[])(int); -private: /* 0xE40 */ NPC_KKRI_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCcCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_kn.h b/include/d/actor/d_a_npc_kn.h index 9e1794db97..7df2cfa247 100644 --- a/include/d/actor/d_a_npc_kn.h +++ b/include/d/actor/d_a_npc_kn.h @@ -421,7 +421,6 @@ public: static DUSK_GAME_DATA fopAc_ac_c* mFindActorPtrs[50]; static DUSK_GAME_DATA int mFindCount; -private: /* 0x0E44 */ J3DModel* mpPodModel; /* 0x0E48 */ mDoExt_invisibleModel mInvisModel; /* 0x0E50 */ dKy_tevstr_c mTevStr; diff --git a/include/d/actor/d_a_npc_knj.h b/include/d/actor/d_a_npc_knj.h index f8db1cdedb..7738d231b2 100644 --- a/include/d/actor/d_a_npc_knj.h +++ b/include/d/actor/d_a_npc_knj.h @@ -83,7 +83,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_KNJ_HIO_CLASS* mpHIO; /* 0xE44 */ u8 mType; /* 0xE48 */ actionFunc mNextAction; diff --git a/include/d/actor/d_a_npc_kolin.h b/include/d/actor/d_a_npc_kolin.h index 28f455be35..860ae1b942 100644 --- a/include/d/actor/d_a_npc_kolin.h +++ b/include/d/actor/d_a_npc_kolin.h @@ -161,7 +161,6 @@ public: return 0; } -private: /* 0x0E40 */ NPC_KOLIN_HIO_CLASS* mpHIO; /* 0x0E44 */ J3DModel* mpClothModel; /* 0x0E48 */ dCcD_Cyl field_0xe48; diff --git a/include/d/actor/d_a_npc_kolinb.h b/include/d/actor/d_a_npc_kolinb.h index bf4392a697..4655234347 100644 --- a/include/d/actor/d_a_npc_kolinb.h +++ b/include/d/actor/d_a_npc_kolinb.h @@ -166,7 +166,6 @@ public: u32 getModelType() { return fopAcM_GetParam(this) >> 28; } -private: /* 0xE40 */ NPC_KOLINB_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl field_0xe44; /* 0xF80 */ Mtx mMtx; diff --git a/include/d/actor/d_a_npc_kyury.h b/include/d/actor/d_a_npc_kyury.h index 8469dd3528..7322dfd905 100644 --- a/include/d/actor/d_a_npc_kyury.h +++ b/include/d/actor/d_a_npc_kyury.h @@ -126,7 +126,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[2]; -private: /* 0xE40 */ NPC_KYURY_HIO_CLASS* mpHIO; /* 0xE44 */ J3DModel* mpModel[2]; /* 0xE4C */ dCcD_Cyl mCyl; diff --git a/include/d/actor/d_a_npc_len.h b/include/d/actor/d_a_npc_len.h index 13b959c3b7..b22a0059e0 100644 --- a/include/d/actor/d_a_npc_len.h +++ b/include/d/actor/d_a_npc_len.h @@ -113,7 +113,6 @@ public: u8 getPathID() { return (fopAcM_GetParam(this) & 0xFF00) >> 8; } u8 getBitSW() { return (fopAcM_GetParam(this) & 0xff0000) >> 16; } -private: /* 0xE40 */ NPC_LEN_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_lud.h b/include/d/actor/d_a_npc_lud.h index afecfdc0c7..a2be5fa65d 100644 --- a/include/d/actor/d_a_npc_lud.h +++ b/include/d/actor/d_a_npc_lud.h @@ -114,7 +114,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[8]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[8]; -private: /* 0xE40 */ mDoExt_McaMorfSO* mpBowlMorf; /* 0xE44 */ NPC_LUD_HIO_CLASS* mpHIO; /* 0xE48 */ J3DModel* mpModel[2]; diff --git a/include/d/actor/d_a_npc_maro.h b/include/d/actor/d_a_npc_maro.h index 1c51a63b44..8acd11e43c 100644 --- a/include/d/actor/d_a_npc_maro.h +++ b/include/d/actor/d_a_npc_maro.h @@ -187,7 +187,6 @@ public: return (fopAcM_GetParam(this) & 0xF000000) >> 24; } -private: /* 0x0F7C */ NPC_MARO_HIO_CLASS* mpHIO; /* 0x0F80 */ dCcD_Cyl mCyl1; /* 0x10BC */ int field_0x10bc; diff --git a/include/d/actor/d_a_npc_midp.h b/include/d/actor/d_a_npc_midp.h index 60efbbac80..3021051d77 100644 --- a/include/d/actor/d_a_npc_midp.h +++ b/include/d/actor/d_a_npc_midp.h @@ -151,7 +151,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_MIDP_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_moi.h b/include/d/actor/d_a_npc_moi.h index 547c3a1dcd..94e1ef8861 100644 --- a/include/d/actor/d_a_npc_moi.h +++ b/include/d/actor/d_a_npc_moi.h @@ -161,7 +161,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[5]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[5]; -private: /* 0x0E40 */ NPC_MOI_HIO_CLASS* mpHIO; /* 0x0E44 */ J3DModel* mpModel[6]; /* 0x0E5C */ dCcD_Cyl mCyl[4]; diff --git a/include/d/actor/d_a_npc_moir.h b/include/d/actor/d_a_npc_moir.h index f9aa841a60..f217ab7698 100644 --- a/include/d/actor/d_a_npc_moir.h +++ b/include/d/actor/d_a_npc_moir.h @@ -214,7 +214,6 @@ public: static DUSK_GAME_DATA EventFn mEvtSeqList[4]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ J3DModel* mpHeadgearModel; /* 0xBDC */ daNpcF_MatAnm_c* mpMatAnm; diff --git a/include/d/actor/d_a_npc_pachi_besu.h b/include/d/actor/d_a_npc_pachi_besu.h index 7fa17d8b2d..2cc79d2364 100644 --- a/include/d/actor/d_a_npc_pachi_besu.h +++ b/include/d/actor/d_a_npc_pachi_besu.h @@ -147,7 +147,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[11]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[11]; -private: /* 0xE40 */ NPC_PACHI_BESU_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_pachi_maro.h b/include/d/actor/d_a_npc_pachi_maro.h index 379c2c204b..be398f85e0 100644 --- a/include/d/actor/d_a_npc_pachi_maro.h +++ b/include/d/actor/d_a_npc_pachi_maro.h @@ -187,7 +187,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[11]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[11]; -private: /* 0xE40 */ NPC_PACHI_MARO_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_pachi_taro.h b/include/d/actor/d_a_npc_pachi_taro.h index 12de6aedf4..e424292f6c 100644 --- a/include/d/actor/d_a_npc_pachi_taro.h +++ b/include/d/actor/d_a_npc_pachi_taro.h @@ -199,7 +199,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[11]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[11]; -private: /* 0x0E40 */ NPC_PACHI_TARO_HIO_CLASS* mpHIO; /* 0x0E44 */ J3DModel* mpModels[2]; /* 0x0E4C */ dCcD_Cyl mCyl; diff --git a/include/d/actor/d_a_npc_passer.h b/include/d/actor/d_a_npc_passer.h index d2f6f0b764..e166e635b9 100644 --- a/include/d/actor/d_a_npc_passer.h +++ b/include/d/actor/d_a_npc_passer.h @@ -123,7 +123,6 @@ public: static DUSK_GAME_DATA seqFunc m_seq26_funcTbl[3]; static DUSK_GAME_DATA seqFunc m_seq27_funcTbl[1]; -private: /* 0xAC8 */ J3DModel* mpModel; /* 0xACC */ PathTrace_c m_path; /* 0xAF4 */ daNpcPasser_c::actionFunc* mAction; diff --git a/include/d/actor/d_a_npc_passer2.h b/include/d/actor/d_a_npc_passer2.h index 792bbfd329..148a3b2981 100644 --- a/include/d/actor/d_a_npc_passer2.h +++ b/include/d/actor/d_a_npc_passer2.h @@ -53,7 +53,6 @@ public: static DUSK_GAME_DATA actionFunc ActionTable[1][2]; -private: /* 0x9EC */ J3DModel* mpModel; /* 0x9F0 */ PathTrace_c m_path; /* 0xA18 */ actionFunc* mAction; diff --git a/include/d/actor/d_a_npc_post.h b/include/d/actor/d_a_npc_post.h index 7b31506513..ef44f7ee1e 100644 --- a/include/d/actor/d_a_npc_post.h +++ b/include/d/actor/d_a_npc_post.h @@ -156,7 +156,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[2]; -private: /* 0x0E40 */ mDoExt_McaMorfSO* mpFlagModelMorf; /* 0x0E44 */ NPC_POST_HIO_CLASS* mHIO; /* 0x0E48 */ J3DModel* mpLetterModels[2]; diff --git a/include/d/actor/d_a_npc_pouya.h b/include/d/actor/d_a_npc_pouya.h index c30b0e0199..4af1d1ce68 100644 --- a/include/d/actor/d_a_npc_pouya.h +++ b/include/d/actor/d_a_npc_pouya.h @@ -149,7 +149,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[3]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[3]; -private: /* 0xE40 */ NPC_POUYA_HIO_CLASS* mpHIO; /* 0xE44 */ J3DModel* mpModel[1]; /* 0xE48 */ dCcD_Cyl mCyl; diff --git a/include/d/actor/d_a_npc_prayer.h b/include/d/actor/d_a_npc_prayer.h index bc2735a43f..438bc784f9 100644 --- a/include/d/actor/d_a_npc_prayer.h +++ b/include/d/actor/d_a_npc_prayer.h @@ -79,7 +79,6 @@ public: static DUSK_GAME_DATA EvtSeq DUSK_CONST mEvtSeqList[]; -private: /* 0xB48 */ Z2CreatureCitizen mSound; /* 0xBEC */ u8 field_0xBEC[0xBF0 - 0xBEC]; /* 0xBF0 */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_raca.h b/include/d/actor/d_a_npc_raca.h index 9cd1849880..187ca700c7 100644 --- a/include/d/actor/d_a_npc_raca.h +++ b/include/d/actor/d_a_npc_raca.h @@ -148,7 +148,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_RACA_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_rafrel.h b/include/d/actor/d_a_npc_rafrel.h index 3b3ac191bd..0bb9e76194 100644 --- a/include/d/actor/d_a_npc_rafrel.h +++ b/include/d/actor/d_a_npc_rafrel.h @@ -107,7 +107,6 @@ public: static DUSK_GAME_DATA int (daNpcRafrel_c::*mEvtSeqList[])(int); -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ J3DModel* mpItemModel; /* 0xBDC */ daNpcF_MatAnm_c* mpMatAnm; diff --git a/include/d/actor/d_a_npc_saru.h b/include/d/actor/d_a_npc_saru.h index 7f04c509e2..dce1d7b4a3 100644 --- a/include/d/actor/d_a_npc_saru.h +++ b/include/d/actor/d_a_npc_saru.h @@ -135,7 +135,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[4]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[4]; -private: /* 0xE40 */ NPC_SARU_HIO_CLASS* mpHIO; /* 0xE44 */ J3DModel* mpRoseModels[2]; /* 0xE4C */ dCcD_Cyl field_0xe4c; diff --git a/include/d/actor/d_a_npc_seib.h b/include/d/actor/d_a_npc_seib.h index f2d0e04820..855be4ac41 100644 --- a/include/d/actor/d_a_npc_seib.h +++ b/include/d/actor/d_a_npc_seib.h @@ -87,7 +87,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_SEIB_HIO_CLASS* mpHIO; /* 0xE44 */ u8 mType; /* 0xE48 */ actionFunc mActionFunc1; diff --git a/include/d/actor/d_a_npc_seic.h b/include/d/actor/d_a_npc_seic.h index 526331f05e..609d38588b 100644 --- a/include/d/actor/d_a_npc_seic.h +++ b/include/d/actor/d_a_npc_seic.h @@ -90,7 +90,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_SEIC_HIO_CLASS* mpHIO; /* 0xE44 */ u8 mType; /* 0xE45 */ u8 field_0xe45[0xe48 - 0xe45]; diff --git a/include/d/actor/d_a_npc_seid.h b/include/d/actor/d_a_npc_seid.h index 37cc6302d6..91a1920598 100644 --- a/include/d/actor/d_a_npc_seid.h +++ b/include/d/actor/d_a_npc_seid.h @@ -89,7 +89,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_SEID_HIO_CLASS* mpHIO; /* 0xE44 */ u8 mType; /* 0xE48 */ actionFunc mAction; diff --git a/include/d/actor/d_a_npc_seira.h b/include/d/actor/d_a_npc_seira.h index d750c70711..c4f2ef1a96 100644 --- a/include/d/actor/d_a_npc_seira.h +++ b/include/d/actor/d_a_npc_seira.h @@ -117,7 +117,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[2]; -private: /* 0x0F7C */ mDoExt_McaMorfSO* mpSeiraMorf; /* 0x0F80 */ NPC_SEIRA_HIO_CLASS* mpHIO; /* 0x0F80 */ dCcD_Cyl mCyl1; diff --git a/include/d/actor/d_a_npc_seira2.h b/include/d/actor/d_a_npc_seira2.h index 496357af4e..13aeb71e65 100644 --- a/include/d/actor/d_a_npc_seira2.h +++ b/include/d/actor/d_a_npc_seira2.h @@ -109,7 +109,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0x0F7C */ mDoExt_McaMorfSO* mpSeiraMorf; /* 0x0F80 */ NPC_SEIRA2_HIO_CLASS* mpHIO; /* 0x0F84 */ dCcD_Cyl mCyl1; diff --git a/include/d/actor/d_a_npc_seirei.h b/include/d/actor/d_a_npc_seirei.h index a25c9671ae..b27f6084fc 100644 --- a/include/d/actor/d_a_npc_seirei.h +++ b/include/d/actor/d_a_npc_seirei.h @@ -102,7 +102,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[2]; -private: /* 0xE40 */ NPC_SEIREI_HIO_CLASS* mpHIO; /* 0xE44 */ u8 mType; /* 0xE45 */ u8 arg0; diff --git a/include/d/actor/d_a_npc_shad.h b/include/d/actor/d_a_npc_shad.h index 7f4490c183..59f1901059 100644 --- a/include/d/actor/d_a_npc_shad.h +++ b/include/d/actor/d_a_npc_shad.h @@ -237,7 +237,6 @@ public: } } -private: /* 0xB48 */ J3DModel* mBookKnifeModel; /* 0xB4C */ J3DModel* mKomonshoModel; /* 0xB50 */ Z2Creature mSound; diff --git a/include/d/actor/d_a_npc_shaman.h b/include/d/actor/d_a_npc_shaman.h index cdb7f7d7e9..1a462a7d00 100644 --- a/include/d/actor/d_a_npc_shaman.h +++ b/include/d/actor/d_a_npc_shaman.h @@ -117,7 +117,6 @@ public: static DUSK_GAME_DATA const int mSceneChangeNoTable[48]; static DUSK_GAME_DATA queryFunc mQueries[48]; -private: /* 0xE40 */ NPC_SHA_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_shoe.h b/include/d/actor/d_a_npc_shoe.h index f9bee53132..b93259b60f 100644 --- a/include/d/actor/d_a_npc_shoe.h +++ b/include/d/actor/d_a_npc_shoe.h @@ -112,7 +112,6 @@ public: static DUSK_GAME_DATA EventFn DUSK_CONST mEvtSeqList[1]; -private: /* 0xB48 */ J3DModel* mpModel1; /* 0xB4C */ J3DModel* mpModel2; /* 0xB50 */ Z2CreatureCitizen mCreature; diff --git a/include/d/actor/d_a_npc_sola.h b/include/d/actor/d_a_npc_sola.h index bfeb993cc7..b7fff5b57b 100644 --- a/include/d/actor/d_a_npc_sola.h +++ b/include/d/actor/d_a_npc_sola.h @@ -114,7 +114,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_SOLA_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 field_0xf80; diff --git a/include/d/actor/d_a_npc_soldierA.h b/include/d/actor/d_a_npc_soldierA.h index dfc114d97e..aaca72ec8b 100644 --- a/include/d/actor/d_a_npc_soldierA.h +++ b/include/d/actor/d_a_npc_soldierA.h @@ -82,7 +82,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[3]; static DUSK_GAME_DATA cutFunc DUSK_CONST mEvtCutList[3]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ J3DModel* mSpearModel; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_soldierB.h b/include/d/actor/d_a_npc_soldierB.h index 66813628f9..d39f54edc5 100644 --- a/include/d/actor/d_a_npc_soldierB.h +++ b/include/d/actor/d_a_npc_soldierB.h @@ -78,7 +78,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mEvtCutList[2]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ J3DModel* mSpearModel; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_taro.h b/include/d/actor/d_a_npc_taro.h index 1d0fa6274b..83ff47fc07 100644 --- a/include/d/actor/d_a_npc_taro.h +++ b/include/d/actor/d_a_npc_taro.h @@ -159,7 +159,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[17]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[17]; -private: /* 0x0E40 */ NPC_TARO_HIO_CLASS* mpHIO; /* 0x0E44 */ J3DModel* mModels[2]; /* 0x0E4C */ dCcD_Cyl mCyl1; diff --git a/include/d/actor/d_a_npc_the.h b/include/d/actor/d_a_npc_the.h index fba5678e01..2a230afb8e 100644 --- a/include/d/actor/d_a_npc_the.h +++ b/include/d/actor/d_a_npc_the.h @@ -208,7 +208,6 @@ public: } } -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_theB.h b/include/d/actor/d_a_npc_theB.h index 92e0b0b3f0..e2bc9d410d 100644 --- a/include/d/actor/d_a_npc_theB.h +++ b/include/d/actor/d_a_npc_theB.h @@ -151,7 +151,6 @@ public: static DUSK_GAME_DATA cutFunc mEvtSeqList[6]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_tkc.h b/include/d/actor/d_a_npc_tkc.h index 3148354b36..6ec0150e5c 100644 --- a/include/d/actor/d_a_npc_tkc.h +++ b/include/d/actor/d_a_npc_tkc.h @@ -108,7 +108,6 @@ public: static DUSK_GAME_DATA evtFunc mEvtSeqList[4]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_ActorMngr_c mActorMngr[1]; diff --git a/include/d/actor/d_a_npc_tkj.h b/include/d/actor/d_a_npc_tkj.h index 8e7e2567b1..3191f1ee3b 100644 --- a/include/d/actor/d_a_npc_tkj.h +++ b/include/d/actor/d_a_npc_tkj.h @@ -97,7 +97,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA int (daNpcTkj_c::* DUSK_CONST mCutList[])(int); -private: /* 0xE40 */ NPC_TKJ_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCcCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_tks.h b/include/d/actor/d_a_npc_tks.h index 0342a378fe..837a0c89ec 100644 --- a/include/d/actor/d_a_npc_tks.h +++ b/include/d/actor/d_a_npc_tks.h @@ -134,7 +134,6 @@ public: inline void playTsuboAnm(); inline int getPlayerArea(); -private: /* 0x0B48 */ Z2Creature mSound; /* 0x0BD8 */ daNpcTksTsubo_c mTksTsubo; /* 0x1160 */ daNpcF_MatAnm_c* mpMatAnm; diff --git a/include/d/actor/d_a_npc_toby.h b/include/d/actor/d_a_npc_toby.h index 4f53117112..9abb7c032f 100644 --- a/include/d/actor/d_a_npc_toby.h +++ b/include/d/actor/d_a_npc_toby.h @@ -132,7 +132,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[7]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[7]; -private: /* 0x0E40 */ NPC_TOBY_HIO_CLASS* mpHIO; /* 0x0E44 */ J3DModel* mpTobyModels[1]; /* 0x0E48 */ dCcD_Cyl mCyl1; diff --git a/include/d/actor/d_a_npc_uri.h b/include/d/actor/d_a_npc_uri.h index 78a327592a..2650de4d64 100644 --- a/include/d/actor/d_a_npc_uri.h +++ b/include/d/actor/d_a_npc_uri.h @@ -139,7 +139,6 @@ public: static DUSK_GAME_DATA const char* mCutNameList[7]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[7]; -private: /* 0x0E40 */ NPC_URI_HIO_CLASS* mpHIO; /* 0x0E44 */ J3DModel* mpModel[1]; /* 0x0E48 */ dCcD_Cyl mCyl; diff --git a/include/d/actor/d_a_npc_wrestler.h b/include/d/actor/d_a_npc_wrestler.h index b910ed50f9..c840259074 100644 --- a/include/d/actor/d_a_npc_wrestler.h +++ b/include/d/actor/d_a_npc_wrestler.h @@ -232,7 +232,6 @@ public: static DUSK_GAME_DATA EventFn DUSK_CONST mEvtSeqList[7]; -private: /* 0xB48 */ Z2Creature mSound; /* 0xBD8 */ daNpcWrestler_HIOParam* field_0xbd8; /* 0xBDC */ daNpcWrestler_HIOParamSub* field_0xbdc; diff --git a/include/d/actor/d_a_npc_yamid.h b/include/d/actor/d_a_npc_yamid.h index b175aa7a53..b9bb6bb288 100644 --- a/include/d/actor/d_a_npc_yamid.h +++ b/include/d/actor/d_a_npc_yamid.h @@ -132,7 +132,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[2]; -private: /* 0xE40 */ NPC_YAMID_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl field_0xe44; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_yamis.h b/include/d/actor/d_a_npc_yamis.h index 4c23311c5e..272b0d5b72 100644 --- a/include/d/actor/d_a_npc_yamis.h +++ b/include/d/actor/d_a_npc_yamis.h @@ -127,7 +127,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[2]; -private: /* 0xE40 */ NPC_YAMIS_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl field_0xe44; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_yamit.h b/include/d/actor/d_a_npc_yamit.h index a59b6a841f..0a12c2875d 100644 --- a/include/d/actor/d_a_npc_yamit.h +++ b/include/d/actor/d_a_npc_yamit.h @@ -126,7 +126,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[2]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[2]; -private: /* 0xE40 */ NPC_YAMIT_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl field_0xe44; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_yelia.h b/include/d/actor/d_a_npc_yelia.h index 737a3b4e28..c86d8e6ac6 100644 --- a/include/d/actor/d_a_npc_yelia.h +++ b/include/d/actor/d_a_npc_yelia.h @@ -104,7 +104,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[6]; static DUSK_GAME_DATA int (daNpc_Yelia_c::*mCutList[6])(int); -private: /* 0xE40 */ NPC_YELIA_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCcCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_ykm.h b/include/d/actor/d_a_npc_ykm.h index 5ad9e7210b..5bbb5077f6 100644 --- a/include/d/actor/d_a_npc_ykm.h +++ b/include/d/actor/d_a_npc_ykm.h @@ -287,7 +287,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[10]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[10]; -// private: /* 0x0E40 */ mDoExt_McaMorfSO* mFishModelMorf; /* 0x0E44 */ mDoExt_McaMorfSO* mLeafModelMorf; /* 0x0E48 */ NPC_YKM_HIO_CLASS* mpHIO; diff --git a/include/d/actor/d_a_npc_zanb.h b/include/d/actor/d_a_npc_zanb.h index 51d99c8253..78c37f6c76 100644 --- a/include/d/actor/d_a_npc_zanb.h +++ b/include/d/actor/d_a_npc_zanb.h @@ -102,7 +102,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList[1]; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_ZANB_HIO_CLASS* mHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_zant.h b/include/d/actor/d_a_npc_zant.h index cbd33863d2..f9cd1c5b9b 100644 --- a/include/d/actor/d_a_npc_zant.h +++ b/include/d/actor/d_a_npc_zant.h @@ -86,7 +86,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_ZANT_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_zelR.h b/include/d/actor/d_a_npc_zelR.h index e7810c850c..bfe7d963a0 100644 --- a/include/d/actor/d_a_npc_zelR.h +++ b/include/d/actor/d_a_npc_zelR.h @@ -96,7 +96,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA EventFn mCutList[1]; -private: /* 0xE40 */ NPC_ZELR_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_zelRo.h b/include/d/actor/d_a_npc_zelRo.h index b3e2533999..a57235bff9 100644 --- a/include/d/actor/d_a_npc_zelRo.h +++ b/include/d/actor/d_a_npc_zelRo.h @@ -150,7 +150,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_ZELRO_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 mType; diff --git a/include/d/actor/d_a_npc_zelda.h b/include/d/actor/d_a_npc_zelda.h index 6c3da5a2b3..e4be31296e 100644 --- a/include/d/actor/d_a_npc_zelda.h +++ b/include/d/actor/d_a_npc_zelda.h @@ -103,7 +103,6 @@ public: static DUSK_GAME_DATA const char* mCutNameList; static DUSK_GAME_DATA cutFunc DUSK_CONST mCutList[1]; -private: /* 0xE40 */ NPC_ZELDA_HIO_CLASS* mpHIO; /* 0xE44 */ dCcD_Cyl mCyl; /* 0xF80 */ u8 field_0xf80; diff --git a/include/d/actor/d_a_npc_zrc.h b/include/d/actor/d_a_npc_zrc.h index 6f6a00061b..e9992ae157 100644 --- a/include/d/actor/d_a_npc_zrc.h +++ b/include/d/actor/d_a_npc_zrc.h @@ -96,7 +96,6 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[2]; static DUSK_GAME_DATA EventFn DUSK_CONST mEvtCutList[2]; -// private: /* 0xB48 */ Z2Creature mCreatureSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_zrz.h b/include/d/actor/d_a_npc_zrz.h index 9e03215aa4..a02ebb8227 100644 --- a/include/d/actor/d_a_npc_zrz.h +++ b/include/d/actor/d_a_npc_zrz.h @@ -114,7 +114,6 @@ public: void himoCalc(); void adjustShapeAngle() {} -// private: /* 0x0B48 */ Z2Creature mCreatureSound; /* 0x0BD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0x0BDC */ mDoExt_invisibleModel mInvisibleModel; diff --git a/include/d/actor/d_a_obj_Lv5Key.h b/include/d/actor/d_a_obj_Lv5Key.h index 6e0251b151..11e2bd3019 100644 --- a/include/d/actor/d_a_obj_Lv5Key.h +++ b/include/d/actor/d_a_obj_Lv5Key.h @@ -65,7 +65,6 @@ public: cXyz& getLocalOffset() { return mLocalOffset; } -private: /* 0x568 */ u8 field_0x568[0x56C - 0x568]; /* 0x56C */ Z2SoundObjSimple mSound; /* 0x58C */ u8 field_0x58C[0x590 - 0x58C]; diff --git a/include/d/actor/d_a_obj_Turara.h b/include/d/actor/d_a_obj_Turara.h index 99d5a3a245..16f27d8550 100644 --- a/include/d/actor/d_a_obj_Turara.h +++ b/include/d/actor/d_a_obj_Turara.h @@ -56,7 +56,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x5b8 */ request_of_phase_process_class mPhaseReq; /* 0x5c0 */ J3DModel* mpModel[2]; /* 0x5c8 */ u8 mMode; diff --git a/include/d/actor/d_a_obj_TvCdlst.h b/include/d/actor/d_a_obj_TvCdlst.h index afe204bc03..5329727f6a 100644 --- a/include/d/actor/d_a_obj_TvCdlst.h +++ b/include/d/actor/d_a_obj_TvCdlst.h @@ -33,7 +33,6 @@ public: u8 getOnSw() { return fopAcM_GetParamBit(this,0,8); } u8 getOffSw() { return fopAcM_GetParamBit(this,0x10,8); } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_Y_taihou.h b/include/d/actor/d_a_obj_Y_taihou.h index 0e96cf62ed..002b85d853 100644 --- a/include/d/actor/d_a_obj_Y_taihou.h +++ b/include/d/actor/d_a_obj_Y_taihou.h @@ -37,7 +37,6 @@ public: s32 getIronBallId() { return mIronBallId; } void startBomb() { mStartBomb = 0xffff; } -private: /* 0x5b8 */ Mtx mMtx; /* 0x5e8 */ J3DModel* mpModel; /* 0x5ec */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_amiShutter.h b/include/d/actor/d_a_obj_amiShutter.h index b0e881c4c0..9fabc75220 100644 --- a/include/d/actor/d_a_obj_amiShutter.h +++ b/include/d/actor/d_a_obj_amiShutter.h @@ -62,7 +62,6 @@ public: bool isShutterOpen() { return mOpen; } -private: /* 0x5b8 */ request_of_phase_process_class mPhaseReq; /* 0x5c0 */ J3DModel* mpModel; /* 0x5c4 */ u8 mMode; diff --git a/include/d/actor/d_a_obj_ari.h b/include/d/actor/d_a_obj_ari.h index a771b054f1..03b80016bd 100644 --- a/include/d/actor/d_a_obj_ari.h +++ b/include/d/actor/d_a_obj_ari.h @@ -40,7 +40,6 @@ public: bool CreateChk(); cPhs_Step create(); -private: /* 0x590 */ dBgS_GndChk mGndChk; /* 0x5E4 */ u8 field_0x5e4[4]; /* 0x5E8 */ u8 mAction; diff --git a/include/d/actor/d_a_obj_automata.h b/include/d/actor/d_a_obj_automata.h index 95fec65cff..17e470af99 100644 --- a/include/d/actor/d_a_obj_automata.h +++ b/include/d/actor/d_a_obj_automata.h @@ -44,7 +44,7 @@ public: * */ class daObj_AutoMata_c : public fopAc_ac_c { -private: +public: /* 0x568 */ OBJ_AUTOMATA_HIO_CLASS* mpHIO; /* 0x56C */ mDoExt_McaMorfSO* mpMorf; /* 0x570 */ Z2Creature mCreature; diff --git a/include/d/actor/d_a_obj_avalanche.h b/include/d/actor/d_a_obj_avalanche.h index 89de60d2e8..ef72babdef 100644 --- a/include/d/actor/d_a_obj_avalanche.h +++ b/include/d/actor/d_a_obj_avalanche.h @@ -46,7 +46,6 @@ public: u8 getEvId() { return fopAcM_GetParamBit(this, 8, 8); } void setAction(u8 action) { mAction = action; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ mDoExt_bckAnm* mBckAnm; diff --git a/include/d/actor/d_a_obj_batta.h b/include/d/actor/d_a_obj_batta.h index fca663390b..a1eaefc45e 100644 --- a/include/d/actor/d_a_obj_batta.h +++ b/include/d/actor/d_a_obj_batta.h @@ -43,7 +43,6 @@ public: return mActionFunc == i_func; } -private: /* 0x590 */ Z2Creature mCreature; /* 0x620 */ mDoExt_McaMorfSO* mpMorf; /* 0x624 */ mDoExt_brkAnm* mBrk; diff --git a/include/d/actor/d_a_obj_bbox.h b/include/d/actor/d_a_obj_bbox.h index 7ee461b557..1f7c2eef60 100644 --- a/include/d/actor/d_a_obj_bbox.h +++ b/include/d/actor/d_a_obj_bbox.h @@ -26,7 +26,6 @@ public: u32 getSwNo() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_bemos.h b/include/d/actor/d_a_obj_bemos.h index 38501b708b..3d0c10790d 100644 --- a/include/d/actor/d_a_obj_bemos.h +++ b/include/d/actor/d_a_obj_bemos.h @@ -131,7 +131,6 @@ public: int Delete(); static DUSK_GAME_DATA s16 const M_dir_base[4]; - // private: /* 0x05A0 */ request_of_phase_process_class mPhase; /* 0x05A8 */ J3DModel* mpModel; /* 0x05AC */ mDoExt_brkAnm* mSerchBrk; diff --git a/include/d/actor/d_a_obj_bhashi.h b/include/d/actor/d_a_obj_bhashi.h index 7600d73f67..7ce2cd1c23 100644 --- a/include/d/actor/d_a_obj_bhashi.h +++ b/include/d/actor/d_a_obj_bhashi.h @@ -50,7 +50,6 @@ public: virtual int Draw(); virtual int Delete(); -private: /* 0x05A0 */ u8 field_0x5A0[0x05A4 - 0x05A0]; /* 0x05A4 */ int mMode; /* 0x05A8 */ s16 field_0x5a8; diff --git a/include/d/actor/d_a_obj_bkdoor.h b/include/d/actor/d_a_obj_bkdoor.h index 69d3191028..793d86f09c 100644 --- a/include/d/actor/d_a_obj_bkdoor.h +++ b/include/d/actor/d_a_obj_bkdoor.h @@ -26,7 +26,6 @@ public: virtual int Draw(); virtual int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ csXyz mRotation; diff --git a/include/d/actor/d_a_obj_bky_rock.h b/include/d/actor/d_a_obj_bky_rock.h index 1f5952c35e..1214a610c9 100644 --- a/include/d/actor/d_a_obj_bky_rock.h +++ b/include/d/actor/d_a_obj_bky_rock.h @@ -63,7 +63,6 @@ public: static DUSK_GAME_DATA dCcD_SrcCyl const s_CcDCyl; static DUSK_GAME_DATA exeProc s_exeProc[3]; -private: /* 0x568 */ int mVibrationTimer; /* 0x578 */ u8 mMode; /* 0x579 */ bool field_0x579; diff --git a/include/d/actor/d_a_obj_bmWindow.h b/include/d/actor/d_a_obj_bmWindow.h index 161f4768a9..a3d71fa9c2 100644 --- a/include/d/actor/d_a_obj_bmWindow.h +++ b/include/d/actor/d_a_obj_bmWindow.h @@ -39,7 +39,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_bmshutter.h b/include/d/actor/d_a_obj_bmshutter.h index c114421fe8..6ca4c05fe9 100644 --- a/include/d/actor/d_a_obj_bmshutter.h +++ b/include/d/actor/d_a_obj_bmshutter.h @@ -46,7 +46,6 @@ public: void setAction(u8 action) { mAction = action; } void setMoveMode(u8 mode) { mMoveMode = mode; } -private: /* 0x5a0 */ request_of_phase_process_class mPhase; /* 0x5a8 */ J3DModel* mModel; /* 0x5ac */ s16 mEventIdx; diff --git a/include/d/actor/d_a_obj_bombf.h b/include/d/actor/d_a_obj_bombf.h index 1d175caa94..661450e397 100644 --- a/include/d/actor/d_a_obj_bombf.h +++ b/include/d/actor/d_a_obj_bombf.h @@ -21,7 +21,6 @@ public: int execute(); int draw(); -private: /* 0x568 */ request_of_phase_process_class field_0x568; /* 0x570 */ J3DModel* field_0x570; /* 0x574 */ u32 field_0x574; diff --git a/include/d/actor/d_a_obj_bosswarp.h b/include/d/actor/d_a_obj_bosswarp.h index 71b5b2b58f..4f6c6e4282 100644 --- a/include/d/actor/d_a_obj_bosswarp.h +++ b/include/d/actor/d_a_obj_bosswarp.h @@ -60,7 +60,6 @@ public: bool isFirst() { return fopAcM_GetParamBit(this, 0x1b, 1); } void setAction(u8 action) { mAction = action; } -// private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ mDoExt_btkAnm* mpBtkAnm[2]; diff --git a/include/d/actor/d_a_obj_boumato.h b/include/d/actor/d_a_obj_boumato.h index 4852aee6f9..6a0266e548 100644 --- a/include/d/actor/d_a_obj_boumato.h +++ b/include/d/actor/d_a_obj_boumato.h @@ -50,7 +50,7 @@ public: * */ class daObj_BouMato_c : public fopAc_ac_c { -private: +public: /* 0x568 */ OBJ_BOUMATO_HIO_CLASS* mpHIO; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ J3DModel* mModel; diff --git a/include/d/actor/d_a_obj_bsGate.h b/include/d/actor/d_a_obj_bsGate.h index b40c5dcf6d..0c9730d63d 100644 --- a/include/d/actor/d_a_obj_bsGate.h +++ b/include/d/actor/d_a_obj_bsGate.h @@ -35,7 +35,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_bubblePilar.h b/include/d/actor/d_a_obj_bubblePilar.h index 7fb8009564..e19cbbae2f 100644 --- a/include/d/actor/d_a_obj_bubblePilar.h +++ b/include/d/actor/d_a_obj_bubblePilar.h @@ -39,7 +39,6 @@ public: static DUSK_GAME_DATA dCcD_SrcGObjInf const mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModels[2]; /* 0x5B0 */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_burnbox.h b/include/d/actor/d_a_obj_burnbox.h index f87678dccc..dc4634ea85 100644 --- a/include/d/actor/d_a_obj_burnbox.h +++ b/include/d/actor/d_a_obj_burnbox.h @@ -28,7 +28,6 @@ public: u8 getType() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpBoxModel; /* 0x5AC */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_carry.h b/include/d/actor/d_a_obj_carry.h index 8147cbfb60..54c8c73043 100644 --- a/include/d/actor/d_a_obj_carry.h +++ b/include/d/actor/d_a_obj_carry.h @@ -407,7 +407,6 @@ public: /* 0xE26 */ u8 field_0xe26; /* 0xE27 */ u8 field_0xe27; -private: u16 getType_private() { return field_0xd18 >> 1 & 0x1f; } }; diff --git a/include/d/actor/d_a_obj_catdoor.h b/include/d/actor/d_a_obj_catdoor.h index b7ed95bd14..c352c25806 100644 --- a/include/d/actor/d_a_obj_catdoor.h +++ b/include/d/actor/d_a_obj_catdoor.h @@ -80,7 +80,6 @@ public: Z2GetAudioMgr()->seStart(Z2SE_OBJ_GZ_NE_DOOR_OP, ¤t.pos, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ daObjCatDoor_Door_c mDoor1; /* 0x660 */ daObjCatDoor_Door_c mDoor2; diff --git a/include/d/actor/d_a_obj_cblock.h b/include/d/actor/d_a_obj_cblock.h index 04881776ec..75454ebb5d 100644 --- a/include/d/actor/d_a_obj_cblock.h +++ b/include/d/actor/d_a_obj_cblock.h @@ -49,7 +49,6 @@ public: u8 getSwNo() { return fopAcM_GetParamBit(this, 16, 8); } u8 getPathID() { return fopAcM_GetParamBit(this, 24, 8); } -private: /* 0x5A0 */ request_of_phase_process_class phase; /* 0x5A8 */ J3DModel* model1; /* 0x5AC */ J3DModel* model2; diff --git a/include/d/actor/d_a_obj_cboard.h b/include/d/actor/d_a_obj_cboard.h index 52a5f753e4..9bcdb51ed5 100644 --- a/include/d/actor/d_a_obj_cboard.h +++ b/include/d/actor/d_a_obj_cboard.h @@ -28,7 +28,6 @@ public: u8 getArg0() { return fopAcM_GetParamBit(this, 4, 4); } u8 getSwNo() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ Mtx field_0x5a8; }; diff --git a/include/d/actor/d_a_obj_cdoor.h b/include/d/actor/d_a_obj_cdoor.h index ee06fd790c..60d54539e9 100644 --- a/include/d/actor/d_a_obj_cdoor.h +++ b/include/d/actor/d_a_obj_cdoor.h @@ -51,7 +51,6 @@ public: int getSwitchNum() { return mSw; } void setChainID(u32 i_id) { mChainID = i_id; } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ bool mEnd; diff --git a/include/d/actor/d_a_obj_chandelier.h b/include/d/actor/d_a_obj_chandelier.h index 45158dea34..a0592f4374 100644 --- a/include/d/actor/d_a_obj_chandelier.h +++ b/include/d/actor/d_a_obj_chandelier.h @@ -49,7 +49,6 @@ public: void moveHookOn() { field_0x60a = 1; } -private: /* 0x5A8 */ cXyz field_0x5a8; /* 0x5B4 */ Mtx mMtx; /* 0x5E4 */ u8 field_0x5e4[4]; diff --git a/include/d/actor/d_a_obj_chest.h b/include/d/actor/d_a_obj_chest.h index a5caedae86..e225c4a254 100644 --- a/include/d/actor/d_a_obj_chest.h +++ b/include/d/actor/d_a_obj_chest.h @@ -39,7 +39,6 @@ public: u8 getSwNo() { return fopAcM_GetParamBit(this, 0, 8); } u32 getArg0() { return fopAcM_GetParamBit(this, 8, 4); } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ dBgS_ObjAcch mObjAcch; diff --git a/include/d/actor/d_a_obj_cho.h b/include/d/actor/d_a_obj_cho.h index 49dd5332b1..adb3faef58 100644 --- a/include/d/actor/d_a_obj_cho.h +++ b/include/d/actor/d_a_obj_cho.h @@ -46,7 +46,6 @@ public: cPhs_Step create(); inline int Draw(); -private: /* 0x590 */ dBgS_AcchCir mAcchCir; /* 0x5D0 */ dBgS_ObjAcch mAcch; /* 0x7A8 */ dCcD_Stts mCcStts; diff --git a/include/d/actor/d_a_obj_cowdoor.h b/include/d/actor/d_a_obj_cowdoor.h index 1ed34370d8..a084ca0188 100644 --- a/include/d/actor/d_a_obj_cowdoor.h +++ b/include/d/actor/d_a_obj_cowdoor.h @@ -22,7 +22,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class field_0x5a0; /* 0x5A8 */ J3DModel* field_0x5a8; }; diff --git a/include/d/actor/d_a_obj_crope.h b/include/d/actor/d_a_obj_crope.h index bde25e379c..e0f0220000 100644 --- a/include/d/actor/d_a_obj_crope.h +++ b/include/d/actor/d_a_obj_crope.h @@ -52,7 +52,6 @@ public: mCoCancelTimer = 30; } -private: /* 0x0568 */ request_of_phase_process_class mPhase; /* 0x0570 */ dCcD_Stts mCcStts; /* 0x05AC */ dCcD_Sph mCollider; diff --git a/include/d/actor/d_a_obj_crvfence.h b/include/d/actor/d_a_obj_crvfence.h index dd760bb43f..9b6298553f 100644 --- a/include/d/actor/d_a_obj_crvfence.h +++ b/include/d/actor/d_a_obj_crvfence.h @@ -34,7 +34,6 @@ public: int Draw(); int Delete(); -private: /* 0x05A0 */ int field_0x5a0; /* 0x05A4 */ int mAction; /* 0x05A8 */ u8 field_0x5a8; diff --git a/include/d/actor/d_a_obj_crvgate.h b/include/d/actor/d_a_obj_crvgate.h index 1fb4d1a12b..fed7868085 100644 --- a/include/d/actor/d_a_obj_crvgate.h +++ b/include/d/actor/d_a_obj_crvgate.h @@ -50,7 +50,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ s32 mEventID; /* 0x5A4 */ bool mFlagGateClosed; /* 0x5A5 */ bool field_0x5a5; diff --git a/include/d/actor/d_a_obj_crvlh_down.h b/include/d/actor/d_a_obj_crvlh_down.h index ce1c31bfa8..06ad6c89f0 100644 --- a/include/d/actor/d_a_obj_crvlh_down.h +++ b/include/d/actor/d_a_obj_crvlh_down.h @@ -28,7 +28,6 @@ public: virtual int Draw(); virtual int Delete(); -private: /* 0x5A0 */ fpc_ProcID field_0x5a0; /* 0x5A4 */ fpc_ProcID field_0x5a4; /* 0x5A8 */ cXyz field_0x5a8; diff --git a/include/d/actor/d_a_obj_crvlh_up.h b/include/d/actor/d_a_obj_crvlh_up.h index 7f24ba03a6..950018311a 100644 --- a/include/d/actor/d_a_obj_crvlh_up.h +++ b/include/d/actor/d_a_obj_crvlh_up.h @@ -26,7 +26,6 @@ public: virtual int Draw(); virtual int Delete(); -private: /* 0x568 */ u8 field_0x568[0x5a4 - 0x5a0]; /* 0x5A4 */ fpc_ProcID field_0x5a4; /* 0x5A8 */ fpc_ProcID field_0x5a8; diff --git a/include/d/actor/d_a_obj_crvsteel.h b/include/d/actor/d_a_obj_crvsteel.h index a8d6305495..09ddc5be2a 100644 --- a/include/d/actor/d_a_obj_crvsteel.h +++ b/include/d/actor/d_a_obj_crvsteel.h @@ -29,7 +29,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ u32 mSmokeParticle1; /* 0x5A4 */ u32 mSmokeParticle2; /* 0x5A8 */ s16 mAction; diff --git a/include/d/actor/d_a_obj_crystal.h b/include/d/actor/d_a_obj_crystal.h index 0d6fcc1fc6..3c2fbfb110 100644 --- a/include/d/actor/d_a_obj_crystal.h +++ b/include/d/actor/d_a_obj_crystal.h @@ -22,7 +22,6 @@ public: int draw(); int _delete(); -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel[2]; /* 0x578 */ JPABaseEmitter* mpEmitter; diff --git a/include/d/actor/d_a_obj_cwall.h b/include/d/actor/d_a_obj_cwall.h index 396729be80..66d760a337 100644 --- a/include/d/actor/d_a_obj_cwall.h +++ b/include/d/actor/d_a_obj_cwall.h @@ -61,7 +61,6 @@ public: u8 getType() { return fopAcM_GetParamBit(this, 4, 4); } u8 getSwbit() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mWallModel; /* 0x5AC */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_damCps.h b/include/d/actor/d_a_obj_damCps.h index 1ce75cc663..d92a38184c 100644 --- a/include/d/actor/d_a_obj_damCps.h +++ b/include/d/actor/d_a_obj_damCps.h @@ -26,7 +26,6 @@ public: int _delete(); u8 getSwNo(); -private: /* 0x0570 */ dCcD_Stts mStts; /* 0x05AC */ dCcD_Cps mCps; /* 0x06F0 */ cM3dGCpsS mCapsule; diff --git a/include/d/actor/d_a_obj_dan.h b/include/d/actor/d_a_obj_dan.h index 15caef361a..3e3b62fc9b 100644 --- a/include/d/actor/d_a_obj_dan.h +++ b/include/d/actor/d_a_obj_dan.h @@ -39,7 +39,6 @@ public: bool CreateChk(); cPhs_Step create(); -private: /* 0x590 */ dCcD_Stts mCcStts; /* 0x5CC */ dCcD_Sph mCcSph; /* 0x704 */ daPy_boomerangMove_c mBoomerangMove; diff --git a/include/d/actor/d_a_obj_digholl.h b/include/d/actor/d_a_obj_digholl.h index 33f078a1ea..f985bbe165 100644 --- a/include/d/actor/d_a_obj_digholl.h +++ b/include/d/actor/d_a_obj_digholl.h @@ -21,7 +21,6 @@ public: void onDigStart() { field_0x568 = 1; } s16 getSceneNum() const { return field_0x56c; } -private: /* 0x568 */ u8 field_0x568; /* 0x569 */ u8 field_0x569; /* 0x56A */ u8 field_0x56a; diff --git a/include/d/actor/d_a_obj_digplace.h b/include/d/actor/d_a_obj_digplace.h index 404b35aff4..0b29ff11f6 100644 --- a/include/d/actor/d_a_obj_digplace.h +++ b/include/d/actor/d_a_obj_digplace.h @@ -17,7 +17,6 @@ public: int create(); int execute(); -private: /* 0x568 */ u8 mType; /* 0x569 */ u8 mSwitch; /* 0x56A */ u8 mDigFlg; diff --git a/include/d/actor/d_a_obj_digsnow.h b/include/d/actor/d_a_obj_digsnow.h index 09af66d091..4ef3de5958 100644 --- a/include/d/actor/d_a_obj_digsnow.h +++ b/include/d/actor/d_a_obj_digsnow.h @@ -50,7 +50,6 @@ public: void startDig() { mMode = ACTION_DIG_e; } void endDig() { mMode = ACTION_END_e; } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ dBgS_ObjAcch mAcch; diff --git a/include/d/actor/d_a_obj_enemy_create.h b/include/d/actor/d_a_obj_enemy_create.h index 3b29caddab..64da7b5203 100644 --- a/include/d/actor/d_a_obj_enemy_create.h +++ b/include/d/actor/d_a_obj_enemy_create.h @@ -22,7 +22,6 @@ public: void NameChk(); int create(); -private: /* 0x568 */ u8 field_0x568; /* 0x569 */ u8 mEnemyNum; /* 0x56A */ u8 mActivateSw; diff --git a/include/d/actor/d_a_obj_fallobj.h b/include/d/actor/d_a_obj_fallobj.h index 0c4efed3f3..f59a58991a 100644 --- a/include/d/actor/d_a_obj_fallobj.h +++ b/include/d/actor/d_a_obj_fallobj.h @@ -50,7 +50,6 @@ public: return this->speed.y != 0.0f; }; -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ Mtx mMtx; /* 0x5D8 */ u8 field_0x5d8; diff --git a/include/d/actor/d_a_obj_fan.h b/include/d/actor/d_a_obj_fan.h index 1ababac7a6..a4907c01a1 100644 --- a/include/d/actor/d_a_obj_fan.h +++ b/include/d/actor/d_a_obj_fan.h @@ -29,7 +29,6 @@ public: u8 getType() { return fopAcM_GetParamBit(this,0,4); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ dBgW* field_0x5ac; diff --git a/include/d/actor/d_a_obj_fchain.h b/include/d/actor/d_a_obj_fchain.h index 7c4ae74faf..e9250fbe24 100644 --- a/include/d/actor/d_a_obj_fchain.h +++ b/include/d/actor/d_a_obj_fchain.h @@ -31,11 +31,6 @@ public: csXyz* getAngle() { return field_0x8a4; } J3DModelData* getModelData() { return mModelData; } -#if TARGET_PC - void onInterpCallback(); -#endif - -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModelData* mModelData; /* 0x574 */ daObjFchain_shape_c mShape; @@ -46,14 +41,6 @@ private: /* 0x694 */ cXyz field_0x694[22]; /* 0x79C */ cXyz field_0x79c[22]; /* 0x8A4 */ csXyz field_0x8a4[22]; - -#if TARGET_PC - static const int CHAIN_COUNT = 22; - cXyz mChainInterpPrev[CHAIN_COUNT]; - cXyz mChainInterpCurr[CHAIN_COUNT]; - bool mChainInterpPrevValid; - bool mChainInterpCurrValid; -#endif }; STATIC_ASSERT(sizeof(daObjFchain_c) == 0x928); diff --git a/include/d/actor/d_a_obj_fireWood.h b/include/d/actor/d_a_obj_fireWood.h index 07a5d5334b..5ce85234de 100644 --- a/include/d/actor/d_a_obj_fireWood.h +++ b/include/d/actor/d_a_obj_fireWood.h @@ -27,7 +27,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x568 */ u8 field_0x568[0x574 - 0x568]; /* 0x574 */ dCcD_Stts mCcStts; /* 0x5B0 */ dCcD_Cyl mCcCyl; diff --git a/include/d/actor/d_a_obj_fireWood2.h b/include/d/actor/d_a_obj_fireWood2.h index bcb1bee95d..209ccf8ffd 100644 --- a/include/d/actor/d_a_obj_fireWood2.h +++ b/include/d/actor/d_a_obj_fireWood2.h @@ -28,7 +28,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x568 */ u8 field_0x568[0x574 - 0x568]; /* 0x574 */ dCcD_Stts mCcStts; /* 0x5B0 */ dCcD_Cyl mCcCyl; diff --git a/include/d/actor/d_a_obj_firepillar.h b/include/d/actor/d_a_obj_firepillar.h index 620373422a..8490a1c488 100644 --- a/include/d/actor/d_a_obj_firepillar.h +++ b/include/d/actor/d_a_obj_firepillar.h @@ -47,7 +47,6 @@ public: int draw(); int _delete(); -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ int field_0x570; /* 0x574 */ dBgS_ObjAcch mAcch; diff --git a/include/d/actor/d_a_obj_firepillar2.h b/include/d/actor/d_a_obj_firepillar2.h index 359cbf6bb3..579342b8dc 100644 --- a/include/d/actor/d_a_obj_firepillar2.h +++ b/include/d/actor/d_a_obj_firepillar2.h @@ -51,7 +51,6 @@ public: u8 getArg0() { return fopAcM_GetParamBit(this,8,8); } u8 getSwNo() { return fopAcM_GetParamBit(this,0,8); } -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mModel; /* 0x574 */ mDoExt_bckAnm* mBck; diff --git a/include/d/actor/d_a_obj_flag.h b/include/d/actor/d_a_obj_flag.h index 8187fe33b2..8ac8662fb9 100644 --- a/include/d/actor/d_a_obj_flag.h +++ b/include/d/actor/d_a_obj_flag.h @@ -28,7 +28,7 @@ public: * */ class daObjFlag_c : public fopAc_ac_c { -private: +public: /* 0x568 */ J3DModel* mpModel1; /* 0x56c */ J3DModel* mpModel2; /* 0x570 */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_flag2.h b/include/d/actor/d_a_obj_flag2.h index c9ef033190..676da2141d 100644 --- a/include/d/actor/d_a_obj_flag2.h +++ b/include/d/actor/d_a_obj_flag2.h @@ -83,7 +83,6 @@ public: static DUSK_GAME_DATA daObjFlag2_Attr_c const M_attr; -private: /* 0x0568 */ J3DModel* mModel; /* 0x056C */ request_of_phase_process_class mFlagPhase; /* 0x0574 */ request_of_phase_process_class mArcPhase; diff --git a/include/d/actor/d_a_obj_flag3.h b/include/d/actor/d_a_obj_flag3.h index b93394ce15..26bfd5b506 100644 --- a/include/d/actor/d_a_obj_flag3.h +++ b/include/d/actor/d_a_obj_flag3.h @@ -96,7 +96,6 @@ public: static DUSK_GAME_DATA daObjFlag3_Attr_c const M_attr; const daObjFlag3_Attr_c& attr() const { return M_attr; } -private: /* 0x0568 */ J3DModel* mModel; /* 0x056C */ request_of_phase_process_class mFlagPhase; /* 0x0574 */ request_of_phase_process_class mArcPhase; diff --git a/include/d/actor/d_a_obj_gadget.h b/include/d/actor/d_a_obj_gadget.h index cb2c99af97..a52f9ab27b 100644 --- a/include/d/actor/d_a_obj_gadget.h +++ b/include/d/actor/d_a_obj_gadget.h @@ -17,7 +17,7 @@ class daObj_Gadget_HIO_c; * */ class daObj_Gadget_c : public fopAc_ac_c { -private: +public: /* 0x568 */ daObj_Gadget_HIO_c* field_0x568; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ J3DModel* mModel; diff --git a/include/d/actor/d_a_obj_ganonwall.h b/include/d/actor/d_a_obj_ganonwall.h index 02d0cd1d99..c5e6f7d06a 100644 --- a/include/d/actor/d_a_obj_ganonwall.h +++ b/include/d/actor/d_a_obj_ganonwall.h @@ -26,7 +26,6 @@ public: u32 getEventBit1() { return fopAcM_GetParamBit(this, 0, 10); } u32 getEventBit2() { return fopAcM_GetParamBit(this, 10, 10); } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ mDoExt_btkAnm* mpBtkAnm; diff --git a/include/d/actor/d_a_obj_ganonwall2.h b/include/d/actor/d_a_obj_ganonwall2.h index a938a32141..0026094a8d 100644 --- a/include/d/actor/d_a_obj_ganonwall2.h +++ b/include/d/actor/d_a_obj_ganonwall2.h @@ -25,7 +25,6 @@ public: u32 getEventBit1() { return fopAcM_GetParamBit(this, 0, 10); } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ mDoExt_btkAnm* mpBtkAnm; diff --git a/include/d/actor/d_a_obj_gb.h b/include/d/actor/d_a_obj_gb.h index 3f5a354f5e..2bc435b2df 100644 --- a/include/d/actor/d_a_obj_gb.h +++ b/include/d/actor/d_a_obj_gb.h @@ -36,5 +36,4 @@ public: STATIC_ASSERT(sizeof(obj_gb_class) == 0x6bc); - #endif /* D_A_OBJ_GB_H */ diff --git a/include/d/actor/d_a_obj_geyser.h b/include/d/actor/d_a_obj_geyser.h index 54a7f3bcb8..244d3b1fcf 100644 --- a/include/d/actor/d_a_obj_geyser.h +++ b/include/d/actor/d_a_obj_geyser.h @@ -62,7 +62,6 @@ public: u8 getArg3() { return fopAcM_GetParamBit(this, 24, 8); } u8 getSwNo() { return 0xFF; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ dCcD_Stts mCcStts; diff --git a/include/d/actor/d_a_obj_glowSphere.h b/include/d/actor/d_a_obj_glowSphere.h index c6c2ee220e..062a9ce7d5 100644 --- a/include/d/actor/d_a_obj_glowSphere.h +++ b/include/d/actor/d_a_obj_glowSphere.h @@ -30,7 +30,6 @@ public: bool isSet() { return mIsSet; } daGlwSph_c* getpSph() { return mpSph; } -private: /* 0x0 */ daGlwSph_c* mpSph; /* 0x4 */ s8 mIsSet; }; diff --git a/include/d/actor/d_a_obj_goGate.h b/include/d/actor/d_a_obj_goGate.h index a0a66c7f32..8ed98a43e6 100644 --- a/include/d/actor/d_a_obj_goGate.h +++ b/include/d/actor/d_a_obj_goGate.h @@ -40,7 +40,6 @@ public: u32 getLR() { return fopAcM_GetParamBit(this, 8, 8); } u32 getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_gomikabe.h b/include/d/actor/d_a_obj_gomikabe.h index 441fff371f..a0a01c4e20 100644 --- a/include/d/actor/d_a_obj_gomikabe.h +++ b/include/d/actor/d_a_obj_gomikabe.h @@ -41,7 +41,6 @@ public: int Draw(); int Delete(); -private: /* 0x05A0 */ u8 mSwBit; /* 0x05A1 */ u8 mAction; /* 0x05A2 */ u8 field_0x5a2; diff --git a/include/d/actor/d_a_obj_gpTaru.h b/include/d/actor/d_a_obj_gpTaru.h index b3b31a5c9d..1ab93b2586 100644 --- a/include/d/actor/d_a_obj_gpTaru.h +++ b/include/d/actor/d_a_obj_gpTaru.h @@ -46,7 +46,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ J3DModel* mpModel; /* 0x578 */ u8 mMode; diff --git a/include/d/actor/d_a_obj_gra2.h b/include/d/actor/d_a_obj_gra2.h index 3cf17e090c..4add220bcd 100644 --- a/include/d/actor/d_a_obj_gra2.h +++ b/include/d/actor/d_a_obj_gra2.h @@ -243,7 +243,6 @@ public: void setDemoMode(s16 mDemoMode) { mDemoCamMode = mDemoMode; } s16 getDemoMode() { return mDemoCamMode; } -private: /* 0x0A48 */ u16 field_0xa48; /* 0x0A4C */ OBJ_GRA_HIO_CLASS* mpHIO; /* 0x0A50 */ Process field_0xa50; diff --git a/include/d/actor/d_a_obj_graWall.h b/include/d/actor/d_a_obj_graWall.h index ebcd1cdb58..037767547a 100644 --- a/include/d/actor/d_a_obj_graWall.h +++ b/include/d/actor/d_a_obj_graWall.h @@ -20,7 +20,6 @@ public: void col_init(); void col_set(); -private: /* 0x568 */ dCcD_Stts mStts; /* 0x5A4 */ dCcD_Cyl mCyl; /* 0x6E0 */ u8 field_0x6e0; diff --git a/include/d/actor/d_a_obj_gra_rock.h b/include/d/actor/d_a_obj_gra_rock.h index 24e816a829..21ec800563 100644 --- a/include/d/actor/d_a_obj_gra_rock.h +++ b/include/d/actor/d_a_obj_gra_rock.h @@ -33,7 +33,6 @@ public: static DUSK_GAME_DATA dCcD_SrcCyl const mCcDCyl; -private: /* 0x5A0 */ request_of_phase_process_class mPhases[5]; /* 0x5C8 */ J3DModel* mModel; /* 0x5CC */ mDoExt_btpAnm mBtp; diff --git a/include/d/actor/d_a_obj_grave_stone.h b/include/d/actor/d_a_obj_grave_stone.h index d41d58348a..9155a2f54c 100644 --- a/include/d/actor/d_a_obj_grave_stone.h +++ b/include/d/actor/d_a_obj_grave_stone.h @@ -36,7 +36,6 @@ public: int Draw(); int Delete(); -private: friend class daNpc_zrZ_c; /* 0x5A0 */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_groundwater.h b/include/d/actor/d_a_obj_groundwater.h index 5e9061d89c..1fc0f0ef37 100644 --- a/include/d/actor/d_a_obj_groundwater.h +++ b/include/d/actor/d_a_obj_groundwater.h @@ -47,7 +47,6 @@ public: u8 getSw() { return fopAcM_GetParamBit(this, 8, 8); } u8 getSw2() { return fopAcM_GetParamBit(this, 16, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel1; /* 0x5AC */ J3DModel* mModel2; diff --git a/include/d/actor/d_a_obj_grz_rock.h b/include/d/actor/d_a_obj_grz_rock.h index cd65d2e906..4c8f382071 100644 --- a/include/d/actor/d_a_obj_grz_rock.h +++ b/include/d/actor/d_a_obj_grz_rock.h @@ -28,7 +28,6 @@ public: void setSmashFlag(u8 i_smashFlag) { mSmashFlag = i_smashFlag; } -private: /* 0x568 */ request_of_phase_process_class mPhase[1]; /* 0x5A0 */ J3DModel* mpModel; /* 0x5AC */ dBgS_ObjAcch mBgc; diff --git a/include/d/actor/d_a_obj_hakai_brl.h b/include/d/actor/d_a_obj_hakai_brl.h index dba4b3cd3f..9bd109da7c 100644 --- a/include/d/actor/d_a_obj_hakai_brl.h +++ b/include/d/actor/d_a_obj_hakai_brl.h @@ -34,7 +34,6 @@ public: static DUSK_GAME_DATA dCcD_SrcCyl const s_CcDCyl; -private: /* 0x574 */ J3DModel* mpModel; /* 0x578 */ dCcD_Stts mStts; /* 0x5B4 */ dCcD_Cyl mCyl; diff --git a/include/d/actor/d_a_obj_hakai_ftr.h b/include/d/actor/d_a_obj_hakai_ftr.h index 45c815c5f9..ac7f749860 100644 --- a/include/d/actor/d_a_obj_hakai_ftr.h +++ b/include/d/actor/d_a_obj_hakai_ftr.h @@ -31,7 +31,6 @@ public: static DUSK_GAME_DATA dCcD_SrcCyl const s_CcDCyl; -private: /* 0x574 */ Mtx mMtx; /* 0x5A4 */ dBgW* field_0x5a4; /* 0x5A8 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_hasu2.h b/include/d/actor/d_a_obj_hasu2.h index 9c36c0d180..203b4ff159 100644 --- a/include/d/actor/d_a_obj_hasu2.h +++ b/include/d/actor/d_a_obj_hasu2.h @@ -29,7 +29,6 @@ public: virtual ~daObjMHasu_c() {}; void setRideFlag(bool i_ride) { mRideFlag = i_ride; } -private: /* 0x5A8 */ Mtx mMtx; /* 0x5D8 */ Mtx mMtx2; /* 0x608 */ J3DModel* mModel; diff --git a/include/d/actor/d_a_obj_hbombkoya.h b/include/d/actor/d_a_obj_hbombkoya.h index 9f9c0a5149..b1d76f819b 100644 --- a/include/d/actor/d_a_obj_hbombkoya.h +++ b/include/d/actor/d_a_obj_hbombkoya.h @@ -39,7 +39,6 @@ public: int getSwNo() { return fopAcM_GetParamBit(this, 0, 8); } int getSw2No() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x5A8 */ // dEvLib_callback_c /* 0x5B8 */ Mtx mBgMtx; /* 0x5E8 */ Mtx mMtx; diff --git a/include/d/actor/d_a_obj_hfuta.h b/include/d/actor/d_a_obj_hfuta.h index 77015715d0..4f4ee44eeb 100644 --- a/include/d/actor/d_a_obj_hfuta.h +++ b/include/d/actor/d_a_obj_hfuta.h @@ -37,7 +37,6 @@ public: void setMode(u8 mode) { mMode = mode; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ cXyz mTransM; diff --git a/include/d/actor/d_a_obj_hsTarget.h b/include/d/actor/d_a_obj_hsTarget.h index 1e0e775e12..50a497eb8e 100644 --- a/include/d/actor/d_a_obj_hsTarget.h +++ b/include/d/actor/d_a_obj_hsTarget.h @@ -23,7 +23,6 @@ public: u8 getModelType() { return fopAcM_GetParamBit(this, 0, 4); } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mIndex; diff --git a/include/d/actor/d_a_obj_iceleaf.h b/include/d/actor/d_a_obj_iceleaf.h index da79be5bc5..42ad6237e7 100644 --- a/include/d/actor/d_a_obj_iceleaf.h +++ b/include/d/actor/d_a_obj_iceleaf.h @@ -61,7 +61,6 @@ public: u8 checkRideStatus() { return fopAcM_GetParamBit(this, 0x10, 8); } u8 getSwbit() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ dBgS_ObjAcch mAcch; diff --git a/include/d/actor/d_a_obj_itamato.h b/include/d/actor/d_a_obj_itamato.h index 10c6459887..b9080f7db7 100644 --- a/include/d/actor/d_a_obj_itamato.h +++ b/include/d/actor/d_a_obj_itamato.h @@ -46,7 +46,7 @@ public: #endif class daObj_ItaMato_c : public fopAc_ac_c { -private: +public: /* 0x568 */ OBJ_ITAMATO_HIO_CLASS* mHIO; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ J3DModel* mpModels[2]; diff --git a/include/d/actor/d_a_obj_kabuto.h b/include/d/actor/d_a_obj_kabuto.h index 9738147885..2b9d6255ee 100644 --- a/include/d/actor/d_a_obj_kabuto.h +++ b/include/d/actor/d_a_obj_kabuto.h @@ -47,7 +47,6 @@ public: cPhs_Step create(); inline int Draw(); -private: /* 0x590 */ dCcD_Stts mCcStts; /* 0x5CC */ dCcD_Sph mCcSph; /* 0x704 */ daPy_boomerangMove_c mBoomerangMove; diff --git a/include/d/actor/d_a_obj_kag.h b/include/d/actor/d_a_obj_kag.h index 58692d7a8c..09c9a088a0 100644 --- a/include/d/actor/d_a_obj_kag.h +++ b/include/d/actor/d_a_obj_kag.h @@ -39,7 +39,6 @@ public: void kag_setParticle(); inline int CreateHeap(); inline int draw(); -private: /* 0x590 */ request_of_phase_process_class mPhase; /* 0x598 */ Z2Creature mSound; /* 0x628 */ mDoExt_McaMorfSO* mpModelMorf; diff --git a/include/d/actor/d_a_obj_kago.h b/include/d/actor/d_a_obj_kago.h index 265db41c57..4a6ed74730 100644 --- a/include/d/actor/d_a_obj_kago.h +++ b/include/d/actor/d_a_obj_kago.h @@ -125,7 +125,6 @@ public: field_0xb68 = 0.0f; } -private: /* 0x568 */ OBJ_KAGO_HIO_CLASS* mpHIO; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ J3DModel* field_0x574; diff --git a/include/d/actor/d_a_obj_kaisou.h b/include/d/actor/d_a_obj_kaisou.h index 3398c24884..36d6f568df 100644 --- a/include/d/actor/d_a_obj_kaisou.h +++ b/include/d/actor/d_a_obj_kaisou.h @@ -28,7 +28,6 @@ public: inline int CreateHeap(); inline int Draw(); -private: /* 0x568 */ u8 field_0x568; /* 0x569 */ u8 field_0x569; /* 0x56A */ u8 field_0x56a[0x56e - 0x56a]; diff --git a/include/d/actor/d_a_obj_kamakiri.h b/include/d/actor/d_a_obj_kamakiri.h index 3b2d56e533..52b648ea48 100644 --- a/include/d/actor/d_a_obj_kamakiri.h +++ b/include/d/actor/d_a_obj_kamakiri.h @@ -56,7 +56,6 @@ public: u8 CreateChk(); int create(); -private: /* 0x590 */ dCcD_Stts mStts; /* 0x5CC */ dCcD_Sph mSphere; /* 0x704 */ daPy_boomerangMove_c mBoomerangMove; diff --git a/include/d/actor/d_a_obj_kantera.h b/include/d/actor/d_a_obj_kantera.h index 0228b51fd5..fbe230ed16 100644 --- a/include/d/actor/d_a_obj_kantera.h +++ b/include/d/actor/d_a_obj_kantera.h @@ -33,7 +33,6 @@ public: void setStatus(u8 i_status) { mStatus = i_status; } -private: /* 0x92C */ u32 field_0x92c; /* 0x930 */ s32 field_0x930; /* 0x934 */ u8 mStatus; diff --git a/include/d/actor/d_a_obj_katatsumuri.h b/include/d/actor/d_a_obj_katatsumuri.h index e481d3eb3a..93b9368fb5 100644 --- a/include/d/actor/d_a_obj_katatsumuri.h +++ b/include/d/actor/d_a_obj_katatsumuri.h @@ -55,7 +55,6 @@ public: bool CreateChk(); int create(); -private: /* 0x590 */ dBgS_ObjAcch mAcch; /* 0x768 */ dBgS_GndChk mGndChk; /* 0x7BC */ u8 field_0x7bc[0x7c0 - 0x7bc]; diff --git a/include/d/actor/d_a_obj_kazeneko.h b/include/d/actor/d_a_obj_kazeneko.h index 73147b6fbe..164612ecfc 100644 --- a/include/d/actor/d_a_obj_kazeneko.h +++ b/include/d/actor/d_a_obj_kazeneko.h @@ -46,7 +46,6 @@ public: const KazoNekoAttr& attr() { return M_attr; } static DUSK_GAME_DATA KazoNekoAttr const M_attr; -private: /* 0x568 */ J3DModel* mModel; /* 0x56C */ J3DModel* mArmModels[4]; /* 0x57C */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_keyhole.h b/include/d/actor/d_a_obj_keyhole.h index b8a813776d..fd39641ad8 100644 --- a/include/d/actor/d_a_obj_keyhole.h +++ b/include/d/actor/d_a_obj_keyhole.h @@ -66,18 +66,9 @@ public: /* 0x2CA7 */ s8 hide_lock; /* 0x2CA8 */ cXyz field_0x2ca8; /* 0x2CB4 */ u8 field_0x2cb4; - -#if TARGET_PC - Mtx mChainInterpPrev[6][16]; - Mtx mChainInterpCurr[6][16]; - bool mChainInterpPrevValid; - bool mChainInterpCurrValid; -#endif }; -#if !TARGET_PC STATIC_ASSERT(sizeof(obj_keyhole_class) == 0x2CB8); -#endif class daObj_Keyhole_HIO_c : public JORReflexible { public: diff --git a/include/d/actor/d_a_obj_kgate.h b/include/d/actor/d_a_obj_kgate.h index 96798028b2..f7fe72767c 100644 --- a/include/d/actor/d_a_obj_kgate.h +++ b/include/d/actor/d_a_obj_kgate.h @@ -46,7 +46,6 @@ public: u8 getEventID() { return fopAcM_GetParamBit(this, 0x14, 8); } u8 getNameArg() { return fopAcM_GetParamBit(this, 0x10, 4); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpGateModel; /* 0x5AC */ J3DModel* mpGateModel2; diff --git a/include/d/actor/d_a_obj_kiPot.h b/include/d/actor/d_a_obj_kiPot.h index 01d7cf76c0..248daa2d46 100644 --- a/include/d/actor/d_a_obj_kiPot.h +++ b/include/d/actor/d_a_obj_kiPot.h @@ -23,7 +23,6 @@ public: int Draw(); int Delete(); -private: /* 0x568 */ u8 field_0x568[0x574 - 0x568]; /* 0x574 */ u8 mMode; /* 0x575 */ u8 field_0x575; diff --git a/include/d/actor/d_a_obj_kita.h b/include/d/actor/d_a_obj_kita.h index 1e343c78ce..09eff51298 100644 --- a/include/d/actor/d_a_obj_kita.h +++ b/include/d/actor/d_a_obj_kita.h @@ -17,7 +17,6 @@ public: STATIC_ASSERT(sizeof(kita_s) == 0x60); - /** * @ingroup actors-objects * @class obj_kita_class diff --git a/include/d/actor/d_a_obj_kjgjs.h b/include/d/actor/d_a_obj_kjgjs.h index 495750bd89..189c672c7d 100644 --- a/include/d/actor/d_a_obj_kjgjs.h +++ b/include/d/actor/d_a_obj_kjgjs.h @@ -25,7 +25,6 @@ public: u32 getType() { return fopAcM_GetParamBit(this, 0, 4); } -private: /* 0x5A8 */ Mtx mMtx[2]; /* 0x608 */ J3DModel* mpModel; /* 0x60C */ s32 field_0x60c; diff --git a/include/d/actor/d_a_obj_klift00.h b/include/d/actor/d_a_obj_klift00.h index ab5403d51b..fb4c05fde0 100644 --- a/include/d/actor/d_a_obj_klift00.h +++ b/include/d/actor/d_a_obj_klift00.h @@ -17,7 +17,7 @@ class daObjKLift00_c : public dBgS_MoveBgActor, public request_of_phase_process_class { public: cPhs_Step create1st(); - void setMtx(); + void setMtx(IF_DUSK(bool presentation = false)); void rideActor(fopAc_ac_c*); int CreateHeap(); int Create(); @@ -25,15 +25,10 @@ public: int Draw(); int Delete(); -#if TARGET_PC - void onInterpCallback(); -#endif - enum Param_e { LOCK_e = (1 << 6), NO_BASE_DISP = (1 << 7) }; -private: struct ChainPos { cXyz mDeltaPosVector; cXyz mCurrentPos; @@ -54,13 +49,6 @@ private: /* 0x1020 */ dCcD_Cyl mCylinderCollider; /* 0x115C */ s32 mStopSwingingFrames; -#if TARGET_PC - cXyz mChainInterpPrev[64]; - cXyz mChainInterpCurr[64]; - bool mChainInterpPrevValid; - bool mChainInterpCurrValid; -#endif - // Number of chain models u32 getArg0() { return fopAcM_GetParamBit(this, 0, 6); diff --git a/include/d/actor/d_a_obj_knBullet.h b/include/d/actor/d_a_obj_knBullet.h index e8056fbb25..fa2d5397a2 100644 --- a/include/d/actor/d_a_obj_knBullet.h +++ b/include/d/actor/d_a_obj_knBullet.h @@ -26,7 +26,6 @@ public: u8 getActionMode() { return mActionMode; } void setActionMode(u8 i_action) { mActionMode = i_action; } -private: /* 0x568 */ Mtx mMtx; /* 0x598 */ dCcD_Stts mCcStts; /* 0x5D4 */ dCcD_Sph mCcSph; diff --git a/include/d/actor/d_a_obj_kshutter.h b/include/d/actor/d_a_obj_kshutter.h index b217adef2d..a0a2204c0d 100644 --- a/include/d/actor/d_a_obj_kshutter.h +++ b/include/d/actor/d_a_obj_kshutter.h @@ -64,7 +64,6 @@ public: u8 getType() { return fopAcM_GetParamBit(this, 8, 8); } u8 getSwNo() { return fopAcM_GetParamBit(this, 0, 8); } void setAction(u8 action) { mAction = action; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase1; /* 0x5A8 */ request_of_phase_process_class mPhase2; /* 0x5B0 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_ktOnFire.h b/include/d/actor/d_a_obj_ktOnFire.h index 39875e122e..7d292bf286 100644 --- a/include/d/actor/d_a_obj_ktOnFire.h +++ b/include/d/actor/d_a_obj_ktOnFire.h @@ -26,7 +26,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x568 */ dCcD_Stts mCcStts; /* 0x5A4 */ dCcD_Cyl mCcCyl; /* 0x6E0 */ u8 mWaitTime; diff --git a/include/d/actor/d_a_obj_kuwagata.h b/include/d/actor/d_a_obj_kuwagata.h index 9df3f0bc35..a14b0544bc 100644 --- a/include/d/actor/d_a_obj_kuwagata.h +++ b/include/d/actor/d_a_obj_kuwagata.h @@ -47,7 +47,6 @@ public: int create(); inline int Draw(); -private: /* 0x590 */ dCcD_Stts mStts; /* 0x5CC */ dCcD_Sph mSph; /* 0x704 */ daPy_boomerangMove_c mBoomerangMove; diff --git a/include/d/actor/d_a_obj_kwheel00.h b/include/d/actor/d_a_obj_kwheel00.h index ceaf132fd6..2d58cb32f1 100644 --- a/include/d/actor/d_a_obj_kwheel00.h +++ b/include/d/actor/d_a_obj_kwheel00.h @@ -41,7 +41,6 @@ public: enum Type_e { TYPE_LARGE_GOLD, TYPE_SMALL_PLATINUM }; -private: enum QuadrantalAngle_e { DEG_INVALID = -1, DEG_0, DEG_90, DEG_180, DEG_270, DEG_MAX}; /* 0x5B8 */ Mtx mNewBgMtx; diff --git a/include/d/actor/d_a_obj_kwheel01.h b/include/d/actor/d_a_obj_kwheel01.h index 40bc025db4..9c6fa31075 100644 --- a/include/d/actor/d_a_obj_kwheel01.h +++ b/include/d/actor/d_a_obj_kwheel01.h @@ -27,7 +27,6 @@ public: int Delete(); bool eventStart(); -private: /* 0x5B8 */ Mtx mNewBgMtx; /* 0x5E8 */ Mtx mTransformMtx; /* 0x618 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_kznkarm.h b/include/d/actor/d_a_obj_kznkarm.h index c70908fcb8..3838d1f9ff 100644 --- a/include/d/actor/d_a_obj_kznkarm.h +++ b/include/d/actor/d_a_obj_kznkarm.h @@ -63,7 +63,6 @@ public: static DUSK_GAME_DATA daObjKznkarm_Attr_c const M_attr; static DUSK_GAME_DATA actionFunc ActionTable[4][2]; -private: /* 0x568 */ J3DModel* mpModel; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ dBgS_AcchCir mAcchCir; diff --git a/include/d/actor/d_a_obj_ladder.h b/include/d/actor/d_a_obj_ladder.h index 72f6fce4d2..b46bf99603 100644 --- a/include/d/actor/d_a_obj_ladder.h +++ b/include/d/actor/d_a_obj_ladder.h @@ -76,7 +76,6 @@ namespace daObjLadder { static DUSK_GAME_DATA char const M_arcname[5]; static DUSK_GAME_DATA Mtx M_tmp_mtx; -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ Type_e mType; diff --git a/include/d/actor/d_a_obj_laundry.h b/include/d/actor/d_a_obj_laundry.h index cc79bc4e05..3dd76c6a39 100644 --- a/include/d/actor/d_a_obj_laundry.h +++ b/include/d/actor/d_a_obj_laundry.h @@ -56,7 +56,6 @@ public: inline int daObjLdy_Draw(); inline int daObjLdy_Execute(); -private: static DUSK_GAME_DATA const daObjLdy_Attr_c mAttr; /* 0x568 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_laundry_rope.h b/include/d/actor/d_a_obj_laundry_rope.h index 3ecab164b2..85d76cbe8d 100644 --- a/include/d/actor/d_a_obj_laundry_rope.h +++ b/include/d/actor/d_a_obj_laundry_rope.h @@ -58,7 +58,6 @@ public: static DUSK_GAME_DATA const daObjLndRope_Attr_c mAttr; static const daObjLndRope_Hio_c M_Hio; -private: /* 0x0568 */ mDoExt_3DlineMat1_c mRopeMat; /* 0x05a4 */ request_of_phase_process_class mPhase; /* 0x05ac */ Mtx mMtx; diff --git a/include/d/actor/d_a_obj_life_container.h b/include/d/actor/d_a_obj_life_container.h index d4dc5bb334..a7b209cb94 100644 --- a/include/d/actor/d_a_obj_life_container.h +++ b/include/d/actor/d_a_obj_life_container.h @@ -64,7 +64,6 @@ public: current.pos = new_pos; } -// private: /* 0x92C */ fpc_ProcID mItemId; /* 0x930 */ int mCounter; /* 0x934 */ u8 mStatus; diff --git a/include/d/actor/d_a_obj_lv1Candle00.h b/include/d/actor/d_a_obj_lv1Candle00.h index e9e62da388..c70d484930 100644 --- a/include/d/actor/d_a_obj_lv1Candle00.h +++ b/include/d/actor/d_a_obj_lv1Candle00.h @@ -30,7 +30,6 @@ public: int Draw(); int Delete(); -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_lv1Candle01.h b/include/d/actor/d_a_obj_lv1Candle01.h index 632c117f10..1f1dbe4ee4 100644 --- a/include/d/actor/d_a_obj_lv1Candle01.h +++ b/include/d/actor/d_a_obj_lv1Candle01.h @@ -27,7 +27,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_lv2Candle.h b/include/d/actor/d_a_obj_lv2Candle.h index 19be29a16e..424dd844fd 100644 --- a/include/d/actor/d_a_obj_lv2Candle.h +++ b/include/d/actor/d_a_obj_lv2Candle.h @@ -37,7 +37,6 @@ public: u8 getSwType() { return fopAcM_GetParamBit(this, 8, 4); } u8 getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_lv3Candle.h b/include/d/actor/d_a_obj_lv3Candle.h index 7b9765136d..76edaaac77 100644 --- a/include/d/actor/d_a_obj_lv3Candle.h +++ b/include/d/actor/d_a_obj_lv3Candle.h @@ -29,7 +29,6 @@ public: int getSwBit() { return fopAcM_GetParamBit(this, 0, 8); } int getType() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ u8 mType; diff --git a/include/d/actor/d_a_obj_lv3Water.h b/include/d/actor/d_a_obj_lv3Water.h index de4ff03356..82f5be1661 100644 --- a/include/d/actor/d_a_obj_lv3Water.h +++ b/include/d/actor/d_a_obj_lv3Water.h @@ -45,7 +45,6 @@ public: int getParamType() { return (shape_angle.x & 0xFF00) >> 8; } u8 getType() { return mType; } -private: /* 0x5B8 */ request_of_phase_process_class mPhase; /* 0x5C0 */ J3DModel* mpModel1; /* 0x5C4 */ J3DModel* mpModel2; diff --git a/include/d/actor/d_a_obj_lv3Water2.h b/include/d/actor/d_a_obj_lv3Water2.h index be13ef4819..4227e8bcb0 100644 --- a/include/d/actor/d_a_obj_lv3Water2.h +++ b/include/d/actor/d_a_obj_lv3Water2.h @@ -29,7 +29,6 @@ public: int Delete(); bool eventStart(); -private: /* 0x5B8 */ request_of_phase_process_class mPhase; /* 0x5C0 */ J3DModel* mpModel; /* 0x5C4 */ mDoExt_btkAnm mRefractionAnm; // Applied to water surface diff --git a/include/d/actor/d_a_obj_lv3saka00.h b/include/d/actor/d_a_obj_lv3saka00.h index f08f4a5d19..5207e74de8 100644 --- a/include/d/actor/d_a_obj_lv3saka00.h +++ b/include/d/actor/d_a_obj_lv3saka00.h @@ -28,7 +28,6 @@ public: u8 getSwbit() { return fopAcM_GetParamBit(this, 0, 8) & 0xFF; } u8 getType() { return fopAcM_GetParamBit(this, 28, 4) & 0xFF; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ dBgW* mpBgW2; }; diff --git a/include/d/actor/d_a_obj_lv3waterEff.h b/include/d/actor/d_a_obj_lv3waterEff.h index fdf7b204dc..a612168fac 100644 --- a/include/d/actor/d_a_obj_lv3waterEff.h +++ b/include/d/actor/d_a_obj_lv3waterEff.h @@ -18,7 +18,6 @@ public: int execute(); int _delete(); -private: /* 0x568 */ cXyz mSePositions[2]; /* 0x580 */ JPABaseEmitter* mParticles[4]; /* 0x590 */ u8 unused_0x590[0x594 - 0x590]; diff --git a/include/d/actor/d_a_obj_lv4CandleDemoTag.h b/include/d/actor/d_a_obj_lv4CandleDemoTag.h index 58378ecd4e..4ac8618776 100644 --- a/include/d/actor/d_a_obj_lv4CandleDemoTag.h +++ b/include/d/actor/d_a_obj_lv4CandleDemoTag.h @@ -43,7 +43,6 @@ public: virtual ~dalv4CandleDemoTag_c() {} virtual bool eventStart(); -private: /* 0x584 */ u8 mMode; /* 0x585 */ u8 mPathID; /* 0x586 */ u8 mSwNG; diff --git a/include/d/actor/d_a_obj_lv4EdShutter.h b/include/d/actor/d_a_obj_lv4EdShutter.h index a19de47873..992431c64d 100644 --- a/include/d/actor/d_a_obj_lv4EdShutter.h +++ b/include/d/actor/d_a_obj_lv4EdShutter.h @@ -45,7 +45,6 @@ public: u8 getZenmetuSw() { return shape_angle.z & 0xFF; } u8 getStatus() { return mStatus; } -private: /* 0x5B8 */ request_of_phase_process_class mPhase; /* 0x5C0 */ J3DModel* mpModel; /* 0x5C4 */ u8 mMode; diff --git a/include/d/actor/d_a_obj_lv4HsTarget.h b/include/d/actor/d_a_obj_lv4HsTarget.h index fbb1c8862a..c618aec40e 100644 --- a/include/d/actor/d_a_obj_lv4HsTarget.h +++ b/include/d/actor/d_a_obj_lv4HsTarget.h @@ -23,7 +23,6 @@ public: virtual int Draw(); virtual int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; }; diff --git a/include/d/actor/d_a_obj_lv4PoGate.h b/include/d/actor/d_a_obj_lv4PoGate.h index 480885fa0a..bd46dd30c4 100644 --- a/include/d/actor/d_a_obj_lv4PoGate.h +++ b/include/d/actor/d_a_obj_lv4PoGate.h @@ -52,7 +52,6 @@ public: int getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_lv4RailWall.h b/include/d/actor/d_a_obj_lv4RailWall.h index 7cffceedc0..39858be708 100644 --- a/include/d/actor/d_a_obj_lv4RailWall.h +++ b/include/d/actor/d_a_obj_lv4RailWall.h @@ -50,7 +50,6 @@ public: u8 getSwbit() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5B0 */ request_of_phase_process_class mPhase; /* 0x5B8 */ J3DModel* mpModel; /* 0x5BC */ dBgS_ObjAcch field_0x5bc; diff --git a/include/d/actor/d_a_obj_lv4SlideWall.h b/include/d/actor/d_a_obj_lv4SlideWall.h index fcafa19dfc..1e08e68206 100644 --- a/include/d/actor/d_a_obj_lv4SlideWall.h +++ b/include/d/actor/d_a_obj_lv4SlideWall.h @@ -32,7 +32,6 @@ public: int getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_lv4chandelier.h b/include/d/actor/d_a_obj_lv4chandelier.h index d0e7c53515..2b965d5831 100644 --- a/include/d/actor/d_a_obj_lv4chandelier.h +++ b/include/d/actor/d_a_obj_lv4chandelier.h @@ -43,7 +43,6 @@ public: int getAddChain2() { return fopAcM_GetParamBit(this, 12, 4); } int getSw() { return fopAcM_GetParamBit(this, 16, 8); } -private: /* 0x05A8 */ Mtx field_0x5a8; /* 0x05D8 */ Mtx field_0x5d8; /* 0x0608 */ J3DModel* mModel; diff --git a/include/d/actor/d_a_obj_lv4floor.h b/include/d/actor/d_a_obj_lv4floor.h index 7b6681419d..704ccece93 100644 --- a/include/d/actor/d_a_obj_lv4floor.h +++ b/include/d/actor/d_a_obj_lv4floor.h @@ -38,7 +38,6 @@ public: u8 getSwbit() { return fopAcM_GetParamBit(this, 0, 8) & 0xFF; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ f32 mMoveYPos; diff --git a/include/d/actor/d_a_obj_lv4gear.h b/include/d/actor/d_a_obj_lv4gear.h index 5e649c0722..9c3d4920c1 100644 --- a/include/d/actor/d_a_obj_lv4gear.h +++ b/include/d/actor/d_a_obj_lv4gear.h @@ -31,7 +31,6 @@ public: u8 getType() { return fopAcM_GetParamBit(this, 8, 4); } u8 checkSE() { return fopAcM_GetParamBit(this, 12, 4); } -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ s16 mTarget; diff --git a/include/d/actor/d_a_obj_lv4prelvtr.h b/include/d/actor/d_a_obj_lv4prelvtr.h index 2fd50068ac..9e4846671c 100644 --- a/include/d/actor/d_a_obj_lv4prelvtr.h +++ b/include/d/actor/d_a_obj_lv4prelvtr.h @@ -26,7 +26,6 @@ public: int getSwNo() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A8 */ Mtx mMtx1; /* 0x5D8 */ Mtx mMtx2; /* 0x608 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_lv4sand.h b/include/d/actor/d_a_obj_lv4sand.h index 0ff6420e27..1b6272440b 100644 --- a/include/d/actor/d_a_obj_lv4sand.h +++ b/include/d/actor/d_a_obj_lv4sand.h @@ -46,7 +46,6 @@ public: u8 getSwbit() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5B0 */ request_of_phase_process_class mPhase; /* 0x5B8 */ J3DModel* mpModel; /* 0x5BC */ mDoExt_btkAnm* mpBtk; diff --git a/include/d/actor/d_a_obj_lv5FloorBoard.h b/include/d/actor/d_a_obj_lv5FloorBoard.h index 1dcba0ade2..e8ae0d18b2 100644 --- a/include/d/actor/d_a_obj_lv5FloorBoard.h +++ b/include/d/actor/d_a_obj_lv5FloorBoard.h @@ -38,7 +38,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x5A0 */ dCcD_Stts mCcStts; /* 0x5DC */ dCcD_Cyl mCcCyl; /* 0x718 */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_lv5IceWall.h b/include/d/actor/d_a_obj_lv5IceWall.h index c9f459e75d..e04d413249 100644 --- a/include/d/actor/d_a_obj_lv5IceWall.h +++ b/include/d/actor/d_a_obj_lv5IceWall.h @@ -42,7 +42,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x5A0 */ dCcD_Stts mCcStts; /* 0x5DC */ dCcD_Cyl mCcCyl[2]; /* 0x854 */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_lv5SwIce.h b/include/d/actor/d_a_obj_lv5SwIce.h index 8adc14f2af..9e990e3da7 100644 --- a/include/d/actor/d_a_obj_lv5SwIce.h +++ b/include/d/actor/d_a_obj_lv5SwIce.h @@ -38,7 +38,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x5A0 */ dCcD_Stts mCcStts; /* 0x5DC */ dCcD_Cyl mCcCyl; /* 0x718 */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_lv5ychndlr.h b/include/d/actor/d_a_obj_lv5ychndlr.h index 8db78c0afa..dc3a8f1ce2 100644 --- a/include/d/actor/d_a_obj_lv5ychndlr.h +++ b/include/d/actor/d_a_obj_lv5ychndlr.h @@ -26,7 +26,6 @@ public: virtual int Delete(); virtual ~daObjYchndlr_c() {} -private: /* 0x5A8 */ Mtx field_0x5a8; /* 0x5D8 */ Mtx field_0x5d8; /* 0x608 */ J3DModel* mpChandlierModel; diff --git a/include/d/actor/d_a_obj_lv6FurikoTrap.h b/include/d/actor/d_a_obj_lv6FurikoTrap.h index 8a5f17336d..5257bc540a 100644 --- a/include/d/actor/d_a_obj_lv6FurikoTrap.h +++ b/include/d/actor/d_a_obj_lv6FurikoTrap.h @@ -28,7 +28,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcSph mCcDSph; -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_lv6Lblock.h b/include/d/actor/d_a_obj_lv6Lblock.h index 0dbe6a330a..80080c702b 100644 --- a/include/d/actor/d_a_obj_lv6Lblock.h +++ b/include/d/actor/d_a_obj_lv6Lblock.h @@ -34,7 +34,6 @@ public: int getUpMax() { return fopAcM_GetParamBit(this, 8, 4); } int getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_lv6SwGate.h b/include/d/actor/d_a_obj_lv6SwGate.h index 331e7ae07d..e2844e06ee 100644 --- a/include/d/actor/d_a_obj_lv6SwGate.h +++ b/include/d/actor/d_a_obj_lv6SwGate.h @@ -38,7 +38,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x5A0 */ dCcD_Stts mCcStts; /* 0x5DC */ dCcD_Cyl mCcDCyls[12]; /* 0x14AC */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_lv6SzGate.h b/include/d/actor/d_a_obj_lv6SzGate.h index 80601341ec..db3ec0ccce 100644 --- a/include/d/actor/d_a_obj_lv6SzGate.h +++ b/include/d/actor/d_a_obj_lv6SzGate.h @@ -45,7 +45,6 @@ public: u32 getWaitTime() { return fopAcM_GetParamBit(this, 8, 8); } u32 getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* field_0x5a8; /* 0x5AC */ J3DModel* field_0x5ac; diff --git a/include/d/actor/d_a_obj_lv6Tenbin.h b/include/d/actor/d_a_obj_lv6Tenbin.h index 6cdd8e3538..429ecb58be 100644 --- a/include/d/actor/d_a_obj_lv6Tenbin.h +++ b/include/d/actor/d_a_obj_lv6Tenbin.h @@ -27,7 +27,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel1; /* 0x5AC */ J3DModel* mModel2; diff --git a/include/d/actor/d_a_obj_lv6TogeRoll.h b/include/d/actor/d_a_obj_lv6TogeRoll.h index 9fedcd5932..4fc557b8b4 100644 --- a/include/d/actor/d_a_obj_lv6TogeRoll.h +++ b/include/d/actor/d_a_obj_lv6TogeRoll.h @@ -66,7 +66,6 @@ public: static DUSK_GAME_DATA dCcD_SrcSph mCcDSph; static DUSK_GAME_DATA dCcD_SrcCps mCcDCps; -private: /* 0x05A0 */ request_of_phase_process_class mPhase; /* 0x05A8 */ J3DModel* mpModel; /* 0x05AC */ u8 mPathID; diff --git a/include/d/actor/d_a_obj_lv6elevta.h b/include/d/actor/d_a_obj_lv6elevta.h index b98ff06f58..192c568552 100644 --- a/include/d/actor/d_a_obj_lv6elevta.h +++ b/include/d/actor/d_a_obj_lv6elevta.h @@ -33,7 +33,6 @@ public: int getSw2No() { return fopAcM_GetParamBit(this, 8, 8); }; int getEvent() { return fopAcM_GetParamBit(this, 0x10, 8); }; -private: /* 0x5B8 */ Mtx mMtx1; /* 0x5E8 */ Mtx mMtx2; /* 0x618 */ J3DModel* mModel; diff --git a/include/d/actor/d_a_obj_lv7bridge.h b/include/d/actor/d_a_obj_lv7bridge.h index 938b87e08f..f53b24e708 100644 --- a/include/d/actor/d_a_obj_lv7bridge.h +++ b/include/d/actor/d_a_obj_lv7bridge.h @@ -45,7 +45,6 @@ public: void setDestroyAnmA() { field_0xa94 = 0; } void setDestroyAnmB() { field_0xa94 = 1; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ dBgS_ObjAcch mAcch; diff --git a/include/d/actor/d_a_obj_lv8KekkaiTrap.h b/include/d/actor/d_a_obj_lv8KekkaiTrap.h index aa68df0114..423e59a500 100644 --- a/include/d/actor/d_a_obj_lv8KekkaiTrap.h +++ b/include/d/actor/d_a_obj_lv8KekkaiTrap.h @@ -32,7 +32,6 @@ public: int getType() { return fopAcM_GetParamBit(this, 0, 4); } int getSwBit() { return fopAcM_GetParamBit(this, 4, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mIsSwitch; diff --git a/include/d/actor/d_a_obj_lv8Lift.h b/include/d/actor/d_a_obj_lv8Lift.h index 0b39e25e89..1fa4d4b7f6 100644 --- a/include/d/actor/d_a_obj_lv8Lift.h +++ b/include/d/actor/d_a_obj_lv8Lift.h @@ -58,9 +58,6 @@ public: void setNextPoint(); int Draw(); int Delete(); -#if TARGET_PC - friend void daL8Lift_interp_callback(bool isSimFrame, void* pUserWork); -#endif u8 getPthID() { return fopAcM_GetParamBit(this, 0, 8); } u8 getMoveSpeed() { return fopAcM_GetParamBit(this, 8, 4); } @@ -68,7 +65,6 @@ public: static DUSK_GAME_DATA f32 const mSpeed[16]; -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ mDoExt_btkAnm mBtk; diff --git a/include/d/actor/d_a_obj_lv9SwShutter.h b/include/d/actor/d_a_obj_lv9SwShutter.h index 06607f5c1c..44e0eef556 100644 --- a/include/d/actor/d_a_obj_lv9SwShutter.h +++ b/include/d/actor/d_a_obj_lv9SwShutter.h @@ -31,7 +31,6 @@ public: int getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_magLift.h b/include/d/actor/d_a_obj_magLift.h index a8f597bfef..7c15edb05e 100644 --- a/include/d/actor/d_a_obj_magLift.h +++ b/include/d/actor/d_a_obj_magLift.h @@ -49,7 +49,6 @@ public: static DUSK_GAME_DATA f32 const mSpeed[16]; -private: /* 0x5a0 */ request_of_phase_process_class mPhaseReq; /* 0x5a8 */ J3DModel* mpModel; /* 0x5ac */ u8 field_0x5ac [0x5ae - 0x5ac]; diff --git a/include/d/actor/d_a_obj_magLiftRot.h b/include/d/actor/d_a_obj_magLiftRot.h index 647fcee80a..04621f7ddc 100644 --- a/include/d/actor/d_a_obj_magLiftRot.h +++ b/include/d/actor/d_a_obj_magLiftRot.h @@ -29,7 +29,6 @@ public: virtual int Draw(); virtual int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ mDoExt_btkAnm mBtk; diff --git a/include/d/actor/d_a_obj_master_sword.h b/include/d/actor/d_a_obj_master_sword.h index fd41a8d079..e462e61481 100644 --- a/include/d/actor/d_a_obj_master_sword.h +++ b/include/d/actor/d_a_obj_master_sword.h @@ -51,7 +51,6 @@ public: static DUSK_GAME_DATA daObjMasterSword_Attr_c const mAttr; static DUSK_GAME_DATA actionFunc ActionTable[]; -// private: /* 0x568 */ J3DModel* mpModel; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ mDoExt_btkAnm mBtk; diff --git a/include/d/actor/d_a_obj_metalbox.h b/include/d/actor/d_a_obj_metalbox.h index b7f2fb10cd..10c6462547 100644 --- a/include/d/actor/d_a_obj_metalbox.h +++ b/include/d/actor/d_a_obj_metalbox.h @@ -15,7 +15,7 @@ * */ class daObjMBox_c : public dBgS_MoveBgActor { -private: +public: /* 0x5a0 */ request_of_phase_process_class mPhase; /* 0x5a8 */ J3DModel* mpModel; /* 0x5ac */ dBgS_ObjAcch mAcch; diff --git a/include/d/actor/d_a_obj_mgate.h b/include/d/actor/d_a_obj_mgate.h index 5b86c8aa17..9dc0d63114 100644 --- a/include/d/actor/d_a_obj_mgate.h +++ b/include/d/actor/d_a_obj_mgate.h @@ -39,7 +39,6 @@ public: bool checkLock() { return fopAcM_GetParamBit(this, 5, 1); } u8 checkOpen() { return fopAcM_GetParamBit(this, 8, 4); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ request_of_phase_process_class mKeyPhase; /* 0x5B0 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_mhole.h b/include/d/actor/d_a_obj_mhole.h index aa692338d2..c9818708fd 100644 --- a/include/d/actor/d_a_obj_mhole.h +++ b/include/d/actor/d_a_obj_mhole.h @@ -36,7 +36,6 @@ public: u8 getKind() { return fopAcM_GetParamBit(this, 12, 4); } u8 getSwbit() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ mDoExt_brkAnm* mpBrkAnm; diff --git a/include/d/actor/d_a_obj_mie.h b/include/d/actor/d_a_obj_mie.h index 6c95ed7f84..faa413e204 100644 --- a/include/d/actor/d_a_obj_mie.h +++ b/include/d/actor/d_a_obj_mie.h @@ -54,7 +54,7 @@ public: * */ class daObj_Mie_c : public fopAc_ac_c { -private: +public: /* 0x568 */ OBJ_MIE_HIO_CLASS* mpHIO; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ J3DModel* mModel; diff --git a/include/d/actor/d_a_obj_mirror_6pole.h b/include/d/actor/d_a_obj_mirror_6pole.h index 17cf211be3..1a53d23399 100644 --- a/include/d/actor/d_a_obj_mirror_6pole.h +++ b/include/d/actor/d_a_obj_mirror_6pole.h @@ -56,7 +56,6 @@ public: static DUSK_GAME_DATA const actionFunc ActionTable[][2]; -private: /* 0x568 */ J3DModel* mpModel; /* 0x56C */ mDoExt_bckAnm* mpBck; /* 0x570 */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_mirror_chain.h b/include/d/actor/d_a_obj_mirror_chain.h index 106272f116..e04ab228d5 100644 --- a/include/d/actor/d_a_obj_mirror_chain.h +++ b/include/d/actor/d_a_obj_mirror_chain.h @@ -47,7 +47,6 @@ public: u8 getSwitchNo() { return (fopAcM_GetParam(this) >> 8) & 0xff; } void setAnmSpeed(f32 speed) { mpBckAnm->setPlaySpeed(speed); } -private: /* 0x568 */ J3DModel* mpModel; /* 0x56C */ J3DModel* mpPortalModel; /* 0x570 */ mDoExt_bckAnm* mpBckAnm; diff --git a/include/d/actor/d_a_obj_mirror_sand.h b/include/d/actor/d_a_obj_mirror_sand.h index db27ccfaeb..ea7a8a6f30 100644 --- a/include/d/actor/d_a_obj_mirror_sand.h +++ b/include/d/actor/d_a_obj_mirror_sand.h @@ -37,7 +37,6 @@ public: dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[354]); } -private: /* 0x568 */ J3DModel* mpModel; /* 0x56C */ mDoExt_bckAnm* mpBck; /* 0x570 */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_mirror_screw.h b/include/d/actor/d_a_obj_mirror_screw.h index 482506ffc0..7cdf294f9b 100644 --- a/include/d/actor/d_a_obj_mirror_screw.h +++ b/include/d/actor/d_a_obj_mirror_screw.h @@ -65,7 +65,6 @@ public: (this->*mpActionFunc[1])(); } -private: /* 0x5A0 */ J3DModel* mpModel; /* 0x5A4 */ request_of_phase_process_class mPhaseReq; /* 0x5AC */ daObjMirrorScrew_actionFunc* mpActionFunc; diff --git a/include/d/actor/d_a_obj_nameplate.h b/include/d/actor/d_a_obj_nameplate.h index d6aa6356bb..7d5140838a 100644 --- a/include/d/actor/d_a_obj_nameplate.h +++ b/include/d/actor/d_a_obj_nameplate.h @@ -51,7 +51,6 @@ public: inline int draw(); inline int createHeap(); -private: /* 0x568 */ J3DModel* model; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ Mtx mMtx; diff --git a/include/d/actor/d_a_obj_nan.h b/include/d/actor/d_a_obj_nan.h index 708d75199e..f1c9881764 100644 --- a/include/d/actor/d_a_obj_nan.h +++ b/include/d/actor/d_a_obj_nan.h @@ -42,7 +42,6 @@ public: inline int CreateHeap(); inline int draw(); -private: /* 0x590 */ request_of_phase_process_class mPhase; /* 0x598 */ Z2Creature mCreatureSound; /* 0x628 */ mDoExt_McaMorfSO* mMorf; diff --git a/include/d/actor/d_a_obj_octhashi.h b/include/d/actor/d_a_obj_octhashi.h index 4b5ef5459a..9bb21ce9a4 100644 --- a/include/d/actor/d_a_obj_octhashi.h +++ b/include/d/actor/d_a_obj_octhashi.h @@ -33,7 +33,6 @@ public: int Draw(); int Delete(); -private: /* 0x5a0 */ cXyz field_0x5a0[8]; /* 0x600 */ cXyz field_0x600[8]; /* 0x660 */ csXyz field_0x660[8]; @@ -53,7 +52,6 @@ private: public: /* 0x6e8 */ s16 field_0x6e8; /* 0x6ec */ Z2Creature mSound; -private: /* 0x077c */ u8 field_0x77c[4]; /* 0x0780 */ J3DModel* mpModel[8]; /* 0x07a0 */ u8 field_0x7a0[0x7a4 - 0x7a0]; diff --git a/include/d/actor/d_a_obj_oiltubo.h b/include/d/actor/d_a_obj_oiltubo.h index b4ee8612bc..58aeba161d 100644 --- a/include/d/actor/d_a_obj_oiltubo.h +++ b/include/d/actor/d_a_obj_oiltubo.h @@ -36,7 +36,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl DUSK_CONST mCcDCyl; -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ J3DModel* mpBModel; diff --git a/include/d/actor/d_a_obj_onsen.h b/include/d/actor/d_a_obj_onsen.h index 64af56f1d5..43ba02bc25 100644 --- a/include/d/actor/d_a_obj_onsen.h +++ b/include/d/actor/d_a_obj_onsen.h @@ -25,7 +25,6 @@ public: u32 getType() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel[2]; /* 0x5B0 */ mDoExt_btkAnm* mpBtk; diff --git a/include/d/actor/d_a_obj_pdtile.h b/include/d/actor/d_a_obj_pdtile.h index 1dcb327acc..05e04063aa 100644 --- a/include/d/actor/d_a_obj_pdtile.h +++ b/include/d/actor/d_a_obj_pdtile.h @@ -35,7 +35,6 @@ public: u8 getType() { return fopAcM_GetParamBit(this, 0, 4); } -private: /* 0x5A8 */ Mtx mBgMtx; /* 0x5D8 */ Mtx field_0x5d8; /* 0x608 */ J3DModel* mModel; diff --git a/include/d/actor/d_a_obj_pdwall.h b/include/d/actor/d_a_obj_pdwall.h index 45e8213f39..698ac6a6bb 100644 --- a/include/d/actor/d_a_obj_pdwall.h +++ b/include/d/actor/d_a_obj_pdwall.h @@ -26,7 +26,6 @@ public: int getSwNo() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A8 */ Mtx field_0x5a8; /* 0x5D8 */ J3DModel* mpModels[10]; /* 0x600 */ Mtx field_0x600[10]; diff --git a/include/d/actor/d_a_obj_picture.h b/include/d/actor/d_a_obj_picture.h index 50d44d808b..af175f93e9 100644 --- a/include/d/actor/d_a_obj_picture.h +++ b/include/d/actor/d_a_obj_picture.h @@ -45,7 +45,6 @@ public: #endif static DUSK_GAME_DATA dCcD_SrcCyl s_CcDCyl_pic_at; -private: /* 0x574 */ dCcD_Stts field_0x574; /* 0x5B0 */ dCcD_Stts field_0x5b0; /* 0x5EC */ dCcD_Stts field_0x5ec; diff --git a/include/d/actor/d_a_obj_pleaf.h b/include/d/actor/d_a_obj_pleaf.h index 5e7a5cde1c..12f14a2bad 100644 --- a/include/d/actor/d_a_obj_pleaf.h +++ b/include/d/actor/d_a_obj_pleaf.h @@ -44,7 +44,7 @@ public: * */ class daObj_Pleaf_c : public fopAc_ac_c { -private: +public: /* 0x568 */ OBJ_PLEAF_HIO_CLASS* mpHIO; /* 0x56C */ request_of_phase_process_class mPhaseReq; /* 0x574 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_poCandle.h b/include/d/actor/d_a_obj_poCandle.h index defcdb7858..c76356d2df 100644 --- a/include/d/actor/d_a_obj_poCandle.h +++ b/include/d/actor/d_a_obj_poCandle.h @@ -48,7 +48,6 @@ public: u8 getIDNum() { return mIDNum; } void setFireFlag(u8 flag) { mFireFlag = flag; } -private: /* 0x5B8 */ request_of_phase_process_class mPhase; /* 0x5C0 */ J3DModel* mpModel; /* 0x5C4 */ cXyz mFirePos; diff --git a/include/d/actor/d_a_obj_poFire.h b/include/d/actor/d_a_obj_poFire.h index b8c9198b7c..10a91cbf34 100644 --- a/include/d/actor/d_a_obj_poFire.h +++ b/include/d/actor/d_a_obj_poFire.h @@ -68,7 +68,6 @@ public: u8 getMoveEnd() { return mMoveEnd; } u32 getID() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x584 */ request_of_phase_process_class mPhase; /* 0x58C */ int mStaffID; /* 0x590 */ s16 unk590; diff --git a/include/d/actor/d_a_obj_poTbox.h b/include/d/actor/d_a_obj_poTbox.h index 714f7338d3..44b18117e6 100644 --- a/include/d/actor/d_a_obj_poTbox.h +++ b/include/d/actor/d_a_obj_poTbox.h @@ -33,7 +33,6 @@ public: int getSw() { return fopAcM_GetParamBit(this, 0, 8); } int getStat() { return fopAcM_GetParamBit(this, 8, 4); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpBoxModel; /* 0x5AC */ J3DModel* mpAnimModel; diff --git a/include/d/actor/d_a_obj_prop.h b/include/d/actor/d_a_obj_prop.h index d61025630b..157f8dde6b 100644 --- a/include/d/actor/d_a_obj_prop.h +++ b/include/d/actor/d_a_obj_prop.h @@ -26,7 +26,6 @@ public: u32 getNameArg_0() { return fopAcM_GetParamBit(this, 0, 4); } u32 getArg_0() { return fopAcM_GetParamBit(this, 4, 4); } -private: /* 0x574 */ J3DModel* mpModel; /* 0x578 */ u8 mArg; }; diff --git a/include/d/actor/d_a_obj_pumpkin.h b/include/d/actor/d_a_obj_pumpkin.h index 9dd015c00e..74ba8f3026 100644 --- a/include/d/actor/d_a_obj_pumpkin.h +++ b/include/d/actor/d_a_obj_pumpkin.h @@ -56,7 +56,7 @@ public: * */ class daObj_Pumpkin_c : public fopAc_ac_c { -private: +public: /* 0x568 */ OBJ_PUMPKIN_HIO_CLASS* mpHIO; /* 0x56C */ request_of_phase_process_class mPhaseReq; /* 0x574 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_rcircle.h b/include/d/actor/d_a_obj_rcircle.h index 43811a0702..5ef1324f2c 100644 --- a/include/d/actor/d_a_obj_rcircle.h +++ b/include/d/actor/d_a_obj_rcircle.h @@ -25,7 +25,6 @@ public: int getSwBit() { return fopAcM_GetParamBit(this, 0, 8) & 0xFF; } -private: /* 0x574 */ mDoExt_brkAnm mBrk; /* 0x58C */ mDoExt_btkAnm mBtk; /* 0x5A4 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_rfHole.h b/include/d/actor/d_a_obj_rfHole.h index d053d6800c..00a873229c 100644 --- a/include/d/actor/d_a_obj_rfHole.h +++ b/include/d/actor/d_a_obj_rfHole.h @@ -42,7 +42,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_rope_bridge.h b/include/d/actor/d_a_obj_rope_bridge.h index 90532c9ba7..8eababd778 100644 --- a/include/d/actor/d_a_obj_rope_bridge.h +++ b/include/d/actor/d_a_obj_rope_bridge.h @@ -44,7 +44,6 @@ public: u8 getSwbit2() { return fopAcM_GetParamBit(this, 24, 8); } u8 getType() { return fopAcM_GetParamBit(this, 16, 2); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ request_of_phase_process_class mRopePhase; /* 0x5B0 */ J3DModel* mpBrgModel; diff --git a/include/d/actor/d_a_obj_rotTrap.h b/include/d/actor/d_a_obj_rotTrap.h index 9534358f4a..069bfe285c 100644 --- a/include/d/actor/d_a_obj_rotTrap.h +++ b/include/d/actor/d_a_obj_rotTrap.h @@ -37,7 +37,6 @@ public: static DUSK_GAME_DATA const dCcD_SrcGObjInf mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x05A0 */ request_of_phase_process_class mPhase; /* 0x05A8 */ J3DModel* mpModel; /* 0x05AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_roten.h b/include/d/actor/d_a_obj_roten.h index 9b4b17a19d..ea78731b77 100644 --- a/include/d/actor/d_a_obj_roten.h +++ b/include/d/actor/d_a_obj_roten.h @@ -25,7 +25,6 @@ public: char DUSK_CONST* getResName(); virtual ~daObj_Roten_c(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ JPABaseEmitter* mEmitters[2]; diff --git a/include/d/actor/d_a_obj_rstair.h b/include/d/actor/d_a_obj_rstair.h index 053692ba9e..6c31ff4648 100644 --- a/include/d/actor/d_a_obj_rstair.h +++ b/include/d/actor/d_a_obj_rstair.h @@ -58,7 +58,6 @@ public: u8 getEventID() { return fopAcM_GetParamBit(this, 0, 8); } void setAction(u8 action) { mAction = action; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ J3DModel* mWaterModels[2]; diff --git a/include/d/actor/d_a_obj_saidan.h b/include/d/actor/d_a_obj_saidan.h index ef38588f00..f421cf55ad 100644 --- a/include/d/actor/d_a_obj_saidan.h +++ b/include/d/actor/d_a_obj_saidan.h @@ -35,7 +35,6 @@ public: virtual int Draw(); virtual int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_sakuita.h b/include/d/actor/d_a_obj_sakuita.h index e9e4e82120..621084a592 100644 --- a/include/d/actor/d_a_obj_sakuita.h +++ b/include/d/actor/d_a_obj_sakuita.h @@ -45,7 +45,6 @@ public: static DUSK_GAME_DATA daObjSakuita_Attr_c const M_attr; -private: /* 0x568 */ J3DModel* mModel; /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ Mtx mMtx; diff --git a/include/d/actor/d_a_obj_sakuita_rope.h b/include/d/actor/d_a_obj_sakuita_rope.h index dfbed5af0e..37df115ea9 100644 --- a/include/d/actor/d_a_obj_sakuita_rope.h +++ b/include/d/actor/d_a_obj_sakuita_rope.h @@ -82,7 +82,6 @@ public: static DUSK_GAME_DATA daObjItaRope_Attr_c const M_attr; -private: /* 0x570 */ mDoExt_3DlineMat1_c mLineMat; /* 0x5A4 */ request_of_phase_process_class mPhase; /* 0x5AC */ Mtx mMtx; diff --git a/include/d/actor/d_a_obj_scannon.h b/include/d/actor/d_a_obj_scannon.h index 0b1a79e18f..e376f55d40 100644 --- a/include/d/actor/d_a_obj_scannon.h +++ b/include/d/actor/d_a_obj_scannon.h @@ -83,7 +83,6 @@ public: static DUSK_GAME_DATA void (daSCannon_c::*DUSK_CONST s_demoExeProc_FireFirst[][2])(); static DUSK_GAME_DATA void (daSCannon_c::*DUSK_CONST s_demoExeProc_FireSecond[][2])(); -private: /* 0x574 */ request_of_phase_process_class mZevPhase; /* 0x57C */ request_of_phase_process_class mPtlPhase; /* 0x584 */ mDoExt_bckAnm mBck; diff --git a/include/d/actor/d_a_obj_scannon_crs.h b/include/d/actor/d_a_obj_scannon_crs.h index d7eb7c8121..12369f58fd 100644 --- a/include/d/actor/d_a_obj_scannon_crs.h +++ b/include/d/actor/d_a_obj_scannon_crs.h @@ -51,7 +51,6 @@ public: static DUSK_GAME_DATA void (daSCannonCrs_c::*s_exeProc[])(daMidna_c*); -private: /* 0x574 */ cXyz mPortalWaitPos; /* 0x580 */ dMsgFlow_c mMsgFlow; /* 0x5CC */ mDoExt_bckAnm mBck; diff --git a/include/d/actor/d_a_obj_scannon_ten.h b/include/d/actor/d_a_obj_scannon_ten.h index 7bc5b24ed8..ce6c840cfe 100644 --- a/include/d/actor/d_a_obj_scannon_ten.h +++ b/include/d/actor/d_a_obj_scannon_ten.h @@ -60,7 +60,6 @@ public: static DUSK_GAME_DATA const ExeProc s_exeProc[]; static DUSK_GAME_DATA const ExeProc s_demoExeProc[][2]; -private: /* 0x574 */ J3DModel* mpModel; /* 0x578 */ mDoExt_bckAnm mBck; /* 0x594 */ dBgW* mpBgW; diff --git a/include/d/actor/d_a_obj_sekidoor.h b/include/d/actor/d_a_obj_sekidoor.h index 46da5cb8a9..8f2791a673 100644 --- a/include/d/actor/d_a_obj_sekidoor.h +++ b/include/d/actor/d_a_obj_sekidoor.h @@ -59,7 +59,6 @@ public: void open() { mOpening = true; } bool chkDestroy() { return (mDestroyed == true); } -private: /* 0x5A0 */ OBJ_SEKIDOOR_HIO_CLASS* mpHIO; /* 0x5A4 */ request_of_phase_process_class mPhaseReq; /* 0x5AC */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_sekizo.h b/include/d/actor/d_a_obj_sekizo.h index 0f0847dc24..2011417a96 100644 --- a/include/d/actor/d_a_obj_sekizo.h +++ b/include/d/actor/d_a_obj_sekizo.h @@ -50,7 +50,6 @@ public: void initBaseMtx(); void setBaseMtx(); -private: /* 0x5A0 */ OBJ_SEKIZO_HIO_CLASS* mpHIO; /* 0x5A4 */ request_of_phase_process_class mPhaseReq; /* 0x5AC */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_shield.h b/include/d/actor/d_a_obj_shield.h index 11b4a2668d..248c26be5b 100644 --- a/include/d/actor/d_a_obj_shield.h +++ b/include/d/actor/d_a_obj_shield.h @@ -59,7 +59,6 @@ public: void setStatus(u8 status) { mStatus = status; } void setAction(u8 action) { mAction = action; } -private: /* 0x92C */ fpc_ProcID mItemId; /* 0x930 */ int mRotAngleCoeff; /* 0x934 */ u8 mStatus; diff --git a/include/d/actor/d_a_obj_sm_door.h b/include/d/actor/d_a_obj_sm_door.h index bec15a0ae8..bd6553c0d2 100644 --- a/include/d/actor/d_a_obj_sm_door.h +++ b/include/d/actor/d_a_obj_sm_door.h @@ -31,7 +31,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ bool mUseAlphaModel; /* 0x5A1 */ u8 field_0x5a1[0x5AC - 0x5A1]; /* 0x5AC */ s16 mMode; @@ -54,5 +53,4 @@ private: STATIC_ASSERT(sizeof(daObjSM_DOOR_c) == 0x858); - #endif /* D_A_OBJ_SM_DOOR_H */ diff --git a/include/d/actor/d_a_obj_smallkey.h b/include/d/actor/d_a_obj_smallkey.h index 2becc9bae2..7bf38de4af 100644 --- a/include/d/actor/d_a_obj_smallkey.h +++ b/include/d/actor/d_a_obj_smallkey.h @@ -14,7 +14,7 @@ * */ class daKey_c : public daItemBase_c { -private: +public: void seStartTwinkle_private(u32); public: diff --git a/include/d/actor/d_a_obj_smgdoor.h b/include/d/actor/d_a_obj_smgdoor.h index aad2800ece..b22734b3e3 100644 --- a/include/d/actor/d_a_obj_smgdoor.h +++ b/include/d/actor/d_a_obj_smgdoor.h @@ -53,7 +53,6 @@ public: virtual int Draw(); virtual int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel[2]; /* 0x5B0 */ s16 field_0x5b0; diff --git a/include/d/actor/d_a_obj_smoke.h b/include/d/actor/d_a_obj_smoke.h index 61a3b56a51..abd172afbe 100644 --- a/include/d/actor/d_a_obj_smoke.h +++ b/include/d/actor/d_a_obj_smoke.h @@ -24,7 +24,6 @@ public: int execute(); int _delete(); -private: /* 0x568 */ JPABaseEmitter* mParticle; }; diff --git a/include/d/actor/d_a_obj_smtile.h b/include/d/actor/d_a_obj_smtile.h index 735914f0a0..c85000e7b8 100644 --- a/include/d/actor/d_a_obj_smtile.h +++ b/include/d/actor/d_a_obj_smtile.h @@ -41,7 +41,7 @@ public: * */ class daObj_SMTile_c : public fopAc_ac_c { -private: +public: /* 0x568 */ mDoExt_brkAnm mBrk; /* 0x580 */ OBJ_SMTILE_HIO_CLASS* mpHIO; /* 0x584 */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_smw_stone.h b/include/d/actor/d_a_obj_smw_stone.h index 02f58284ff..05913cd339 100644 --- a/include/d/actor/d_a_obj_smw_stone.h +++ b/include/d/actor/d_a_obj_smw_stone.h @@ -29,7 +29,6 @@ public: u8 getSwBit0() { return fopAcM_GetParamBit(this, 0, 8); } void deleteStone() { mDelete = true; } -private: /* 0x574 */ cBgS_PolyInfo mPolyInfo; /* 0x584 */ J3DModel* mpModel; /* 0x588 */ dBgW* mpBgW; diff --git a/include/d/actor/d_a_obj_snowEffTag.h b/include/d/actor/d_a_obj_snowEffTag.h index 748106cc81..bc995c749c 100644 --- a/include/d/actor/d_a_obj_snowEffTag.h +++ b/include/d/actor/d_a_obj_snowEffTag.h @@ -23,7 +23,6 @@ public: int getArg0() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x568 */ u8 field_0x568; /* 0x569 */ bool mPlayedSound; /* 0x56C */ f32 mMaxSize; diff --git a/include/d/actor/d_a_obj_snow_soup.h b/include/d/actor/d_a_obj_snow_soup.h index 91c23c8f85..3b96cd566f 100644 --- a/include/d/actor/d_a_obj_snow_soup.h +++ b/include/d/actor/d_a_obj_snow_soup.h @@ -28,7 +28,6 @@ public: void createSmkEmtChange(int); void deleteSmkEmtChange(int); -private: /* 0x574 */ cXyz mFirewoodPos; /* 0x580 */ cXyz mBubblePos; /* 0x58C */ JPABaseEmitter* mpSmkEmtCommon[4]; diff --git a/include/d/actor/d_a_obj_spinLift.h b/include/d/actor/d_a_obj_spinLift.h index 548df3f910..d2f0a50b03 100644 --- a/include/d/actor/d_a_obj_spinLift.h +++ b/include/d/actor/d_a_obj_spinLift.h @@ -40,7 +40,6 @@ public: u8 getTimer() { return (current.angle.x & 0xff00) >> 8; } int getEndSw() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 field_0x5ac; diff --git a/include/d/actor/d_a_obj_ss_base.h b/include/d/actor/d_a_obj_ss_base.h index 4d275a43cd..2101dd3237 100644 --- a/include/d/actor/d_a_obj_ss_base.h +++ b/include/d/actor/d_a_obj_ss_base.h @@ -18,7 +18,6 @@ public: u16 getValueNumber() { return mValueNum; } void setValueNumber(u16 valueNum) { mValueNum = valueNum; } -private: /* 0x56C */ void* mpParentPtr; /* 0x570 */ u16 field_0x570; /* 0x572 */ u16 mFlowNodeNum; diff --git a/include/d/actor/d_a_obj_ss_drink.h b/include/d/actor/d_a_obj_ss_drink.h index e49644f6b6..c4c0a1a72b 100644 --- a/include/d/actor/d_a_obj_ss_drink.h +++ b/include/d/actor/d_a_obj_ss_drink.h @@ -50,7 +50,6 @@ public: static DUSK_GAME_DATA dCcD_SrcGObjInf const mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl DUSK_CONST mCcDCyl; -private: /* 0x578 */ mDoExt_btpAnm* mpBtpAnm; /* 0x57C */ mDoExt_brkAnm* mpBrkAnm; /* 0x580 */ u8 field_0x580; diff --git a/include/d/actor/d_a_obj_ss_item.h b/include/d/actor/d_a_obj_ss_item.h index cd219a1832..59f8f56407 100644 --- a/include/d/actor/d_a_obj_ss_item.h +++ b/include/d/actor/d_a_obj_ss_item.h @@ -49,7 +49,6 @@ public: static DUSK_GAME_DATA dCcD_SrcGObjInf const mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl DUSK_CONST mCcDCyl; -private: /* 0x578 */ request_of_phase_process_class mPhase; /* 0x580 */ J3DModel* mpModel; /* 0x584 */ dBgS_ObjAcch mAcch; diff --git a/include/d/actor/d_a_obj_stairBlock.h b/include/d/actor/d_a_obj_stairBlock.h index 3d3b8d5283..1b04d0bcc1 100644 --- a/include/d/actor/d_a_obj_stairBlock.h +++ b/include/d/actor/d_a_obj_stairBlock.h @@ -26,7 +26,6 @@ public: static DUSK_GAME_DATA dCcD_SrcGObjInf const mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcSph mCcDSph; -private: /* 0x056C */ request_of_phase_process_class mPhaseReq; /* 0x0574 */ J3DModel* mpModel[10]; /* 0x059C */ mDoExt_bckAnm mBckAnm[10]; diff --git a/include/d/actor/d_a_obj_stick.h b/include/d/actor/d_a_obj_stick.h index ebc08e08b8..ce72273254 100644 --- a/include/d/actor/d_a_obj_stick.h +++ b/include/d/actor/d_a_obj_stick.h @@ -45,7 +45,7 @@ public: * */ class daObj_Stick_c : public fopAc_ac_c { -private: +public: /* 0x568 */ OBJ_STICK_HIO_CLASS* mpHIO; /* 0x56c */ request_of_phase_process_class mPhase; /* 0x574 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_stone.h b/include/d/actor/d_a_obj_stone.h index b5f4da8c39..dfeb11f539 100644 --- a/include/d/actor/d_a_obj_stone.h +++ b/include/d/actor/d_a_obj_stone.h @@ -56,7 +56,6 @@ public: int draw(); int _delete(); -private: /* 0x0568 */ request_of_phase_process_class mPhase; /* 0x0570 */ J3DModel* mpModel; /* 0x0574 */ dBgS_ObjAcch mChkObj; diff --git a/include/d/actor/d_a_obj_stoneMark.h b/include/d/actor/d_a_obj_stoneMark.h index fbe56ce31d..04540fd9c7 100644 --- a/include/d/actor/d_a_obj_stoneMark.h +++ b/include/d/actor/d_a_obj_stoneMark.h @@ -23,7 +23,6 @@ public: int draw(); int _delete(); -private: /* 0x0568 */ request_of_phase_process_class mpPhase; /* 0x0570 */ TGXTexObj mTexObj; /* 0x0590 */ dBgS_ObjAcch mObjAcch; diff --git a/include/d/actor/d_a_obj_stopper.h b/include/d/actor/d_a_obj_stopper.h index 9a100c754b..62eda0ae39 100644 --- a/include/d/actor/d_a_obj_stopper.h +++ b/include/d/actor/d_a_obj_stopper.h @@ -59,7 +59,6 @@ public: u8 getSwNo() { return fopAcM_GetParamBit(this, 0, 8); } u8 getSwNo2() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x5A0 */ u8 field_0x5a0[8]; // unused /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ dBgS_ObjAcch mAcch; diff --git a/include/d/actor/d_a_obj_stopper2.h b/include/d/actor/d_a_obj_stopper2.h index 7ec86cb818..ccae3af298 100644 --- a/include/d/actor/d_a_obj_stopper2.h +++ b/include/d/actor/d_a_obj_stopper2.h @@ -35,7 +35,6 @@ public: u32 getSwbit() { return fopAcM_GetParamBit(this, 0, 8); } void setAction(u8 i_action) { mAction = i_action; } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ f32 mOffsetY; diff --git a/include/d/actor/d_a_obj_suisya.h b/include/d/actor/d_a_obj_suisya.h index c92146f5af..59067d84a4 100644 --- a/include/d/actor/d_a_obj_suisya.h +++ b/include/d/actor/d_a_obj_suisya.h @@ -20,7 +20,6 @@ public: int CreateHeap(); int create(); -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ u32 mShadowKey; diff --git a/include/d/actor/d_a_obj_swBallA.h b/include/d/actor/d_a_obj_swBallA.h index 1785565a4e..910c031050 100644 --- a/include/d/actor/d_a_obj_swBallA.h +++ b/include/d/actor/d_a_obj_swBallA.h @@ -41,7 +41,6 @@ public: void onFlag(u8 idx, u8 val) { field_0x5af[idx] |= val; } u8 checkFlag(u8 idx, u8 val) { return field_0x5af[idx] & val; } -private: /* 0x57C */ request_of_phase_process_class field_0x57c; /* 0x584 */ J3DModel* mModel; /* 0x588 */ mDoExt_btkAnm* field_0x588; diff --git a/include/d/actor/d_a_obj_swBallB.h b/include/d/actor/d_a_obj_swBallB.h index 31a7971232..31a8f4210e 100644 --- a/include/d/actor/d_a_obj_swBallB.h +++ b/include/d/actor/d_a_obj_swBallB.h @@ -44,7 +44,6 @@ public: void setLightOn() { field_0x5ac = 1; } void setLightOff() { field_0x5ad = 1; } -private: /* 0x57C */ request_of_phase_process_class field_0x57c; /* 0x584 */ J3DModel* mModel; /* 0x588 */ mDoExt_btkAnm* field_0x588; diff --git a/include/d/actor/d_a_obj_swLight.h b/include/d/actor/d_a_obj_swLight.h index 1a9fc50cca..9d84030e96 100644 --- a/include/d/actor/d_a_obj_swLight.h +++ b/include/d/actor/d_a_obj_swLight.h @@ -47,7 +47,6 @@ public: u8 getOnTime() { return fopAcM_GetParamBit(this, 16, 8); } u8 getEvent() { return (u8)field_0xb22; } -private: /* 0x5B0 */ request_of_phase_process_class mPhase; /* 0x5B8 */ J3DModel* mpModel; /* 0x6F4 */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_swchain.h b/include/d/actor/d_a_obj_swchain.h index 270fb8f5ab..8bd68b4478 100644 --- a/include/d/actor/d_a_obj_swchain.h +++ b/include/d/actor/d_a_obj_swchain.h @@ -80,7 +80,6 @@ public: return FALSE; } -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ J3DModelData* mChainModelData; diff --git a/include/d/actor/d_a_obj_swhang.h b/include/d/actor/d_a_obj_swhang.h index 097da890b3..37ddbb3ea2 100644 --- a/include/d/actor/d_a_obj_swhang.h +++ b/include/d/actor/d_a_obj_swhang.h @@ -78,7 +78,6 @@ public: u16 checkFlag(u16 flag) { return mFlags & flag; } s16 getAngleY() { return shape_angle.y; } -private: /* 0x5B0 */ request_of_phase_process_class mPhase; /* 0x5B8 */ J3DModel* mpModel; /* 0x5BC */ dCcD_Stts mCcStts; diff --git a/include/d/actor/d_a_obj_sword.h b/include/d/actor/d_a_obj_sword.h index 163175ddec..63bc7d576a 100644 --- a/include/d/actor/d_a_obj_sword.h +++ b/include/d/actor/d_a_obj_sword.h @@ -31,7 +31,6 @@ public: u8 getSwbit() { return fopAcM_GetParamBit(this, 8, 8); } void setStatus(u8 status) { mStatus = status; } -private: /* 0x92c */ u32 field_0x92c; /* 0x930 */ fpc_ProcID mProcID; /* 0x934 */ u8 mStatus; diff --git a/include/d/actor/d_a_obj_syRock.h b/include/d/actor/d_a_obj_syRock.h index 2c881585ed..3fa10456f8 100644 --- a/include/d/actor/d_a_obj_syRock.h +++ b/include/d/actor/d_a_obj_syRock.h @@ -46,7 +46,6 @@ public: static DUSK_GAME_DATA dCcD_SrcGObjInf const mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x5B8 */ request_of_phase_process_class mPhase; /* 0x5C0 */ J3DModel* mpModels[2]; /* 0x5C8 */ u8 mMode; diff --git a/include/d/actor/d_a_obj_szbridge.h b/include/d/actor/d_a_obj_szbridge.h index eff6a17a9c..8702506280 100644 --- a/include/d/actor/d_a_obj_szbridge.h +++ b/include/d/actor/d_a_obj_szbridge.h @@ -25,7 +25,6 @@ public: int Delete(); virtual ~daObjSZbridge_c() {} -private: /* 0x5A8 */ Mtx mBgMtx; /* 0x5D8 */ Mtx field_0x5d8; /* 0x608 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_obj_table.h b/include/d/actor/d_a_obj_table.h index cea036783c..44de1d1a47 100644 --- a/include/d/actor/d_a_obj_table.h +++ b/include/d/actor/d_a_obj_table.h @@ -27,7 +27,6 @@ public: s16 getMessageNo() { return fopAcM_GetParam(this) & 0xFFFF; } -private: /* 0x5A0 */ J3DModel* mpModel; /* 0x5A4 */ request_of_phase_process_class mPhaseReq; /* 0x5AC */ dMsgFlow_c mMsgFlow; diff --git a/include/d/actor/d_a_obj_takaraDai.h b/include/d/actor/d_a_obj_takaraDai.h index b745683dd6..657e032bcc 100644 --- a/include/d/actor/d_a_obj_takaraDai.h +++ b/include/d/actor/d_a_obj_takaraDai.h @@ -26,7 +26,6 @@ public: u32 getSwBit() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpBaseModel; /* 0x5AC */ J3DModel* mpTopModel; diff --git a/include/d/actor/d_a_obj_tgake.h b/include/d/actor/d_a_obj_tgake.h index 1871794df9..6862ad4979 100644 --- a/include/d/actor/d_a_obj_tgake.h +++ b/include/d/actor/d_a_obj_tgake.h @@ -28,7 +28,6 @@ public: u16 getEventBit2() { return fopAcM_GetParamBit(this, 10, 10); } u8 getType() { return fopAcM_GetParamBit(this, 0x14, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u16 mEventBit1; diff --git a/include/d/actor/d_a_obj_thashi.h b/include/d/actor/d_a_obj_thashi.h index 3796ee0629..1934b57c56 100644 --- a/include/d/actor/d_a_obj_thashi.h +++ b/include/d/actor/d_a_obj_thashi.h @@ -25,8 +25,6 @@ public: int Draw(); int Delete(); -// private: - /* 0x5A0 */ dBgW* unk5A0; /* 0x5A4 */ u32 unk5A4; /* 0x5A8 */ u32 unk5A8; diff --git a/include/d/actor/d_a_obj_timeFire.h b/include/d/actor/d_a_obj_timeFire.h index 62fb3460d9..0b7a5ab5ec 100644 --- a/include/d/actor/d_a_obj_timeFire.h +++ b/include/d/actor/d_a_obj_timeFire.h @@ -28,7 +28,6 @@ public: u8 getEndTime() { return fopAcM_GetParamBit(this, 16, 8); } u8 getScale() { return fopAcM_GetParamBit(this, 24, 8); } -private: /* 0x568 */ u8 field_0x568; /* 0x569 */ u8 field_0x569; /* 0x56A */ u8 mStartTime; diff --git a/include/d/actor/d_a_obj_timer.h b/include/d/actor/d_a_obj_timer.h index 120a23b3a9..5778bf5ca1 100644 --- a/include/d/actor/d_a_obj_timer.h +++ b/include/d/actor/d_a_obj_timer.h @@ -54,7 +54,6 @@ class Act_c : public fopAc_ac_c { return daObj::PrmAbstract(this, PRM_2, PRM_24); } - private: /* 0x568 */ int field_0x568; /* 0x56C */ int field_0x56c; /* 0x570 */ u8 field_0x570; diff --git a/include/d/actor/d_a_obj_tmoon.h b/include/d/actor/d_a_obj_tmoon.h index 4bf3c3620c..2992316782 100644 --- a/include/d/actor/d_a_obj_tmoon.h +++ b/include/d/actor/d_a_obj_tmoon.h @@ -25,7 +25,6 @@ public: u32 getEventBit1() { return fopAcM_GetParamBit(this, 0, 10); } u32 getEventBit2() { return fopAcM_GetParamBit(this, 10, 10); } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ u16 field_0x574; diff --git a/include/d/actor/d_a_obj_toaru_maki.h b/include/d/actor/d_a_obj_toaru_maki.h index 4f7e8d2645..d980f9410f 100644 --- a/include/d/actor/d_a_obj_toaru_maki.h +++ b/include/d/actor/d_a_obj_toaru_maki.h @@ -25,7 +25,6 @@ public: u32 getNameArg_0() { return fopAcM_GetParamBit(this, 0, 4); } -private: /* 0x574 */ J3DModel* mpModel; /* 0x578 */ dBgW* mpBgW; /* 0x57C */ Mtx mMtx; diff --git a/include/d/actor/d_a_obj_togeTrap.h b/include/d/actor/d_a_obj_togeTrap.h index 4055d6a42e..a67451ea0a 100644 --- a/include/d/actor/d_a_obj_togeTrap.h +++ b/include/d/actor/d_a_obj_togeTrap.h @@ -47,7 +47,6 @@ public: static DUSK_GAME_DATA dCcD_SrcGObjInf const mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcCyl mCcDCyl; -private: /* 0x5A0 */ dCcD_Stts mStts; /* 0x5DC */ dCcD_Cyl mCyls[4]; /* 0xACC */ request_of_phase_process_class mPhase; diff --git a/include/d/actor/d_a_obj_tombo.h b/include/d/actor/d_a_obj_tombo.h index 5dfdcb6451..288d35611e 100644 --- a/include/d/actor/d_a_obj_tombo.h +++ b/include/d/actor/d_a_obj_tombo.h @@ -48,7 +48,6 @@ public: inline int Draw(); -private: /* 0x590 */ dCcD_Stts mStts; /* 0x5CC */ dCcD_Sph mSph; /* 0x704 */ daPy_boomerangMove_c mBoomerangMove; diff --git a/include/d/actor/d_a_obj_tornado.h b/include/d/actor/d_a_obj_tornado.h index 30e8e2c1e3..c3330d73f9 100644 --- a/include/d/actor/d_a_obj_tornado.h +++ b/include/d/actor/d_a_obj_tornado.h @@ -30,7 +30,6 @@ public: u8 getTimer() { return fopAcM_GetParamBit(this, 0, 8); } u8 getSwbit() { return fopAcM_GetParamBit(this, 0x18, 8); } -private: /* 0x568 */ dCcD_Stts mStts; /* 0x5A4 */ dCcD_Cps mCps; /* 0x6E8 */ cM3dGCpsS mWindCps; diff --git a/include/d/actor/d_a_obj_twGate.h b/include/d/actor/d_a_obj_twGate.h index 683a7f18fb..0a00c2b834 100644 --- a/include/d/actor/d_a_obj_twGate.h +++ b/include/d/actor/d_a_obj_twGate.h @@ -23,7 +23,6 @@ public: int getGateType() { return fopAcM_GetParamBit(this, 4, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel; /* 0x5AC */ mDoExt_btkAnm mBtk; diff --git a/include/d/actor/d_a_obj_vground.h b/include/d/actor/d_a_obj_vground.h index fed893aef1..bd9e266ebf 100644 --- a/include/d/actor/d_a_obj_vground.h +++ b/include/d/actor/d_a_obj_vground.h @@ -24,7 +24,6 @@ public: u32 getSwbit() { return (u8) fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ mDoExt_btkAnm* mpBtk; diff --git a/include/d/actor/d_a_obj_volcball.h b/include/d/actor/d_a_obj_volcball.h index ddd299a26e..cb1e895e8f 100644 --- a/include/d/actor/d_a_obj_volcball.h +++ b/include/d/actor/d_a_obj_volcball.h @@ -113,7 +113,6 @@ public: void setAction(u8 i_action) { mMode = i_action; } -private: /* 0x057C */ request_of_phase_process_class mPhase; /* 0x0584 */ dBgS_AcchCir mAcchCir; /* 0x05C4 */ dCcD_Stts mCcStts; diff --git a/include/d/actor/d_a_obj_volcbom.h b/include/d/actor/d_a_obj_volcbom.h index 17f9db911d..60401dca2d 100644 --- a/include/d/actor/d_a_obj_volcbom.h +++ b/include/d/actor/d_a_obj_volcbom.h @@ -84,7 +84,6 @@ public: u8 getArg0() { return fopAcM_GetParamBit(this, 16, 8); } u16 getMsgID() { return mMsgID; } -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mModel1; /* 0x5AC */ mDoExt_btkAnm* mBtk1; diff --git a/include/d/actor/d_a_obj_waterGate.h b/include/d/actor/d_a_obj_waterGate.h index 66a076be78..ae81773aec 100644 --- a/include/d/actor/d_a_obj_waterGate.h +++ b/include/d/actor/d_a_obj_waterGate.h @@ -33,7 +33,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ request_of_phase_process_class mPhase; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 mMode; diff --git a/include/d/actor/d_a_obj_waterPillar.h b/include/d/actor/d_a_obj_waterPillar.h index 19b55971e8..987abc3923 100644 --- a/include/d/actor/d_a_obj_waterPillar.h +++ b/include/d/actor/d_a_obj_waterPillar.h @@ -76,7 +76,6 @@ public: mStartStalactiteShake = false; } -private: /* 0x584 */ request_of_phase_process_class mPhase; /* 0x58C */ J3DModel* mpModel; /* 0x590 */ mDoExt_btkAnm mVTexScrollAnm; diff --git a/include/d/actor/d_a_obj_waterfall.h b/include/d/actor/d_a_obj_waterfall.h index e7c143910d..7a69938455 100644 --- a/include/d/actor/d_a_obj_waterfall.h +++ b/include/d/actor/d_a_obj_waterfall.h @@ -24,7 +24,6 @@ public: void push_player(); int draw(); int _delete(); -private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ u8 pad[4]; /* 0x574 */ dCcD_Stts mCylColliderStts; diff --git a/include/d/actor/d_a_obj_wchain.h b/include/d/actor/d_a_obj_wchain.h index b9f791d058..d9377d327a 100644 --- a/include/d/actor/d_a_obj_wchain.h +++ b/include/d/actor/d_a_obj_wchain.h @@ -57,7 +57,6 @@ public: mDown = true; } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpHandleModel; /* 0x574 */ J3DModelData* mpChainModelData; diff --git a/include/d/actor/d_a_obj_wdStick.h b/include/d/actor/d_a_obj_wdStick.h index 08bc2a1faf..e68f5a4a8e 100644 --- a/include/d/actor/d_a_obj_wdStick.h +++ b/include/d/actor/d_a_obj_wdStick.h @@ -53,7 +53,6 @@ public: static DUSK_GAME_DATA dCcD_SrcGObjInf const mCcDObjInfo; static DUSK_GAME_DATA dCcD_SrcSph mCcDSph; -private: /* 0x56C */ request_of_phase_process_class mPhase; /* 0x574 */ J3DModel* mModel; /* 0x578 */ u8 mMode; diff --git a/include/d/actor/d_a_obj_well_cover.h b/include/d/actor/d_a_obj_well_cover.h index b25ad59abe..f4fb2250d0 100644 --- a/include/d/actor/d_a_obj_well_cover.h +++ b/include/d/actor/d_a_obj_well_cover.h @@ -29,7 +29,6 @@ public: u8 getSwNo() { return fopAcM_GetParam(this); } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ u8 field_0x5AC[0x5B0 - 0x5AC]; diff --git a/include/d/actor/d_a_obj_wind_stone.h b/include/d/actor/d_a_obj_wind_stone.h index f5e52dfc94..107cecc92c 100644 --- a/include/d/actor/d_a_obj_wind_stone.h +++ b/include/d/actor/d_a_obj_wind_stone.h @@ -36,7 +36,6 @@ public: u32 getSwBit2() { return fopAcM_GetParamBit(this, 8, 8); } u32 getDelEveFlgId() { return fopAcM_GetParamBit(this, 0x10, 0x10); } -private: /* 0x574 */ cBgS_PolyInfo mPolyInfo; /* 0x584 */ J3DModel* mpModel; /* 0x588 */ dBgW* mpBgW; diff --git a/include/d/actor/d_a_obj_window.h b/include/d/actor/d_a_obj_window.h index 83b62740a4..8d1b0412d7 100644 --- a/include/d/actor/d_a_obj_window.h +++ b/include/d/actor/d_a_obj_window.h @@ -27,7 +27,6 @@ public: u8 getType() { return fopAcM_GetParamBit(this, 0, 8); } u8 getLightInf() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x5A0 */ request_of_phase_process_class mPhaseReq; /* 0x5A8 */ J3DModel* mpModel; /* 0x5AC */ mDoExt_bckAnm* mpBckAnm; diff --git a/include/d/actor/d_a_obj_wood_pendulum.h b/include/d/actor/d_a_obj_wood_pendulum.h index 1a1ee024b3..9ee5126ad8 100644 --- a/include/d/actor/d_a_obj_wood_pendulum.h +++ b/include/d/actor/d_a_obj_wood_pendulum.h @@ -26,7 +26,6 @@ public: u32 getArg0() { return fopAcM_GetParamBit(this, 0, 8); } u32 getArg1() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ dCcD_Stts mStts; diff --git a/include/d/actor/d_a_obj_wood_statue.h b/include/d/actor/d_a_obj_wood_statue.h index c0214679db..3ddd94bc62 100644 --- a/include/d/actor/d_a_obj_wood_statue.h +++ b/include/d/actor/d_a_obj_wood_statue.h @@ -52,7 +52,6 @@ public: void setStatus(u8 status) { mStatus = status; } u8 getSwbit2() { return fopAcM_GetParamBit(this, 8, 8); } -private: /* 0x92C */ fpc_ProcID mItemId; /* 0x930 */ int field_0x930; /* 0x934 */ int mStaffIdx; diff --git a/include/d/actor/d_a_obj_wsword.h b/include/d/actor/d_a_obj_wsword.h index aa611af336..2860313c19 100644 --- a/include/d/actor/d_a_obj_wsword.h +++ b/include/d/actor/d_a_obj_wsword.h @@ -25,7 +25,6 @@ public: int draw(); int _delete(); -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ dBgS_ObjAcch mAcch; diff --git a/include/d/actor/d_a_obj_yel_bag.h b/include/d/actor/d_a_obj_yel_bag.h index caeba24ae5..9c3f452a11 100644 --- a/include/d/actor/d_a_obj_yel_bag.h +++ b/include/d/actor/d_a_obj_yel_bag.h @@ -53,7 +53,7 @@ public: * */ class daObj_YBag_c : public fopAc_ac_c { -private: +public: /* 0x568 */ request_of_phase_process_class mPhases[3]; /* 0x580 */ J3DModel* mModel; /* 0x584 */ OBJ_YBAG_HIO_CLASS* mpHIO; diff --git a/include/d/actor/d_a_obj_yobikusa.h b/include/d/actor/d_a_obj_yobikusa.h index a3dfa944fa..ed1019c7ad 100644 --- a/include/d/actor/d_a_obj_yobikusa.h +++ b/include/d/actor/d_a_obj_yobikusa.h @@ -87,7 +87,6 @@ public: return var_r31 > 0; } -private: /* 0x568 */ J3DModel* mpActiveModel; /* 0x56C */ J3DModel* mpModelTypeA; /* 0x570 */ J3DModel* mpModelTypeB; diff --git a/include/d/actor/d_a_obj_zrTurara.h b/include/d/actor/d_a_obj_zrTurara.h index 679ae20635..c52a21f2cc 100644 --- a/include/d/actor/d_a_obj_zrTurara.h +++ b/include/d/actor/d_a_obj_zrTurara.h @@ -29,7 +29,6 @@ public: int Draw(); int Delete(); -private: /* 0x5A0 */ dCcD_Stts mCcStatus; /* 0x5DC */ dCcD_Cyl mCcCyl; /* 0x718 */ request_of_phase_process_class mPhaseReq; diff --git a/include/d/actor/d_a_obj_zrTuraraRock.h b/include/d/actor/d_a_obj_zrTuraraRock.h index 1f92de6e82..922948ee9c 100644 --- a/include/d/actor/d_a_obj_zrTuraraRock.h +++ b/include/d/actor/d_a_obj_zrTuraraRock.h @@ -30,7 +30,6 @@ public: int Draw(); int Delete(); -private: /* 0x568 */ dCcD_Stts mCcStatus; /* 0x5A4 */ dCcD_Sph mCcSph; /* 0x6DC */ request_of_phase_process_class mPhaseReq; diff --git a/include/d/actor/d_a_obj_zra_rock.h b/include/d/actor/d_a_obj_zra_rock.h index 586fc176c5..925c49a5ee 100644 --- a/include/d/actor/d_a_obj_zra_rock.h +++ b/include/d/actor/d_a_obj_zra_rock.h @@ -28,7 +28,6 @@ public: void setDemoStart() { mDemoStart = 1; } -private: friend class daNpc_zrZ_c; /* 0x5A0 */ request_of_phase_process_class mPhaseReq; diff --git a/include/d/actor/d_a_passer_mng.h b/include/d/actor/d_a_passer_mng.h index ced02d9d1d..a3aa50db35 100644 --- a/include/d/actor/d_a_passer_mng.h +++ b/include/d/actor/d_a_passer_mng.h @@ -259,7 +259,6 @@ public: static DUSK_GAME_DATA const Group* mGroupTbl[4]; -private: /* 0x568 */ fpc_ProcID* childProcIds; /* 0x56C */ dPath* mPath; /* 0x570 */ int startTime; diff --git a/include/d/actor/d_a_peru.h b/include/d/actor/d_a_peru.h index 771e879b10..8a997b7a85 100644 --- a/include/d/actor/d_a_peru.h +++ b/include/d/actor/d_a_peru.h @@ -189,7 +189,6 @@ public: static DUSK_GAME_DATA const char* mCutNameList[3]; static DUSK_GAME_DATA cutAppearFunc mCutList[3]; -private: /* 0x0E40 */ daNpcT_ActorMngr_c mActors[3]; /* 0x0E58 */ int field_0xe58; /* 0x0E5C */ actionFunc mActionFunc; diff --git a/include/d/actor/d_a_player.h b/include/d/actor/d_a_player.h index ce6158fcf7..6eb4757c18 100644 --- a/include/d/actor/d_a_player.h +++ b/include/d/actor/d_a_player.h @@ -25,7 +25,6 @@ public: mNowSetFlg = 0; } -private: /* 0x14 */ u16 mEndFlg; /* 0x16 */ u16 mNowSetFlg; }; @@ -34,9 +33,13 @@ class daPy_sightPacket_c : public dDlst_base_c { public: daPy_sightPacket_c() {} virtual void draw(); +#if TARGET_PC + virtual ~daPy_sightPacket_c(); +#else virtual ~daPy_sightPacket_c() {} +#endif - void setSight(); + void setSight(IF_DUSK(bool registerPacket = true)); void setSightImage(ResTIMG* i_img); u8 getDrawFlg() { return mDrawFlag; } @@ -70,7 +73,6 @@ public: static DUSK_GAME_DATA s16 m_dropAngleY; static DUSK_GAME_DATA s16 m_eventKeepFlg; -private: /* 0x0 */ u8 field_0x0; /* 0x2 */ s16 field_0x2; /* 0x4 */ f32 m_offsetY; @@ -151,7 +153,6 @@ public: fopAc_ac_c* getActor() const { return mActor; } fopAc_ac_c* getActorConst() const { return mActor; } -private: /* 0x0 */ fpc_ProcID mID; /* 0x4 */ fopAc_ac_c* mActor; }; // Size: 0x8 @@ -296,7 +297,6 @@ public: void resetDemoType() { setDemoType(0); } void setStartDemoType() { setDemoType(DEMO_TYPE_START_e); } -private: /* 0x00 */ u16 mDemoType; /* 0x02 */ s16 mDemoMoveAngle; /* 0x04 */ s16 mTimer; diff --git a/include/d/actor/d_a_ppolamp.h b/include/d/actor/d_a_ppolamp.h index f1e1a9f26d..d55754d97b 100644 --- a/include/d/actor/d_a_ppolamp.h +++ b/include/d/actor/d_a_ppolamp.h @@ -25,7 +25,6 @@ public: void moveSwing(); void initParam(); -private: /* 0x574 */ J3DModel* mModel1; /* 0x578 */ J3DModel* mModel2; /* 0x57C */ mDoExt_bckAnm mBck; diff --git a/include/d/actor/d_a_spinner.h b/include/d/actor/d_a_spinner.h index 1dac4594a3..0ef81f3947 100644 --- a/include/d/actor/d_a_spinner.h +++ b/include/d/actor/d_a_spinner.h @@ -99,7 +99,6 @@ public: void forceDelete() { mDeleteFlg = true; } -private: /* 0x568 */ J3DModel* mpModel; /* 0x56C */ mDoExt_bckAnm mBck; /* 0x588 */ J3DAnmTransform* mpSpoutBck; diff --git a/include/d/actor/d_a_swBall.h b/include/d/actor/d_a_swBall.h index 74bfa90593..df4877e04b 100644 --- a/include/d/actor/d_a_swBall.h +++ b/include/d/actor/d_a_swBall.h @@ -30,7 +30,6 @@ public: u8 getType() { return fopAcM_GetParamBit(this, 24, 4); } bool checkPullLBall() { return fopAcM_GetParamBit(this, 28, 2) == 0; } -private: /* 0x568 */ fpc_ProcID mBallIDs[8]; /* 0x588 */ u8 mTimer; /* 0x589 */ u8 mAction; diff --git a/include/d/actor/d_a_swLBall.h b/include/d/actor/d_a_swLBall.h index 0a633fcb5b..c3d132a40f 100644 --- a/include/d/actor/d_a_swLBall.h +++ b/include/d/actor/d_a_swLBall.h @@ -42,7 +42,6 @@ public: return fopAcM_GetParamBit(this,28,2) == 0; } -private: /* 0x568 */ fpc_ProcID mProcIds[2]; /* 0x570 */ u8 mRunTimer; /* 0x571 */ u8 mAction; diff --git a/include/d/actor/d_a_swTime.h b/include/d/actor/d_a_swTime.h index b3b96fa104..f61d5a5e7f 100644 --- a/include/d/actor/d_a_swTime.h +++ b/include/d/actor/d_a_swTime.h @@ -30,7 +30,6 @@ public: int execute(); int _delete(); -private: /* 0x568 */ u8 mTime; /* 0x569 */ u8 mSwbit; /* 0x56A */ u8 mSwbit2; diff --git a/include/d/actor/d_a_tag_CstaSw.h b/include/d/actor/d_a_tag_CstaSw.h index b40a9d178e..e73fd18662 100644 --- a/include/d/actor/d_a_tag_CstaSw.h +++ b/include/d/actor/d_a_tag_CstaSw.h @@ -16,7 +16,6 @@ public: u32 getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x568 */ u8 mSwitch; /* 0x569 */ u8 unused[0x570 - 0x569]; diff --git a/include/d/actor/d_a_tag_TWgate.h b/include/d/actor/d_a_tag_TWgate.h index 1886690db2..683d01449b 100644 --- a/include/d/actor/d_a_tag_TWgate.h +++ b/include/d/actor/d_a_tag_TWgate.h @@ -118,7 +118,6 @@ public: #endif static DUSK_GAME_DATA const actionFunc ActionTable[][2]; -private: /* 0x568 */ mDoExt_McaMorfSO* mpMorf; /* 0x56C */ request_of_phase_process_class mPhaseZevArc; /* 0x574 */ request_of_phase_process_class mPhaseMdRes; diff --git a/include/d/actor/d_a_tag_bottle_item.h b/include/d/actor/d_a_tag_bottle_item.h index cd122c8cad..3cc2a274d4 100644 --- a/include/d/actor/d_a_tag_bottle_item.h +++ b/include/d/actor/d_a_tag_bottle_item.h @@ -8,7 +8,7 @@ class daTag_BottleItem_c; typedef int (daTag_BottleItem_c::*ProcessFunc)(void*); class daTag_BottleItem_c : public fopAc_ac_c { -private: +public: /* 0x0568 */ ProcessFunc mProcessFunc; /* 0x056C */ u16 mEventType; /* 0x0576 */ u8 mBottleItemType; diff --git a/include/d/actor/d_a_tag_chkpoint.h b/include/d/actor/d_a_tag_chkpoint.h index 4871ec3946..51fa95e17d 100644 --- a/include/d/actor/d_a_tag_chkpoint.h +++ b/include/d/actor/d_a_tag_chkpoint.h @@ -45,7 +45,6 @@ public: return cPhs_COMPLEATE_e; } -private: /* 0x568 */ u8 field_0x568[8]; /* 0x570 */ cXyz mVtxA; /* 0x57C */ cXyz mVtxB; diff --git a/include/d/actor/d_a_tag_evtarea.h b/include/d/actor/d_a_tag_evtarea.h index 4bf0ec404e..f83e034aa1 100644 --- a/include/d/actor/d_a_tag_evtarea.h +++ b/include/d/actor/d_a_tag_evtarea.h @@ -45,7 +45,6 @@ public: void noEffect() { field_0x56c = 1; } -private: /* 0x568 vtable */ /* 0x56C */ u8 field_0x56c; }; diff --git a/include/d/actor/d_a_tag_lv6CstaSw.h b/include/d/actor/d_a_tag_lv6CstaSw.h index 17b58e4b28..384d9bd6d9 100644 --- a/include/d/actor/d_a_tag_lv6CstaSw.h +++ b/include/d/actor/d_a_tag_lv6CstaSw.h @@ -16,7 +16,6 @@ public: u32 getSw() { return fopAcM_GetParamBit(this, 0, 8); } -private: /* 0x568 */ u8 mSwitch; /* 0x569 */ u8 unused[0x570 - 0x569]; }; diff --git a/include/d/actor/d_a_tag_mhint.h b/include/d/actor/d_a_tag_mhint.h index e4c5260079..c573467ae0 100644 --- a/include/d/actor/d_a_tag_mhint.h +++ b/include/d/actor/d_a_tag_mhint.h @@ -26,7 +26,6 @@ public: NULL); } -private: /* 0x568 */ u8 field_0x568; /* 0x569 */ u8 mSwitch; /* 0x56A */ u8 field_0x56a; diff --git a/include/d/actor/d_a_tag_mmsg.h b/include/d/actor/d_a_tag_mmsg.h index 9fdd0b3c66..bcafa0c37d 100644 --- a/include/d/actor/d_a_tag_mmsg.h +++ b/include/d/actor/d_a_tag_mmsg.h @@ -14,7 +14,6 @@ public: void onUseFlg() { mUseFlg = true; } bool checkNoAttention() const { return mAttention == 0xFF; } -private: /* 0x568 */ u8 field_0x568; /* 0x569 */ u8 field_0x569; /* 0x56A */ u8 mAttention; diff --git a/include/d/actor/d_a_tag_mstop.h b/include/d/actor/d_a_tag_mstop.h index dd27dbb005..774af17007 100644 --- a/include/d/actor/d_a_tag_mstop.h +++ b/include/d/actor/d_a_tag_mstop.h @@ -17,7 +17,6 @@ public: int execute(); bool checkNoAttention() const { return field_0x56b == 0xFF; } -private: /* 0x568 */ u8 field_0x568; /* 0x569 */ u8 mSwitch; /* 0x56A */ u8 field_0x56a; diff --git a/include/d/actor/d_a_tag_myna2.h b/include/d/actor/d_a_tag_myna2.h index 7eecb0c1d7..70605612e3 100644 --- a/include/d/actor/d_a_tag_myna2.h +++ b/include/d/actor/d_a_tag_myna2.h @@ -12,7 +12,6 @@ public: int getMode() { return fopAcM_GetParam(this) & 0xFF; } f32 getExtent() { return scale.x * 100.0f;} -private: /* 0x568 */ u32 mSwitchNo; /* 0x56C */ u32 mMode; /* 0x570 */ s16 mTimer; diff --git a/include/d/actor/d_a_tag_setBall.h b/include/d/actor/d_a_tag_setBall.h index 6e75928c6b..0619e2a677 100644 --- a/include/d/actor/d_a_tag_setBall.h +++ b/include/d/actor/d_a_tag_setBall.h @@ -16,7 +16,6 @@ public: s32 getType() { return mType; } -private: u8 getType_private() { return fopAcM_GetParamBit(this, 8, 4); } /* 0x568 */ u8 mType; diff --git a/include/d/actor/d_a_tag_statue_evt.h b/include/d/actor/d_a_tag_statue_evt.h index 1bdc0d4f52..59237c09eb 100644 --- a/include/d/actor/d_a_tag_statue_evt.h +++ b/include/d/actor/d_a_tag_statue_evt.h @@ -25,7 +25,6 @@ public: int draw(); int _delete(); -private: /* 0x568 */ request_of_phase_process_class mArcPhase; /* 0x570 */ request_of_phase_process_class mEvArcPhase; /* 0x578 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_tag_telop.h b/include/d/actor/d_a_tag_telop.h index 6e28231138..0d6fbeb7a1 100644 --- a/include/d/actor/d_a_tag_telop.h +++ b/include/d/actor/d_a_tag_telop.h @@ -13,7 +13,6 @@ public: u16 getMessageNo() { return home.angle.z; } -private: /* 0x568 */ u16 mMessageNo; /* 0x56C */ int mMessageStatus; }; diff --git a/include/d/actor/d_a_tag_waterfall.h b/include/d/actor/d_a_tag_waterfall.h index 80ffe7bec2..6076e53042 100644 --- a/include/d/actor/d_a_tag_waterfall.h +++ b/include/d/actor/d_a_tag_waterfall.h @@ -22,7 +22,6 @@ public: int _delete(); cPhs_Step create(); -private: /* 0x568 */ f32 mCylinderRadiusA; /* 0x56C */ f32 mCylinderRadiusB; /* 0x570 */ f32 mCylinderHeight; diff --git a/include/d/actor/d_a_tag_yami.h b/include/d/actor/d_a_tag_yami.h index 8410899186..fc17586688 100644 --- a/include/d/actor/d_a_tag_yami.h +++ b/include/d/actor/d_a_tag_yami.h @@ -36,7 +36,6 @@ public: void setCoR(f32 i_value) { mCoR = i_value; } void setCoH(f32 i_value) { mCoH = i_value; } -private: /* 0x568 */ dCcD_Cyl mCyl; /* 0x6A4 */ dCcD_Stts mStts; /* 0x6E0 */ u8 mCoHitValid; diff --git a/include/d/actor/d_a_talk.h b/include/d/actor/d_a_talk.h index 9c86f88610..b7715d88cd 100644 --- a/include/d/actor/d_a_talk.h +++ b/include/d/actor/d_a_talk.h @@ -22,7 +22,6 @@ public: u16 getStatus(); u32 messageSet(); -private: /* 0x568 */ dMsgFlow_c mMsgFlow; /* 0x5B4 */ u32 mMessageID; }; diff --git a/include/d/actor/d_a_tbox.h b/include/d/actor/d_a_tbox.h index 6d0528d29d..6b07594577 100644 --- a/include/d/actor/d_a_tbox.h +++ b/include/d/actor/d_a_tbox.h @@ -148,7 +148,6 @@ public: field_0x9fc = 1; } -private: /* 0x718 */ u8 field_0x718; /* 0x71C */ request_of_phase_process_class mPhase; /* 0x724 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_tbox2.h b/include/d/actor/d_a_tbox2.h index 5e3989db57..29df2d8389 100644 --- a/include/d/actor/d_a_tbox2.h +++ b/include/d/actor/d_a_tbox2.h @@ -67,7 +67,6 @@ public: u8 getModelType() { return fopAcM_GetParamBit(this, 8, 4); } u8 getItemNo() { return fopAcM_GetParamBit(this, 0, 8); } -// private: /* 0x718 */ u8 mReturnRupee; /* 0x71C */ request_of_phase_process_class mPhase; /* 0x724 */ J3DModel* mpModel; diff --git a/include/d/actor/d_a_title.h b/include/d/actor/d_a_title.h index 465fe689c1..f6e70b4276 100644 --- a/include/d/actor/d_a_title.h +++ b/include/d/actor/d_a_title.h @@ -50,7 +50,6 @@ public: void KeyWaitPosMove(); #endif -private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ JKRHeap* mpHeap; /* 0x574 */ J3DModel* mpModel; @@ -69,7 +68,7 @@ private: /* 0x5FB */ u8 mProcID; /* 0x5FC */ u8 mWaitTimer; /* 0x600 */ CPaneMgrAlpha* field_0x600; - /* 0x604 */ u8 field_0x604; + /* 0x604 */ DUSK_IF_ELSE(f32, u8) field_0x604; }; // Size: 0x608 typedef void (daTitle_c::*procFunc)(); diff --git a/include/d/d_a_shop_item_static.h b/include/d/d_a_shop_item_static.h index 7126578092..e79973fc65 100644 --- a/include/d/d_a_shop_item_static.h +++ b/include/d/d_a_shop_item_static.h @@ -107,7 +107,6 @@ public: SHOP_ITEMNO_MAX, }; -private: /* 0x92C */ Mtx mMtx; /* 0x95C */ int field_0x95c; /* 0x960 */ s16 mAngleX; diff --git a/include/d/d_attention.h b/include/d/d_attention.h index 8e3b8be1f5..b1c269651f 100644 --- a/include/d/d_attention.h +++ b/include/d/d_attention.h @@ -18,7 +18,6 @@ public: fopAc_ac_c* getZHintTarget() { return convPId(field_0x8); } -private: /* 0x0 */ fpc_ProcID mHintActorID; /* 0x4 */ int mPriority; /* 0x8 */ fpc_ProcID field_0x8; @@ -34,7 +33,6 @@ public: fopAc_ac_c* getCatghTarget() { return convPId(mCatghTargetID); } u8 getChangeItem() { return mChangeItem; } -private: /* 0x00 */ fpc_ProcID mRequestActorID; /* 0x04 */ int field_0x4; /* 0x08 */ f32 mDistance; @@ -120,7 +118,6 @@ public: fopAc_ac_c* getLookTarget() { return convPId(mLookTargetID); } -private: fpc_ProcID mRequestActorID; int field_0x4; f32 mDistance; diff --git a/include/d/d_bg_plc.h b/include/d/d_bg_plc.h index da360fd0e4..f5361ff883 100644 --- a/include/d/d_bg_plc.h +++ b/include/d/d_bg_plc.h @@ -20,7 +20,6 @@ public: static const int ZELDA_CODE_SIZE = sizeof(sBgPc); -private: /* 0x00 */ sBgPlc* m_base; }; diff --git a/include/d/d_bg_s_acch.h b/include/d/d_bg_s_acch.h index 04a388c61b..a1a1517cc4 100644 --- a/include/d/d_bg_s_acch.h +++ b/include/d/d_bg_s_acch.h @@ -13,7 +13,7 @@ class fopAc_ac_c; class dBgS_AcchCir : public cBgS_PolyInfo { -private: +public: /* 0x10 */ u32 m_flags; /* 0x14 */ cM3dGCir m_cir; /* 0x28 */ f32 m_wall_rr; @@ -197,7 +197,6 @@ public: f32 GetCx() const { return pm_pos->x; } f32 GetCz() const { return pm_pos->z; } -private: /* 0x02C */ u32 m_flags; /* 0x030 */ cXyz* pm_pos; /* 0x034 */ cXyz* pm_old_pos; @@ -237,7 +236,6 @@ public: /* 0x0DC */ dBgS_GndChk m_gnd; /* 0x130 */ dBgS_RoofChk m_roof; /* 0x180 */ dBgS_WtrChk m_wtr; -private: /* 0x1D4 */ u8 m_wtr_mode; }; // Size: 0x1D8 diff --git a/include/d/d_bg_s_grp_pass_chk.h b/include/d/d_bg_s_grp_pass_chk.h index eea3414b98..6253f67f18 100644 --- a/include/d/d_bg_s_grp_pass_chk.h +++ b/include/d/d_bg_s_grp_pass_chk.h @@ -25,7 +25,6 @@ public: void OnAll() { mGrp |= FULL_GRP; } u32 MaskNormalGrp() { return mGrp & NORMAL_GRP; } u32 MaskWaterGrp() { return mGrp & WATER_GRP; } -private: /* 0x4 */ u32 mGrp; }; diff --git a/include/d/d_bg_s_poly_pass_chk.h b/include/d/d_bg_s_poly_pass_chk.h index 6f7778189a..759321c6da 100644 --- a/include/d/d_bg_s_poly_pass_chk.h +++ b/include/d/d_bg_s_poly_pass_chk.h @@ -42,7 +42,6 @@ public: void SetStatue(); void SetUnderwaterRoof(); -private: /* 0x4 */ bool mObject; /* 0x5 */ bool mCamera; /* 0x6 */ bool mLink; diff --git a/include/d/d_bg_s_roof_chk.h b/include/d/d_bg_s_roof_chk.h index 387c05fff2..e404eef0c4 100644 --- a/include/d/d_bg_s_roof_chk.h +++ b/include/d/d_bg_s_roof_chk.h @@ -17,7 +17,6 @@ public: f32 GetNowY() { return mNowY; } cXyz* GetPosP() { return &m_pos; } -private: /* 0x3C */ cXyz m_pos; /* 0x48 */ int field_0x48; /* 0x4C */ f32 mNowY; diff --git a/include/d/d_bg_s_spl_grp_chk.h b/include/d/d_bg_s_spl_grp_chk.h index 447bbf4d73..46df7df35a 100644 --- a/include/d/d_bg_s_spl_grp_chk.h +++ b/include/d/d_bg_s_spl_grp_chk.h @@ -30,7 +30,6 @@ public: cXyz& GetPosP() { return m_ground; } f32 GetRoof() { return m_roof; } -private: /* 0x3C */ cXyz m_ground; /* 0x48 */ f32 m_roof; /* 0x4C */ f32 m_height; diff --git a/include/d/d_bg_w.h b/include/d/d_bg_w.h index ae37d1c6d9..c9692a7c61 100644 --- a/include/d/d_bg_w.h +++ b/include/d/d_bg_w.h @@ -6,9 +6,12 @@ #include "d/d_bg_w_base.h" #include #include -#include "helpers/offset_ptr.h" + +#if TARGET_PC #include "helpers/endian.h" #include "helpers/endian_ssystem.h" +#include "helpers/offset_ptr.h" +#endif class cBgS_GrpPassChk; class cBgS_PolyPassChk; @@ -346,7 +349,6 @@ public: void SetArrowStickCallback(dBgW_ArrowStickCallback func) { m_arrow_stick_callback = func; } void OnMoveFlag() { m_flags |= (u8)1; } -private: /* 0xB0 */ dBgW_CrrFunc m_crr_func; /* 0xB4 */ dBgW_RideCallback m_ride_callback; /* 0xB8 */ dBgW_ArrowStickCallback m_arrow_stick_callback; diff --git a/include/d/d_bg_w_base.h b/include/d/d_bg_w_base.h index 6940df06fa..f30604d2d3 100644 --- a/include/d/d_bg_w_base.h +++ b/include/d/d_bg_w_base.h @@ -124,7 +124,6 @@ public: void OnPushPullOk() { m_pushPull_Ok = true; } void OffPushPullOk() { m_pushPull_Ok = false; } -private: /* 0x08 */ u8 m_priority; /* 0x09 */ u8 m_roomId; /* 0x0A */ u8 field_0xa; diff --git a/include/d/d_bg_w_kcol.h b/include/d/d_bg_w_kcol.h index 84b0da03f2..b0abe32102 100644 --- a/include/d/d_bg_w_kcol.h +++ b/include/d/d_bg_w_kcol.h @@ -6,7 +6,10 @@ #include "d/d_bg_plc.h" #include "d/d_bg_s_sph_chk.h" #include "d/d_bg_w_base.h" + +#if TARGET_PC #include "helpers/offset_ptr.h" +#endif class cBgS_GrpPassChk; class cBgS_PolyPassChk; @@ -117,7 +120,6 @@ public: void getTri1Pos(KC_PrismData* pd, Vec** nrm) const { *nrm = &m_pkc_head->m_pos_data[pd->pos_i]; } -private: /* 0x18 */ KC_Header* m_pkc_head; /* 0x1C */ dBgPlc m_code; /* 0x20 */ cM3dGAab m_bnd; diff --git a/include/d/d_cc_d.h b/include/d/d_cc_d.h index 40ffae33da..739abdcaa4 100644 --- a/include/d/d_cc_d.h +++ b/include/d/d_cc_d.h @@ -179,7 +179,6 @@ public: cXyz* GetHitPosP() { return &mHitPos; } void ClrHit() { dCcD_GAtTgCoCommonBase::ClrActorInfo(); } - // private: /* 0x1C */ u8 mSe; /* 0x1D */ u8 mMtrl; /* 0x1E */ u8 mHitMark; @@ -215,7 +214,6 @@ public: u8 GetSe() { return mSe; } void ClrHit() { dCcD_GAtTgCoCommonBase::ClrActorInfo(); } -private: /* 0x1C */ u8 mSe; /* 0x1D */ u8 mMtrl; /* 0x1E */ u8 mHitMark; @@ -258,7 +256,6 @@ public: void SetTgSpl(dCcG_Tg_Spl spl) { mTg = spl; } void OnNoActor() { field_0x1C |= 1; } - // private: /* 0x04 */ u8 mAt; /* 0x05 */ u8 mTg; /* 0x06 */ u8 mRoomId; diff --git a/include/d/d_cc_mass_s.h b/include/d/d_cc_mass_s.h index 672adb913a..291cb4552c 100644 --- a/include/d/d_cc_mass_s.h +++ b/include/d/d_cc_mass_s.h @@ -6,7 +6,7 @@ typedef void (*dCcMassS_ObjCallback)(fopAc_ac_c*, cXyz*, u32); class dCcMassS_Obj { -private: +public: /* 0x00 */ cCcD_Obj* mpObj; /* 0x04 */ u8 mPriority; /* 0x08 */ dCcMassS_ObjCallback mpAreaCheckFunc; @@ -28,7 +28,7 @@ public: STATIC_ASSERT(0x20 == sizeof(dCcMassS_Obj)); class dCcMassS_HitInf { -private: +public: /* 0x00 */ cCcD_Obj* mpArea; /* 0x04 */ cCcD_Obj* mpAtObj; /* 0x08 */ cCcD_Obj* mpCoObj; diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h index 62b8744b51..dcf681162f 100644 --- a/include/d/d_com_inf_game.h +++ b/include/d/d_com_inf_game.h @@ -249,7 +249,6 @@ public: ~dComIfG_resLoader_c(); int load(char const**, JKRHeap*); -private: /* 0x0 */ const char** mResNameTable; /* 0x4 */ request_of_phase_process_class mPhase; /* 0xC */ u8 mLoadIndex; diff --git a/include/d/d_cursor_mng.h b/include/d/d_cursor_mng.h index 38ba03f214..9837e6f1cb 100644 --- a/include/d/d_cursor_mng.h +++ b/include/d/d_cursor_mng.h @@ -95,7 +95,7 @@ class dCsr_mng_c { BOOL set(mDoGph_gInf_c::csr_c* i_csr, u16, u8, u8); }; -private: +public: dCsr_mng_c() : is_csr_on(true) { } @@ -116,7 +116,6 @@ public: static dCsr_mng_c* m_myObj; -private: /* 0x00 */ list_c m_csr_list; /* 0x08 */ list_c m_obj_list; /* 0x10 */ bool is_csr_on; diff --git a/include/d/d_demo.h b/include/d/d_demo.h index 8eef24d7e3..a35c44e0ed 100644 --- a/include/d/d_demo.h +++ b/include/d/d_demo.h @@ -155,7 +155,6 @@ public: f32 getAnmTransition() { return mAnmTransition; } u32 getShapeId() { return mShape; } -private: /* 0x04 */ u16 mFlags; /* 0x08 */ cXyz mTrans; /* 0x14 */ cXyz mScale; @@ -220,7 +219,6 @@ public: f32 getFovy() { return mProjFovy; } f32 getRoll() { return mViewRoll; } -private: /* 0x04 */ u8 mFlags; /* 0x08 */ f32 mProjNear; /* 0x0C */ f32 mProjFar; @@ -247,7 +245,6 @@ public: void onEnable(u8 flag) { mFlags |= flag; } -private: /* 0x04 */ u8 mFlags; /* 0x05 */ GXColor mColor; }; @@ -275,7 +272,6 @@ public: void onEnable(u8 flag) { mFlags |= flag; } -private: /* 0x04 */ u8 mFlags; /* 0x08 */ JStage::TELight mLightType; /* 0x0C */ GXDistAttnFn mDistAttenFn; @@ -309,7 +305,6 @@ public: void onEnable(u8 flag) { mFlags |= flag; } -private: /* 0x04 */ u8 mFlags; /* 0x05 */ s8 mFogType; /* 0x08 */ f32 mStartZ; @@ -327,7 +322,6 @@ public: void setObject(dDemo_object_c* i_object) { mpObject = i_object; } -private: /* 0x4 */ dDemo_object_c* mpObject; }; diff --git a/include/d/d_drawlist.h b/include/d/d_drawlist.h index 25d92aff54..4baf6ed324 100644 --- a/include/d/d_drawlist.h +++ b/include/d/d_drawlist.h @@ -5,10 +5,13 @@ #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J3DGraphBase/J3DSys.h" #include "SSystem/SComponent/c_m3d_g_pla.h" -#include "helpers/gx_helper.h" #include "f_op/f_op_view.h" -#include "global.h" #include "m_Do/m_Do_ext.h" +#include "global.h" + +#if TARGET_PC +#include "helpers/gx_helper.h" +#endif class J3DDrawBuffer; class J3DModel; @@ -107,7 +110,6 @@ public: mRnd.init(r0, r1, r2); } -private: /* 0x04 */ cM_rnd_c mRnd; /* 0x10 */ cXyz field_0x10; /* 0x1C */ GXColor mLineColor; @@ -129,7 +131,6 @@ public: void setAlpha(u8 alpha) { mAlpha = alpha; } J2DPicture* getPicture() { return &mpPicture; } -private: /* 0x004 */ u8 field_0x4; /* 0x008 */ J2DPicture mpPicture; /* 0x158 */ s16 mPosX; @@ -271,7 +272,6 @@ public: void setZsortNext(dDlst_shadowReal_c* next) { mZsortNext = next; } void setZsortPre(dDlst_shadowReal_c* pre) { mZsortPre = pre; } -private: /* 0x0000 */ u8 mState; /* 0x0001 */ u8 field_0x1; /* 0x0002 */ u8 mModelNum; @@ -303,7 +303,6 @@ public: static DUSK_GAME_DATA TGXTexObj mSimpleTexObj; -private: /* 0x00000 */ u8 field_0x0; /* 0x00001 */ u8 mRealNum; /* 0x00002 */ u8 mSimpleNum; @@ -332,7 +331,6 @@ public: view_port_class* getViewPort() { return &mViewport; } scissor_class* getScissor() { return &mViewport.scissor; } -private: /* 0x00 */ view_port_class mViewport; /* 0x28 */ s8 mCameraID; /* 0x29 */ u8 mMode; @@ -443,7 +441,7 @@ public: } #if TARGET_PC - void refresh3DlineMats(const cXyz& eye); + void refresh3DlineMats(); #endif void peekZdata() { mPeekZ.peekData(); } @@ -518,7 +516,6 @@ public: static DUSK_GAME_DATA f32 mWipeSpeed; static DUSK_GAME_DATA u8 mWipe; -private: /* 0x00000 */ J3DDrawBuffer* mDrawBuffers[DB_LIST_MAX]; /* 0x00054 */ dDlst_base_c* mpCopy2DDrawLists[4]; /* 0x00064 */ dDlst_base_c** mpCopy2DStart; diff --git a/include/d/d_event_data.h b/include/d/d_event_data.h index eef55b1164..786088250f 100644 --- a/include/d/d_event_data.h +++ b/include/d/d_event_data.h @@ -3,7 +3,10 @@ #include "global.h" #include "f_pc/f_pc_base.h" + +#if TARGET_PC #include "helpers/endian.h" +#endif struct msg_class; @@ -190,7 +193,6 @@ public: int getCurrentCut() { return mCurrentCut; } int getStartCut() { return mStartCut; } - // private: /* 0x00 */ char mName[8]; /* 0x08 */ u8 mWork[0x18]; // PROBLEM: this buffer is now too small for StaffWork to fit in /* 0x20 */ BE(s32) mTagID; @@ -293,7 +295,6 @@ public: static const int FlagMax = 0x2800; -private: u32 mFlags[320]; }; // Size = 0x500 @@ -346,7 +347,6 @@ public: void setIDataP(int* p_idata) { mIDataP = p_idata; } void setSDataP(char* p_sdata) { mSDataP = p_sdata; } - // private: /* 0x00 */ event_binary_data_header* mHeaderP; /* 0x04 */ dEvDtEvent_c* mEventP; /* 0x08 */ dEvDtStaff_c* mStaffP; diff --git a/include/d/d_event_manager.h b/include/d/d_event_manager.h index de046797ff..75aa5a4a03 100644 --- a/include/d/d_event_manager.h +++ b/include/d/d_event_manager.h @@ -112,7 +112,6 @@ public: dEvM_HIO_c& getEventHIO(); #endif -private: /* 0x000 */ dEvDtBase_c mEventList[BASE_MAX]; /* 0x18C */ int mCameraPlay; /* 0x190 */ dEvent_exception_c mException; diff --git a/include/d/d_file_sel_info.h b/include/d/d_file_sel_info.h index 37dadbeb1b..89c4272b22 100644 --- a/include/d/d_file_sel_info.h +++ b/include/d/d_file_sel_info.h @@ -37,7 +37,6 @@ public: CPaneMgrAlpha* getNoDatBase() { return mNoDatBase; } void draw() { _draw(); } -// private: /* 0x04 */ JKRArchive* mArchive; /* 0x08 */ dDlst_FileInfo_c mFileInfo; /* 0x1C */ u8 field_0x1c[4]; diff --git a/include/d/d_file_sel_warning.h b/include/d/d_file_sel_warning.h index c8740b5f74..b4a3790688 100644 --- a/include/d/d_file_sel_warning.h +++ b/include/d/d_file_sel_warning.h @@ -35,6 +35,9 @@ public: void drawSelf(); void setText(u32); void setFontColor(JUtility::TColor, JUtility::TColor); +#if TARGET_PC + void presentAnims(); +#endif virtual ~dFile_warning_c(); @@ -47,7 +50,7 @@ public: /* 0x1C */ CPaneMgr* mpRootPane; /* 0x20 */ J2DTextBox* field_0x20; /* 0x24 */ J2DAnmTransform* field_0x24; - /* 0x28 */ int field_0x28; + /* 0x28 */ DUSK_IF_ELSE(f32, int) field_0x28; /* 0x2C */ int field_0x2c; /* 0x30 */ u8 field_0x30[4]; /* 0x34 */ f32 field_0x34; diff --git a/include/d/d_file_select.h b/include/d/d_file_select.h index 48d81d9ad5..c68f68db5d 100644 --- a/include/d/d_file_select.h +++ b/include/d/d_file_select.h @@ -385,7 +385,7 @@ public: void menuCursorShow(); void yesnoWakuAlpahAnmInit(u8, u8, u8, u8); bool yesnoWakuAlpahAnm(u8); - #if TARGET_PC +#if TARGET_PC void fileSelectWide(); bool pointerDataSelect(); bool pointerMenuSelect(); @@ -398,7 +398,10 @@ public: modoruTxtDispAnmInit(0); mDataSelProc = DATASELPROC_NAME_TO_DATA_SELECT_MOVE; } - #endif + void presentLayoutAnims(); + void presentAnims(); + void presentNameInputFade(); +#endif void _draw(); void errorMoveAnmInitSet(int, int); bool errorMoveAnm(); @@ -523,7 +526,7 @@ public: /* 0x00A0 */ s32 field_0x00a0; /* 0x00A4 */ CPaneMgr* mBaseMovePane; /* 0x00A8 */ u8 field_0x00a8[0xb0-0xa8]; - /* 0x00B0 */ s32 mBaseMoveAnmFrame; + /* 0x00B0 */ DUSK_IF_ELSE(f32, s32) mBaseMoveAnmFrame; /* 0x00B4 */ s32 mBaseMoveAnmFrameMax; /* 0x00B8 */ u8 field_0x00b8; /* 0x00B9 */ u8 field_0x00b9; @@ -532,26 +535,26 @@ public: /* 0x00BC */ CPaneMgr* mSelFilePanes[3]; /* 0x00C8 */ f32 field_0x00c8[3]; /* 0x00D4 */ f32 field_0x00d4[3]; - /* 0x00E0 */ s32 field_0x00e0[3]; + /* 0x00E0 */ DUSK_IF_ELSE(f32, s32) field_0x00e0[3]; /* 0x00EC */ s32 field_0x00ec; /* 0x00F0 */ CPaneMgr* mYnSelPane[2]; - /* 0x00F8 */ s32 field_0x00f8[2]; - /* 0x0100 */ s32 field_0x0100; + /* 0x00F8 */ DUSK_IF_ELSE(f32, s32) field_0x00f8[2]; + /* 0x0100 */ DUSK_IF_ELSE(f32, s32) field_0x0100; /* 0x0104 */ s32 field_0x0104; /* 0x0108 */ bool field_0x0108; /* 0x0109 */ bool field_0x0109; /* 0x010A */ u8 field_0x10a[2]; /* 0x010C */ J2DPane* mBaseSubPane; - /* 0x0110 */ int field_0x0110; + /* 0x0110 */ DUSK_IF_ELSE(f32, int) field_0x0110; /* 0x0114 */ int field_0x0114; /* 0x0118 */ J2DPane* m3mMenuPane; /* 0x011C */ J2DPane* mNameBasePane; - /* 0x0120 */ int field_0x0120; + /* 0x0120 */ DUSK_IF_ELSE(f32, int) field_0x0120; /* 0x0124 */ int field_0x0124; /* 0x0128 */ bool field_0x0128; /* 0x0129 */ u8 field_0x0129[0x012C - 0x0129]; /* 0x012C */ J2DPane* mErrorMsgPane; - /* 0x0130 */ int field_0x0130; + /* 0x0130 */ DUSK_IF_ELSE(f32, int) field_0x0130; /* 0x0134 */ int field_0x0134; /* 0x0138 */ CPaneMgrAlpha* mErrorMsgTxtPane[2]; /* 0x0140 */ TEXT_SPAN mErrorMsgStringPtr[2]; @@ -578,24 +581,24 @@ public: /* 0x01BE */ u8 field_0x01be[2]; /* 0x01C0 */ CPaneMgr* mYnSelTxtPane[2]; /* 0x01C8 */ J2DAnmColor* mFileSelBpk; - /* 0x01CC */ s32 mSelFileBpkFrame; + /* 0x01CC */ DUSK_IF_ELSE(f32, s32) mSelFileBpkFrame; /* 0x01D0 */ J2DAnmTextureSRTKey* mFileSel05Btk; - /* 0x01D4 */ s32 mSelFileBtk05Frame; + /* 0x01D4 */ DUSK_IF_ELSE(f32, s32) mSelFileBtk05Frame; /* 0x01D8 */ J2DAnmColor* mSelFileBookBpk; - /* 0x01DC */ s32 mSelFileBookBpkFrame; + /* 0x01DC */ DUSK_IF_ELSE(f32, s32) mSelFileBookBpkFrame; /* 0x01E0 */ J2DAnmTextureSRTKey* mSelFileBookBtk; - /* 0x01E4 */ s32 mSelFileBookBtkFrame; + /* 0x01E4 */ DUSK_IF_ELSE(f32, s32) mSelFileBookBtkFrame; /* 0x01E8 */ J2DAnmTevRegKey* mSelFileBookBrk; - /* 0x01EC */ s32 mSelFileBookBrkFrame; + /* 0x01EC */ DUSK_IF_ELSE(f32, s32) mSelFileBookBrkFrame; /* 0x01F0 */ J2DAnmTextureSRTKey* mDtEffBtk; - /* 0x01F4 */ s32 mDtEffBtkFrame; + /* 0x01F4 */ DUSK_IF_ELSE(f32, s32) mDtEffBtkFrame; /* 0x01F8 */ J2DAnmTextureSRTKey* mCpEffBtk; - /* 0x01FC */ s32 mCpEffBtkFrame; + /* 0x01FC */ DUSK_IF_ELSE(f32, s32) mCpEffBtkFrame; /* 0x0200 */ J2DAnmTevRegKey* mCpDtEffBrk; - /* 0x0204 */ s32 mCpDtEffBrkFrame; + /* 0x0204 */ DUSK_IF_ELSE(f32, s32) mCpDtEffBrkFrame; /* 0x0208 */ u8 field_0x0208; /* 0x0209 */ u8 field_0x0209; - /* 0x020A */ u8 mFadeTimer; + /* 0x020A */ DUSK_IF_ELSE(f32, u8) mFadeTimer; /* 0x020B */ u8 field_0x020b; /* 0x020C */ CPaneMgrAlpha* mHeaderTxtPane[2]; /* 0x0214 */ TEXT_SPAN mHeaderStringPtr[2]; @@ -648,7 +651,7 @@ public: /* 0x02A0 */ J2DAnmTransform* mCpSelBck2; /* 0x02A4 */ CPaneMgr* mCpSelPane[3]; /* 0x02B0 */ u8 field_0x02b0[0x2b4-0x2b0]; - /* 0x02B4 */ int field_0x02b4[3]; + /* 0x02B4 */ DUSK_IF_ELSE(f32, int) field_0x02b4[3]; /* 0x02C0 */ CPaneMgr* mCpSelPane_moyo[2]; /* 0x02C8 */ CPaneMgr* mCpSelPane_gold[2]; /* 0x02D0 */ CPaneMgr* mCpSelPane_gold2[2]; @@ -658,30 +661,30 @@ public: /* 0x02E4 */ u8 field_0x02e4[2]; /* 0x02E4 */ u8 field_0x02e6[0x2e8-0x2e6]; /* 0x02E8 */ J2DAnmColor* mCpSelBpk; - /* 0x02EC */ s32 mCpSelBpkFrame; + /* 0x02EC */ DUSK_IF_ELSE(f32, s32) mCpSelBpkFrame; /* 0x02F0 */ J2DAnmTextureSRTKey* mCpSel03Btk; - /* 0x02F4 */ s32 mCpSel03BtkFrame; + /* 0x02F4 */ DUSK_IF_ELSE(f32, s32) mCpSel03BtkFrame; /* 0x02F8 */ J2DAnmColor* mCpSelBookBpk; - /* 0x02FC */ s32 mCpSelBookBpkFrame; + /* 0x02FC */ DUSK_IF_ELSE(f32, s32) mCpSelBookBpkFrame; /* 0x0300 */ J2DAnmTextureSRTKey* mCpSelBookBtk; - /* 0x0304 */ s32 mCpSelBookBtkFrame; + /* 0x0304 */ DUSK_IF_ELSE(f32, s32) mCpSelBookBtkFrame; /* 0x0308 */ J2DAnmTevRegKey* mCpSelBookBrk; - /* 0x030C */ s32 mCpSelBookBrkFrame; + /* 0x030C */ DUSK_IF_ELSE(f32, s32) mCpSelBookBrkFrame; /* 0x0310 */ J2DAnmTransform* mYnSelBck; /* 0x0314 */ J2DAnmTransform* mYnSelBck2; /* 0x0318 */ J2DAnmColor* mYnSelBpk; - /* 0x031C */ s32 mYnSelBpkFrame; + /* 0x031C */ DUSK_IF_ELSE(f32, s32) mYnSelBpkFrame; /* 0x0320 */ J2DAnmTextureSRTKey* mYnSelBtk; - /* 0x0324 */ s32 mYnSelBtkFrame; + /* 0x0324 */ DUSK_IF_ELSE(f32, s32) mYnSelBtkFrame; /* 0x0328 */ J2DAnmTransform* m3mBck; /* 0x032C */ J2DAnmTransform* m3mBck2; /* 0x0330 */ J2DAnmColor* m3mBpk; - /* 0x0334 */ s32 m3mBpkFrame; + /* 0x0334 */ DUSK_IF_ELSE(f32, s32) m3mBpkFrame; /* 0x0338 */ J2DAnmTextureSRTKey* m3mBtk; - /* 0x033C */ s32 m3mBtkFrame; + /* 0x033C */ DUSK_IF_ELSE(f32, s32) m3mBtkFrame; /* 0x0340 */ CPaneMgr* m3mSelPane[3]; - /* 0x034C */ s32 field_0x034c[3]; - /* 0x0358 */ int field_0x0358; + /* 0x034C */ DUSK_IF_ELSE(f32, s32) field_0x034c[3]; + /* 0x0358 */ DUSK_IF_ELSE(f32, int) field_0x0358; /* 0x035C */ int field_0x035c; /* 0x0360 */ bool field_0x0360; /* 0x0361 */ u8 field_0x0361[3]; @@ -694,7 +697,7 @@ public: /* 0x0390 */ u8 field_0x0391[3]; /* 0x0394 */ CPaneMgr* m3mSelTextPane[3]; /* 0x03A0 */ J2DAnmTextureSRTKey* mSelDtBtk; - /* 0x03A4 */ s32 mSelDtBtkFrame; + /* 0x03A4 */ DUSK_IF_ELSE(f32, s32) mSelDtBtkFrame; /* 0x03A8 */ CPaneMgr* mSelDtPane_mset; /* 0x03AC */ s16 mWaitTimer; /* 0x03AE */ u8 field_0x03AE[0x03B0 - 0x03AE]; @@ -706,26 +709,24 @@ public: /* 0x2374 */ u8 mFadeFlag; /* 0x2375 */ bool mHasDrawn; - #if PLATFORM_GCN +#if PLATFORM_GCN /* 0x2378 */ J2DPicture* mpFadePict; - #endif -#ifdef TARGET_PC - dDlst_FileSelFade_c mFadeDlst; - bool mGameModeSaveStartBuildUi = true; - GameModeNewSaveState mGameModeNewSaveState = GAME_MODE_STATE_PENDING; #endif - #if PLATFORM_WII || PLATFORM_SHIELD +#if PLATFORM_WII || PLATFORM_SHIELD /* 0x2376 */ u8 field_0x2376[SAVEFILE_SIZE]; /* 0x4332 */ u8 field_0x4332; /* 0x4333 */ u8 field_0x4333; - #endif -}; - -#ifdef TARGET_PC -STATIC_ASSERT(sizeof(dFile_select_c) == 0x237C + sizeof(dDlst_FileSelFade_c)); -#else -STATIC_ASSERT(sizeof(dFile_select_c) == 0x237C); #endif +#if TARGET_PC + dDlst_FileSelFade_c mFadeDlst; + bool mGameModeSaveStartBuildUi = true; + GameModeNewSaveState mGameModeNewSaveState = GAME_MODE_STATE_PENDING; + bool mNameMoveRequested = false; +#endif +}; + +STATIC_ASSERT(sizeof(dFile_select_c) == 0x237C); + #endif /* D_FILE_D_FILE_SELECT_H */ diff --git a/include/d/d_kantera_icon_meter.h b/include/d/d_kantera_icon_meter.h index 67e2dff278..cf1464e82f 100644 --- a/include/d/d_kantera_icon_meter.h +++ b/include/d/d_kantera_icon_meter.h @@ -13,7 +13,6 @@ public: void setScreen(J2DScreen* screen) { mp_scrn = screen; } J2DScreen* getScreen() { return mp_scrn; } -private: /* 0x04 */ J2DScreen* mp_scrn; }; @@ -30,7 +29,6 @@ public: void drawSelf() { mpKanteraIcon->draw(); } -// private: /* 0x04 */ dDlst_KanteraIcon_c* mpKanteraIcon; /* 0x08 */ CPaneMgr* mpParent; /* 0x0C */ CPaneMgr* mpGauge; diff --git a/include/d/d_ky_thunder.h b/include/d/d_ky_thunder.h index 1e174ad6f8..5a72252a9a 100644 --- a/include/d/d_ky_thunder.h +++ b/include/d/d_ky_thunder.h @@ -25,7 +25,6 @@ public: ~dThunder_c() { mDoExt_destroySolidHeap(mpHeap); } -private: /* 0x0F8 */ JKRSolidHeap* mpHeap; /* 0x0FC */ dThunder_modelInfo_c mModelInfo; /* 0x138 */ cXyz field_0x138; diff --git a/include/d/d_map.h b/include/d/d_map.h index b42def27c7..b70238cc46 100644 --- a/include/d/d_map.h +++ b/include/d/d_map.h @@ -134,7 +134,6 @@ public: static const int PALETTE_NUMBER = 50; #endif -private: /* 0x34 */ s32 m_outSideBlackLineCnt; /* 0x38 */ s32 field_0x38; /* 0x3C */ s32 field_0x3c; @@ -199,7 +198,6 @@ public: static dMap_c* m_mySelfPointer; -private: /* 0x40 */ dMap_prm_res_s* m_res_src; /* 0x44 */ dMap_prm_res_s* m_res; /* 0x48 */ ResTIMG* mResTIMG; diff --git a/include/d/d_menu_collect.h b/include/d/d_menu_collect.h index 0811fb00a6..0a7dcbf0d3 100644 --- a/include/d/d_menu_collect.h +++ b/include/d/d_menu_collect.h @@ -129,7 +129,6 @@ public: J2DPicture* getBlackTex() { return mpBlackTex; } u8 getSubWindowOpenCheck() { return mSubWindowOpenCheck; } -// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRExpHeap* mpSubHeap; /* 0x00C */ void* field_0xc; @@ -195,6 +194,9 @@ public: /* 0x25F */ u8 field_0x25f; /* 0x260 */ u8 field_0x260; /* 0x261 */ u8 field_0x261; +#if TARGET_PC + f32 mBackAlpha = 0.0f; +#endif }; class dMenu_Collect3D_c { @@ -225,7 +227,6 @@ public: static DUSK_GAME_DATA f32 mViewOffsetY; -private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRSolidHeap* mpSolidHeap; /* 0x00C */ dMenu_Collect2D_c* mpCollect2D; @@ -260,7 +261,6 @@ public: bool isKeyCheck() { return mpCollect2D->isKeyCheck(); } bool isOutCheck() { return mpCollect2D->isOutCheck(); } -private: /* 0x4 */ dMenu_Collect2D_c* mpCollect2D; /* 0x8 */ dMenu_Collect3D_c* mpCollect3D; }; diff --git a/include/d/d_menu_dmap.h b/include/d/d_menu_dmap.h index 4d1341cc67..2dd7630ba8 100644 --- a/include/d/d_menu_dmap.h +++ b/include/d/d_menu_dmap.h @@ -91,10 +91,6 @@ public: void calcCursor(); void drawCursor(); - #if TARGET_PC - void dMapBgWide(); - #endif - void setDPDFloorSelCurPos(s8 i_pos) { field_0xdd6 = i_pos; } f32 getMapWidth() { return mMapWidth; } @@ -108,7 +104,7 @@ public: } #if TARGET_PC - void resetScrollArrowMask() { field_0xdda = 0; } + void dMapBgWide(); #endif /* 0xC98 */ JKRExpHeap* mpHeap; @@ -174,16 +170,15 @@ public: /* 0xDCC */ f32 field_0xdcc; /* 0xDD0 */ u8 field_0xdd0; /* 0xDD1 */ u8 field_0xdd1; - /* 0xDD2 */ u8 field_0xdd2; + /* 0xDD2 */ DUSK_IF_ELSE(f32, u8) field_0xdd2; /* 0xDD3 */ u8 field_0xdd3; /* 0xDD4 */ s8 field_0xdd4; /* 0xDD5 */ u8 field_0xdd5; /* 0xDD6 */ s8 field_0xdd6; - /* 0xDD7 */ u8 field_0xdd7; + /* 0xDD7 */ DUSK_IF_ELSE(f32, u8) field_0xdd7; /* 0xDD8 */ u8 field_0xdd8; /* 0xDD9 */ u8 field_0xdd9; /* 0xDDA */ u8 field_0xdda; - #if TARGET_PC J2DTextBox* mpPoeCountPane; J2DPicture* mpPoeCountIcon; @@ -210,6 +205,10 @@ public: void mapControl(); bool isOpen(); bool isClose(); +#if TARGET_PC + void presentAnims(); + void presentMapView(); +#endif void _draw(); void itemInfo_init_proc(); void itemInfo_proc(); @@ -255,7 +254,6 @@ public: static DUSK_GAME_DATA dMenu_Dmap_c* myclass; -private: /* 0x004 */ dMenu_DmapMapCtrl_c* mMapCtrl; /* 0x008 */ dMenu_DmapBg_c* mpDrawBg; /* 0x00C */ u8 field_0xc[0x10 - 0xC]; diff --git a/include/d/d_menu_dmap_map.h b/include/d/d_menu_dmap_map.h index 3f3eb37a0f..6b16730360 100644 --- a/include/d/d_menu_dmap_map.h +++ b/include/d/d_menu_dmap_map.h @@ -191,10 +191,10 @@ public: /* 0xEE */ s8 field_0xee; /* 0xEF */ u8 field_0xef; /* 0xF0 */ u8 field_0xf0; - /* 0xF1 */ u8 field_0xf1; - /* 0xF2 */ u8 field_0xf2; - /* 0xF3 */ u8 field_0xf3; - /* 0xF4 */ s8 field_0xf4; + /* 0xF1 */ DUSK_IF_ELSE(f32, u8) field_0xf1; + /* 0xF2 */ DUSK_IF_ELSE(f32, u8) field_0xf2; + /* 0xF3 */ DUSK_IF_ELSE(f32, u8) field_0xf3; + /* 0xF4 */ DUSK_IF_ELSE(f32, s8) field_0xf4; /* 0xF5 */ u8 field_0xf5; /* 0xF6 */ u8 field_0xf6; /* 0xF7 */ s8 field_0xf7; diff --git a/include/d/d_menu_fishing.h b/include/d/d_menu_fishing.h index f2e9419ce3..b3b451c968 100644 --- a/include/d/d_menu_fishing.h +++ b/include/d/d_menu_fishing.h @@ -43,7 +43,6 @@ public: u8 getStatus() { return mStatus; } -// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRArchive* mpArchive; /* 0x00C */ STControl* mpStick; diff --git a/include/d/d_menu_fmap.h b/include/d/d_menu_fmap.h index 2e5ee95772..c3ea6ec10b 100644 --- a/include/d/d_menu_fmap.h +++ b/include/d/d_menu_fmap.h @@ -112,12 +112,20 @@ public: class dMenu_Fmap_c { public: typedef void (dMenu_Fmap_c::*process)(); - + dMenu_Fmap_c(JKRExpHeap*, STControl*, CSTControl*, u8, u8, u8, f32, f32, u8); void _create(); void _delete(); void _move(); void _draw(); +#if TARGET_PC + void presentAnims(); + void presentZoomView(u8 process, f32 zoomLevel, f32 zoomBlend); + void captureRenderState(); + void captureZoomEnd(u8 process); + void resetRenderState(); + void resetZoomEnd(); +#endif u8 getNextStatus(u8*); bool isSync(); void all_map_init(); @@ -266,11 +274,10 @@ public: /* 0x1B */ PROC_HOWL_DEMO1, /* 0x1C */ PROC_HOWL_DEMO2, /* 0x1D */ PROC_HOWL_DEMO3, - }; + }; static DUSK_GAME_DATA dMenu_Fmap_c* MyClass; -// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRExpHeap* mpTalkHeap; /* 0x00C */ STControl* mpStick; diff --git a/include/d/d_menu_fmap2D.h b/include/d/d_menu_fmap2D.h index 3c37c3da74..be638899bd 100644 --- a/include/d/d_menu_fmap2D.h +++ b/include/d/d_menu_fmap2D.h @@ -156,9 +156,6 @@ public: void setRegionCursor(u8 i_value) { mRegionCursor = i_value; } void setMapDrawFlag(bool i_flag) { mMapDrawFlag = i_flag; } void resetDrug() { field_0x1238 = 0; } -#if TARGET_PC - void resetScrollArrowMask() { field_0x122d = 0; } -#endif void offArrowDrawFlag() { mArrowDrawFlag = false; } void onArrowDrawFlag() { mArrowDrawFlag = true; } @@ -288,8 +285,8 @@ public: /* 0x120C */ JUtility::TColor field_0x120c; /* 0x1210 */ float field_0x1210; /* 0x1214 */ float field_0x1214; - /* 0x1218 */ s16 field_0x1218; - /* 0x121A */ s16 field_0x121a; + /* 0x1218 */ DUSK_IF_ELSE(f32, s16) field_0x1218; + /* 0x121A */ DUSK_IF_ELSE(f32, s16) field_0x121a; /* 0x121C */ u8 mRegionTextureReadNum[8]; /* 0x1224 */ u8 mSpotNum; /* 0x1225 */ u8 mStageDataNum; diff --git a/include/d/d_menu_fmap_map.h b/include/d/d_menu_fmap_map.h index b0d1063d18..b92cceec76 100644 --- a/include/d/d_menu_fmap_map.h +++ b/include/d/d_menu_fmap_map.h @@ -172,6 +172,10 @@ public: ~dMenu_FmapMap_c(); void _create(u16, u16, u16, u16, void*); void _delete(); +#if TARGET_PC + void presentAnims(); + void presentRendering(dMenu_Fmap_world_data_c*, int, f32, f32, f32, f32); +#endif void draw(); void rendering(line_class const*); int getLineWidth(int); @@ -224,9 +228,15 @@ public: /* 0xDC */ int mStageCursor; /* 0xE0 */ int mLastStageCursor; /* 0xE4 */ u8 mRegionCursor; - /* 0xE5 */ u8 mFlashTimer; + /* 0xE5 */ DUSK_IF_ELSE(f32, u8) mFlashTimer; /* 0xE8 */ u8* mp_roomList; /* 0xEC */ int m_roomListNumber; +#if TARGET_PC + f32 mFlashFrame = 0.0f; + f32 mPaletteFrame[2] = {}; + bool mResetFlashFrame = true; + bool mPaletteFrameInitialized = false; +#endif static dMenu_FmapMap_c* mMySelfPointer; }; diff --git a/include/d/d_menu_insect.h b/include/d/d_menu_insect.h index 5e0b453949..43f4af0602 100644 --- a/include/d/d_menu_insect.h +++ b/include/d/d_menu_insect.h @@ -60,7 +60,6 @@ public: u8 getStatus() { return mStatus; } -// private: /* 0x04 */ JKRExpHeap* mpHeap; /* 0x08 */ JKRArchive* mpArchive; /* 0x0C */ STControl* mpStick; diff --git a/include/d/d_menu_item_explain.h b/include/d/d_menu_item_explain.h index 7b79ebbad5..59d212e53f 100644 --- a/include/d/d_menu_item_explain.h +++ b/include/d/d_menu_item_explain.h @@ -51,7 +51,6 @@ public: field_0xd8 = y; } -private: /* 0x04 */ JKRExpHeap* mpHeap; /* 0x08 */ JKRArchive* mpArchive; /* 0x0C */ dMsgString_c* mpInfoString; diff --git a/include/d/d_menu_letter.h b/include/d/d_menu_letter.h index 21747c7e78..f714205853 100644 --- a/include/d/d_menu_letter.h +++ b/include/d/d_menu_letter.h @@ -57,6 +57,7 @@ public: #if TARGET_PC bool pointerWait(); + void presentAnims(); #endif virtual void draw() { _draw(); } @@ -64,7 +65,6 @@ public: u8 getStatus() { return mStatus; } -// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRArchive* mpArchive; /* 0x00C */ STControl* mpStick; @@ -110,7 +110,7 @@ public: /* 0x360 */ f32 field_0x360; /* 0x364 */ f32 field_0x364; /* 0x368 */ s16 field_0x368; - /* 0x36A */ s16 field_0x36a; + /* 0x36A */ DUSK_IF_ELSE(f32, s16) field_0x36a; /* 0x36C */ u8 mStatus; /* 0x36D */ u8 mProcess; /* 0x36E */ u8 mIndex; diff --git a/include/d/d_menu_map_common.h b/include/d/d_menu_map_common.h index 55de3d0f6c..87ffa25136 100644 --- a/include/d/d_menu_map_common.h +++ b/include/d/d_menu_map_common.h @@ -123,8 +123,8 @@ public: /* 0x06C */ u32 _6c; /* 0x070 */ IconInfo_s mIconInfo[128]; /* 0xC70 */ u16 mIconNum; - /* 0xC72 */ s16 mBlinkTimer; - /* 0xC74 */ s16 mLightDropFlashTimer; + /* 0xC72 */ DUSK_IF_ELSE(f32, s16) mBlinkTimer; + /* 0xC74 */ DUSK_IF_ELSE(f32, s16) mLightDropFlashTimer; /* 0xC76 */ u16 _c76; /* 0xC78 */ f32 mBlinkAlpha; /* 0xC7C */ f32 _c7c; diff --git a/include/d/d_menu_option.h b/include/d/d_menu_option.h index f158498dd7..8ccacf6490 100644 --- a/include/d/d_menu_option.h +++ b/include/d/d_menu_option.h @@ -96,6 +96,8 @@ public: bool dpdMenuMove(); #if TARGET_PC bool pointerConfirmSelect(); + void presentLayoutAnims(); + void presentAnims(); #endif void paneResize(u64); void initialize(); @@ -117,7 +119,6 @@ public: return ((u8)mUseFlag & (u8)i_flag) != 0 ? true : false; } -// private: /* 0x004 */ J2DScreen* mpBackScreen; /* 0x008 */ J2DScreen* mpScreen; /* 0x00C */ J2DScreen* mpClipScreen; @@ -191,11 +192,11 @@ public: /* 0x3B4 */ f32 field_0x3b4; /* 0x3B8 */ JUtility::TColor field_0x3b8; /* 0x3BC */ JUtility::TColor field_0x3bc; - /* 0x3C0 */ int field_0x3c0; - /* 0x3C4 */ int field_0x3c4; - /* 0x3C8 */ int field_0x3c8[4]; + /* 0x3C0 */ DUSK_IF_ELSE(f32, int) field_0x3c0; + /* 0x3C4 */ DUSK_IF_ELSE(f32, int) field_0x3c4; + /* 0x3C8 */ DUSK_IF_ELSE(f32, int) field_0x3c8[4]; /* 0x3D8 */ s16 mFrame; - /* 0x3DA */ s16 field_0x3da; + /* 0x3DA */ DUSK_IF_ELSE(f32, s16) field_0x3da; /* 0x3DC */ u16 field_0x3dc; /* 0x3DE */ u16 field_0x3de; /* 0x3E0 */ u8 field_0x3e0; diff --git a/include/d/d_menu_ring.h b/include/d/d_menu_ring.h index d0f021339e..eb569da86b 100644 --- a/include/d/d_menu_ring.h +++ b/include/d/d_menu_ring.h @@ -27,6 +27,10 @@ public: void _delete(); void _move(); void _draw(); +#if TARGET_PC + void advanceSelectItem(); + void captureRenderState(); +#endif void setKanteraPos(f32, f32); bool isOpen(); bool isMoveEnd(); @@ -84,7 +88,6 @@ public: void drawFlag0() { mDrawFlag = 0; } void setStatus(u8 i_status) { mStatus = i_status; } -// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ STControl* mpStick; /* 0x00C */ CSTControl* mpCStick; @@ -167,7 +170,7 @@ public: /* 0x67E */ s16 field_0x67e; /* 0x680 */ s16 mCursorSpeed; /* 0x682 */ s16 field_0x682; - /* 0x684 */ s16 field_0x684; + /* 0x684 */ DUSK_IF_ELSE(f32, s16) field_0x684; /* 0x686 */ s16 field_0x686[4]; /* 0x68E */ s16 field_0x68e; /* 0x690 */ u8 mItemSlots[MAX_ITEM_SLOTS]; @@ -208,16 +211,6 @@ public: /* 0x6D2 */ u8 field_0x6d2; /* 0x6D3 */ u8 field_0x6d3; #if TARGET_PC - f32 mSelectItemSlideElapsed[4]; - f32 mCursorInterpPrevX; - f32 mCursorInterpPrevY; - f32 mCursorInterpCurrX; - f32 mCursorInterpCurrY; - s16 mCursorInterpPrevAngle; - s16 mCursorInterpCurrAngle; - bool mCursorInterpPrevAngular; - bool mCursorInterpCurrAngular; - bool mCursorInterpInit; bool mPointerTouchPressHoveredCurrent; #endif }; diff --git a/include/d/d_menu_save.h b/include/d/d_menu_save.h index 7e94d1d1c9..b12322ba8c 100644 --- a/include/d/d_menu_save.h +++ b/include/d/d_menu_save.h @@ -27,7 +27,6 @@ public: void setScrnExplain(dMsgScrnExplain_c* p_scrn) { mpScrn = p_scrn; } -private: /* 0x4 */ dMsgScrnExplain_c* mpScrn; }; @@ -264,11 +263,12 @@ public: void setInitSaveData(); void _draw(); - #if TARGET_PC +#if TARGET_PC void menuSaveWide(); bool pointerSaveSelect(); bool pointerYesNoSelect(bool errorSelect, u8 errParam = 0, u8 soundParam = 0); - #endif + void presentLayoutAnims(); +#endif void _draw2(); @@ -278,7 +278,6 @@ public: u8 getEndStatus() { return mEndStatus; } void setUseType(u8 type) { mUseType = type; } -// private: /* 0x0004 */ JKRArchive* mpArchive; /* 0x0008 */ mDoDvdThd_mountArchive_c* mpMount; /* 0x000C */ STControl* stick; @@ -295,16 +294,16 @@ public: /* 0x0054 */ u8 mSelectedFile; /* 0x0055 */ u8 mLastSelFile; /* 0x0058 */ CPaneMgr* mpSelectMoveBase; - /* 0x005C */ int mDataBaseMoveAnmFrame; + /* 0x005C */ DUSK_IF_ELSE(f32, int) mDataBaseMoveAnmFrame; /* 0x0060 */ int mDataBaseMoveFrameMax; /* 0x0064 */ u8 field_0x64; /* 0x0065 */ u8 field_0x65; /* 0x0068 */ CPaneMgr* mpSelData[3]; - /* 0x0074 */ int field_0x74[3]; + /* 0x0074 */ DUSK_IF_ELSE(f32, int) field_0x74[3]; /* 0x0080 */ int field_0x80; /* 0x0084 */ CPaneMgr* mpNoYes[2]; - /* 0x008C */ int field_0x8c[2]; - /* 0x0094 */ int mYesNoMoveAnmFrame; + /* 0x008C */ DUSK_IF_ELSE(f32, int) field_0x8c[2]; + /* 0x0094 */ DUSK_IF_ELSE(f32, int) mYesNoMoveAnmFrame; /* 0x0098 */ int mYesNoMoveAnmMax; /* 0x009C */ u8 field_0x9c; /* 0x009D */ u8 field_0x9d; @@ -312,7 +311,7 @@ public: /* 0x00A0 */ J2DPane* field_0xa0; /* 0x00A4 */ u8 field_0xa4[0x10]; /* 0x00B4 */ J2DPane* field_0xb4; - /* 0x00B8 */ int field_0xb8; + /* 0x00B8 */ DUSK_IF_ELSE(f32, int) field_0xb8; /* 0x00BC */ int field_0xbc; /* 0x00C0 */ CPaneMgrAlpha* mpErrTxtPane[2]; /* 0x00C8 */ TEXT_SPAN mpErrTxt[2]; @@ -336,15 +335,15 @@ public: /* 0x0134 */ u8 mNoYesWakuAnmTimer[2]; /* 0x0138 */ CPaneMgr* mpNoYesTxt[2]; /* 0x0140 */ J2DAnmColor* mpFileWakuAnm; - /* 0x0144 */ int mFileWakuAnmFrame; + /* 0x0144 */ DUSK_IF_ELSE(f32, int) mFileWakuAnmFrame; /* 0x0148 */ J2DAnmTextureSRTKey* mpFileWakuRotAnm; - /* 0x014C */ int mFileWakuRotAnmFrame; + /* 0x014C */ DUSK_IF_ELSE(f32, int) mFileWakuRotAnmFrame; /* 0x0150 */ J2DAnmColor* field_0x150; - /* 0x0154 */ int field_0x154; + /* 0x0154 */ DUSK_IF_ELSE(f32, int) field_0x154; /* 0x0158 */ J2DAnmTextureSRTKey* field_0x158; - /* 0x015C */ int field_0x15c; + /* 0x015C */ DUSK_IF_ELSE(f32, int) field_0x15c; /* 0x0160 */ J2DAnmTevRegKey* field_0x160; - /* 0x0164 */ int field_0x164; + /* 0x0164 */ DUSK_IF_ELSE(f32, int) field_0x164; /* 0x0168 */ CPaneMgrAlpha* mpHeaderTxtPane[2]; /* 0x0170 */ TEXT_SPAN mpHeaderTxt[2]; /* 0x0178 */ u8 mHeaderTxtType; // 0: Select Menu 1: YesNo Menu @@ -396,6 +395,9 @@ public: /* 0x21A1 */ u8 field_0x21a1; /* 0x21A2 */ u8 field_0x21a2; /* 0x21A3 */ u8 field_0x21a3; +#if TARGET_PC + u8 mYesNoSelAnmCol; +#endif }; #endif /* D_MENU_D_MENU_SAVE_H */ diff --git a/include/d/d_menu_skill.h b/include/d/d_menu_skill.h index d21332c341..bd5e16e879 100644 --- a/include/d/d_menu_skill.h +++ b/include/d/d_menu_skill.h @@ -51,6 +51,7 @@ public: #if TARGET_PC bool pointerWait(); + void presentAnims(); #endif virtual void draw() { _draw(); } @@ -58,7 +59,6 @@ public: u8 getStatus() { return mStatus; } -// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRArchive* mpArchive; /* 0x00C */ STControl* mpStick; @@ -91,7 +91,7 @@ public: /* 0x1F4 */ f32 mBarScale[2]; /* 0x1FC */ u32 mStringID; /* 0x200 */ s16 mFrame; - /* 0x202 */ s16 mProcFrame; + /* 0x202 */ DUSK_IF_ELSE(f32, s16) mProcFrame; /* 0x204 */ u8 mStatus; /* 0x205 */ u8 mProcess; /* 0x206 */ u8 mIndex; diff --git a/include/d/d_menu_window.h b/include/d/d_menu_window.h index 65e415077d..1c5aeadbe5 100644 --- a/include/d/d_menu_window.h +++ b/include/d/d_menu_window.h @@ -193,7 +193,6 @@ public: bool isShowFlag() { return (mShowFlag & 1) != 0; } bool isFadeNowCheck() { return mDoGph_gInf_c::getFader()->getStatus() == 1; } -private: /* 0x0FC */ int field_0xfc; /* 0x100 */ JKRExpHeap* mpHeap; /* 0x104 */ STControl* mpStick; diff --git a/include/d/d_meter2.h b/include/d/d_meter2.h index c8dc17bca8..b3bde323ae 100644 --- a/include/d/d_meter2.h +++ b/include/d/d_meter2.h @@ -100,7 +100,11 @@ public: bool isShowFlag(int i_no) { return field_0x1e6 & (1 << i_no); } void onShowFlag(int i_no) { field_0x1e6 |= (1 << i_no); } -private: +#if TARGET_PC + void presentAnims(); + void presentMap(); +#endif + /* 0x0FC */ int field_0xfc; /* 0x100 */ JKRExpHeap* mpHeap; /* 0x104 */ JKRExpHeap* mpSubHeap; @@ -300,6 +304,16 @@ private: /* 0x460 */ u8 field_0x460[0x4bc - 0x460]; /* 0x4BC */ u8 field_0x4bc; /* 0x4BC */ u8 field_0x4bd; + +#if TARGET_PC + struct { + f32 vesselX, vesselY, vesselScale, vesselAlpha; + f32 buttonAX[2], buttonAY[2]; + f32 buttonBX[2], buttonBY[2]; + f32 crossX, crossY; + bool ready; + } mPresentationTargets; +#endif }; #endif /* D_METER_D_METER2_H */ diff --git a/include/d/d_meter2_draw.h b/include/d/d_meter2_draw.h index aaad9dec70..927d71d63c 100644 --- a/include/d/d_meter2_draw.h +++ b/include/d/d_meter2_draw.h @@ -11,6 +11,7 @@ class JKRExpHeap; class JKRHeap; class dKantera_icon_c; +IF_DUSK(class dMeterMap_c); class dMeter2Draw_c : public dDlst_base_c { public: @@ -46,7 +47,8 @@ public: void playPikariBpkAnimation(f32); void playOxygenBpkAnimation(J2DAnmColor*); void drawPikari(f32, f32, f32*, f32, JUtility::TColor, JUtility::TColor, - JUtility::TColor, JUtility::TColor, f32, u8); + JUtility::TColor, JUtility::TColor, f32, u8 + IF_DUSK_ARG(f32 elapsedFrames = -1.0f)); void drawPikari(CPaneMgr*, f32*, f32, JUtility::TColor, JUtility::TColor, JUtility::TColor, JUtility::TColor, f32, u8); void drawPikariHakusha(f32, f32, f32, f32, JUtility::TColor, JUtility::TColor, @@ -59,11 +61,11 @@ public: void drawKanteraScreen(u8); void drawMagic(s16, s16, f32, f32); void setAlphaMagicChange(bool); - void drawKantera(s32, s32, f32, f32); + void drawKantera(s32, DUSK_IF_ELSE(f32, s32), f32, f32); void setAlphaKanteraChange(bool); void setAlphaKanteraAnimeMin(); void setAlphaKanteraAnimeMax(); - void drawOxygen(s32, s32, f32, f32); + void drawOxygen(s32, DUSK_IF_ELSE(f32, s32), f32, f32); void setAlphaOxygenChange(bool); void setAlphaOxygenAnimeMin(); void setAlphaOxygenAnimeMax(); @@ -134,7 +136,11 @@ public: J2DScreen* getMainScreenPtr() { return mpScreen; } bool isEmphasisC() { return field_0x766 == 7 ? true : false; } +#if TARGET_PC + f32 getMeterGaugeAlphaRate(u8 i_no); +#else f32 getMeterGaugeAlphaRate(u8 i_no) { return mMeterAlphaRate[i_no]; } +#endif bool isEmphasisA() { return field_0x761 == 7 ? true : false; } bool isEmphasisB() { return field_0x762 == 7 ? true : false; } bool isEmphasisBin() { return field_0x763 == 7 ? true : false; } @@ -152,9 +158,29 @@ public: constexpr f32 getButtonZAlpha() const { return mButtonZAlpha; } + + void beginExecution(); + void endExecution(); + bool isExecuting() const { return mExecuting; } + void setExecuting(bool executing) { mExecuting = executing; } + + void publishKantera(s32 now, s32 max); + void publishOxygen(s32 now, s32 max); + + void present(); + void presentVessel(f32 i_posX, f32 i_posY, f32 i_scale); + void presentButtonA(f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, f32 i_scale); + void presentButtonB(f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, f32 i_scale); + void presentMapAlpha(dMeterMap_c* map); + + void showLightDrop(bool show, bool get_path = false); + void showCross(bool show); + void showMap(bool show); + + bool lightDropRowUsed() const; + void resetRows(); #endif -// private: /* 0x004 */ item_params mItemParams[4]; /* 0x074 */ JKRExpHeap* heap; /* 0x078 */ J2DScreen* mpScreen; @@ -285,7 +311,7 @@ public: /* 0x742 */ s16 field_0x742[3]; /* 0x748 */ u8 field_0x748[0xC]; /* 0x756 */ u16 field_0x754; - /* 0x756 */ s16 field_0x756; + /* 0x756 */ DUSK_IF_ELSE(f32, s16) field_0x756; /* 0x758 */ u8 field_0x758; /* 0x759 */ u8 field_0x759; /* 0x75A */ u8 field_0x75a; @@ -364,6 +390,9 @@ public: /* 0x858 */ GXColor mButtonZTextColor; /* 0x85C */ GXColor mButtonXYTextColor; /* 0x860 */ u8 field_0x860[2]; +#if TARGET_PC + bool mExecuting; +#endif }; #endif /* D_METER_D_METER2_DRAW_H */ diff --git a/include/d/d_meter_button.h b/include/d/d_meter_button.h index 2d9a4bb5d8..04a2f7f488 100644 --- a/include/d/d_meter_button.h +++ b/include/d/d_meter_button.h @@ -255,7 +255,7 @@ public: /* 0x4B2 */ u16 mMsgID; /* 0x4B4 */ s16 field_0x4b4; /* 0x4B6 */ s16 field_0x4b6; - /* 0x4B8 */ s16 field_0x4b8[2]; + /* 0x4B8 */ DUSK_IF_ELSE(f32, s16) field_0x4b8[2]; /* 0x4BC */ u8 field_0x4bc[2]; /* 0x4BE */ u8 field_0x4be[2]; /* 0x4C0 */ bool mPlayedButtonSound[BUTTON_NUM]; @@ -344,10 +344,8 @@ public: /* 0x624 */ f32 mMidonaPosX; /* 0x628 */ f32 mMidonaPosY; /* 0x62C */ f32 mMidonaScale; - -#ifdef TARGET_PC - bool mWasListen[2]; - bool mWasRepeat[2]; +#if TARGET_PC + u16 mBlinkButton = 0; #endif }; diff --git a/include/d/d_meter_map.h b/include/d/d_meter_map.h index b17f51846e..630d50b71d 100644 --- a/include/d/d_meter_map.h +++ b/include/d/d_meter_map.h @@ -4,6 +4,10 @@ #include "d/d_map_path_dmap.h" #include "JSystem/JHostIO/JORReflexible.h" +#if TARGET_PC +#include "global.h" +#endif + class J2DPicture; class dMap_c; class dMap_HIO_c; @@ -102,7 +106,6 @@ public: /* 0x04 */ u8 field_0x4_debug; /* 0x08 */ int field_0x8_debug; #endif -private: /* 0x04 */ J2DPicture* mMapJ2DPicture; /* 0x08 */ dMap_c* mMap; /* 0x0C */ s32 mIsCompass; @@ -112,13 +115,16 @@ private: /* 0x1C */ f32 mDrawPosY; /* 0x20 */ f32 mSizeW; /* 0x24 */ f32 mSizeH; - /* 0x28 */ s16 mSlidePositionOffset; + /* 0x28 */ DUSK_IF_ELSE(f32, s16) mSlidePositionOffset; /* 0x2A */ u8 field_0x2a; /* 0x2B */ u8 field_0x2b; /* 0x2C */ u8 mMapAlpha; /* 0x2D */ u8 mMapIsInside; /* 0x2E */ u8 field_0x2e; /* 0x30 */ int field_0x30; +#if TARGET_PC + f32 mSlidePositionOffsetTarget; +#endif }; #endif /* D_METER_D_METER_MAP_H */ diff --git a/include/d/d_meter_string.h b/include/d/d_meter_string.h index 382fcbf99b..a02daea8d7 100644 --- a/include/d/d_meter_string.h +++ b/include/d/d_meter_string.h @@ -17,7 +17,12 @@ public: virtual int _execute(u32); virtual int _delete(); - f32 acc(s16 param_0, s16 param_1, s16 param_2) { + f32 acc(DUSK_IF_ELSE(f32, s16) param_0, DUSK_IF_ELSE(f32, s16) param_1, DUSK_IF_ELSE(f32, s16) param_2) { +#if TARGET_PC + if (param_0 == param_2) { + return 1.0f; + } +#endif return ((f32)(param_1 - param_2) * (f32)(param_1 - param_2)) / ((f32)(param_0 - param_2) * (f32)(param_0 - param_2)); } diff --git a/include/d/d_model.h b/include/d/d_model.h index 72949f5f03..466592d89c 100644 --- a/include/d/d_model.h +++ b/include/d/d_model.h @@ -33,7 +33,6 @@ public: u16 getMaterialId() { return mMaterialId; } dKy_tevstr_c* getTevstr() { return mpTevstr; } -private: /* 0x10 */ J3DModelData* mpModelData; /* 0x14 */ dKy_tevstr_c* mpTevstr; /* 0x18 */ u16 mMaterialId; @@ -55,7 +54,6 @@ public: static DUSK_GAME_DATA dMdl_mng_c* m_myObj; -private: /* 0x00 */ dMdl_c field_0x0[4]; /* 0x80 */ u8 field_0x80; }; diff --git a/include/d/d_msg_class.h b/include/d/d_msg_class.h index c70345ca92..bd26cae084 100644 --- a/include/d/d_msg_class.h +++ b/include/d/d_msg_class.h @@ -4,10 +4,11 @@ #include "JSystem/JMessage/control.h" #include "JSystem/JMessage/JMessage.h" #include "SSystem/SComponent/c_xyz.h" + +#if TARGET_PC #include "helpers/endian.h" #include "helpers/string.hpp" -#if TARGET_PC #define D_MSG_CLASS_PAGE_CNT_MAX 40 #define D_MSG_CLASS_CHAR_CNT_MAX 0x210 #define D_MSG_CLASS_LINE_MAX 12 @@ -196,12 +197,16 @@ struct jmessage_tReference : public JMessage::TReference { void addDrawLightCount() { mDrawLightCount++; } void setCharAlpha(f32 alpha) { mCharAlpha = alpha; } +#if TARGET_PC + void addCharAlpha(); +#else void addCharAlpha() { mCharAlpha += mAddCharAlpha; if (mCharAlpha > 255.0f) { mCharAlpha = 255.0f; } } +#endif void setLineLength(int i_no, f32 i_strLen, f32 i_spaceLen) { mStrLength[i_no] = i_strLen; @@ -214,12 +219,16 @@ struct jmessage_tReference : public JMessage::TReference { } } +#if TARGET_PC + void addCharAllAlphaRate(); +#else void addCharAllAlphaRate() { mCharAllAlphaRate += mAddCharAllAlphaRate; if (mCharAllAlphaRate > 1.0f) { mCharAllAlphaRate = 1.0f; } } +#endif void addLineLength(int param_0, f32 param_1, f32 param_2) { mStrLength[param_0] += param_1; diff --git a/include/d/d_msg_flow.h b/include/d/d_msg_flow.h index cff5857cb7..b7ffb11fc7 100644 --- a/include/d/d_msg_flow.h +++ b/include/d/d_msg_flow.h @@ -2,7 +2,10 @@ #define D_MSG_D_MSG_FLOW_H #include + +#if TARGET_PC #include "helpers/endian.h" +#endif enum { NODETYPE_MESSAGE_e = 1, @@ -188,7 +191,6 @@ public: static DUSK_GAME_DATA queryFunc mQueryList[53]; static DUSK_GAME_DATA eventFunc mEventList[43]; -private: /* 0x04 */ u8* mFlow_p; /* 0x08 */ u8* mLabelInfo_p; /* 0x0C */ union mesg_flow { diff --git a/include/d/d_msg_object.h b/include/d/d_msg_object.h index 3e8c535800..84dbcc9f8d 100644 --- a/include/d/d_msg_object.h +++ b/include/d/d_msg_object.h @@ -21,6 +21,9 @@ public: int _create(msg_class*); int _execute(); int _draw(); +#if TARGET_PC + void presentAnims(); +#endif int _delete(); void setMessageIndex(u32, u32, bool); void setMessageIndexDemo(u32, bool); @@ -285,7 +288,7 @@ public: /* 0x164 */ u16 mNodeIdx; /* 0x166 */ u16 field_0x166; /* 0x168 */ u16 field_0x168; - /* 0x16A */ s16 field_0x16a; + /* 0x16A */ DUSK_IF_ELSE(f32, s16) field_0x16a; /* 0x16C */ s16 mCurrentGroupID; // group whose BMG is parsed; -1 none, 0 common, 1-8 stage /* 0x16E */ s16 field_0x16e; /* 0x170 */ s16 mNowTalkFlowNo; diff --git a/include/d/d_msg_out_font.h b/include/d/d_msg_out_font.h index c6715757eb..df9b9e4368 100644 --- a/include/d/d_msg_out_font.h +++ b/include/d/d_msg_out_font.h @@ -25,7 +25,6 @@ public: void setTextBoxPtr(J2DTextBox* p_textBox) { mpTextBoxPtr = p_textBox; } void resetType() { mType = 0x47; } -// private: /* 0x04 */ J2DTextBox* mpTextBoxPtr; /* 0x08 */ f32 mPosX; /* 0x0C */ f32 mPosY; @@ -40,7 +39,7 @@ class COutFont_c { public: COutFont_c(u8); void initialize(); - void setBlendAnime(J2DPicture*, s16); + void setBlendAnime(J2DPicture*, DUSK_IF_ELSE(f32, s16)); const char* getBtiName(int); virtual ~COutFont_c(); @@ -53,13 +52,12 @@ public: void setRupeeColor(u8 color) { mRupeeColor = color; } -// private: /* 0x004 */ COutFontSet_c* mpOfs[35]; /* 0x090 */ J2DPicture* mpPane[70]; /* 0x1A8 */ f32 mAlphaRatio; /* 0x1AC */ f32 field_0x1ac; /* 0x1B0 */ f32 field_0x1b0; - /* 0x1B4 */ s16 field_0x1b4[70]; + /* 0x1B4 */ DUSK_IF_ELSE(f32, s16) field_0x1b4[70]; /* 0x240 */ bool field_0x240; /* 0x241 */ u8 mRupeeColor; /* 0x242 */ u8 field_0x242; diff --git a/include/d/d_msg_scrn_3select.h b/include/d/d_msg_scrn_3select.h index 1cb92e5be6..f70bbe5789 100644 --- a/include/d/d_msg_scrn_3select.h +++ b/include/d/d_msg_scrn_3select.h @@ -26,10 +26,16 @@ public: dMsgScrn3Select_c(); virtual ~dMsgScrn3Select_c(); bool isSelect(); +#if TARGET_PC + bool isAnimeActive() { return mProcess < PROC_MAX_e; } +#endif void setString(DUSK_CONST char*, DUSK_CONST char*, DUSK_CONST char*); void setRubyString(DUSK_CONST char*, DUSK_CONST char*, DUSK_CONST char*); void translate(f32, f32); void draw(f32, f32); +#if TARGET_PC + void presentAnims(); +#endif void selAnimeInit(u8, u8, u8, f32, u8); bool selAnimeMove(u8, u8, bool); bool selAnimeEnd(); @@ -60,7 +66,6 @@ public: void offAnimeUpdate(int param_0) { field_0x114 &= ~(u8)(1 << param_0); } u8 getDPDPoint() { return mDPDPoint; } -private: /* 0x004 */ J2DScreen* mpScreen; /* 0x008 */ J2DAnmTransform* mpAnmBck; /* 0x00C */ J2DAnmColorKey* mpAnmBpk; diff --git a/include/d/d_msg_scrn_base.h b/include/d/d_msg_scrn_base.h index 17fd7380d3..fe91363970 100644 --- a/include/d/d_msg_scrn_base.h +++ b/include/d/d_msg_scrn_base.h @@ -37,6 +37,10 @@ public: virtual void fukiAlpha(f32); virtual void fontAlpha(f32); virtual void fukiPosCalc(u8) {} +#if TARGET_PC + virtual void presentAnims(); + virtual bool isSelectAnimeActive() { return false; } +#endif f32 getSelTextBoxPosX(int idx) { return mSelTextBoxPosX[idx]; } f32 getSelTextBoxPosY(int idx) { return mSelTextBoxPosY[idx]; } diff --git a/include/d/d_msg_scrn_boss.h b/include/d/d_msg_scrn_boss.h index d72c992b39..3adcc57289 100644 --- a/include/d/d_msg_scrn_boss.h +++ b/include/d/d_msg_scrn_boss.h @@ -15,7 +15,6 @@ public: virtual void fontAlpha(f32); virtual void fukiAlpha(f32); -private: /* 0xC4 */ CPaneMgr* mpBaseParent; /* 0xC8 */ CPaneMgr* mpFontParent; }; diff --git a/include/d/d_msg_scrn_explain.h b/include/d/d_msg_scrn_explain.h index 20931ec9ac..2868d173f9 100644 --- a/include/d/d_msg_scrn_explain.h +++ b/include/d/d_msg_scrn_explain.h @@ -23,6 +23,9 @@ public: dMsgScrnExplain_c(STControl*, u8, bool, u8); void move(); void draw(J2DOrthoGraph*); +#if TARGET_PC + void presentAnims(); +#endif void wait_init(); void wait_proc(); void open_request_init(); @@ -49,7 +52,6 @@ public: void onForceSelect() { mForceSelect = 1; } void setKeyWaitTimer(s16 timer) { mKeyWaitTimer = timer; } -private: /* 0x04 */ dMsgString_c* mpString_c; /* 0x08 */ CPaneMgr* mpTm_c[2]; /* 0x10 */ CPaneMgr* mpTmr_c[2]; @@ -68,7 +70,7 @@ private: /* 0x50 */ f32 field_0x50; /* 0x54 */ u32 mOpenMsgId; /* 0x58 */ bool field_0x58; - /* 0x5A */ s16 field_0x5a; + /* 0x5A */ DUSK_IF_ELSE(f32, s16) field_0x5a; /* 0x5C */ s16 field_0x5c; /* 0x5E */ s16 mKeyWaitTimer; /* 0x60 */ u8 mStatus; diff --git a/include/d/d_msg_scrn_howl.h b/include/d/d_msg_scrn_howl.h index 5db5db0232..c168b1f4f3 100644 --- a/include/d/d_msg_scrn_howl.h +++ b/include/d/d_msg_scrn_howl.h @@ -8,6 +8,9 @@ struct dMsgScrnHowl_c : public dMsgScrnBase_c { ~dMsgScrnHowl_c(); void exec(); void drawSelf(); +#if TARGET_PC + void presentAnims(); +#endif void guide_on_init(); void guide_on_proc(); void guide_off_init(); @@ -77,7 +80,7 @@ struct dMsgScrnHowl_c : public dMsgScrnBase_c { /* 0x1A18 */ f32 field_0x1a18[30]; /* 0x1A90 */ f32 field_0x1a90[30]; /* 0x1B08 */ f32 field_0x1b08[3]; - /* 0x1B14 */ s16 field_0x1b14[0x300]; + /* 0x1B14 */ DUSK_IF_ELSE(f32, s16) field_0x1b14[0x300]; /* 0x2114 */ u32 field_0x2114; /* 0x2118 */ u32 field_0x2118; /* 0x211C */ u32 field_0x211c; @@ -113,6 +116,8 @@ struct dMsgScrnHowl_c : public dMsgScrnBase_c { #if TARGET_PC u8 showCursor; + f32 mSampleAcc; + f32 mPulseFrame; #endif }; diff --git a/include/d/d_msg_scrn_item.h b/include/d/d_msg_scrn_item.h index 865071a2af..a0387b8127 100644 --- a/include/d/d_msg_scrn_item.h +++ b/include/d/d_msg_scrn_item.h @@ -13,6 +13,9 @@ struct dMsgScrnItem_c : public dMsgScrnBase_c { ~dMsgScrnItem_c(); void exec(); void drawSelf(); +#if TARGET_PC + void presentAnims(); +#endif void arwAnimeInit(); void arwAnimeMove(); void dotAnimeInit(); @@ -23,6 +26,9 @@ struct dMsgScrnItem_c : public dMsgScrnBase_c { void selectAnimeInit(u8, u8, f32, u8); bool selectAnimeMove(u8, u8, bool); bool selectAnimeEnd(); +#if TARGET_PC + bool isSelectAnimeActive(); +#endif void fukiScale(f32); void fukiTrans(f32, f32); void fukiAlpha(f32); diff --git a/include/d/d_msg_scrn_kanban.h b/include/d/d_msg_scrn_kanban.h index bba4ebd734..7e9fcb1caf 100644 --- a/include/d/d_msg_scrn_kanban.h +++ b/include/d/d_msg_scrn_kanban.h @@ -13,6 +13,9 @@ struct dMsgScrnKanban_c : public dMsgScrnBase_c { ~dMsgScrnKanban_c(); void exec(); void draw(); +#if TARGET_PC + void presentAnims(); +#endif void fukiAlpha(f32); void fukiScale(f32); void fukiTrans(f32, f32); diff --git a/include/d/d_msg_scrn_talk.h b/include/d/d_msg_scrn_talk.h index 1c8283ad44..c33b67a76c 100644 --- a/include/d/d_msg_scrn_talk.h +++ b/include/d/d_msg_scrn_talk.h @@ -23,6 +23,10 @@ struct dMsgScrnTalk_c : public dMsgScrnBase_c { void selectAnimeInit(u8, u8, f32, u8); bool selectAnimeMove(u8, u8, bool); bool selectAnimeEnd(); +#if TARGET_PC + void presentAnims(); + bool isSelectAnimeActive(); +#endif void fukiScale(f32); void fukiTrans(f32, f32); void fukiAlpha(f32); @@ -42,7 +46,7 @@ struct dMsgScrnTalk_c : public dMsgScrnBase_c { /* 0x0F4 */ f32 field_0xf4; /* 0x0F8 */ f32 field_0xf8[150]; /* 0x350 */ f32 field_0x350[3]; - /* 0x35C */ s16 field_0x35c[150]; + /* 0x35C */ DUSK_IF_ELSE(f32, s16) field_0x35c[150]; /* 0x488 */ u8 field_0x488; }; diff --git a/include/d/d_msg_scrn_tree.h b/include/d/d_msg_scrn_tree.h index f30f8a6193..844d378efd 100644 --- a/include/d/d_msg_scrn_tree.h +++ b/include/d/d_msg_scrn_tree.h @@ -11,6 +11,9 @@ struct dMsgScrnTree_c : public dMsgScrnBase_c { virtual ~dMsgScrnTree_c(); void exec(); void draw(); +#if TARGET_PC + void presentAnims(); +#endif void fukiAlpha(f32); void fukiScale(f32); void fukiTrans(f32, f32); diff --git a/include/d/d_msg_string.h b/include/d/d_msg_string.h index 45a148b401..2d92939141 100644 --- a/include/d/d_msg_string.h +++ b/include/d/d_msg_string.h @@ -39,7 +39,6 @@ public: drawOutFontLocal(param_0, param_1); } -private: /* 0x24 */ COutFont_c* mpOutFont; /* 0x28 */ u8 field_0x28; }; diff --git a/include/d/d_msg_string_base.h b/include/d/d_msg_string_base.h index 4624393f06..020c557f85 100644 --- a/include/d/d_msg_string_base.h +++ b/include/d/d_msg_string_base.h @@ -30,7 +30,6 @@ public: virtual void drawOutFontLocal(J2DTextBox*, f32); virtual void drawFontLocal(J2DTextBox*, u8, f32, f32, f32, f32, u32, u8); -private: /* 0x04 */ JMessage::TResourceContainer* mpResCont; /* 0x08 */ jmessage_string_tControl* mpCtrl; /* 0x0C */ jmessage_string_tReference* mpRefer; diff --git a/include/d/d_name.h b/include/d/d_name.h index ddd99127c6..95779b576a 100644 --- a/include/d/d_name.h +++ b/include/d/d_name.h @@ -111,9 +111,9 @@ public: void menuCursorMove2(); void selectCursorPosSet(int); - #if TARGET_PC +#if TARGET_PC void nameWide(); - #endif +#endif void _draw(); void screenSet(); @@ -130,15 +130,14 @@ public: void setNextNameStr(char* i_name) { SAFE_STRCPY(mNextNameStr,i_name); } void draw() { _draw(); } -private: /* 0x004 */ STControl* stick; /* 0x008 */ JKRArchive* archive; /* 0x00C */ dDlst_NameIN_c nameIn; /* 0x020 */ dSelect_cursor_c* mSelIcon; /* 0x024 */ J2DAnmColorKey* mCursorColorKey; - /* 0x028 */ int mCurColAnmF; + /* 0x028 */ DUSK_IF_ELSE(f32, int) mCurColAnmF; /* 0x02C */ J2DAnmTextureSRTKey* mCursorTexKey; - /* 0x030 */ int mCurTexAnmF; + /* 0x030 */ DUSK_IF_ELSE(f32, int) mCurTexAnmF; /* 0x034 */ CPaneMgrAlpha* mNameCursor[8]; /* 0x054 */ TEXT_SPAN mNameText[8]; /* 0x074 */ CPaneMgr* mMojiIcon[65]; diff --git a/include/d/d_pane_class.h b/include/d/d_pane_class.h index 345cae1274..29cee0c2ea 100644 --- a/include/d/d_pane_class.h +++ b/include/d/d_pane_class.h @@ -4,6 +4,10 @@ #include "JSystem/JUtility/TColor.h" #include "d/d_pane_class_alpha.h" +#if TARGET_PC +#include +#endif + class JKRHeap; void dPaneClass_showNullPane(J2DScreen*); @@ -26,6 +30,9 @@ public: void setBlackWhite(JUtility::TColor, JUtility::TColor); void paneTrans(f32, f32); void paneScale(f32, f32); +#if TARGET_PC + void presentAnime(); +#endif bool scaleAnime(s16, f32, f32, u8); bool colorAnime(s16, JUtility::TColor, JUtility::TColor, JUtility::TColor, JUtility::TColor, u8); @@ -54,8 +61,14 @@ public: void resize(f32 x, f32 y) { getPanePtr()->resize(x, y); } void move(f32 x, f32 y) { getPanePtr()->move(x, y); } - void scaleAnimeStart(s16 v) { mScaleAnime = v; } - void colorAnimeStart(s16 start) { mColorAnime = start; } + void scaleAnimeStart(DUSK_IF_ELSE(f32, s16) v) { + mScaleAnime = v; + IF_DUSK(mScaleAnimation.active = false;) + } + void colorAnimeStart(DUSK_IF_ELSE(f32, s16) start) { + mColorAnime = start; + IF_DUSK(mColorAnimation.active = false;) + } f32 getPosX() { return getPanePtr()->getBounds().i.x; } f32 getPosY() { return getPanePtr()->getBounds().i.y; } @@ -93,7 +106,6 @@ public: JUtility::TColor getInitBlack() { return mInitBlack; } JUtility::TColor getInitWhite() { return mInitWhite; } -private: /* 0x1C */ void* mpFirstStackSize; /* 0x20 */ s16* field_0x20; /* 0x24 */ JGeometry::TVec2 mInitPos; @@ -107,10 +119,14 @@ private: /* 0x5C */ JUtility::TColor mInitBlack; /* 0x60 */ s16 field_0x60; /* 0x62 */ s16 field_0x62; - /* 0x64 */ s16 mScaleAnime; + /* 0x64 */ DUSK_IF_ELSE(f32, s16) mScaleAnime; /* 0x66 */ s16 field_0x66; /* 0x68 */ s16 field_0x68; - /* 0x6A */ s16 mColorAnime; + /* 0x6A */ DUSK_IF_ELSE(f32, s16) mColorAnime; +#if TARGET_PC + Animation mScaleAnimation; + Animation> mColorAnimation; +#endif }; #endif /* D_PANE_D_PANE_CLASS_H */ diff --git a/include/d/d_pane_class_alpha.h b/include/d/d_pane_class_alpha.h index 82590e5f02..f55227a3ee 100644 --- a/include/d/d_pane_class_alpha.h +++ b/include/d/d_pane_class_alpha.h @@ -9,6 +9,16 @@ class JKRExpHeap; class CPaneMgrAlpha { public: +#if TARGET_PC + template + struct Animation { + bool active = false; + s16 duration; + u8 curve; + T start, end; + }; +#endif + virtual ~CPaneMgrAlpha(); virtual void setAlpha(u8); @@ -18,9 +28,12 @@ public: void show(); void hide(); bool isVisible(); - f32 rateCalc(s16, s16, u8); + f32 rateCalc(DUSK_IF_ELSE(f32, s16), DUSK_IF_ELSE(f32, s16), u8); void setAlphaRate(f32); f32 getAlphaRate(); +#if TARGET_PC + void presentAlphaAnime(); +#endif bool alphaAnime(s16 timer, u8 startAlpha, u8 endAlpha, u8 calcType); bool alphaAnimeLoop(s16, u8, u8, u8); void childPaneCount(J2DPane*); @@ -29,8 +42,11 @@ public: J2DPane* getPanePtr() { return mPane; } u8 getAlpha() { return getPanePtr()->getAlpha(); } - s16 getAlphaTimer() { return mAlphaTimer; } - void alphaAnimeStart(s16 start) { mAlphaTimer = start; } + DUSK_IF_ELSE(f32, s16) getAlphaTimer() { return mAlphaTimer; } + void alphaAnimeStart(DUSK_IF_ELSE(f32, s16) start) { + mAlphaTimer = start; + IF_DUSK(mAlphaAnimation.active = false;) + } u8 getInitAlpha() { return mInitAlpha; } /* 0x04 */ J2DPane* mPane; @@ -38,9 +54,12 @@ public: /* 0x0C */ void* mpFirstStackAlpha; /* 0x10 */ u8* field_0x10; /* 0x14 */ s16 mChildPaneCount; - /* 0x16 */ s16 mAlphaTimer; + /* 0x16 */ DUSK_IF_ELSE(f32, s16) mAlphaTimer; /* 0x18 */ u8 mInitAlpha; /* 0x19 */ u8 mFlags; +#if TARGET_PC + Animation mAlphaAnimation; +#endif }; class CPaneMgrAlphaMorf : public CPaneMgrAlpha { @@ -54,7 +73,6 @@ public: virtual ~CPaneMgrAlphaMorf(); -private: /* 0x1C */ void* mpFirstSaveAlpha; /* 0x20 */ u8* field_0x20; /* 0x24 */ void* mpFirstGetAlpha; diff --git a/include/d/d_particle.h b/include/d/d_particle.h index 7ae4d12c07..a5849288f3 100644 --- a/include/d/d_particle.h +++ b/include/d/d_particle.h @@ -261,7 +261,6 @@ public: void setRate(f32 rate) { mRate = rate; } void setMaxCnt(int i_maxCnt) { mMaxCnt = i_maxCnt; } -private: /* 0x04 */ f32 mRate; /* 0x08 */ int mMaxCnt; /* 0x0C */ cXyz const* field_0xc; @@ -313,7 +312,6 @@ public: u16 getNameId() { return mNameId; } dPa_levelEcallBack* getCallback() { return mCallback; } - private: /* 0x00 */ u32 mId; /* 0x04 */ u16 mNameId; /* 0x06 */ u8 mStatus; @@ -510,7 +508,6 @@ public: static DUSK_GAME_DATA dPa_particleTracePcallBack_c mParticleTracePCB; static DUSK_GAME_DATA u8 mStatus; -private: /* 0x000 */ JKRSolidHeap* mHeap; /* 0x004 */ JPAResourceManager* mCommonResMng; /* 0x008 */ JKRExpHeap* m_resHeap; diff --git a/include/d/d_resorce.h b/include/d/d_resorce.h index b70d2d5452..3330ff5b3d 100644 --- a/include/d/d_resorce.h +++ b/include/d/d_resorce.h @@ -51,7 +51,6 @@ public: static const int NAME_MAX = 9; #endif -private: /* 0x00 */ char mArchiveName[11]; /* 0x0C */ u16 mCount; /* 0x10 */ mDoDvdThd_mountArchive_c* mDMCommand; diff --git a/include/d/d_s_name.h b/include/d/d_s_name.h index 72a44c02de..c32b4b903c 100644 --- a/include/d/d_s_name.h +++ b/include/d/d_s_name.h @@ -56,7 +56,6 @@ public: void tex_data_set(); #endif -private: #if VERSION == VERSION_GCN_PAL u8 field_0x1c4_pal[0x28]; #endif diff --git a/include/d/d_save.h b/include/d/d_save.h index 06458890e4..ec10360034 100644 --- a/include/d/d_save.h +++ b/include/d/d_save.h @@ -8,7 +8,10 @@ #include "d/d_item_data.h" #include "JSystem/JUtility/JUTAssert.h" #include "JSystem/JHostIO/JORReflexible.h" + +#if TARGET_PC #include "helpers/endian.h" +#endif static const int DEFAULT_SELECT_ITEM_INDEX = 0; static const int MAX_SELECT_ITEM = 4; @@ -594,7 +597,6 @@ public: dSv_player_info_c& getPlayerInfo() { return mPlayerInfo; } dSv_player_config_c& getConfig() { return mConfig; } -private: /* 0x000 */ dSv_player_status_a_c mPlayerStatusA; /* 0x028 */ dSv_player_status_b_c mPlayerStatusB; /* 0x040 */ dSv_horse_place_c mHorsePlace; @@ -892,7 +894,6 @@ public: class dSv_reserve_c { public: -private: u8 unk[80]; }; diff --git a/include/d/d_save_HIO.h b/include/d/d_save_HIO.h index 2ee6e4570b..f7d4770e08 100644 --- a/include/d/d_save_HIO.h +++ b/include/d/d_save_HIO.h @@ -10,7 +10,6 @@ public: virtual ~dSvBit_childTransformHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 WF[4]; /* 0x09 */ u8 field_0x9[4]; @@ -23,7 +22,6 @@ public: virtual ~dSvBit_childDarknessHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 TW[6]; /* 0x0B */ u8 field_0xB[6]; @@ -36,7 +34,6 @@ public: virtual ~dSvBit_childOtherHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 mDropNum; /* 0x06 */ u8 field_0x6; @@ -51,7 +48,6 @@ public: virtual ~dSvBit_childTbPerfectionHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 field_0x5[0x40]; /* 0x45 */ u8 field_0x45[0x40]; @@ -64,7 +60,6 @@ public: mPerfectSave.init(); } -private: /* 0x04 */ u8 field_0x4; /* 0x08 */ dSvBit_childTbPerfectionHIO_c mPerfectSave; }; @@ -76,7 +71,6 @@ public: virtual ~dSvBit_childSwZoneHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ bool field_0x5[0x20]; /* 0x25 */ bool field_0x25[0x20]; @@ -89,7 +83,6 @@ public: virtual ~dSvBit_childSwPerfectionHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 field_0x5[0x80]; /* 0x85 */ u8 field_0x85[0x80]; @@ -102,7 +95,6 @@ public: virtual ~dSvBit_childSwOneZoneHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ bool field_0x5[0x10]; /* 0x15 */ bool field_0x15[0x10]; @@ -115,7 +107,6 @@ public: virtual ~dSvBit_childSwDungeonHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 field_0x5[0x40]; /* 0x45 */ u8 field_0x45[0x40]; @@ -131,7 +122,6 @@ public: field_0x1e0.init(); } -private: /* 0x004 */ u8 field_0x4; /* 0x008 */ dSvBit_childSwPerfectionHIO_c field_0x8; /* 0x110 */ dSvBit_childSwDungeonHIO_c field_0x110; @@ -146,7 +136,6 @@ public: virtual ~dSvBit_childItZoneHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 field_0x5[0x20]; /* 0x25 */ u8 field_0x25[0x20]; @@ -159,7 +148,6 @@ public: virtual ~dSvBit_childItPerfectionHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 field_0x5[0x20]; /* 0x25 */ u8 field_0x25[0x20]; @@ -172,7 +160,6 @@ public: virtual ~dSvBit_childItOneZoneHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 field_0x5[0x10]; /* 0x15 */ u8 field_0x15[0x10]; @@ -185,7 +172,6 @@ public: virtual ~dSvBit_childItDungeonHIO_c() {} -private: /* 0x04 */ u8 field_0x4; /* 0x05 */ u8 field_0x5[0x80]; /* 0x85 */ u8 field_0x85[0x80]; @@ -201,7 +187,6 @@ public: mOneZoneSave.init(); } -private: /* 0x004 */ u8 field_0x4; /* 0x008 */ dSvBit_childItPerfectionHIO_c mFullSave; /* 0x050 */ dSvBit_childItDungeonHIO_c mDungeonSave; diff --git a/include/d/d_select_cursor.h b/include/d/d_select_cursor.h index e98db19731..891280e801 100644 --- a/include/d/d_select_cursor.h +++ b/include/d/d_select_cursor.h @@ -42,12 +42,18 @@ public: virtual ~dSelect_cursor_c(); virtual void draw(); +#if TARGET_PC + void setInterp(bool enabled); + void setPos(f32 x, f32 y, const cXyz* world = nullptr); + void setWorldPos(const cXyz& position, f32 offsetX, f32 offsetY) { setPos(offsetX, offsetY, &position); } +#else void setPos(f32 x, f32 y) { mPositionX = x; mPositionY = y; } +#endif -#ifdef TARGET_PC +#if TARGET_PC f32 getPositionX() const { return mPositionX; } f32 getPositionY() const { return mPositionY; } @@ -66,7 +72,6 @@ public: void onPlayAllAnime() { field_0xb4 = 0xff; } void offPlayAllAnime() { field_0xb4 = 0; } -private: /* 0x04 */ J2DScreen* mpScreen; /* 0x08 */ J2DPane* mpPane; /* 0x0C */ dSelect_icon_c* mpSelectIcon; @@ -101,6 +106,11 @@ private: /* 0xB5 */ u8 mNameIdx; /* 0xB6 */ u8 field_0xb6; /* 0xB7 */ bool mUpdateFlag; +#if TARGET_PC + cXyz mWorldPosition{0.0f, 0.0f, 0.0f}; + bool mWorldCursor = false; + bool mInterpolatePosition = true; +#endif }; #endif /* D_D_SELECT_CURSOR_H */ diff --git a/include/d/d_shop_item_ctrl.h b/include/d/d_shop_item_ctrl.h index 9016be18ea..61ed325241 100644 --- a/include/d/d_shop_item_ctrl.h +++ b/include/d/d_shop_item_ctrl.h @@ -18,7 +18,6 @@ public: void setMessageIndex(int i, u16 messageIndex) { mMessageIndex[i] = messageIndex; } u16 getMessageIndex(int i) { return mMessageIndex[i]; } -private: /* 0x04 */ u32 mItemIndex[7]; /* 0x20 */ u16 mMessageIndex[7]; }; diff --git a/include/d/d_simple_model.h b/include/d/d_simple_model.h index 25b9e6e2d1..e87462c318 100644 --- a/include/d/d_simple_model.h +++ b/include/d/d_simple_model.h @@ -36,7 +36,6 @@ public: void setIdx(u8 idx) { this->idx = idx; } #endif -private: /* 0x00 */ JKRSolidHeap* mpHeap; /* 0x04 */ J3DModel* mpModel; /* 0x08 */ int mCreateNum; @@ -59,7 +58,6 @@ public: virtual ~dSmplMdl_draw_c(); -private: /* 0x04 */ diff_model_c mModel[8]; /* 0xA4 */ int mListCount; /* 0xA8 */ modelList_c mList[500]; diff --git a/include/d/d_stage.h b/include/d/d_stage.h index f323726f6e..fb8fd3124b 100644 --- a/include/d/d_stage.h +++ b/include/d/d_stage.h @@ -4,10 +4,14 @@ #include "SSystem/SComponent/c_lib.h" #include "d/d_kankyo.h" #include "d/d_kankyo_data.h" -#include "helpers/offset_ptr.h" #include "f_op/f_op_actor_mng.h" #include "global.h" + +#if TARGET_PC +#include "helpers/offset_ptr.h" + #include "os_report.h" +#endif enum StageType { /* 0x0 */ ST_FIELD, @@ -547,7 +551,6 @@ public: /* vt[91] */ virtual void setElst(dStage_Elst_c*) = 0; /* vt[92] */ virtual dStage_Elst_c* getElst(void) = 0; -private: /* 0x04 */ s8 mRoomNo; }; @@ -1261,7 +1264,6 @@ public: static u8 mNoArcBank; #endif -private: /* 0x0 */ u8 field_0x0[4]; }; @@ -1276,7 +1278,6 @@ public: s8 getDarkArea() const { return mDarkArea; } void setDarkArea(s8 darkArea) { mDarkArea = darkArea; } -private: /* 0x0 */ char mName[8]; /* 0x8 */ s16 mPoint; /* 0xA */ s8 mRoomNo; @@ -1297,7 +1298,6 @@ public: u8 getWipeSpeed() const { return wipe_speed; } dStage_startStage_c* getStartStage() { return this; } -private: s8 enabled; s8 wipe; u8 wipe_speed; diff --git a/include/d/d_timer.h b/include/d/d_timer.h index 104a4cae9e..f73573e9d3 100644 --- a/include/d/d_timer.h +++ b/include/d/d_timer.h @@ -57,6 +57,9 @@ public: #endif void drawPikari(int); +#if TARGET_PC + void presentAnims(); +#endif virtual void draw(); virtual ~dDlst_TimerScrnDraw_c(); @@ -68,16 +71,20 @@ public: void resetCowID() { mCowID = 0; } bool isVisible() { return mTimerVisible; } - f32 acc(s16 param_0, s16 param_1, s16 param_2) { + f32 acc(DUSK_IF_ELSE(f32, s16) param_0, DUSK_IF_ELSE(f32, s16) param_1, DUSK_IF_ELSE(f32, s16) param_2) { +#if TARGET_PC + if (param_0 == param_2) { + return 1.0f; + } +#endif return ((f32)(param_1 - param_2) * (f32)(param_1 - param_2)) / ((f32)(param_0 - param_2) * (f32)(param_0 - param_2)); } - + void setTimerTrans(f32 x, f32 y) { mTimerTransX = x; mTimerTransY = y; } -private: /* 0x004 */ J2DScreen* mpScreen; /* 0x008 */ J2DScreen* mpGetInScreen; /* 0x00C */ J2DAnmTransform* mpGetInBck; @@ -104,7 +111,7 @@ private: /* 0x3CC */ int field_0x3CC; /* 0x3D0 */ int field_0x3D0; /* 0x3D4 */ int field_0x3D4; - /* 0x3D8 */ int field_0x3D8; + /* 0x3D8 */ DUSK_IF_ELSE(f32, int) field_0x3D8; /* 0x3DC */ u8 mCowID; /* 0x3DD */ u8 mHIOType; /* 0x3DE */ u8 field_0x3DE; @@ -145,7 +152,6 @@ public: u8 isReadyFlag() { return m_is_ready; } -private: /* 0x0FC */ dDlst_TimerScrnDraw_c* mp_tm_scrn; /* 0x100 */ JKRExpHeap* mp_heap; /* 0x104 */ u8 field_0x104[4]; diff --git a/include/d/d_tresure.h b/include/d/d_tresure.h index e043dd94fb..4335a6a215 100644 --- a/include/d/d_tresure.h +++ b/include/d/d_tresure.h @@ -2,7 +2,10 @@ #define D_D_TRESURE_H #include + +#if TARGET_PC #include "helpers/offset_ptr.h" +#endif class dTres_c { public: diff --git a/include/d/d_vibration.h b/include/d/d_vibration.h index b61d0099b4..6e886a314b 100644 --- a/include/d/d_vibration.h +++ b/include/d/d_vibration.h @@ -94,7 +94,6 @@ public: static DUSK_GAME_DATA const vib_pattern MQ_patt[VIBMODE_Q_MAX]; static DUSK_GAME_DATA const vib_pattern CQ_patt[VIBMODE_Q_MAX]; -private: #if DEBUG /* 0x00 */ dVibTest_c mVibTest; #elif PARTIAL_DEBUG diff --git a/include/f_op/f_op_actor_mng.h b/include/f_op/f_op_actor_mng.h index 7aa3bf2156..29dfac1389 100644 --- a/include/f_op/f_op_actor_mng.h +++ b/include/f_op/f_op_actor_mng.h @@ -12,9 +12,10 @@ #include "f_pc/f_pc_manager.h" #include "m_Do/m_Do_hostIO.h" #include "SSystem/SComponent/c_phase.h" + +#if TARGET_PC #include "helpers/endian_ssystem.h" -#if !__MWERKS__ // mwerks compiler makes value initialization act like default initialization so we need // to be explicit about default initialization in modern compilers #define fopAcM_ct_placement(ptr, ClassName) JKR_NEW_ARGS (ptr) ClassName @@ -23,13 +24,10 @@ #endif #define fopAcM_ct(ptr, ClassName) \ - if ((ptr)->layer_tag.layer == NULL) { OSPanic(__FILE__, __LINE__, "UH OH"); } \ if (!fopAcM_CheckCondition(ptr, fopAcCnd_INIT_e)) { \ fopAcM_ct_placement(ptr, ClassName); \ fopAcM_OnCondition(ptr, fopAcCnd_INIT_e); \ - } \ - if ((ptr)->layer_tag.layer == NULL) { OSPanic(__FILE__, __LINE__, "Oh come on"); } - + } #define fopAcM_RegisterDeleteID(i_this, actor_name_str) \ ("Delete -> " actor_name_str "(id=%d)\n", fopAcM_GetID(i_this)) diff --git a/include/f_pc/f_pc_deletor.h b/include/f_pc/f_pc_deletor.h index c0b7da0fc2..1667303fc4 100644 --- a/include/f_pc/f_pc_deletor.h +++ b/include/f_pc/f_pc_deletor.h @@ -7,6 +7,7 @@ typedef struct base_process_class base_process_class; BOOL fpcDt_IsComplete(); +int fpcDt_deleteMethod(base_process_class* i_proc); int fpcDt_ToDeleteQ(base_process_class* i_proc); int fpcDt_ToQueue(base_process_class* i_proc); void fpcDt_Handler(); diff --git a/include/helpers/alignment.hpp b/include/helpers/alignment.hpp new file mode 100644 index 0000000000..54f46b0678 --- /dev/null +++ b/include/helpers/alignment.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace dusk::helpers { + +/** + * Read data from an address that may not be aligned properly. + * @tparam T Type of data to read. + * @param ptr Address to read from. + * @return The copied value. + */ +template + requires std::is_trivially_copyable_v +[[nodiscard]] constexpr T read_unaligned(u8 const* ptr) { + T copy; + memcpy(©, ptr, sizeof(T)); + return copy; +} + +} // namespace dusk::helpers diff --git a/include/helpers/cast.hpp b/include/helpers/cast.hpp new file mode 100644 index 0000000000..ef6d8421a6 --- /dev/null +++ b/include/helpers/cast.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +/** + * Helper functions for performing casts. + */ +namespace dusk::helpers::cast { +/** + * Implementation details of dusk::helpers::cast. + */ +namespace _impl { +[[noreturn]] void overrun_high(); +[[noreturn]] void overrun_low(); +} // namespace _impl + +template +concept IntCastable = std::is_integral_v && std::is_trivially_copyable_v; + +/** + * Helper type that allows easily casting between integer types, + * that safely aborts if an overflow were to occur. + * Usage: + * @code + * size_t foobar = 20; + * int real = bounded_cast(foobar); + * @endcode + */ +template +struct bounded_cast { + Source src; + + template + [[nodiscard]] constexpr operator Target() const { + if (std::cmp_greater(src, std::numeric_limits::max())) [[unlikely]] { + _impl::overrun_high(); + } + + if (std::cmp_less(src, std::numeric_limits::min())) [[unlikely]] { + _impl::overrun_low(); + } + + return static_cast(src); + } +}; +} // namespace dusk::helpers::cast diff --git a/include/helpers/endian.h b/include/helpers/endian.h index 689f9f0dab..b9727ce4c7 100644 --- a/include/helpers/endian.h +++ b/include/helpers/endian.h @@ -28,35 +28,96 @@ #define BSWAP32(x) (x) #endif +// clang-format off +// yeah ofc Microsoft's _byteswap_* aren't constexpr like the GCC/Clang ones. +constexpr u16 be16_manual(u16 val) +{ + return (val >> 8) | + (val << 8); +} + +constexpr u32 be32_manual(u32 val) +{ + return (val >> 24) | + ((val << 8) & 0x00FF0000) | + ((val >> 8) & 0x0000FF00) | + (val << 24); +} + +constexpr u64 be64_manual(u64 val) +{ + return (val >> 56) | + ((val << 40) & 0x00FF000000000000) | + ((val << 24) & 0x0000FF0000000000) | + ((val << 8) & 0x000000FF00000000) | + ((val >> 8) & 0x00000000FF000000) | + ((val >> 24) & 0x0000000000FF0000) | + ((val >> 40) & 0x000000000000FF00) | + (val << 56); +} +// clang-format on + // Big-Endian to Host conversion -inline u16 be16(u16 val) { return BSWAP16(val); } -inline s16 be16s(s16 val) { return (s16)BSWAP16((u16)val); } -inline u32 be32(u32 val) { return BSWAP32(val); } -inline s32 be32s(s32 val) { return (s32)BSWAP32((u32)val); } -inline u64 be64(u64 val) { return BSWAP64(val); } -inline s64 be64s(s64 val) { return (s64)BSWAP64((u64)val); } +// _manual is behind std::is_constant_evaluated() checks, +// to avoid pessimizing debug perf too much. +constexpr u16 be16(u16 val) { + if (std::is_constant_evaluated()) { + return be16_manual(val); + } + return BSWAP16(val); +} +constexpr s16 be16s(s16 val) { + if (std::is_constant_evaluated()) { + return (s16)be16_manual((u16)val); + } + return (s16)BSWAP16((u16)val); +} +constexpr u32 be32(u32 val) { + if (std::is_constant_evaluated()) { + return be32_manual(val); + } + return BSWAP32(val); +} +constexpr s32 be32s(s32 val) { + if (std::is_constant_evaluated()) { + return (s32)be32_manual((u32)val); + } + return (s32)BSWAP32((u32)val); +} +constexpr u64 be64(u64 val) { + if (std::is_constant_evaluated()) { + return be64_manual(val); + } + return BSWAP64(val); +} +constexpr s64 be64s(s64 val) { + if (std::is_constant_evaluated()) { + return (s64)be64_manual((u64)val); + } + return (s64)BSWAP64((u64)val); +} #ifdef TARGET_PC // Helper wrappers so code below reads nicely: -static inline u16 RES_U16(u16 v) { +constexpr u16 RES_U16(u16 v) { return be16(v); } -static inline s16 RES_S16(s16 v) { +constexpr s16 RES_S16(s16 v) { return be16s(v); } -static inline u32 RES_U32(u32 v) { +constexpr u32 RES_U32(u32 v) { return be32(v); } -static inline s32 RES_S32(s32 v) { +constexpr s32 RES_S32(s32 v) { return be32s(v); } -static inline u64 RES_U64(u64 v) { +constexpr u64 RES_U64(u64 v) { return be64(v); } -static inline s64 RES_S64(s64 v) { +constexpr s64 RES_S64(s64 v) { return be64s(v); } -static inline f32 RES_F32(f32 v) { +constexpr f32 RES_F32(f32 v) { return std::bit_cast(RES_S32(std::bit_cast(v))); } #else @@ -76,33 +137,33 @@ static inline f32 RES_F32(f32 v) { template struct BE { T inner; - BE() = default; - BE(const T& from) { + constexpr BE() noexcept = default; + constexpr BE(const T& from) noexcept { inner = swap(from); } // post-ops - T operator--(int) { + constexpr T operator--(int) { T orig = inner; *this -= 1; return swap(orig); } - T operator++(int) { + constexpr T operator++(int) { T orig = inner; *this += 1; return swap(orig); } - operator T() const { + constexpr operator T() const noexcept { return swap(inner); } - T host[[nodiscard]]() const { + constexpr T host[[nodiscard]]() const noexcept { return swap(inner); } - static T swap[[nodiscard]](T val); + static constexpr T swap[[nodiscard]](T val) noexcept; }; #define BIN_ASSIGN_OP(op) \ @@ -124,44 +185,43 @@ BIN_ASSIGN_OP(^=); #undef BIN_ASSIGN_OP - template<> -inline u16 BE::swap(u16 val) { +constexpr u16 BE::swap(u16 val) noexcept { return RES_U16(val); } template<> -inline s16 BE::swap(s16 val) { +constexpr s16 BE::swap(s16 val) noexcept { return RES_S16(val); } template<> -inline u32 BE::swap(u32 val) { +constexpr u32 BE::swap(u32 val) noexcept { return RES_U32(val); } template<> -inline s32 BE::swap(s32 val) { +constexpr s32 BE::swap(s32 val) noexcept { return RES_S32(val); } template<> -inline s64 BE::swap(s64 val) { +constexpr s64 BE::swap(s64 val) noexcept { return RES_S64(val); } template<> -inline u64 BE::swap(u64 val) { +constexpr u64 BE::swap(u64 val) noexcept { return RES_U64(val); } template<> -inline f32 BE::swap(f32 val) { +constexpr f32 BE::swap(f32 val) noexcept { return RES_F32(val); } template<> -inline S16Vec BE::swap(S16Vec val) { +constexpr S16Vec BE::swap(S16Vec val) noexcept { return { BE::swap(val.x), BE::swap(val.y), @@ -175,23 +235,23 @@ struct BE { BE y; BE z; - BE() = default; - BE(f32 x, f32 y, f32 z) { + constexpr BE() noexcept = default; + constexpr BE(f32 x, f32 y, f32 z) noexcept { this->x = x; this->y = y; this->z = z; } - BE(const Vec& from) { + constexpr BE(const Vec& from) noexcept { x = from.x; y = from.y; z = from.z; } - operator Vec() const { + constexpr operator Vec() const noexcept { return { x, y, z }; } - static Vec swap(Vec val) { + constexpr static Vec swap(Vec val) noexcept { return { BE::swap(val.x), BE::swap(val.y), @@ -204,7 +264,7 @@ template <> struct BE { BE contents[4][4]; - auto& operator[](int x) const { + constexpr auto& operator[](int x) const noexcept { return contents[x]; } }; @@ -213,11 +273,11 @@ template <> struct BE { BE contents[3][4]; - auto& operator[](int x) const { + constexpr auto& operator[](int x) const noexcept { return contents[x]; } - void to_host(Mtx& mtx) const { + constexpr void to_host(Mtx& mtx) const noexcept { for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { mtx[i][j] = contents[i][j]; @@ -231,15 +291,15 @@ template <> struct BE { BE contents[2][3]; - auto& operator[](int x) { + constexpr auto& operator[](int x) noexcept { return contents[x]; } - auto& operator[](int x) const { + constexpr auto& operator[](int x) const noexcept { return contents[x]; } - void to_host(Mtx23& mtx) const { + constexpr void to_host(Mtx23& mtx) const noexcept { for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { mtx[i][j] = contents[i][j]; @@ -249,26 +309,26 @@ struct BE { }; template -void be_swap(T& val) { +constexpr void be_swap(T& val) noexcept { val = BE::swap(val); } template -void be_swap(T (& val)[N]) { +constexpr void be_swap(T (& val)[N]) noexcept { for (u32 i = 0; i < N; i++) { be_swap(val[i]); } } template -void be_swap(T array[], const u32 size) { +constexpr void be_swap(T array[], const u32 size) noexcept { for (u32 i = 0; i < size; i++) { be_swap(array[i]); } } template<> -inline void be_swap(Mtx44& val) { +constexpr void be_swap(Mtx44& val) noexcept { for (auto & x : val) { for (float & y : x) { be_swap(y); @@ -277,7 +337,7 @@ inline void be_swap(Mtx44& val) { } template<> -inline void be_swap(Mtx& val) { +constexpr void be_swap(Mtx& val) noexcept { for (auto & x : val) { for (float & y : x) { be_swap(y); @@ -285,6 +345,7 @@ inline void be_swap(Mtx& val) { } } +#define LE(T) T #define BE(T) BE #define BE_HOST(T) (T.host()) #else diff --git a/include/helpers/endian_gx.hpp b/include/helpers/endian_gx.hpp index 1193cf1d7c..201e667844 100644 --- a/include/helpers/endian_gx.hpp +++ b/include/helpers/endian_gx.hpp @@ -3,12 +3,28 @@ #include "dolphin/gx/GXStruct.h" #include "endian.h" + +#define IMPL_ENUM(type) \ +template <> \ +constexpr type BE::swap(type val) noexcept { \ + return static_cast(be32(val)); \ +} + +IMPL_ENUM(GXCullMode); +IMPL_ENUM(GXAttr); +IMPL_ENUM(GXAttrType); +IMPL_ENUM(GXCompType); +IMPL_ENUM(GXCompCnt); + +#undef IMPL_ENUM + + template <> struct BE { BE attr; BE type; - static GXVtxDescList swap[[nodiscard]](GXVtxDescList val); + constexpr static GXVtxDescList swap[[nodiscard]](GXVtxDescList val) noexcept; }; template <> @@ -18,5 +34,31 @@ struct BE { BE type; u8 frac; - static GXVtxAttrFmtList swap[[nodiscard]](GXVtxAttrFmtList val); + constexpr static GXVtxAttrFmtList swap[[nodiscard]](GXVtxAttrFmtList val) noexcept; }; + +template <> +constexpr GXColorS10 BE::swap(GXColorS10 val) noexcept { + return { + be16s(val.r), + be16s(val.g), + be16s(val.b), + be16s(val.a), + }; +} + +constexpr GXVtxDescList BE::swap(GXVtxDescList val) noexcept { + return { + BE::swap(val.attr), + BE::swap(val.type), + }; +} + +constexpr GXVtxAttrFmtList BE::swap(GXVtxAttrFmtList val) noexcept { + return { + BE::swap(val.attr), + BE::swap(val.cnt), + BE::swap(val.type), + val.frac + }; +} diff --git a/include/helpers/endian_ssystem.h b/include/helpers/endian_ssystem.h index b383ef6e43..cea10ea2df 100644 --- a/include/helpers/endian_ssystem.h +++ b/include/helpers/endian_ssystem.h @@ -60,3 +60,11 @@ struct BE { }; } }; + +template<> +constexpr cXy BE::swap(cXy val) noexcept { + return { + BE::swap(val.x), + BE::swap(val.y), + }; +} diff --git a/include/m_Do/m_Do_MemCardRWmng.h b/include/m_Do/m_Do_MemCardRWmng.h index 15f95d6347..4ede2c1828 100644 --- a/include/m_Do/m_Do_MemCardRWmng.h +++ b/include/m_Do/m_Do_MemCardRWmng.h @@ -24,7 +24,11 @@ struct mDoMemCdRWm_HeaderData { s32 mDoMemCdRWm_Restore(CARDFileInfo* param_0, void* param_1, u32 param_2); s32 mDoMemCdRWm_Store(CARDFileInfo* param_0, void* param_1, u32 param_2); static void mDoMemCdRWm_BuildHeader(mDoMemCdRWm_HeaderData* header); +#if TARGET_PC +static s32 mDoMemCdRWm_SetCardStat(CARDFileInfo* file); +#else static void mDoMemCdRWm_SetCardStat(CARDFileInfo* file); +#endif static BOOL mDoMemCdRWm_CheckCardStat(CARDFileInfo* file); static u32 mDoMemCdRWm_CalcCheckSum(void* data, u32 size); static u64 mDoMemCdRWm_CalcCheckSumGameData(void* data, u32 size); diff --git a/include/m_Do/m_Do_dvd_thread.h b/include/m_Do/m_Do_dvd_thread.h index ebb4253838..dcb3f2fc65 100644 --- a/include/m_Do/m_Do_dvd_thread.h +++ b/include/m_Do/m_Do_dvd_thread.h @@ -68,7 +68,7 @@ public: JKRMemArchive* getArchive() const { return mArchive; } JKRHeap* getHeap() const { return mHeap; } -// private: +private: /* 0x14 */ u8 mMountDirection; /* 0x18 */ s32 mEntryNumber; /* 0x1C */ JKRMemArchive* mArchive; diff --git a/include/m_Do/m_Do_ext.h b/include/m_Do/m_Do_ext.h index bd1baac2df..83a6a78230 100644 --- a/include/m_Do/m_Do_ext.h +++ b/include/m_Do/m_Do_ext.h @@ -80,7 +80,13 @@ public: int remove(J3DModelData* i_modelData) { return i_modelData->removeTexMtxAnimator(mpAnm); } void entryFrame() { entryFrame(getFrame()); } - void entryFrame(f32 frame) { mpAnm->setFrame(frame); } +#if TARGET_PC + void entryFrame(f32 frame); +#else + void entryFrame(f32 frame) { + mpAnm->setFrame(frame); + } +#endif J3DAnmTextureSRTKey* getBtkAnm() const { return mpAnm; } @@ -110,7 +116,13 @@ public: int remove(J3DModelData* i_modelData) { return i_modelData->removeTevRegAnimator(mpAnm); } void entryFrame() { entryFrame(getFrame()); } - void entryFrame(f32 frame) { mpAnm->setFrame(frame); } +#if TARGET_PC + void entryFrame(f32 frame); +#else + void entryFrame(f32 frame) { + mpAnm->setFrame(frame); + } +#endif J3DAnmTevRegKey* getBrkAnm() const { return mpAnm; } @@ -542,10 +554,18 @@ public: virtual void setMaterial() = 0; virtual void draw() = 0; #if TARGET_PC - virtual void refreshGeometryForPresentationEye(const cXyz& eye) {} + virtual void captureInterpPoints() {} + virtual void refreshGeometryForPresentation() {} #endif /* 0x4 */ mDoExt_3DlineMat_c* field_0x4; + +#if TARGET_PC +protected: + u8 mInterpKind; + f32 mInterpWidth; + u16 mInterpTaper; +#endif }; class mDoExt_3DlineMat0_c : public mDoExt_3DlineMat_c { @@ -567,6 +587,10 @@ public: virtual int getMaterialID() { return 0; } virtual void setMaterial(); virtual void draw(); +#if TARGET_PC + void captureInterpPoints() override; + void refreshGeometryForPresentation() override; +#endif cXyz* getPos(int param_0) { return field_0x18[param_0].field_0x0; } f32* getSize(int param_0) { return field_0x18[param_0].field_0x4; } @@ -585,18 +609,14 @@ class dKy_tevstr_c; class mDoExt_3DlineMat1_c : public mDoExt_3DlineMat_c { public: int init(u16, u16, ResTIMG*, int); -#if TARGET_PC - void update(int, GXColor&, dKy_tevstr_c*, const cXyz* presentationEye = nullptr); - void update(int, f32, GXColor&, u16, dKy_tevstr_c*, const cXyz* presentationEye = nullptr); -#else void update(int, GXColor&, dKy_tevstr_c*); void update(int, f32, GXColor&, u16, dKy_tevstr_c*); -#endif int getMaterialID() { return 1; } void setMaterial(); void draw(); #if TARGET_PC - void refreshGeometryForPresentationEye(const cXyz& eye) override; + void captureInterpPoints() override; + void refreshGeometryForPresentation() override; #endif cXyz* getPos(int i_idx) { return mpLines[i_idx].field_0x0; } @@ -611,11 +631,6 @@ private: /* 0x34 */ u16 field_0x34; /* 0x36 */ u8 mIsDrawn; /* 0x38 */ mDoExt_3Dline_c* mpLines; -#if TARGET_PC - u8 mInterpLineKind; - f32 mInterpLineF; - u16 mInterpLineU16; -#endif }; class mDoExt_3DlineMat2_c : public mDoExt_3DlineMat1_c { diff --git a/include/m_Do/m_Do_lib.h b/include/m_Do/m_Do_lib.h index 6b85cc0660..9ceaa9c137 100644 --- a/include/m_Do/m_Do_lib.h +++ b/include/m_Do/m_Do_lib.h @@ -2,13 +2,10 @@ #define M_DO_M_DO_LIB_H #include "JSystem/J3DU/J3DUClipper.h" -#include #if TARGET_PC -#include "JSystem/JGeometry.h" -#endif - #include "helpers/gx_helper.h" +#endif typedef struct Vec Vec; struct ResTIMG; diff --git a/include/m_Do/m_Do_mtx.h b/include/m_Do/m_Do_mtx.h index 89e7e8b7cf..fdf5b66fed 100644 --- a/include/m_Do/m_Do_mtx.h +++ b/include/m_Do/m_Do_mtx.h @@ -5,9 +5,12 @@ #include "SSystem/SComponent/c_xyz.h" #include -#include "JSystem/JMath/JMath.h" +#if TARGET_PC #include "helpers/endian.h" +#include "JSystem/JMath/JMath.h" +#endif + extern u8 g_printCurrentHeapDebug; DUSK_GAME_EXTERN u8 g_printOtherHeapDebug; diff --git a/include/m_Do/m_Do_printf.h b/include/m_Do/m_Do_printf.h index a71669f432..dc30b1c720 100644 --- a/include/m_Do/m_Do_printf.h +++ b/include/m_Do/m_Do_printf.h @@ -2,7 +2,6 @@ #define M_DO_M_DO_PRINTF_H #include -#include "helpers/endian.h" void my_PutString(const char*); void mDoPrintf_vprintf_Interrupt(char const*, va_list); diff --git a/libs/JSystem/include/JSystem/J2DGraph/J2DManage.h b/libs/JSystem/include/JSystem/J2DGraph/J2DManage.h index 27218d0d0d..3eb1967e2c 100644 --- a/libs/JSystem/include/JSystem/J2DGraph/J2DManage.h +++ b/libs/JSystem/include/JSystem/J2DGraph/J2DManage.h @@ -3,6 +3,10 @@ #include +#if TARGET_PC + #include "helpers/endian.h" +#endif + class JSUInputStream; /** diff --git a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DAnimation.h b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DAnimation.h index f006183d07..80af43182b 100644 --- a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DAnimation.h +++ b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DAnimation.h @@ -6,9 +6,9 @@ #include #include "global.h" +#if TARGET_PC #include "helpers/endian.h" -#if TARGET_PC #define OFFSET_PTR_V0 BE(u32) #else #define OFFSET_PTR_V0 void* @@ -478,27 +478,45 @@ struct J3DAnmClusterFullTable { */ class J3DAnmBase { public: +#if TARGET_PC + J3DAnmBase(); +#else J3DAnmBase() { mAttribute = 0; field_0x5 = 0; mFrameMax = 0; mFrame = 0.0f; } +#endif +#if TARGET_PC + J3DAnmBase(s16 frameMax); +#else J3DAnmBase(s16 frameMax) { mAttribute = 0; field_0x5 = 0; mFrameMax = frameMax; mFrame = 0.0f; } +#endif +#if TARGET_PC + virtual ~J3DAnmBase(); +#else virtual ~J3DAnmBase() {} +#endif virtual s32 getKind() const = 0; u8 getAttribute() const { return mAttribute; } s16 getFrameMax() const { return mFrameMax; } f32 getFrame() const { return mFrame; } - void setFrame(f32 frame) { mFrame = frame; } +#if TARGET_PC + void setFrame(f32 frame); +#else + void setFrame(f32 frame) { + mFrame = frame; + } +#endif /* 0x4 */ u8 mAttribute; /* 0x5 */ u8 field_0x5; @@ -960,30 +978,66 @@ public: void init(int endFrame) { init((s16)endFrame); } BOOL checkPass(f32); void update(); +#if TARGET_PC + virtual ~J3DFrameCtrl(); +#else virtual ~J3DFrameCtrl() {} +#endif u8 getAttribute() const { return mAttribute; } - void setAttribute(u8 attr) { mAttribute = attr; } +#if TARGET_PC + void setAttribute(u8 attr); +#else + void setAttribute(u8 attr) { + mAttribute = attr; + } +#endif u8 getState() const { return mState; } bool checkState(u8 state) const { return mState & state ? true : false; } s16 getStart() const { return mStart; } +#if TARGET_PC + void setStart(s16 start); +#else void setStart(s16 start) { mStart = start; mFrame = start; } +#endif s16 getEnd() const { return mEnd; } - void setEnd(s16 end) { mEnd = end; } +#if TARGET_PC + void setEnd(s16 end); +#else + void setEnd(s16 end) { + mEnd = end; + } +#endif s16 getLoop() const { return mLoop; } - void setLoop(s16 loop) { mLoop = loop; } +#if TARGET_PC + void setLoop(s16 loop); +#else + void setLoop(s16 loop) { + mLoop = loop; + } +#endif f32 getRate() const { return mRate; } void setRate(f32 rate) { mRate = rate; } f32 getFrame() const { return mFrame; } - void setFrame(f32 frame) { mFrame = frame; } +#if TARGET_PC + void setFrame(f32 frame); +#else + void setFrame(f32 frame) { + mFrame = frame; + } +#endif +#if TARGET_PC + void reset(); +#else void reset() { mFrame = mStart; mRate = 1.0f; mState = 0; } +#endif /* 0x04 */ u8 mAttribute; /* 0x05 */ u8 mState; diff --git a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DMaterialAnm.h b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DMaterialAnm.h index 4c3f0f75bb..94d3315d2e 100644 --- a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DMaterialAnm.h +++ b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DMaterialAnm.h @@ -31,10 +31,15 @@ public: void setAnmFlag(bool flag) { mAnmFlag = flag; } bool getAnmFlag() const { return mAnmFlag; } +#if TARGET_PC + void calc(GXColor* pColor) const; + J3DAnmColor* getAnimation() const { return mAnmColor; } +#else void calc(GXColor* pColor) const { J3D_ASSERT_NULLPTR(507, pColor != NULL); mAnmColor->getColor(field_0x0, pColor); } +#endif private: /* 0x0 */ u16 field_0x0; @@ -66,10 +71,15 @@ public: void setAnmFlag(bool flag) { mAnmFlag = flag; } +#if TARGET_PC + void calc(J3DTextureSRTInfo* pSRTInfo) const; + J3DAnmTextureSRTKey* getAnimation() const { return mAnmTransform; } +#else void calc(J3DTextureSRTInfo* pSRTInfo) const { J3D_ASSERT_NULLPTR(519, pSRTInfo != NULL); mAnmTransform->getTransform(field_0x0, pSRTInfo); } +#endif bool getAnmFlag() const { return mAnmFlag; } @@ -108,6 +118,9 @@ public: void setAnmFlag(bool flag) { mAnmFlag = flag; } bool getAnmFlag() const { return mAnmFlag; } +#if TARGET_PC + J3DAnmTexPattern* getAnimation() const { return mAnmTexPattern; } +#endif J3DAnmTexPattern* getAnmTexPattern() { return mAnmTexPattern; } private: @@ -141,10 +154,15 @@ public: void setAnmFlag(bool flag) { mAnmFlag = flag; } bool getAnmFlag() const { return mAnmFlag; } +#if TARGET_PC + void calc(GXColorS10* pColor) const; + J3DAnmTevRegKey* getAnimation() const { return mAnmTevReg; } +#else void calc(GXColorS10* pColor) const { J3D_ASSERT_NULLPTR(545, pColor != NULL); mAnmTevReg->getTevColorReg(field_0x0, pColor); } +#endif private: /* 0x0 */ u16 field_0x0; @@ -177,10 +195,15 @@ public: void setAnmFlag(bool flag) { mAnmFlag = flag; } bool getAnmFlag() const { return mAnmFlag; } +#if TARGET_PC + void calc(GXColor* pColor) const; + J3DAnmTevRegKey* getAnimation() const { return mAnmTevReg; } +#else void calc(GXColor* pColor) const { J3D_ASSERT_NULLPTR(558, pColor != NULL); mAnmTevReg->getTevKonstReg(field_0x0, pColor); } +#endif private: /* 0x0 */ u16 field_0x0; @@ -197,6 +220,9 @@ public: J3DMaterialAnm() { initialize(); } void initialize(); +#if TARGET_PC + bool hasMaterialAnimation() const; +#endif void setMatColorAnm(int, J3DMatColorAnm*); void setTexMtxAnm(int, J3DTexMtxAnm*); void setTexNoAnm(int, J3DTexNoAnm*); @@ -207,6 +233,12 @@ public: virtual void calc(J3DMaterial*) const; const J3DTexMtxAnm& getTexMtxAnm(int i) const { return mTexMtxAnm[i]; } +#if TARGET_PC + const J3DMatColorAnm& getMatColorAnm(int i) const { return mMatColorAnm[i]; } + const J3DTexNoAnm& getTexNoAnm(int i) const { return mTexNoAnm[i]; } + const J3DTevColorAnm& getTevColorAnm(int i) const { return mTevColorAnm[i]; } + const J3DTevKColorAnm& getTevKColorAnm(int i) const { return mTevKColorAnm[i]; } +#endif private: /* 0x04 */ J3DMatColorAnm mMatColorAnm[2]; diff --git a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h index d55bb82465..042dc81dd3 100644 --- a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h +++ b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h @@ -79,7 +79,8 @@ public: virtual ~J3DModel() {} #if TARGET_PC - static void interp_callback(bool isSimFrame, void* pUserWork); + void calc_presentation_base_mtx(); + void prepare_presentation_view(); #endif J3DModelData* getModelData() { return mModelData; } @@ -107,6 +108,7 @@ public: Vec* getBaseScale() { return &mBaseScale; } #if TARGET_PC void setAnmMtx(int jointNo, Mtx m); + void forgetMtx(); #else void setAnmMtx(int jointNo, Mtx m) { mMtxBuffer->setAnmMtx(jointNo, m); @@ -133,6 +135,9 @@ public: /* 0xD0 */ J3DVtxColorCalc* mVtxColorCalc; /* 0xD4 */ J3DUnkCalc1* mUnkCalc1; /* 0xD8 */ J3DUnkCalc2* mUnkCalc2; +#if TARGET_PC + Mtx mPresentationBase; +#endif }; #endif /* J3DMODEL_H */ diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h b/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h index 1b565a069e..20d700097c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h @@ -3,6 +3,10 @@ #include "JSystem/JGeometry.h" +namespace dusk::mods::svc::audio_res::bst { +struct SoundTableReplacementSlot; +} + class JAIAudible; class JAISoundID; struct JASSoundParams; @@ -14,7 +18,8 @@ struct JASSoundParams; struct JAIAudience { virtual ~JAIAudience(); virtual JAIAudible* newAudible(JGeometry::TVec3 const&, JAISoundID, - JGeometry::TVec3 const*, u32) = 0; + JGeometry::TVec3 const*, u32 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const*)) = 0; virtual int getMaxChannels() = 0; virtual void deleteAudible(JAIAudible*) = 0; virtual u32 calcPriority(JAIAudible*) = 0; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISe.h b/libs/JSystem/include/JSystem/JAudio2/JAISe.h index 408a84703a..2ff990bec0 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISe.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISe.h @@ -43,7 +43,7 @@ public: void startTrack_(const JASSoundParams& params); void JAISeCategoryMgr_mixOut_(bool, const JASSoundParams& params, JAISoundActivity activity); void JAISeCategoryMgr_calc_(); - void JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience); + void JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)); bool prepare_getSeqData_(); void prepare_(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h index 928d85525b..57fdbd114a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h @@ -104,7 +104,7 @@ public: JAISe* newSe_(int category, u32 priority); void calc(); void mixOut(); - bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr); + bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)); int getNumActiveSe() const; /* 0x004 */ JAISoundActivity mSoundActivity; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISound.h b/libs/JSystem/include/JSystem/JAudio2/JAISound.h index fa262a7426..9dbd84d35a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISound.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISound.h @@ -6,7 +6,16 @@ #include "JSystem/JUtility/JUTAssert.h" #include "global.h" #include "helpers/endian.h" -#include + +#if TARGET_PC +#include + +namespace dusk::mods::svc::audio_res::bst { +struct SoundTableReplacementSlot; +struct SoundEffectReplacementSlot; +struct StreamReplacementSlot; +} +#endif class JAISound; @@ -101,7 +110,7 @@ struct JAISoundStatus_ { bool isMute() const { return field_0x0.flags.mute; } bool isPaused() const { return field_0x0.flags.paused; } void pauseWhenOut() { - field_0x1.flags.flag3 = 1; + field_0x1.flags.mPauseWhenOut = 1; } /* 0x0 */ union { @@ -120,9 +129,9 @@ struct JAISoundStatus_ { /* 0x1 */ union { u8 value; struct { - u8 flag1 : 1; + u8 mComesBack : 1; u8 flag2 : 1; - u8 flag3 : 1; + u8 mPauseWhenOut : 1; u8 flag4 : 1; u8 flag5 : 1; u8 flag6 : 1; @@ -271,10 +280,16 @@ class JAITempoMgr; */ class JAISound { public: +#if TARGET_PC + using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot; + using SoundEffectReplacementSlot = dusk::mods::svc::audio_res::bst::SoundEffectReplacementSlot; + using StreamReplacementSlot = dusk::mods::svc::audio_res::bst::StreamReplacementSlot; +#endif + void releaseHandle(); void attachHandle(JAISoundHandle* handle); JAISound(); - void start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience); + void start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)); bool acceptsNewAudible() const; void newAudible(const JGeometry::TVec3&, JGeometry::TVec3 const*, u32, JAIAudience*); @@ -297,6 +312,11 @@ public: virtual bool JAISound_tryDie_() = 0; JAISoundID getID() const { return soundID_; } +#if TARGET_PC + SoundTableReplacementSlot* getReplacement() const { + return replacement.get(); + } +#endif u32 getAnimationState() const { return status_.state.flags.animationState; } bool isAnimated() const { return getAnimationState() != 0; } void setAnimationState(u32 state) { @@ -309,7 +329,7 @@ public: bool hasLifeTime() const { return status_.field_0x1.flags.flag2; } void removeLifeTime_() { - status_.field_0x1.flags.flag1 = false; + status_.field_0x1.flags.mComesBack = false; status_.field_0x1.flags.flag2 = 0; } @@ -346,7 +366,7 @@ public: void setComesBack(bool param_0) { JUT_ASSERT(354, status_.state.flags.calcedOnce == 0); - status_.field_0x1.flags.flag1 = 1; + status_.field_0x1.flags.mComesBack = 1; if (param_0) { status_.pauseWhenOut(); } @@ -380,6 +400,9 @@ public: /* 0x34 */ u32 priority_; /* 0x38 */ s32 count_; /* 0x3C */ JAISoundParams params_; +#if TARGET_PC + std::shared_ptr replacement; +#endif }; // Size: 0x98 STATIC_ASSERT(sizeof(JAISound) == 0x98); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h b/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h index 4f536a0f63..23713d96a9 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h @@ -9,13 +9,17 @@ * */ struct JAISoundInfo : public JASGlobalInstance { + IF_DUSK(using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot); + IF_DUSK(using SoundEffectReplacementSlot = dusk::mods::svc::audio_res::bst::SoundEffectReplacementSlot); + IF_DUSK(using StreamReplacementSlot = dusk::mods::svc::audio_res::bst::StreamReplacementSlot); + JAISoundInfo(bool); virtual int getSoundType(JAISoundID) const = 0; virtual int getCategory(JAISoundID) const = 0; - virtual u32 getPriority(JAISoundID) const = 0; - virtual void getSeInfo(JAISoundID, JAISe*) const = 0; + virtual u32 getPriority(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const = 0; + virtual void getSeInfo(JAISoundID, JAISe* IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const = 0; virtual void getSeqInfo(JAISoundID, JAISeq*) const = 0; - virtual void getStreamInfo(JAISoundID, JAIStream*) const = 0; + virtual void getStreamInfo(JAISoundID, JAIStream* IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const = 0; virtual ~JAISoundInfo(); }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h b/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h index 22d175154a..eccdc8deac 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h @@ -9,14 +9,14 @@ */ struct JAISoundParamsProperty { void init() { - field_0x0 = 1.0f; - field_0x4 = 0.0f; - field_0x8 = 1.0f; + mVolume = 1.0f; + mFxMix = 0.0f; + mPitch = 1.0f; } - /* 0x00 */ f32 field_0x0; - /* 0x04 */ f32 field_0x4; - /* 0x08 */ f32 field_0x8; + /* 0x00 */ f32 mVolume; + /* 0x04 */ f32 mFxMix; + /* 0x08 */ f32 mPitch; }; // Size: 0xC /** diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStream.h b/libs/JSystem/include/JSystem/JAudio2/JAIStream.h index d6d6694be0..31ef4da4f5 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStream.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStream.h @@ -20,7 +20,7 @@ public: JAIStream(JAIStreamMgr* streamMgr, JAISoundStrategyMgr* soundStrategyMgr); void JAIStreamMgr_startID_(JAISoundID id, s32 streamFileEntry, const JGeometry::TVec3* posPtr, JAIAudience* audience, - int category); + int category IF_DUSK_ARG(std::shared_ptr replacement)); bool prepare_prepareStream_(); void prepare_(); void prepare_startStream_(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h index eee03cc168..d528a25f85 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h @@ -8,7 +8,9 @@ * */ struct JAIStreamDataMgr { - virtual s32 getStreamFileEntry(JAISoundID) = 0; + IF_DUSK(using StreamReplacementSlot2 = dusk::mods::svc::audio_res::bst::StreamReplacementSlot); + + virtual s32 getStreamFileEntry(JAISoundID IF_DUSK_ARG(StreamReplacementSlot2 const*)) = 0; virtual ~JAIStreamDataMgr(); }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h index 7c8e68a852..27d69d57df 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h @@ -15,7 +15,7 @@ struct JAIStreamDataMgr; class JAIStreamMgr : public JASGlobalInstance { public: JAIStreamMgr(bool setInstance); - bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr); + bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)); void freeDeadStream_(); void calc(); void stop(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h index 5fe6b84db4..fdd141a5c5 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h @@ -14,20 +14,20 @@ const int OSC_MAX = 2; */ struct JASInstParam : public JASSoundParams { JASInstParam() { - field_0x14 = NULL; - field_0x18 = 0; - field_0x1a = 0; - field_0x1c = 0; - field_0x1d = 0; - field_0x1e = 0; + mOscillators = NULL; + mDirectRelease = 0; + mWaveId = 0; + mChannelType = 0; + mOscillatorCount = 0; + mDontSetKey = 0; } - JASOscillator::Data** field_0x14; - u16 field_0x18; - u16 field_0x1a; - u8 field_0x1c; - u8 field_0x1d; - u8 field_0x1e; + JASOscillator::Data** mOscillators; + u16 mDirectRelease; + u16 mWaveId; + u8 mChannelType; // Always CHANNEL_WAVE in practice + u8 mOscillatorCount; + u8 mDontSetKey; }; /** @@ -52,10 +52,10 @@ struct JASBasicInst : public JASInst { void setHighKey(int key) { mHighKey = key; } /* 0x0 */ s32 mHighKey; - /* 0x4 */ u16 field_0x4; + /* 0x4 */ u16 mWaveId; /* 0x6 */ u16 field_0x6; - /* 0x8 */ f32 field_0x8; - /* 0xC */ f32 field_0xc; + /* 0x8 */ f32 mVolumeMult; + /* 0xC */ f32 mPitchMult; }; JASBasicInst(); @@ -74,7 +74,7 @@ struct JASBasicInst : public JASInst { /* 0x04 */ f32 mVolume; /* 0x08 */ f32 mPitch; - /* 0x0C */ JASOscillator::Data const* field_0xc[OSC_MAX]; + /* 0x0C */ JASOscillator::Data const* mOscillators[OSC_MAX]; /* 0x10 */ u32 mKeymapCount; /* 0x14 */ TKeymap* mKeymap; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h index f6a10fa0c7..ed4ddbf4cd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h @@ -12,21 +12,27 @@ struct JASBasicWaveBank : public JASWaveBank { struct TWaveHandle : public JASWaveHandle { TWaveHandle() { mHeap = NULL; } virtual intptr_t getWavePtr() const; - virtual const JASWaveInfo* getWaveInfo() const { return &field_0x4; } + virtual const JASWaveInfo* getWaveInfo() const { return &mWaveInfo; } +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] void const* getAramBaseAddress() const override { return nullptr; } +#endif bool compareHeap(JASHeap* heap) const { return mHeap == heap;} - /* 0x04 */ JASWaveInfo field_0x4; + /* 0x04 */ JASWaveInfo mWaveInfo; /* 0x28 */ JASHeap* mHeap; }; struct TGroupWaveInfo { TGroupWaveInfo() { - field_0x0 = 0xffff; - field_0x4 = -1; + waveId = 0xffff; + mOffsetStart = -1; } - /* 0x00 */ u16 field_0x0; - /* 0x04 */ int field_0x4; + /* 0x00 */ u16 waveId; + /* 0x04 */ int mOffsetStart; }; struct TWaveGroup : JASWaveArc { @@ -44,7 +50,7 @@ struct JASBasicWaveBank : public JASWaveBank { u32 getWaveCount() const { return mWaveCount; } }; - JASBasicWaveBank(); + JASBasicWaveBank(IF_DUSK(u32 bankId)); ~JASBasicWaveBank(); TWaveGroup* getWaveGroup(u32); void setGroupCount(u32, JKRHeap*); @@ -56,7 +62,7 @@ struct JASBasicWaveBank : public JASWaveBank { JASWaveArc* getWaveArc(u32 param_0) { return getWaveGroup(param_0); } u32 getArcCount() const { return mGroupCount; } - /* 0x04 */ OSMutex field_0x4; + /* 0x04 */ OSMutex mWaveTableMutex; /* 0x1C */ TWaveHandle* mWaveTable; /* 0x20 */ TWaveGroup* mWaveGroupArray; /* 0x24 */ u16 mHandleCount; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index af922638da..77bfb45966 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -8,6 +8,7 @@ #include "JSystem/JAudio2/JASWaveInfo.h" #include "JSystem/JAudio2/JASDSPInterface.h" #include +#include struct JASDSPChannel; @@ -15,6 +16,10 @@ namespace JASDsp { struct TChannel; } +#if TARGET_PC +#include "JSystem/JAudio2/JASSampleDataReference.h" +#endif + /** * @ingroup jsystem-jaudio * @@ -159,13 +164,30 @@ public: struct { u32 mChannelType; // CHANNEL_WAVE or CHANNEL_OSCILLATOR JASWaveInfo mWaveInfo; - } field_0xdc; + } mAnon; union { u32 mWaveAramAddress; u32 mOscillatorSomething; u32 field_0x104; }; +#if TARGET_PC + /** + * Memory address at which this sound effect should consider ARAM to start. + * + * By changing this, sound effects can effectively be played back from anywhere in memory, + * rather than just the emulated ARAM space. + * + * If nullptr, the regular emulated ARAM is used. + */ + void const* mAramBaseAddress; + + /** + * @see JASSampleDataReference + */ + std::unique_ptr mSampleReference; +#endif + static DUSK_GAME_DATA OSMessageQueue sBankDisposeMsgQ; static DUSK_GAME_DATA OSMessage sBankDisposeMsg[16]; static DUSK_GAME_DATA OSMessage sBankDisposeList[16]; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h index b413052da7..c45b72f2d8 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h @@ -109,7 +109,9 @@ namespace JASDsp { * Pitch shift via changing playback speed. */ /* 0x004 */ u16 mPitch; +#if !TARGET_PC /* 0x006 */ short _unused1; +#endif /** * Set to 1 when playback starts, cleared by DSP later, @@ -118,19 +120,27 @@ namespace JASDsp { * (Corroborated by fields JASAramStream checks never being cleared explicitly by CPU.) */ /* 0x008 */ u16 mResetFlag; +#if !TARGET_PC /* 0x00A */ u8 _unused2[0x00C - 0x00A]; +#endif /* 0x00C */ s16 mPauseFlag; +#if !TARGET_PC /* 0x00E */ short _unused3; +#endif /* 0x010 */ OutputChannelConfig mOutputChannels[DSP_OUTPUT_CHANNELS]; +#if !TARGET_PC /* 0x040 */ u8 _unused4[0x050 - 0x040]; +#endif /* 0x050 */ u16 mAutoMixerPanDolby; // pan is upper 8 bits, dolby lower 8. /* 0x052 */ u16 mAutoMixerFxMix; /* 0x054 */ u16 mAutoMixerInitVolume; /* 0x056 */ u16 mAutoMixerVolume; /* 0x058 */ u16 mAutoMixerBeenSet; +#if !TARGET_PC /* 0x05A */ u8 _unused5[0x060 - 0x05A]; /* 0x060 */ short field_0x060; // Only cleared to zero, presumed used by DSP. /* 0x062 */ u8 _unused6[0x064 - 0x062]; +#endif /** * Samples per ADPCM frame for ADPCM audio. Seems just set to 1 for PCM formats. @@ -139,7 +149,9 @@ namespace JASDsp { /* 0x064 */ u16 mSamplesPerBlock; /* 0x066 */ short field_0x066; // Only cleared to zero, presumed used by DSP. /* 0x068 */ u32 mSamplePosition; // Only ever initialized by code, name is guess. +#if !TARGET_PC /* 0x06C */ u8 _unused7[0x070 - 0x06C]; +#endif /** * Current audio read position in ARAM. Updated by DSP. @@ -151,11 +163,13 @@ namespace JASDsp { * Gets written by DSP, but also CPU. */ /* 0x074 */ u32 mSamplesLeft; // Never directly cleared to zero. Seems sus. Cleared by DSP? +#if !TARGET_PC /* 0x078 */ short field_0x078[4]; // Only cleared to zero, presumed used by DSP. /* 0x080 */ short field_0x080[20]; // Only cleared to zero, presumed used by DSP. /* 0x0A8 */ short field_0x0a8[4]; // Only cleared to zero, presumed used by DSP. /* 0x0B0 */ u16 field_0x0b0[16]; // Only cleared to zero, presumed used by DSP. /* 0x0D0 */ u8 _unused8[0x100 - 0x0D0]; +#endif /* 0x100 */ u16 mBytesPerBlock; /* 0x102 */ u16 mLoopFlag; @@ -176,9 +190,26 @@ namespace JASDsp { /* 0x118 */ u32 mWaveAramAddress; /* 0x11C */ int mSampleCount; /* 0x120 */ s16 fir_filter_params[8]; +#if !TARGET_PC /* 0x130 */ u8 _unused9[0x148 - 0x130]; +#endif /* 0x148 */ s16 iir_filter_params[8]; +#if !TARGET_PC /* 0x158 */ u8 _unused10[0x180 - 0x158]; +#endif + +#if TARGET_PC + /** + * Memory address at which this sound effect should consider ARAM to start. + * This means fields like mWaveAramAddress will be relative to this pointer instead. + * + * By changing this, sound effects can effectively be played back from anywhere in memory, + * rather than just the emulated ARAM space. + * + * If nullptr, the regular emulated ARAM is used. + */ + void const* mAramBaseAddress; +#endif }; void boot(void (*)(void*)); diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h b/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h index 8e5c8df244..f791b21865 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h @@ -21,10 +21,10 @@ struct JASDrumSet : public JASInst { /* 0x00 */ f32 mVolume; /* 0x04 */ f32 mPitch; /* 0x08 */ f32 mPan; - /* 0x0C */ u16 field_0xc; - /* 0x0E */ u16 field_0xe; - /* 0x10 */ f32 field_0x10; - /* 0x14 */ f32 field_0x14; + /* 0x0C */ u16 mDirectRelease; + /* 0x0E */ u16 mWaveId; + /* 0x10 */ f32 mVolumeMult; + /* 0x14 */ f32 mPitchMult; }; JASDrumSet(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h b/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h new file mode 100644 index 0000000000..d196b4f34c --- /dev/null +++ b/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h @@ -0,0 +1,18 @@ +#ifndef DUSKLIGHT_JASSAMPLEDATAREFERENCE_H +#define DUSKLIGHT_JASSAMPLEDATAREFERENCE_H + +#if TARGET_PC +/** + * An object to manage the lifetime of the sample data pointed to by JASChannel. + * + * JASChannel can (optionally) accept an unique_ptr to an object to this type, + * in which case it will be destroyed when the JASChannel is done. + * + * @see JASChannel::mSampleReference + */ +struct JASSampleDataReference { + virtual ~JASSampleDataReference() = default; +}; +#endif + +#endif // DUSKLIGHT_JASSAMPLEDATAREFERENCE_H diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h index 4fbeee32b9..8ccbc659bd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h @@ -10,12 +10,18 @@ struct JASSimpleWaveBank : JASWaveBank, JASWaveArc { intptr_t getWavePtr() const; TWaveHandle(); const JASWaveInfo* getWaveInfo() const; +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] void const* getAramBaseAddress() const override { return nullptr; } +#endif /* 0x04 */ JASWaveInfo mWaveInfo; /* 0x28 */ JASHeap* mHeap; }; - JASSimpleWaveBank(); + JASSimpleWaveBank(IF_DUSK(u32 bankId)); ~JASSimpleWaveBank(); void setWaveTableSize(u32, JKRHeap*); JASWaveHandle* getWaveHandle(u32) const; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h b/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h index 9fe201308c..da9af7c37c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h @@ -26,7 +26,9 @@ public: }; struct TCtrlWave { - /* 0x0 */ BE(u32) _00; + // Wave ID in lower half. + // Upper bits appear to be group ID, which is unused by the game code. + /* 0x0 */ BE(u32) mWaveAndGroupId; }; struct TWave { @@ -57,13 +59,14 @@ public: }; struct TCtrl { - /* 0x0 */ u8 _00[4]; + /* 0x0 */ BE(u32) mMagic; // 'C-DF'. /* 0x4 */ BE(u32) mWaveCount; /* 0x8 */ TOffset mCtrlWaveOffsets[0]; }; struct TCtrlScene { - /* 0x0 */ u8 _00[0xC]; + /* 0x0 */ BE(u32) mMagic; // 'SCNE' + /* 0x4 */ u8 _00[0x8]; /* 0xC */ TOffset mCtrlOffset; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h index 0ad4e044c8..7caa344c8f 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h @@ -66,7 +66,7 @@ struct JASWaveArc : JASDisposer { }; /* 0x04 */ mutable JASHeap mHeap; - /* 0x48 */ u32 _48; + /* 0x48 */ u32 mCurrentlyLoaded; /* 0x4C */ volatile s32 mStatus; /* 0x50 */ int mEntryNum; /* 0x54 */ u32 mFileLength; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 04bd541204..a092f7450e 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -2,6 +2,11 @@ #define JASWAVEINFO_H #include +#include + +#if TARGET_PC +#include "JSystem/JAudio2/JASSampleDataReference.h" +#endif struct JASWaveArc; @@ -17,7 +22,7 @@ struct JASWaveArc; struct JASWaveInfo { JASWaveInfo() { mBaseKey = 0x3c; - field_0x20 = &one; + mpLoaded = &one; } /* 0x00 */ u8 mWaveFormat; @@ -31,7 +36,7 @@ struct JASWaveInfo { /* 0x18 */ int mSampleCount; /* 0x1C */ s16 mpLast; /* 0x1E */ s16 mpPenult; - /* 0x20 */ const u32* field_0x20; + /* 0x20 */ const u32* mpLoaded; static DUSK_GAME_DATA u32 one; }; @@ -45,6 +50,17 @@ public: virtual ~JASWaveHandle() {} virtual const JASWaveInfo* getWaveInfo() const = 0; virtual intptr_t getWavePtr() const = 0; +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] virtual void const* getAramBaseAddress() const = 0; + + /** + * Create a @ref JASSampleDataReference to keep the sample data for this wave alive. + */ + [[nodiscard]] virtual std::unique_ptr getSampleReference() const { return nullptr; } +#endif }; /** @@ -53,6 +69,12 @@ public: */ class JASWaveBank { public: +#if TARGET_PC + const u32 bankId; + + JASWaveBank(u32 bankId) : bankId(bankId) { } +#endif + virtual ~JASWaveBank() {} virtual JASWaveHandle* getWaveHandle(u32) const = 0; virtual JASWaveArc* getWaveArc(u32) = 0; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h index cc5653fe25..4c0ade1282 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h @@ -10,7 +10,7 @@ */ struct JAUAudibleParam { f32 getDopplerPower() const { - return field_0x0.bytes.b0_0 * (1.0f / 15.0f); + return field_0x0.bytes.mDopplerPower * (1.0f / 15.0f); } union { @@ -19,14 +19,18 @@ struct JAUAudibleParam { BE(u16) f1; } half; struct { - u8 b0_0 : 4; - u8 b0_4 : 1; - u8 b0_5 : 1; - u8 b0_6 : 1; - u8 b0_7 : 1; - u8 b1_0 : 1; - u8 b1_1 : 1; - u8 b1_2_7 : 6; + u8 mDopplerPower : 4; + u8 mCalculatePriority : 1; + u8 mCalcDistanceVolume : 1; + u8 mCalcFxMix : 1; + u8 mCullAtMaxDistance : 1; + u8 mCalcPan : 1; + u8 mCalcDolby : 1; + /** + * Most bits in this field are unused, 8 indicates clamping of volume + * to ensure it doesn't go below 0.2 in mixChannelOut() + */ + u8 mClampMinVolume : 6; u8 b2; u8 b3; } bytes; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h index 86fa88f913..4083b4e774 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h @@ -10,8 +10,10 @@ */ class JAUSoundInfo : public JASGlobalInstance { public: + IF_DUSK(using SoundTableReplacementSlot2 = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot); + JAUSoundInfo(bool param_0) : JASGlobalInstance(param_0) {} - virtual u16 getAudibleSw(JAISoundID) const = 0; + virtual u16 getAudibleSw(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot2 const*)) const = 0; virtual u16 getBgmSeqResourceID(JAISoundID) const = 0; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index fbd3427649..3b38ff2eb6 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -5,16 +5,179 @@ #include "JSystem/JAudio2/JASGadget.h" #include "helpers/endian.h" +// Constants for TypeIDs of JAUSoundTable entries. + +/** + * Used as a test to see if the type ID is valid. All other type IDs contain this bit. + */ +#define SOUND_TYPEID_VALID 0x40 + +/** + * Sound is a sound effect. + */ +#define SOUND_TYPEID_SOUND_EFFECT 0x51 + +/** + * Sound is a music sequence. + */ +#define SOUND_TYPEID_SEQUENCE 0x60 + +/** + * Sound is a streamed music file. + */ +#define SOUND_TYPEID_STREAM 0x70 + +/** + * Sound is a streamed music file. Stopped automatically on scene change by Z2SceneMgr.cpp + */ +#define SOUND_TYPEID_STREAM_ALT 0x71 + +// +// Values for mSwBit on sound effect items. +// + +/** + * Sound is always calculated as max priority (0). + */ +#define SOUND_SW_ALWAYS_MAX_PRIORITY 0x0000'0001 + +/** + * Don't calculate volume by distance. + */ +#define SOUND_SW_IGNORE_DISTANCE_VOL 0x0000'0002 + +/** + * Don't calculate FX mix (reverb) by distance. + */ +#define SOUND_SW_IGNORE_FX_MIX 0x0000'0004 + +/** + * Mute all BGM sequences while this sound is playing. + */ +#define SOUND_SW_MUTE_BGM 0x0000'0008 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK + */ +#define SOUND_SW_RANDOM_PITCH_OFFSET 4 + +/** + * 4-bit value (0-15) to control the power of pitch randomization on sound playback. + * Code acts different for values above 8, not sure what the exact implication is. + */ +#define SOUND_SW_RANDOM_PITCH_MASK 0x0000'00F0 + +/** + * Offset to shift to access @see SOUND_SW_DOPPLER_POWER_MASK + */ +#define SOUND_SW_DOPPLER_POWER_OFFSET 8 + +/** + * 4-bit value (0-15) to scale the power of the Doppler effect for this sound. + */ +#define SOUND_SW_DOPPLER_POWER_MASK 0x0000'0F00 + +/** + * Don't calculate panning (left/right) values for this sound. + */ +#define SOUND_SW_IGNORE_PAN 0x0000'1000 + +/** + * Don't calculate Dolby (behind/front) values for this sound. + */ +#define SOUND_SW_IGNORE_DOLBY 0x0000'2000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_1 0x0000'4000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_2 0x0000'8000 + +/** + * 3-bit mask used to select a volume distance/falloff class for this sound. + */ +#define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000 +#define SOUND_SW_VOL_DIST_BIT_OFFSET 16 + +/** + * Limit minimum volume of this sound (after distance falloff) to 0.2. + */ +#define SOUND_SW_CLAMP_MIN_VOLUME 0x0008'0000 + +/** + * 3-bit mask used to select a *different* volume distance/falloff class for this sound. + * @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work. + */ +#define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000 +#define SOUND_SW_VOL_DIST_BIT_2_OFFSET 20 + +/** + * Mark sound as "far away" or "culled" when at max distance (selected by distance class). + * This affects a bunch of stuff like culling, automatic stopping, priorities, etc. + */ +#define SOUND_SW_CULL_AT_MAX_DISTANCE 0x0080'0000 + +/** + * Not sure what this does. + * Something causing volume/pan/dolby adjustment in Z2Audible::setOuterParams? + */ +#define SOUND_SW_VOL_SOMETHING_MASK 0x0F00'0000 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_VOLUME_MASK + */ +#define SOUND_SW_RANDOM_VOLUME_OFFSET 28 + +/** + * 4-bit value (0-15) to control the power of volume randomization on sound playback. + */ +#define SOUND_SW_RANDOM_VOLUME_MASK 0xF000'0000 + +#define SOUND_VOL_DIST_BIT_MASK_SHIFTED (SOUND_SW_VOL_DIST_BIT_MASK >> 16) +#define SOUND_VOL_DIST_BIT_2_MASK_SHIFTED (SOUND_SW_VOL_DIST_BIT_2_MASK >> 16) +#define SOUND_VOL_SOMETHING_MASK_SHIFTED (SOUND_SW_VOL_SOMETHING_MASK >> 16) + /** * @ingroup jsystem-jaudio - * + * A single entry in the sound table. + * Fields are interpreted differently depending on the type ID of the entry, + * which is not stored in this struct. */ struct JAUSoundTableItem { u8 mPriority; - u8 field_0x1; - BE(u16) mResourceId; - BE(u32) field_0x4; - BE(f32) field_0x8; + u8 mVolume; + union { + /** + * For music sequences: the resource ID of the sequence. + */ + BE(u16) mResourceId; + + /** + * For streamed music: bitpacked channel configuration. + */ + BE(u16) mStreamPanParameters; + }; + union { + /** + * For sound effects: bitpacked field controlling a *bunch* of audio parameters. + */ + BE(u32) mSwBit; + + /** + * For streamed music: offset (relative to start of BST) + * to null-terminated string containing music file path. + */ + BE(u32) mStreamFilePath; + }; + + /** + * For sound effects: pitch multiplier. + */ + BE(f32) mPitch; }; /** @@ -24,23 +187,25 @@ struct JAUSoundTableItem { template struct JAUSoundTable_ { JAUSoundTable_() { - field_0x0 = NULL; - field_0x4 = 0; + mData = NULL; + mRoot = 0; } void reset() { - field_0x0 = NULL; - field_0x4 = NULL; + mData = NULL; + mRoot = NULL; } - void init(const void* param_0) { - field_0x0 = param_0; + void init(const void* dataStart) { + mData = dataStart; // magic number is not in debug rom. I'm not sure what this comparison is (maybe some sort of '' number?) // I also do not know how it is different between JAUSoundTable and JAUSoundNameTable - if (*(BE(u32)*)field_0x0 + 0xbdad0000 != Root::magicNumber()) { - field_0x0 = NULL; + // Future person here: This is checking for either "BST " or "BSTN", with the second two letters in Root::magicNumber(). + // Idk why the operations here are all weird but I can't use objdiff right now. + if (*(BE(u32)*)mData + 0xbdad0000 != Root::magicNumber()) { + mData = NULL; } else { - field_0x4 = (Root*)((u8*)field_0x0 + *((BE(u32)*)field_0x0 + 3)); + mRoot = (Root*)((u8*)mData + *((BE(u32)*)mData + 3)); } } @@ -48,34 +213,34 @@ struct JAUSoundTable_ { if (index < 0) { return NULL; } - if ((u32)index >= field_0x4->mSectionNumber) { + if ((u32)index >= mRoot->mSectionNumber) { return NULL; } - u32 offset = field_0x4->mSectionOffsets[index]; + u32 offset = mRoot->mSectionOffsets[index]; if (offset == 0) { return NULL; } - return (Section*)((u8*)field_0x0 + offset); + return (Section*)((u8*)mData + offset); } - Group* getGroup(Section* param_1, int index) const { + Group* getGroup(Section* section, int index) const { int iVar1; if (index < 0) { return NULL; } - if ((u32)index >= param_1->mNumGroups) { + if ((u32)index >= section->mNumGroups) { return NULL; } - u32 offset = param_1->getGroupOffset(index); + u32 offset = section->getGroupOffset(index); if (offset == 0) { return NULL; } - return (Group*)((u8*)field_0x0 + offset); + return (Group*)((u8*)mData + offset); } - const void* field_0x0; - Root* field_0x4; + const void* mData; + Root* mRoot; }; /** @@ -83,7 +248,7 @@ struct JAUSoundTable_ { * */ struct JAUSoundTableRoot { - static inline u32 magicNumber() { return 0x5420; } + static inline u32 magicNumber() { return 'T '; } // Second half of "BST " BE(u32) mSectionNumber; BE(u32) mSectionOffsets[0]; }; @@ -134,7 +299,7 @@ struct JAUSoundTableGroup { BE(u32) mNumItems; BE(u32) field_0x4; - u8 mTypeIds[0]; + u8 mTypeIds[0]; // TODO: Should probably be BE(u32), but I can't objdiff rn. }; /** @@ -142,13 +307,17 @@ struct JAUSoundTableGroup { * */ struct JAUSoundTable : public JASGlobalInstance { - JAUSoundTable(bool param_0) : JASGlobalInstance(param_0) { +#if TARGET_PC + using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot; +#endif + + JAUSoundTable(bool setInstance) : JASGlobalInstance(setInstance) { } ~JAUSoundTable() {} void init(void const*); - u8 getTypeID(JAISoundID) const; - JAUSoundTableItem* getData(JAISoundID) const; + u8 getTypeID(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const*)) const; + JAUSoundTableItem DUSK_CONST* getData(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const*)) const; int getNumGroups_inSection(u8) const; int getNumItems_inGroup(u8, u8) const; @@ -157,11 +326,11 @@ struct JAUSoundTable : public JASGlobalInstance { if (offset == 0) { return NULL; } - return (JAUSoundTableItem*)((u8*)field_0x0.field_0x0 + offset); + return (JAUSoundTableItem*)((u8*)field_0x0.mData + offset); } - const void* getResource() const { return field_0x0.field_0x0; } - bool isValid() const { return field_0x0.field_0x0 != NULL; } + const void* getResource() const { return field_0x0.mData; } + bool isValid() const { return field_0x0.mData != NULL; } JAUSoundTable_ field_0x0; }; @@ -171,7 +340,7 @@ struct JAUSoundTable : public JASGlobalInstance { * */ struct JAUSoundNameTableRoot { - static inline u32 magicNumber() { return 0x544e; } + static inline u32 magicNumber() { return 'TN'; } // Second half of "BSTN" BE(u32) mSectionNumber; BE(u32) mSectionOffsets[0]; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h index 65eef3f8b9..1f29d54f9d 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h @@ -29,7 +29,7 @@ struct JAUStreamFileTable { * */ struct JAUStreamDataMgr_StreamFileTable : public JAIStreamDataMgr, JAUStreamFileTable { - virtual s32 getStreamFileEntry(JAISoundID); + virtual s32 getStreamFileEntry(JAISoundID IF_DUSK_ARG(StreamReplacementSlot2 const*)); virtual ~JAUStreamDataMgr_StreamFileTable() {} }; diff --git a/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h b/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h index 84fc657387..deca5cc102 100644 --- a/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h +++ b/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h @@ -4,13 +4,12 @@ #include #if TARGET_PC -#include - struct ParticleDrawCtx { bool batch; // off = immediate mode bool useTexMtx; // UVs transformed by texMtx bool useClr0; // prm color in GX_VA_CLR0 bool useClr1; // env color in GX_VA_CLR1 + float age; Mtx texMtx; GXColor clr0; GXColor clr1; @@ -232,11 +231,5 @@ void JPARegistPrmAlphaEnv(JPA_DRAW_PARTICLE_ARGS); #undef JPA_DRAW_PARTICLE_ARGS -#if TARGET_PC -void JPAInterpBillboard(JPAEmitterWorkData*, JPABaseParticle*); -void JPAInterpRotBillboard(JPAEmitterWorkData*, JPABaseParticle*); -void JPAInterpDirection(JPAEmitterWorkData*, JPABaseParticle*); -void JPAInterpRotDirection(JPAEmitterWorkData*, JPABaseParticle*); -#endif #endif /* JPABASESHAPE_H */ diff --git a/libs/JSystem/include/JSystem/JParticle/JPAParticle.h b/libs/JSystem/include/JSystem/JParticle/JPAParticle.h index c864a5cd4a..842f293286 100644 --- a/libs/JSystem/include/JSystem/JParticle/JPAParticle.h +++ b/libs/JSystem/include/JSystem/JParticle/JPAParticle.h @@ -24,9 +24,6 @@ public: void init_c(JPAEmitterWorkData*, JPABaseParticle*); bool calc_p(JPAEmitterWorkData*); bool calc_c(JPAEmitterWorkData*); -#if TARGET_PC - void interp(JPAEmitterWorkData*, void const* drawFunc); -#endif bool canCreateChild(JPAEmitterWorkData*); f32 getWidth(JPABaseEmitter const*) const; f32 getHeight(JPABaseEmitter const*) const; diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DAnimation.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DAnimation.cpp index 8bbdc68204..3ad9093ff8 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DAnimation.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DAnimation.cpp @@ -5,7 +5,12 @@ #include "JSystem/J3DGraphBase/J3DStruct.h" #include "JSystem/JMath/JMath.h" +#if TARGET_PC +#include "dusk/interp/material.h" +#endif + void J3DFrameCtrl::init(s16 endFrame) { + IF_DUSK(dusk::interp::material::reset(this)); mAttribute = EMode_LOOP; mState = 0; mStart = 0; @@ -134,6 +139,7 @@ int J3DFrameCtrl::checkPass(f32 passFrame) { void J3DFrameCtrl::update() { + IF_DUSK(dusk::interp::material::Update materialUpdate(*this)); mState = 0; mFrame += mRate; @@ -1456,3 +1462,67 @@ void J3DAnmTevRegKey::searchUpdateMaterialID(J3DModelData* pModelData) { J3D_ASSERT_NULLPTR(2119, pModelData != NULL); searchUpdateMaterialID(&pModelData->getMaterialTable()); } + +#if TARGET_PC +J3DAnmBase::J3DAnmBase() { + dusk::interp::material::reset(this); + mAttribute = 0; + field_0x5 = 0; + mFrameMax = 0; + mFrame = 0.0f; +} + +J3DAnmBase::J3DAnmBase(s16 frameMax) { + dusk::interp::material::reset(this); + mAttribute = 0; + field_0x5 = 0; + mFrameMax = frameMax; + mFrame = 0.0f; +} + +J3DAnmBase::~J3DAnmBase() { + dusk::interp::material::reset(this); +} + +void J3DAnmBase::setFrame(f32 frame) { + dusk::interp::material::reset(this); + mFrame = frame; +} + +J3DFrameCtrl::~J3DFrameCtrl() { + dusk::interp::material::reset(this); +} + +void J3DFrameCtrl::setAttribute(u8 attr) { + dusk::interp::material::reset(this); + mAttribute = attr; +} + +void J3DFrameCtrl::setStart(s16 start) { + dusk::interp::material::reset(this); + mStart = start; + mFrame = start; +} + +void J3DFrameCtrl::setEnd(s16 end) { + dusk::interp::material::reset(this); + mEnd = end; +} + +void J3DFrameCtrl::setLoop(s16 loop) { + dusk::interp::material::reset(this); + mLoop = loop; +} + +void J3DFrameCtrl::setFrame(f32 frame) { + dusk::interp::material::reset(this); + mFrame = frame; +} + +void J3DFrameCtrl::reset() { + dusk::interp::material::reset(this); + mFrame = mStart; + mRate = 1.0f; + mState = 0; +} +#endif diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp index 77dc45365b..49771243d2 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp @@ -8,7 +8,12 @@ #include "JSystem/JSupport/JSupport.h" +#if TARGET_PC +#include "dusk/interp/vertex.h" +#endif + J3DDeformData::J3DDeformData() { + IF_DUSK(dusk::interp::vertex::invalidate(this)); mClusterNum = 0; mClusterKeyNum = 0; mClusterVertexNum = 0; @@ -67,6 +72,7 @@ void J3DDeformData::deform(J3DVertexBuffer* buffer) { } void J3DDeformData::setAnm(J3DAnmCluster* anm) { + IF_DUSK(dusk::interp::vertex::invalidate(this)); for (u16 i = 0; i < mClusterNum; i++) { DEFORMER(i)->setAnmCluster(anm); } diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DMaterialAnm.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DMaterialAnm.cpp index f280a1583e..cd18d9ec82 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DMaterialAnm.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DMaterialAnm.cpp @@ -3,6 +3,10 @@ #include "JSystem/J3DGraphAnimator/J3DMaterialAnm.h" #include "JSystem/J3DGraphBase/J3DMaterial.h" +#if TARGET_PC +#include "dusk/interp/material.h" +#endif + void J3DMaterialAnm::initialize() { for (int i = 0; i < ARRAY_SIZE(mMatColorAnm); i++) { mMatColorAnm[i].setAnmFlag(false); @@ -114,3 +118,44 @@ void J3DMaterialAnm::setTevKColorAnm(int idx, J3DTevKColorAnm* pTevKColorAnm) { mTevKColorAnm[idx] = *pTevKColorAnm; } } + +#if TARGET_PC +bool J3DMaterialAnm::hasMaterialAnimation() const { + for (const auto& track : mMatColorAnm) { + if (track.getAnmFlag()) return true; + } + for (const auto& track : mTexMtxAnm) { + if (track.getAnmFlag()) return true; + } + for (const auto& track : mTevColorAnm) { + if (track.getAnmFlag()) return true; + } + for (const auto& track : mTevKColorAnm) { + if (track.getAnmFlag()) return true; + } + return false; +} + +void J3DMatColorAnm::calc(GXColor* pColor) const { + J3D_ASSERT_NULLPTR(507, pColor != NULL); + dusk::interp::material::Sample materialSample(mAnmColor); + mAnmColor->getColor(field_0x0, pColor); +} + +void J3DTexMtxAnm::calc(J3DTextureSRTInfo* pSRTInfo) const { + J3D_ASSERT_NULLPTR(519, pSRTInfo != NULL); + dusk::interp::material::sample_texture(mAnmTransform, field_0x0, pSRTInfo); +} + +void J3DTevColorAnm::calc(GXColorS10* pColor) const { + J3D_ASSERT_NULLPTR(545, pColor != NULL); + dusk::interp::material::Sample materialSample(mAnmTevReg); + mAnmTevReg->getTevColorReg(field_0x0, pColor); +} + +void J3DTevKColorAnm::calc(GXColor* pColor) const { + J3D_ASSERT_NULLPTR(558, pColor != NULL); + dusk::interp::material::Sample materialSample(mAnmTevReg); + mAnmTevReg->getTevKonstReg(field_0x0, pColor); +} +#endif diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp index 51f3951e38..7f7c999832 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp @@ -10,7 +10,9 @@ #include "JSystem/JKernel/JKRHeap.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/material.h" +#include "dusk/interp/vertex.h" #endif #define J3D_ASSERTMSG(LINE, COND, MSG) JUT_ASSERT_MSG(LINE, (COND) != 0, MSG) @@ -29,6 +31,7 @@ void J3DModel::initialize() { MTXIdentity(mBaseTransformMtx); MTXIdentity(mInternalView); + IF_DUSK(MTXIdentity(mPresentationBase)); mMtxBuffer = NULL; mMatPacket = NULL; @@ -101,17 +104,37 @@ s32 J3DModel::entryModelData(J3DModelData* pModelData, u32 mdlFlags, u32 mtxNum) } #if TARGET_PC -void J3DModel::interp_callback(bool isSimFrame, void* pUserWork) { - J3DModel* i_this = static_cast(pUserWork); - if (!isSimFrame) { - i_this->calcMaterial(); - i_this->diff(); +void J3DModel::calc_presentation_base_mtx() { + Mtx identity; + MTXIdentity(identity); + J3DCalcViewBaseMtx(identity, mBaseScale, mBaseTransformMtx, mPresentationBase); + dusk::interp::record_final_mtx(mPresentationBase); + prepare_presentation_view(); +} + +void J3DModel::prepare_presentation_view() { + Mtx replacement; + MtxP presentationBase = mPresentationBase; + if (dusk::interp::lookup_replacement(mPresentationBase, replacement)) { + presentationBase = replacement; } + MTXConcat(j3dSys.getViewMtx(), presentationBase, mInternalView); } void J3DModel::setAnmMtx(int jointNo, Mtx m) { mMtxBuffer->setAnmMtx(jointNo, m); - dusk::frame_interp::record_final_mtx(mMtxBuffer->getAnmMtx(jointNo)); + dusk::interp::record_final_mtx(mMtxBuffer->getAnmMtx(jointNo)); +} + +void J3DModel::forgetMtx() { + dusk::interp::vertex::reset(&mVertexBuffer); + dusk::interp::forget_mtx(mPresentationBase); + for (u16 i = 0; i < mModelData->getJointNum(); ++i) { + dusk::interp::forget_mtx(getAnmMtx(i)); + } + for (u16 i = 0; i < mModelData->getWEvlpMtxNum(); ++i) { + dusk::interp::forget_mtx(getWeightAnmMtx(i)); + } } #endif @@ -299,7 +322,16 @@ void J3DModel::calcMaterial() { material->getMaterialAnm()->calc(material); } - material->calc(getAnmMtx(material->getJoint()->getJntNo())); + MtxP jointMtx = getAnmMtx(material->getJoint()->getJntNo()); +#if TARGET_PC + Mtx presentedJoint; + if (dusk::interp::is_presentation_active() && + dusk::interp::lookup_replacement(jointMtx, presentedJoint)) + { + jointMtx = presentedJoint; + } +#endif + material->calc(jointMtx); } } @@ -468,13 +500,15 @@ void J3DModel::calc() { mCalcCallBack(this, 0); } -#ifdef TARGET_PC +#if TARGET_PC + dusk::interp::vertex::capture(&mVertexBuffer, mDeformData); + for (u16 i = 0; i < mModelData->getJointNum(); ++i) { - dusk::frame_interp::record_final_mtx(getAnmMtx(i)); + dusk::interp::record_final_mtx(getAnmMtx(i)); } for (u16 i = 0; i < mModelData->getWEvlpMtxNum(); ++i) { - dusk::frame_interp::record_final_mtx(getWeightAnmMtx(i)); + dusk::interp::record_final_mtx(getWeightAnmMtx(i)); } #endif } @@ -505,8 +539,9 @@ void J3DModel::entry() { } #if TARGET_PC - if (mModelData->needsInterpCallBack()) - dusk::frame_interp::add_interpolation_callback(&J3DModel::interp_callback, this); + if (mModelData->needsInterpCallBack()) { + dusk::interp::material::record_model(this); + } #endif } @@ -516,18 +551,20 @@ void J3DModel::viewCalc() { if (getModelData()->checkFlag(0x10)) { if (getMtxCalcMode() == 2) { +#if TARGET_PC + calc_presentation_base_mtx(); +#else J3DCalcViewBaseMtx(j3dSys.getViewMtx(), mBaseScale, mBaseTransformMtx, (MtxP)&mInternalView); -#ifdef TARGET_PC - dusk::frame_interp::record_final_mtx(mInternalView); #endif } } else if (isCpuSkinningOn()) { if (getMtxCalcMode() == 2) { +#if TARGET_PC + calc_presentation_base_mtx(); +#else J3DCalcViewBaseMtx(j3dSys.getViewMtx(), mBaseScale, mBaseTransformMtx, (MtxP)&mInternalView); -#ifdef TARGET_PC - dusk::frame_interp::record_final_mtx(mInternalView); #endif } } else if (checkFlag(J3DMdlFlag_SkinPosCpu)) { @@ -549,15 +586,6 @@ void J3DModel::viewCalc() { DCStoreRange(getNrmMtxPtr(), mModelData->getDrawMtxNum() * sizeof(Mtx33)); } -#ifdef TARGET_PC - Mtx* drawMtx = getDrawMtxPtr(); - if (drawMtx != J3DMtxBuffer::sNoUseDrawMtxPtr) { - for (u16 i = 0; i < mModelData->getDrawMtxNum(); ++i) { - dusk::frame_interp::record_final_mtx(drawMtx[i]); - } - } -#endif - prepareShapePackets(); } diff --git a/libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp b/libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp index 40c523643e..2d175ff871 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp @@ -8,7 +8,10 @@ #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" #include "JSystem/J3DGraphBase/J3DMaterial.h" #include "JSystem/JKernel/JKRHeap.h" -#include "tracy/Tracy.hpp" + +#if TARGET_PC +#include +#endif void J3DDrawBuffer::calcZRatio() { mZRatio = (mZFar - mZNear) / (f32)mEntryTableSize; diff --git a/libs/JSystem/src/J3DGraphBase/J3DGD.cpp b/libs/JSystem/src/J3DGraphBase/J3DGD.cpp index eb89fc3b81..74e5c2c759 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DGD.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DGD.cpp @@ -6,7 +6,10 @@ #include "JSystem/J3DGraphBase/J3DGD.h" #include "JSystem/J3DGraphBase/J3DFifo.h" -#include + +#if TARGET_PC +#include "dusk/logging.h" +#endif void J3DGDSetGenMode(u8 nTexGens, u8 nChans, u8 nTevs, u8 nInds, GXCullMode cm) { diff --git a/libs/JSystem/src/J3DGraphBase/J3DMaterial.cpp b/libs/JSystem/src/J3DGraphBase/J3DMaterial.cpp index 48d4731ed9..1bd619c0e0 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DMaterial.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DMaterial.cpp @@ -4,6 +4,10 @@ #include "JSystem/J3DGraphBase/J3DMaterial.h" #include "JSystem/JKernel/JKRHeap.h" +#if TARGET_PC +#include "JSystem/J3DGraphAnimator/J3DMaterialAnm.h" +#endif + J3DColorBlock* J3DMaterial::createColorBlock(u32 flags) { J3DColorBlock* rv = NULL; switch (flags) { @@ -373,6 +377,9 @@ s32 J3DMaterial::newSingleSharedDisplayList(u32 dlSize) { #if TARGET_PC bool J3DMaterial::needsInterpCallBack() const { + if (mMaterialAnm != nullptr && mMaterialAnm->hasMaterialAnimation()) { + return true; + } for (int i = 0, n = getTexGenNum(); i < n; i++) { J3DTexMtx* pTexMtx = mTexGenBlock->getTexMtx(i); if (pTexMtx != NULL) { diff --git a/libs/JSystem/src/J3DGraphBase/J3DPacket.cpp b/libs/JSystem/src/J3DGraphBase/J3DPacket.cpp index f87283df47..fb82b8244d 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DPacket.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DPacket.cpp @@ -1,15 +1,18 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep -#include -#include +#include "JSystem/J3DGraphBase/J3DPacket.h" #include "JSystem/J3DGraphAnimator/J3DModel.h" #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" #include "JSystem/J3DGraphBase/J3DMaterial.h" -#include "JSystem/J3DGraphBase/J3DPacket.h" #include "JSystem/J3DGraphBase/J3DShapeMtx.h" #include "JSystem/JKernel/JKRHeap.h" +#include +#include #include "global.h" -#include "tracy/Tracy.hpp" + +#if TARGET_PC +#include +#endif J3DError J3DDisplayListObj::newDisplayList(u32 maxSize) { mMaxSize = ALIGN_NEXT(maxSize, 0x20); @@ -339,6 +342,11 @@ int J3DShapePacket::newDifferedDisplayList(u32 diffFlags) { void J3DShapePacket::prepareDraw() const { mpModel->getVertexBuffer()->setArray(); j3dSys.setModel(mpModel); +#if TARGET_PC + if (mpModel->getMtxCalcMode() == 2) { + mpModel->prepare_presentation_view(); + } +#endif j3dSys.setShapePacket((J3DShapePacket*)this); J3DShapeMtx::setLODFlag(mpModel->checkFlag(J3DMdlFlag_EnableLOD) != 0); diff --git a/libs/JSystem/src/J3DGraphBase/J3DShape.cpp b/libs/JSystem/src/J3DGraphBase/J3DShape.cpp index 22eb76a533..07ed23ed4a 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DShape.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DShape.cpp @@ -1,15 +1,17 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/J3DGraphBase/J3DShape.h" - -#include -#include -#include -#include "JSystem/J3DGraphBase/J3DFifo.h" #include "JSystem/J3DGraphBase/J3DPacket.h" #include "JSystem/J3DGraphBase/J3DVertex.h" +#include "JSystem/J3DGraphBase/J3DFifo.h" +#include + +#if TARGET_PC #include "JSystem/JKernel/JKRHeap.h" -#include "tracy/Tracy.hpp" + +#include +#include +#endif void J3DGDSetVtxAttrFmtv(GXVtxFmt, GXVtxAttrFmtList const*, bool); void J3DFifoLoadPosMtxImm(Mtx, u32); diff --git a/libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp b/libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp index 41d2e96ef0..ae9021f76c 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp @@ -6,13 +6,16 @@ #include "JSystem/J3DGraphBase/J3DMatBlock.h" #include "JSystem/J3DGraphBase/J3DSys.h" #include "JSystem/J3DGraphBase/J3DTexture.h" -#include "dusk/frame_interpolation.h" + +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#endif DUSK_GAME_DATA u16 J3DShapeMtx::sMtxLoadCache[10]; -#ifdef TARGET_PC +#if TARGET_PC static void J3DFrameInterpConcat(MtxP lhs, MtxP rhs, Mtx out) { - if (!dusk::frame_interp::lookup_concat_replacement(lhs, rhs, out)) { + if (!dusk::interp::lookup_concat_replacement(lhs, rhs, out)) { MTXConcat(lhs, rhs, out); } } diff --git a/libs/JSystem/src/J3DGraphBase/J3DSys.cpp b/libs/JSystem/src/J3DGraphBase/J3DSys.cpp index bdacff4276..0f5beb9db5 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DSys.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DSys.cpp @@ -1,15 +1,16 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep -#include "JSystem/J3DGraphBase/J3DFifo.h" #include "JSystem/J3DGraphBase/J3DSys.h" #include "JSystem/J3DGraphBase/J3DTevs.h" #include "JSystem/J3DGraphBase/J3DTexture.h" -#include "helpers/gx_helper.h" +#include "JSystem/J3DGraphBase/J3DFifo.h" #include "global.h" -#include "tracy/Tracy.hpp" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/interp/frame_interpolation.h" +#include "helpers/gx_helper.h" + +#include #endif DUSK_GAME_DATA J3DSys j3dSys; @@ -378,7 +379,7 @@ void J3DSys::reinitPixelProc() { #if TARGET_PC void J3DSys::setViewMtx(const Mtx m) { Mtx patched; - if (dusk::frame_interp::lookup_replacement(m, patched)) { + if (dusk::interp::lookup_replacement(m, patched)) { m = patched; } MTXCopy(m, mViewMtx); diff --git a/libs/JSystem/src/J3DGraphBase/J3DVertex.cpp b/libs/JSystem/src/J3DGraphBase/J3DVertex.cpp index 4c1944478a..6d605e64c6 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DVertex.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DVertex.cpp @@ -6,6 +6,10 @@ #include "JSystem/JKernel/JKRHeap.h" #include +#if TARGET_PC +#include "dusk/interp/vertex.h" +#endif + J3DVertexData::J3DVertexData() { mVtxNum = 0; mNrmNum = 0; @@ -31,6 +35,7 @@ J3DVertexData::J3DVertexData() { } void J3DVertexBuffer::setVertexData(J3DVertexData* pVtxData) { + IF_DUSK(dusk::interp::vertex::reset(this)); J3D_ASSERT_NULLPTR(175, pVtxData != NULL); mVtxData = pVtxData; @@ -50,6 +55,7 @@ void J3DVertexBuffer::setVertexData(J3DVertexData* pVtxData) { } void J3DVertexBuffer::init() { + IF_DUSK(dusk::interp::vertex::reset(this)); mVtxData = NULL; mVtxPosArray[0] = mVtxPosArray[1] = NULL; @@ -65,11 +71,15 @@ void J3DVertexBuffer::init() { frameInit(); } -J3DVertexBuffer::~J3DVertexBuffer() {} +J3DVertexBuffer::~J3DVertexBuffer() { + IF_DUSK(dusk::interp::vertex::reset(this)); +} void J3DVertexBuffer::setArray() const { - j3dSys.setVtxPos(mCurrentVtxPos, mVtxData->getVtxNum()); - j3dSys.setVtxNrm(mCurrentVtxNrm, mVtxData->getNrmNum()); + j3dSys.setVtxPos(DUSK_IF_ELSE(dusk::interp::vertex::positions(this, mCurrentVtxPos), mCurrentVtxPos), + mVtxData->getVtxNum()); + j3dSys.setVtxNrm(DUSK_IF_ELSE(dusk::interp::vertex::normals(this, mCurrentVtxNrm), mCurrentVtxNrm), + mVtxData->getNrmNum()); j3dSys.setVtxCol(mCurrentVtxCol, mVtxData->getColNum()); } diff --git a/libs/JSystem/src/JAudio2/JAISe.cpp b/libs/JSystem/src/JAudio2/JAISe.cpp index 504c875bad..16f3c284da 100644 --- a/libs/JSystem/src/JAudio2/JAISe.cpp +++ b/libs/JSystem/src/JAudio2/JAISe.cpp @@ -3,6 +3,7 @@ #include "JSystem/JAudio2/JAISe.h" #include "JSystem/JAudio2/JAIAudience.h" #include "JSystem/JAudio2/JAISeMgr.h" +#include "dusk/mods/svc/audio_res/bst.hpp" JAISe::JAISe(JAISeMgr* seMgr, JAISoundStrategyMgr* soundStrategyMgr, u32 priority) : JSULink(this) { inner_.mSoundStrategyMgr = soundStrategyMgr; @@ -70,7 +71,7 @@ void JAISe::JAISeCategoryMgr_mixOut_(bool param_0, const JASSoundParams& params, if (inner_.field_0x26c) { switch (inner_.track.getStatus()) { case JASTrack::STATUS_STOPPED: - if (status_.field_0x1.flags.flag1) { + if (status_.field_0x1.flags.mComesBack) { startTrack_(params); } else { stop_JAISound_(); @@ -84,8 +85,8 @@ void JAISe::JAISeCategoryMgr_mixOut_(bool param_0, const JASSoundParams& params, startTrack_(params); } } - } else if (status_.field_0x1.flags.flag1) { - if (status_.field_0x1.flags.flag3) { + } else if (status_.field_0x1.flags.mComesBack) { + if (status_.field_0x1.flags.mPauseWhenOut) { inner_.track.pause(true); } else { stopTrack_(); @@ -129,10 +130,10 @@ bool JAISe::JAISound_tryDie_() { return false; } -void JAISe::JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience) { +void JAISe::JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)) { JUT_ASSERT(221, inner_.track.getStatus() == JASTrack::STATUS_FREE); - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(std::static_pointer_cast(replacement))); inner_.field_0x26c = 0; if (inner_.mSoundStrategyMgr) { inner_.field_0x278 = inner_.mSoundStrategyMgr->calc(id); diff --git a/libs/JSystem/src/JAudio2/JAISeMgr.cpp b/libs/JSystem/src/JAudio2/JAISeMgr.cpp index 0809ede5c6..825ac8d78e 100644 --- a/libs/JSystem/src/JAudio2/JAISeMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAISeMgr.cpp @@ -4,6 +4,7 @@ #include "JSystem/JAudio2/JAISoundHandles.h" #include "JSystem/JAudio2/JAISoundInfo.h" #include "JSystem/JAudio2/JASReport.h" +#include "dusk/mods/svc/audio_res/bst.hpp" bool JAISeCategoryMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { { @@ -167,7 +168,7 @@ JAISeMgr::JAISeMgr(bool setInstance) : JASGlobalInstance(setInstance) } bool JAISeMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { if (mCategoryMgrs[i].JAISeCategoryMgr::isUsingSeqData(seqDataRegion)) { return true; } @@ -177,7 +178,7 @@ bool JAISeMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { int JAISeMgr::releaseSeqData(const JAISeqDataRegion& seqDataRegion) { bool r30 = 0; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { switch (mCategoryMgrs[i].JAISeCategoryMgr::releaseSeqData(seqDataRegion)) { case 0: return 0; @@ -191,14 +192,14 @@ int JAISeMgr::releaseSeqData(const JAISeqDataRegion& seqDataRegion) { } void JAISeMgr::setCategoryArrangement(const JAISeCategoryArrangement& arrangement) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].setMaxActiveSe(arrangement.mItems[i].mMaxActiveSe); mCategoryMgrs[i].setMaxInactiveSe(arrangement.mItems[i].mMaxInactiveSe); } } void JAISeMgr::getCategoryArrangement(JAISeCategoryArrangement* arrangement) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { int active = mCategoryMgrs[i].getMaxActiveSe(); JUT_ASSERT(299, active <= 255); arrangement->mItems[i].mMaxActiveSe = active; @@ -209,19 +210,19 @@ void JAISeMgr::getCategoryArrangement(JAISeCategoryArrangement* arrangement) { } void JAISeMgr::stop() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].stop(); } } void JAISeMgr::stopSoundID(JAISoundID id) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].stopSoundID(id); } } void JAISeMgr::initParams() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].getParams()->init(); } } @@ -270,21 +271,21 @@ JAISe* JAISeMgr::newSe_(int category, u32 priority) { void JAISeMgr::calc() { mParams.calc(); - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_calc_(); } - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_freeDeadSe_(); } } void JAISeMgr::mixOut() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_mixOut_(mParams, mSoundActivity); } } -bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { +bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)) { if (handle && *handle) { (*handle)->stop(); } @@ -294,7 +295,7 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry u32 category; if (soundInfoPtr != NULL) { category = soundInfoPtr->getCategory(id); - priority = soundInfoPtr->getPriority(id); + priority = soundInfoPtr->getPriority(id IF_DUSK_ARG(replacement.get())); } else { category = id.id_.info.type.parts.groupID; priority = 0; @@ -309,10 +310,10 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry } JAIAudience* audiencePtr = getAudience(category); - se->JAISeMgr_startID_(id, posPtr, audiencePtr); + se->JAISeMgr_startID_(id, posPtr, audiencePtr IF_DUSK_ARG(replacement)); if (soundInfoPtr != NULL) { - soundInfoPtr->getSeInfo(id, se); + soundInfoPtr->getSeInfo(id, se IF_DUSK_ARG(replacement.get())); } if (handle != NULL) { @@ -324,7 +325,7 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry int JAISeMgr::getNumActiveSe() const { int num = 0; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { num += mCategoryMgrs[i].getNumSe(); } return num; diff --git a/libs/JSystem/src/JAudio2/JAISeq.cpp b/libs/JSystem/src/JAudio2/JAISeq.cpp index c05494b82e..36895d2d63 100644 --- a/libs/JSystem/src/JAudio2/JAISeq.cpp +++ b/libs/JSystem/src/JAudio2/JAISeq.cpp @@ -28,7 +28,7 @@ void JAISeq::JAISeqMgr_startID_(JAISoundID id, const JGeometry::TVec3* posP JAIAudience* audience, int category, int param_4) { inner_.field_0x39c = category; reserveChildTracks_(param_4); - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(nullptr)); if (inner_.strategyMgr) { field_0x3a8 = inner_.strategyMgr->calc(id); diff --git a/libs/JSystem/src/JAudio2/JAISound.cpp b/libs/JSystem/src/JAudio2/JAISound.cpp index fe6b45bb8b..78141503ed 100644 --- a/libs/JSystem/src/JAudio2/JAISound.cpp +++ b/libs/JSystem/src/JAudio2/JAISound.cpp @@ -54,16 +54,16 @@ s32 JAISoundStatus_::unlockIfLocked() { } void JAISoundParams::mixOutAll(const JASSoundParams& inParams, JASSoundParams* outParams, f32 param_2) { - outParams->mVolume = move_.params_.mVolume * (inParams.mVolume * property_.field_0x0) * param_2; - outParams->mFxMix = move_.params_.mFxMix + (inParams.mFxMix + property_.field_0x4); - outParams->mPitch = move_.params_.mPitch * (inParams.mPitch * property_.field_0x8); + outParams->mVolume = move_.params_.mVolume * (inParams.mVolume * property_.mVolume) * param_2; + outParams->mFxMix = move_.params_.mFxMix + (inParams.mFxMix + property_.mFxMix); + outParams->mPitch = move_.params_.mPitch * (inParams.mPitch * property_.mPitch); outParams->mPan = (inParams.mPan + move_.params_.mPan) - 0.5f; outParams->mDolby = inParams.mDolby + move_.params_.mDolby; } JAISound::JAISound() : params_() {} -void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience) { +void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)) { handle_ = NULL; soundID_ = id; status_.init(); @@ -72,9 +72,10 @@ void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPt audience_ = audience; prepareCount_ = 0; count_ = 0; + IF_DUSK(this->replacement = std::move(replacement)); if (posPtr != NULL && audience_ != NULL) { - audible_ = audience_->newAudible(*posPtr, soundID_, NULL, 0); + audible_ = audience_->newAudible(*posPtr, soundID_, NULL, 0 IF_DUSK_ARG(getReplacement())); } else { audible_ = NULL; } @@ -98,7 +99,7 @@ void JAISound::newAudible(const JGeometry::TVec3& pos, } JUT_ASSERT(157, audience_); - audible_ = audience_->newAudible(pos, soundID_, param_1, param_2); + audible_ = audience_->newAudible(pos, soundID_, param_1, param_2 IF_DUSK_ARG(getReplacement())); } void JAISound::stop(u32 fadeTime) { @@ -181,7 +182,7 @@ bool JAISound::calc_JAISound_() { } if (audience_ != NULL && audible_ != NULL) { - if ((priority_ = audience_->calcPriority(audible_)) == 0xFFFFFFFF && status_.field_0x1.flags.flag1 == 0) { + if ((priority_ = audience_->calcPriority(audible_)) == 0xFFFFFFFF && status_.field_0x1.flags.mComesBack == 0) { stop_JAISound_(); } } else { diff --git a/libs/JSystem/src/JAudio2/JAIStream.cpp b/libs/JSystem/src/JAudio2/JAIStream.cpp index 5799080b12..e75292a34b 100644 --- a/libs/JSystem/src/JAudio2/JAIStream.cpp +++ b/libs/JSystem/src/JAudio2/JAIStream.cpp @@ -5,6 +5,7 @@ #include "JSystem/JAudio2/JAISoundChild.h" #include "JSystem/JAudio2/JAIStreamDataMgr.h" #include "JSystem/JAudio2/JAIAudience.h" +#include "dusk/mods/svc/audio_res/bst.hpp" static void JAIStream_JASAramStreamCallback_(u32 type, JASAramStream* aramStream, void* userData) { JAIStream* stream = (JAIStream*)userData; @@ -34,10 +35,10 @@ JAIStream::JAIStream(JAIStreamMgr* streamMgr, JAISoundStrategyMgr* so void JAIStream::JAIStreamMgr_startID_(JAISoundID id, s32 streamFileEntry, const JGeometry::TVec3* posPtr, JAIAudience* audience, - int category) { + int category IF_DUSK_ARG(std::shared_ptr replacement)) { field_0x298 = category; field_0x294 = streamFileEntry; - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(std::static_pointer_cast(replacement))); field_0x290 = 0; if (field_0x2c0 != NULL) { diff --git a/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp b/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp index 2baafdf24a..ca982bafc7 100644 --- a/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp @@ -14,13 +14,13 @@ JAIStreamMgr::JAIStreamMgr(bool setInstance) : JASGlobalInstance(s mActivity.init(); } -bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { +bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)) { JUT_ASSERT(37, streamDataMgr_); if (handle != NULL && *handle) { (*handle)->stop(); } - s32 streamFileEntry = streamDataMgr_->getStreamFileEntry(id); + s32 streamFileEntry = streamDataMgr_->getStreamFileEntry(id IF_DUSK_ARG(replacement.get())); if (streamFileEntry < 0) { JUT_WARN(46, "Cannot find the stream file entry for ID:%08x\n", id.id_.composite_) return false; @@ -38,9 +38,9 @@ bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeom return false; } - stream->JAIStreamMgr_startID_(id, streamFileEntry, posPtr, mAudience, category); + stream->JAIStreamMgr_startID_(id, streamFileEntry, posPtr, mAudience, category IF_DUSK_ARG(replacement)); if (soundInfo != NULL) { - soundInfo->getStreamInfo(id, stream); + soundInfo->getStreamInfo(id, stream IF_DUSK_ARG(replacement.get())); } if (handle != NULL) { diff --git a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp index 545b0d41dc..dc0289ce0f 100644 --- a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp @@ -18,7 +18,9 @@ #include #include -#include "tracy/Tracy.hpp" +#if TARGET_PC +#include +#endif DUSK_GAME_DATA s16* JASDriver::sDmaDacBuffer[3]; @@ -44,7 +46,7 @@ DUSK_GAME_DATA u32 JASDriver::sOutputRate; DUSK_GAME_DATA JASMixMode JASDriver::sMixMode = MIX_MODE_EXTRA; -DUSK_GAME_DATA f32 JASDriver::sDacRate = 32028.5f; +DUSK_GAME_DATA f32 JASDriver::sDacRate = DUSK_IF_ELSE(32000.0f, 32028.5f); DUSK_GAME_DATA u32 JASDriver::sSubFrames = 0x00000007; @@ -109,9 +111,7 @@ void JASDriver::setOutputRate(JASOutputRate param_0) { sDacRate = 48000.0f; } -#if !TARGET_PC - sDacRate *= 1.0008897f; -#endif + IF_NOT_DUSK(sDacRate *= 1.0008897f); } DUSK_GAME_DATA const JASDriver::MixFunc JASDriver::sMixFuncs[4] = { diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 6a15869a7c..74026d94ff 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -713,7 +713,7 @@ void JASAramStream::channelStart() { wave_info.mpPenult = 0; // probably a fake match, this should be set in the JASWaveInfo constructor static u32 const one = 1; - wave_info.field_0x20 = &one; + wave_info.mpLoaded = &one; JASChannel* jc = JKR_NEW JASChannel(channelCallback, this); JUT_ASSERT(963, jc); jc->setPriority(0x7f7f); @@ -722,9 +722,9 @@ void JASAramStream::channelStart() { } jc->setInitPitch(mSampleRate / JASDriver::getDacRate()); jc->setOscInit(0, &OSC_ENV); - jc->field_0xdc.mWaveInfo = wave_info; + jc->mAnon.mWaveInfo = wave_info; jc->mWaveAramAddress = mAramAddress + sBlockSize * mAramBlocksPerChannel * i; - jc->field_0xdc.mChannelType = 0; + jc->mAnon.mChannelType = 0; int ret = jc->playForce(); JUT_ASSERT(977, ret); JUT_ASSERT_MSG(979, mChannels[i] == NULL, "channelStart for already playing channel"); diff --git a/libs/JSystem/src/JAudio2/JASBNKParser.cpp b/libs/JSystem/src/JAudio2/JASBNKParser.cpp index ad8aa1ee46..6fbd3ad5cf 100644 --- a/libs/JSystem/src/JAudio2/JASBNKParser.cpp +++ b/libs/JSystem/src/JAudio2/JASBNKParser.cpp @@ -115,9 +115,9 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h JASBasicInst::TKeymap* keymap = instp->getKeyRegion(j); keymap->setHighKey(*data >> 0x18); u32 fVar4 = data[1]; - keymap->field_0x4 = JSULoHalf(data[3]); - keymap->field_0x8 = *(BE(f32)*)&data[4]; - keymap->field_0xc = *(BE(f32)*)&data[5]; + keymap->mWaveId = JSULoHalf(data[3]); + keymap->mVolumeMult = *(BE(f32)*)&data[4]; + keymap->mPitchMult = *(BE(f32)*)&data[5]; data += 2; for (int k = 0; k < fVar4; k++) { data += 4; @@ -159,9 +159,9 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h ptr++; } u32 pVar6 = ptr[0]; - percp->field_0xe = JSULoHalf(ptr[2]); - percp->field_0x10 = *(BE(f32)*)&ptr[3]; - percp->field_0x14 = *(BE(f32)*)&ptr[4]; + percp->mWaveId = JSULoHalf(ptr[2]); + percp->mVolumeMult = *(BE(f32)*)&ptr[3]; + percp->mPitchMult = *(BE(f32)*)&ptr[4]; for (int k = 0; k < pVar6; k++) {} drump->setPerc(j, percp); } @@ -260,9 +260,9 @@ JASBasicBank* JASBNKParser::Ver0::createBasicBank(void const* stream, JKRHeap* h TKeymap* tkeymap = tinst->mKeymapOffset[j].ptr(header); keymap->setHighKey(tkeymap->mHighKey); TVmap* tvmap = tkeymap->mVmapOffset.ptr(header); - keymap->field_0x4 = JSULoHalf(tvmap->field_0x4); - keymap->field_0x8 = tvmap->field_0x8; - keymap->field_0xc = tvmap->field_0xc; + keymap->mWaveId = JSULoHalf(tvmap->field_0x4); + keymap->mVolumeMult = tvmap->field_0x8; + keymap->mPitchMult = tvmap->field_0xc; } bank->setInst(i, instp); @@ -288,9 +288,9 @@ JASBasicBank* JASBNKParser::Ver0::createBasicBank(void const* stream, JKRHeap* h percp->setRelease(tperc->mRelease[j]); } TVmap* vmap = tpmap->mVmapOffset.ptr(header); - percp->field_0xe = JSULoHalf(vmap->field_0x4); - percp->field_0x10 = vmap->field_0x8; - percp->field_0x14 = vmap->field_0xc; + percp->mWaveId = JSULoHalf(vmap->field_0x4); + percp->mVolumeMult = vmap->field_0x8; + percp->mPitchMult = vmap->field_0xc; setp->setPerc(j, percp); } } @@ -314,8 +314,8 @@ JASOscillator::Data* JASBNKParser::Ver0::findOscPtr(JASBasicBank* bank, THeader if (inst != NULL) { JASInstParam param; inst->getParam(0x3c, 0x7f, ¶m); - if (j < param.field_0x1d) { - return param.field_0x14[j]; + if (j < param.mOscillatorCount) { + return param.mOscillators[j]; } } } diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 29ebe11cb5..0acc5b9ed4 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -1,64 +1,83 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JAudio2/JASBank.h" + +#include "dusk/mods/svc/audio_res/wsys.hpp" #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASBasicInst.h" #include "JSystem/JAudio2/JASBasicWaveBank.h" #include "JSystem/JAudio2/JASChannel.h" +using namespace dusk::mods::svc::audio_res::wsys; + // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations -JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 param_3, u16 param_4, - void (*param_5)(u32, JASChannel*, JASDsp::TChannel*, void*), - void* param_6) { - if (param_1 >= 0xf0) { - return noteOnOsc(param_1 - 0xf0, param_2, param_3, param_4, param_5, param_6); +JASChannel* JASBank::noteOn(JASBank const* bank, int program, u8 key, u8 velocity, u16 priority, + void (*callback)(u32, JASChannel*, JASDsp::TChannel*, void*), + void* callback_data) { + if (program >= 0xf0) { + return noteOnOsc(program - 0xf0, key, velocity, priority, callback, callback_data); } - if (!param_0) { + if (!bank) { return NULL; } - JASInstParam stack_60; - if (!param_0->getInstParam(param_1, param_2, param_3, &stack_60)) { + JASInstParam instParam; + if (!bank->getInstParam(program, key, velocity, &instParam)) { return NULL; } - JASWaveBank* waveBank = param_0->getWaveBank(); + JASWaveBank* waveBank = bank->getWaveBank(); if (!waveBank) { return NULL; } - JASWaveHandle* waveHandle = waveBank->getWaveHandle(stack_60.field_0x1a); + JASWaveHandle* waveHandle = waveBank->getWaveHandle(instParam.mWaveId); if (!waveHandle) { return NULL; } +#if TARGET_PC + auto const wave_key = AudioWaveKey(static_cast(waveBank->bankId), instParam.mWaveId); + std::lock_guard lock(s_replacements_mutex); + auto const found_replacement = s_replacements.find(wave_key); + if (found_replacement != s_replacements.end()) { + waveHandle = &found_replacement->second; + } + + auto const aramBase = waveHandle->getAramBaseAddress(); +#endif + const JASWaveInfo* waveInfo = waveHandle->getWaveInfo(); if (!waveInfo) { return NULL; } intptr_t wavePtr = waveHandle->getWavePtr(); - if (!wavePtr) { + if (!wavePtr IF_DUSK(&& !aramBase)) { return NULL; } - JASChannel* channel = JKR_NEW JASChannel(param_5, param_6); + JASChannel* channel = JKR_NEW JASChannel(callback, callback_data); if (!channel) { return NULL; } - channel->setPriority(param_4); - channel->field_0xdc.mWaveInfo = *waveInfo; + channel->setPriority(priority); + channel->mAnon.mWaveInfo = *waveInfo; channel->mWaveAramAddress = wavePtr; - channel->field_0xdc.mChannelType = stack_60.field_0x1c; - channel->setBankDisposeID(param_0); - channel->setInitPitch(stack_60.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); - if (stack_60.field_0x1e == 0) { - channel->setKey(param_2 - waveInfo->mBaseKey); +#if TARGET_PC + channel->mAramBaseAddress = aramBase; + channel->mSampleReference = waveHandle->getSampleReference(); +#endif + channel->mAnon.mChannelType = instParam.mChannelType; + channel->setBankDisposeID(bank); + channel->setInitPitch(instParam.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); + if (instParam.mDontSetKey == 0) { + channel->setKey(key - waveInfo->mBaseKey); } - channel->setInitVolume(stack_60.mVolume); - channel->setVelocity(param_3); - channel->setInitPan(stack_60.mPan); - channel->setInitFxmix(stack_60.mFxMix); - channel->setInitDolby(stack_60.mDolby); - for (u32 i = 0; i < stack_60.field_0x1d; i++) { - channel->setOscInit(i, stack_60.field_0x14[i]); + channel->setInitVolume(instParam.mVolume); + channel->setVelocity(velocity); + channel->setInitPan(instParam.mPan); + channel->setInitFxmix(instParam.mFxMix); + channel->setInitDolby(instParam.mDolby); + for (u32 i = 0; i < instParam.mOscillatorCount; i++) { + channel->setOscInit(i, instParam.mOscillators[i]); } - channel->setDirectRelease(stack_60.field_0x18); + channel->setDirectRelease(instParam.mDirectRelease); if (!channel->play()) { return NULL; } @@ -66,24 +85,24 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 } // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations -JASChannel* JASBank::noteOnOsc(int param_0, u8 param_1, u8 param_2, u16 param_3, - void (*param_4)(u32, JASChannel*, JASDsp::TChannel*, void*), - void* param_5) { +JASChannel* JASBank::noteOnOsc(int param_0, u8 key, u8 velocity, u16 priority, + void (*callback)(u32, JASChannel*, JASDsp::TChannel*, void*), + void* callback_data) { static JASOscillator::Point const OSC_RELEASE_TABLE[2] = { {0x0001, 0x000A, 0x0000}, {0x000F, 0x0000, 0x0000}, }; static const JASOscillator::Data OSC_ENV = {0, 1.0f, NULL, OSC_RELEASE_TABLE, 1.0f, 0.0f}; - JASChannel* channel = JKR_NEW JASChannel(param_4, param_5); + JASChannel* channel = JKR_NEW JASChannel(callback, callback_data); if (!channel) { return NULL; } - channel->setPriority(param_3); + channel->setPriority(priority); channel->mOscillatorSomething = param_0; - channel->field_0xdc.mChannelType = 2; + channel->mAnon.mChannelType = CHANNEL_OSCILLATOR; channel->setInitPitch(16736.016f / JASDriver::getDacRate()); - channel->setKey(param_1 - channel->field_0xdc.mWaveInfo.mBaseKey); - channel->setVelocity(param_2); + channel->setKey(key - channel->mAnon.mWaveInfo.mBaseKey); + channel->setVelocity(velocity); channel->setOscInit(0, &OSC_ENV); if (!channel->play()) { return NULL; diff --git a/libs/JSystem/src/JAudio2/JASBasicBank.cpp b/libs/JSystem/src/JAudio2/JASBasicBank.cpp index 9b3c032bbb..16fef1d746 100644 --- a/libs/JSystem/src/JAudio2/JASBasicBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicBank.cpp @@ -14,20 +14,20 @@ void JASBasicBank::newInstTable(u8 num, JKRHeap* heap) { mInstNumMax = num; mInstTable = JKR_NEW_ARRAY_ARGS(JASInst*, mInstNumMax, heap, 0); #if TARGET_ANDROID - JASCalc::_bzero(mInstTable, mInstNumMax * 4); + JASCalc::_bzero(mInstTable, mInstNumMax * sizeof(mInstTable[0])); #else - JASCalc::bzero(mInstTable, mInstNumMax * 4); + JASCalc::bzero(mInstTable, mInstNumMax * sizeof(mInstTable[0])); #endif } } -bool JASBasicBank::getInstParam(int prg_no, int param_1, int param_2, +bool JASBasicBank::getInstParam(int prg_no, int key, int velocity, JASInstParam* o_param) const { JASInst* inst = getInst(prg_no); if (inst == NULL) { return false; } - return inst->getParam(param_1, param_2, o_param); + return inst->getParam(key, velocity, o_param); } void JASBasicBank::setInst(int prg_no, JASInst* inst) { diff --git a/libs/JSystem/src/JAudio2/JASBasicInst.cpp b/libs/JSystem/src/JAudio2/JASBasicInst.cpp index fba53d010f..50e764eeac 100644 --- a/libs/JSystem/src/JAudio2/JASBasicInst.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicInst.cpp @@ -14,9 +14,9 @@ JASBasicInst::JASBasicInst() { mKeymapCount = 0; mKeymap = NULL; #if TARGET_ANDROID - JASCalc::_bzero(field_0xc, sizeof(field_0xc)); + JASCalc::_bzero(mOscillators, sizeof(mOscillators)); #else - JASCalc::bzero(field_0xc, sizeof(field_0xc)); + JASCalc::bzero(mOscillators, sizeof(mOscillators)); #endif } @@ -24,18 +24,18 @@ JASBasicInst::~JASBasicInst() { JKR_DELETE_ARRAY(mKeymap); } -bool JASBasicInst::getParam(int param_0, int param_1, JASInstParam* param_2) const { - UNUSED(param_1); - param_2->field_0x1c = 0; - param_2->field_0x1e = 0; - param_2->field_0x14 = (JASOscillator::Data**)&field_0xc; - param_2->field_0x1d = 2; - param_2->mVolume = mVolume; - param_2->mPitch = mPitch; +bool JASBasicInst::getParam(int key, int velocity, JASInstParam* o_param) const { + UNUSED(velocity); + o_param->mChannelType = 0; + o_param->mDontSetKey = 0; + o_param->mOscillators = (JASOscillator::Data**)&mOscillators; + o_param->mOscillatorCount = 2; + o_param->mVolume = mVolume; + o_param->mPitch = mPitch; TKeymap* keyMap = NULL; for (int i = 0; i < mKeymapCount; i++) { - if (param_0 <= mKeymap[i].mHighKey) { + if (key <= mKeymap[i].mHighKey) { keyMap = &mKeymap[i]; break; } @@ -45,9 +45,9 @@ bool JASBasicInst::getParam(int param_0, int param_1, JASInstParam* param_2) con return false; } - param_2->mVolume *= keyMap->field_0x8; - param_2->mPitch *= keyMap->field_0xc; - param_2->field_0x1a = u16(keyMap->field_0x4); + o_param->mVolume *= keyMap->mVolumeMult; + o_param->mPitch *= keyMap->mPitchMult; + o_param->mWaveId = u16(keyMap->mWaveId); return true; } @@ -61,7 +61,7 @@ void JASBasicInst::setKeyRegionCount(u32 count, JKRHeap* param_1) { void JASBasicInst::setOsc(int index, JASOscillator::Data const* param_1) { JUT_ASSERT(128, index < OSC_MAX); JUT_ASSERT(129, index >= 0); - field_0xc[index] = param_1; + mOscillators[index] = param_1; } JASBasicInst::TKeymap* JASBasicInst::getKeyRegion(int index) { diff --git a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp index ac11742e6b..9f9e16325c 100644 --- a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp @@ -6,12 +6,12 @@ #include "JSystem/JUtility/JUTAssert.h" #include -JASBasicWaveBank::JASBasicWaveBank() { +JASBasicWaveBank::JASBasicWaveBank(IF_DUSK(u32 bankId)) IF_DUSK(: JASWaveBank(bankId)) { mWaveTable = NULL; mWaveGroupArray = NULL; mHandleCount = 0; mGroupCount = 0; - OSInitMutex(&field_0x4); + OSInitMutex(&mWaveTableMutex); } JASBasicWaveBank::~JASBasicWaveBank() { @@ -44,13 +44,13 @@ void JASBasicWaveBank::setWaveTableSize(u32 param_0, JKRHeap* param_1) { } void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) { - JASMutexLock lock(&field_0x4); + JASMutexLock lock(&mWaveTableMutex); for (u32 i = 0; i < param_0->getWaveCount(); i++) { TWaveHandle* handle = mWaveTable + param_0->getWaveID(i); if (!handle->mHeap) { handle->mHeap = ¶m_0->mHeap; - handle->field_0x4.field_0x20 = ¶m_0->_48; - handle->field_0x4.mOffsetStart = param_0->mCtrlWaveArray[i].field_0x4; + handle->mWaveInfo.mpLoaded = ¶m_0->mCurrentlyLoaded; + handle->mWaveInfo.mOffsetStart = param_0->mCtrlWaveArray[i].mOffsetStart; } } } @@ -58,13 +58,13 @@ void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) DUSK_GAME_DATA u32 JASBasicWaveBank::mNoLoad; void JASBasicWaveBank::decWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) { - JASMutexLock lock(&field_0x4); + JASMutexLock lock(&mWaveTableMutex); for (u32 i = 0; i < param_0->getWaveCount(); i++) { TWaveHandle* handle = mWaveTable + param_0->getWaveID(i); if (handle->mHeap == ¶m_0->mHeap) { handle->mHeap = NULL; - handle->field_0x4.field_0x20 = &mNoLoad; - handle->field_0x4.mOffsetStart = -1; + handle->mWaveInfo.mpLoaded = &mNoLoad; + handle->mWaveInfo.mOffsetStart = -1; } } } @@ -80,15 +80,15 @@ JASWaveHandle* JASBasicWaveBank::getWaveHandle(u32 param_0) const { } void JASBasicWaveBank::setWaveInfo(JASBasicWaveBank::TWaveGroup* wgrp, int index, - u16 param_2, JASWaveInfo const& param_3) { + u16 waveId, JASWaveInfo const& param_3) { JUT_ASSERT(204, wgrp); JUT_ASSERT(205, index < wgrp->mWaveCount); JUT_ASSERT(206, index >= 0); - mWaveTable[param_2].field_0x4 = param_3; - mWaveTable[param_2].field_0x4.field_0x20 = &mNoLoad; - mWaveTable[param_2].field_0x4.mOffsetStart = -1; - wgrp->mCtrlWaveArray[index].field_0x0 = param_2; - wgrp->mCtrlWaveArray[index].field_0x4 = param_3.mOffsetStart; + mWaveTable[waveId].mWaveInfo = param_3; + mWaveTable[waveId].mWaveInfo.mpLoaded = &mNoLoad; + mWaveTable[waveId].mWaveInfo.mOffsetStart = -1; + wgrp->mCtrlWaveArray[index].waveId = waveId; + wgrp->mCtrlWaveArray[index].mOffsetStart = param_3.mOffsetStart; } JASBasicWaveBank::TWaveGroup::TWaveGroup() { @@ -122,7 +122,7 @@ void JASBasicWaveBank::TWaveGroup::onEraseDone() { u32 JASBasicWaveBank::TWaveGroup::getWaveID(int index) const { JUT_ASSERT(298, index < mWaveCount); JUT_ASSERT(299, index >= 0); - return mCtrlWaveArray[index].field_0x0; + return mCtrlWaveArray[index].waveId; } intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const { @@ -131,5 +131,5 @@ intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const { if (base == 0) { return 0; } - return (intptr_t)base + field_0x4.mOffsetStart; + return (intptr_t)base + mWaveInfo.mOffsetStart; } diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index a99c13ce63..5b4a377321 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -26,6 +26,9 @@ JASChannel::JASChannel(Callback i_callback, void* i_callbackData) : mCallback(i_callback), mCallbackData(i_callbackData), mUpdateTimer(0), +#if TARGET_PC + mAramBaseAddress(nullptr), +#endif mBankDisposeID(NULL), mKey(0), mVelocity(0x7f), @@ -34,7 +37,7 @@ JASChannel::JASChannel(Callback i_callback, void* i_callbackData) : mKeySweepCount(0), mSkipSamples(0) { - field_0xdc.mChannelType = 0; + mAnon.mChannelType = 0; field_0x104 = 0; mMixConfig[0].whole = 0x150; mMixConfig[1].whole = 0x210; @@ -173,11 +176,7 @@ void JASChannel::updateEffectorParam(JASDsp::TChannel* i_channel, u16* i_mixerVo f32 pan = 0.5f; f32 dolby = 0.0f; -#if TARGET_PC - u32 effectiveOutputMode = dusk::audio::EnableHrtf ? JAS_OUTPUT_SURROUND : JASDriver::getOutputMode(); -#else u32 effectiveOutputMode = JASDriver::getOutputMode(); -#endif switch (effectiveOutputMode) { case JAS_OUTPUT_MONO: break; @@ -237,7 +236,7 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_START, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { + if (mAnon.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; @@ -253,9 +252,12 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { return -1; } - switch (field_0xdc.mChannelType) { + switch (mAnon.mChannelType) { case 0: - i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples); + i_channel->setWaveInfo(mAnon.mWaveInfo, mWaveAramAddress, mSkipSamples); +#if TARGET_PC + i_channel->mAramBaseAddress = mAramBaseAddress; +#endif break; case 2: i_channel->setOscInfo(mOscillatorSomething); @@ -315,7 +317,7 @@ s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_PLAY, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { + if (mAnon.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; diff --git a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp index 7a9b5cda3e..375447dfbb 100644 --- a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp @@ -523,9 +523,12 @@ void JASDsp::TChannel::init() { void JASDsp::TChannel::playStart() { JUT_ASSERT(508, dspMutex); field_0x10c = 0; +#if !TARGET_PC field_0x060 = 0; +#endif mResetFlag = 1; field_0x066 = 0; +#if !TARGET_PC int i; for (i = 0; i < 4; i++) { field_0x078[i] = 0; @@ -534,6 +537,7 @@ void JASDsp::TChannel::playStart() { for (i = 0; i < 20; i++) { field_0x080[i] = 0; } +#endif mIsActive = 1; } @@ -601,9 +605,11 @@ void JASDsp::TChannel::setWaveInfo(JASWaveInfo const& waveInfo, u32 aramAddress, break; } } +#if !TARGET_PC for (i = 0; i < 16; i++) { field_0x0b0[i] = 0; } +#endif } } diff --git a/libs/JSystem/src/JAudio2/JASDriverIF.cpp b/libs/JSystem/src/JAudio2/JASDriverIF.cpp index 87a88adca0..a08e3339ea 100644 --- a/libs/JSystem/src/JAudio2/JASDriverIF.cpp +++ b/libs/JSystem/src/JAudio2/JASDriverIF.cpp @@ -3,6 +3,7 @@ #include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASDSPInterface.h" +#include "dusk/settings.h" #include void JASDriver::setDSPLevel(f32 param_0) { @@ -30,7 +31,18 @@ void JASDriver::setOutputMode(u32 mode) { } u32 JASDriver::getOutputMode() { +#ifdef TARGET_PC + switch (dusk::getSettings().audio.outputMode) { + case dusk::AudioOutputMode::StereoSpeakers: + return JAS_OUTPUT_STEREO; + case dusk::AudioOutputMode::StereoHeadphones: + case dusk::AudioOutputMode::Surround6ch: + case dusk::AudioOutputMode::Surround8ch: + return JAS_OUTPUT_SURROUND; + } +#else return JASDriver::JAS_SYSTEM_OUTPUT_MODE; +#endif } void JASDriver::waitSubFrame() { diff --git a/libs/JSystem/src/JAudio2/JASDrumSet.cpp b/libs/JSystem/src/JAudio2/JASDrumSet.cpp index 399cc117ec..4b6a95b289 100644 --- a/libs/JSystem/src/JAudio2/JASDrumSet.cpp +++ b/libs/JSystem/src/JAudio2/JASDrumSet.cpp @@ -26,8 +26,8 @@ void JASDrumSet::newPercArray(u8 num, JKRHeap* heap) { } } -bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { - UNUSED(param_1); +bool JASDrumSet::getParam(int key, int velocity, JASInstParam* o_param) const { + UNUSED(velocity); JUT_ASSERT(48, key >= 0); if (mPercArray == NULL) { return false; @@ -43,12 +43,12 @@ bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { return false; } - param_2->field_0x1c = 0; - param_2->field_0x1e = 1; - param_2->mVolume = perc->mVolume; - param_2->mPitch = perc->mPitch; - param_2->mPan = perc->mPan; - param_2->field_0x18 = u16(perc->field_0xc); + o_param->mChannelType = 0; + o_param->mDontSetKey = 1; + o_param->mVolume = perc->mVolume; + o_param->mPitch = perc->mPitch; + o_param->mPan = perc->mPan; + o_param->mDirectRelease = u16(perc->mDirectRelease); static JASOscillator::Data osc; @@ -61,11 +61,11 @@ bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { static JASOscillator::Data* oscp = &osc; - param_2->field_0x14 = &oscp; - param_2->field_0x1d = 1; - param_2->mVolume *= perc->field_0x10; - param_2->mPitch *= perc->field_0x14; - param_2->field_0x1a = u16(perc->field_0xe); + o_param->mOscillators = &oscp; + o_param->mOscillatorCount = 1; + o_param->mVolume *= perc->mVolumeMult; + o_param->mPitch *= perc->mPitchMult; + o_param->mWaveId = u16(perc->mWaveId); return true; } @@ -107,12 +107,12 @@ JASDrumSet::TPerc::TPerc() { mVolume = 1.0f; mPitch = 1.0f; mPan = 0.5f; - field_0xc = 1000; + mDirectRelease = 1000; } void JASDrumSet::TPerc::setRelease(u32 release) { JUT_ASSERT(224, release < 0x10000); - field_0xc = release; + mDirectRelease = release; } u32 JASDrumSet::getType() const { return 'PERC'; } diff --git a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp index 52935324d8..f9e4382512 100644 --- a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp @@ -3,7 +3,7 @@ #include "JSystem/JAudio2/JASSimpleWaveBank.h" #include -JASSimpleWaveBank::JASSimpleWaveBank() { +JASSimpleWaveBank::JASSimpleWaveBank(IF_DUSK(u32 bankId)) IF_DUSK(: JASWaveBank(bankId)) { mWaveTable = NULL; mWaveTableSize = 0; } @@ -28,7 +28,7 @@ JASWaveHandle* JASSimpleWaveBank::getWaveHandle(u32 no) const { void JASSimpleWaveBank::setWaveInfo(u32 no, JASWaveInfo const& waveInfo) { mWaveTable[no].mWaveInfo = waveInfo; - mWaveTable[no].mWaveInfo.field_0x20 = &_48; + mWaveTable[no].mWaveInfo.mpLoaded = &mCurrentlyLoaded; mWaveTable[no].mHeap = &mHeap; } diff --git a/libs/JSystem/src/JAudio2/JASTrack.cpp b/libs/JSystem/src/JAudio2/JASTrack.cpp index 4b1a7ba7aa..32e66e140c 100644 --- a/libs/JSystem/src/JAudio2/JASTrack.cpp +++ b/libs/JSystem/src/JAudio2/JASTrack.cpp @@ -288,13 +288,13 @@ void JASTrack::setLatestKey(u8 param_0) { field_0x22b += getTransposeTotal(); } -JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 param_1, u32 param_2, +JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 key, u32 velocity, u32 i_updateTimer) { JASBank* bank = NULL; if (mBankTable != NULL) { bank = mBankTable->getBank(mBankNumber); } - JASChannel* channel = JASBank::noteOn(bank, mProgNumber, param_1, param_2, + JASChannel* channel = JASBank::noteOn(bank, mProgNumber, key, velocity, mNoteOnPrio | mReleasePrio << 8, channelUpdateCallback, i_channelMgr); if (channel == NULL) { @@ -315,17 +315,17 @@ JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 para return channel; } -int JASTrack::noteOn(u32 noteid, u32 param_1, u32 param_2) { +int JASTrack::noteOn(u32 noteid, u32 key, u32 velocity) { JUT_ASSERT(486, noteid != 0 && noteid < TChannelMgr::CHANNEL_MAX); if (isMute()) { return 0; } int ret = 1; - param_1 += getTransposeTotal(); + key += getTransposeTotal(); for (u32 i = 0; i < mChannelMgrCount; i++) { if (mChannelMgrs[i] != NULL) { mChannelMgrs[i]->noteOff(noteid, 0); - JASChannel* channel = channelStart(mChannelMgrs[i], param_1, param_2, 0); + JASChannel* channel = channelStart(mChannelMgrs[i], key, velocity, 0); if (channel == NULL) { ret = 0; } @@ -356,7 +356,7 @@ int JASTrack::gateOn(u32 param_0, u32 i_velocity, f32 i_time, u32 i_flags) { } else { JASChannel* channel = channel_mgr->mChannels[0]; if (channel != NULL) { - channel->setKey(uvar7 - channel->field_0xdc.mWaveInfo.mBaseKey); + channel->setKey(uvar7 - channel->mAnon.mWaveInfo.mBaseKey); channel->setVelocity(i_velocity); channel->setUpdateTimer(update_timer); } diff --git a/libs/JSystem/src/JAudio2/JASVoiceBank.cpp b/libs/JSystem/src/JAudio2/JASVoiceBank.cpp index 0d3cd3adb2..3ba1dff173 100644 --- a/libs/JSystem/src/JAudio2/JASVoiceBank.cpp +++ b/libs/JSystem/src/JAudio2/JASVoiceBank.cpp @@ -15,9 +15,9 @@ bool JASVoiceBank::getInstParam(int param_0, int param_1, int param_2, return false; } sOscTable = (JASOscillator::Data*)&sOscData; - param_3->field_0x1a = param_0; - param_3->field_0x1d = 1; - param_3->field_0x14 = &sOscTable; + param_3->mWaveId = param_0; + param_3->mOscillatorCount = 1; + param_3->mOscillators = &sOscTable; return true; } diff --git a/libs/JSystem/src/JAudio2/JASWSParser.cpp b/libs/JSystem/src/JAudio2/JASWSParser.cpp index 519eaa3307..91552b24c5 100644 --- a/libs/JSystem/src/JAudio2/JASWSParser.cpp +++ b/libs/JSystem/src/JAudio2/JASWSParser.cpp @@ -30,7 +30,7 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap* u32 free_size = heap->getFreeSize(); THeader* header = (THeader*)stream; - JASBasicWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASBasicWaveBank; + JASBasicWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASBasicWaveBank IF_DUSK((header->mId)); if (wave_bank == NULL) { return NULL; } @@ -60,7 +60,7 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap* wave_info.mpLast = wave->mpLast; wave_info.mpPenult = wave->mpPenult; TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[j].ptr(header); - u16 local_74 = JSULoHalf(ctrl_wave->_00); + u16 local_74 = JSULoHalf(ctrl_wave->mWaveAndGroupId); wave_bank->setWaveInfo(wave_group, j, local_74, wave_info); } wave_group->setFileName(archive->mFileName); @@ -82,7 +82,7 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap return NULL; } - JASSimpleWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASSimpleWaveBank; + JASSimpleWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASSimpleWaveBank IF_DUSK((header->mId)); if (wave_bank == NULL) { return NULL; } @@ -94,7 +94,7 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap TWaveArchive* archive = archiveBank->mArchiveOffsets[0].ptr(header); for (int i = 0; i < ctrl->mWaveCount; i++) { TCtrlWave* ctrlWave = ctrl->mCtrlWaveOffsets[i].ptr(header); - u32 tmp = JSULoHalf(ctrlWave->_00); + u32 tmp = JSULoHalf(ctrlWave->mWaveAndGroupId); if (max < tmp) { max = tmp; } @@ -116,8 +116,8 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap wave_info.mpLast = wave->mpLast; wave_info.mpPenult = wave->mpPenult; TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[i].ptr(header); - u32 tmp = JSULoHalf(ctrl_wave->_00); - wave_bank->setWaveInfo(tmp, wave_info); + u32 waveId = JSULoHalf(ctrl_wave->mWaveAndGroupId); + wave_bank->setWaveInfo(waveId, wave_info); } wave_bank->setFileName(archive->mFileName); diff --git a/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp b/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp index 02730f46b4..5ba257c423 100644 --- a/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp +++ b/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp @@ -38,7 +38,7 @@ char* JASWaveArcLoader::getCurrentDir() { } JASWaveArc::JASWaveArc() : mHeap(this) { - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 0; mEntryNum = -1; mFileLength = 0; @@ -57,7 +57,7 @@ bool JASWaveArc::loadSetup(u32 param_0) { if (mStatus != 1) { return false; } - _48 = 1; + mCurrentlyLoaded = 1; mStatus = 2; return true; } @@ -71,7 +71,7 @@ bool JASWaveArc::eraseSetup() { mStatus = 0; return false; } - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 0; return true; } @@ -91,7 +91,7 @@ void JASWaveArc::loadToAramCallback(void* this_) { bool JASWaveArc::sendLoadCmd() { JASMutexLock mutexLock(&mMutex); - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 1; loadToAramCallbackParams commandInfo; commandInfo.mWavArc = this; diff --git a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp index cc94097712..fe12281a01 100644 --- a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp +++ b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp @@ -47,7 +47,12 @@ namespace { mStreamFileDVDEntryNums[i] = DVDConvertPathToEntrynum(stack_14.getFilePath(i)); } } - virtual s32 getStreamFileEntry(JAISoundID id) { + virtual s32 getStreamFileEntry(JAISoundID id IF_DUSK_ARG(StreamReplacementSlot2 const* replacement)) { +#if TARGET_PC + if (replacement) { + CRASH("TStreamDataMgr::getStreamFileEntry not implemented for replacements"); + } +#endif u32 short_id = id.id_.info.waveID; if (short_id >= mNumStreamFiles) { return -1; diff --git a/libs/JSystem/src/JAudio2/JAUSoundTable.cpp b/libs/JSystem/src/JAudio2/JAUSoundTable.cpp index cab2e45a3c..802b1c3cb9 100644 --- a/libs/JSystem/src/JAudio2/JAUSoundTable.cpp +++ b/libs/JSystem/src/JAudio2/JAUSoundTable.cpp @@ -6,6 +6,8 @@ #include "JSystem/JAudio2/JAUSoundTable.h" +#include "dusk/mods/svc/audio_res/bst.hpp" + void JAUSoundTable::init(void const* param_0) { if (param_0 != NULL) { field_0x0.init(param_0); @@ -14,11 +16,16 @@ void JAUSoundTable::init(void const* param_0) { } } -u8 JAUSoundTable::getTypeID(JAISoundID param_0) const { +u8 JAUSoundTable::getTypeID(JAISoundID param_0 IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { #if DUSK_AUDIO_DISABLED if (this == NULL) { return 0xff; } +#endif +#if TARGET_PC + if (replacement) { + return replacement->get_type_id(); + } #endif if (param_0.isAnonymous()) { return 0xff; @@ -34,11 +41,16 @@ u8 JAUSoundTable::getTypeID(JAISoundID param_0) const { return group->getTypeID(param_0.id_.info.waveID); } -JAUSoundTableItem* JAUSoundTable::getData(JAISoundID param_0) const { +JAUSoundTableItem DUSK_CONST* JAUSoundTable::getData(JAISoundID param_0 IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { #if DUSK_AUDIO_DISABLED if (this == NULL) { return NULL; } +#endif +#if TARGET_PC + if (replacement) { + return &replacement->item; + } #endif if (param_0.isAnonymous()) { return NULL; diff --git a/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp b/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp index f30c5c4a01..7f37f21f3e 100644 --- a/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp +++ b/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp @@ -31,7 +31,12 @@ const char* JAUStreamFileTable::getFilePath(int index) const { return (char*)(data + *(int*)(data + 8 + (index * sizeof(s32)))); } -s32 JAUStreamDataMgr_StreamFileTable::getStreamFileEntry(JAISoundID soundId) { +s32 JAUStreamDataMgr_StreamFileTable::getStreamFileEntry(JAISoundID soundId IF_DUSK_ARG(StreamReplacementSlot2 const* replacement)) { +#if TARGET_PC + if (replacement) { + CRASH("JAUStreamDataMgr_StreamFileTable::getStreamFileEntry not implemented for replacements"); + } +#endif const char* filePath = getFilePath(soundId.id_.info.waveID); if (filePath == NULL) { return -1; diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 466335eae9..939659205e 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -12,17 +12,19 @@ #include "global.h" #include -#ifdef TARGET_PC +#if TARGET_PC #include "dusk/dusk.h" -#include "dusk/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" #include "dusk/logging.h" #include "dusk/settings.h" #include "dusk/time.h" -#include "f_op/f_op_overlap_mng.h" #include "helpers/gx_helper.h" -#include "SDL3/SDL_timer.h" -#include "tracy/Tracy.hpp" +#include "f_op/f_op_overlap_mng.h" + +#include +#include #include #endif @@ -217,12 +219,13 @@ void JFWDisplay::endGX() { if (mFader != NULL) { ortho.setPort(); -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) { - mFader->advance(); - } - if (mFader->getStatus() != JUTFader::Wait) { - mFader->draw(); +#if TARGET_PC + if (dusk::game_clock::g_frameTiming.separatePresentation) { + if (mFader->getStatus() != JUTFader::Wait) { + mFader->draw(); + } + } else { + mFader->control(); } #else mFader->control(); @@ -381,7 +384,7 @@ static void waitForTick(u32 p1, u16 p2) { #if TARGET_PC static Limiter limiter; - if (dusk::frame_interp::is_enabled() || dusk::getTransientSettings().turboMode) { + if (dusk::interp::is_enabled() || dusk::getTransientSettings().turboMode) { limiter.Reset(); dusk::frameUsagePct = 0.f; return; diff --git a/libs/JSystem/src/JParticle/JPABaseShape.cpp b/libs/JSystem/src/JParticle/JPABaseShape.cpp index c47d978b00..fff0a6e5bc 100644 --- a/libs/JSystem/src/JParticle/JPABaseShape.cpp +++ b/libs/JSystem/src/JParticle/JPABaseShape.cpp @@ -10,9 +10,10 @@ #include #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/interp/particle.h" + +#include #endif -#include "tracy/Tracy.hpp" #if TARGET_PC #define JPA_DRAW_CTX_PARAM , ParticleDrawCtx* ctx @@ -301,7 +302,7 @@ void JPAGenTexCrdMtxPrj(JPAEmitterWorkData* param_0) { void JPAGenCalcTexCrdMtxAnm(JPAEmitterWorkData* work) { ZoneScoped; JPABaseShape* shape = work->mpRes->getBsp(); - f32 dVar16 = work->mpEmtr->mTick; + f32 dVar16 = DUSK_IF_ELSE(dusk::interp::particle::emitter_age(work->mpEmtr), work->mpEmtr->mTick); f32 dVar15 = 0.5f * (1.0f + shape->getTilingS()); f32 dVar14 = 0.5f * (1.0f + shape->getTilingT()); f32 dVar11 = (dVar16 * shape->getIncTransX()) + shape->getInitTransX(); @@ -333,7 +334,7 @@ void JPAGenCalcTexCrdMtxAnm(JPAEmitterWorkData* work) { void JPALoadCalcTexCrdMtxAnm(JPAEmitterWorkData* work, JPABaseParticle* param_1 JPA_DRAW_CTX_PARAM) { ZoneScoped; JPABaseShape* shape = work->mpRes->getBsp(); - f32 dVar16 = param_1->mAge; + f32 dVar16 = DUSK_IF_ELSE(ctx->age, param_1->mAge); f32 dVar15 = 0.5f * (1.0f + shape->getTilingS()); f32 dVar14 = 0.5f * (1.0f + shape->getTilingT()); f32 dVar11 = (dVar16 * shape->getIncTransX()) + shape->getInitTransX(); @@ -458,7 +459,7 @@ void loadPrj(JPAEmitterWorkData const* work, const Mtx srt) { static void loadPrjAnm(JPAEmitterWorkData const* work, const Mtx srt) { JPABaseShape* shape = work->mpRes->getBsp(); - f32 dVar16 = work->mpEmtr->getAge(); + f32 dVar16 = DUSK_IF_ELSE(dusk::interp::particle::emitter_age(work->mpEmtr), work->mpEmtr->getAge()); f32 dVar15 = 0.5f * (1.0f + shape->getTilingS()); f32 dVar14 = 0.5f * (1.0f + shape->getTilingT()); f32 dVar11 = (dVar16 * shape->getIncTransX()) + shape->getInitTransX(); @@ -547,24 +548,6 @@ static void submit_particle_quad( p_prj[work->mPrjType](work, posMtx); GXCallDisplayList(dl, dlSize); } - -void JPAInterpBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { - Mtx ptclPosMtx; - MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z); - dusk::frame_interp::record_final_mtx(ptclPosMtx, ptcl); -} - -void JPAInterpRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { - Mtx ptclPosMtx; - f32 sinRot = JMASSin(ptcl->mRotateAngle); - f32 cosRot = JMASCos(ptcl->mRotateAngle); - MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z); - ptclPosMtx[0][0] = cosRot; - ptclPosMtx[0][1] = -sinRot; - ptclPosMtx[1][0] = sinRot; - ptclPosMtx[1][1] = cosRot; - dusk::frame_interp::record_final_mtx(ptclPosMtx, ptcl); -} #endif void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) { @@ -574,13 +557,6 @@ void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_C ZoneScoped; JGeometry::TVec3 pos; -#if TARGET_PC - Mtx ptclPosMtx; - if (dusk::frame_interp::lookup_replacement(ptcl, ptclPosMtx)) { - pos.set(ptclPosMtx[0][3], ptclPosMtx[1][3], ptclPosMtx[2][3]); - MTXMultVec(work->mPosCamMtx, &pos, &pos); - } else -#endif { MTXMultVec(work->mPosCamMtx, &ptcl->mPosition, &pos); } @@ -613,16 +589,6 @@ void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRA JGeometry::TVec3 pos; f32 sinRot, cosRot; -#if TARGET_PC - Mtx ptclPosMtx; - MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z); - if (dusk::frame_interp::lookup_replacement(ptcl, ptclPosMtx)) { - pos.set(ptclPosMtx[0][3], ptclPosMtx[1][3], ptclPosMtx[2][3]); - sinRot = ptclPosMtx[1][0]; - cosRot = ptclPosMtx[0][0]; - MTXMultVec(work->mPosCamMtx, &pos, &pos); - } else -#endif { MTXMultVec(work->mPosCamMtx, &ptcl->mPosition, &pos); sinRot = JMASSin(ptcl->mRotateAngle); @@ -734,7 +700,7 @@ void dirTypePosInv(JPAEmitterWorkData const* work, JPABaseParticle const* param_ void dirTypeEmtrDir(JPAEmitterWorkData const* work, JPABaseParticle const* param_1, JGeometry::TVec3* param_2) { - param_2->set(work->mGlobalEmtrDir); + param_2->set(DUSK_IF_ELSE(dusk::interp::particle::emitter_direction(work->mpEmtr, work->mGlobalEmtrDir), work->mGlobalEmtrDir)); } void dirTypePrevPtcl(JPAEmitterWorkData const* work, JPABaseParticle const* param_1, @@ -743,9 +709,14 @@ void dirTypePrevPtcl(JPAEmitterWorkData const* work, JPABaseParticle const* para param_1->getGlobalPosition(&aTStack_24); if (work->mpCurNode->getPrev() != work->mpAlivePtcl->getEnd()) { +#if TARGET_PC + param_2->set(dusk::interp::particle::position(work->mpCurNode->getPrev()->getObject())); +#else work->mpCurNode->getPrev()->getObject()->getGlobalPosition(param_2); +#endif } else { work->mpEmtr->calcEmitterGlobalPosition(param_2); + IF_DUSK(param_2->set(dusk::interp::particle::emitter_position(work->mpEmtr, *param_2))); } param_2->sub(aTStack_24); } @@ -875,171 +846,6 @@ static u8* p_dl[2] = { jpa_dl_x, }; -#if TARGET_PC -static bool make_direction_mtx(JPAEmitterWorkData* work, JPABaseParticle* ptcl, Mtx posMtx) { - JGeometry::TVec3 axisY; - JGeometry::TVec3 axisZ; - JGeometry::TVec3 baseAxis(ptcl->mBaseAxis); - p_direction[work->mDirType](work, ptcl, &axisY); - if (axisY.isZero()) { - return false; - } - - axisY.normalize(); - axisZ.cross(baseAxis, axisY); - if (axisZ.isZero()) { - return false; - } - - axisZ.normalize(); - baseAxis.cross(axisY, axisZ); - baseAxis.normalize(); - ptcl->mBaseAxis.set(baseAxis); - - f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; - f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; - posMtx[0][0] = baseAxis.x; - posMtx[0][1] = axisY.x; - posMtx[0][2] = axisZ.x; - posMtx[0][3] = ptcl->mPosition.x; - posMtx[1][0] = baseAxis.y; - posMtx[1][1] = axisY.y; - posMtx[1][2] = axisZ.y; - posMtx[1][3] = ptcl->mPosition.y; - posMtx[2][0] = baseAxis.z; - posMtx[2][1] = axisY.z; - posMtx[2][2] = axisZ.z; - posMtx[2][3] = ptcl->mPosition.z; - p_plane[work->mPlaneType](posMtx, scaleX, scaleY); - return true; -} - -static bool make_rot_direction_mtx(JPAEmitterWorkData* work, JPABaseParticle* ptcl, Mtx posMtx) { - f32 sinRot = JMASSin(ptcl->mRotateAngle); - f32 cosRot = JMASCos(ptcl->mRotateAngle); - JGeometry::TVec3 axisY; - JGeometry::TVec3 axisZ; - JGeometry::TVec3 baseAxis(ptcl->mBaseAxis); - p_direction[work->mDirType](work, ptcl, &axisY); - if (axisY.isZero()) { - return false; - } - - axisY.normalize(); - axisZ.cross(baseAxis, axisY); - if (axisZ.isZero()) { - return false; - } - - axisZ.normalize(); - baseAxis.cross(axisY, axisZ); - baseAxis.normalize(); - ptcl->mBaseAxis.set(baseAxis); - - f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; - f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; - Mtx rotMtx; - Mtx dirMtx; - p_rot[work->mRotType](sinRot, cosRot, rotMtx); - p_plane[work->mPlaneType](rotMtx, scaleX, scaleY); - dirMtx[0][0] = baseAxis.x; - dirMtx[0][1] = axisY.x; - dirMtx[0][2] = axisZ.x; - dirMtx[0][3] = ptcl->mPosition.x; - dirMtx[1][0] = baseAxis.y; - dirMtx[1][1] = axisY.y; - dirMtx[1][2] = axisZ.y; - dirMtx[1][3] = ptcl->mPosition.y; - dirMtx[2][0] = baseAxis.z; - dirMtx[2][1] = axisY.z; - dirMtx[2][2] = axisZ.z; - dirMtx[2][3] = ptcl->mPosition.z; - MTXConcat(dirMtx, rotMtx, posMtx); - return true; -} - -void JPAInterpDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { - JGeometry::TVec3 axisY; - JGeometry::TVec3 axisZ; - p_direction[work->mDirType](work, ptcl, &axisY); - - if (axisY.isZero()) { - return; - } - - axisY.normalize(); - axisZ.cross(ptcl->mBaseAxis, axisY); - - if (axisZ.isZero()) { - return; - } - - axisZ.normalize(); - ptcl->mBaseAxis.cross(axisY, axisZ); - ptcl->mBaseAxis.normalize(); - Mtx posMtx; - f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; - f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; - posMtx[0][0] = ptcl->mBaseAxis.x; - posMtx[0][1] = axisY.x; - posMtx[0][2] = axisZ.x; - posMtx[0][3] = ptcl->mPosition.x; - posMtx[1][0] = ptcl->mBaseAxis.y; - posMtx[1][1] = axisY.y; - posMtx[1][2] = axisZ.y; - posMtx[1][3] = ptcl->mPosition.y; - posMtx[2][0] = ptcl->mBaseAxis.z; - posMtx[2][1] = axisY.z; - posMtx[2][2] = axisZ.z; - posMtx[2][3] = ptcl->mPosition.z; - p_plane[work->mPlaneType](posMtx, scaleX, scaleY); - dusk::frame_interp::record_final_mtx(posMtx, ptcl); -} - -void JPAInterpRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { - f32 sinRot = JMASSin(ptcl->mRotateAngle); - f32 cosRot = JMASCos(ptcl->mRotateAngle); - JGeometry::TVec3 axisY; - JGeometry::TVec3 axisZ; - p_direction[work->mDirType](work, ptcl, &axisY); - - if (axisY.isZero()) { - return; - } - - axisY.normalize(); - axisZ.cross(ptcl->mBaseAxis, axisY); - - if (axisZ.isZero()) { - return; - } - - axisZ.normalize(); - ptcl->mBaseAxis.cross(axisY, axisZ); - ptcl->mBaseAxis.normalize(); - f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; - f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; - Mtx mtx1; - Mtx mtx2; - p_rot[work->mRotType](sinRot, cosRot, mtx1); - p_plane[work->mPlaneType](mtx1, scaleX, scaleY); - mtx2[0][0] = ptcl->mBaseAxis.x; - mtx2[0][1] = axisY.x; - mtx2[0][2] = axisZ.x; - mtx2[0][3] = ptcl->mPosition.x; - mtx2[1][0] = ptcl->mBaseAxis.y; - mtx2[1][1] = axisY.y; - mtx2[1][2] = axisZ.y; - mtx2[1][3] = ptcl->mPosition.y; - mtx2[2][0] = ptcl->mBaseAxis.z; - mtx2[2][1] = axisY.z; - mtx2[2][2] = axisZ.z; - mtx2[2][3] = ptcl->mPosition.z; - MTXConcat(mtx2, mtx1, mtx1); - dusk::frame_interp::record_final_mtx(mtx1, ptcl); -} -#endif - void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) { if (ptcl->checkStatus(JPAPtclStts_Invisible)) { return; @@ -1048,13 +854,6 @@ void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_C ZoneScoped; Mtx posMtx; -#if TARGET_PC - if (!dusk::frame_interp::lookup_replacement(ptcl, posMtx) && - !make_direction_mtx(work, ptcl, posMtx)) - { - return; - } -#else { JGeometry::TVec3 axisY; JGeometry::TVec3 axisZ; @@ -1090,7 +889,6 @@ void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_C posMtx[2][3] = ptcl->mPosition.z; p_plane[work->mPlaneType](posMtx, scaleX, scaleY); } -#endif MTXConcat(work->mPosCamMtx, posMtx, posMtx); #if TARGET_PC @@ -1111,13 +909,6 @@ void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRA Mtx mtx1; Mtx mtx2; -#if TARGET_PC - if (!dusk::frame_interp::lookup_replacement(ptcl, mtx1) && - !make_rot_direction_mtx(work, ptcl, mtx1)) - { - return; - } -#else { f32 sinRot = JMASSin(ptcl->mRotateAngle); f32 cosRot = JMASCos(ptcl->mRotateAngle); @@ -1157,7 +948,6 @@ void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRA mtx2[2][3] = ptcl->mPosition.z; MTXConcat(mtx2, mtx1, mtx1); } -#endif MTXConcat(work->mPosCamMtx, mtx1, mtx2); #if TARGET_PC submit_particle_quad(work, ctx, mtx2, p_dl[work->mDLType], sizeof(jpa_dl)); @@ -1329,6 +1119,11 @@ void JPADrawStripe(JPAEmitterWorkData* param_0) { node = node_func(node), coord += step) { param_0->mpCurNode = node; JPABaseParticle* particle = node->getObject(); +#if TARGET_PC + JPABaseParticle scratch; + float age; + particle = dusk::interp::particle::present(particle, scratch, age); +#endif local_ec.set(particle->mPosition); dVar11 = JMASSin(particle->mRotateAngle); dVar12 = JMASCos(particle->mRotateAngle); @@ -1420,6 +1215,11 @@ void JPADrawStripeX(JPAEmitterWorkData* param_0) { node = node_func(node), coord += step) { param_0->mpCurNode = node; JPABaseParticle* particle = node->getObject(); +#if TARGET_PC + JPABaseParticle scratch; + float age; + particle = dusk::interp::particle::present(particle, scratch, age); +#endif local_b4.set(particle->mPosition); dVar11 = JMASSin(particle->mRotateAngle); dVar12 = JMASCos(particle->mRotateAngle); @@ -1470,6 +1270,11 @@ void JPADrawStripeX(JPAEmitterWorkData* param_0) { node = node_func(node), coord += step) { param_0->mpCurNode = node; JPABaseParticle* particle = node->getObject(); +#if TARGET_PC + JPABaseParticle scratch; + float age; + particle = dusk::interp::particle::present(particle, scratch, age); +#endif local_b4.set(particle->mPosition); dVar11 = JMASCos(particle->mRotateAngle); dVar12 = -JMASSin(particle->mRotateAngle); diff --git a/libs/JSystem/src/JParticle/JPAEmitter.cpp b/libs/JSystem/src/JParticle/JPAEmitter.cpp index aa168a44a4..e033c9bc7e 100644 --- a/libs/JSystem/src/JParticle/JPAEmitter.cpp +++ b/libs/JSystem/src/JParticle/JPAEmitter.cpp @@ -7,10 +7,15 @@ #include "JSystem/JParticle/JPABaseShape.h" #include +#if TARGET_PC +#include "dusk/interp/particle.h" +#endif + JPAEmitterCallBack::~JPAEmitterCallBack() { } void JPABaseEmitter::init(JPAEmitterManager* param_0, JPAResource* param_1) { + IF_DUSK(dusk::interp::particle::reset(this)); mpEmtrMgr = param_0; pRes = param_1; pRes->getDyn()->getEmitterScl(&mLocalScl); diff --git a/libs/JSystem/src/JParticle/JPAEmitterManager.cpp b/libs/JSystem/src/JParticle/JPAEmitterManager.cpp index 3a793000ed..0dcaa3b8b5 100644 --- a/libs/JSystem/src/JParticle/JPAEmitterManager.cpp +++ b/libs/JSystem/src/JParticle/JPAEmitterManager.cpp @@ -8,7 +8,9 @@ #include "JSystem/JUtility/JUTAssert.h" #include +#if TARGET_PC #include "tracy/Tracy.hpp" +#endif JPAEmitterManager::JPAEmitterManager(u32 i_ptclNum, u32 i_emtrNum, JKRHeap* pHeap, u8 i_gidMax, u8 i_ridMax) { diff --git a/libs/JSystem/src/JParticle/JPAParticle.cpp b/libs/JSystem/src/JParticle/JPAParticle.cpp index 99ba95453c..a98f228337 100644 --- a/libs/JSystem/src/JParticle/JPAParticle.cpp +++ b/libs/JSystem/src/JParticle/JPAParticle.cpp @@ -8,7 +8,7 @@ #include "JSystem/JParticle/JPAExtraShape.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/interp/particle.h" #endif JPAParticleCallBack::~JPAParticleCallBack() { @@ -16,6 +16,7 @@ JPAParticleCallBack::~JPAParticleCallBack() { } void JPABaseParticle::init_p(JPAEmitterWorkData* work) { + IF_DUSK(dusk::interp::particle::reset(this)); JPABaseEmitter* emtr = work->mpEmtr; JPAExtraShape* esp = work->mpRes->getEsp(); JPABaseShape* bsp = work->mpRes->getBsp(); @@ -126,6 +127,7 @@ void JPABaseParticle::init_p(JPAEmitterWorkData* work) { } void JPABaseParticle::init_c(JPAEmitterWorkData* work, JPABaseParticle* parent) { + IF_DUSK(dusk::interp::particle::reset_child(this, parent, work)); JPABaseEmitter* emtr = work->mpEmtr; JPAChildShape* csp = work->mpRes->getCsp(); @@ -208,27 +210,6 @@ void JPABaseParticle::init_c(JPAEmitterWorkData* work, JPABaseParticle* parent) mTexAnmIdx = 0; } -#if TARGET_PC -void JPABaseParticle::interp(JPAEmitterWorkData* work, void const* drawFunc) { - if (!dusk::frame_interp::is_enabled()) - return; - - // don't interpolate the first frame - if (mAge == 0) - return; - - if (drawFunc == JPADrawBillboard) { - JPAInterpBillboard(work, this); - } else if (drawFunc == JPADrawRotBillboard) { - JPAInterpRotBillboard(work, this); - } else if (drawFunc == JPADrawDirection) { - JPAInterpDirection(work, this); - } else if (drawFunc == JPADrawRotDirection) { - JPAInterpRotDirection(work, this); - } -} -#endif - bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) { if (++mAge >= mLifeTime) { return true; @@ -267,21 +248,13 @@ bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) { } } + IF_DUSK(dusk::interp::particle::capture_birth(this, work)); mLocalPosition.add(mVelocity); mPosition.set(mOffsetPosition.x + mLocalPosition.x * work->mPublicScale.x, mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y, mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z); -#if TARGET_PC - JPABaseShape* pBsp = work->mpRes->getBsp(); - work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX(); - work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pBsp->getBaseSizeY(); - work->mDirType = pBsp->getDirType(); - work->mRotType = pBsp->getRotType(); - work->mDLType = pBsp->getType() == 4 || pBsp->getType() == 8; - work->mPlaneType = work->mDLType ? 2 : pBsp->getBasePlaneType(); - interp(work, (void const*)work->mpRes->mpDrawParticleFuncList[0]); -#endif + IF_DUSK(dusk::interp::particle::capture(this)); return false; } @@ -320,27 +293,13 @@ bool JPABaseParticle::calc_c(JPAEmitterWorkData* work) { work->mpRes->calc_c(work, this); mRotateAngle += mRotateSpeed; + IF_DUSK(dusk::interp::particle::capture_birth(this, work)); mLocalPosition.add(mVelocity); mPosition.set(mOffsetPosition.x + mLocalPosition.x * work->mPublicScale.x, mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y, mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z); -#if TARGET_PC - JPABaseShape* pBsp = work->mpRes->getBsp(); - JPAChildShape* pCsp = work->mpRes->getCsp(); - if (pCsp->isScaleInherited()) { - work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX(); - work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pBsp->getBaseSizeY(); - } else { - work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pCsp->getScaleX(); - work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pCsp->getScaleY(); - } - work->mDirType = pCsp->getDirType(); - work->mRotType = pCsp->getRotType(); - work->mDLType = pCsp->getType() == 4 || pCsp->getType() == 8; - work->mPlaneType = work->mDLType ? 2 : pCsp->getBasePlaneType(); - interp(work, (void const*)work->mpRes->mpDrawParticleChildFuncList[0]); -#endif + IF_DUSK(dusk::interp::particle::capture(this)); return false; } diff --git a/libs/JSystem/src/JParticle/JPAResource.cpp b/libs/JSystem/src/JParticle/JPAResource.cpp index b67ac47489..aaddb1ae5f 100644 --- a/libs/JSystem/src/JParticle/JPAResource.cpp +++ b/libs/JSystem/src/JParticle/JPAResource.cpp @@ -1,10 +1,6 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JParticle/JPAResource.h" - -#include - -#include #include "JSystem/JKernel/JKRHeap.h" #include "JSystem/JParticle/JPABaseShape.h" #include "JSystem/JParticle/JPAChildShape.h" @@ -15,11 +11,13 @@ #include "JSystem/JParticle/JPAKeyBlock.h" #include "JSystem/JParticle/JPAParticle.h" #include "JSystem/JParticle/JPAResourceManager.h" +#include #include "global.h" -#include "tracy/Tracy.hpp" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/interp/particle.h" + +#include #define JPA_DRAW_CTX_ARG , &ctx #else @@ -839,15 +837,6 @@ bool JPAResource::calc(JPAEmitterWorkData* work, JPABaseEmitter* emtr) { } } -#ifdef TARGET_PC - if (((pBsp && pBsp->getDirType() == 3) || (pCsp && pCsp->getDirType() == 3)) && - dusk::frame_interp::is_enabled()) - { - // ensure mGlobalEmtrDir is valid - calcWorkData_d(work); - } -#endif - JPANode* next = NULL; for (JPANode* node = emtr->mAlivePtclBase.getFirst(); node != emtr->mAlivePtclBase.getEnd(); node = next) { next = node->getNext(); @@ -864,6 +853,7 @@ bool JPAResource::calc(JPAEmitterWorkData* work, JPABaseEmitter* emtr) { } emtr->mTick++; + IF_DUSK(dusk::interp::particle::capture(emtr)); } return false; @@ -874,6 +864,7 @@ void JPAResource::draw(JPAEmitterWorkData* work, JPABaseEmitter* emtr) { work->mpEmtr = emtr; work->mpRes = this; work->mDrawCount = 0; + IF_DUSK(dusk::interp::particle::EmitterDraw presentation(emtr)); calcWorkData_d(work); pBsp->setGX(work); for (s32 i = 1; i <= emtr->getDrawTimes(); i++) { @@ -1051,8 +1042,10 @@ static bool draw_particle_batch(JPAEmitterWorkData* work) { GXBegin(GX_QUADS, GX_VTXFMT1, GX_AUTO); while (node != work->mpEmtr->mAlivePtclBase.getEnd()) { work->mpCurNode = node; + JPABaseParticle scratch; + JPABaseParticle* particle = dusk::interp::particle::present(node->getObject(), scratch, ctx.age); for (int i = res->mpDrawParticleFuncListNum - 1; i >= 0; i--) { - (*res->mpDrawParticleFuncList[i])(work, node->getObject(), &ctx); + (*res->mpDrawParticleFuncList[i])(work, particle, &ctx); } node = fwdAhead ? node->getPrev() : node->getNext(); } @@ -1113,9 +1106,13 @@ void JPAResource::drawP(JPAEmitterWorkData* work) { JPANode* node = work->mpEmtr->mAlivePtclBase.getLast(); for (; node != work->mpEmtr->mAlivePtclBase.getEnd(); node = node->getPrev()) { work->mpCurNode = node; +#if TARGET_PC + JPABaseParticle scratch; + JPABaseParticle* particle = dusk::interp::particle::present(node->getObject(), scratch, ctx.age); +#endif if (mpDrawParticleFuncList != NULL) { for (int i = mpDrawParticleFuncListNum - 1; i >= 0; i--) { - (*mpDrawParticleFuncList[i])(work, node->getObject() JPA_DRAW_CTX_ARG); + (*mpDrawParticleFuncList[i])(work, DUSK_IF_ELSE(particle, node->getObject()) JPA_DRAW_CTX_ARG); } } } @@ -1123,9 +1120,13 @@ void JPAResource::drawP(JPAEmitterWorkData* work) { JPANode* node = work->mpEmtr->mAlivePtclBase.getFirst(); for (; node != work->mpEmtr->mAlivePtclBase.getEnd(); node = node->getNext()) { work->mpCurNode = node; +#if TARGET_PC + JPABaseParticle scratch; + JPABaseParticle* particle = dusk::interp::particle::present(node->getObject(), scratch, ctx.age); +#endif if (mpDrawParticleFuncList != NULL) { for (int i = mpDrawParticleFuncListNum - 1; i >= 0; i--) { - (*mpDrawParticleFuncList[i])(work, node->getObject() JPA_DRAW_CTX_ARG); + (*mpDrawParticleFuncList[i])(work, DUSK_IF_ELSE(particle, node->getObject()) JPA_DRAW_CTX_ARG); } } } @@ -1180,9 +1181,13 @@ void JPAResource::drawC(JPAEmitterWorkData* work) { JPANode* node = work->mpEmtr->mAlivePtclChld.getLast(); for (; node != work->mpEmtr->mAlivePtclChld.getEnd(); node = node->getPrev()) { work->mpCurNode = node; +#if TARGET_PC + JPABaseParticle scratch; + JPABaseParticle* particle = dusk::interp::particle::present(node->getObject(), scratch, ctx.age); +#endif if (mpDrawParticleChildFuncList != NULL) { for (int i = mpDrawParticleChildFuncListNum - 1; i >= 0; i--) { - (*mpDrawParticleChildFuncList[i])(work, node->getObject() JPA_DRAW_CTX_ARG); + (*mpDrawParticleChildFuncList[i])(work, DUSK_IF_ELSE(particle, node->getObject()) JPA_DRAW_CTX_ARG); } } } @@ -1190,9 +1195,13 @@ void JPAResource::drawC(JPAEmitterWorkData* work) { JPANode* node = work->mpEmtr->mAlivePtclChld.getFirst(); for (; node != work->mpEmtr->mAlivePtclChld.getEnd(); node = node->getNext()) { work->mpCurNode = node; +#if TARGET_PC + JPABaseParticle scratch; + JPABaseParticle* particle = dusk::interp::particle::present(node->getObject(), scratch, ctx.age); +#endif if (mpDrawParticleChildFuncList != NULL) { for (int i = mpDrawParticleChildFuncListNum - 1; i >= 0; i--) { - (*mpDrawParticleChildFuncList[i])(work, node->getObject() JPA_DRAW_CTX_ARG); + (*mpDrawParticleChildFuncList[i])(work, DUSK_IF_ELSE(particle, node->getObject()) JPA_DRAW_CTX_ARG); } } } diff --git a/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp b/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp index 2f408d6184..b6f41098e5 100644 --- a/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp +++ b/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp @@ -4,7 +4,7 @@ #if TARGET_PC #include "dusk/audio.h" -#include "dusk/frame_interpolation.h" +#include "dusk/interp/frame_interpolation.h" #include "dusk/settings.h" #endif @@ -655,10 +655,10 @@ value_or_fun: value: #if TARGET_PC - if (dusk::frame_interp::is_enabled() && u <= 5 && + if (dusk::interp::is_enabled() && u <= 5 && (operation == data::UNK_0x2 || operation == data::UNK_0x3 || operation == data::UNK_0x12)) { - dusk::frame_interp::request_presentation_sync(); + dusk::interp::request_presentation_sync(); } #endif adaptor->adaptor_setVariableValue(control, u, operation, param_2, param_3); @@ -666,11 +666,11 @@ value: value_n: #if TARGET_PC - if (dusk::frame_interp::is_enabled() && + if (dusk::interp::is_enabled() && (pN == TAdaptor_camera::sauVariableValue_3_POSITION_XYZ || pN == TAdaptor_camera::sauVariableValue_3_TARGET_POSITION_XYZ) && (operation == data::UNK_0x2 || operation == data::UNK_0x3 || operation == data::UNK_0x12)) { - dusk::frame_interp::request_presentation_sync(); + dusk::interp::request_presentation_sync(); } #endif adaptor->adaptor_setVariableValue_n(control, pN, u, operation, param_2, param_3); diff --git a/libs/JSystem/src/JUtility/JUTFader.cpp b/libs/JSystem/src/JUtility/JUTFader.cpp index a8d9fe6028..cf9a9e2364 100644 --- a/libs/JSystem/src/JUtility/JUTFader.cpp +++ b/libs/JSystem/src/JUtility/JUTFader.cpp @@ -8,9 +8,10 @@ #include "JSystem/JUtility/JUTFader.h" #include "JSystem/J2DGraph/J2DOrthoGraph.h" -#ifdef TARGET_PC +#if TARGET_PC +#include "dusk/game_clock.h" + #include -#include "dusk/frame_interpolation.h" #endif JUTFader::JUTFader(int x, int y, int width, int height, JUtility::TColor pColor) @@ -38,6 +39,7 @@ void JUTFader::advance() { case FadeIn: #if AVOID_UB if (mDuration == 0) { + IF_DUSK(mColor.a = 0); mStatus = Wait; break; } @@ -52,6 +54,7 @@ void JUTFader::advance() { case FadeOut: #if AVOID_UB if (mDuration == 0) { + IF_DUSK(mColor.a = 0xFF); mStatus = None; break; } @@ -68,26 +71,26 @@ void JUTFader::advance() { void JUTFader::control() { advance(); + IF_DUSK_BLOCK(getStatus() != Wait) draw(); + IF_DUSK_BLOCK_END } void JUTFader::draw() { - if (mColor.a != 0) { -#ifdef TARGET_PC - if (dusk::frame_interp::is_enabled() && mDuration != 0) { - const auto step = dusk::frame_interp::get_interpolation_step(); - const auto progress = static_cast(mTimer) / static_cast(mDuration); - const auto timer = mTimer - 1 + step + progress; - auto alpha = timer / mDuration; - if (mStatus == FadeIn) { - alpha = 1.0f - alpha; - } - alpha = std::clamp(alpha, 0.0f, 1.0f); - mColor.a = static_cast(alpha * 255.0f); - } +#if TARGET_PC + JUtility::TColor color = mColor; + if (dusk::game_clock::g_frameTiming.separatePresentation && mDuration != 0 && + (mStatus == FadeIn || mStatus == FadeOut)) + { + const f32 timer = std::min(mTimer + dusk::game_clock::sample_interpolation_step(), + static_cast(mDuration)); + const u8 alpha = static_cast((timer * 0xFF) / mDuration); + color.a = mStatus == FadeIn ? 0xFF - alpha : alpha; + } #endif + if (DUSK_IF_ELSE(color, mColor).a != 0) { J2DOrthoGraph orthograph; - orthograph.setColor(mColor); + orthograph.setColor(DUSK_IF_ELSE(color, mColor)); orthograph.fillBox(mBox); } } diff --git a/libs/freeverb/include/freeverb/tuning.h b/libs/freeverb/include/freeverb/tuning.h index baaa9ce004..727c01b680 100644 --- a/libs/freeverb/include/freeverb/tuning.h +++ b/libs/freeverb/include/freeverb/tuning.h @@ -7,54 +7,50 @@ #ifndef _tuning_ #define _tuning_ -const int numcombs = 8; -const int numallpasses = 4; -const float muted = 0; -const float fixedgain = 0.015f; -const float scalewet = 3; -const float scaledry = 2; -const float scaledamp = 0.4f; -const float scaleroom = 0.28f; -const float offsetroom = 0.7f; -const float initialroom = 0.5f; -const float initialdamp = 0.5f; -const float initialwet = 1/scalewet; -const float initialdry = 0; -const float initialwidth = 1; -const float initialmode = 0; -const float freezemode = 0.5f; -const int stereospread = 23; +constexpr int numcombs = 8; +constexpr int numallpasses = 4; +constexpr float muted = 0; +constexpr float fixedgain = 0.015f; +constexpr float scalewet = 3; +constexpr float scaledry = 2; +constexpr float scaledamp = 0.4f; +constexpr float scaleroom = 0.28f; +constexpr float offsetroom = 0.7f; +constexpr float initialroom = 0.5f; +constexpr float initialdamp = 0.5f; +constexpr float initialwet = 1/scalewet; +constexpr float initialdry = 0; +constexpr float initialwidth = 1; +constexpr float initialmode = 0; +constexpr float freezemode = 0.5f; +constexpr float srscale = 48000.0f/32000.0f; +constexpr int stereospread = 23*srscale; -// These values assume 44.1KHz sample rate -// they will probably be OK for 48KHz sample rate -// but would need scaling for 96KHz (or other) sample rates. -// The values were obtained by listening tests. -const int combtuningL1 = 1116; -const int combtuningR1 = 1116+stereospread; -const int combtuningL2 = 1188; -const int combtuningR2 = 1188+stereospread; -const int combtuningL3 = 1277; -const int combtuningR3 = 1277+stereospread; -const int combtuningL4 = 1356; -const int combtuningR4 = 1356+stereospread; -const int combtuningL5 = 1422; -const int combtuningR5 = 1422+stereospread; -const int combtuningL6 = 1491; -const int combtuningR6 = 1491+stereospread; -const int combtuningL7 = 1557; -const int combtuningR7 = 1557+stereospread; -const int combtuningL8 = 1617; -const int combtuningR8 = 1617+stereospread; -const int allpasstuningL1 = 556; -const int allpasstuningR1 = 556+stereospread; -const int allpasstuningL2 = 441; -const int allpasstuningR2 = 441+stereospread; -const int allpasstuningL3 = 341; -const int allpasstuningR3 = 341+stereospread; -const int allpasstuningL4 = 225; -const int allpasstuningR4 = 225+stereospread; +constexpr int combtuningL1 = 1116*srscale; +constexpr int combtuningR1 = combtuningL1+stereospread; +constexpr int combtuningL2 = 1188*srscale; +constexpr int combtuningR2 = combtuningL2+stereospread; +constexpr int combtuningL3 = 1277*srscale; +constexpr int combtuningR3 = combtuningL3+stereospread; +constexpr int combtuningL4 = 1356*srscale; +constexpr int combtuningR4 = combtuningL4+stereospread; +constexpr int combtuningL5 = 1422*srscale; +constexpr int combtuningR5 = combtuningL5+stereospread; +constexpr int combtuningL6 = 1491*srscale; +constexpr int combtuningR6 = combtuningL6+stereospread; +constexpr int combtuningL7 = 1557*srscale; +constexpr int combtuningR7 = combtuningL7+stereospread; +constexpr int combtuningL8 = 1617*srscale; +constexpr int combtuningR8 = combtuningL8+stereospread; +constexpr int allpasstuningL1 = 556*srscale; +constexpr int allpasstuningR1 = allpasstuningL1+stereospread; +constexpr int allpasstuningL2 = 441*srscale; +constexpr int allpasstuningR2 = allpasstuningL2+stereospread; +constexpr int allpasstuningL3 = 341*srscale; +constexpr int allpasstuningR3 = allpasstuningL3+stereospread; +constexpr int allpasstuningL4 = 225*srscale; +constexpr int allpasstuningR4 = allpasstuningL4+stereospread; #endif//_tuning_ //ends - diff --git a/libs/freeverb/revmodel.cpp b/libs/freeverb/revmodel.cpp index d4f9cf2513..80b143be9d 100644 --- a/libs/freeverb/revmodel.cpp +++ b/libs/freeverb/revmodel.cpp @@ -6,6 +6,7 @@ #include "revmodel.hpp" #include "denormals.h" +#include revmodel::revmodel() { @@ -194,8 +195,9 @@ void revmodel::update() for(i=0; iinfo(mod_ctx, "template_mod initialized"); + AudioWaveHandle handle; + svc_audio_res->replace_wave( + mod_ctx, AUDIO_WAVE_BANK_SOUND_EFFECTS, 4238, "res/go.opus", nullptr, &handle); + svc_audio_res->replace_wave( + mod_ctx, AUDIO_WAVE_BANK_SOUND_EFFECTS, 4237, "res/go.opus", nullptr, &handle); + + constexpr AudioSoundTableEffectInfo effect(128, 1, 1.5); + + svc_audio_res->replace_sound_table_effect(mod_ctx, SE_CATEGORY_CHARA_SE, + 0, // MIDNA_APPEAR + &effect, nullptr); + + return MOD_OK; +} + +MOD_EXPORT ModResult mod_update(ModError*) { + return MOD_OK; +} + +MOD_EXPORT ModResult mod_shutdown(ModError*) { + svc_log->info(mod_ctx, "template_mod unloaded"); + return MOD_OK; +} +} diff --git a/mods/basic_cosmetics_mod/mod.json b/mods/basic_cosmetics_mod/mod.json deleted file mode 100644 index d6c258bfe9..0000000000 --- a/mods/basic_cosmetics_mod/mod.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "dev.twilitrealm.basic_cosmetics", - "name": "Basic Cosmetics", - "version": "1.0.0", - "author": "Twilit Realm", - "description": "Change Basic Cosmetic Colors" -} diff --git a/mods/basic_cosmetics_mod/src/color_utils.cpp b/mods/basic_cosmetics_mod/src/color_utils.cpp deleted file mode 100644 index d1dc2eb096..0000000000 --- a/mods/basic_cosmetics_mod/src/color_utils.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "color_utils.hpp" - -uint8_t desaturate_rgb_565(uint16_t rgb565Val) { - const uint32_t r = (rgb565Val & 0xf800) >> 11; - const uint32_t g = (rgb565Val & 0x7e0) >> 5; - const uint32_t b = rgb565Val & 0x1f; - - // Here we are doing a quicker (0.22 * r + 0.72 * g + 0.06 * b) which - // uses multiplies and shifts rather than division. - const uint32_t combined = 30480413 * r + 49085341 * g + 8312839 * b; - uint8_t shifted = (combined >> 24) & 0xff; - - // Check if should round up shifted value. - if (shifted < 0xff && combined & 0x00800000) { - shifted += 1; - } - - return shifted; -} - -uint16_t blend_overlay_rgb_565(uint8_t grayVal, GXColor color) { - uint32_t rTimes255, gTimes255, bTimes255; - - if (grayVal <= 0x7f) { - const uint32_t grayTimesTwo = 2 * grayVal; - - rTimes255 = grayTimesTwo * color.r; - gTimes255 = grayTimesTwo * color.g; - bTimes255 = grayTimesTwo * color.b; - } else { - const uint32_t multiplier = 2 * (255 - grayVal); - - rTimes255 = 255 * 255 - multiplier * (255 - color.r); - gTimes255 = 255 * 255 - multiplier * (255 - color.g); - bTimes255 = 255 * 255 - multiplier * (255 - color.b); - } - - // Divide each by 255 - const uint32_t r = (rTimes255 + 1 + (rTimes255 >> 8)) >> 8; - const uint32_t g = (gTimes255 + 1 + (gTimes255 >> 8)) >> 8; - const uint32_t b = (bTimes255 + 1 + (bTimes255 >> 8)) >> 8; - - return ((r & 0xf8) << 8) | ((g & 0xfc) << 3) | ((b & 0xf8) >> 3); -} - -// Helper function to perform overlay blending on a single 8-bit color channel -uint8_t blend_overlay_channel(uint8_t base, uint8_t blend) { - if (base < 128) { - return static_cast((2 * base * blend) / 255); - } - - return static_cast(255 - (2 * (255 - base) * (255 - blend)) / 255); -} - -bool is_valid_hex_color_str(std::string_view hexStr) { - return hexStr.find_first_not_of("0123456789ABCDEFabcdef") == std::string_view::npos && - hexStr.length() == 6; -} - -GXColor hex_color_str_to_gx_color(const std::string& hexColorStr) { - u8 r = std::stoi(hexColorStr.substr(0, 2), nullptr, 16); - u8 g = std::stoi(hexColorStr.substr(2, 2), nullptr, 16); - u8 b = std::stoi(hexColorStr.substr(4, 2), nullptr, 16); - return GXColor{r, g, b}; -} - -static f32 rainbowPhaseAngle = 0.f; - -GXColor get_rainbow_rgb(f32 amplitude) { - f32 phase_rad = rainbowPhaseAngle * M_PI / 180.0f; - - u8 r_val = (u8)(amplitude * (sinf(phase_rad) + 1.0f) + 0.5f); - u8 g_val = (u8)(amplitude * (sinf(phase_rad + 2.0f * M_PI / 3.0f) + 1.0f) + 0.5f); - u8 b_val = (u8)(amplitude * (sinf(phase_rad + 4.0f * M_PI / 3.0f) + 1.0f)); - GXColor rgbColor; - rgbColor.r = r_val; - rgbColor.g = g_val; - rgbColor.b = b_val; - rgbColor.a = 0xff; - return rgbColor; -} - -void update_rainbow_rgb(f32 increment) { - rainbowPhaseAngle += increment; - if (rainbowPhaseAngle >= 360.0f) { - rainbowPhaseAngle -= 360.0f; - } -} \ No newline at end of file diff --git a/mods/basic_cosmetics_mod/src/color_utils.hpp b/mods/basic_cosmetics_mod/src/color_utils.hpp deleted file mode 100644 index 94e1ce62a8..0000000000 --- a/mods/basic_cosmetics_mod/src/color_utils.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/** - * File originally copied from console TPR with permission from isaac - * https://github.com/zsrtp/libtp_rel/blob/master/include/util/color_utils.h - */ - -#include - -#include -#include - -// Desaturates an RGB565 color to a u8 gray value (0xFF being white and 0x00 -// being black). -uint8_t desaturate_rgb_565(uint16_t rgb565Val); - -// Performs an "Overlay" blend of a u8 gray value and a pointer to a u8 -// array of {r,g,b}. Returns the result as an RGB565. -uint16_t blend_overlay_rgb_565(uint8_t grayVal, GXColor color); - -// Perform overlay blending on a single 8-bit color channel -uint8_t blend_overlay_channel(uint8_t base, uint8_t blend); - -bool is_valid_hex_color_str(std::string_view hexStr); - -GXColor hex_color_str_to_gx_color(const std::string& hexColorStr); - -GXColor get_rainbow_rgb(f32 amplitude); - -void update_rainbow_rgb(f32 increment); diff --git a/mods/basic_cosmetics_mod/src/hooks.cpp b/mods/basic_cosmetics_mod/src/hooks.cpp deleted file mode 100644 index 2c878f7632..0000000000 --- a/mods/basic_cosmetics_mod/src/hooks.cpp +++ /dev/null @@ -1,745 +0,0 @@ -#include "hooks.hpp" - -#include "color_utils.hpp" -#include "midna_hair_color.hpp" -#include "mod.hpp" -#include "types.h" - -#include "mods/svc/hook.hpp" -#include "mods/svc/log.hpp" - -#include "SSystem/SComponent/c_phase.h" -#include "d/actor/d_a_alink.h" -#include "d/actor/d_a_midna.h" -#include "d/d_a_item_static.h" -#include "d/d_bright_check.h" -#include "d/d_file_sel_info.h" -#include "d/d_file_select.h" -#include "d/d_kankyo.h" -#include "d/d_kantera_icon_meter.h" -#include "d/d_menu_collect.h" -#include "d/d_menu_dmap.h" -#include "d/d_menu_fishing.h" -#include "d/d_menu_fmap2D.h" -#include "d/d_menu_insect.h" -#include "d/d_menu_letter.h" -#include "d/d_menu_option.h" -#include "d/d_menu_ring.h" -#include "d/d_menu_save.h" -#include "d/d_menu_skill.h" -#include "d/d_meter2.h" -#include "d/d_meter2_draw.h" -#include "d/d_meter2_info.h" -#include "d/d_meter_button.h" -#include "d/d_meter_hakusha.h" -#include "d/d_msg_object.h" -#include "d/d_msg_out_font.h" -#include "d/d_msg_scrn_base.h" -#include "d/d_pane_class.h" - -// Lantern ambience color -DEFINE_HOOK(&dKy_WolfEyeLight_set, WolfEyeLightSet); -void wolf_eye_light_set_post(ModContext*, void*, void*, void*) { - auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true); - if (maybeLanternColor.has_value()) { - auto lanternColor = maybeLanternColor.value(); - - dScnKy_env_light_c* kankyo = dKy_getEnvlight(); - kankyo->field_0x0c18[0].mColor.r = lanternColor.r; - kankyo->field_0x0c18[0].mColor.g = lanternColor.g; - kankyo->field_0x0c18[0].mColor.b = lanternColor.b; - } -} - -// Lantern Sphere color -DEFINE_HOOK(&daAlink_c::preKandelaarDraw, PreKandelaarDraw); -void pre_kandelaar_draw_post(ModContext*, void*, void*, void*) { - auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true); - if (maybeLanternColor.has_value()) { - auto lanternColor = maybeLanternColor.value(); - - J3DMaterial* mat_p = daAlink_getAlinkActorClass() - ->mpKanteraGlowModel->getModelData() - ->getMaterialNodePointer(0); - - J3DGXColorS10 color; - color.r = lanternColor.r; - color.g = lanternColor.g; - color.b = lanternColor.b; - color.a = 255; - mat_p->setTevColor(1, &color); - - color.r = lanternColor.r; - color.g = lanternColor.g; - color.b = lanternColor.b; - mat_p->setTevColor(2, &color); - } -} - -// Main Lantern Meter Color -DEFINE_HOOK(&CPaneMgr::setBlackWhite, CPaneMgrSetBlackWhite); -HookAction cpane_mgr_set_black_white_pre(ModContext*, void* args, void*, void*) { - // Check for magic meter - auto pane = mods::arg(args, 0); - if (dMeter2Info_getMeterClass() == NULL || - pane != dMeter2Info_getMeterClass()->getMeterDrawPtr()->mpMagicMeter) - { - return HOOK_CONTINUE; - } - - // If magic meter, check to see we're setting lantern colors - auto& black = mods::arg_ref(args, 1); - auto& white = mods::arg_ref(args, 2); - if (black != JUtility::TColor(255, 255, 140, 255) && - white != JUtility::TColor(230, 170, 0, 255)) - { - return HOOK_CONTINUE; - } - - auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true); - if (maybeLanternColor.has_value()) { - auto lanternColor = maybeLanternColor.value(); - black = JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255); - white = JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255); - } - - return HOOK_CONTINUE; -} - -// Lantern Icon Meter Color -DEFINE_HOOK(&dKantera_icon_c::setNowGauge, KanteraIconSetNowGauge); -void kantera_icon_set_now_gauge_post(ModContext*, void* args, void*, void*) { - auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true); - if (maybeLanternColor.has_value()) { - auto lanternColor = maybeLanternColor.value(); - - auto kanteraIcon = mods::arg(args, 0); - kanteraIcon->mpGauge->setBlackWhite( - JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255), - JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255)); - } -} - -// Light Sword Effect Color -DEFINE_HOOK(&daAlink_c::setLightningSwordEffect, SetLightningSwordEffect); -void set_lightning_sword_effect_post(ModContext*, void* args, void*, void*) { - auto maybeGlowColor = get_config_var_color(get_cvars().lightSwordGlowColor, true); - if (maybeGlowColor.has_value()) { - auto glowColor = maybeGlowColor.value(); - - auto link = mods::arg(args, 0); - // Check copied from inside the hooked function - if (link->mEquipItem == 0x103 && link->checkNoResetFlg3(daPy_py_c::FLG3_UNK_100000)) { - for (size_t i = 0; i < 3; i++) { - auto emitter = dComIfGp_particle_getEmitter(link->field_0x327c[i]); - if (emitter != NULL) { - emitter->setGlobalEnvColor(glowColor.r, glowColor.g, glowColor.b); - emitter->setGlobalPrmColor(glowColor.r, glowColor.g, glowColor.b); - } - } - } - } -} - -void recolor_ui_button(ConfigVarHandle option, u64 tag, J2DScreen* screen) { - auto buttonColorStr = get_str_option(option, ""); - if (is_valid_hex_color_str(buttonColorStr)) { - auto color = hex_color_str_to_gx_color(buttonColorStr); - auto element = static_cast(screen->search(tag)); - if (element != nullptr) { - element->setBlackWhite( - JUtility::TColor(0, 0, 0, 0), JUtility::TColor(color.r, color.g, color.b, 0xFF)); - } - } -} - -// Main gameplay UI. Top left hearts and top right buttons -DEFINE_HOOK(&dMeter2Draw_c::init, dMeter2Init); -void d_meter_2_init_post(ModContext*, void* args, void*, void*) { - auto dMeter2Draw = mods::arg(args, 0); - auto screen = dMeter2Draw->getMainScreenPtr(); - - // Heart tags on main UI - static constexpr std::array kHeartTags = {MULTI_CHAR('hear_00'), MULTI_CHAR('hear_01'), - MULTI_CHAR('hear_02'), MULTI_CHAR('hear_03'), MULTI_CHAR('hear_04'), MULTI_CHAR('hear_05'), - MULTI_CHAR('hear_06'), MULTI_CHAR('hear_07'), MULTI_CHAR('hear_08'), MULTI_CHAR('hear_09'), - MULTI_CHAR('hear_10'), MULTI_CHAR('hear_11'), MULTI_CHAR('hear_12'), MULTI_CHAR('hear_13'), - MULTI_CHAR('hear_14'), MULTI_CHAR('hear_15'), MULTI_CHAR('hear_16'), MULTI_CHAR('hear_17'), - MULTI_CHAR('hear_18'), MULTI_CHAR('hear_19'), MULTI_CHAR('bigh_00'), MULTI_CHAR('bigh_01'), - MULTI_CHAR('bigh_02'), MULTI_CHAR('bigh_03')}; - - auto maybeHeartColor = get_config_var_color(get_cvars().heartColor); - if (maybeHeartColor.has_value()) { - auto heartColor = maybeHeartColor.value(); - for (auto tag : kHeartTags) { - auto element = static_cast(screen->search(tag)); - if (element != nullptr) { - element->setBlackWhite(heartColor, JUtility::TColor(200, 200, 200, 255)); - } - } - } - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); - recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('x_btn'), screen); - recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('y_btn'), screen); - recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen); -} - -// Hearts on the file select screen -DEFINE_HOOK(&dFile_info_c::setHeartCnt, FileInfoSetHeartCount); -void file_info_set_heart_count_post(ModContext*, void* args, void*, void*) { - auto fileInfo = mods::arg(args, 0); - - // Heart tags on file select - static constexpr std::array kFileSelectHeartTags{ - MULTI_CHAR('hear_20'), - MULTI_CHAR('hear_21'), - MULTI_CHAR('hear_22'), - MULTI_CHAR('hear_23'), - MULTI_CHAR('hear_24'), - MULTI_CHAR('hear_25'), - MULTI_CHAR('hear_26'), - MULTI_CHAR('hear_27'), - MULTI_CHAR('hear_28'), - MULTI_CHAR('hear_29'), - MULTI_CHAR('hear_30'), - MULTI_CHAR('hear_31'), - MULTI_CHAR('hear_32'), - MULTI_CHAR('hear_33'), - MULTI_CHAR('hear_34'), - MULTI_CHAR('hear_35'), - MULTI_CHAR('hear_36'), - MULTI_CHAR('hear_37'), - MULTI_CHAR('hear_38'), - MULTI_CHAR('hear_39'), - }; - - auto maybeHeartColor = get_config_var_color(get_cvars().heartColor); - if (maybeHeartColor.has_value()) { - auto heartColor = maybeHeartColor.value(); - for (auto tag : kFileSelectHeartTags) { - auto element = static_cast(fileInfo->mFileInfo.Scr->search(tag)); - if (element != nullptr) { - element->setBlackWhite(heartColor, JUtility::TColor(200, 200, 200, 255)); - } - } - } -} - -// Buttons that appear contextually at the bottom of the screen during gameplay -// (Are X, Y ever used there?) -DEFINE_HOOK(&dMeterButton_c::screenInitButton, ScreenInitButton); -void screen_init_button_post(ModContext*, void* args, void*, void*) { - auto meterButton = mods::arg(args, 0); - auto screen = meterButton->mpButtonScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); - recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('x_btn'), screen); - recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('y_btn'), screen); - recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen); -} - -// A and B buttons when saving a file -DEFINE_HOOK(&dMenu_save_c::screenSet, MenuSaveScreenSet); -void menu_save_screen_set_post(ModContext*, void* args, void*, void*) { - auto menuSave = mods::arg(args, 0); - auto screen = menuSave->mSaveSel.Scr; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('wabtn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('wbbtn'), screen); -} - -// A and B buttons when selecting a file -DEFINE_HOOK(&dFile_select_c::screenSet, FileSelectScreenSet); -void file_select_screen_set_post(ModContext*, void* args, void*, void*) { - auto fileSelect = mods::arg(args, 0); - auto screen = fileSelect->fileSel.Scr; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('wabtn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('wbbtn'), screen); -} - -// A button on brightness check screen -DEFINE_HOOK(&dBrightCheck_c::screenSet, BrightCheckScreenSet); -void bright_check_screen_set_post(ModContext*, void* args, void*, void*) { - auto brightCheck = mods::arg(args, 0); - auto screen = brightCheck->mBrightCheck.Scr; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen); -} - -// X and Y buttons on the item wheel screen -DEFINE_HOOK(&dMenu_Ring_c::_create, MenuRingCreate); -void menu_ring_create_post(ModContext*, void* args, void*, void*) { - auto menuRing = mods::arg(args, 0); - auto screen = menuRing->mpScreen; - - recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('xbtn'), screen); - recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('ybtn'), screen); -} - -// A and B buttons on the main pause screen -DEFINE_HOOK(&dMenu_Collect2D_c::_create, MenuCollect2DCreate); -void menu_collect_2D_create_post(ModContext*, void* args, void*, void*) { - auto menuCollect2D = mods::arg(args, 0); - auto screen = menuCollect2D->mpScreenIcon; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A and B buttons on the fishing journal screen -DEFINE_HOOK(&dMenu_Fishing_c::screenSetDoIcon, MenuFishingScreenSetDoIcon); -void menu_fishing_screen_set_do_icon_post(ModContext*, void* args, void*, void*) { - auto menuFishing = mods::arg(args, 0); - auto screen = menuFishing->mpIconScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A and B buttons on the insect collection screen -DEFINE_HOOK(&dMenu_Insect_c::screenSetDoIcon, MenuInsectScreenSetDoIcon); -void menu_insect_screen_set_do_icon_post(ModContext*, void* args, void*, void*) { - auto menuInsect = mods::arg(args, 0); - auto screen = menuInsect->mpIconScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A and B buttons on the letters screen -DEFINE_HOOK(&dMenu_Letter_c::screenSetDoIcon, MenuLetterScreenSetDoIcon); -void menu_letter_screen_set_do_icon_post(ModContext*, void* args, void*, void*) { - auto menuLetter = mods::arg(args, 0); - auto screen = menuLetter->mpIconScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A, B, and Z buttons on option screen -DEFINE_HOOK(&dMenu_Option_c::_create, MenuOptionCreate); -void menu_option_create_post(ModContext*, void* args, void*, void*) { - auto menuOption = mods::arg(args, 0); - auto screen = menuOption->mpScreenIcon; - auto backScreen = menuOption->mpBackScreen; - auto tvScreen = menuOption->mpTVScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); - recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('g_zbtn'), backScreen); - - // A Button on option's brightness check screen - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), tvScreen); -} - -// A and B buttons on the hidden skills screen -DEFINE_HOOK(&dMenu_Skill_c::screenSetDoIcon, MenuSkillScreenSetDoIcon); -void menu_skill_screen_set_do_icon_post(ModContext*, void* args, void*, void*) { - auto menuSkill = mods::arg(args, 0); - auto screen = menuSkill->mpIconScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A, B, and Z buttons on the field map screen -DEFINE_HOOK(&dMenu_Fmap_c::_create, MenuFMapCreate); -void menu_fmap_create_post(ModContext*, void* args, void*, void*) { - auto menuFMap = mods::arg(args, 0); - auto screen = menuFMap->mpDraw2DTop->mpTitleScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn1'), screen); - recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen); -} - -// A and B buttons on the dungeon map screen -DEFINE_HOOK_SYMBOL("dMenu_DmapBg_c::buttonIconScreenInit", void(dMenu_DmapBg_c*), MenuDMapButtonIconScreenInit); -void menu_dmap_button_icon_screen_init_post(ModContext*, void* args, void*, void*) { - auto menuDMap = mods::arg(args, 0); - auto screen = menuDMap->mButtonScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A button on the howling screen -DEFINE_HOOK(&dMsgObject_c::talkStartInit, MsgObjectTalkStartInit); -void msg_object_talk_start_init_post(ModContext*, void* args, void*, void*) { - auto msgObject = mods::arg(args, 0); - - // If textbox kind is howling - if (msgObject->mFukiKind == 17) { - auto screen = msgObject->mpScrnDraw->mpScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('abtn'), screen); - } -} - -// A button when on Epona -DEFINE_HOOK(&dMeterHakusha_c::_create, MeterHakushaCreate); -void meter_hakusha_create_post(ModContext*, void* args, void*, void*) { - auto meterHakusha = mods::arg(args, 0); - auto screen = meterHakusha->mpButtonScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen); -} - -// A, B, X, and Y button icons in text -DEFINE_HOOK(&COutFont_c::createPane, OutFontCreatePane); -void out_font_create_pane_post(ModContext*, void* args, void*, void*) { - auto outFont = mods::arg(args, 0); - auto paneArr = outFont->mpPane; - - auto maybeAButtonColor = get_config_var_color(get_cvars().aButtonColor); - if (maybeAButtonColor.has_value()) { - auto aButtonColor = maybeAButtonColor.value(); - paneArr[0]->setBlackWhite(JUtility::TColor(255, 255, 255, 0), - JUtility::TColor(aButtonColor.r, aButtonColor.g, aButtonColor.b, 255)); - } - - auto maybeBButtonColor = get_config_var_color(get_cvars().bButtonColor); - if (maybeBButtonColor.has_value()) { - auto bButtonColor = maybeBButtonColor.value(); - paneArr[1]->setBlackWhite(JUtility::TColor(255, 255, 255, 0), - JUtility::TColor(bButtonColor.r, bButtonColor.g, bButtonColor.b, 255)); - } - - // Condition copied from hooked function - auto xyBlack = JUtility::TColor(255, 255, 255, 0); - if (outFont->field_0x242 == 1) { - xyBlack = JUtility::TColor(0, 0, 0, 0); - } - - auto maybeXButtonColor = get_config_var_color(get_cvars().xButtonColor); - if (maybeXButtonColor.has_value()) { - auto xButtonColor = maybeXButtonColor.value(); - paneArr[5]->setBlackWhite( - xyBlack, JUtility::TColor(xButtonColor.r, xButtonColor.g, xButtonColor.b, 255)); - } - - auto maybeYButtonColor = get_config_var_color(get_cvars().yButtonColor); - if (maybeYButtonColor.has_value()) { - auto yButtonColor = maybeYButtonColor.value(); - paneArr[6]->setBlackWhite( - xyBlack, JUtility::TColor(yButtonColor.r, yButtonColor.g, yButtonColor.b, 255)); - } -} - -// Heart icon in text -DEFINE_HOOK(&COutFontSet_c::drawFont, OutFontSetDrawFont); -void out_font_set_draw_font_post(ModContext*, void* args, void*, void*) { - auto outFontSet = mods::arg(args, 0); - - auto maybeHeartColor = get_config_var_color(get_cvars().heartColor); - if (maybeHeartColor.has_value()) { - auto heartColor = maybeHeartColor.value(); - u32 heartColor_u32 = heartColor.r << 24 | heartColor.g << 16 | heartColor.b << 8 | 0xFF; - if (outFontSet->getType() == 0x1B) { // Heart icon type - outFontSet->mColor = heartColor_u32; - } - } -} - -// Heart Drop/Piece of Heart/Heart Container model color -DEFINE_HOOK(&daItemBase_c::CreateItemHeap, ItemBaseCreateItemHeap); -void item_base_create_item_heap_post(ModContext*, void* args, void*, void*) { - auto itemBase = mods::arg(args, 0); - if (itemBase == NULL) { - return; - } - auto itemNo = itemBase->m_itemNo; - if (itemNo == dItemNo_HEART_e || itemNo == dItemNo_UTAWA_HEART_e || - itemNo == dItemNo_KAKERA_HEART_e) - { - auto maybeHeartColor = get_config_var_color(get_cvars().heartColor); - if (maybeHeartColor.has_value()) { - auto heartColor = maybeHeartColor.value(); - auto heartColorS10 = GXColorS10(heartColor.r, heartColor.g, heartColor.b, heartColor.a); - - // Edit inner heart material color for Piece of Heart / Heart Container - if (itemNo == dItemNo_UTAWA_HEART_e || itemNo == dItemNo_KAKERA_HEART_e) { - *itemBase->mpModel->getModelData()->getMaterialNodePointer(3)->getTevKColor(1) = - heartColor; - *itemBase->mpModel->getModelData()->getMaterialNodePointer(3)->getTevColor(1) = - heartColorS10; - } - - const u8 heartColorRGB[3] = {heartColor.r, heartColor.g, heartColor.b}; - u8** cRegTable = - reinterpret_cast(&itemBase->mpBrkAnm->getBrkAnm()->mAnmCRegDataR); - u8** kRegTable = - reinterpret_cast(&itemBase->mpBrkAnm->getBrkAnm()->mAnmKRegDataR); - - for (int i = 0; i < 3; i++) { - u8* cReg = cRegTable[i]; - u8* kReg = kRegTable[i]; - - auto curColor = heartColorRGB[i]; - - // Set heart drop inner heart color - cReg[0x3] = curColor; - cReg[0xB] = curColor; - - // Set heart drop outer heart color - kReg[0x3] = curColor; - kReg[0xB] = curColor; - - if (itemNo == dItemNo_KAKERA_HEART_e) { - cReg[0x13] = curColor; - cReg[0x1B] = curColor; - kReg[0x13] = curColor; - kReg[0x1B] = curColor; - } - if (itemNo == dItemNo_UTAWA_HEART_e) { - cReg[0x13] = curColor; - kReg[0x13] = curColor; - kReg[0x1B] = curColor; - kReg[0x23] = curColor; - kReg[0x2B] = curColor; - } - } - } - } -} - -// Midna Hair Color -DEFINE_HOOK(&daMidna_c::create, MidnaCreate); -void midna_create_post(ModContext*, void* args, void* retval, void*) { - auto step = reinterpret_cast(retval); - // Don't set colors if midna isn't done loading - if (*step != cPhs_COMPLEATE_e) { - return; - } - - auto midna = mods::arg(args, 0); - midna->field_0x6e0 = g_currentMidnaHairColors.normalColor; - if (dKy_darkworld_check()) { - midna->field_0x6e8 = g_currentMidnaHairColors.normalKColor; - midna->field_0x6ec = g_currentMidnaHairColors.normalKColor2; - } else { - midna->field_0x6e8 = g_currentMidnaHairColors.lNormalKColor; - midna->field_0x6ec = g_currentMidnaHairColors.lNormalKColor2; - } -} - -// Override Midna Hair Color Part 2 -DEFINE_HOOK(&daMidna_c::setBodyPartMatrix, MidnaSetBodyPartMatrix); - -static GXColorS10 midnaField0x6e0{}; -static GXColor midnaField0x6e8{}; -static GXColor midnaField0x6ec{}; - -// Copy the original values before setBodyPartMatrix runs -HookAction midna_set_body_part_matrix_pre(ModContext*, void* args, void* retval, void* userdata) { - auto midna = mods::arg(args, 0); - - midnaField0x6e0 = midna->field_0x6e0; - midnaField0x6e8 = midna->field_0x6e8; - midnaField0x6ec = midna->field_0x6ec; - - return HOOK_CONTINUE; -} - -void midna_set_body_part_matrix_post(ModContext*, void* args, void* retval, void* userdata) { - auto midna = mods::arg(args, 0); - if (midna->mpHairhandBmd != NULL) { - // Restore the original values from before setBodyPartMatrix ran (this undoes the chase) - midna->field_0x6e0 = midnaField0x6e0; - midna->field_0x6e8 = midnaField0x6e8; - midna->field_0x6ec = midnaField0x6ec; - - // Statement copied from inside function to determine colors - bool bigColors = - midna->checkStateFlg0(daMidna_c::FLG0_UNK_10000000) || - midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_HAIR].mResID || - midna->mBckHeap[2].getIdx() == - daMidna_c::m_anmDataTable[daMidna_c::ANM_S_TAKES].mResID || - midna->mBckHeap[2].getIdx() == - daMidna_c::m_anmDataTable[daMidna_c::ANM_S_WAITS].mResID || - midna->mBckHeap[2].getIdx() == - daMidna_c::m_anmDataTable[daMidna_c::ANM_S_PACKAWAY].mResID || - midna->mBckHeap[2].getIdx() == - daMidna_c::m_anmDataTable[daMidna_c::ANM_GRABST].mResID || - midna->checkEndResetStateFlg0(daMidna_c::ERFLG0_UNK_40) || - dComIfGp_checkPlayerStatus1(0, 0x800000); - - GXColorS10 color{}; - GXColor kcolor1{}; - GXColor kcolor2{}; - - // Set our own colors - if (bigColors) { - kcolor1 = g_currentMidnaHairColors.bigKColor; - if (dKy_darkworld_check()) { - color = g_currentMidnaHairColors.bigColor; - kcolor2 = g_currentMidnaHairColors.normalKColor2; - } else { - color = g_currentMidnaHairColors.lBigColor; - kcolor2 = g_currentMidnaHairColors.lBigKColor2; - } - } else { - color = g_currentMidnaHairColors.normalColor; - if (dKy_darkworld_check()) { - kcolor1 = g_currentMidnaHairColors.normalKColor; - kcolor2 = g_currentMidnaHairColors.normalKColor2; - } else { - kcolor1 = g_currentMidnaHairColors.lNormalKColor; - kcolor2 = g_currentMidnaHairColors.lNormalKColor2; - } - } - - // Reapply the chase that happens in the function - cLib_chaseS(&midna->field_0x6e0.r, color.r, 10); - cLib_chaseS(&midna->field_0x6e0.g, color.g, 10); - cLib_chaseS(&midna->field_0x6e0.b, color.b, 10); - cLib_chaseUC(&midna->field_0x6e8.r, kcolor1.r, 10); - cLib_chaseUC(&midna->field_0x6e8.g, kcolor1.g, 10); - cLib_chaseUC(&midna->field_0x6e8.b, kcolor1.b, 10); - cLib_chaseUC(&midna->field_0x6ec.r, kcolor2.r, 10); - cLib_chaseUC(&midna->field_0x6ec.g, kcolor2.g, 10); - cLib_chaseUC(&midna->field_0x6ec.b, kcolor2.b, 10); - } -} - -// Midna Charge Ring Color -DEFINE_HOOK(&daAlink_c::setWolfLockDomeModel, SetWolfLockDomeModel); -void wolf_lock_dome_model_post(ModContext*, void*, void*, void*) { - auto domeRingColorStr = get_str_option(get_cvars().midnaChargeRingColor, ""); - if (is_valid_hex_color_str(domeRingColorStr)) { - auto domeRingColor = hex_color_str_to_gx_color(domeRingColorStr); - const u8 domeWave1RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b}; - const u8 domeWave2RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b}; - u8** chromaRegisterTable = - reinterpret_cast(&daAlink_getAlinkActorClass()->field_0x0724->mAnmCRegDataR); - - for (int i = 0; i < 3; i++) { - u8* currentTable = chromaRegisterTable[i]; - const u8 currentWave1Color = domeWave1RGBA[i]; - const u8 currentWave2Color = domeWave2RGBA[i]; - const u8 currentBaseColor = (currentWave1Color + currentWave2Color) / 2; - - currentTable[0x3] = currentBaseColor; // Set Alpha for the ring base - currentTable[0x13] = currentWave1Color; // Set Alpha for ring wave 1 - currentTable[0x23] = currentWave2Color; // Set Alpha for ring wave 2 - currentTable[0xB] = currentBaseColor; // Set Alpha for darkworld ring base - currentTable[0x1B] = currentWave1Color; // Set Alpha for darkworld ring wave 1 - currentTable[0x2B] = currentWave2Color; // Set Alpha for darkworld ring wave 2 - } - } -} - -#define ADD_POST_HOOK(defined_hook, function, original) \ - result = mods::hook::add_post(function); \ - if (result != MOD_OK) { \ - mods::log::debug( \ - "failed to add post hook to" #original ", Result {}", static_cast(result)); \ - return result; \ - } - -#define ADD_PRE_HOOK(defined_hook, function, original) \ - result = mods::hook::add_pre(function); \ - if (result != MOD_OK) { \ - mods::log::debug( \ - "failed to add pre hook to" #original ", Result {}", static_cast(result)); \ - return result; \ - } - -ModResult add_all_hooks() { - ModResult result{}; - - // Hooks for lantern glow - ADD_POST_HOOK(WolfEyeLightSet, wolf_eye_light_set_post, dKy_WolfEyeLight_set) - ADD_POST_HOOK(PreKandelaarDraw, pre_kandelaar_draw_post, daAlink_c::preKandelaarDraw) - - // Hooks for lantern meter color - ADD_PRE_HOOK(CPaneMgrSetBlackWhite, cpane_mgr_set_black_white_pre, CPaneMgr::setBlackWhite) - ADD_POST_HOOK( - KanteraIconSetNowGauge, kantera_icon_set_now_gauge_post, dKantera_icon_c::setNowGauge) - - // Hook for midna charge ring - ADD_POST_HOOK(SetWolfLockDomeModel, wolf_lock_dome_model_post, daAlink_c::setWolfLockDomeModel) - - // Hook for light sword glow - ADD_POST_HOOK(SetLightningSwordEffect, set_lightning_sword_effect_post, - daAlink_c::setLightningSwordEffect) - - // Hooks for Midna Hair Color - ADD_POST_HOOK(MidnaCreate, midna_create_post, daMidna_c::create) - ADD_PRE_HOOK( - MidnaSetBodyPartMatrix, midna_set_body_part_matrix_pre, daMidna_c::setBodyPartMatrix) - ADD_POST_HOOK( - MidnaSetBodyPartMatrix, midna_set_body_part_matrix_post, daMidna_c::setBodyPartMatrix) - - // Hooks for UI colors - ADD_POST_HOOK(dMeter2Init, d_meter_2_init_post, dMeter2Draw_c::init) - ADD_POST_HOOK(FileInfoSetHeartCount, file_info_set_heart_count_post, dFile_info_c::setHeartCnt) - ADD_POST_HOOK(ScreenInitButton, screen_init_button_post, dMeterButton_c::screenInitButton) - ADD_POST_HOOK(MenuSaveScreenSet, menu_save_screen_set_post, dMenu_save_c::screenSet) - ADD_POST_HOOK(FileSelectScreenSet, file_select_screen_set_post, dFile_select_c::screenSet) - ADD_POST_HOOK(BrightCheckScreenSet, bright_check_screen_set_post, dBrightCheck_c::screenSet) - ADD_POST_HOOK(MenuRingCreate, menu_ring_create_post, dMenu_Ring_c::_create) - ADD_POST_HOOK(MenuCollect2DCreate, menu_collect_2D_create_post, dMenu_Collect2D_c::_create) - ADD_POST_HOOK(MenuFishingScreenSetDoIcon, menu_fishing_screen_set_do_icon_post, - dMenu_Fishing_c::screenSetDoIcon) - ADD_POST_HOOK(MenuInsectScreenSetDoIcon, menu_insect_screen_set_do_icon_post, - dMenu_Insect_c::screenSetDoIcon) - ADD_POST_HOOK(MenuLetterScreenSetDoIcon, menu_letter_screen_set_do_icon_post, - dMenu_Letter_c::screenSetDoIcon) - ADD_POST_HOOK(MenuOptionCreate, menu_option_create_post, dMenu_Option_c::_create) - ADD_POST_HOOK(MenuSkillScreenSetDoIcon, menu_skill_screen_set_do_icon_post, - dMenu_Skill_c::screenSetDoIcon) - ADD_POST_HOOK(OutFontCreatePane, out_font_create_pane_post, COutFont_c::createPane) - ADD_POST_HOOK(OutFontSetDrawFont, out_font_set_draw_font_post, COutFontSet_c::drawFont) - ADD_POST_HOOK(MenuFMapCreate, menu_fmap_create_post, dMenu_Fmap_c::_create) - ADD_POST_HOOK(MenuDMapButtonIconScreenInit, menu_dmap_button_icon_screen_init_post, - dMenu_DmapBg_c::buttonIconScreenInit) - ADD_POST_HOOK( - MsgObjectTalkStartInit, msg_object_talk_start_init_post, dMsgObject_c::talkStartInit) - ADD_POST_HOOK(MeterHakushaCreate, meter_hakusha_create_post, dMeterHakusha_c::_create) - - // Heart Model Color - ADD_POST_HOOK( - ItemBaseCreateItemHeap, item_base_create_item_heap_post, daItemBase_c::CreateItemHeap) - - return MOD_OK; -} - -#define UNINSTALL_HOOK(defined_hook) mods::hook::uninstall(svc_hook); - -ModResult remove_all_hooks() { - UNINSTALL_HOOK(WolfEyeLightSet) - UNINSTALL_HOOK(PreKandelaarDraw) - UNINSTALL_HOOK(CPaneMgrSetBlackWhite) - UNINSTALL_HOOK(KanteraIconSetNowGauge) - UNINSTALL_HOOK(SetWolfLockDomeModel) - UNINSTALL_HOOK(SetLightningSwordEffect) - UNINSTALL_HOOK(MidnaCreate) - UNINSTALL_HOOK(MidnaSetBodyPartMatrix) - UNINSTALL_HOOK(MidnaSetBodyPartMatrix) - UNINSTALL_HOOK(dMeter2Init) - UNINSTALL_HOOK(FileInfoSetHeartCount) - UNINSTALL_HOOK(ScreenInitButton) - UNINSTALL_HOOK(MenuSaveScreenSet) - UNINSTALL_HOOK(FileSelectScreenSet) - UNINSTALL_HOOK(BrightCheckScreenSet) - UNINSTALL_HOOK(MenuRingCreate) - UNINSTALL_HOOK(MenuCollect2DCreate) - UNINSTALL_HOOK(MenuFishingScreenSetDoIcon) - UNINSTALL_HOOK(MenuInsectScreenSetDoIcon) - UNINSTALL_HOOK(MenuLetterScreenSetDoIcon) - UNINSTALL_HOOK(MenuOptionCreate) - UNINSTALL_HOOK(MenuSkillScreenSetDoIcon) - UNINSTALL_HOOK(OutFontCreatePane) - UNINSTALL_HOOK(OutFontSetDrawFont) - UNINSTALL_HOOK(MenuFMapCreate) - UNINSTALL_HOOK(MenuDMapButtonIconScreenInit) - UNINSTALL_HOOK(MsgObjectTalkStartInit) - UNINSTALL_HOOK(MeterHakushaCreate) - UNINSTALL_HOOK(ItemBaseCreateItemHeap) - return MOD_OK; -} diff --git a/mods/basic_cosmetics_mod/src/hooks.hpp b/mods/basic_cosmetics_mod/src/hooks.hpp deleted file mode 100644 index 62bb77cde9..0000000000 --- a/mods/basic_cosmetics_mod/src/hooks.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "mods/service.hpp" - -ModResult add_all_hooks(); - -ModResult remove_all_hooks(); \ No newline at end of file diff --git a/mods/basic_cosmetics_mod/src/midna_hair_color.cpp b/mods/basic_cosmetics_mod/src/midna_hair_color.cpp deleted file mode 100644 index 905a4345ff..0000000000 --- a/mods/basic_cosmetics_mod/src/midna_hair_color.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "midna_hair_color.hpp" -#include "mod.hpp" - -#include "mods/svc/log.hpp" - -#include - -namespace { -constexpr std::array, 10> kMidnaHairColors = {{ - // Default - {{ - /*l_lNormalKColor*/ /*l_normalKColor*/ /*l_normalColor*/ - {0xFF, 0xDC, 0x00}, {0xB4, 0x87, 0x00}, {0x50, 0x00, 0x00}, - /*l_bigKColor*/ /*l_lBigColor*/ /*l_bigColor*/ - {0x1E, 0x00, 0x00}, {0xFF, 0x78, 0x00}, {0xFF, 0x64, 0x78}, - /*l_lNormalKColor2*//*l_normalKColor2*/ /*l_lBigKColor2*/ - {0x00, 0xC3, 0xEB}, {0x00, 0xC3, 0xC3}, {0xAA, 0xFF, 0xC3} - }}, - // Pink - {{ - {0xF5, 0xCF, 0xF3}, {0xAD, 0x7F, 0x7F}, {0x1B, 0x00, 0x20}, - {0x3C, 0x02, 0x58}, {0xE3, 0x72, 0xF2}, {0xE3, 0x5F, 0xF8}, - {0xDD, 0x00, 0xEB}, {0xDD, 0x00, 0xC3}, {0xF6, 0x4C, 0xFF} - }}, - // Red - {{ - {0xE4, 0x65, 0x41}, {0xA1, 0x3E, 0x22}, {0x21, 0x00, 0x00}, - {0x4F, 0x02, 0x01}, {0xF0, 0x3A, 0x25}, {0xF0, 0x30, 0x8C}, - {0xEB, 0x00, 0x00}, {0xEB, 0x00, 0x00}, {0xFF, 0x4F, 0x3A} - }}, - // Yellow - {{ - {0x91, 0x83, 0x0E}, {0x66, 0x50, 0x07}, {0x0E, 0x0B, 0x00}, - {0x24, 0x25, 0x02}, {0xCB, 0xB7, 0x00}, {0xCB, 0x99, 0x78}, - {0xEB, 0xDE, 0x00}, {0xEB, 0xDE, 0x00}, {0xFF, 0xF8, 0xBF} - }}, - // Green - {{ - {0x35, 0x79, 0x53}, {0x25, 0x4A, 0x2B}, {0x00, 0x0E, 0x05}, - {0x0A, 0x29, 0x10}, {0x00, 0xB6, 0x6F}, {0x00, 0x98, 0xB3}, - {0x1F, 0xEB, 0x00}, {0x1F, 0xEB, 0x00}, {0x9A, 0xFF, 0x81} - }}, - // Blue - {{ - {0x00, 0x72, 0xFF}, {0x00, 0x46, 0x85}, {0x00, 0x08, 0x28}, - {0x16, 0x1B, 0x5D}, {0x00, 0x60, 0xFF}, {0x00, 0x50, 0xFF}, - {0x00, 0x48, 0xEB}, {0x00, 0x48, 0xC3}, {0x3A, 0x66, 0xFF} - }}, - // Purple - {{ - {0x6F, 0x34, 0xFF}, {0x4E, 0x20, 0x85}, {0x0D, 0x00, 0x34}, - {0x15, 0x08, 0x79}, {0x62, 0x00, 0xFF}, {0x62, 0x00, 0xFF}, - {0x7B, 0x00, 0xEB}, {0x7B, 0x00, 0xC3}, {0x94, 0x3E, 0xFF} - }}, - // Brown - {{ - {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x1A, 0x05, 0x00}, - {0x3C, 0x19, 0x0E}, {0x3F, 0x1D, 0x0B}, {0x3F, 0x18, 0x7E}, - {0x3F, 0x1D, 0x0B}, {0x3F, 0x1D, 0x09}, {0x59, 0x32, 0x1E} - }}, - // White - {{ - {0xF0, 0xF1, 0xF1}, {0xA9, 0x94, 0x7E}, {0x09, 0x0B, 0x0C}, - {0x22, 0x24, 0x24}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xD5, 0xFF}, - {0xEA, 0xEA, 0xEA}, {0xEA, 0xEA, 0xC2}, {0xF3, 0xF3, 0xF3} - }}, - // Black - {{ - {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x0B, 0x0B, 0x0B}, - {0x23, 0x23, 0x23}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x78}, - {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00} - }} -}}; - -size_t get_midna_hair_color_index(ConfigVarHandle handle) { - const auto optionIndex = get_int_option(handle, 0); - if (optionIndex < 0 || optionIndex >= static_cast(kMidnaHairColors.size())) { - return 0; - } - return static_cast(optionIndex); -} -} - -MidnaHairColors g_currentMidnaHairColors = { - .normalColor = {0x50, 0x00, 0x00, 0x00}, - .normalKColor = {0xB4, 0x87, 0x00, 0x00}, - .normalKColor2 = {0x00, 0xC3, 0xC3, 0x00}, - .bigColor = {0xFF, 0x64, 0x78, 0x00}, - .bigKColor = {0x1E, 0x00, 0x00, 0x00}, - .lNormalKColor = {0xFF, 0xDC, 0x00, 0x00}, - .lNormalKColor2 = {0x00, 0xC3, 0xEB, 0x00}, - .lBigColor = {0xFF, 0x78, 0x00, 0x00}, - .lBigKColor2 = {0xAA, 0xFF, 0xC3, 0x00}, -}; - -void set_all_midna_hair_colors() { - auto& g_cvars = get_cvars(); - auto hairBaseColor = get_midna_hair_color_index(g_cvars.midnaHairBaseColor); - auto hairTipsColor = get_midna_hair_color_index(g_cvars.midnaHairTipsColor); - - // Colors we have to convert to GXColorS10 - auto& normalColor = kMidnaHairColors.at(hairBaseColor)[2]; - auto& bigColor = kMidnaHairColors.at(hairBaseColor)[5]; - auto& lBigColor = kMidnaHairColors.at(hairBaseColor)[4]; - - g_currentMidnaHairColors.normalColor = GXColorS10{normalColor.r, normalColor.g, normalColor.b}; - g_currentMidnaHairColors.normalKColor = kMidnaHairColors.at(hairBaseColor)[1]; - g_currentMidnaHairColors.normalKColor2 = kMidnaHairColors.at(hairTipsColor)[7]; - g_currentMidnaHairColors.bigColor = GXColorS10{bigColor.r, bigColor.g, bigColor.b}; - g_currentMidnaHairColors.bigKColor = kMidnaHairColors.at(hairBaseColor)[3]; - g_currentMidnaHairColors.lNormalKColor = kMidnaHairColors.at(hairBaseColor)[0]; - g_currentMidnaHairColors.lNormalKColor2 = kMidnaHairColors.at(hairTipsColor)[6]; - g_currentMidnaHairColors.lBigColor = GXColorS10{lBigColor.r, lBigColor.g, lBigColor.b}; - g_currentMidnaHairColors.lBigKColor2 = kMidnaHairColors.at(hairTipsColor)[8]; -} diff --git a/mods/basic_cosmetics_mod/src/midna_hair_color.hpp b/mods/basic_cosmetics_mod/src/midna_hair_color.hpp deleted file mode 100644 index ad08f7c8c6..0000000000 --- a/mods/basic_cosmetics_mod/src/midna_hair_color.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -struct MidnaHairColors { - GXColorS10 normalColor; - GXColor normalKColor; - GXColor normalKColor2; - GXColorS10 bigColor; - GXColor bigKColor; - GXColor lNormalKColor; - GXColor lNormalKColor2; - GXColorS10 lBigColor; - GXColor lBigKColor2; -}; - -extern MidnaHairColors g_currentMidnaHairColors; - -void set_all_midna_hair_colors(); diff --git a/mods/basic_cosmetics_mod/src/mod.cpp b/mods/basic_cosmetics_mod/src/mod.cpp deleted file mode 100644 index 89a321da9c..0000000000 --- a/mods/basic_cosmetics_mod/src/mod.cpp +++ /dev/null @@ -1,653 +0,0 @@ -#include "mod.hpp" -#include "color_utils.hpp" -#include "hooks.hpp" -#include "midna_hair_color.hpp" -#include "texture_utils.hpp" - -#include "mods/service.hpp" -#include "mods/svc/config.h" -#include "mods/svc/hook.hpp" -#include "mods/svc/log.h" -#include "mods/svc/log.hpp" -#include "mods/svc/ui.h" - -#include "d/d_com_inf_game.h" - -#include - -#include -#include -#include - -DEFINE_MOD(); -IMPORT_SERVICE(LogService, svc_log); -IMPORT_SERVICE(ConfigService, svc_config); -IMPORT_SERVICE(HookService, svc_hook); -IMPORT_SERVICE(TextureService, svc_texture); -IMPORT_SERVICE(UiService, svc_ui); - -static cvars g_cvars; - -cvars& get_cvars() { - return g_cvars; -} - -std::string get_str_option(ConfigVarHandle handle, const std::string& fallback) { - std::string value{}; - size_t outLength{}; - svc_config->get_string(mod_ctx, handle, NULL, 0, &outLength); - value.resize(outLength); - if (handle == 0 || svc_config->get_string( - mod_ctx, handle, value.data(), value.size() + 1, &outLength) != MOD_OK) - { - return fallback; - } - return value; -} - -int64_t get_int_option(ConfigVarHandle handle, int64_t fallback) { - int64_t value = fallback; - if (handle == 0 || svc_config->get_int(mod_ctx, handle, &value) != MOD_OK) { - return fallback; - } - return value; -} - -// Helper for getting configVar color -std::optional get_config_var_color(ConfigVarHandle handle, bool allowRainbow) { - auto colorStr = get_str_option(handle, ""); - // Convert to lowercase - for (auto& c : colorStr) { - c = static_cast(std::tolower(static_cast(c))); - } - if (colorStr == "rainbow" && allowRainbow) { - auto color = get_rainbow_rgb(127.5f); - color.r /= 2; - color.g /= 2; - color.b /= 2; - return color; - } - if (is_valid_hex_color_str(colorStr)) { - return hex_color_str_to_gx_color(colorStr); - } - return std::nullopt; -} - -namespace { -UiWindowHandle g_cosmeticsWindow = 0; -bool g_loadedAllBaseTextures = false; -constexpr uint32_t kTextureLoadRetryFrames = 30; -uint32_t g_textureLoadRetryCountdown = 0; - -constexpr const char* kOverlayPresets[] = { - "ab706e", - "6382a0", - "94749a", - "ec8644", - "b9ab00", - "ec9fc8", - "505154", - "f8f7f4", - "91723e", -}; - -constexpr const char* kLightPresets[] = { - "ff0000", - "f68821", - "f6f321", - "00ff00", - "0000ff", - "8000ff", - "a0a0a0", - "30d0d0", -}; - -constexpr const char* kRainbowLightPresets[] = { - "rainbow", - "ff0000", - "f68821", - "f6f321", - "00ff00", - "0000ff", - "8000ff", - "a0a0a0", -}; - -constexpr const char* kAButtonPresets[] = { - "ff0000", - "ff5000", - "ffaf00", - "0080ff", - "0000ff", - "8000ff", - "5555ff", - "ff20ff", -}; - -constexpr const char* kBButtonPresets[] = { - "ffff40", - "ffa0ff", - "00e87b", - "00aaff", - "6078ff", - "000000", - "00f3ff", -}; - -constexpr const char* kXyButtonPresets[] = { - "ff0000", - "ff8200", - "f7df00", - "70ff00", - "00bd11", - "0000ff", - "800088", - "000000", - "ff00aa", - "00ffff", -}; - -constexpr const char* kZButtonPresets[] = { - "ff0000", - "ff8200", - "f7df00", - "70ff00", - "00bd11", - "800088", - "000000", - "00ffff", -}; - -constexpr const char* kChargeRingPresets[] = { - "ff9f9f", - "ff0000", - "ffff00", - "00ff00", - "0000ff", - "ff00ff", - "331900", - "feffff", - "000000", -}; - -ModResult register_str_option( - const char* name, const char* defaultValue, ConfigVarHandle& outHandle, ModError* error) { - ConfigVarDesc cvarDesc = CONFIG_VAR_DESC_INIT; - cvarDesc.name = name; - cvarDesc.type = CONFIG_VAR_STRING; - cvarDesc.default_string = defaultValue; - if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) { - return mods::set_error(error, MOD_ERROR, "failed to register cosmetics option"); - } - return MOD_OK; -} - -ModResult register_int_option( - const char* name, int64_t defaultValue, ConfigVarHandle& outHandle, ModError* error) { - ConfigVarDesc cvarDesc = CONFIG_VAR_DESC_INIT; - cvarDesc.name = name; - cvarDesc.type = CONFIG_VAR_INT; - cvarDesc.default_int = defaultValue; - if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) { - return mods::set_error(error, MOD_ERROR, "failed to register cosmetics option"); - } - return MOD_OK; -} - -void add_control(UiElementHandle pane, const UiControlDesc& desc) { - auto result = svc_ui->pane_add_control(mod_ctx, pane, &desc, nullptr); - if (result != MOD_OK) { - mods::log::debug("pane_add_control failed {}", static_cast(result)); - } -} - -template -void add_cosmetic_option( - UiElementHandle pane, ConfigVarHandle cvar, const char* name, const char* const (&presets)[N]) { - UiControlDesc control = UI_CONTROL_DESC_INIT; - control.kind = UI_CONTROL_COLOR; - control.label = name; - control.binding = UI_BINDING_CONFIG_VAR; - control.config_var = cvar; - control.color_presets = presets; - control.color_preset_count = N; - add_control(pane, control); -} - -void add_midna_hair_option(UiElementHandle left, ConfigVarHandle cvar, const std::string& name) { - static const char* kMidnaHairOptions[] = { - "Default", "Pink", "Red", "Yellow", "Green", "Blue", "Purple", "Brown", "White", "Black"}; - UiControlDesc control = UI_CONTROL_DESC_INIT; - control.kind = UI_CONTROL_SELECT; - control.label = name.c_str(); - control.help_rml = "Choose Midna's hair color."; - control.binding = UI_BINDING_CONFIG_VAR; - control.config_var = cvar; - control.options = kMidnaHairOptions; - control.option_count = 10; - add_control(left, control); -} - -void add_group( - UiElementHandle groups, UiElementHandle colors, const char* label, UiGroupBuildFn build) { - UiGroupDesc group = UI_GROUP_DESC_INIT; - group.label = label; - group.build = build; - const auto result = svc_ui->pane_add_group(mod_ctx, groups, colors, &group, nullptr); - if (result != MOD_OK) { - mods::log::debug("pane_add_group failed {}", static_cast(result)); - } -} - -ModResult build_hero_tunic_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Hero's Tunic"); - add_cosmetic_option(pane, g_cvars.herosTunicCapColor, "Cap", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.herosTunicTorsoColor, "Body", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.herosTunicSkirtColor, "Skirt", kOverlayPresets); - return MOD_OK; -} - -ModResult build_zora_armor_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Zora Armor"); - add_cosmetic_option(pane, g_cvars.zoraArmorCapColor, "Cap", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.zoraArmorHelmetColor, "Helmet", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.zoraArmorTorsoColor, "Torso", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.zoraArmorScalesColor, "Scales", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.zoraArmorFlippersColor, "Flippers", kOverlayPresets); - return MOD_OK; -} - -ModResult build_sword_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Swords"); - add_cosmetic_option(pane, g_cvars.woodenSwordColor, "Wooden Sword", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.ordonSwordBladeColor, "Ordon Blade", kLightPresets); - add_cosmetic_option(pane, g_cvars.ordonSwordHandleColor, "Ordon Handle", kLightPresets); - add_cosmetic_option(pane, g_cvars.msBladeColor, "Master Sword Blade", kLightPresets); - add_cosmetic_option(pane, g_cvars.msHandleColor, "Master Sword Handle", kLightPresets); - add_cosmetic_option( - pane, g_cvars.lightSwordGlowColor, "Light Sword Glow", kRainbowLightPresets); - return MOD_OK; -} - -ModResult build_equipment_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Equipment"); - add_cosmetic_option(pane, g_cvars.lanternGlowColor, "Lantern Glow", kRainbowLightPresets); - add_cosmetic_option(pane, g_cvars.boomerangColor, "Gale Boomerang", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.ironBootsColor, "Iron Boots", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.spinnerColor, "Spinner", kOverlayPresets); - return MOD_OK; -} - -ModResult build_button_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Buttons"); - add_cosmetic_option(pane, g_cvars.aButtonColor, "A Button", kAButtonPresets); - add_cosmetic_option(pane, g_cvars.bButtonColor, "B Button", kBButtonPresets); - add_cosmetic_option(pane, g_cvars.xButtonColor, "X Button", kXyButtonPresets); - add_cosmetic_option(pane, g_cvars.yButtonColor, "Y Button", kXyButtonPresets); - add_cosmetic_option(pane, g_cvars.zButtonColor, "Z Button", kZButtonPresets); - return MOD_OK; -} - -ModResult build_hud_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "HUD"); - add_cosmetic_option(pane, g_cvars.heartColor, "Hearts", kBButtonPresets); - return MOD_OK; -} - -ModResult build_link_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Link"); - add_cosmetic_option(pane, g_cvars.linkHairColor, "Hair", kOverlayPresets); - return MOD_OK; -} - -ModResult build_midna_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Midna"); - add_cosmetic_option(pane, g_cvars.midnaChargeRingColor, "Charge Ring", kChargeRingPresets); - return MOD_OK; -} - -ModResult build_companion_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Companions"); - add_cosmetic_option(pane, g_cvars.wolfLinkColor, "Wolf Link", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.eponaColor, "Epona", kOverlayPresets); - return MOD_OK; -} - -ModResult build_equipment_colors_tab( - ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, left, "Color Groups"); - add_group(left, right, "Hero's Tunic", build_hero_tunic_colors); - add_group(left, right, "Zora Armor", build_zora_armor_colors); - add_group(left, right, "Swords", build_sword_colors); - add_group(left, right, "Equipment", build_equipment_colors); - - return MOD_OK; -} - -ModResult build_ui_colors_tab( - ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, left, "Color Groups"); - add_group(left, right, "Buttons", build_button_colors); - add_group(left, right, "HUD", build_hud_colors); - - return MOD_OK; -} - -ModResult build_misc_colors_tab( - ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, left, "Hair Presets"); - add_midna_hair_option(left, g_cvars.midnaHairBaseColor, "Midna's Hair Base Color"); - add_midna_hair_option(left, g_cvars.midnaHairTipsColor, "Midna's Hair Tips Color"); - svc_ui->pane_add_section(mod_ctx, left, "Color Groups"); - add_group(left, right, "Link", build_link_colors); - add_group(left, right, "Midna", build_midna_colors); - add_group(left, right, "Companions", build_companion_colors); - - return MOD_OK; -} - -void on_cosmetics_menu_window_closed(ModContext*, UiWindowHandle, void*) { - g_cosmeticsWindow = 0; -} - -void on_open_cosmetics_menu(ModContext*, void*) { - if (g_cosmeticsWindow != 0) { - return; - } - UiTabDesc tabs[] = {UI_TAB_DESC_INIT, UI_TAB_DESC_INIT, UI_TAB_DESC_INIT}; - tabs[0].title = "Equipment"; - tabs[0].build = build_equipment_colors_tab; - tabs[1].title = "Interface"; - tabs[1].build = build_ui_colors_tab; - tabs[2].title = "Characters"; - tabs[2].build = build_misc_colors_tab; - UiWindowDesc desc = UI_WINDOW_DESC_INIT; - desc.tabs = tabs; - desc.tab_count = ARRAY_SIZE(tabs); - desc.on_closed = on_cosmetics_menu_window_closed; - if (svc_ui->window_push(mod_ctx, &desc, &g_cosmeticsWindow) != MOD_OK) { - svc_log->error(mod_ctx, "failed to open basic cosmetics window"); - } -} - -ModResult build_panel(ModContext*, UiElementHandle panel, void*, ModError*) { - UiControlDesc control = UI_CONTROL_DESC_INIT; - control.kind = UI_CONTROL_GROUP; - control.label = "Open Cosmetics Menu"; - control.on_pressed = on_open_cosmetics_menu; - add_control(panel, control); - return MOD_OK; -} -} // namespace - -void load_base_texture_data() { - // Go through each texture we can recolor and attempt to load and store - // the texture data for future recoloring - for (auto& replacements : get_texture_replacements() | std::views::values) { - for (auto& replacement : replacements) { - // If we've already loaded the texture data, don't load it again - if (replacement.loadedTextureData) { - continue; - } - - // Avoid noisy resource lookups until the archive has finished loading. - auto* resInfo = dComIfG_getObjectResInfo(replacement.arc); - if (resInfo == nullptr || resInfo->getArchive() == nullptr) { - continue; - } - - // Try to get the model this texture is part of. If we can't get it, try again later - auto model = static_cast( - dComIfG_getObjectRes(replacement.arc, replacement.modelFileName)); - if (model == nullptr) { - continue; - } - - J3DTexture* tex = model->getTexture(); - JUTNameTab* nametable = model->getTextureName(); - if (tex != nullptr && nametable != nullptr) { - for (u16 i = 0; i < tex->getNum(); i++) { - const char* texName = nametable->getName(i); - if (texName != nullptr && std::strcmp(texName, replacement.textureName) == 0) { - // Once we've found the texture, set all our TextureKey and TextureData - // fields that we can set right now. - auto imageHeader = tex->getResTIMG(i); - auto& key = replacement.key; - auto& data = replacement.data; - key.kind = TEXTURE_KEY_SOURCE; - key.has_tlut = imageHeader->numColors > 0; - key.width = imageHeader->width; - key.height = imageHeader->height; - key.gx_format = imageHeader->format; - - // Currently, no replaced textures have a tlut - key.tlut_hash = replacement.tlutHash; - - // Calculate the size of the image data - const uint32_t mipCount = - imageHeader->mipmapEnabled ? - std::max(imageHeader->mipmapCount, 1) : - 1; - auto size = get_image_data_size( - imageHeader->format, imageHeader->width, imageHeader->height, mipCount); - replacement.baseTextureData.resize(size); - std::memcpy( - replacement.baseTextureData.data(), tex->getImgDataPtr(i), size); - - // Source texture keys hash only the base mip level. - const auto baseMipSize = get_image_data_size( - imageHeader->format, imageHeader->width, imageHeader->height, 1); - auto textureHash = - XXH64(replacement.baseTextureData.data(), baseMipSize, 0); - replacement.key.texture_hash = textureHash; - - mods::log::debug("Loaded base texture data for {}. size: {:X} hash: {:X}", - replacement.textureName, size, textureHash); - replacement.loadedTextureData = true; - - data.width = imageHeader->width; - data.height = imageHeader->height; - data.mip_count = mipCount; - data.size = size; - data.gx_format = imageHeader->format; - - break; - } - } - } - } - } - - // If we've loaded all base textures for recoloring, then we don't need to call this function - // again - g_loadedAllBaseTextures = std::ranges::all_of( - get_texture_replacements() | std::views::values, [](auto& replacementList) { - return std::ranges::all_of( - replacementList, [](const TextureReplacementData& replacement) { - return replacement.loadedTextureData; - }); - }); -} - -ModResult check_and_set_recolored_textures() { - for (auto& [configVar, replacements] : get_texture_replacements()) { - // If the configvar hasn't been set, don't continue - if (configVar == 0) { - continue; - } - - auto maybeColor = get_config_var_color(configVar); - if (!maybeColor.has_value()) { - // An empty or invalid option means the original texture should be restored. - for (auto& replacement : replacements) { - if (replacement.handle != 0) { - auto result = svc_texture->unregister(mod_ctx, replacement.handle); - if (result != MOD_OK) { - mods::log::debug("Could not unregister replacement for {}. Result: {}", - replacement.textureName, static_cast(result)); - } - replacement.handle = 0; - } - replacement.curColor.reset(); - } - continue; - } - - auto color = maybeColor.value(); - for (auto& replacement : replacements) { - // If we haven't loaded the base texture yet, don't try to recolor it - if (!replacement.loadedTextureData) { - continue; - } - - // If our color hasn't changed, don't try to recolor - auto& curColor = replacement.curColor; - if (curColor == std::nullopt || curColor.value().r != color.r || - curColor.value().g != color.g || curColor.value().b != color.b) - { - // Make a copy of the base texture data to recolor - auto newTexture = replacement.baseTextureData; - recolor_texture(replacement, color, newTexture); - - TextureData newTextureData = replacement.data; - newTextureData.data = newTexture.data(); - newTextureData.size = newTexture.size(); - - // Keep the current replacement active if registering the new one fails. - TextureReplacementHandle newHandle{}; - auto result = svc_texture->register_data( - mod_ctx, &replacement.key, &newTextureData, &newHandle); - if (result != MOD_OK) { - mods::log::debug("Could not register_data for {}. Result: {}", - replacement.textureName, static_cast(result)); - } else { - mods::log::debug("Registered replacement for {}.", replacement.textureName, - static_cast(result)); - auto oldHandle = replacement.handle; - replacement.handle = newHandle; - curColor = color; - - if (oldHandle != 0) { - result = svc_texture->unregister(mod_ctx, oldHandle); - if (result != MOD_OK) { - mods::log::debug( - "Could not unregister previous replacement for {}. Result: {}", - replacement.textureName, static_cast(result)); - } - } - } - } - } - } - - return MOD_OK; -} - -void unregister_all_texture_handles() { - for (auto& replacements : get_texture_replacements() | std::views::values) { - for (auto& replacement : replacements) { - if (replacement.handle != 0) { - svc_texture->unregister(mod_ctx, replacement.handle); - replacement.handle = 0; - } - replacement.curColor.reset(); - } - } -} - -#define REGISTER_COSMETIC_OPTION(option) \ - result = register_str_option(#option, NULL, g_cvars.option, error); \ - if (result != MOD_OK) { \ - return result; \ - } - -extern "C" { -MOD_EXPORT ModResult mod_initialize(ModError* error) { - svc_log->info(mod_ctx, "basic_cosmetics_mod initialized"); - - ModResult result{}; - - REGISTER_COSMETIC_OPTION(herosTunicCapColor) - REGISTER_COSMETIC_OPTION(herosTunicTorsoColor) - REGISTER_COSMETIC_OPTION(herosTunicSkirtColor) - REGISTER_COSMETIC_OPTION(zoraArmorCapColor) - REGISTER_COSMETIC_OPTION(zoraArmorHelmetColor) - REGISTER_COSMETIC_OPTION(zoraArmorTorsoColor) - REGISTER_COSMETIC_OPTION(zoraArmorScalesColor) - REGISTER_COSMETIC_OPTION(zoraArmorFlippersColor) - REGISTER_COSMETIC_OPTION(lanternGlowColor) - REGISTER_COSMETIC_OPTION(woodenSwordColor) - REGISTER_COSMETIC_OPTION(ordonSwordBladeColor) - REGISTER_COSMETIC_OPTION(ordonSwordHandleColor) - REGISTER_COSMETIC_OPTION(msBladeColor) - REGISTER_COSMETIC_OPTION(msHandleColor) - REGISTER_COSMETIC_OPTION(lightSwordGlowColor) - REGISTER_COSMETIC_OPTION(boomerangColor) - REGISTER_COSMETIC_OPTION(ironBootsColor) - REGISTER_COSMETIC_OPTION(spinnerColor) - REGISTER_COSMETIC_OPTION(aButtonColor) - REGISTER_COSMETIC_OPTION(bButtonColor) - REGISTER_COSMETIC_OPTION(xButtonColor) - REGISTER_COSMETIC_OPTION(yButtonColor) - REGISTER_COSMETIC_OPTION(zButtonColor) - REGISTER_COSMETIC_OPTION(heartColor) - - result = register_int_option("midnaHairBaseColor", 0, g_cvars.midnaHairBaseColor, error); - if (result != MOD_OK) { - return result; - } - - result = register_int_option("midnaHairTipsColor", 0, g_cvars.midnaHairTipsColor, error); - if (result != MOD_OK) { - return result; - } - - REGISTER_COSMETIC_OPTION(midnaChargeRingColor) - REGISTER_COSMETIC_OPTION(linkHairColor) - REGISTER_COSMETIC_OPTION(wolfLinkColor) - REGISTER_COSMETIC_OPTION(eponaColor) - - UiModsPanelDesc panelDesc = UI_MODS_PANEL_DESC_INIT; - panelDesc.build = build_panel; - svc_ui->register_mods_panel(mod_ctx, &panelDesc); - - // Add all our hooks - result = add_all_hooks(); - if (result != MOD_OK) { - return result; - } - - g_loadedAllBaseTextures = false; - g_textureLoadRetryCountdown = 0; - return MOD_OK; -} - -MOD_EXPORT ModResult mod_update(ModError*) { - update_rainbow_rgb(1.0f); - set_all_midna_hair_colors(); - - if (!g_loadedAllBaseTextures) { - if (g_textureLoadRetryCountdown == 0) { - load_base_texture_data(); - g_textureLoadRetryCountdown = kTextureLoadRetryFrames; - } else { - --g_textureLoadRetryCountdown; - } - } - - check_and_set_recolored_textures(); - - return MOD_OK; -} - -MOD_EXPORT ModResult mod_shutdown(ModError*) { - svc_log->info(mod_ctx, "basic_cosmetics_mod unloaded"); - g_cosmeticsWindow = 0; - remove_all_hooks(); - unregister_all_texture_handles(); - get_texture_replacements().clear(); - g_loadedAllBaseTextures = false; - g_textureLoadRetryCountdown = 0; - return MOD_OK; -} -} diff --git a/mods/basic_cosmetics_mod/src/mod.hpp b/mods/basic_cosmetics_mod/src/mod.hpp deleted file mode 100644 index 6d77949b03..0000000000 --- a/mods/basic_cosmetics_mod/src/mod.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "mods/svc/config.h" -#include "mods/svc/texture.h" - -#include - -#include -#include -#include - -struct cvars { - ConfigVarHandle herosTunicCapColor = 0; - ConfigVarHandle herosTunicTorsoColor = 0; - ConfigVarHandle herosTunicSkirtColor = 0; - ConfigVarHandle zoraArmorCapColor = 0; - ConfigVarHandle zoraArmorHelmetColor = 0; - ConfigVarHandle zoraArmorTorsoColor = 0; - ConfigVarHandle zoraArmorScalesColor = 0; - ConfigVarHandle zoraArmorFlippersColor = 0; - ConfigVarHandle lanternGlowColor = 0; - ConfigVarHandle woodenSwordColor = 0; - ConfigVarHandle ordonSwordBladeColor = 0; - ConfigVarHandle ordonSwordHandleColor = 0; - ConfigVarHandle msBladeColor = 0; - ConfigVarHandle msHandleColor = 0; - ConfigVarHandle lightSwordGlowColor = 0; - ConfigVarHandle boomerangColor = 0; - ConfigVarHandle ironBootsColor = 0; - ConfigVarHandle spinnerColor = 0; - ConfigVarHandle heartColor = 0; - ConfigVarHandle aButtonColor = 0; - ConfigVarHandle bButtonColor = 0; - ConfigVarHandle xButtonColor = 0; - ConfigVarHandle yButtonColor = 0; - ConfigVarHandle zButtonColor = 0; - ConfigVarHandle midnaHairBaseColor = 0; - ConfigVarHandle midnaHairTipsColor = 0; - ConfigVarHandle midnaChargeRingColor = 0; - ConfigVarHandle linkHairColor = 0; - ConfigVarHandle wolfLinkColor = 0; - ConfigVarHandle eponaColor = 0; -}; - -struct TextureReplacementData { - const char* arc{}; - const char* modelFileName{}; - const char* textureName{}; - uint64_t textureHash{}; - uint64_t tlutHash{}; - TextureKey key = TEXTURE_KEY_INIT; - TextureData data = TEXTURE_DATA_INIT; - TextureReplacementHandle handle{}; - std::vector baseTextureData{}; - bool loadedTextureData = false; - std::optional curColor{}; -}; - -cvars& get_cvars(); - -std::string get_str_option(ConfigVarHandle handle, const std::string& fallback); - -int64_t get_int_option(ConfigVarHandle handle, int64_t fallback); - -std::optional get_config_var_color(ConfigVarHandle handle, bool allowRainbow = false); \ No newline at end of file diff --git a/mods/basic_cosmetics_mod/src/texture_utils.cpp b/mods/basic_cosmetics_mod/src/texture_utils.cpp deleted file mode 100644 index 98c60dbad3..0000000000 --- a/mods/basic_cosmetics_mod/src/texture_utils.cpp +++ /dev/null @@ -1,682 +0,0 @@ -#include "texture_utils.hpp" -#include "color_utils.hpp" -#include "mod.hpp" - -#include "mods/svc/config.h" -#include "mods/svc/log.hpp" - -#include "JSystem/J3DGraphLoader/J3DModelLoader.h" -#include "JSystem/JSupport/JSupport.h" -#include "JSystem/JUtility/JUTNameTab.h" -#include "d/actor/d_a_alink.h" - -static void get_gx_tile_info(uint8_t format, uint32_t& tileWidth, uint32_t& tileHeight, uint32_t& tileSize) { - switch (format) { - case GX_TF_I8: - case GX_TF_IA4: - case GX_TF_C8: - tileWidth = 8; tileHeight = 4; tileSize = 32; - break; - case GX_TF_IA8: - case GX_TF_RGB565: - case GX_TF_RGB5A3: - case GX_TF_C14X2: - tileWidth = 4; tileHeight = 4; tileSize = 32; - break; - case GX_TF_RGBA8: - tileWidth = 4; tileHeight = 4; tileSize = 64; - break; - case GX_TF_I4: - case GX_TF_C4: - case GX_TF_CMPR: - default: - tileWidth = 8; tileHeight = 8; tileSize = 32; - break; - } -} - -uint32_t get_image_data_size(uint32_t format, uint32_t width, uint32_t height, uint32_t mipmapCount) { - - uint32_t tileWidth, tileHeight, tileSize; - get_gx_tile_info(format, tileWidth, tileHeight, tileSize); - - uint32_t totalSize = 0; - - for (uint8_t i = 0; i < mipmapCount; ++i) { - // Round dimensions up to nearest tile boundary - uint32_t paddedWidth = (width + tileWidth - 1) & ~(tileWidth - 1); - uint32_t paddedHeight = (height + tileHeight - 1) & ~(tileHeight - 1); - - uint32_t tilesX = paddedWidth / tileWidth; - uint32_t tilesY = paddedHeight / tileHeight; - - totalSize += tilesX * tilesY * tileSize; - - // Downscale dimensions for next mipmap level - width = std::max(1u, width >> 1); - height = std::max(1u, height >> 1); - } - - return totalSize; -} - -// When left is greater than right -// 0b00 points to the left color -// 0b01 points to the right color -// 0b10 is closer to left color -// 0b11 is closer to right color - -// When left is not greater than right -// 0b00 points to the left color -// 0b01 points to the right color -// 0b10 is midway between the colors -// 0b11 is transparent - -// That means when maintaining the relative order, if we have to swap the colors: - -// in the case of left being greater than right: -// 0b00 will swap to 0b01 -// 0b01 will swap to 0b00 -// 0b10 will swap to 0b11 -// 0b11 will swap to 0b10 -// So the left bit stays the same, and the right bit changes -// Can do xor (^) like 0b01010101 or 0x55 for each u16 - -// in the case of left not being greater than right: -// 0b00 will swap to 0b01 -// 0b01 will swap to 0b00 -// 0b10 will stay the same -// 0b11 will stay the same -// so if the left bit is a 0, the right bit will change -uint32_t swap_index_bits(bool leftIsGreater, uint32_t bits) { - if (leftIsGreater) { - return bits ^ 0x55555555; - } - - const uint32_t mask = ((bits >> 1) & 0x55555555) ^ 0x55555555; - return bits ^ mask; -} - -void recolor_cmpr_texture(const TextureReplacementData& replacementData, const GXColor color, std::vector& newTextureDataOut) -{ - uint16_t recolors[0x100]; - for (int32_t i = 0; i < 0x100; i++) { - recolors[i] = blend_overlay_rgb_565(i, color); - } - - const uint8_t mipCount = (replacementData.data.mip_count > 0) ? replacementData.data.mip_count : 1; - uint32_t mipWidth = replacementData.key.width; - uint32_t mipHeight = replacementData.key.height; - - uint8_t* currentAddr = newTextureDataOut.data(); - - for (uint8_t mip = 0; mip < mipCount; ++mip) { - // Round dimensions up to the nearest 8x8 tile boundary - const uint32_t roundedWidth = (mipWidth + 7) & ~7; - const uint32_t roundedHeight = (mipHeight + 7) & ~7; - - const uint32_t numBlocks = (roundedWidth / 8) * (roundedHeight / 8); - const uint32_t iterations = numBlocks * 4; // 4 CMPR sub-blocks per 8x8 tile - - for (uint32_t i = 0; i < iterations; i++) { - auto* rgb565Ptr = reinterpret_cast*>(currentAddr); - - auto leftRgb565 = rgb565Ptr[0]; - auto rightRgb565 = rgb565Ptr[1]; - const bool leftIsGreater = leftRgb565 > rightRgb565; - - const uint32_t leftGrayVal = desaturate_rgb_565(leftRgb565); - const uint32_t rightGrayVal = desaturate_rgb_565(rightRgb565); - - uint16_t leftNewRgb565 = recolors[leftGrayVal]; - uint16_t rightNewRgb565 = recolors[rightGrayVal]; - - bool needsBitSwap = false; - - if (leftIsGreater) { - if (leftNewRgb565 == rightNewRgb565) { - // Need to make sure that subtracting 1 does not mess - // everything up. For example, 0x1000 - 1 => 0x0fff which is - // a completely different color. - if ((leftNewRgb565 & 0x1f) == 0) { - // If left value has 0 blue, we change its blue to 1. - leftNewRgb565 += 1; - } - rightNewRgb565 = leftNewRgb565 - 1; - } - else if (leftNewRgb565 < rightNewRgb565) { - needsBitSwap = true; - } - } - else if (leftNewRgb565 > rightNewRgb565) { - needsBitSwap = true; - } - - if (needsBitSwap) { - // The left and right colors are swapping so that their values - // are relative in the same way. We need to update the bits - // referencing the palette entries to handle the swap. - const uint16_t temp = leftNewRgb565; - leftNewRgb565 = rightNewRgb565; - rightNewRgb565 = temp; - - auto wordPtr = reinterpret_cast*>(currentAddr); - const uint32_t bits = wordPtr[1]; - - const uint32_t newBits = swap_index_bits(leftIsGreater, bits); - wordPtr[1] = newBits; - } - - rgb565Ptr[0] = leftNewRgb565; - rgb565Ptr[1] = rightNewRgb565; - - currentAddr += 8; - } - - // Halve dimensions for the next mipmap level - mipWidth = std::max(1u, mipWidth >> 1); - mipHeight = std::max(1u, mipHeight >> 1); - } -} - -void recolor_rgb5a3_texture(const TextureReplacementData& replacementData, const GXColor color, std::vector& newTextureDataOut) -{ - // Precompute lookup tables for both RGB555 (opaque) and RGB444 (translucent) modes - uint16_t recolors_rgb555[0x100]; - uint16_t recolors_rgb444[0x100]; - - for (int32_t i = 0; i < 0x100; i++) { - const uint8_t r = blend_overlay_channel(i, color.r); - const uint8_t g = blend_overlay_channel(i, color.g); - const uint8_t b = blend_overlay_channel(i, color.b); - - // Pack as RGB555: Bit 15 set to 1 + 5 bits R, G, B - recolors_rgb555[i] = 0x8000 | ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3); - - // Pack as RGB444: 4 bits R, G, B (Bit 15 remains 0) - recolors_rgb444[i] = ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4); - } - - const uint8_t mipCount = - replacementData.data.mip_count > 0 ? replacementData.data.mip_count : 1; - uint32_t mipWidth = replacementData.key.width; - uint32_t mipHeight = replacementData.key.height; - auto* pixelPtr = reinterpret_cast*>(newTextureDataOut.data()); - - for (uint8_t mip = 0; mip < mipCount; ++mip) { - const uint32_t roundedWidth = (mipWidth + 3) & ~3; - const uint32_t roundedHeight = (mipHeight + 3) & ~3; - const uint32_t totalPixels = roundedWidth * roundedHeight; - - for (uint32_t i = 0; i < totalPixels; i++) { - const uint16_t rawPixel = pixelPtr[i]; - - // MSB determines if pixel is opaque or translucent - if (rawPixel & 0x8000) { - // Pixel is opaque - const uint8_t r5 = (rawPixel >> 10) & 0x1F; - const uint8_t g5 = (rawPixel >> 5) & 0x1F; - const uint8_t b5 = rawPixel & 0x1F; - - // Expand 5-bit to 8-bit - const uint8_t r8 = (r5 << 3) | (r5 >> 2); - const uint8_t g8 = (g5 << 3) | (g5 >> 2); - const uint8_t b8 = (b5 << 3) | (b5 >> 2); - - const uint8_t grayVal = static_cast((r8 * 77 + g8 * 150 + b8 * 29) >> 8); - - pixelPtr[i] = recolors_rgb555[grayVal]; - } else { - // Pixel is translucent - const uint16_t alpha3 = rawPixel & 0x7000; - - const uint8_t r4 = (rawPixel >> 8) & 0x0F; - const uint8_t g4 = (rawPixel >> 4) & 0x0F; - const uint8_t b4 = rawPixel & 0x0F; - - // Expand 4-bit to 8-bit - const uint8_t r8 = (r4 << 4) | r4; - const uint8_t g8 = (g4 << 4) | g4; - const uint8_t b8 = (b4 << 4) | b4; - - const uint8_t grayVal = static_cast((r8 * 77 + g8 * 150 + b8 * 29) >> 8); - - // Combine original alpha with recolored RGB444 - pixelPtr[i] = alpha3 | recolors_rgb444[grayVal]; - } - } - - pixelPtr += totalPixels; - mipWidth = std::max(1u, mipWidth >> 1); - mipHeight = std::max(1u, mipHeight >> 1); - } -} - -// Function to encode a single 4x4 sub-block (16 pixels) into an 8-byte CMPR block -static void encode_cmpr_sub_block(uint8_t* dst, const uint8_t pixels[16]) { - uint8_t min_val = 255; - uint8_t max_val = 0; - - for (int i = 0; i < 16; ++i) { - if (pixels[i] < min_val) min_val = pixels[i]; - if (pixels[i] > max_val) max_val = pixels[i]; - } - - auto intensity_to_rgb565 = [](uint8_t val) -> uint16_t { - uint16_t r5 = val >> 3; - uint16_t g6 = val >> 2; - uint16_t b5 = val >> 3; - return static_cast((r5 << 11) | (g6 << 5) | b5); - }; - - uint16_t c0_565 = intensity_to_rgb565(max_val); - uint16_t c1_565 = intensity_to_rgb565(min_val); - uint32_t indices = 0; - - if (max_val > min_val) { - // Enforce c0_565 > c1_565 in unsigned 16-bit representation to use 4-color mode - if (c0_565 == c1_565) { - if ((c0_565 & 0x001F) < 0x001F) { - c0_565 += 1; - } else { - c1_565 -= 1; - } - } - - // Interpolated 8-bit intensity values for quantization - const int c0 = max_val; - const int c1 = min_val; - const int c2 = (2 * max_val + min_val) / 3; - const int c3 = (max_val + 2 * min_val) / 3; - - // Map each pixel to the nearest palette entry - for (int i = 0; i < 16; ++i) { - const int p = pixels[i]; - const int d0 = std::abs(p - c0); - const int d1 = std::abs(p - c1); - const int d2 = std::abs(p - c2); - const int d3 = std::abs(p - c3); - - uint32_t best_idx = 0; - int min_d = d0; - - if (d1 < min_d) { min_d = d1; best_idx = 1; } - if (d2 < min_d) { min_d = d2; best_idx = 2; } - if (d3 < min_d) { min_d = d3; best_idx = 3; } - - indices |= (best_idx << (30 - (2 * i))); - } - } - - // Account for big endian data expectation - dst[0] = static_cast(c0_565 >> 8); - dst[1] = static_cast(c0_565 & 0xFF); - dst[2] = static_cast(c1_565 >> 8); - dst[3] = static_cast(c1_565 & 0xFF); - dst[4] = static_cast(indices >> 24); - dst[5] = static_cast((indices >> 16) & 0xFF); - dst[6] = static_cast((indices >> 8) & 0xFF); - dst[7] = static_cast(indices & 0xFF); -} - -bool convert_i8_to_cmpr(TextureReplacementData& replacementData, std::vector& cmprOut) { - if (cmprOut.empty()) { - return false; - } - - const uint8_t mipCount = (replacementData.data.mip_count > 0) ? replacementData.data.mip_count : 1; - const auto expectedInputSize = get_image_data_size( - GX_TF_I8, replacementData.key.width, replacementData.key.height, mipCount); - if (cmprOut.size() < expectedInputSize) { - return false; - } - - const auto sourceData = cmprOut; - std::vector convertedData(get_image_data_size( - GX_TF_CMPR, replacementData.key.width, replacementData.key.height, mipCount)); - uint32_t mipWidth = replacementData.key.width; - uint32_t mipHeight = replacementData.key.height; - - const uint8_t* readPtr = sourceData.data(); - uint8_t* writePtr = convertedData.data(); - - for (uint8_t mip = 0; mip < mipCount; ++mip) { - const uint32_t paddedWidthI8 = (mipWidth + 7) & ~7; - const uint32_t paddedHeightI8 = (mipHeight + 3) & ~3; - const uint32_t tilesX_I8 = paddedWidthI8 / 8; - - const uint32_t paddedWidthCMPR = (mipWidth + 7) & ~7; - const uint32_t paddedHeightCMPR = (mipHeight + 7) & ~7; - const uint32_t blocksX_CMPR = paddedWidthCMPR / 8; - const uint32_t blocksY_CMPR = paddedHeightCMPR / 8; - - for (uint32_t by = 0; by < blocksY_CMPR; ++by) { - for (uint32_t bx = 0; bx < blocksX_CMPR; ++bx) { - uint8_t subBlockPixels[4][16]{}; - - for (uint32_t subBlockY = 0; subBlockY < 2; ++subBlockY) { - for (uint32_t subBlockX = 0; subBlockX < 2; ++subBlockX) { - const uint32_t subBlock = subBlockY * 2 + subBlockX; - for (uint32_t row = 0; row < 4; ++row) { - for (uint32_t col = 0; col < 4; ++col) { - const uint32_t x = bx * 8 + subBlockX * 4 + col; - const uint32_t y = by * 8 + subBlockY * 4 + row; - if (x >= mipWidth || y >= mipHeight) { - continue; - } - - const uint32_t tile = (y / 4) * tilesX_I8 + (x / 8); - const uint32_t tileOffset = (y % 4) * 8 + (x % 8); - subBlockPixels[subBlock][row * 4 + col] = - readPtr[tile * 32 + tileOffset]; - } - } - } - } - - for (const auto& subBlockPixel : subBlockPixels) { - encode_cmpr_sub_block(writePtr, subBlockPixel); - writePtr += 8; - } - } - } - - const uint32_t tilesY_I8 = paddedHeightI8 / 4; - readPtr += tilesX_I8 * tilesY_I8 * 32; - - mipWidth = std::max(1u, mipWidth >> 1); - mipHeight = std::max(1u, mipHeight >> 1); - } - - cmprOut.swap(convertedData); - - // Update format - replacementData.data.gx_format = GX_TF_CMPR; - - return true; -} - -void recolor_texture(TextureReplacementData& replacementData, GXColor color, std::vector& newTextureDataOut) { - - switch (replacementData.key.gx_format) { - case GX_TF_CMPR: - recolor_cmpr_texture(replacementData, color, newTextureDataOut); - break; - case GX_TF_RGB5A3: - recolor_rgb5a3_texture(replacementData, color, newTextureDataOut); - break; - case GX_TF_I8: - if (convert_i8_to_cmpr(replacementData, newTextureDataOut)) { - recolor_cmpr_texture(replacementData, color, newTextureDataOut); - } else { - mods::log::debug("Could not convert {} from i8 to cmpr", replacementData.textureName); - } - break; - default: - break; - } -} - -std::unordered_map>& get_texture_replacements() { - static std::unordered_map> replacements{}; - - if (replacements.empty()) { - replacements = { - {get_cvars().herosTunicCapColor, { - { - .arc = "Kmdl", - .modelFileName = "al_head.bmd", - .textureName = "al_cap", - } - }}, - {get_cvars().herosTunicTorsoColor, { - { - .arc = "Kmdl", - .modelFileName = "al.bmd", - .textureName = "al_upbody", - } - }}, - {get_cvars().herosTunicSkirtColor, { - { - .arc = "Kmdl", - .modelFileName = "al.bmd", - .textureName = "al_lowbody", - } - }}, - {get_cvars().zoraArmorCapColor, { - { - .arc = "Zmdl", - .modelFileName = "zl_head.bmd", - .textureName = "zl_cap", - } - }}, - {get_cvars().zoraArmorHelmetColor, { - { - .arc = "Zmdl", - .modelFileName = "zl_head.bmd", - .textureName = "zl_helmet", - } - }}, - {get_cvars().zoraArmorTorsoColor, { - { - .arc = "Zmdl", - .modelFileName = "zl.bmd", - .textureName = "zl_armor", - }, - { - .arc = "Zmdl", - .modelFileName = "zl.bmd", - .textureName = "zl_armL", - } - }}, - {get_cvars().zoraArmorScalesColor, { - { - .arc = "Zmdl", - .modelFileName = "zl.bmd", - .textureName = "zl_body", - } - }}, - {get_cvars().zoraArmorFlippersColor, { - { - .arc = "Zmdl", - .modelFileName = "zl.bmd", - .textureName = "zl_boots", - } - }}, - {get_cvars().woodenSwordColor, { - { - .arc = "Bmdl", // Ordon Clothes Model - .modelFileName = "al_swb.bmd", - .textureName = "al_SWB", - }, - { - .arc = "Kmdl", // Hero's Tunic Model - .modelFileName = "al_swb.bmd", - .textureName = "al_SWB", - }, - { - .arc = "Zmdl", // Zora Armor Model - .modelFileName = "al_swb.bmd", - .textureName = "al_SWB", - }, - { - .arc = "Mmdl", // Magic Armor Model - .modelFileName = "al_swb.bmd", - .textureName = "al_SWB", - }, - { - .arc = "O_gD_SWB", // Get Item Model - .modelFileName = "o_gd_al_swb.bmd", - .textureName = "al_SWB", - } - }}, - {get_cvars().ordonSwordHandleColor, { - { - .arc = "Alink", - .modelFileName = "al_swa.bmd", - .textureName = "al_SWgripA", - }, - { - .arc = "O_gD_SWA", // Get Item Model - .modelFileName = "o_gd_al_swa.bmd", - .textureName = "al_SWgripA", - } - }}, - {get_cvars().ordonSwordBladeColor, { - { - .arc = "Alink", - .modelFileName = "al_swa.bmd", - .textureName = "al_SWA", - } - }}, - {get_cvars().msHandleColor, { - { - .arc = "Alink", - .modelFileName = "al_swm.bmd", - .textureName = "al_SWgripM", - } - }}, - {get_cvars().msBladeColor, { - { - .arc = "Alink", - .modelFileName = "al_swm.bmd", - .textureName = "al_SWM", - } - }}, - {get_cvars().boomerangColor, { - { - .arc = "Alink", // Boomerang in Link's hand - .modelFileName = "al_boom.bmd", - .textureName = "L_al_boom00", - }, - { - .arc = "E_mk", // Boomerang in Ook's hand - .modelFileName = "bm.bmd", - .textureName = "L_al_boom00", - }, - { - .arc = "E_mk", // Boomerang in Ook's hand - .modelFileName = "bm.bmd", - .textureName = "bm_boom", - }, - { - .arc = "O_gD_boom", // Get Item Model - .modelFileName = "o_gd_boom.bmd", - .textureName = "L_al_boom00", - } - }}, - {get_cvars().ironBootsColor, { - { - .arc = "Bmdl", // Ordon Clothes Model - .modelFileName = "al_bootsh.bmd", - .textureName = "al_bootsH", - }, - { - .arc = "Kmdl", // Hero's Tunic Model - .modelFileName = "al_bootsh.bmd", - .textureName = "al_bootsH", - }, - { - .arc = "Zmdl", // Zora Armor Model - .modelFileName = "al_bootsh.bmd", - .textureName = "al_bootsH", - }, - { - .arc = "Mmdl", // Magic Armor Model - .modelFileName = "al_bootsh.bmd", - .textureName = "al_bootsH", - }, - { - .arc = "O_gD_boot", // Get Item Model - .modelFileName = "o_gd_al_bootsh.bmd", - .textureName = "al_bootsH", - } - }}, - {get_cvars().spinnerColor, { - { - .arc = "Alink", // Spinner used by Link - .modelFileName = "al_sp.bmd", - .textureName = "al_SP", - }, - { - .arc = "O_gD_SP", // Get Item Model - .modelFileName = "o_gd_al_sp.bmd", - .textureName = "al_SP", - } - }}, - {get_cvars().linkHairColor, { - { - .arc = "Bmdl", // Ordon Clothes Model - .modelFileName = "bl_head.bmd", - .textureName = "bl_hair", - }, - { - .arc = "Kmdl", // Hero's Tunic Model - .modelFileName = "al_head.bmd", - .textureName = "al_hair", - }, - { - .arc = "Mmdl", // Magic Armor Model - .modelFileName = "ml_head.bmd", - .textureName = "al_hair", - } - }}, - {get_cvars().wolfLinkColor, { - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_body", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.1", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.2", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.3", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.4", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.5", - } - }}, - {get_cvars().eponaColor, { - { - .arc = "Horse", - .modelFileName = "hs.bmd", - .textureName = "hs_body", - }, - { - .arc = "Horse", - .modelFileName = "hs.bmd", - .textureName = "hs_eye.1", - }, - { - .arc = "Horse", - .modelFileName = "hs.bmd", - .textureName = "hs_eye.2", - }, - { - .arc = "Horse", - .modelFileName = "hs.bmd", - .textureName = "hs_eye.3", - }, - }}, - }; - } - - return replacements; -} diff --git a/mods/basic_cosmetics_mod/src/texture_utils.hpp b/mods/basic_cosmetics_mod/src/texture_utils.hpp deleted file mode 100644 index 49dd0df81a..0000000000 --- a/mods/basic_cosmetics_mod/src/texture_utils.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -/** - * File originally copied from console TPR with permission from isaac - * https://github.com/zsrtp/libtp_rel/blob/master/include/util/texture_utils.h - */ - -#include "mod.hpp" - -#include - -#include -#include - -uint32_t get_image_data_size( - uint32_t format, uint32_t width, uint32_t height, uint32_t mipmapCount); - -void recolor_texture( - TextureReplacementData& replacementData, GXColor color, std::vector& newTextureDataOut); - -std::unordered_map>& get_texture_replacements(); diff --git a/mods/cosmetics b/mods/cosmetics new file mode 160000 index 0000000000..fd93491089 --- /dev/null +++ b/mods/cosmetics @@ -0,0 +1 @@ +Subproject commit fd93491089fa95441789244f2b2fd22a451ecbcc diff --git a/mods/basic_cosmetics_mod/CMakeLists.txt b/mods/custom_actor_demo/CMakeLists.txt similarity index 52% rename from mods/basic_cosmetics_mod/CMakeLists.txt rename to mods/custom_actor_demo/CMakeLists.txt index f00540864b..d7e55536d4 100644 --- a/mods/basic_cosmetics_mod/CMakeLists.txt +++ b/mods/custom_actor_demo/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.25) -project(basic_cosmetics_mod CXX) +project(custom_actor_demo CXX) +set(CMAKE_CXX_STANDARD 20) if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root") @@ -12,24 +13,9 @@ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) endif () endif () -include(FetchContent) - -message(STATUS "basic_cosmetics_mod: Fetching xxhash") -FetchContent_Declare( - xxhash - GIT_REPOSITORY https://github.com/Cyan4973/xxHash.git - GIT_TAG v0.8.3 -) -FetchContent_MakeAvailable(xxhash) - -set(COSMETIC_SOURCES src/color_utils.cpp src/midna_hair_color.cpp src/texture_utils.cpp src/hooks.cpp) - -add_mod(basic_cosmetics_mod +add_mod(custom_actor_demo FEATURES game fmt - SOURCES src/mod.cpp ${COSMETIC_SOURCES} + SOURCES src/mod.cpp src/m_a_obj_wrock.cpp src/m_a_mine.cpp MOD_JSON mod.json RES_DIR res - BUNDLE ) - -target_link_libraries(basic_cosmetics_mod PRIVATE xxHash::xxhash) diff --git a/mods/custom_actor_demo/mod.json b/mods/custom_actor_demo/mod.json new file mode 100644 index 0000000000..55f57c264d --- /dev/null +++ b/mods/custom_actor_demo/mod.json @@ -0,0 +1,7 @@ +{ + "id": "dev.twilitrealm.custom_actor_demo", + "name": "[Demo] Custom Actor", + "version": "1.0.0", + "author": "Twilit Realm", + "description": "A mod to demonstrate how to create and use custom actors. Spawns a \"wrock\" in South Faron Woods." +} diff --git a/mods/custom_actor_demo/res/.gitkeep b/mods/custom_actor_demo/res/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mods/custom_actor_demo/src/m_a_mine.cpp b/mods/custom_actor_demo/src/m_a_mine.cpp new file mode 100644 index 0000000000..fd7fa77b65 --- /dev/null +++ b/mods/custom_actor_demo/src/m_a_mine.cpp @@ -0,0 +1,227 @@ +#include "m_a_mine.hpp" +#include "d/d_com_inf_game.h" +#include "res/Object/O_mD_jira.h" + +// The name of the archive in /res/Object/ +static const char* l_resName = "O_mD_jira"; + +// The actor's heap (for resources) should be enough to hold the data for the model and collision +static constexpr u32 heap_size = ALIGN_NEXT(16832, 0x20); + +ma_Mine_c::~ma_Mine_c() { + // Called every time the actor is deleted + + // Delete the sound object + mSound.deleteObject(); + + // Request to unload the archive (the data acts as a shared pointer, and only gets deleted when + // the reference counter goes to zero) + dComIfG_resDelete(&mPhase, l_resName); +} + +cPhs_Step ma_Mine_c::create() { + // Because of how the actor system works, an actor's constructor doesn't get called when an + // actor is created. We need to manually do it here with the following ma: + fopAcM_ct(this, ma_Mine_c); + + // The create function gets called until we return cPhs_COMPLEATE_e while an actor is loading. + // We request to load the archive here, and wait until the dvd completes loading it + cPhs_Step step = dComIfG_resLoad(&mPhase, l_resName); + if (step == cPhs_COMPLEATE_e) { + // Initialize the solid heap for the actor's resources, if needed + if (!fopAcM_entrySolidHeap(this, createHeapCallBack, heap_size)) { + return cPhs_ERROR_e; + } + + // Setup our collision sphere and register it to the world + // Set a circle "wall" of 30 units around the actor + mAcchCir.SetWall(30.0f, 30.0f); + mAcch.Set(this, 1, &mAcchCir); + mAcch.ClrWaterNone(); + mAcch.SetRoofCrrHeight(60.0f); + mAcch.SetWaterCheckOffset(10000.0f); + mAcch.SetWtrChkMode(2); + mAcch.OnLineCheck(); + + mCcStts.Init(30, 0xFF, this); + + // Collision Sphere for the actor (copied from Bomb Actor) + static const dCcD_SrcSph + l_sphSrc = {.mObjInf = + { + .mObj = {.mFlags = 0x0, + .mSrcObjHitInf = {.mObjAt = {.mType = AT_TYPE_BOMB, + .mAtp = 0x4, + .mBase = {.mSPrm = 0x1e}}, + .mObjTg = {.mType = 0xd8fbffef, .mBase = {.mSPrm = 0x11}}, + .mObjCo = {.mBase = {.mSPrm = 0x79}}}}, + .mGObjAt{.mSe = dCcD_SE_NONE, + .mHitMark = 0x0, + .mSpl = 0x1, + .mMtrl = 0x0, + .mBase = {.mGFlag = 0x0}}, + .mGObjTg{.mSe = dCcD_SE_NONE, + .mHitMark = 0x0, + .mSpl = 0x0, + .mMtrl = 0x0, + .mBase = {.mGFlag = 0x4}}, + .mGObjCo{.mBase = {.mGFlag = 0x0}}, + }, + .mSphAttr = {.mSph = {.mCenter = {0.0f, 0.0f, 0.0f}, .mRadius = 80.0f}}}; + + mCollisionSphere.Set(l_sphSrc); + mCollisionSphere.SetStts(&mCcStts); + + // We register a callback anytime the actor is hit (both attacks and is hit) + mCollisionSphere.SetAtHitCallback(atHitCallback); + mCollisionSphere.SetTgHitCallback(atHitCallback); + mCollisionSphere.OffTgSetBit(); + mCollisionSphere.OffCoSetBit(); + mCollisionSphere.OnAtSetBit(); // Enable the attack sphere + + // Set the initial matrix and cull box for the actor + fopAcM_SetMtx(this, mpModel->getBaseTRMtx()); + fopAcM_SetMin(this, -36.0f, 0.0f, -36.0f); + fopAcM_SetMax(this, 36.0f, 66.0f, 36.0f); + + // Call execute so the actor's information in the world can be updated + Execute(); + } + return step; +} + +// Initializes the heap that all instances of this actor will use for resources +// Gets called from the callback in fopAcM_entrySolidHeap +int ma_Mine_c::CreateHeap() { + // Get the bmd data from the archive and initialize it + J3DModelData* model_data = + (J3DModelData*)dComIfG_getObjectRes(l_resName, dRes_INDEX_O_MD_JIRA_BMD_O_MD_JIRAI_e); + if (model_data == NULL) { + return 0; + } + mpModel = mDoExt_J3DModel__create(model_data, 0x80000, 0x11000084); + if (mpModel == NULL) { + return 0; + } + + // Create the sound object the actor will use + mSound.init(¤t.pos, 1); + + return 1; +} + +int ma_Mine_c::createHeapCallBack(fopAc_ac_c* i_this) { + return static_cast(i_this)->CreateHeap(); +} + +int ma_Mine_c::Delete() { + // Call the destructor anytime we delete the actor + this->~ma_Mine_c(); + return 1; +} + +int ma_Mine_c::Execute() { + // Update collision with the world + mAcch.CrrPos(dComIfG_Bgsp()); + mCollisionSphere.SetC(attention_info.position); + dComIfG_Ccsp()->Set(&mCollisionSphere); + + // Get the collision below the actor + cBgS_GndChk groundChunk = mAcch.m_gnd; + f32 groundH = mAcch.GetGroundH(); + if (groundH != -G_CM3D_F_INF) { + // Set the actor's environment colors to match the room's current ones + int roomNo = dComIfG_Bgsp().GetRoomId(groundChunk); + tevStr.YukaCol = dComIfG_Bgsp().GetPolyColor(groundChunk); + tevStr.room_no = roomNo; + + // Set the actor's room to be where it is sitting + mCcStts.SetRoomId(roomNo); + fopAcM_SetRoomNo(this, roomNo); + + // Get the reverb info here that the actor can use when exploding + mReverb = dComIfGp_getReverb(roomNo); + } + + // Update the model's transformation matrix to match the actor's transform + mDoMtx_stack_c::transS(current.pos.x, current.pos.y, current.pos.z); + mDoMtx_stack_c::ZXYrotM(shape_angle); + mDoMtx_stack_c::scaleM(scale); + mpModel->setBaseTRMtx(mDoMtx_stack_c::get()); + + // Set any attention flags (if needed) + eyePos = attention_info.position = current.pos; + attention_info.flags = 0; + return 1; +} + +int ma_Mine_c::Draw() { + // Update the model's lighting with the scene + g_env_light.settingTevStruct(0x20, ¤t.pos, &tevStr); + g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr); + + // Set the bmd model to be drawn when the display list is executed + mDoExt_modelUpdateDL(mpModel); + + return 1; +} + +void ma_Mine_c::atHit(dCcD_GObjInf* i_atObjInf) { + // Create particles with these IDs at the actor's position + static const u16 normalNameID[] = { + 0x161, 0x162, 0x163, 0x164, 0x165, 0x166, 0x167, 0x168, 0x1EC}; + for (int i = 0; i < ARRAY_SIZE(normalNameID); i++) { + dComIfGp_particle_setColor(normalNameID[i], ¤t.pos, &tevStr, NULL, NULL, 0.0f, 0xFF, + &shape_angle, &scale, NULL, -1, NULL); + } + + // Create an explosion sound + mSound.startSound(Z2SE_OBJ_BOMB_EXPLODE, 0, mReverb); + + // Vibrate the controller + dComIfGp_getVibration().StartShock(4, 31, cXyz(0.0f, 1.0f, 0.0f)); + + // Request to delete the actor so it disappears + fopAcM_delete(this); +} + +void ma_Mine_c::atHitCallback(fopAc_ac_c* i_tgActor, dCcD_GObjInf* i_tgObjInf, + fopAc_ac_c* i_atActor, dCcD_GObjInf* i_atObjInf) { + // This callback gets triggered anytime an intersection happens with the object's collision sphere + ((ma_Mine_c*)i_tgActor)->atHit(i_atObjInf); +} + +static cPhs_Step ma_Mine_create(void* i_this) { + return static_cast(i_this)->create(); +} + +static int maMine_Delete(void* i_this) { + return static_cast(i_this)->Delete(); +} + +static int maMine_Execute(void* i_this) { + return static_cast(i_this)->Execute(); +} + +static int maMine_Draw(void* i_this) { + return static_cast(i_this)->Draw(); +} + +static int maMine_IsDelete(void*) { + return 1; +} + +s16 ma_Mine_c::sProcName = -1; +ActorHandle ma_Mine_c::sActorHandle = -1; +const ActorProfileDesc ma_Mine_c::sProfile = {.name = MA_MINE_NAME, + .priority_group = 7, + .process_size = sizeof(ma_Mine_c), + .draw_priority = fpcDwPi_OBJ_LBOX_e, // An unused draw priority + .status = fopAcStts_UNK_0x40000_e | fopAcStts_UNK_0x4000_e | fopAcStts_CULL_e, + .group = fopAc_ACTOR_e, + .cull_type = fopAc_CULLBOX_CUSTOM_e, + .create_function = ma_Mine_create, + .delete_function = maMine_Delete, + .execute_function = maMine_Execute, + .is_delete_function = maMine_IsDelete, + .draw_function = maMine_Draw}; diff --git a/mods/custom_actor_demo/src/m_a_mine.hpp b/mods/custom_actor_demo/src/m_a_mine.hpp new file mode 100644 index 0000000000..147938473f --- /dev/null +++ b/mods/custom_actor_demo/src/m_a_mine.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "mods/svc/actor.h" + +// Base actor class definitions +#include "f_op/f_op_actor.h" + +// Definitions for request_of_phase_process_class and cPhs_Step +#include "SSystem/SComponent/c_phase.h" + +// Definitions for collision +#include "d/d_bg_s_acch.h" +#include "d/d_bg_w.h" +#include "d/d_cc_d.h" + +#define MA_MINE_NAME "m_mine" + +class ma_Mine_c : public fopAc_ac_c { +public: + request_of_phase_process_class mPhase; + J3DModel* mpModel; + dBgS_ObjAcch mAcch; + dBgS_AcchCir mAcchCir; + Mtx mColliderMtx; + dCcD_Stts mCcStts; + dCcD_Sph mCollisionSphere; + Z2SoundObjSimple mSound; + s8 mReverb; + + virtual ~ma_Mine_c(); + cPhs_Step create(); + int CreateHeap(); + int Delete(); + int Execute(); + int Draw(); + void atHit(dCcD_GObjInf* i_atObjInf); + static int createHeapCallBack(fopAc_ac_c*); + static void atHitCallback(fopAc_ac_c* i_tgActor, dCcD_GObjInf* i_tgObjInf, + fopAc_ac_c* i_atActor, dCcD_GObjInf* i_atObjInf); + + static s16 sProcName; + static ActorHandle sActorHandle; + static const ActorProfileDesc sProfile; +}; diff --git a/mods/custom_actor_demo/src/m_a_obj_wrock.cpp b/mods/custom_actor_demo/src/m_a_obj_wrock.cpp new file mode 100644 index 0000000000..bb40d10cf3 --- /dev/null +++ b/mods/custom_actor_demo/src/m_a_obj_wrock.cpp @@ -0,0 +1,195 @@ +/* + * m_a_obj_wrock.cpp + * An example actor for a rock that can be placed in the world. + */ + +#include "m_a_obj_wrock.hpp" +#include "d/d_com_inf_game.h" +#include "res/Object/WRock.h" + +// The name of the archive in /res/Object/ +static const char* l_resName = "Wrock"; + +// The actor's heap (for resources) should be enough to hold the data for the model and collision +static constexpr u32 heap_size = ALIGN_NEXT(13952, 0x20) + ALIGN_NEXT(1920, 0x20); + +maObj_Wrock_c::~maObj_Wrock_c() { + // Called every time the actor is deleted + + // Remove the collider from the world's collision + if (mpCollider != NULL) { + dComIfG_Bgsp().Release(mpCollider); + } + + // Request to unload the archive (the data acts as a shared pointer, and only gets deleted when + // the reference counter goes to zero) + dComIfG_resDelete(&mPhase, l_resName); +} + +cPhs_Step maObj_Wrock_c::create() { + // Because of how the actor system works, an actor's constructor doesn't get called when an + // actor is created. We need to manually do it here with the following macro: + fopAcM_ct(this, maObj_Wrock_c); + + // The create function gets called until we return cPhs_COMPLEATE_e while an actor is loading. + // We request to load the archive here, and wait until the dvd completes loading it + cPhs_Step step = dComIfG_resLoad(&mPhase, l_resName); + if (step == cPhs_COMPLEATE_e) { + // Initialize the solid heap for the actor's resources, if needed + if (!fopAcM_entrySolidHeap(this, createHeapCallBack, heap_size)) { + return cPhs_ERROR_e; + } + + // Register the actor's collider to the current world's collision + if (mpCollider != NULL) { + if (dComIfG_Bgsp().Regist(mpCollider, this) == true) { + return cPhs_ERROR_e; + } + } + + // Set the initial matrix and cull box for the actor + fopAcM_SetMtx(this, mpModel->getBaseTRMtx()); + fopAcM_setCullSizeBox(this, -400.0f, -400.0f, -400.0f, 400.0f, 400.0f, 400.0f); + + // Setup collision info (will be used in execute) + mAcch.Set(¤t.pos, &old.pos, this, 1, &mAcchCir, &speed, ¤t.angle, &shape_angle); + + // Call execute so the actor's information in the world can be updated + Execute(); + } + return step; +} + +// Initializes the heap that all instances of this actor will use for resources +// Gets called from the callback in fopAcM_entrySolidHeap +int maObj_Wrock_c::CreateHeap() { + // Get the bmd data from the archive and initialize it + J3DModelData* model_data = + (J3DModelData*)dComIfG_getObjectRes(l_resName, dRes_INDEX_WROCK_BMD_WROCK_e); + if (model_data == NULL) { + return 0; + } + mpModel = mDoExt_J3DModel__create(model_data, 0x80000, 0x11000084); + if (mpModel == NULL) { + return 0; + } + + // Get the dzb collision data from the archive and initialize it + mpCollider = JKR_NEW dBgW(); + if (mpCollider == NULL) { + return 0; + } + cBgD_t* dzb = (cBgD_t*)dComIfG_getObjectRes(l_resName, dRes_INDEX_WROCK_DZB_WROCK_e); + if (mpCollider->Set(dzb, 1, &mColliderMtx) == true) { + return 0; + } + mpCollider->SetCrrFunc(dBgS_MoveBGProc_Typical); + return 1; +} + +int maObj_Wrock_c::createHeapCallBack(fopAc_ac_c* i_this) { + return static_cast(i_this)->CreateHeap(); +} + +int maObj_Wrock_c::Delete() { + // Call the destructor anytime we delete the actor + this->~maObj_Wrock_c(); + return 1; +} + +int maObj_Wrock_c::Execute() { + // Update collision with the world + mAcch.CrrPos(dComIfG_Bgsp()); + + // Get the collision below the actor + mGndChk = mAcch.m_gnd; + mGroundH = mAcch.GetGroundH(); + if (mGroundH != -G_CM3D_F_INF) { + // Set the actor's environment colors to match the room's current ones + tevStr.YukaCol = dComIfG_Bgsp().GetPolyColor(mGndChk); + tevStr.room_no = dComIfG_Bgsp().GetRoomId(mGndChk); + + // Set the actor's room to be where it is sitting + fopAcM_SetRoomNo(this, dComIfG_Bgsp().GetRoomId(mGndChk)); + } + + // Update the model's transformation matrix to match the actor's transform + mDoMtx_stack_c::transS(current.pos.x, current.pos.y, current.pos.z); + mDoMtx_stack_c::ZXYrotM(shape_angle); + mDoMtx_stack_c::scaleM(scale); + mpModel->setBaseTRMtx(mDoMtx_stack_c::get()); + + // Copy the model's transformation matrix to the collider's transformation matrix and update the + // collider + if (mpCollider != NULL) { + PSMTXCopy(mpModel->getBaseTRMtx(), mColliderMtx); + mpCollider->Move(); + } + + // Set any attention flags (if needed) + eyePos = attention_info.position = current.pos; + attention_info.flags = 0; + return 1; +} + +int maObj_Wrock_c::Draw() { + // Update the model's lighting with the scene + g_env_light.settingTevStruct(0x20, ¤t.pos, &tevStr); + g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr); + + // Set the current dlist to BG Which means that shadows can be cast on it + // Because of the messy collider, the shadows don't look great, but this is here as an example + dComIfGd_setListBG(); + + // Set the bmd model to be drawn when the display list is executed + mDoExt_modelUpdateDL(mpModel); + + // Cast a shadow for the actor onto the ground. + // We can only do this if we are drawing to the normal dlist, not the BG dlist + // if (mGroundH != -G_CM3D_F_INF) { + // mShadow = dComIfGd_setShadow(mShadow, 1, mpModel, ¤t.pos, + // 2000.0f, 0.0f, + // current.pos.y, mGroundH, mGndChk, &tevStr, 0, + // 1.0f, &dDlst_shadowControl_c::mSimpleTexObj); + // } + + // Reset the active dlist + dComIfGd_setList(); + + return 1; +} + +static cPhs_Step maObj_Wrock_Create(void* i_this) { + return static_cast(i_this)->create(); +} + +static int maObj_Wrock_Delete(void* i_this) { + return static_cast(i_this)->Delete(); +} + +static int maObj_Wrock_Execute(void* i_this) { + return static_cast(i_this)->Execute(); +} + +static int maObj_Wrock_Draw(void* i_this) { + return static_cast(i_this)->Draw(); +} + +static int maObj_Wrock_IsDelete(void*) { + return 1; +} + +s16 maObj_Wrock_c::sProcName = -1; +ActorHandle maObj_Wrock_c::sActorHandle = -1; +const ActorProfileDesc maObj_Wrock_c::sProfile = {.name = MAOBJ_WROCK_NAME, + .priority_group = 7, + .process_size = sizeof(maObj_Wrock_c), + .draw_priority = fpcDwPi_OBJ_LBOX_e, // An unused draw priority + .status = fopAcStts_UNK_0x40000_e | fopAcStts_UNK_0x4000_e | fopAcStts_CULL_e, + .group = fopAc_ACTOR_e, + .cull_type = fopAc_CULLBOX_CUSTOM_e, + .create_function = maObj_Wrock_Create, + .delete_function = maObj_Wrock_Delete, + .execute_function = maObj_Wrock_Execute, + .is_delete_function = maObj_Wrock_IsDelete, + .draw_function = maObj_Wrock_Draw}; diff --git a/mods/custom_actor_demo/src/m_a_obj_wrock.hpp b/mods/custom_actor_demo/src/m_a_obj_wrock.hpp new file mode 100644 index 0000000000..1e18d36f5f --- /dev/null +++ b/mods/custom_actor_demo/src/m_a_obj_wrock.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "mods/svc/actor.h" + +// Base actor class definitions +#include "f_op/f_op_actor.h" + +// Definitions for request_of_phase_process_class and cPhs_Step +#include "SSystem/SComponent/c_phase.h" + +// Definitions for collision +#include "d/d_bg_s_acch.h" +#include "d/d_bg_w.h" + +#define MAOBJ_WROCK_NAME "wrock" + +class maObj_Wrock_c : public fopAc_ac_c { +public: + request_of_phase_process_class mPhase; + J3DModel* mpModel; + dBgS_ObjAcch mAcch; + cBgS_GndChk mGndChk; + dBgS_AcchCir mAcchCir; + Mtx mColliderMtx; + dBgW* mpCollider; + f32 mGroundH; + int mShadow; + + virtual ~maObj_Wrock_c(); + cPhs_Step create(); + int CreateHeap(); + int Delete(); + int Execute(); + int Draw(); + static int createHeapCallBack(fopAc_ac_c*); + + static s16 sProcName; + static ActorHandle sActorHandle; + static const ActorProfileDesc sProfile; +}; diff --git a/mods/custom_actor_demo/src/mod.cpp b/mods/custom_actor_demo/src/mod.cpp new file mode 100644 index 0000000000..971ad6e4f0 --- /dev/null +++ b/mods/custom_actor_demo/src/mod.cpp @@ -0,0 +1,85 @@ +#include "mods/service.hpp" +#include "mods/svc/actor.h" +#include "mods/svc/log.hpp" +#include "mods/svc/stage.h" + +#include "d/d_com_inf_game.h" + +#include "m_a_mine.hpp" +#include "m_a_obj_wrock.hpp" + +#include + +DEFINE_MOD(); +IMPORT_SERVICE(LogService, svc_log); +IMPORT_SERVICE(ActorService, svc_actor); +IMPORT_SERVICE(StageService, svc_stage); + +extern "C" { +MOD_EXPORT ModResult mod_initialize(ModError*) { + if (svc_actor->register_actor(mod_ctx, &maObj_Wrock_c::sProfile, &maObj_Wrock_c::sProcName, + &maObj_Wrock_c::sActorHandle) != MOD_OK) + { + mods::log::error("Failed to register actor wrock!"); + return MOD_ERROR; + } + + const stage_actor_data_class wrockParams{ + .name = MAOBJ_WROCK_NAME, + .base = + { + .parameters = 0, + .position = {-14324.0f, 0.0f, 341.0f}, + .angle = {0, -16595, 0}, + .setID = 0xFFFF, + }, + }; + if (svc_stage->add_actor( + mod_ctx, "F_SP108", 0, -1, &wrockParams, sizeof(wrockParams), nullptr) != MOD_OK) + { + mods::log::error("Adding wrock to F_SP108 Failed!"); + return MOD_ERROR; + } + + if (svc_actor->register_actor(mod_ctx, &ma_Mine_c::sProfile, &ma_Mine_c::sProcName, + &ma_Mine_c::sActorHandle) != MOD_OK) + { + mods::log::error("Failed to register actor " MA_MINE_NAME); + return MOD_ERROR; + } + + static const std::array minePositions = { + {{-14445.0f, 11.0f, 1304.0f}, {-14557.0f, 7.0f, 990.0f}, {-14790.0f, 7.0f, 634.0f}, + {-14829.0f, 0.0f, 144.0f}, {-14615.0f, 0.0f, -170.0f}, {-14283.0f, 10.0f, -420.0f}}}; + for (const auto& pos : minePositions) { + const stage_actor_data_class mineParams{ + .name = MA_MINE_NAME, + .base = + { + .parameters = 0, + .position = {pos.x, pos.y, pos.z}, + .angle = {0x2000, (s16)(pos.x*100000), 0}, // Adjusted and seemingly random angle + .setID = 0xFFFF, + }, + }; + if (svc_stage->add_actor( + mod_ctx, "F_SP108", 0, -1, &mineParams, sizeof(mineParams), nullptr) != MOD_OK) + { + mods::log::error("Adding mine to F_SP108 Failed!"); + return MOD_ERROR; + } + } + + mods::log::info("custom_actor_demo initialized"); + return MOD_OK; +} + +MOD_EXPORT ModResult mod_update(ModError*) { + return MOD_OK; +} + +MOD_EXPORT ModResult mod_shutdown(ModError*) { + mods::log::info("custom_actor_demo shutdown"); + return MOD_OK; +} +} diff --git a/mods/luau_runtime/CMakeLists.txt b/mods/luau_runtime/CMakeLists.txt new file mode 100644 index 0000000000..f7729d3db2 --- /dev/null +++ b/mods/luau_runtime/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.25) +project(luau_runtime CXX) + +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root") + option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + if (DUSK_MOD_USE_FULL_TREE) + add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL) + else () + add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL) + endif () +endif () + +include(FetchContent) + +set(LUAU_BUILD_CLI OFF CACHE BOOL "" FORCE) +set(LUAU_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(LUAU_BUILD_WEB OFF CACHE BOOL "" FORCE) +set(LUAU_WERROR OFF CACHE BOOL "" FORCE) +FetchContent_Declare(luau + URL https://github.com/luau-lang/luau/archive/refs/tags/0.734.tar.gz + URL_HASH SHA256=cb55a891226d8c70284e22eb9281cc2b4496c709a4050f52aaa18a355fe7b1a3 + DOWNLOAD_EXTRACT_TIMESTAMP FALSE + EXCLUDE_FROM_ALL +) +FetchContent_MakeAvailable(luau) + +FetchContent_Declare(luabridge3 + URL https://github.com/TwilitRealm/LuaBridge3/archive/refs/tags/twilit-v1.zip + URL_HASH SHA256=aa65e6e3446363d03842134182db28e6206e08d366b8efb8f66d26f96a3314be + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) +FetchContent_MakeAvailable(luabridge3) + +add_mod(luau_runtime + SOURCES + src/services/bindings.cpp + src/services/config.cpp + src/services/config.hpp + src/services/services.cpp + src/services/services.hpp + src/services/ui.cpp + src/services/ui.hpp + src/lua_bridge_helpers.cpp + src/lua_bridge_helpers.hpp + src/lua_helpers.cpp + src/lua_helpers.hpp + src/runtime.hpp + src/runtime.cpp + MOD_JSON mod.json + RES_DIR res + BUNDLE +) + +target_precompile_headers(luau_runtime PRIVATE src/lua_bridge_helpers.hpp src/lua_helpers.hpp) + +# As far as I can tell, Luau already does stack checks. +target_compile_definitions(luau_runtime PRIVATE LUABRIDGE_SAFE_STACK_CHECKS=0) +add_subdirectory(src/services/audio_res) + +target_link_libraries(luau_runtime PRIVATE Luau.VM Luau.Compiler LuaBridge) diff --git a/mods/luau_runtime/mod.json b/mods/luau_runtime/mod.json new file mode 100644 index 0000000000..788a57f554 --- /dev/null +++ b/mods/luau_runtime/mod.json @@ -0,0 +1,7 @@ +{ + "id": "dev.twilitrealm.luau", + "name": "Luau Support", + "version": "1.0.0", + "author": "Twilit Realm", + "description": "Luau script runtime for mods" +} diff --git a/mods/luau_runtime/res/licenses/LUAU-MIT.txt b/mods/luau_runtime/res/licenses/LUAU-MIT.txt new file mode 100644 index 0000000000..2f2aa17066 --- /dev/null +++ b/mods/luau_runtime/res/licenses/LUAU-MIT.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019-2025 Roblox Corporation +Copyright (c) 1994-2019 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/mods/luau_runtime/src/lua_bridge_helpers.cpp b/mods/luau_runtime/src/lua_bridge_helpers.cpp new file mode 100644 index 0000000000..6825fd08f8 --- /dev/null +++ b/mods/luau_runtime/src/lua_bridge_helpers.cpp @@ -0,0 +1,38 @@ +#include "lua_bridge_helpers.hpp" + +luabridge::TypeResult> +luabridge::Stack::get(lua_State* L, int) { + auto& ref = luau_runtime::vm_from_registry(L); + return std::reference_wrapper(ref); +} + +luabridge::TypeResult> +luabridge::Stack::get(lua_State* L, int) { + auto const& ref = luau_runtime::vm_from_registry(L); + return std::reference_wrapper(ref); +} + +namespace luau_runtime { + +BridgeScriptHandle::BridgeScriptHandle(uint64_t const handle) noexcept : handle(handle) {} + +BridgeScriptHandle::~BridgeScriptHandle() = default; + +void BridgeScriptHandle::unregister(lua_State* state, Vm& vm) { + if (handle == 0) { + luaL_error(state, "Stale handle"); + } + + try { + unregister_impl(state, vm); + } catch (std::exception const&) { + // If an exception occurs, disallow the Lua code from attempting to re-run unregister. + // Since we assume the object may be in an undefined state (but hopefully not?) + handle = 0; + throw; + } + + handle = 0; +} + +} // namespace luau_runtime diff --git a/mods/luau_runtime/src/lua_bridge_helpers.hpp b/mods/luau_runtime/src/lua_bridge_helpers.hpp new file mode 100644 index 0000000000..2f51274573 --- /dev/null +++ b/mods/luau_runtime/src/lua_bridge_helpers.hpp @@ -0,0 +1,112 @@ +#pragma once + +// clang-format off +#include "lua.h" +#include "lualib.h" +#include "LuaBridge/LuaBridge.h" +// clang-format on +#include "runtime.hpp" + +/** + * Defines (i.e. implements) a LuaBridge3 Stack converter for an enum that is passed by string + * name. + * + * @param type The type name of the enum to convert. + * @param names The constant field containing the string name <-> enum value mapping. + * @see DECLARE_STRING_ENUM + */ +#define DEFINE_STRING_ENUM(type, names) \ + Result Stack::push(lua_State* L, type value) { \ + return Stack::push( \ + L, ::luau_runtime::enum_value_to_str(L, value, names)); \ + } \ + \ + TypeResult Stack::get(lua_State* L, int index) { \ + const char* str = lua_tolstring(L, index, nullptr); \ + return ::luau_runtime::enum_str_to_value(L, str, names); \ + } + +/** + * Declares a LuaBridge3 Stack converter for an enum that is passed by string name. + * + * @param type The type name of the enum to convert. + * @see DEFINE_STRING_ENUM + */ +#define DECLARE_STRING_ENUM(type) \ + template <> \ + struct Stack { \ + [[nodiscard]] static Result push(lua_State* L, type value); \ + [[nodiscard]] static TypeResult get(lua_State* L, int index); \ + } + +/** + * Template specialization to allow luau_runtime::Vm& to be directly acquired + * from a LuaBridge3 function. + */ +template <> +struct luabridge::Stack { + [[nodiscard]] static TypeResult> get( + lua_State* L, int); +}; + +/** + * Template specialization to allow luau_runtime::Vm const& to be directly acquired + * from a LuaBridge3 function. + */ +template <> +struct luabridge::Stack { + [[nodiscard]] static TypeResult> get( + lua_State* L, int); +}; + +namespace luau_runtime { + +/** + * Base type for "handle" types using LuaBridge3. + * + * Inherit from it, then register your derived class with LuaBridge3. + * Provide the base class @ref unregister member function. + */ +class BridgeScriptHandle { +protected: + uint64_t handle; + explicit BridgeScriptHandle(uint64_t handle) noexcept; + virtual void unregister_impl(lua_State* state, Vm& vm) = 0; + +public: + virtual ~BridgeScriptHandle(); + void unregister(lua_State* state, Vm& vm); +}; + +} // namespace luau_runtime + +/** + * Convert a @ref luaBridge::LuaRef to an *optional* value. + * + * This uses the default LuaBridge3 handling for std::optional, + * but just makes the type specification and casting a bit less verbose. + * + * @tparam T The type contained in the resulting optional. + * @tparam TLuaRef The type of the lua ref. Let this be inferred, as table items are unnameable. + * @param value Lua ref to convert to the desired type. + */ +template +std::optional opt(TLuaRef const& value) { + return value.template cast>().value(); +} + +/** + * Convert a @ref luaBridge::LuaRef to a value, with a default fallback if nil. + * + * This uses the default LuaBridge3 handling for std::optional, + * but just makes the type specification and casting a bit less verbose. + * + * @tparam T The resulting type. + * @tparam TLuaRef The type of the lua ref. Let this be inferred, as table items are unnameable. + * @param value Lua ref to convert to the desired type. + * @param default_value The default value if the lua item is nil. + */ +template +T opt_or(TLuaRef const& value, T const& default_value) { + return opt(value).value_or(default_value); +} diff --git a/mods/luau_runtime/src/lua_helpers.cpp b/mods/luau_runtime/src/lua_helpers.cpp new file mode 100644 index 0000000000..e09f582945 --- /dev/null +++ b/mods/luau_runtime/src/lua_helpers.cpp @@ -0,0 +1,118 @@ +#include "lua_helpers.hpp" + +#include + +namespace luau_runtime { + +bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback) { + lua_getfield(state, table, field); + const bool value = lua_isnil(state, -1) ? fallback : luaL_checkboolean(state, -1) != 0; + lua_pop(state, 1); + return value; +} + +bool to_int64(lua_State* state, int index, int64_t& outValue) { + if (lua_isinteger64(state, index)) { + outValue = lua_tointeger64(state, index, nullptr); + return true; + } + if (!lua_isnumber(state, index)) { + return false; + } + const double value = lua_tonumber(state, index); + constexpr double kMaxSafeInteger = 9007199254740991.0; + if (!std::isfinite(value) || value < -kMaxSafeInteger || value > kMaxSafeInteger || + std::trunc(value) != value) + { + return false; + } + outValue = static_cast(value); + return true; +} + +int64_t check_int64(lua_State* state, int index) { + int64_t value = 0; + if (!to_int64(state, index, value)) { + luaL_argerror(state, index, "integer value expected"); + } + return value; +} + +int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback) { + lua_getfield(state, table, field); + const int64_t value = lua_isnil(state, -1) ? fallback : check_int64(state, -1); + lua_pop(state, 1); + return value; +} + +double get_optional_number(lua_State* state, int table, const char* field, double fallback) { + lua_getfield(state, table, field); + const double value = lua_isnil(state, -1) ? fallback : luaL_checknumber(state, -1); + lua_pop(state, 1); + return value; +} + +double get_number(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + const double value = luaL_checknumber(state, -1); + lua_pop(state, 1); + return value; +} + +uint32_t get_optional_uint32(lua_State* state, int table, const char* field, uint32_t fallback) { + lua_getfield(state, table, field); + const uint32_t value = lua_isnil(state, -1) ? fallback : luaL_checkunsigned(state, -1); + lua_pop(state, 1); + return value; +} + +uint32_t get_uint32(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + const uint32_t value = luaL_checkunsigned(state, -1); + lua_pop(state, 1); + return value; +} + +int32_t get_optional_int32(lua_State* state, int table, const char* field, int32_t fallback) { + lua_getfield(state, table, field); + const int32_t value = lua_isnil(state, -1) ? fallback : luaL_checkinteger(state, -1); + lua_pop(state, 1); + return value; +} + +std::string get_optional_string( + lua_State* state, int table, const char* field, std::string_view fallback) { + std::string result; + lua_getfield(state, table, field); + if (!lua_isnil(state, -1)) { + size_t length = 0; + const char* value = luaL_checklstring(state, -1, &length); + result.assign(value, length); + } else { + result = fallback; + } + lua_pop(state, 1); + return result; +} + +int ref_optional_function(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + if (lua_isnil(state, -1)) { + lua_pop(state, 1); + return LUA_NOREF; + } + luaL_argexpected(state, lua_isfunction(state, -1), table, "function field"); + const int ref = lua_ref(state, -1); + lua_pop(state, 1); + return ref; +} + +int ref_required_function(lua_State* state, int table, const char* field) { + const int ref = ref_optional_function(state, table, field); + if (ref == LUA_NOREF) { + luaL_error(state, "field '%s' is required", field); + } + return ref; +} + +} // namespace luau_runtime diff --git a/mods/luau_runtime/src/lua_helpers.hpp b/mods/luau_runtime/src/lua_helpers.hpp new file mode 100644 index 0000000000..52e3b8210f --- /dev/null +++ b/mods/luau_runtime/src/lua_helpers.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include "lua.h" +#include "lualib.h" + +#include +#include + +/* + * Helper functions for API bound between Lua <-> C++ + */ + +namespace luau_runtime { + +bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback); +double get_optional_number(lua_State* state, int table, const char* field, double fallback); +int32_t get_optional_int32(lua_State* state, int table, const char* field, int32_t fallback); +uint32_t get_optional_uint32(lua_State* state, int table, const char* field, uint32_t fallback); +std::string get_optional_string( + lua_State* state, int table, const char* field, std::string_view fallback = {}); + +bool to_int64(lua_State* state, int index, int64_t& outValue); +int64_t check_int64(lua_State* state, int index); +int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback); +int ref_optional_function(lua_State* state, int table, const char* field); +int ref_required_function(lua_State* state, int table, const char* field); + +double get_number(lua_State* state, int table, const char* field); +uint32_t get_uint32(lua_State* state, int table, const char* field); + +/** + * Concept that demands things be an enum. C++ is such a cool language. + */ +template +concept Enum = std::is_enum_v; + +/** + * Specifies a name/value pair for an enum. This is used by helpers to do automatic conversions + * between the native and (string) Lua form. + * @tparam T The enum type + */ +template +struct EnumName { + T value; + std::string_view name; + + constexpr EnumName(T value, std::string_view name) : value(value), name(name) {} +}; + +/** + * Convert an enum's string name to the actual C++ value. + * + * Raises a Lua error if the string is not mapped. + * + * @tparam T Type of the enum + * @param state The Lua state. + * @param str The string value, from Lua. + * @param options The predefined set of possible enum names. + */ +template +T enum_str_to_value(lua_State* state, char const* str, EnumName const (&options)[N]) { + std::string_view strv(str); + for (size_t i = 0; i < N; ++i) { + if (options[i].name == strv) { + return static_cast(i); + } + } + + luaL_errorL(state, "Invalid enum value for %s: '%s'", typeid(T).name(), str); +} + +/** + * Convert an enum's C++ value to the Lua-exposed string. + * + * Raises a Lua error if the string is not mapped. + * + * @tparam T Type of the enum + * @param state The Lua state. + * @param value The C++ enum value. + * @param options The predefined set of possible enum names. + */ +template +std::string_view enum_value_to_str(lua_State* state, T value, EnumName const (&options)[N]) { + for (auto [nameValue, name] : options) { + if (nameValue == value) { + return name; + } + } + + luaL_errorL(state, "Attempted to return invalid enum to Lua!"); +} + +} // namespace luau_runtime diff --git a/mods/luau_runtime/src/runtime.cpp b/mods/luau_runtime/src/runtime.cpp new file mode 100644 index 0000000000..87c103e3b3 --- /dev/null +++ b/mods/luau_runtime/src/runtime.cpp @@ -0,0 +1,612 @@ +#include "runtime.hpp" +#include "services/services.hpp" + +#include "Luau/Common.h" +#include "luacode.h" +#include "mods/runtime.h" +#include "mods/service.hpp" +#include "mods/svc/audio_res.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LUAU_FASTFLAG(LuauIntegerLibrary) +LUAU_FASTFLAG(LuauIntegerType2) + +DEFINE_MOD(); +IMPORT_OPTIONAL_SERVICE(LogService, svc_log); +IMPORT_OPTIONAL_SERVICE(HostService, svc_host); +IMPORT_OPTIONAL_SERVICE(ConfigService, svc_config); +IMPORT_OPTIONAL_SERVICE(ResourceService, svc_resource); +IMPORT_OPTIONAL_SERVICE(OverlayService, svc_overlay); +IMPORT_OPTIONAL_SERVICE(TextureService, svc_texture); +IMPORT_OPTIONAL_SERVICE(UiService, svc_ui); +IMPORT_OPTIONAL_SERVICE(AudioResService, svc_audio_res); + +namespace luau_runtime { +namespace { + +// Just here to provide a globally-unique address to use as a registry index. +// Index into the Lua registry to store the Vm*. +int vm_registry_index; + +std::unordered_map> s_vms; + +void* limited_realloc(void* userData, void* pointer, size_t oldSize, size_t newSize) { + auto& budget = *static_cast(userData); + if (newSize == 0) { + std::free(pointer); + budget.used -= std::min(budget.used, oldSize); + return nullptr; + } + + const size_t growth = newSize > oldSize ? newSize - oldSize : 0; + if (growth > budget.limit - std::min(budget.used, budget.limit)) { + return nullptr; + } + void* result = std::realloc(pointer, newSize); + if (result != nullptr) { + budget.used -= std::min(budget.used, oldSize); + budget.used += newSize; + } + return result; +} + +int traceback_handler(lua_State* state) { + const char* message = lua_tostring(state, 1); + luaL_traceback(state, state, message != nullptr ? message : "Luau error", 1); + return 1; +} + +class DeadlineScope { +public: + DeadlineScope(Vm& vm, std::chrono::steady_clock::duration budget) + : m_vm{vm}, m_previousDeadline{vm.deadline}, m_previousActive{vm.deadlineActive} { + const auto requested = std::chrono::steady_clock::now() + budget; + if (!vm.deadlineActive || requested < vm.deadline) { + vm.deadline = requested; + } + vm.deadlineActive = true; + ++vm.callDepth; + } + + ~DeadlineScope() { + --m_vm.callDepth; + m_vm.deadline = m_previousDeadline; + m_vm.deadlineActive = m_previousActive; + } + +private: + Vm& m_vm; + std::chrono::steady_clock::time_point m_previousDeadline; + bool m_previousActive; +}; + +bool protected_call(lua_State* state, Vm& vm, int argumentCount, int resultCount, + std::chrono::steady_clock::duration budget, std::string& outError) { + const int functionIndex = lua_gettop(state) - argumentCount; + lua_pushcfunction(state, traceback_handler, "dusklight traceback"); + lua_insert(state, functionIndex); + + DeadlineScope deadline{vm, budget}; + const int status = lua_pcall(state, argumentCount, resultCount, functionIndex); + if (status != LUA_OK) { + const char* message = lua_tostring(state, -1); + outError = message != nullptr ? message : "unknown Luau error"; + lua_pop(state, 1); + lua_remove(state, functionIndex); + return false; + } + lua_remove(state, functionIndex); + return true; +} + +std::optional normalize_module_path( + std::string_view currentPath, std::string_view requested) { + if ((!requested.starts_with("./") && !requested.starts_with("../")) || + requested.find('\\') != std::string_view::npos) + { + return std::nullopt; + } + + std::vector parts; + const auto parentEnd = currentPath.rfind('/'); + std::string combined; + if (parentEnd != std::string_view::npos) { + combined.assign(currentPath.substr(0, parentEnd + 1)); + } + combined.append(requested); + + size_t begin = 0; + while (begin <= combined.size()) { + const size_t end = combined.find('/', begin); + const auto part = std::string_view{combined}.substr( + begin, end == std::string::npos ? combined.size() - begin : end - begin); + if (part.empty() || part == ".") { + } else if (part == "..") { + if (parts.empty()) { + return std::nullopt; + } + parts.pop_back(); + } else { + parts.push_back(part); + } + if (end == std::string::npos) { + break; + } + begin = end + 1; + } + if (parts.empty()) { + return std::nullopt; + } + + std::string normalized; + for (const auto part : parts) { + if (!normalized.empty()) { + normalized.push_back('/'); + } + normalized.append(part); + } + + return normalized; +} + +std::optional try_load_direct_lua_files(lua_State* state, ModContext* mod, char const* modName, std::string const& fileWithoutExt) { + // Try .luau first. + auto try_luau = fileWithoutExt + ".luau"; + std::optional found_path; + if (svc_resource->file_exists(mod, try_luau.c_str())) { + found_path = try_luau; + } + + // Try .lua, if both it and .luau exist, there's a conflict. + auto try_lua = fileWithoutExt + ".lua"; + if (svc_resource->file_exists(mod, try_lua.c_str())) { + if (found_path.has_value()) { + luaL_error(state, "Cannot load module '%s' if both '%s' and '%s' exist", modName, try_luau.c_str(), try_lua.c_str()); + } + + found_path = try_lua; + } + + return found_path; +} + +std::string resolve_module_path(lua_State* state, ModContext* mod, std::string_view currentPath, std::string_view requested) { + // For reference: https://github.com/luau-lang/rfcs/blob/79c722717ffd96e5c5b5a9493ff01fda8a5171be/docs/amended-require-resolution.md + + // Normalize to remove ./ and ../ and such. Should be a path relative to res/ after this. + auto normalized = normalize_module_path(currentPath, requested); + if (!normalized.has_value()) { + luaL_error(state, "module paths must be relative and remain inside the mod's res directory"); + } + + auto has_lua_ext = normalized->ends_with(".lua") || normalized->ends_with(".luau"); + if (has_lua_ext) { + // No fancy logic in this case. + if (!svc_resource->file_exists(mod, normalized->c_str())) { + luaL_error(state, "Cannot load '%s': file does not exist", normalized->c_str()); + } + + return std::move(*normalized); + } + + auto found_path = try_load_direct_lua_files(state, mod, normalized->c_str(), *normalized); + if (found_path.has_value()) { + // If a .lua(u) file *and* a matching directory exist, it's a conflict. + if (svc_resource->directory_exists(mod, normalized->c_str())) { + luaL_error(state, "Cannot load '%s' because conflicting directory '%s' also exists", found_path->c_str(), normalized->c_str()); + } + } else { + // Check for directory/init.lua(u) + auto initPath = *normalized + "/init"; + + found_path = try_load_direct_lua_files(state, mod, normalized->c_str(), initPath); + } + + if (!found_path.has_value()) { + luaL_error(state, "Cannot load '%s': unable to locate '%s.lua(u)' or '%s/init.lua(u)'", + found_path->c_str(), normalized->c_str(), normalized->c_str()); + } + + return std::move(*found_path); +} + +int module_require(lua_State* state); +int print(lua_State* state); + +void install_module_require(lua_State* state, Vm& vm, std::string_view currentPath) { + lua_pushlightuserdata(state, &vm); + lua_pushlstring(state, currentPath.data(), currentPath.size()); + lua_pushcclosure(state, module_require, "require", 2); + lua_setglobal(state, "require"); +} + +void install_globals(Vm& vm) { + push_vm_closure(vm.state, vm, print, "print"); + lua_setglobal(vm.state, "print"); +} + +bool load_source_module( + lua_State* caller, Vm& vm, const std::string& path, bool keepResult, std::string& outError) { + if (svc_resource == nullptr) { + outError = "ResourceService is not available in this Dusklight build"; + return false; + } + + ResourceBuffer buffer = RESOURCE_BUFFER_INIT; + const ModResult loadResult = svc_resource->load(vm.subject, path.c_str(), &buffer); + if (loadResult != MOD_OK) { + outError = loadResult == MOD_UNAVAILABLE ? "module not found: res/" + path : + "failed to load module: res/" + path; + return false; + } + std::string source; + if (buffer.data != nullptr) { + source.assign(static_cast(buffer.data), buffer.size); + } + svc_resource->free(vm.subject, &buffer); + + size_t bytecodeSize = 0; + lua_CompileOptions options{}; + options.optimizationLevel = 1; + options.debugLevel = 1; + char* bytecode = luau_compile(source.data(), source.size(), &options, &bytecodeSize); + if (bytecode == nullptr) { + outError = "Luau compiler ran out of memory"; + return false; + } + + lua_State* moduleState = lua_newthread(caller); + if (moduleState == nullptr) { + std::free(bytecode); + outError = "Luau VM ran out of memory"; + return false; + } + luaL_sandboxthread(moduleState); + install_module_require(moduleState, vm, path); + + const std::string chunkName = "@res/" + path; + const int loadStatus = luau_load(moduleState, chunkName.c_str(), bytecode, bytecodeSize, 0); + std::free(bytecode); + if (loadStatus != LUA_OK) { + const char* message = lua_tostring(moduleState, -1); + outError = message != nullptr ? message : "failed to load Luau bytecode"; + lua_pop(caller, 1); + return false; + } + if (!protected_call(moduleState, vm, 0, keepResult ? 1 : 0, kLifecycleBudget, outError)) { + lua_pop(caller, 1); + return false; + } + if (!keepResult) { + lua_pop(caller, 1); + return true; + } + if (lua_isnil(moduleState, -1)) { + outError = "module res/" + path + " must return a value"; + lua_pop(caller, 1); + return false; + } + + lua_xmove(moduleState, caller, 1); + lua_remove(caller, -2); + return true; +} + +int module_require(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + const std::string requested = luaL_checkstring(state, 1); + + std::string moduleName; + ModuleOpenFn factory = nullptr; + if (requested.starts_with("dusklight.")) { + moduleName = requested; + factory = services::module_factory(moduleName); + if (factory == nullptr) { + luaL_error(state, "unknown module '%s'", moduleName.c_str()); + } + } else { + const char* currentPath = lua_tostring(state, lua_upvalueindex(2)); + moduleName = resolve_module_path(state, vm.subject, currentPath != nullptr ? currentPath : "main.luau", requested); + } + + if (const auto found = vm.moduleRefs.find(moduleName); found != vm.moduleRefs.end()) { + lua_getref(state, found->second); + return 1; + } + if (!vm.loadingModules.insert(moduleName).second) { + luaL_error(state, "cyclic require of '%s'", moduleName.c_str()); + } + + std::string error; + if (factory != nullptr) { + factory(state); + } else if (!load_source_module(state, vm, moduleName, true, error)) { + vm.loadingModules.erase(moduleName); + luaL_error(state, "%s", error.c_str()); + } + vm.loadingModules.erase(moduleName); + + const int ref = lua_ref(state, -1); + vm.moduleRefs.emplace(moduleName, ref); + return 1; +} + +int print(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_log == nullptr) { + service_unavailable(state, "LogService"); + } + + std::string value; + int const numArgs = lua_gettop(state); + for (int i = 0; i < numArgs; i += 1) { + if (i != 0) { + value.push_back('\t'); + } + + size_t length; + char const* str = luaL_tolstring(state, i + 1, &length); + value.append(str, length); + lua_pop(state, 1); + } + + svc_log->info(vm.subject, value.data()); + return 0; +} + +ModResult runtime_activate(ModContext*, ModContext* subject, ModError* outError) { + if (subject == nullptr) { + return set_error(outError, MOD_INVALID_ARGUMENT, "Delegated mod context is null"); + } + if (s_vms.contains(subject)) { + return set_error(outError, MOD_CONFLICT, "Delegated mod is already active"); + } + + auto vm = std::make_unique(); + vm->subject = subject; + vm->state = lua_newstate(limited_realloc, &vm->memory); + if (vm->state == nullptr) { + return set_error(outError, MOD_ERROR, "Failed to create Luau VM"); + } + lua_pushlightuserdata(vm->state, vm.get()); + lua_rawsetp(vm->state, LUA_REGISTRYINDEX, &vm_registry_index); + lua_callbacks(vm->state)->userdata = vm.get(); +#if NDEBUG // Annoying for debuggers + lua_callbacks(vm->state)->interrupt = [](lua_State* state, int gc) { + auto* current = static_cast(lua_callbacks(state)->userdata); + if (gc < 0 && current != nullptr && current->deadlineActive && + std::chrono::steady_clock::now() > current->deadline) + { + luaL_error(state, "script execution exceeded its time budget"); + } + }; +#endif + luaL_openlibs(vm->state); + install_globals(*vm); + luaL_sandbox(vm->state); + + std::string error; + vm->loadingModules.insert("main.luau"); + const bool loadedMain = load_source_module(vm->state, *vm, "main.luau", false, error); + vm->loadingModules.erase("main.luau"); + if (!loadedMain) { + return set_error(outError, MOD_ERROR, error); + } + + s_vms.emplace(subject, std::move(vm)); + return MOD_OK; +} + +ModResult runtime_update(ModContext*, ModContext* subject, ModError* outError) { + const auto found = s_vms.find(subject); + if (found == s_vms.end()) { + return set_error(outError, MOD_INVALID_ARGUMENT, "Delegated mod is not active"); + } + Vm& vm = *found->second; + const size_t callbackCount = vm.updateRefs.size(); + if (callbackCount == 0) { + return MOD_OK; + } + + DeadlineScope deadline{vm, kUpdateBudget}; + std::string error; + for (size_t i = 0; i < callbackCount; ++i) { + const int ref = vm.updateRefs[i]; + if (!call_ref(vm, ref, 0, 0, kUpdateBudget, error)) { + return set_error(outError, MOD_ERROR, error); + } + } + return MOD_OK; +} + +ModResult runtime_deactivate(ModContext*, ModContext* subject, ModError*) { + const auto found = s_vms.find(subject); + if (found == s_vms.end()) { + return MOD_OK; + } + + { + Vm& vm = *found->second; + DeadlineScope deadline{vm, kLifecycleBudget}; + const size_t callbackCount = vm.shutdownRefs.size(); + for (size_t i = callbackCount; i > 0; --i) { + std::string error; + const int ref = vm.shutdownRefs[i - 1]; + if (!call_ref(vm, ref, 0, 0, kLifecycleBudget, error) && svc_log != nullptr) { + svc_log->write(subject, LOG_LEVEL_ERROR, error.c_str()); + } + } + } + s_vms.erase(found); + return MOD_OK; +} + +constexpr ModRuntimeService s_runtimeService{ + .header = SERVICE_HEADER(ModRuntimeService, 1, 0), + .activate = runtime_activate, + .update = runtime_update, + .deactivate = runtime_deactivate, +}; +EXPORT_SERVICE_AS(s_runtimeService, "dev.twilitrealm.luau"); + +} // namespace + +Vm::~Vm() { + if (state != nullptr) { + lua_close(state); + } +} + +Vm& vm_from_upvalue(lua_State* state) { + auto* vm = static_cast(lua_tolightuserdata(state, lua_upvalueindex(1))); + if (vm == nullptr) { + luaL_error(state, "missing Luau runtime context"); + } + return *vm; +} + +Vm& vm_from_registry(lua_State* state) { + lua_rawgetp(state, LUA_REGISTRYINDEX, &vm_registry_index); + auto* vm = static_cast(lua_tolightuserdata(state, -1)); + if (vm == nullptr) { + luaL_error(state, "missing Luau runtime context"); + } + lua_pop(state, 1); + return *vm; +} + +void push_vm_closure(lua_State* state, Vm& vm, lua_CFunction function, const char* name) { + lua_pushlightuserdata(state, &vm); + lua_pushcclosure(state, function, name, 1); +} + +void set_function(lua_State* state, Vm& vm, const char* name, lua_CFunction function) { + push_vm_closure(state, vm, function, name); + lua_setfield(state, -2, name); +} + +[[noreturn]] void service_unavailable(lua_State* state, const char* name) { + luaL_error(state, "%s is not available in this Dusklight build", name); +} + +void check_result(lua_State* state, ModResult result, const char* operation) { + if (result == MOD_OK) { + return; + } + const char* resultName = "error"; + switch (result) { + case MOD_UNAVAILABLE: + resultName = "unavailable"; + break; + case MOD_UNSUPPORTED: + resultName = "unsupported"; + break; + case MOD_CONFLICT: + resultName = "conflict"; + break; + case MOD_INVALID_ARGUMENT: + resultName = "invalid argument"; + break; + default: + break; + } + luaL_error(state, "%s failed: %s", operation, resultName); +} + +Callback& retain_callback(Vm& vm) { + auto callback = std::make_unique(); + callback->vm = &vm; + callback->refs.fill(LUA_NOREF); + vm.callbacks.push_back(std::move(callback)); + return *vm.callbacks.back(); +} + +bool call_ref(Vm& vm, int ref, int argumentCount, int resultCount, + std::chrono::steady_clock::duration budget, std::string& outError) { + lua_State* state = vm.state; + lua_getref(state, ref); + if (!lua_isfunction(state, -1)) { + lua_pop(state, 1); + outError = "callback is no longer a function"; + return false; + } + if (argumentCount != 0) { + lua_insert(state, -argumentCount - 1); + } + return protected_call(state, vm, argumentCount, resultCount, budget, outError); +} + +void fail_callback(Vm& vm, std::string_view callbackName, std::string_view error) { + const std::string message = std::string{callbackName} + ": " + std::string{error}; + if (svc_host != nullptr) { + svc_host->fail(vm.subject, MOD_ERROR, message.c_str()); + } else if (svc_log != nullptr) { + svc_log->write(vm.subject, LOG_LEVEL_ERROR, message.c_str()); + } +} + +ModResult set_error(ModError* outError, ModResult result, std::string_view message) { + if (outError != nullptr && outError->struct_size >= sizeof(ModError)) { + outError->code = result; + const size_t size = std::min(message.size(), sizeof(outError->message) - 1); + std::memcpy(outError->message, message.data(), size); + outError->message[size] = '\0'; + } + return result; +} + +void create_handle_metatable( + lua_State* state, const char* name, const luaL_Reg* methods, const char* typeName) { + luaL_newmetatable(state, name); + luaL_register(state, nullptr, methods); + lua_pushvalue(state, -1); + lua_setfield(state, -2, "__index"); + lua_pushstring(state, typeName); + lua_setfield(state, -2, "__type"); + lua_setreadonly(state, -1, true); + lua_pop(state, 1); +} + +ScriptHandle& check_handle(lua_State* state, int index, const char* metatable, HandleKind kind) { + auto* handle = static_cast(luaL_checkudata(state, index, metatable)); + if (handle == nullptr || handle->kind != kind || handle->vm == nullptr || handle->value == 0) { + luaL_argerror(state, index, "stale handle"); + } + return *handle; +} + +void push_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind, const char* metatable, + ConfigVarType configType) { + auto* handle = static_cast(lua_newuserdata(state, sizeof(ScriptHandle))); + *handle = ScriptHandle{.vm = &vm, .value = value, .kind = kind, .configType = configType}; + luaL_getmetatable(state, metatable); + lua_setmetatable(state, -2); +} + +} // namespace luau_runtime + +extern "C" { +MOD_EXPORT ModResult mod_initialize(ModError*) { + FFlag::LuauIntegerType2.value = true; + FFlag::LuauIntegerLibrary.value = true; + return MOD_OK; +} + +MOD_EXPORT ModResult mod_update(ModError*) { + return MOD_OK; +} + +MOD_EXPORT ModResult mod_shutdown(ModError*) { + luau_runtime::s_vms.clear(); + return MOD_OK; +} +} diff --git a/mods/luau_runtime/src/runtime.hpp b/mods/luau_runtime/src/runtime.hpp new file mode 100644 index 0000000000..fd743efcfd --- /dev/null +++ b/mods/luau_runtime/src/runtime.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "lua.h" +#include "lualib.h" + +#include "mods/api.h" +#include "mods/svc/config.h" +#include "mods/svc/host.h" +#include "mods/svc/log.h" +#include "mods/svc/overlay.h" +#include "mods/svc/resource.h" +#include "mods/svc/texture.h" +#include "mods/svc/ui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace luau_runtime { + +constexpr size_t kMemoryLimit = 64u * 1024u * 1024u; +constexpr auto kUpdateBudget = std::chrono::milliseconds{250}; +constexpr auto kLifecycleBudget = std::chrono::seconds{5}; +constexpr auto kCallbackBudget = std::chrono::milliseconds{250}; + +struct MemoryBudget { + size_t used = 0; + size_t limit = kMemoryLimit; +}; + +struct Vm; + +struct Callback { + Vm* vm = nullptr; + std::array refs{}; + std::string returnedString; + int tag = 0; +}; + +struct Vm { + MemoryBudget memory; + lua_State* state = nullptr; + ModContext* subject = nullptr; + std::vector updateRefs; + std::vector shutdownRefs; + std::unordered_map moduleRefs; + std::unordered_set loadingModules; + std::vector> callbacks; + std::chrono::steady_clock::time_point deadline{}; + unsigned callDepth = 0; + bool deadlineActive = false; + + Vm() = default; + Vm(Vm const&) = delete; + Vm(Vm&&) = delete; + ~Vm(); +}; + +enum class HandleKind : uint8_t { + ConfigVar, + ConfigSubscription, + Overlay, + Texture, + UiWindow, + UiDialog, + UiElement, + UiStyle, + UiMenuTab, + UiList, + UiContextMenu, +}; + +struct ScriptHandle { + Vm* vm = nullptr; + uint64_t value = 0; + HandleKind kind = HandleKind::ConfigVar; + ConfigVarType configType = CONFIG_VAR_BOOL; +}; + +using ModuleOpenFn = int (*)(lua_State* state); + +Vm& vm_from_upvalue(lua_State* state); +Vm& vm_from_registry(lua_State* state); +void push_vm_closure(lua_State* state, Vm& vm, lua_CFunction function, const char* name); +void set_function(lua_State* state, Vm& vm, const char* name, lua_CFunction function); + +[[noreturn]] void service_unavailable(lua_State* state, const char* name); +void check_result(lua_State* state, ModResult result, const char* operation); + +Callback& retain_callback(Vm& vm); +bool call_ref(Vm& vm, int ref, int argumentCount, int resultCount, + std::chrono::steady_clock::duration budget, std::string& outError); +void fail_callback(Vm& vm, std::string_view callbackName, std::string_view error); +ModResult set_error(ModError* outError, ModResult result, std::string_view message); + +void create_handle_metatable( + lua_State* state, const char* name, const luaL_Reg* methods, const char* typeName); +ScriptHandle& check_handle(lua_State* state, int index, const char* metatable, HandleKind kind); +void push_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind, const char* metatable, + ConfigVarType configType = CONFIG_VAR_BOOL); + +} // namespace luau_runtime diff --git a/mods/luau_runtime/src/services/audio_res/CMakeLists.txt b/mods/luau_runtime/src/services/audio_res/CMakeLists.txt new file mode 100644 index 0000000000..e4a1029377 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(luau_runtime PRIVATE + audio_res.cpp + bst.cpp + bst.hpp + wsys.cpp + wsys.hpp +) diff --git a/mods/luau_runtime/src/services/audio_res/audio_res.cpp b/mods/luau_runtime/src/services/audio_res/audio_res.cpp new file mode 100644 index 0000000000..315f6c904c --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/audio_res.cpp @@ -0,0 +1,52 @@ +#include "../../lua_bridge_helpers.hpp" +#include "../../runtime.hpp" +#include "../services.hpp" +#include "LuaBridge/LuaBridge.h" + +#include "mods/svc/audio_res.h" + +#include "bst.hpp" +#include "wsys.hpp" + +namespace luau_runtime::services { +namespace { + +constexpr auto kServiceName = "AudioResService"; + +} + +int open_audio_res(lua_State* state) { + using namespace audio_res; + + if (svc_audio_res == nullptr) { + service_unavailable(state, kServiceName); + } + + lua_newtable(state); + + luabridge::getNamespaceFromStack(state) + // WSYS + .addVariable("DEFAULT_KEY", AUDIO_RES_DEFAULT_KEY) + .addFunction("default_wave_info", default_wave_info) + .addFunction("replace_wave", replace_wave) + .addFunction("add_wave", add_wave) + .beginClass("AudioWaveHandle") + .addFunction("unregister", &LuaAudioWaveHandle::unregister) + .endClass() + // BST + .addFunction("default_effect_info", default_effect_info) + .addFunction("replace_sound_table_effect", replace_sound_table_effect) + .addFunction("add_sound_table_effect", add_sound_table_effect) + .addVariable("STREAM_MAX_CHILDREN", STREAM_MAX_CHILDREN) + .addFunction("default_stream_info", default_stream_info) + .addFunction("replace_sound_table_stream", replace_sound_table_stream) + .addFunction("add_sound_table_stream", add_sound_table_stream) + .beginClass("AudioSoundTableHandle") + .addFunction("unregister", &LuaSoundTableHandle::unregister) + .endClass(); + + lua_setreadonly(state, -1, true); + return 1; +} + +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/audio_res/bst.cpp b/mods/luau_runtime/src/services/audio_res/bst.cpp new file mode 100644 index 0000000000..fe670e4a9f --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/bst.cpp @@ -0,0 +1,205 @@ +#include "bst.hpp" + +namespace luau_runtime::services::audio_res { + +LuaSoundTableHandle::LuaSoundTableHandle(AudioSoundTableHandle handle) + : BridgeScriptHandle(handle) {} + +void LuaSoundTableHandle::unregister_impl(lua_State*, Vm& vm) { + svc_audio_res->remove_sound_table(vm.subject, handle); +} + +AudioSoundTableEffectInfo const& default_effect_info() { + return *svc_audio_res->default_effect_info; +} + +LuaSoundTableHandle replace_sound_table_effect(SoundEffectCategory category_id, uint16_t effect_id, + std::optional info, lua_State* state, Vm& vm) { + AudioSoundTableEffectInfo const* effect_info = nullptr; + if (info.has_value()) { + effect_info = &*info; + } + + AudioSoundTableHandle handle; + + check_result(state, + svc_audio_res->replace_sound_table_effect( + vm.subject, category_id, effect_id, effect_info, &handle), + "audio_res.replace_sound_table_effect"); + + return LuaSoundTableHandle(handle); +} + +std::tuple add_sound_table_effect(SoundEffectCategory category_id, + std::optional info, lua_State* state, Vm& vm) { + AudioSoundTableEffectInfo const* effect_info = nullptr; + if (info.has_value()) { + effect_info = &*info; + } + + uint16_t out_effect_id; + AudioSoundTableHandle handle; + + check_result(state, + svc_audio_res->add_sound_table_effect( + vm.subject, category_id, effect_info, &handle, &out_effect_id), + "audio_res.add_sound_table_effect"); + + return {out_effect_id, LuaSoundTableHandle(handle)}; +} + +AudioSoundTableStreamInfo const& default_stream_info() { + return *svc_audio_res->default_stream_info; +} + +LuaSoundTableHandle replace_sound_table_stream(uint16_t stream_id, char const* file_path, + std::optional info, lua_State* state, Vm& vm) { + AudioSoundTableStreamInfo const* stream_info = nullptr; + if (info.has_value()) { + stream_info = &*info; + } + + AudioSoundTableHandle handle; + + check_result(state, + svc_audio_res->replace_sound_table_stream( + vm.subject, stream_id, file_path, stream_info, &handle), + "audio_res.replace_sound_table_stream"); + + return LuaSoundTableHandle(handle); +} + +std::tuple add_sound_table_stream(char const* file_path, + std::optional info, lua_State* state, Vm& vm) { + AudioSoundTableStreamInfo const* stream_info = nullptr; + if (info.has_value()) { + stream_info = &*info; + } + + uint16_t out_stream_id; + AudioSoundTableHandle handle; + + check_result(state, + svc_audio_res->add_sound_table_stream( + vm.subject, file_path, stream_info, &handle, &out_stream_id), + "audio_res.add_sound_table_stream"); + + return {out_stream_id, LuaSoundTableHandle(handle)}; +} + +} // namespace luau_runtime::services::audio_res + +namespace luabridge { +namespace { + +using namespace std::string_view_literals; + +constexpr luau_runtime::EnumName kSoundEffectCategoryNames[] = { + // clang-format off + { SE_CATEGORY_SYSTEM_SE, "system_se"sv }, + { SE_CATEGORY_PLAYER_VOICE, "player_voice"sv }, + { SE_CATEGORY_PLAYER_SE, "player_se"sv }, + { SE_CATEGORY_FOOTNOTE_SE, "footnote_se"sv }, + { SE_CATEGORY_COLLISION_SE, "collision_se"sv }, + { SE_CATEGORY_CHARA_VOICE, "chara_voice"sv }, + { SE_CATEGORY_CHARA_SE, "chara_se"sv }, + { SE_CATEGORY_ENEMY_SE, "enemy_se"sv }, + { SE_CATEGORY_OBJECT_SE, "object_se"sv }, + { SE_CATEGORY_ENV_SE, "env_se"sv }, + // clang-format on +}; + +constexpr luau_runtime::EnumName kStreamPanNames[] = { + // clang-format off + { STREAM_PAN_CENTER, "center"sv }, + { STREAM_PAN_LEFT, "left"sv }, + { STREAM_PAN_RIGHT, "right"sv }, + // clang-format on +}; + +} // namespace + +DEFINE_STRING_ENUM(SoundEffectCategory, kSoundEffectCategoryNames); +DEFINE_STRING_ENUM(StreamPan, kStreamPanNames); + +TypeResult Stack::get( + lua_State* L, int index) { + auto const ref = LuaRef::fromStack(L, index); + + return AudioSoundTableEffectInfo{ + .priority = ref["priority"], + .volume = ref["volume"], + .pitch = ref["pitch"], + .always_max_priority = ref["always_max_priority"], + .ignore_distance_volume = ref["ignore_distance_volume"], + .ignore_distance_fx_mix = ref["ignore_distance_fx_mix"], + .ignore_pan = ref["ignore_pan"], + .ignore_dolby = ref["ignore_dolby"], + .random_volume = ref["random_volume"], + .random_pitch = ref["random_pitch"], + .doppler_power = ref["doppler_power"], + .volume_dist_class = ref["volume_dist_class"], + .clamp_min_volume = ref["clamp_min_volume"], + .cull_at_max_distance = ref["cull_at_max_distance"], + }; +} + +Result Stack::push( + lua_State* L, const AudioSoundTableEffectInfo& value) { + LuaRef const ref = newTable(L); + + ref["priority"] = value.priority; + ref["volume"] = value.volume; + ref["pitch"] = value.pitch; + ref["always_max_priority"] = value.always_max_priority; + ref["ignore_distance_volume"] = value.ignore_distance_volume; + ref["ignore_distance_fx_mix"] = value.ignore_distance_fx_mix; + ref["ignore_pan"] = value.ignore_pan; + ref["ignore_dolby"] = value.ignore_dolby; + ref["random_volume"] = value.random_volume; + ref["random_pitch"] = value.random_pitch; + ref["doppler_power"] = value.doppler_power; + ref["volume_dist_class"] = value.volume_dist_class; + ref["clamp_min_volume"] = value.clamp_min_volume; + ref["cull_at_max_distance"] = value.cull_at_max_distance; + + ref.push(); + + return {}; +} + +TypeResult Stack::get( + lua_State* L, int index) { + auto const ref = LuaRef::fromStack(L, index); + + std::array, STREAM_MAX_CHILDREN> const pan_parameters = + ref["pan_parameters"]; + + auto info = AudioSoundTableStreamInfo{ + .priority = ref["priority"], + .volume = ref["volume"], + .stop_on_scene_change = ref["stop_on_scene_change"], + }; + + for (int i = 0; i < STREAM_MAX_CHILDREN; i++) { + info.pan_parameters[i] = pan_parameters[i].value_or(STREAM_PAN_CENTER); + } + + return info; +} + +Result Stack::push( + lua_State* L, const AudioSoundTableStreamInfo& value) { + LuaRef const ref = newTable(L); + + ref["priority"] = value.priority; + ref["volume"] = value.volume; + ref["pan_parameters"] = value.pan_parameters; + ref["stop_on_scene_change"] = value.stop_on_scene_change; + + ref.push(); + + return {}; +} + +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/bst.hpp b/mods/luau_runtime/src/services/audio_res/bst.hpp new file mode 100644 index 0000000000..a4d7cba591 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/bst.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "../../lua_bridge_helpers.hpp" +#include "../../lua_helpers.hpp" +#include "mods/svc/audio_res.h" + +namespace luau_runtime::services::audio_res { + +class LuaSoundTableHandle final : public BridgeScriptHandle { +protected: + void unregister_impl(lua_State*, Vm& vm) override; + +public: + explicit LuaSoundTableHandle(AudioSoundTableHandle handle); +}; + +AudioSoundTableEffectInfo const& default_effect_info(); + +LuaSoundTableHandle replace_sound_table_effect(SoundEffectCategory category_id, uint16_t effect_id, + std::optional info, lua_State* state, Vm& vm); + +std::tuple add_sound_table_effect(SoundEffectCategory category_id, + std::optional info, lua_State* state, Vm& vm); + +AudioSoundTableStreamInfo const& default_stream_info(); + +LuaSoundTableHandle replace_sound_table_stream(uint16_t stream_id, char const* file_path, + std::optional info, lua_State* state, Vm& vm); + +std::tuple add_sound_table_stream( + char const* file_path, std::optional info, lua_State* state, Vm& vm); + +} // namespace luau_runtime::services::audio_res + +namespace luabridge { + +template <> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioSoundTableEffectInfo& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +template <> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioSoundTableStreamInfo& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +DECLARE_STRING_ENUM(SoundEffectCategory); +DECLARE_STRING_ENUM(StreamPan); + +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/wsys.cpp b/mods/luau_runtime/src/services/audio_res/wsys.cpp new file mode 100644 index 0000000000..cf4bc19039 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/wsys.cpp @@ -0,0 +1,138 @@ +#include "wsys.hpp" + +namespace luau_runtime::services::audio_res { + +LuaAudioWaveHandle::LuaAudioWaveHandle(AudioWaveHandle handle) : BridgeScriptHandle(handle) {} + +void LuaAudioWaveHandle::unregister_impl(lua_State*, Vm& vm) { + svc_audio_res->remove_wave(vm.subject, handle); +} + +AudioWaveInfo const& default_wave_info() { + return *svc_audio_res->default_wave_info; +} + +LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, + std::optional info, lua_State* state, Vm& vm) { + AudioWaveInfo const* wave_info = nullptr; + if (info.has_value()) { + wave_info = &info->info; + } + + AudioWaveHandle handle; + + check_result(state, + svc_audio_res->replace_wave(vm.subject, bank, wave_id, file, wave_info, &handle), + "audio_res.replace_wave"); + + return LuaAudioWaveHandle(handle); +} + +std::tuple add_wave(AudioWaveBank bank, char const* file, + std::optional info, lua_State* state, Vm& vm) { + AudioWaveInfo const* wave_info = nullptr; + if (info.has_value()) { + wave_info = &info->info; + } + + AudioWaveHandle handle; + uint16_t out_wave_id; + + check_result(state, + svc_audio_res->add_wave(vm.subject, bank, file, wave_info, &handle, &out_wave_id), + "audio_res.replace_wave"); + + return {out_wave_id, LuaAudioWaveHandle(handle)}; +} + +} // namespace luau_runtime::services::audio_res + +namespace luabridge { + +Result Stack::push(lua_State* L, const AudioWaveInfo& value) { + LuaRef const ref = newTable(L); + + ref["base_key"] = value.base_key; + ref["loop"] = value.loop; + ref["loop_start_sample"] = value.loop_start_sample; + ref["loop_end_sample"] = value.loop_end_sample; + if (value.raw_wave) { + ref["raw_wave"] = *value.raw_wave; + } + ref.push(); + + return {}; +} + +Result Stack::push(lua_State* L, const AudioRawWave& value) { + LuaRef const ref = newTable(L); + + ref["format"] = value.format; + ref["sample_rate"] = value.sample_rate; + ref["sample_value_last"] = value.sample_value_last; + ref["sample_value_penult"] = value.sample_value_penult; + + ref.push(); + + return {}; +} + +TypeResult Stack::get(lua_State* L, int index) { + auto const ref = LuaRef::fromStack(L, index); + + return AudioRawWave{ + .format = ref["format"], + .sample_rate = ref["sample_rate"], + .sample_value_last = opt_or(ref["sample_value_last"], 0), + .sample_value_penult = opt_or(ref["sample_value_penult"], 0), + }; +} + +using namespace luau_runtime::services::audio_res; + +Result Stack::push(lua_State* L, const OwnedWaveInfo& value) { + return Stack::push(L, value.info); +} + +TypeResult Stack::get(lua_State* L, int index) { + auto const ref = LuaRef::fromStack(L, index); + + AudioWaveInfo info{ + .base_key = opt_or(ref["base_key"], AUDIO_RES_DEFAULT_KEY), + .loop = opt_or(ref["loop"], false), + .loop_start_sample = opt_or(ref["loop_start_sample"], 0), + .loop_end_sample = + opt_or(ref["loop_end_sample"], std::numeric_limits::max()), + }; + + std::unique_ptr raw; + if (!ref["raw_wave"].isNil()) { + raw = std::make_unique(ref["raw_wave"]); + info.raw_wave = raw.get(); + } + + return OwnedWaveInfo{info, std::move(raw)}; +} + +namespace { + +using namespace std::string_view_literals; + +constexpr luau_runtime::EnumName kWaveFormatNames[] = { + {AUDIO_WAVE_FORMAT_ADPCM4, "adpcm4"sv}, + {AUDIO_WAVE_FORMAT_ADPCM2, "adpcm2"sv}, + {AUDIO_WAVE_FORMAT_PCM8, "pcm8"sv}, + {AUDIO_WAVE_FORMAT_PCM16, "pcm16"sv}, +}; + +constexpr luau_runtime::EnumName kWaveBankNames[] = { + {AUDIO_WAVE_BANK_SOUND_EFFECTS, "sound_effects"sv}, + {AUDIO_WAVE_BANK_MUSIC_SAMPLES, "music_samples"sv}, +}; + +} // namespace + +DEFINE_STRING_ENUM(AudioWaveFormat, kWaveFormatNames); +DEFINE_STRING_ENUM(AudioWaveBank, kWaveBankNames); + +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/wsys.hpp b/mods/luau_runtime/src/services/audio_res/wsys.hpp new file mode 100644 index 0000000000..0d35a9b266 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/wsys.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "LuaBridge/LuaBridge.h" +#include "lua.h" +#include "lualib.h" + +#include "../../lua_helpers.hpp" +#include "../../runtime.hpp" +#include "mods/svc/audio_res.h" + +#include + +namespace luau_runtime::services::audio_res { + +struct OwnedWaveInfo { + AudioWaveInfo info; + std::unique_ptr raw_wave; +}; + +class LuaAudioWaveHandle final : public BridgeScriptHandle { +protected: + void unregister_impl(lua_State*, Vm& vm) override; + +public: + explicit LuaAudioWaveHandle(AudioWaveHandle handle); +}; + +AudioWaveInfo const& default_wave_info(); +LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, + std::optional info, lua_State* state, Vm& vm); +std::tuple add_wave(AudioWaveBank bank, char const* file, + std::optional info, lua_State* state, Vm& vm); + +} // namespace luau_runtime::services::audio_res + +namespace luabridge { + +template <> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioWaveInfo& value); +}; + +template <> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioRawWave& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +template <> +struct Stack { + [[nodiscard]] static Result push( + lua_State* L, const luau_runtime::services::audio_res::OwnedWaveInfo& value); + [[nodiscard]] static TypeResult get( + lua_State* L, int index); +}; + +DECLARE_STRING_ENUM(AudioWaveFormat); +DECLARE_STRING_ENUM(AudioWaveBank); + +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/bindings.cpp b/mods/luau_runtime/src/services/bindings.cpp new file mode 100644 index 0000000000..967adc32aa --- /dev/null +++ b/mods/luau_runtime/src/services/bindings.cpp @@ -0,0 +1,341 @@ +#include "../lua_helpers.hpp" +#include "../runtime.hpp" + +#include +#include +#include + +namespace luau_runtime::services { +namespace { + +constexpr char kOverlayMetatable[] = "dusklight.overlay_handle"; +constexpr char kTextureMetatable[] = "dusklight.texture_handle"; + +uint64_t get_hash_field(lua_State* state, int table, const char* field, bool required) { + lua_getfield(state, table, field); + if (lua_isnil(state, -1)) { + lua_pop(state, 1); + if (required) { + luaL_error(state, "field '%s' is required", field); + } + return 0; + } + if (!lua_isinteger64(state, -1)) { + luaL_error(state, "field '%s' must be an integer", field); + } + const uint64_t value = static_cast(lua_tointeger64(state, -1, nullptr)); + lua_pop(state, 1); + return value; +} + +int log_write(lua_State* state, LogLevel level, int messageIndex) { + Vm& vm = vm_from_upvalue(state); + if (svc_log == nullptr) { + service_unavailable(state, "LogService"); + } + size_t length = 0; + const char* message = luaL_checklstring(state, messageIndex, &length); + const std::string copy{message, length}; + svc_log->write(vm.subject, level, copy.c_str()); + return 0; +} + +int log_trace(lua_State* state) { + return log_write(state, LOG_LEVEL_TRACE, 1); +} + +int log_debug(lua_State* state) { + return log_write(state, LOG_LEVEL_DEBUG, 1); +} + +int log_info(lua_State* state) { + return log_write(state, LOG_LEVEL_INFO, 1); +} + +int log_warn(lua_State* state) { + return log_write(state, LOG_LEVEL_WARN, 1); +} + +int log_error(lua_State* state) { + return log_write(state, LOG_LEVEL_ERROR, 1); +} + +int log_write_level(lua_State* state) { + static constexpr const char* kLevels[] = {"trace", "debug", "info", "warn", "error", nullptr}; + const int level = luaL_checkoption(state, 1, nullptr, kLevels); + return log_write(state, static_cast(level), 2); +} + +int host_mod_id(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_host == nullptr) { + service_unavailable(state, "HostService"); + } + lua_pushstring(state, svc_host->mod_id(vm.subject)); + return 1; +} + +int host_mod_name(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_host == nullptr) { + service_unavailable(state, "HostService"); + } + lua_pushstring(state, svc_host->mod_name(vm.subject)); + return 1; +} + +int host_mod_version(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_host == nullptr) { + service_unavailable(state, "HostService"); + } + lua_pushstring(state, svc_host->mod_version(vm.subject)); + return 1; +} + +int host_mod_dir(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_host == nullptr) { + service_unavailable(state, "HostService"); + } + lua_pushstring(state, svc_host->mod_dir(vm.subject)); + return 1; +} + +int host_data_dir(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_host == nullptr || !SERVICE_HAS(svc_host, HostService, data_dir) || + svc_host->data_dir == nullptr) + { + service_unavailable(state, "HostService::data_dir"); + } + const char* path = nullptr; + check_result(state, svc_host->data_dir(vm.subject, &path), "host.data_dir"); + lua_pushstring(state, path != nullptr ? path : ""); + return 1; +} + +int host_fail(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_host == nullptr) { + service_unavailable(state, "HostService"); + } + const char* message = luaL_checkstring(state, 1); + svc_host->fail(vm.subject, MOD_ERROR, message); + luaL_error(state, "%s", message); +} + +int register_host_callback(lua_State* state, std::vector& callbacks) { + luaL_checktype(state, 1, LUA_TFUNCTION); + lua_pushvalue(state, 1); + callbacks.push_back(lua_ref(state, -1)); + lua_pop(state, 1); + return 0; +} + +int host_on_update(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + return register_host_callback(state, vm.updateRefs); +} + +int host_on_shutdown(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + return register_host_callback(state, vm.shutdownRefs); +} + +int resource_load(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_resource == nullptr) { + service_unavailable(state, "ResourceService"); + } + const char* path = luaL_checkstring(state, 1); + ResourceBuffer buffer = RESOURCE_BUFFER_INIT; + check_result(state, svc_resource->load(vm.subject, path, &buffer), "resource.load"); + const auto* data = buffer.data != nullptr ? static_cast(buffer.data) : ""; + const std::string copy{data, buffer.size}; + svc_resource->free(vm.subject, &buffer); + lua_pushlstring(state, copy.data(), copy.size()); + return 1; +} + +int overlay_remove(lua_State* state) { + auto& handle = check_handle(state, 1, kOverlayMetatable, HandleKind::Overlay); + if (svc_overlay == nullptr) { + service_unavailable(state, "OverlayService"); + } + check_result(state, svc_overlay->remove(handle.vm->subject, handle.value), "overlay.remove"); + handle.value = 0; + return 0; +} + +int overlay_add_file(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_overlay == nullptr) { + service_unavailable(state, "OverlayService"); + } + const char* discPath = luaL_checkstring(state, 1); + const char* bundlePath = luaL_checkstring(state, 2); + OverlayHandle handle = 0; + check_result(state, svc_overlay->add_file(vm.subject, discPath, bundlePath, &handle), + "overlay.add_file"); + push_handle(state, vm, handle, HandleKind::Overlay, kOverlayMetatable); + return 1; +} + +int overlay_add_buffer(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_overlay == nullptr) { + service_unavailable(state, "OverlayService"); + } + const char* discPath = luaL_checkstring(state, 1); + size_t size = 0; + const char* data = luaL_checklstring(state, 2, &size); + OverlayHandle handle = 0; + check_result(state, svc_overlay->add_buffer(vm.subject, discPath, data, size, &handle), + "overlay.add_buffer"); + push_handle(state, vm, handle, HandleKind::Overlay, kOverlayMetatable); + return 1; +} + +int texture_unregister(lua_State* state) { + auto& handle = check_handle(state, 1, kTextureMetatable, HandleKind::Texture); + if (svc_texture == nullptr) { + service_unavailable(state, "TextureService"); + } + check_result( + state, svc_texture->unregister(handle.vm->subject, handle.value), "texture.unregister"); + handle.value = 0; + return 0; +} + +int texture_register_file(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_texture == nullptr) { + service_unavailable(state, "TextureService"); + } + const char* path = luaL_checkstring(state, 1); + TextureReplacementHandle handle = 0; + check_result( + state, svc_texture->register_file(vm.subject, path, &handle), "texture.register_file"); + push_handle(state, vm, handle, HandleKind::Texture, kTextureMetatable); + return 1; +} + +int texture_register_data(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_texture == nullptr) { + service_unavailable(state, "TextureService"); + } + luaL_checktype(state, 1, LUA_TTABLE); + luaL_checktype(state, 2, LUA_TTABLE); + + TextureKey key = TEXTURE_KEY_INIT; + key.kind = TEXTURE_KEY_SOURCE; + key.texture_hash = get_hash_field(state, 1, "texture_hash", true); + key.tlut_hash = get_hash_field(state, 1, "tlut_hash", false); + key.width = static_cast(get_optional_int(state, 1, "width", 0)); + key.height = static_cast(get_optional_int(state, 1, "height", 0)); + key.gx_format = static_cast(get_optional_int(state, 1, "gx_format", 0)); + key.has_tlut = get_optional_bool(state, 1, "has_tlut", false); + + lua_getfield(state, 2, "data"); + size_t size = 0; + const char* bytes = luaL_checklstring(state, -1, &size); + TextureData data = TEXTURE_DATA_INIT; + data.data = bytes; + data.size = size; + data.width = static_cast(get_optional_int(state, 2, "width", key.width)); + data.height = static_cast(get_optional_int(state, 2, "height", key.height)); + data.mip_count = static_cast(get_optional_int(state, 2, "mip_count", 1)); + data.gx_format = static_cast(get_optional_int(state, 2, "gx_format", key.gx_format)); + + TextureReplacementHandle handle = 0; + const ModResult result = svc_texture->register_data(vm.subject, &key, &data, &handle); + lua_pop(state, 1); + check_result(state, result, "texture.register_data"); + push_handle(state, vm, handle, HandleKind::Texture, kTextureMetatable); + return 1; +} + +} // namespace + +int open_log(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_log == nullptr) { + service_unavailable(state, "LogService"); + } + lua_newtable(state); + set_function(state, vm, "write", log_write_level); + set_function(state, vm, "trace", log_trace); + set_function(state, vm, "debug", log_debug); + set_function(state, vm, "info", log_info); + set_function(state, vm, "warn", log_warn); + set_function(state, vm, "error", log_error); + lua_setreadonly(state, -1, true); + return 1; +} + +int open_host(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_host == nullptr) { + service_unavailable(state, "HostService"); + } + lua_newtable(state); + lua_pushstring(state, svc_host->version != nullptr ? svc_host->version : ""); + lua_setfield(state, -2, "version"); + set_function(state, vm, "mod_id", host_mod_id); + set_function(state, vm, "mod_name", host_mod_name); + set_function(state, vm, "mod_version", host_mod_version); + set_function(state, vm, "mod_dir", host_mod_dir); + set_function(state, vm, "data_dir", host_data_dir); + set_function(state, vm, "on_update", host_on_update); + set_function(state, vm, "on_shutdown", host_on_shutdown); + set_function(state, vm, "fail", host_fail); + lua_setreadonly(state, -1, true); + return 1; +} + +int open_resource(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_resource == nullptr) { + service_unavailable(state, "ResourceService"); + } + lua_newtable(state); + set_function(state, vm, "load", resource_load); + lua_setreadonly(state, -1, true); + return 1; +} + +int open_overlay(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_overlay == nullptr) { + service_unavailable(state, "OverlayService"); + } + static const luaL_Reg kMethods[] = {{"remove", overlay_remove}, {nullptr, nullptr}}; + create_handle_metatable(state, kOverlayMetatable, kMethods, "OverlayHandle"); + lua_newtable(state); + set_function(state, vm, "add_file", overlay_add_file); + set_function(state, vm, "add_buffer", overlay_add_buffer); + lua_setreadonly(state, -1, true); + return 1; +} + +int open_texture(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_texture == nullptr) { + service_unavailable(state, "TextureService"); + } + static const luaL_Reg kMethods[] = {{"unregister", texture_unregister}, {nullptr, nullptr}}; + create_handle_metatable(state, kTextureMetatable, kMethods, "TextureHandle"); + lua_newtable(state); + set_function(state, vm, "register_file", texture_register_file); + set_function(state, vm, "register_data", texture_register_data); + lua_pushinteger64(state, static_cast(TEXTURE_HASH_WILDCARD)); + lua_setfield(state, -2, "hash_wildcard"); + lua_pushinteger64(state, static_cast(TEXTURE_TLUT_WILDCARD)); + lua_setfield(state, -2, "tlut_wildcard"); + lua_setreadonly(state, -1, true); + return 1; +} + +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/config.cpp b/mods/luau_runtime/src/services/config.cpp new file mode 100644 index 0000000000..28d6939e4e --- /dev/null +++ b/mods/luau_runtime/src/services/config.cpp @@ -0,0 +1,246 @@ +#include "config.hpp" +#include "../runtime.hpp" + +#include +#include + +#include "../lua_helpers.hpp" + +namespace luau_runtime::services { +namespace { + +constexpr char kConfigVarMetatable[] = "dusklight.config_var"; +constexpr char kConfigSubscriptionMetatable[] = "dusklight.config_subscription"; + +void config_changed(ModContext*, ConfigVarHandle, const ConfigVarValue* value, + const ConfigVarValue* previous, void* userData) { + auto& callback = *static_cast(userData); + if (value == nullptr || previous == nullptr) { + return; + } + push_config_value(callback.vm->state, *value); + push_config_value(callback.vm->state, *previous); + std::string error; + if (!call_ref(*callback.vm, callback.refs[0], 2, 0, kCallbackBudget, error)) { + fail_callback(*callback.vm, "config change callback", error); + } +} + +int config_get(lua_State* state) { + auto& handle = check_handle(state, 1, kConfigVarMetatable, HandleKind::ConfigVar); + if (svc_config == nullptr) { + service_unavailable(state, "ConfigService"); + } + switch (handle.configType) { + case CONFIG_VAR_BOOL: { + bool value = false; + check_result( + state, svc_config->get_bool(handle.vm->subject, handle.value, &value), "config get"); + lua_pushboolean(state, value); + break; + } + case CONFIG_VAR_INT: { + int64_t value = 0; + check_result( + state, svc_config->get_int(handle.vm->subject, handle.value, &value), "config get"); + lua_pushinteger64(state, value); + break; + } + case CONFIG_VAR_FLOAT: { + double value = 0; + check_result( + state, svc_config->get_float(handle.vm->subject, handle.value, &value), "config get"); + lua_pushnumber(state, value); + break; + } + case CONFIG_VAR_STRING: { + size_t size = 0; + check_result(state, + svc_config->get_string(handle.vm->subject, handle.value, nullptr, 0, &size), + "config get"); + std::vector value(size + 1); + check_result(state, + svc_config->get_string( + handle.vm->subject, handle.value, value.data(), value.size(), nullptr), + "config get"); + lua_pushlstring(state, value.data(), size); + break; + } + default: + luaL_error(state, "unknown config value type"); + } + return 1; +} + +int config_set(lua_State* state) { + auto& handle = check_handle(state, 1, kConfigVarMetatable, HandleKind::ConfigVar); + if (svc_config == nullptr) { + service_unavailable(state, "ConfigService"); + } + ModResult result = MOD_INVALID_ARGUMENT; + switch (handle.configType) { + case CONFIG_VAR_BOOL: + result = svc_config->set_bool( + handle.vm->subject, handle.value, luaL_checkboolean(state, 2) != 0); + break; + case CONFIG_VAR_INT: + result = svc_config->set_int(handle.vm->subject, handle.value, check_int64(state, 2)); + break; + case CONFIG_VAR_FLOAT: + result = + svc_config->set_float(handle.vm->subject, handle.value, luaL_checknumber(state, 2)); + break; + case CONFIG_VAR_STRING: + result = + svc_config->set_string(handle.vm->subject, handle.value, luaL_checkstring(state, 2)); + break; + default: + break; + } + check_result(state, result, "config set"); + return 0; +} + +int config_unregister(lua_State* state) { + auto& handle = check_handle(state, 1, kConfigVarMetatable, HandleKind::ConfigVar); + if (svc_config == nullptr) { + service_unavailable(state, "ConfigService"); + } + check_result( + state, svc_config->unregister_var(handle.vm->subject, handle.value), "config unregister"); + handle.value = 0; + return 0; +} + +int config_unsubscribe(lua_State* state) { + auto& handle = + check_handle(state, 1, kConfigSubscriptionMetatable, HandleKind::ConfigSubscription); + if (svc_config == nullptr) { + service_unavailable(state, "ConfigService"); + } + check_result( + state, svc_config->unsubscribe(handle.vm->subject, handle.value), "config unsubscribe"); + handle.value = 0; + return 0; +} + +int subscribe(lua_State* state, ScriptHandle& variable, int functionIndex) { + luaL_checktype(state, functionIndex, LUA_TFUNCTION); + Callback& callback = retain_callback(*variable.vm); + callback.refs[0] = lua_ref(state, functionIndex); + + ConfigSubscriptionHandle subscription = 0; + check_result(state, + svc_config->subscribe( + variable.vm->subject, variable.value, config_changed, &callback, &subscription), + "config.subscribe"); + push_handle(state, *variable.vm, subscription, HandleKind::ConfigSubscription, + kConfigSubscriptionMetatable); + return 1; +} + +int config_subscribe(lua_State* state) { + if (svc_config == nullptr) { + service_unavailable(state, "ConfigService"); + } + auto& variable = check_handle(state, 1, kConfigVarMetatable, HandleKind::ConfigVar); + return subscribe(state, variable, 2); +} + +int config_var_subscribe(lua_State* state) { + if (svc_config == nullptr) { + service_unavailable(state, "ConfigService"); + } + auto& variable = check_handle(state, 1, kConfigVarMetatable, HandleKind::ConfigVar); + return subscribe(state, variable, 2); +} + +int config_register(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_config == nullptr) { + service_unavailable(state, "ConfigService"); + } + luaL_checktype(state, 1, LUA_TTABLE); + + const std::string name = get_optional_string(state, 1, "name"); + const std::string type = get_optional_string(state, 1, "type"); + ConfigVarDesc desc = CONFIG_VAR_DESC_INIT; + desc.name = name.c_str(); + if (type == "bool") { + desc.type = CONFIG_VAR_BOOL; + desc.default_bool = get_optional_bool(state, 1, "default", false); + } else if (type == "int") { + desc.type = CONFIG_VAR_INT; + desc.default_int = get_optional_int(state, 1, "default", 0); + } else if (type == "float") { + desc.type = CONFIG_VAR_FLOAT; + desc.default_float = get_optional_number(state, 1, "default", 0); + } else if (type == "string") { + desc.type = CONFIG_VAR_STRING; + } else { + luaL_error(state, "config type must be 'bool', 'int', 'float', or 'string'"); + } + + std::string defaultString; + if (desc.type == CONFIG_VAR_STRING) { + defaultString = get_optional_string(state, 1, "default"); + desc.default_string = defaultString.c_str(); + } + + ConfigVarHandle handle = 0; + check_result(state, svc_config->register_var(vm.subject, &desc, &handle), "config.register"); + push_handle(state, vm, handle, HandleKind::ConfigVar, kConfigVarMetatable, desc.type); + return 1; +} + +} // namespace + +void push_config_value(lua_State* state, const ConfigVarValue& value) { + switch (value.type) { + case CONFIG_VAR_BOOL: + lua_pushboolean(state, value.bool_value); + break; + case CONFIG_VAR_INT: + lua_pushinteger64(state, value.int_value); + break; + case CONFIG_VAR_FLOAT: + lua_pushnumber(state, value.float_value); + break; + case CONFIG_VAR_STRING: + lua_pushlstring(state, value.string_value != nullptr ? value.string_value : "", + value.string_value != nullptr ? value.string_length : 0); + break; + default: + lua_pushnil(state); + break; + } +} + +int open_config(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_config == nullptr) { + service_unavailable(state, "ConfigService"); + } + static const luaL_Reg kVarMethods[] = { + {"get", config_get}, + {"set", config_set}, + {"subscribe", config_var_subscribe}, + {"unregister", config_unregister}, + {nullptr, nullptr}, + }; + static const luaL_Reg kSubscriptionMethods[] = { + {"unsubscribe", config_unsubscribe}, + {nullptr, nullptr}, + }; + create_handle_metatable(state, kConfigVarMetatable, kVarMethods, "ConfigVar"); + create_handle_metatable( + state, kConfigSubscriptionMetatable, kSubscriptionMethods, "ConfigSubscription"); + + lua_newtable(state); + set_function(state, vm, "register", config_register); + set_function(state, vm, "subscribe", config_subscribe); + lua_setreadonly(state, -1, true); + return 1; +} + +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/config.hpp b/mods/luau_runtime/src/services/config.hpp new file mode 100644 index 0000000000..1eb83e0c8f --- /dev/null +++ b/mods/luau_runtime/src/services/config.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "lua.h" +#include "mods/svc/config.h" + +namespace luau_runtime::services { + +void push_config_value(lua_State* state, const ConfigVarValue& value); + +} diff --git a/mods/luau_runtime/src/services/services.cpp b/mods/luau_runtime/src/services/services.cpp new file mode 100644 index 0000000000..1933aa4d30 --- /dev/null +++ b/mods/luau_runtime/src/services/services.cpp @@ -0,0 +1,32 @@ +#include "services.hpp" + +namespace luau_runtime::services { + +namespace { + +using namespace std::string_view_literals; + +constexpr std::pair kModules[] = { + {"dusklight.log"sv, open_log}, + {"dusklight.host"sv, open_host}, + {"dusklight.resource"sv, open_resource}, + {"dusklight.overlay"sv, open_overlay}, + {"dusklight.texture"sv, open_texture}, + {"dusklight.config"sv, open_config}, + {"dusklight.ui"sv, open_ui}, + {"dusklight.audio_res"sv, open_audio_res}, +}; + +} // namespace + +ModuleOpenFn module_factory(std::string_view name) { + for (auto [modName, ptr] : kModules) { + if (name == modName) { + return ptr; + } + } + + return nullptr; +} + +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/services.hpp b/mods/luau_runtime/src/services/services.hpp new file mode 100644 index 0000000000..71660cfa69 --- /dev/null +++ b/mods/luau_runtime/src/services/services.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "../runtime.hpp" + +#include "lua.h" + +namespace luau_runtime::services { + +int open_audio_res(lua_State* state); +int open_log(lua_State* state); +int open_host(lua_State* state); +int open_resource(lua_State* state); +int open_overlay(lua_State* state); +int open_texture(lua_State* state); +int open_config(lua_State* state); +int open_ui(lua_State* state); + +ModuleOpenFn module_factory(std::string_view name); + +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/ui.cpp b/mods/luau_runtime/src/services/ui.cpp new file mode 100644 index 0000000000..86edf99f5b --- /dev/null +++ b/mods/luau_runtime/src/services/ui.cpp @@ -0,0 +1,1049 @@ +#include "ui.hpp" + +#include "../runtime.hpp" + +#include +#include +#include +#include +#include + +#include "../lua_helpers.hpp" + +namespace luau_runtime::services { +namespace { + +using namespace std::string_view_literals; + +constexpr char kUiWindowMetatable[] = "dusklight.ui_window"; +constexpr char kUiDialogMetatable[] = "dusklight.ui_dialog"; +constexpr char kUiElementMetatable[] = "dusklight.ui_element"; +constexpr char kUiStyleMetatable[] = "dusklight.ui_style"; +constexpr char kUiMenuTabMetatable[] = "dusklight.ui_menu_tab"; +constexpr char kUiListMetatable[] = "dusklight.ui_list"; +constexpr char kUiContextMenuMetatable[] = "dusklight.ui_context_menu"; + +const char* ui_metatable(HandleKind kind) { + switch (kind) { + case HandleKind::UiWindow: + return kUiWindowMetatable; + case HandleKind::UiDialog: + return kUiDialogMetatable; + case HandleKind::UiStyle: + return kUiStyleMetatable; + case HandleKind::UiMenuTab: + return kUiMenuTabMetatable; + case HandleKind::UiList: + return kUiListMetatable; + case HandleKind::UiContextMenu: + return kUiContextMenuMetatable; + default: + return kUiElementMetatable; + } +} + +ScriptHandle& check_ui_handle(lua_State* state, int index, HandleKind kind) { + return check_handle(state, index, ui_metatable(kind), kind); +} + +ScriptHandle& check_config_var(lua_State* state, int index) { + luaL_checktype(state, index, LUA_TUSERDATA); + auto* handle = static_cast(lua_touserdata(state, index)); + if (handle == nullptr || handle->kind != HandleKind::ConfigVar || handle->value == 0 || + handle->vm == nullptr) + { + luaL_argerror(state, index, "expected a live ConfigVar"); + } + return *handle; +} + +bool call_callback(Callback& callback, int ref, int argumentCount, int resultCount, + const char* name, std::string* outError = nullptr) { + std::string error; + if (call_ref(*callback.vm, ref, argumentCount, resultCount, kCallbackBudget, error)) { + return true; + } + if (outError != nullptr) { + *outError = std::move(error); + } else { + fail_callback(*callback.vm, name, error); + } + return false; +} + +ModResult call_build( + Callback& callback, int ref, int argumentCount, const char* name, ModError* outError) { + std::string error; + if (call_callback(callback, ref, argumentCount, 0, name, &error)) { + return MOD_OK; + } + return set_error(outError, MOD_ERROR, error); +} + +bool call_predicate(Callback& callback, int ref, int argumentCount, const char* name) { + if (!call_callback(callback, ref, argumentCount, 1, name)) { + return false; + } + lua_State* state = callback.vm->state; + const bool result = lua_toboolean(state, -1) != 0; + lua_pop(state, 1); + return result; +} + +void control_get(ModContext*, void* userData, UiControlValue* outValue) { + auto& callback = *static_cast(userData); + if (outValue == nullptr || !call_callback(callback, callback.refs[0], 0, 1, "control get")) { + return; + } + lua_State* state = callback.vm->state; + switch (static_cast(callback.tag)) { + case UI_CONTROL_TOGGLE: + outValue->bool_value = lua_toboolean(state, -1) != 0; + break; + case UI_CONTROL_NUMBER: + case UI_CONTROL_DROPDOWN: + case UI_CONTROL_SELECT: { + int64_t value = 0; + if (!to_int64(state, -1, value)) { + fail_callback(*callback.vm, "control get", "callback must return an integer"); + } else { + outValue->int_value = value; + } + break; + } + case UI_CONTROL_STRING: + case UI_CONTROL_COLOR: + case UI_CONTROL_FILE_PICKER: { + size_t length = 0; + const char* value = lua_tolstring(state, -1, &length); + if (value == nullptr) { + fail_callback(*callback.vm, "control get", "callback must return a string"); + } else { + callback.returnedString.assign(value, length); + outValue->string_value = callback.returnedString.c_str(); + } + break; + } + default: + break; + } + lua_pop(state, 1); +} + +void control_set(ModContext*, void* userData, const UiControlValue* value) { + auto& callback = *static_cast(userData); + if (value == nullptr) { + return; + } + lua_State* state = callback.vm->state; + switch (static_cast(callback.tag)) { + case UI_CONTROL_TOGGLE: + lua_pushboolean(state, value->bool_value); + break; + case UI_CONTROL_NUMBER: + case UI_CONTROL_DROPDOWN: + case UI_CONTROL_SELECT: + lua_pushinteger64(state, value->int_value); + break; + case UI_CONTROL_STRING: + case UI_CONTROL_COLOR: + case UI_CONTROL_FILE_PICKER: + lua_pushstring(state, value->string_value != nullptr ? value->string_value : ""); + break; + default: + return; + } + call_callback(callback, callback.refs[1], 1, 0, "control set"); +} + +bool control_disabled(ModContext*, void* userData) { + auto& callback = *static_cast(userData); + return call_predicate(callback, callback.refs[3], 0, "control is_disabled"); +} + +bool control_modified(ModContext*, void* userData) { + auto& callback = *static_cast(userData); + return call_predicate(callback, callback.refs[4], 0, "control is_modified"); +} + +bool control_selected(ModContext*, void* userData) { + auto& callback = *static_cast(userData); + return call_predicate(callback, callback.refs[5], 0, "control is_selected"); +} + +void control_pressed(ModContext*, void* userData) { + auto& callback = *static_cast(userData); + call_callback(callback, callback.refs[2], 0, 0, "control on_pressed"); +} + +ModResult panel_build(ModContext*, UiElementHandle pane, void* userData, ModError* outError) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, pane, HandleKind::UiElement); + return call_build(callback, callback.refs[0], 1, "panel build", outError); +} + +ModResult panel_update(ModContext*, void* userData, ModError* outError) { + auto& callback = *static_cast(userData); + return call_build(callback, callback.refs[1], 0, "panel update", outError); +} + +ModResult group_build(ModContext*, UiElementHandle pane, void* userData, ModError* outError) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, pane, HandleKind::UiElement); + return call_build(callback, callback.refs[0], 1, "group build", outError); +} + +ModResult tab_build(ModContext*, UiWindowHandle window, UiElementHandle left, UiElementHandle right, + void* userData, ModError* outError) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, window, HandleKind::UiWindow); + push_ui_handle(callback.vm->state, *callback.vm, left, HandleKind::UiElement); + push_ui_handle(callback.vm->state, *callback.vm, right, HandleKind::UiElement); + return call_build(callback, callback.refs[0], 3, "window tab build", outError); +} + +void window_closed(ModContext*, UiWindowHandle window, void* userData) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, window, HandleKind::UiWindow); + call_callback(callback, callback.refs[0], 1, 0, "window on_closed"); +} + +void dialog_action(ModContext*, UiDialogHandle dialog, void* userData) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, dialog, HandleKind::UiDialog); + call_callback(callback, callback.refs[0], 1, 0, "dialog action"); +} + +bool dialog_action_disabled(ModContext*, void* userData) { + auto& callback = *static_cast(userData); + return call_predicate(callback, callback.refs[1], 0, "dialog action is_disabled"); +} + +void dialog_dismissed(ModContext*, UiDialogHandle dialog, void* userData) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, dialog, HandleKind::UiDialog); + call_callback(callback, callback.refs[0], 1, 0, "dialog on_dismiss"); +} + +ModResult dialog_build(ModContext*, UiElementHandle pane, void* userData, ModError* outError) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, pane, HandleKind::UiElement); + return call_build(callback, callback.refs[1], 1, "dialog build", outError); +} + +void menu_selected(ModContext*, void* userData) { + auto& callback = *static_cast(userData); + call_callback(callback, callback.refs[0], 0, 0, "menu tab on_selected"); +} + +void list_pressed(ModContext*, UiListHandle list, uint64_t key, void* userData) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, list, HandleKind::UiList); + lua_pushinteger64(callback.vm->state, static_cast(key)); + call_callback(callback, callback.refs[0], 2, 0, "list on_pressed"); +} + +bool list_selected(ModContext*, UiListHandle list, uint64_t key, void* userData) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, list, HandleKind::UiList); + lua_pushinteger64(callback.vm->state, static_cast(key)); + return call_predicate(callback, callback.refs[1], 2, "list is_selected"); +} + +bool list_disabled(ModContext*, UiListHandle list, uint64_t key, void* userData) { + auto& callback = *static_cast(userData); + push_ui_handle(callback.vm->state, *callback.vm, list, HandleKind::UiList); + lua_pushinteger64(callback.vm->state, static_cast(key)); + return call_predicate(callback, callback.refs[2], 2, "list is_disabled"); +} + +std::vector string_array(lua_State* state, int table, const char* field) { + std::vector result; + lua_getfield(state, table, field); + if (lua_isnil(state, -1)) { + lua_pop(state, 1); + return result; + } + luaL_checktype(state, -1, LUA_TTABLE); + const int count = lua_objlen(state, -1); + result.reserve(count); + for (int i = 1; i <= count; ++i) { + lua_rawgeti(state, -1, i); + result.emplace_back(luaL_checkstring(state, -1)); + lua_pop(state, 1); + } + lua_pop(state, 1); + return result; +} + +std::vector list_items(lua_State* state, int table, std::vector& labels) { + luaL_checktype(state, table, LUA_TTABLE); + const int count = lua_objlen(state, table); + labels.reserve(count); + std::vector items; + items.reserve(count); + for (int i = 1; i <= count; ++i) { + lua_rawgeti(state, table, i); + luaL_checktype(state, -1, LUA_TTABLE); + labels.push_back(get_optional_string(state, -1, "label")); + UiListItem item = UI_LIST_ITEM_INIT; + item.key = static_cast(get_optional_int(state, -1, "key", 0)); + items.push_back(item); + lua_pop(state, 1); + } + for (size_t i = 0; i < items.size(); ++i) { + items[i].label = labels[i].c_str(); + } + return items; +} + +constexpr EnumName kControlKindNames[] = { + {UI_CONTROL_BUTTON, "button"sv}, + {UI_CONTROL_TOGGLE, "toggle"sv}, + {UI_CONTROL_NUMBER, "number"sv}, + {UI_CONTROL_STRING, "string"sv}, + {UI_CONTROL_SELECT, "select"sv}, + {UI_CONTROL_COLOR, "color"sv}, + {UI_CONTROL_GROUP, "group"sv}, + {UI_CONTROL_FILE_PICKER, "file_picker"sv}, + {UI_CONTROL_ICON_BUTTON, "icon_button"sv}, + {UI_CONTROL_DROPDOWN, "dropdown"sv}, +}; + +UiControlKind control_kind(lua_State* state, const std::string& kind) { + return enum_str_to_value(state, kind.data(), kControlKindNames); +} + +constexpr EnumName kStyleScopeNames[] = { + {UI_SCOPE_PRELAUNCH, "prelaunch"sv}, + {UI_SCOPE_WINDOW, "window"sv}, + {UI_SCOPE_MENU_BAR, "menu_bar"sv}, + {UI_SCOPE_OVERLAY, "overlay"sv}, + {UI_SCOPE_TOUCH_CONTROLS, "touch_controls"sv}, + {UI_SCOPE_GRAPHICS_TUNER, "graphics_tuner"sv}, +}; + +UiStyleScope style_scope(lua_State* state, const std::string& scope) { + return enum_str_to_value(state, scope.data(), kStyleScopeNames); +} + +int pane_add_row(lua_State* state) { + auto& pane = check_ui_handle(state, 1, HandleKind::UiElement); + luaL_checktype(state, 2, LUA_TTABLE); + constexpr EnumName alignNames[] = { + {UI_ROW_ALIGN_START, "start"sv}, + {UI_ROW_ALIGN_END, "end"sv}, + {UI_ROW_ALIGN_CENTER, "center"sv}, + {UI_ROW_ALIGN_SPACE_BETWEEN, "space_between"sv}, + }; + UiRowDesc desc = UI_ROW_DESC_INIT; + const auto align = get_optional_string(state, 2, "align"); + if (!align.empty()) { + desc.align = enum_str_to_value(state, align.c_str(), alignNames); + } + desc.wrap = get_optional_bool(state, 2, "wrap", false); + UiElementHandle row = 0; + check_result( + state, svc_ui->pane_add_row(pane.vm->subject, pane.value, &desc, &row), "ui pane_add_row"); + push_ui_handle(state, *pane.vm, row, HandleKind::UiElement); + return 1; +} + +int pane_add_section(lua_State* state) { + auto& pane = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->pane_add_section(pane.vm->subject, pane.value, luaL_checkstring(state, 2)), + "ui pane_add_section"); + return 0; +} + +int pane_add_text(lua_State* state) { + auto& pane = check_ui_handle(state, 1, HandleKind::UiElement); + UiElementHandle element = 0; + check_result(state, + svc_ui->pane_add_text(pane.vm->subject, pane.value, luaL_checkstring(state, 2), &element), + "ui pane_add_text"); + push_ui_handle(state, *pane.vm, element, HandleKind::UiElement); + return 1; +} + +int pane_add_rml(lua_State* state) { + auto& pane = check_ui_handle(state, 1, HandleKind::UiElement); + UiElementHandle element = 0; + check_result(state, + svc_ui->pane_add_rml(pane.vm->subject, pane.value, luaL_checkstring(state, 2), &element), + "ui pane_add_rml"); + push_ui_handle(state, *pane.vm, element, HandleKind::UiElement); + return 1; +} + +int pane_add_progress(lua_State* state) { + auto& pane = check_ui_handle(state, 1, HandleKind::UiElement); + UiElementHandle element = 0; + check_result(state, + svc_ui->pane_add_progress( + pane.vm->subject, pane.value, static_cast(luaL_checknumber(state, 2)), &element), + "ui pane_add_progress"); + push_ui_handle(state, *pane.vm, element, HandleKind::UiElement); + return 1; +} + +int pane_add_control(lua_State* state) { + auto& pane = check_ui_handle(state, 1, HandleKind::UiElement); + luaL_checktype(state, 2, LUA_TTABLE); + + UiControlDesc desc = UI_CONTROL_DESC_INIT; + const std::string kind = get_optional_string(state, 2, "kind"); + const std::string label = get_optional_string(state, 2, "label"); + const std::string help = get_optional_string(state, 2, "help_rml"); + const std::string icon = get_optional_string(state, 2, "icon"); + const std::string tooltip = get_optional_string(state, 2, "tooltip"); + const std::string prefix = get_optional_string(state, 2, "prefix"); + const std::string suffix = get_optional_string(state, 2, "suffix"); + desc.kind = control_kind(state, kind); + desc.label = label.c_str(); + desc.icon = icon.empty() ? nullptr : icon.c_str(); + desc.tooltip = tooltip.empty() ? nullptr : tooltip.c_str(); + desc.help_rml = help.empty() ? nullptr : help.c_str(); + desc.min = get_optional_int(state, 2, "min", 0); + desc.max = get_optional_int(state, 2, "max", 0); + desc.step = get_optional_int(state, 2, "step", 1); + desc.prefix = prefix.empty() ? nullptr : prefix.c_str(); + desc.suffix = suffix.empty() ? nullptr : suffix.c_str(); + desc.max_length = static_cast(get_optional_int(state, 2, "max_length", 0)); + desc.color_alpha = get_optional_bool(state, 2, "color_alpha", false); + desc.directory_mode = get_optional_bool(state, 2, "directory_mode", false); + desc.string_set_mode = get_optional_string(state, 2, "string_set_mode") == "change" ? + UI_STRING_SET_ON_CHANGE : + UI_STRING_SET_ON_COMMIT; + + std::vector options = string_array(state, 2, "options"); + std::vector optionPointers; + optionPointers.reserve(options.size()); + for (const auto& option : options) { + optionPointers.push_back(option.c_str()); + } + desc.options = optionPointers.data(); + desc.option_count = optionPointers.size(); + std::unique_ptr enabled; + lua_getfield(state, 2, "option_enabled"); + if (!lua_isnil(state, -1)) { + luaL_checktype(state, -1, LUA_TTABLE); + if (lua_objlen(state, -1) != static_cast(options.size())) { + luaL_error(state, "option_enabled must have one boolean per option"); + } + enabled = std::make_unique(options.size()); + for (size_t i = 0; i < options.size(); ++i) { + lua_rawgeti(state, -1, static_cast(i + 1)); + enabled[i] = luaL_checkboolean(state, -1) != 0; + lua_pop(state, 1); + } + } + lua_pop(state, 1); + desc.option_enabled = enabled.get(); + + std::vector presets = string_array(state, 2, "color_presets"); + std::vector presetPointers; + presetPointers.reserve(presets.size()); + for (const auto& preset : presets) { + presetPointers.push_back(preset.c_str()); + } + desc.color_presets = presetPointers.data(); + desc.color_preset_count = presetPointers.size(); + + std::vector filterNames; + std::vector filterPatterns; + std::vector filters; + lua_getfield(state, 2, "file_filters"); + if (!lua_isnil(state, -1)) { + luaL_checktype(state, -1, LUA_TTABLE); + const int count = lua_objlen(state, -1); + filterNames.reserve(count); + filterPatterns.reserve(count); + filters.resize(count); + for (int i = 1; i <= count; ++i) { + lua_rawgeti(state, -1, i); + filterNames.push_back(get_optional_string(state, -1, "name")); + filterPatterns.push_back(get_optional_string(state, -1, "pattern")); + lua_pop(state, 1); + } + for (int i = 0; i < count; ++i) { + filters[i] = {filterNames[i].c_str(), filterPatterns[i].c_str()}; + } + } + lua_pop(state, 1); + desc.file_filters = filters.data(); + desc.file_filter_count = filters.size(); + + Callback& callback = retain_callback(*pane.vm); + callback.tag = desc.kind; + callback.refs[2] = ref_optional_function(state, 2, "on_pressed"); + callback.refs[3] = ref_optional_function(state, 2, "is_disabled"); + callback.refs[4] = ref_optional_function(state, 2, "is_modified"); + callback.refs[5] = ref_optional_function(state, 2, "is_selected"); + desc.user_data = &callback; + desc.on_pressed = callback.refs[2] != LUA_NOREF ? control_pressed : nullptr; + desc.is_disabled = callback.refs[3] != LUA_NOREF ? control_disabled : nullptr; + desc.is_modified = callback.refs[4] != LUA_NOREF ? control_modified : nullptr; + desc.is_selected = callback.refs[5] != LUA_NOREF ? control_selected : nullptr; + + lua_getfield(state, 2, "config_var"); + if (!lua_isnil(state, -1)) { + auto& variable = check_config_var(state, -1); + desc.binding = UI_BINDING_CONFIG_VAR; + desc.config_var = variable.value; + } else if (desc.kind != UI_CONTROL_BUTTON && desc.kind != UI_CONTROL_GROUP && + desc.kind != UI_CONTROL_ICON_BUTTON) + { + desc.binding = UI_BINDING_CALLBACKS; + callback.refs[0] = ref_required_function(state, 2, "get"); + callback.refs[1] = ref_required_function(state, 2, "set"); + desc.get = control_get; + desc.set = control_set; + } + lua_pop(state, 1); + + UiElementHandle element = 0; + check_result(state, svc_ui->pane_add_control(pane.vm->subject, pane.value, &desc, &element), + "ui pane_add_control"); + push_ui_handle(state, *pane.vm, element, HandleKind::UiElement); + return 1; +} + +int pane_add_group(lua_State* state) { + auto& pane = check_ui_handle(state, 1, HandleKind::UiElement); + auto& target = check_ui_handle(state, 2, HandleKind::UiElement); + luaL_checktype(state, 3, LUA_TTABLE); + const std::string label = get_optional_string(state, 3, "label"); + Callback& callback = retain_callback(*pane.vm); + callback.refs[0] = ref_required_function(state, 3, "build"); + UiGroupDesc desc = UI_GROUP_DESC_INIT; + desc.label = label.c_str(); + desc.build = group_build; + desc.user_data = &callback; + UiElementHandle element = 0; + check_result(state, + svc_ui->pane_add_group(pane.vm->subject, pane.value, target.value, &desc, &element), + "ui pane_add_group"); + push_ui_handle(state, *pane.vm, element, HandleKind::UiElement); + return 1; +} + +int pane_add_list(lua_State* state) { + auto& pane = check_ui_handle(state, 1, HandleKind::UiElement); + luaL_checktype(state, 2, LUA_TTABLE); + Callback& callback = retain_callback(*pane.vm); + callback.refs[0] = ref_required_function(state, 2, "on_pressed"); + callback.refs[1] = ref_optional_function(state, 2, "is_selected"); + callback.refs[2] = ref_optional_function(state, 2, "is_disabled"); + + std::vector labels; + std::vector items; + lua_getfield(state, 2, "items"); + if (!lua_isnil(state, -1)) { + items = list_items(state, -1, labels); + } + lua_pop(state, 1); + + UiListDesc desc = UI_LIST_DESC_INIT; + desc.items = items.data(); + desc.item_count = items.size(); + desc.on_pressed = list_pressed; + desc.is_selected = callback.refs[1] != LUA_NOREF ? list_selected : nullptr; + desc.is_disabled = callback.refs[2] != LUA_NOREF ? list_disabled : nullptr; + desc.user_data = &callback; + UiListHandle list = 0; + check_result(state, svc_ui->pane_add_list(pane.vm->subject, pane.value, &desc, &list), + "ui pane_add_list"); + push_ui_handle(state, *pane.vm, list, HandleKind::UiList); + return 1; +} + +int element_set_text(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->elem_set_text(element.vm->subject, element.value, luaL_checkstring(state, 2)), + "ui elem_set_text"); + return 0; +} + +int element_set_rml(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->elem_set_rml(element.vm->subject, element.value, luaL_checkstring(state, 2)), + "ui elem_set_rml"); + return 0; +} + +int element_set_progress(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->elem_set_progress( + element.vm->subject, element.value, static_cast(luaL_checknumber(state, 2))), + "ui elem_set_progress"); + return 0; +} + +int element_set_class(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->elem_set_class(element.vm->subject, element.value, luaL_checkstring(state, 2), + luaL_checkboolean(state, 3) != 0), + "ui elem_set_class"); + return 0; +} + +int control_set_label(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->control_set_label(element.vm->subject, element.value, luaL_checkstring(state, 2)), + "ui control_set_label"); + return 0; +} + +int control_set_icon(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->control_set_icon(element.vm->subject, element.value, luaL_checkstring(state, 2)), + "ui control_set_icon"); + return 0; +} + +int control_set_tooltip(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->control_set_tooltip( + element.vm->subject, element.value, luaL_optstring(state, 2, "")), + "ui control_set_tooltip"); + return 0; +} + +int control_set_options(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + luaL_checktype(state, 2, LUA_TTABLE); + const int count = lua_objlen(state, 2); + std::vector labels; + std::vector options; + labels.reserve(count); + options.reserve(count); + for (int i = 1; i <= count; ++i) { + lua_rawgeti(state, 2, i); + luaL_checktype(state, -1, LUA_TTABLE); + labels.push_back(get_optional_string(state, -1, "label")); + UiControlOption option = UI_CONTROL_OPTION_INIT; + option.label = labels.back().c_str(); + option.enabled = get_optional_bool(state, -1, "enabled", true); + options.push_back(option); + lua_pop(state, 1); + } + check_result(state, + svc_ui->control_set_options( + element.vm->subject, element.value, options.data(), options.size()), + "ui control_set_options"); + return 0; +} + +int element_set_visible(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + check_result(state, + svc_ui->elem_set_visible( + element.vm->subject, element.value, luaL_checkboolean(state, 2) != 0), + "ui elem_set_visible"); + return 0; +} + +int element_focus(lua_State* state) { + auto& element = check_ui_handle(state, 1, HandleKind::UiElement); + const auto result = svc_ui->elem_focus(element.vm->subject, element.value); + if (result != MOD_UNAVAILABLE) { + check_result(state, result, "ui elem_focus"); + } + lua_pushboolean(state, result == MOD_OK); + return 1; +} + +void context_menu_action(ModContext*, void* userData) { + auto& callback = *static_cast(userData); + call_callback(callback, callback.refs[0], 0, 0, "context menu action"); +} + +int context_menu_push(lua_State* state) { + auto& anchor = check_ui_handle(state, 1, HandleKind::UiElement); + luaL_checktype(state, 2, LUA_TTABLE); + lua_getfield(state, 2, "items"); + luaL_checktype(state, -1, LUA_TTABLE); + const int count = lua_objlen(state, -1); + std::vector labels; + std::vector icons; + std::vector items; + labels.reserve(count); + icons.reserve(count); + items.reserve(count); + for (int i = 1; i <= count; ++i) { + lua_rawgeti(state, -1, i); + luaL_checktype(state, -1, LUA_TTABLE); + labels.push_back(get_optional_string(state, -1, "label")); + icons.push_back(get_optional_string(state, -1, "icon")); + Callback& callback = retain_callback(*anchor.vm); + callback.refs[0] = ref_optional_function(state, -1, "on_pressed"); + UiContextMenuItem item = UI_CONTEXT_MENU_ITEM_INIT; + item.label = labels.back().c_str(); + item.icon = icons.back().c_str(); + item.on_pressed = callback.refs[0] != LUA_NOREF ? context_menu_action : nullptr; + item.user_data = &callback; + item.enabled = get_optional_bool(state, -1, "enabled", true); + item.selected = get_optional_bool(state, -1, "selected", false); + item.destructive = get_optional_bool(state, -1, "destructive", false); + item.separator_before = get_optional_bool(state, -1, "separator_before", false); + items.push_back(item); + lua_pop(state, 1); + } + lua_pop(state, 1); + UiContextMenuDesc desc = UI_CONTEXT_MENU_DESC_INIT; + desc.items = items.data(); + desc.item_count = items.size(); + UiContextMenuHandle handle = 0; + check_result(state, svc_ui->context_menu_push(anchor.vm->subject, anchor.value, &desc, &handle), + "ui context_menu_push"); + push_ui_handle(state, *anchor.vm, handle, HandleKind::UiContextMenu); + return 1; +} + +int context_menu_close(lua_State* state) { + auto& menu = check_ui_handle(state, 1, HandleKind::UiContextMenu); + check_result( + state, svc_ui->context_menu_close(menu.vm->subject, menu.value), "ui context_menu_close"); + menu.value = 0; + return 0; +} + +int list_set_items(lua_State* state) { + auto& list = check_ui_handle(state, 1, HandleKind::UiList); + std::vector labels; + auto items = list_items(state, 2, labels); + check_result(state, + svc_ui->list_set_items(list.vm->subject, list.value, items.data(), items.size()), + "ui list_set_items"); + return 0; +} + +int window_close(lua_State* state) { + auto& window = check_ui_handle(state, 1, HandleKind::UiWindow); + check_result(state, svc_ui->window_close(window.vm->subject, window.value), "ui window_close"); + window.value = 0; + return 0; +} + +int dialog_close(lua_State* state) { + auto& dialog = check_ui_handle(state, 1, HandleKind::UiDialog); + check_result(state, svc_ui->dialog_close(dialog.vm->subject, dialog.value), "ui dialog_close"); + dialog.value = 0; + return 0; +} + +int dialog_set_body(lua_State* state) { + auto& dialog = check_ui_handle(state, 1, HandleKind::UiDialog); + check_result(state, + svc_ui->dialog_set_body(dialog.vm->subject, dialog.value, luaL_checkstring(state, 2)), + "ui dialog_set_body"); + return 0; +} + +int dialog_set_icon(lua_State* state) { + auto& dialog = check_ui_handle(state, 1, HandleKind::UiDialog); + check_result(state, + svc_ui->dialog_set_icon(dialog.vm->subject, dialog.value, luaL_checkstring(state, 2)), + "ui dialog_set_icon"); + return 0; +} + +int style_unregister(lua_State* state) { + auto& style = check_ui_handle(state, 1, HandleKind::UiStyle); + check_result( + state, svc_ui->unregister_styles(style.vm->subject, style.value), "ui unregister_styles"); + style.value = 0; + return 0; +} + +int menu_tab_unregister(lua_State* state) { + auto& tab = check_ui_handle(state, 1, HandleKind::UiMenuTab); + check_result( + state, svc_ui->unregister_menu_tab(tab.vm->subject, tab.value), "ui unregister_menu_tab"); + tab.value = 0; + return 0; +} + +int register_mods_panel(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + luaL_checktype(state, 1, LUA_TTABLE); + Callback& callback = retain_callback(vm); + callback.refs[0] = ref_required_function(state, 1, "build"); + callback.refs[1] = ref_optional_function(state, 1, "update"); + UiModsPanelDesc desc = UI_MODS_PANEL_DESC_INIT; + desc.build = panel_build; + desc.update = callback.refs[1] != LUA_NOREF ? panel_update : nullptr; + desc.user_data = &callback; + check_result(state, svc_ui->register_mods_panel(vm.subject, &desc), "ui register_mods_panel"); + return 0; +} + +int window_push(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + luaL_checktype(state, 1, LUA_TTABLE); + std::vector tabs; + std::vector titles; + lua_getfield(state, 1, "tabs"); + luaL_checktype(state, -1, LUA_TTABLE); + const int tabCount = lua_objlen(state, -1); + tabs.reserve(tabCount); + titles.reserve(tabCount); + for (int i = 1; i <= tabCount; ++i) { + lua_rawgeti(state, -1, i); + titles.push_back(get_optional_string(state, -1, "title")); + Callback& callback = retain_callback(vm); + callback.refs[0] = ref_required_function(state, -1, "build"); + callback.refs[1] = ref_optional_function(state, -1, "update"); + UiTabDesc tab = UI_TAB_DESC_INIT; + tab.build = tab_build; + tab.update = callback.refs[1] != LUA_NOREF ? panel_update : nullptr; + tab.user_data = &callback; + tabs.push_back(tab); + lua_pop(state, 1); + } + lua_pop(state, 1); + for (size_t i = 0; i < tabs.size(); ++i) { + tabs[i].title = titles[i].c_str(); + } + + const std::string rcss = get_optional_string(state, 1, "rcss"); + Callback& closed = retain_callback(vm); + closed.refs[0] = ref_optional_function(state, 1, "on_closed"); + UiWindowDesc desc = UI_WINDOW_DESC_INIT; + desc.tabs = tabs.data(); + desc.tab_count = tabs.size(); + desc.rcss = rcss.empty() ? nullptr : rcss.c_str(); + desc.on_closed = closed.refs[0] != LUA_NOREF ? window_closed : nullptr; + desc.user_data = &closed; + UiWindowHandle window = 0; + check_result(state, svc_ui->window_push(vm.subject, &desc, &window), "ui window_push"); + push_ui_handle(state, vm, window, HandleKind::UiWindow); + return 1; +} + +UiDialogVariant dialog_variant(lua_State* state, const std::string& variant) { + if (variant.empty() || variant == "normal") { + return UI_DIALOG_NORMAL; + } + if (variant == "warning") { + return UI_DIALOG_WARNING; + } + if (variant == "danger") { + return UI_DIALOG_DANGER; + } + luaL_error(state, "unknown dialog variant '%s'", variant.c_str()); +} + +int dialog_push(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + luaL_checktype(state, 1, LUA_TTABLE); + const std::string title = get_optional_string(state, 1, "title"); + const std::string body = get_optional_string(state, 1, "body_rml"); + const std::string icon = get_optional_string(state, 1, "icon"); + const std::string variant = get_optional_string(state, 1, "variant"); + + std::vector actions; + std::vector labels; + lua_getfield(state, 1, "actions"); + luaL_checktype(state, -1, LUA_TTABLE); + const int actionCount = lua_objlen(state, -1); + actions.reserve(actionCount); + labels.reserve(actionCount); + for (int i = 1; i <= actionCount; ++i) { + lua_rawgeti(state, -1, i); + labels.push_back(get_optional_string(state, -1, "label")); + Callback& callback = retain_callback(vm); + callback.refs[0] = ref_optional_function(state, -1, "on_pressed"); + callback.refs[1] = ref_optional_function(state, -1, "is_disabled"); + UiDialogAction action = UI_DIALOG_ACTION_INIT; + action.on_pressed = callback.refs[0] != LUA_NOREF ? dialog_action : nullptr; + action.is_disabled = callback.refs[1] != LUA_NOREF ? dialog_action_disabled : nullptr; + action.user_data = &callback; + action.keep_open = get_optional_bool(state, -1, "keep_open", false); + actions.push_back(action); + lua_pop(state, 1); + } + lua_pop(state, 1); + for (size_t i = 0; i < actions.size(); ++i) { + actions[i].label = labels[i].c_str(); + } + + Callback& callback = retain_callback(vm); + callback.refs[0] = ref_optional_function(state, 1, "on_dismiss"); + callback.refs[1] = ref_optional_function(state, 1, "build"); + UiDialogDesc desc = UI_DIALOG_DESC_INIT; + desc.title = title.c_str(); + desc.body_rml = body.c_str(); + desc.icon = icon.empty() ? nullptr : icon.c_str(); + desc.variant = dialog_variant(state, variant); + desc.actions = actions.data(); + desc.action_count = actions.size(); + desc.on_dismiss = callback.refs[0] != LUA_NOREF ? dialog_dismissed : nullptr; + desc.build = callback.refs[1] != LUA_NOREF ? dialog_build : nullptr; + desc.user_data = &callback; + UiDialogHandle dialog = 0; + check_result(state, svc_ui->dialog_push(vm.subject, &desc, &dialog), "ui dialog_push"); + push_ui_handle(state, vm, dialog, HandleKind::UiDialog); + return 1; +} + +int is_any_document_visible(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + bool visible = false; + check_result( + state, svc_ui->is_any_document_visible(vm.subject, &visible), "ui is_any_document_visible"); + lua_pushboolean(state, visible); + return 1; +} + +int register_styles(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + const auto scope = style_scope(state, luaL_checkstring(state, 1)); + UiStyleHandle style = 0; + check_result(state, + svc_ui->register_styles(vm.subject, scope, luaL_checkstring(state, 2), &style), + "ui register_styles"); + push_ui_handle(state, vm, style, HandleKind::UiStyle); + return 1; +} + +int register_styles_file(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + const auto scope = style_scope(state, luaL_checkstring(state, 1)); + UiStyleHandle style = 0; + check_result(state, + svc_ui->register_styles_file(vm.subject, scope, luaL_checkstring(state, 2), &style), + "ui register_styles_file"); + push_ui_handle(state, vm, style, HandleKind::UiStyle); + return 1; +} + +int register_menu_tab(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + luaL_checktype(state, 1, LUA_TTABLE); + const std::string label = get_optional_string(state, 1, "label"); + Callback& callback = retain_callback(vm); + callback.refs[0] = ref_required_function(state, 1, "on_selected"); + UiMenuTabDesc desc = UI_MENU_TAB_DESC_INIT; + desc.label = label.c_str(); + desc.on_selected = menu_selected; + desc.user_data = &callback; + UiMenuTabHandle tab = 0; + check_result(state, svc_ui->register_menu_tab(vm.subject, &desc, &tab), "ui register_menu_tab"); + push_ui_handle(state, vm, tab, HandleKind::UiMenuTab); + return 1; +} + +int push_toast(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + luaL_checktype(state, 1, LUA_TTABLE); + const std::string type = get_optional_string(state, 1, "type"); + const std::string title = get_optional_string(state, 1, "title_rml"); + const std::string body = get_optional_string(state, 1, "body_rml"); + UiToastDesc desc = UI_TOAST_DESC_INIT; + desc.type = type.empty() ? nullptr : type.c_str(); + desc.title_rml = title.empty() ? nullptr : title.c_str(); + desc.body_rml = body.empty() ? nullptr : body.c_str(); + desc.duration_ms = static_cast(get_optional_int(state, 1, "duration_ms", 0)); + check_result(state, svc_ui->push_toast(vm.subject, &desc), "ui push_toast"); + return 0; +} + +int get_clipboard_text(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + size_t size = 0; + check_result( + state, svc_ui->get_clipboard_text(vm.subject, nullptr, 0, &size), "ui get_clipboard_text"); + std::vector text(size + 1); + check_result(state, svc_ui->get_clipboard_text(vm.subject, text.data(), text.size(), nullptr), + "ui get_clipboard_text"); + lua_pushlstring(state, text.data(), size); + return 1; +} + +int set_clipboard_text(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + check_result(state, svc_ui->set_clipboard_text(vm.subject, luaL_checkstring(state, 1)), + "ui set_clipboard_text"); + return 0; +} + +} // namespace + +void push_ui_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind) { + push_handle(state, vm, value, kind, ui_metatable(kind)); +} + +int open_ui(lua_State* state) { + Vm& vm = vm_from_upvalue(state); + if (svc_ui == nullptr) { + service_unavailable(state, "UiService"); + } + static const luaL_Reg kElementMethods[] = { + {"add_section", pane_add_section}, + {"add_text", pane_add_text}, + {"add_rml", pane_add_rml}, + {"add_progress", pane_add_progress}, + {"add_control", pane_add_control}, + {"add_group", pane_add_group}, + {"add_list", pane_add_list}, + {"add_row", pane_add_row}, + {"set_label", control_set_label}, + {"set_icon", control_set_icon}, + {"set_options", control_set_options}, + {"set_tooltip", control_set_tooltip}, + {"set_visible", element_set_visible}, + {"focus", element_focus}, + {"set_text", element_set_text}, + {"set_rml", element_set_rml}, + {"set_progress", element_set_progress}, + {"set_class", element_set_class}, + {nullptr, nullptr}, + }; + static const luaL_Reg kWindowMethods[] = {{"close", window_close}, {nullptr, nullptr}}; + static const luaL_Reg kDialogMethods[] = { + {"close", dialog_close}, + {"set_body", dialog_set_body}, + {"set_icon", dialog_set_icon}, + {nullptr, nullptr}, + }; + static const luaL_Reg kStyleMethods[] = {{"unregister", style_unregister}, {nullptr, nullptr}}; + static const luaL_Reg kMenuMethods[] = { + {"unregister", menu_tab_unregister}, {nullptr, nullptr}}; + static const luaL_Reg kListMethods[] = {{"set_items", list_set_items}, {nullptr, nullptr}}; + create_handle_metatable(state, kUiElementMetatable, kElementMethods, "UiElement"); + create_handle_metatable(state, kUiWindowMetatable, kWindowMethods, "UiWindow"); + create_handle_metatable(state, kUiDialogMetatable, kDialogMethods, "UiDialog"); + create_handle_metatable(state, kUiStyleMetatable, kStyleMethods, "UiStyle"); + create_handle_metatable(state, kUiMenuTabMetatable, kMenuMethods, "UiMenuTab"); + create_handle_metatable(state, kUiListMetatable, kListMethods, "UiList"); + static const luaL_Reg kContextMenuMethods[] = { + {"close", context_menu_close}, {nullptr, nullptr}}; + create_handle_metatable(state, kUiContextMenuMetatable, kContextMenuMethods, "UiContextMenu"); + + lua_newtable(state); + set_function(state, vm, "register_mods_panel", register_mods_panel); + set_function(state, vm, "window_push", window_push); + set_function(state, vm, "dialog_push", dialog_push); + set_function(state, vm, "context_menu_push", context_menu_push); + set_function(state, vm, "is_any_document_visible", is_any_document_visible); + set_function(state, vm, "register_styles", register_styles); + set_function(state, vm, "register_styles_file", register_styles_file); + set_function(state, vm, "register_menu_tab", register_menu_tab); + set_function(state, vm, "push_toast", push_toast); + set_function(state, vm, "get_clipboard_text", get_clipboard_text); + set_function(state, vm, "set_clipboard_text", set_clipboard_text); + lua_setreadonly(state, -1, true); + return 1; +} + +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/ui.hpp b/mods/luau_runtime/src/services/ui.hpp new file mode 100644 index 0000000000..c1b8fb532d --- /dev/null +++ b/mods/luau_runtime/src/services/ui.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "../runtime.hpp" +#include "lua.h" + +namespace luau_runtime::services { + +void push_ui_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind); + +} diff --git a/mods/randomizer b/mods/randomizer index 61ba8c4058..69b6bf288e 160000 --- a/mods/randomizer +++ b/mods/randomizer @@ -1 +1 @@ -Subproject commit 61ba8c40589884955929f467c1eda74d1c88a444 +Subproject commit 69b6bf288e17f9f0b9d4e0e34619cf5583299ef0 diff --git a/platforms/android/app/src/main/AndroidManifest.xml b/platforms/android/app/src/main/AndroidManifest.xml index b7270ee433..8dc7bf3236 100644 --- a/platforms/android/app/src/main/AndroidManifest.xml +++ b/platforms/android/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ android:appCategory="game" android:icon="@mipmap/icon" android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" android:theme="@android:style/Theme.NoTitleBar" android:enableOnBackInvokedCallback="false"> diff --git a/platforms/android/app/src/main/res/xml/network_security_config.xml b/platforms/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000000..aa9543fd1f --- /dev/null +++ b/platforms/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + localhost + 127.0.0.1 + ::1 + + diff --git a/platforms/ios/Info.plist.in b/platforms/ios/Info.plist.in index 395b29b7d7..9a9025b9f7 100644 --- a/platforms/ios/Info.plist.in +++ b/platforms/ios/Info.plist.in @@ -85,5 +85,12 @@ LSSupportsGameMode + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + NSLocalNetworkUsageDescription + Dusklight allows network-enabled mods to connect to devices on your local network. diff --git a/platforms/macos/Info.plist.in b/platforms/macos/Info.plist.in index 7f29358033..3f564d88f7 100644 --- a/platforms/macos/Info.plist.in +++ b/platforms/macos/Info.plist.in @@ -32,5 +32,12 @@ public.app-category.adventure-games LSSupportsGameMode + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + NSLocalNetworkUsageDescription + Dusklight allows network-enabled mods to connect to devices on your local network. diff --git a/platforms/tvos/Info.plist.in b/platforms/tvos/Info.plist.in index 49ed85edd7..7f32e2d207 100644 --- a/platforms/tvos/Info.plist.in +++ b/platforms/tvos/Info.plist.in @@ -47,5 +47,12 @@ Automatic LSSupportsGameMode + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + NSLocalNetworkUsageDescription + Dusklight allows network-enabled mods to connect to devices on your local network. diff --git a/res/rml/command_console.rcss b/res/rml/command_console.rcss index b5378b5d7a..d19246692a 100644 --- a/res/rml/command_console.rcss +++ b/res/rml/command_console.rcss @@ -1,8 +1,6 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { + --console-command-color: #FFD966; + display: block; width: 100%; height: 100%; @@ -19,11 +17,11 @@ console { width: 50%; display: flex; flex-direction: column; - background-color: rgba(0, 0, 0, 60%); + background-color: rgba(var(--color-black-rgb), 60%); pointer-events: auto; - font-family: "Noto Mono"; - font-size: 14dp; - color: #FFFFFF; + font-family: var(--font-family-monospace); + font-size: var(--font-size-sm); + color: var(--color-white); transition: background-color 0.8s linear-in-out; } @@ -31,7 +29,7 @@ output { display: block; overflow: hidden; max-height: 480dp; - padding: 4dp 8dp; + padding: var(--space-xs) var(--space-sm); line-height: 1.4em; } @@ -46,11 +44,11 @@ console:not([open]) { } console:not([open])[fading] { - background-color: rgba(0, 0, 0, 0%); + background-color: rgba(var(--color-black-rgb), 0%); } console[open] { - background-color: rgba(0, 0, 0, 60%); + background-color: rgba(var(--color-black-rgb), 60%); transition: none; } @@ -75,19 +73,19 @@ output[open] line { } line.cmd { - color: #FFD966; + color: var(--console-command-color); } console input { display: none; width: 100%; - background-color: rgba(0, 0, 0, 40%); + background-color: rgba(var(--color-black-rgb), 40%); border: 0dp; - border-top: 1dp rgba(255, 255, 255, 20%); - color: #FFFFFF; - font-family: "Noto Mono"; - font-size: 14dp; - padding: 4dp 8dp; + border-top: 1dp rgba(var(--color-white-rgb), 20%); + color: var(--color-white); + font-family: var(--font-family-monospace); + font-size: var(--font-size-sm); + padding: var(--space-xs) var(--space-sm); } console[open] input { diff --git a/res/rml/controls.rcss b/res/rml/controls.rcss new file mode 100644 index 0000000000..eca4900c27 --- /dev/null +++ b/res/rml/controls.rcss @@ -0,0 +1,208 @@ +button, +select-button { + color: var(--button-color); + background-color: var(--button-background); + opacity: 0.9; + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius-window); + box-shadow: rgba(var(--color-border-rgb), 25%) 0 0 0 1dp; + transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out; +} + +button { + text-align: center; + font-size: var(--font-size-2xl); +} + +button:not(:disabled):hover, +button:not(:disabled):focus-visible, +select-button:not(:disabled):hover, +select-button:not(:disabled):focus-visible { + background-color: var(--button-background-hover); + box-shadow: var(--color-accent) 0 0 0 2dp; +} + +button:not(:disabled):selected, +select-button:not(:disabled):selected { + opacity: 1; + background-color: var(--button-background-selected); +} + +button:not(:disabled):active, +select-button:not(:disabled):active { + opacity: 1; + background-color: var(--button-background-active); + box-shadow: var(--color-accent) 0 0 0 2dp; +} + +button:disabled, +select-button:disabled { + opacity: 0.35; + cursor: default; +} + +select-button { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +select-button key, +select-button value { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +select-button key { + font-family: var(--font-family-heading); + font-weight: bold; + font-size: var(--font-size-xl); + text-transform: uppercase; + flex: 0 1 auto; +} + +select-button value { + flex: 1 0 20%; + text-align: right; + font-size: var(--font-size-2xl); +} + +select-button value.modified { + font-weight: bold; +} + +select-button input { + text-align: right; + font-size: var(--font-size-2xl); +} + +select-button icon { + display: none; + flex: 0 0 auto; +} + +select-button.has-icon icon, +select-button.group-button icon { + display: inline-block; +} + +select-button.group-button > icon { + flex: 0 0 24dp; + margin-left: auto; + width: 24dp; + height: 24dp; + font-size: var(--font-size-4xl); + color: var(--button-color); + decorator: text("" center center); +} + +select-button.group-button value { + display: none; +} + +select-button.dropdown-button icon { + display: none; +} + +select-button.dropdown-button dropdown-indicator { + display: block; + flex: 0 0 16dp; + width: 16dp; + height: 16dp; + font-family: var(--font-family-icons); + font-size: 16dp; + font-weight: normal; + line-height: 1; + transition: transform 0.1s linear-in-out; +} + +select-button.dropdown-button.expanded dropdown-indicator { + transform: rotate(180deg); +} + +select-button.color-input value { + font-family: var(--font-family-monospace); + font-size: var(--font-size-base); +} + +select-button.color-input color-swatch { + display: block; + flex: 0 0 48dp; + width: 48dp; + height: 24dp; +} + +button.compact, +select-button.compact { + padding: 6dp var(--space-md); + font-size: var(--font-size-sm); +} + +select-button.compact key { + font-size: var(--font-size-xs); +} + +select-button.compact value, +select-button.compact input { + font-size: var(--font-size-sm); +} + +button.with-icon { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +button.with-icon icon { + flex-shrink: 0; + font-size: 1.25em; + line-height: 1; +} + +button.primary { + --button-background: rgba(var(--color-interactive-rgb), 18%); + --button-background-hover: rgba(var(--color-interactive-rgb), 42%); + --button-background-selected: rgba(var(--color-interactive-rgb), 42%); + --button-background-active: rgba(var(--color-interactive-rgb), 65%); + box-shadow: rgba(var(--color-accent-rgb), 65%) 0 0 0 1dp; +} + +button.overlay { + --button-background: rgba(var(--color-surface-rgb), 80%); + --button-background-hover: rgba(var(--color-control-rgb), 90%); + --button-background-selected: rgba(var(--color-control-rgb), 90%); + --button-background-active: rgba(var(--color-surface-rgb), 90%); + box-shadow: rgba(var(--color-border-rgb), 60%) 0 0 0 1dp; +} + +button.danger { + box-shadow: rgba(var(--color-error-rgb), 55%) 0 0 0 1dp; +} + +button.icon-button { + display: flex; + align-items: center; + justify-content: center; + width: 44dp; + height: 44dp; + padding: var(--space-sm); + box-sizing: border-box; + flex-shrink: 0; + font-size: var(--font-size-5xl); +} + +button.icon-button icon { + display: block; + flex-shrink: 0; + line-height: 1; + pointer-events: none; +} + +button.icon-button.compact { + width: 30dp; + height: 28dp; + padding: 0; + font-size: var(--font-size-md); +} diff --git a/res/rml/global.rcss b/res/rml/global.rcss new file mode 100644 index 0000000000..b96c7a8dfc --- /dev/null +++ b/res/rml/global.rcss @@ -0,0 +1,244 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +b { + font-weight: bold; +} + +p, +heading, +text-list, +text-list item, +file-path { + display: block; +} + +heading { + font-weight: bold; + line-height: 1.3; +} + +small, +file-path, +status-badge { + font-size: var(--font-size-sm); + font-weight: normal; + line-height: 1.4; + color: rgba(var(--color-text-rgb), 62%); +} + +file-path { + word-break: break-all; +} + +ui-row { + display: flex; + flex-flow: row; + align-items: center; + gap: var(--space-sm); + min-width: 0; + focus: none; +} + +ui-row > * { + flex: 0 1 auto; + min-width: 0; +} + +button, +select-button { + cursor: pointer; + focus: auto; +} + +button.danger { + color: var(--color-error); +} + +icon { + width: 1em; + height: 1em; + font-family: var(--font-family-icons); + font-weight: normal; + line-height: 1; + display: inline-block; + vertical-align: middle; +} + +icon.warning { + decorator: text("" center center); +} + +icon.error { + decorator: text("" center center); +} + +icon.verifying { + decorator: text("" center center); +} + +icon.download { + decorator: text("" center center); +} + +icon.celebration { + decorator: text("" center center); +} + +icon.question-mark { + decorator: text("" center center); +} + +icon.arrow-forward { + decorator: text("" center center); +} + +icon.trophy { + decorator: text("" center center); +} + +icon.controller { + decorator: text("" center center); +} + +icon.download-done { + decorator: text("" center center); +} + +mod-icon { + display: block; + flex: 0 0 auto; + width: 56dp; + height: 56dp; + border-radius: var(--radius-panel); + font-family: var(--font-family-icons); + font-size: 36dp; + background-color: rgba(var(--color-control-rgb), 20%); + color: rgba(var(--color-text-rgb), 45%); + decorator: text("" center center); + overflow: hidden; +} + +mod-icon img, +mod-icon-image { + display: block; + width: 100%; + height: 100%; + border-radius: var(--radius-panel); +} + +status-badge.success { + color: var(--color-success); +} + +status-badge.error { + color: var(--color-error); +} + +status-badge.warning { + color: var(--color-warning); +} + +status-badge.info { + color: var(--color-info); +} + +progress { + display: block; + width: 100%; + height: 6dp; + border-radius: 3dp; + background-color: rgba(var(--color-white-rgb), 10%); +} + +progress fill { + background-color: rgba(var(--color-accent-rgb), 80%); + border-radius: 3dp; +} + +progress.success fill, +progress.installed fill { + background-color: var(--color-success); +} + +progress.info fill, +progress.installing fill { + background-color: rgba(var(--color-info-rgb), 80%); +} + +progress.failed fill { + background-color: var(--color-error); +} + +progress.paused fill { + background-color: rgba(var(--color-text-rgb), 35%); +} + +progress.retrying fill { + background-color: rgba(var(--color-warning-rgb), 55%); +} + +scrollbarvertical { + width: 8dp; + margin: var(--space-xs) var(--space-xs) var(--space-xs) 0; +} + +scrollbarvertical sliderarrowdec, +scrollbarvertical sliderarrowinc { + width: 0; + height: 0; +} + +scrollbarvertical slidertrack { + width: 8dp; +} + +scrollbarvertical sliderbar { + width: 8dp; + min-height: 24dp; + background-color: rgba(var(--color-text-rgb), 45%); + border-radius: 2dp; + transition: background-color 0.2s cubic-in-out; +} + +scrollbarvertical sliderbar:hover, +scrollbarvertical sliderbar:active { + background-color: rgba(var(--color-accent-rgb), 80%); +} + +color-swatch { + display: block; + flex: 0 0 auto; + width: 20dp; + height: 20dp; + border-radius: var(--radius-control); + box-shadow: rgba(var(--color-white-rgb), 45%) 0 0 0 1dp; +} + +color-swatch.empty { + background-color: rgba(var(--color-text-rgb), 12%); + decorator: linear-gradient(135deg, rgba(var(--color-text-rgb), 0) 45%, rgba(var(--color-accent-rgb), 70%) 48%, rgba(var(--color-accent-rgb), 70%) 52%, rgba(var(--color-text-rgb), 0) 55%); +} + +ui-tooltip { + display: none; + position: absolute; + z-index: 1000; + max-width: 240dp; + font-family: var(--font-family-body); + font-weight: normal; + padding: 6dp 10dp; + border: 1dp var(--color-border); + border-radius: var(--radius-panel); + background-color: rgba(var(--color-surface-rgb), 96%); + color: var(--color-text); + font-size: var(--font-size-md); + word-break: break-word; + pointer-events: none; + focus: none; +} + +ui-tooltip.visible { + display: block; +} diff --git a/res/rml/logs.rcss b/res/rml/logs.rcss index 126684f9d2..1e5056b953 100644 --- a/res/rml/logs.rcss +++ b/res/rml/logs.rcss @@ -2,19 +2,20 @@ window.logs content { flex-flow: column; } -window.logs .log-toolbar { +window.logs log-toolbar { display: flex; flex-flow: row; - flex: 0 0 64dp; - height: 64dp; + flex: 0 0 var(--toolbar-height); + height: var(--toolbar-height); align-items: center; - gap: 8dp; + gap: var(--space-sm); padding-right: 72dp; - background-color: rgba(217, 217, 217, 10%); - border-bottom: 2dp #92875B; - font-family: "Fira Sans Condensed"; + background-color: rgba(var(--color-neutral-rgb), 10%); + border-bottom-width: 2dp; + border-bottom-color: var(--color-border); + font-family: var(--font-family-heading); font-weight: bold; - font-size: 18dp; + font-size: var(--font-size-xl); } window.logs > close { @@ -22,76 +23,75 @@ window.logs > close { right: 8dp; } -window.logs .log-title { +window.logs log-title { align-self: stretch; flex: 0 0 auto; - padding: 0 24dp; - line-height: 64dp; + padding: 0 var(--space-xl); + line-height: var(--toolbar-height); text-transform: uppercase; - border-bottom: 4dp #C2A42D; + border-bottom-width: 4dp; + border-bottom-color: var(--color-accent); font-effect: glow(0dp 4dp 0dp 4dp black); } -window.logs .log-title-mod { +window.logs log-title-mod { flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; - font-size: 15dp; - color: rgba(224, 219, 200, 55%); + font-size: var(--font-size-base); + color: rgba(var(--color-text-rgb), 55%); } -.log-toolbar-spacer { +log-toolbar-spacer { flex: 1 1 0; } -.log-toolbar button { +log-toolbar button { flex: 0 0 auto; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; - font-size: 15dp; - padding: 5dp 12dp; } window.logs content pane.log-view { flex: 1 1 0; - padding: 12dp 16dp; + padding: var(--space-md) var(--space-lg); padding-bottom: 0dp; gap: 0dp; } -.log-lines { +log-lines { display: block; } -.log-line { +log-line { display: block; - font-family: "Noto Mono"; - font-size: 13dp; + font-family: var(--font-family-monospace); + font-size: var(--font-size-xs); 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-time { + color: rgba(var(--color-text-rgb), 45%); } -.log-line .log-mod { - color: rgba(194, 164, 45, 80%); +log-line log-mod { + color: rgba(var(--color-accent-rgb), 80%); } -.log-line.lvl-trace, -.log-line.lvl-debug { +log-line.lvl-trace, +log-line.lvl-debug { opacity: 0.55; } -.log-line.lvl-warn .log-msg { - color: #ffa826; +log-line.lvl-warn log-msg { + color: var(--color-warning); } -.log-line.lvl-error .log-msg { - color: #cc4444; +log-line.lvl-error log-msg { + color: var(--color-error); } diff --git a/res/rml/mod_browser.rcss b/res/rml/mod_browser.rcss new file mode 100644 index 0000000000..62c3711e42 --- /dev/null +++ b/res/rml/mod_browser.rcss @@ -0,0 +1,833 @@ +window.mod-browser, +window.mod-browser-detail, +window.screenshot-viewer { + background-color: rgba(var(--color-surface-rgb), 96%); +} + +window.mod-browser > content { + flex-flow: row; +} + +catalog-filters { + display: flex; + flex-flow: column; + flex: 0 0 264dp; + min-width: 0; + padding: var(--space-xl) 18dp; + gap: var(--space-sm); + border-right-width: 1dp; + border-right-color: var(--color-border); + background-color: rgba(var(--color-control-rgb), 45%); +} + +catalog-filters h1, +catalog-results header h1 { + margin: 0; + font-family: var(--font-family-heading); + font-size: var(--font-size-5xl); + font-weight: bold; +} + +catalog-filters h1 { + padding: 0 var(--space-sm) var(--space-sm) var(--space-sm); +} + +catalog-filters h2 { + margin: var(--space-md) var(--space-sm) 0 var(--space-sm); + font-family: var(--font-family-heading); + font-size: var(--font-size-xs); + font-weight: bold; + text-transform: uppercase; + opacity: 0.42; +} + +catalog-results { + display: flex; + flex-flow: column; + flex: 1 1 auto; + min-width: 0; + min-height: 0; + padding: var(--space-xl); + gap: var(--space-lg); +} + +catalog-results header { + display: block; + flex: 0 0 auto; + padding-right: var(--space-2xl); +} + +catalog-results > header small { + display: block; + margin-top: var(--space-2xs); + font-size: var(--font-size-xs); +} + +catalog-viewport { + display: block; + flex: 1 1 auto; + min-width: 0; + min-height: 0; + overflow: hidden auto; +} + +catalog-grid { + display: flex; + flex-flow: row wrap; + min-width: 0; + gap: 14dp; + padding: var(--space-2xs); +} + +.catalog-card { + display: flex; + flex-flow: column; + flex: 0 0 48%; + min-width: 260dp; + height: 310dp; + padding: 0; + overflow: hidden; + text-align: left; + border-radius: 12dp; +} + +catalog-card-art { + display: block; + position: relative; + flex: 0 0 118dp; + min-height: 118dp; +} + +catalog-card-art-image { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + mask-image: linear-gradient(180deg, #fff 45%, transparent); +} + +catalog-card-body { + display: flex; + position: relative; + flex-flow: column; + flex: 1 1 auto; + min-height: 0; + padding: var(--space-xl) var(--space-lg) var(--space-lg); + gap: 7dp; +} + +catalog-official-badge { + padding: var(--space-2xs) 5dp; + border-radius: var(--radius-small); + background-color: rgba(var(--color-accent-rgb), 28%); + color: var(--color-info); + font-style: normal; + font-size: var(--font-size-3xs); +} + +catalog-card-body > section > b { + display: block; + font-family: var(--font-family-heading); + font-size: var(--font-size-sm); + font-weight: bold; + text-transform: uppercase; + color: var(--color-accent); +} + +catalog-card-body > small { + position: absolute; + top: var(--space-md); + right: var(--space-lg); + margin: 0; + padding: 0; + font-size: var(--font-size-sm); + font-weight: normal; + text-transform: none; +} + +.catalog-card mod-icon { + position: absolute; + left: 14dp; + bottom: -18dp; + z-index: 1; + width: 56dp; + height: 56dp; + box-shadow: rgba(var(--color-black-rgb), 60%) 0 6dp 16dp; +} + +catalog-card-body > section { + display: block; + margin: 0; + padding: 0; +} + +catalog-card-body > section h2 { + display: block; + margin: 0; + font-family: var(--font-family-body); + font-size: var(--font-size-3xl); + font-weight: bold; + color: var(--color-neutral); + line-height: 1.5; +} + +catalog-card-body > section small { + display: block; + font-size: var(--font-size-3xs); +} + +catalog-card-body > p { + flex: 1 1 auto; + min-height: 0; + margin: 0; + overflow: hidden; + font-size: var(--font-size-base); + line-height: 1.35; + color: rgba(var(--color-text-rgb), 68%); +} + +catalog-card-body > footer { + display: flex; + align-items: center; + gap: 10dp; + font-size: var(--font-size-3xs); +} + +catalog-card-body > footer stat { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: 3dp; + white-space: nowrap; + opacity: 0.52; +} + +catalog-card-body > footer icon { + flex: 0 0 13dp; + font-size: var(--font-size-sm); + line-height: 1; +} + +catalog-card-body > footer .size { + margin-left: auto; + font-size: var(--font-size-sm); + white-space: nowrap; + opacity: 0.52; +} + +catalog-card-body > footer .size.installed { + color: var(--color-success); +} + +catalog-card-body > footer .size.update-available { + color: var(--color-accent); + opacity: 1; +} + +catalog-pagination { + display: flex; + flex-flow: row; + align-items: center; + flex: 0 0 auto; + gap: var(--space-sm); +} + +catalog-pagination-label { + flex: 1 1 auto; + text-align: center; + font-size: var(--font-size-xs); + opacity: 0.5; +} + +catalog-results-status, +catalog-detail-status { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + height: 100%; + gap: var(--space-sm); + text-align: center; +} + +catalog-results-status h2, +catalog-detail-status h2 { + display: block; + margin: 0; + font-family: var(--font-family-heading); + font-size: var(--font-size-4xl); + font-weight: bold; +} + +catalog-results-status p, +catalog-detail-status p { + display: block; + margin: 0; + opacity: 0.58; +} + +catalog-results-status button, +catalog-detail-status button { + margin-top: var(--space-sm); + font-size: var(--font-size-base); +} + +window.mod-browser-detail > content { + display: block; +} + +detail-scroll { + display: flex; + flex-flow: column; + width: 100%; + height: 100%; + min-width: 0; + overflow: hidden auto; + padding-bottom: var(--space-2xl); +} + +catalog-detail-hero { + display: flex; + position: relative; + flex-flow: column; + justify-content: space-between; + flex: 0 0 220dp; + min-height: 220dp; + padding: 18dp var(--space-xl) 20dp var(--space-xl); +} + +catalog-detail-hero-image { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + mask-image: linear-gradient(180deg, #fff 40%, transparent); +} + +catalog-detail-actions { + display: flex; + position: relative; + z-index: 1; + flex-flow: row; + gap: var(--space-sm); +} + +.catalog-install-action { + display: flex; + position: relative; + align-items: center; + justify-content: center; + gap: 10dp; + min-width: 132dp; + padding: 8dp 22dp; + border-radius: var(--radius-window); + overflow: hidden; + font-size: var(--font-size-2xl); + opacity: 1; + --button-background: rgba(var(--color-control-rgb), 20%); + --button-background-hover: rgba(var(--color-interactive-rgb), 20%); + --button-background-selected: rgba(var(--color-interactive-rgb), 20%); + --button-background-active: rgba(var(--color-interactive-rgb), 20%); + box-shadow: var(--color-accent) 0 0 0 2dp; +} + +.catalog-install-action icon { + flex: 0 0 22dp; + font-size: var(--font-size-3xl); + line-height: 1; +} + +.catalog-install-action > span { + white-space: nowrap; +} + +.catalog-install-action.paused { + box-shadow: rgba(var(--color-border-rgb), 25%) 0 0 0 1dp; +} + +.catalog-install-action.paused:not(:disabled):hover, +.catalog-install-action.paused:not(:disabled):focus-visible { + box-shadow: var(--color-accent) 0 0 0 2dp; +} + +.catalog-install-action progress { + position: absolute; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 4dp; + margin: 0; + border-radius: 0; + overflow: hidden; + background-color: rgba(var(--color-white-rgb), 10%); +} + +.catalog-install-action progress fill { + border-radius: 0; +} + +.catalog-install-action.retrying { + color: var(--color-warning); + box-shadow: rgba(var(--color-warning-rgb), 60%) 0 0 0 2dp; +} + +.catalog-install-action.failed { + color: var(--color-white); + --button-background: rgba(var(--color-error-rgb), 20%); + --button-background-hover: rgba(var(--color-error-rgb), 35%); + --button-background-selected: rgba(var(--color-error-rgb), 35%); + --button-background-active: rgba(var(--color-error-rgb), 35%); + box-shadow: var(--color-error) 0 0 0 2dp; +} + +.catalog-install-action.installed { + color: var(--color-success); + box-shadow: rgba(var(--color-success-rgb), 50%) 0 0 0 2dp; +} + +catalog-install-control { + display: flex; + flex-flow: column; + align-items: flex-end; + flex: 0 0 auto; + gap: var(--space-xs); +} + +catalog-install-control small { + display: block; + max-width: 240dp; + overflow: hidden; + font-size: var(--font-size-xs); + color: rgba(var(--color-text-rgb), 50%); + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; +} + +catalog-install-control small.failed { + color: var(--color-error); +} + +catalog-detail-identity { + display: flex; + position: relative; + z-index: 1; + align-items: flex-end; + gap: var(--space-lg); +} + +catalog-detail-identity mod-icon { + flex: 0 0 70dp; + width: 70dp; + height: 70dp; + box-shadow: rgba(var(--color-black-rgb), 65%) 0 8dp 20dp; +} + +catalog-detail-identity > header { + display: block; + flex: 1 1 auto; + min-width: 0; +} + +catalog-detail-identity > header > b { + display: block; + font-family: var(--font-family-heading); + font-size: var(--font-size-sm); + font-weight: bold; + text-transform: uppercase; + color: var(--color-accent); +} + +catalog-detail-identity h1 { + display: block; + margin: 1dp 0; + font-family: var(--font-family-body); + font-size: var(--font-size-5xl); + font-weight: bold; + color: var(--color-neutral); +} + +catalog-detail-identity h1 small { + margin-left: 9dp; + font-family: var(--font-family-body); + font-size: var(--font-size-xs); + font-weight: normal; +} + +catalog-detail-identity p { + display: block; + margin: 0; + font-size: var(--font-size-xs); + opacity: 0.72; +} + +catalog-detail-stats { + display: flex; + flex-flow: row; + align-items: center; + gap: 28dp; + margin: 0 var(--space-xl); + font-size: var(--font-size-2xs); + color: rgba(var(--color-text-rgb), 58%); +} + +catalog-detail-stats > stat { + display: flex; + align-items: center; + gap: 5dp; +} + +catalog-detail-stats icon { + flex: 0 0 17dp; + font-size: var(--font-size-lg); + line-height: 1; +} + +catalog-detail-body { + display: flex; + flex-flow: row; + align-items: flex-start; + gap: 28dp; + padding: var(--space-xl); +} + +catalog-detail-body main { + display: flex; + flex-flow: column; + flex: 1 1 auto; + min-width: 0; + gap: 28dp; +} + +catalog-detail-body section { + display: block; +} + +catalog-detail-body section.catalog-scroll-anchor { + focus: auto; +} + +catalog-detail-body section.catalog-scroll-anchor:focus-visible { + border-radius: var(--radius-control); + box-shadow: rgba(var(--color-accent-rgb), 45%) 0 0 0 1dp; +} + +catalog-detail-body h2, +catalog-detail-body h3 { + display: block; + margin: 0 0 10dp 0; + font-family: var(--font-family-heading); + font-size: var(--font-size-2xl); + font-weight: bold; +} + +catalog-detail-body h2 small { + margin-left: 7dp; + font-family: var(--font-family-body); + font-size: var(--font-size-3xs); + font-weight: normal; +} + +catalog-fragment { + display: block; + font-size: var(--font-size-md); + line-height: 1.55; + color: var(--color-neutral); +} + +catalog-fragment p, +catalog-fragment ul, +catalog-fragment ol { + display: block; + margin: 0 0 10dp 0; +} + +catalog-fragment ul, +catalog-fragment ol { + padding-left: var(--space-xl); +} + +catalog-fragment li { + display: block; + position: relative; + margin: 3dp 0; +} + +catalog-list-marker { + display: block; + position: absolute; + right: 100%; + width: var(--space-xl); + padding-right: var(--space-sm); + text-align: right; +} + +catalog-fragment h1, +catalog-fragment h2, +catalog-fragment h3, +catalog-fragment h4, +catalog-fragment h5, +catalog-fragment h6 { + display: block; + margin: 15dp 0 5dp 0; + font-family: var(--font-family-heading); + font-weight: bold; + font-size: var(--font-size-xl); + color: var(--color-neutral); +} + +catalog-fragment h1 { + font-size: var(--font-size-5xl); +} + +catalog-fragment h2 { + font-size: var(--font-size-4xl); +} + +catalog-fragment h3 { + font-size: var(--font-size-2xl); +} + +window.screenshot-viewer > close { + display: none; +} + +catalog-gallery { + display: flex; + flex-flow: row; + height: 210dp; + gap: var(--space-sm); +} + +.catalog-screenshot { + position: relative; + overflow: hidden; + flex: 1 1 0; + height: 100%; + min-width: 0; + padding: 0; + border-radius: var(--radius-panel); + --button-background: rgba(var(--color-border-rgb), 14%); + font-size: var(--font-size-5xl); +} + +.catalog-screenshot.primary { + flex: 2 1 0; +} + +catalog-screenshot-image, +catalog-screenshot-more { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; +} + +catalog-screenshot-more { + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(var(--color-black-rgb), 60%); + color: var(--color-white); +} + +catalog-dependencies { + display: flex; + flex-flow: column; + gap: 7dp; + font-size: var(--font-size-xs); +} + +catalog-dependency { + display: flex; + flex-flow: column; + padding: 9dp 11dp; + border-radius: var(--radius-control); + background-color: rgba(var(--color-border-rgb), 9%); +} + +catalog-dependency-status { + display: block; + font-size: var(--font-size-3xs); + opacity: 0.56; +} + +catalog-dependency.missing { + color: var(--color-warning); +} + +catalog-detail-body aside { + display: flex; + flex-flow: column; + flex: 0 0 264dp; + min-width: 0; + padding: var(--space-lg); + gap: var(--space-md); + border-radius: 9dp; + background-color: rgba(var(--color-control-rgb), 42%); + box-shadow: rgba(var(--color-border-rgb), 24%) 0 0 0 1dp; +} + +catalog-detail-body dl { + display: flex; + flex-flow: row wrap; + margin: 0; + font-size: var(--font-size-sm); +} + +catalog-detail-body dt { + flex: 0 0 42%; + padding: 5dp 0; + font-weight: bold; + opacity: 0.8; +} + +catalog-detail-body dd { + flex: 1 1 52%; + margin: 0; + padding: 5dp 0; + text-align: right; +} + +window.screenshot-viewer > content { + flex-flow: column; + padding: 18dp; + gap: var(--space-md); +} + +catalog-screenshot-full { + display: block; + flex: 1 1 auto; + min-width: 0; + min-height: 0; + background-color: rgba(var(--color-black-rgb), 45%); +} + +catalog-screenshot-actions { + display: flex; + flex-flow: row; + justify-content: center; + flex: 0 0 auto; + gap: var(--space-sm); +} + +@media (max-height: 640dp) { + catalog-filters { + flex-basis: 232dp; + padding: 15dp var(--space-md); + gap: 5dp; + } + + catalog-filters h1, + catalog-results header h1 { + font-size: var(--font-size-4xl); + } + + catalog-filters h2 { + margin-top: 6dp; + } + + catalog-results { + padding: 15dp; + gap: 10dp; + } + + .catalog-card { + min-width: 220dp; + height: 254dp; + } + + catalog-card-art { + flex-basis: 78dp; + min-height: 78dp; + } + + catalog-card-body { + padding: 18dp 10dp var(--space-sm) 10dp; + } + + .catalog-card mod-icon { + left: 10dp; + bottom: -14dp; + width: 40dp; + height: 40dp; + } + + catalog-card-body > section > b, + catalog-card-body > small { + font-size: var(--font-size-3xs); + } + + catalog-card-body > section h2 { + font-size: var(--font-size-md); + } + + catalog-card-body > p { + font-size: var(--font-size-3xs); + } + + catalog-detail-hero { + flex-basis: 160dp; + min-height: 160dp; + padding: var(--space-md) 18dp; + } + + catalog-detail-identity mod-icon { + flex-basis: 56dp; + width: 56dp; + height: 56dp; + } + + catalog-detail-identity { + gap: var(--space-md); + } + + catalog-detail-identity > header > b { + font-size: var(--font-size-2xs); + } + + .catalog-install-action { + gap: var(--space-sm); + min-width: 112dp; + padding: 7dp 18dp; + border-radius: 12dp; + font-size: var(--font-size-md); + } + + .catalog-install-action icon { + flex-basis: 18dp; + font-size: var(--font-size-xl); + } + + catalog-install-control { + gap: 3dp; + } + + catalog-install-control small { + max-width: 190dp; + font-size: var(--font-size-3xs); + } + + catalog-detail-identity h1 { + font-size: var(--font-size-3xl); + } + + catalog-detail-body { + padding: 18dp; + gap: 18dp; + } + + catalog-gallery { + height: 160dp; + } +} diff --git a/res/rml/mod_common.rcss b/res/rml/mod_common.rcss new file mode 100644 index 0000000000..f221049b6e --- /dev/null +++ b/res/rml/mod_common.rcss @@ -0,0 +1,36 @@ +update-badge { + display: inline-block; + flex: 0 0 auto; + align-self: center; + min-width: 20dp; + height: 20dp; + margin-left: 8dp; + padding: 0 6dp; + border-radius: 10dp; + background-color: var(--color-accent); + color: #19170f; + font-family: var(--font-family-body); + font-size: 12dp; + font-weight: bold; + line-height: 20dp; + text-align: center; + white-space: nowrap; + vertical-align: middle; + pointer-events: none; + transform: translateY(0dp); +} + +.mod-update-link { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.mod-update-link > update-label { + display: block; + flex: 0 0 auto; +} + +.mod-update-link > update-badge { + margin-left: 0; +} diff --git a/res/rml/mods.rcss b/res/rml/mods.rcss index 34e86f485b..fe08d2df4d 100644 --- a/res/rml/mods.rcss +++ b/res/rml/mods.rcss @@ -1,8 +1,8 @@ window.mods content pane.mod-list { flex: 0 0 360dp; - padding: 16dp; + padding: var(--space-lg); padding-bottom: 0dp; - gap: 4dp; + gap: var(--space-xs); } @media (max-height: 640dp) { @@ -12,136 +12,148 @@ window.mods content pane.mod-list { } window.mods content pane.mod-detail { - gap: 12dp; + gap: var(--space-md); } -.mod-info-row { +mod-entry.online { + min-height: 76dp; +} + +mod-entry.online mod-icon { + color: var(--color-accent); + decorator: text("" center center); +} + +mod-list-separator { + display: block; + height: 1dp; + margin: var(--space-sm) 10dp; + background-color: rgba(var(--color-border-rgb), 30%); +} + +mod-info-row { display: flex; align-items: center; - gap: 12dp; - padding: 4dp 0; + gap: var(--space-md); + padding: var(--space-xs) 0; } -.mod-info-label { - font-family: "Fira Sans Condensed"; - font-weight: bold; +mod-info-row > b { + font-family: var(--font-family-heading); opacity: 0.55; flex: 0 0 auto; } -.mod-info-value { +mod-info-row > span { 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; + flex: 0 0 auto; + gap: var(--space-md); padding: 10dp; border-radius: 10dp; - decorator: vertical-gradient(#c2a42d00 #c2a42d00); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 0%) rgba(var(--color-accent-rgb), 0%)); 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; + box-shadow: rgba(var(--color-border-rgb), 40%) 0 0 0 1dp; } mod-entry:hover, mod-entry:focus-visible { - decorator: vertical-gradient(#c2a42d00 #c2a42d26); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 0) rgba(var(--color-accent-rgb), 38)); } mod-entry:selected { - decorator: vertical-gradient(#c2a42d10 #c2a42d40); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 16) rgba(var(--color-accent-rgb), 64)); } -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 { +mod-info { display: flex; flex-flow: column; flex: 1 1 0; min-width: 0; - gap: 2dp; + gap: var(--space-2xs); } -mod-entry .mod-entry-name { +mod-info header { display: flex; flex-flow: row; - align-items: baseline; + align-items: center; gap: 6dp; } -mod-entry .mod-entry-name-text { +mod-info header b { flex: 0 1 auto; min-width: 0; - font-weight: bold; white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; } -mod-entry .mod-entry-version { +mod-info header update-badge { + margin-left: 0; +} + +mod-meta { + display: flex; + align-items: baseline; + gap: 5dp; + min-width: 0; + font-size: var(--font-size-xs); + color: rgba(var(--color-text-rgb), 50%); +} + +mod-meta > * { + display: block; flex: 0 0 auto; - font-size: 13dp; - color: rgba(224, 219, 200, 50%); + white-space: nowrap; } -mod-entry .mod-entry-status.active { - color: #44cc55; +mod-meta > mod-author { + flex: 0 1 auto; + min-width: 0; + font-size: var(--font-size-xs); + color: rgba(var(--color-text-rgb), 50%); + overflow: hidden; + text-overflow: ellipsis; } -mod-entry .mod-entry-status.failed { - color: #cc4444; -} - -mod-entry .mod-entry-network { - margin-left: 6dp; +mod-network { + flex: 0 0 auto; padding: 1dp 5dp; + font-size: var(--font-size-xs); border-radius: 5dp; - background-color: rgba(67, 151, 219, 20%); - color: #6fb7ef; + background-color: rgba(var(--color-info-rgb), 20%); + color: var(--color-info); } -mod-entry .mod-entry-desc { - font-size: 14dp; +mod-info > p { + display: block; + margin: 0; + font-size: var(--font-size-sm); line-height: 1.3; - color: rgba(224, 219, 200, 65%); + color: rgba(var(--color-text-rgb), 65%); max-height: 2.6em; overflow: hidden; - white-space: pre-wrap; } -mod-entry .mod-entry-sub { - font-size: 13dp; - color: rgba(224, 219, 200, 50%); +mod-info > small { + font-size: var(--font-size-xs); + color: rgba(var(--color-text-rgb), 50%); } -mod-entry.inactive .mod-icon { +mod-entry.inactive mod-icon { filter: grayscale(1); } -mod-entry.inactive .mod-entry-info { +mod-entry.inactive mod-info { opacity: 0.5; } @@ -155,20 +167,29 @@ mod-header.has-banner { margin: -24dp -24dp 0dp -24dp; } -mod-header .mod-actions { +mod-header-image { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + mask-image: linear-gradient(180deg, #fff 40%, transparent); + filter: grayscale(0); +} + +mod-header.inactive mod-header-image { + filter: grayscale(1); +} + +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; + gap: var(--space-sm); } mod-header.no-banner { @@ -177,56 +198,218 @@ mod-header.no-banner { align-items: center; } -mod-header.no-banner .mod-actions { +mod-header.no-banner mod-actions { position: static; } -window.mods .mod-title { +mod-title { display: block; - font-size: 28dp; + font-size: var(--font-size-5xl); font-weight: bold; } -window.mods .mod-title .mod-title-version { +mod-title small { font-weight: normal; - font-size: 16dp; - color: rgba(224, 219, 200, 55%); + font-size: var(--font-size-md); + color: rgba(var(--color-text-rgb), 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; +mod-restart-note { + font-size: var(--font-size-base); + color: var(--color-warning); opacity: 0.85; } -window.mods .mod-description { +mod-description { line-height: 1.5; } -.status-badge { - font-size: 14dp; - opacity: 0.7; +mod-info status-badge { + font-size: var(--font-size-xs); } -.status-badge.active, -.mod-info-label.active { - color: #44cc55; +mod-info-row > b.error { + color: var(--color-error); opacity: 1; } -.status-badge.failed, -.mod-info-label.failed { - color: #cc4444; - opacity: 1; +button.browse-mods-action { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-lg); + margin-right: 48dp; + padding: 22dp; + text-align: left; + --button-background: rgba(var(--color-accent-rgb), 18%); + --button-background-hover: rgba(var(--color-accent-rgb), 28%); + --button-background-selected: rgba(var(--color-accent-rgb), 28%); + --button-background-active: rgba(var(--color-accent-rgb), 38%); + box-shadow: rgba(var(--color-accent-rgb), 65%) 0 0 0 1dp; } -.status-badge.network { - color: #6fb7ef; - opacity: 1; +browse-copy { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-width: 0; + gap: var(--space-sm); +} + +browse-copy h2 { + display: block; + margin: 0; + font-family: var(--font-family-heading); + font-size: var(--font-size-5xl); + font-weight: bold; +} + +browse-copy p { + display: block; + margin: 0; + font-size: var(--font-size-md); + line-height: 1.4; + color: rgba(var(--color-text-rgb), 70%); +} + +.browse-mods-action > icon { + flex: 0 0 auto; + font-size: var(--font-size-4xl); + color: var(--color-accent); +} + +updates-header, update-card, update-changelog, queue-row { + display: block; +} + +updates-header { + padding: var(--space-sm) 0; +} + +updates-header h2, online-section-heading h2 { + display: block; + flex: 1 1 auto; + margin: 0; + font-family: var(--font-family-heading); + font-size: var(--font-size-3xl); + font-weight: bold; +} + +updates-heading, online-section-heading { + display: flex; + align-items: center; + gap: var(--space-sm); + margin-bottom: var(--space-sm); +} + +online-section-heading { + flex-wrap: wrap; + justify-content: space-between; + padding-top: var(--space-sm); +} + +online-section-heading button { + flex: 0 0 auto; + padding: 6dp 12dp; + font-size: var(--font-size-sm); +} + +updates-heading button.icon-button { + flex: 0 0 30dp; + width: 30dp; + height: 30dp; + padding: 4dp; + font-size: 22dp; +} + +updates-header p { + display: block; + margin: 0 0 var(--space-sm); + font-size: var(--font-size-sm); + line-height: 1.4; +} + +updates-actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-sm); +} + +updates-actions button.update-all { + display: flex; + align-items: center; + justify-content: center; + gap: 8dp; + padding: 8dp 14dp; + font-size: var(--font-size-md); + box-shadow: rgba(var(--color-accent-rgb), 40%) 0 0 0 1dp; +} + +updates-actions button.update-all icon { + flex: 0 0 20dp; + font-size: var(--font-size-xl); +} + +updates-actions button.update-all > span { + white-space: nowrap; +} + +queue-row { + border-bottom-width: 1dp; + border-bottom-color: rgba(var(--color-border-rgb), 30%); +} + +queue-row package-row { + padding: 8dp 0 14dp; + border-bottom-width: 0; +} + +window.mods package-row > section > header { + flex-wrap: wrap; +} + +window.mods package-row h3 { + flex-wrap: wrap; +} + +window.mods package-row.failed footer > small { + white-space: normal; + line-height: 1.3; +} + +update-card { + padding: 12dp 0; + border-bottom-width: 1dp; + border-bottom-color: #ffffff22; +} + +update-card package-row { + padding: 0; + border-bottom-width: 0; +} + +update-changelog { + padding: 8dp 12dp; + margin-top: 8dp; + background-color: #00000022; + border-radius: var(--radius-small); + font-size: var(--font-size-md); + line-height: 1.5; +} + +update-changelog p, +update-changelog h1, +update-changelog h2, +update-changelog h3, +update-changelog ul, +update-changelog ol, +update-changelog blockquote, +update-changelog pre { + display: block; + margin: 0 0 var(--space-sm); +} +update-changelog h1, +update-changelog h2, +update-changelog h3 { + font-weight: bold; } diff --git a/res/rml/overlay.rcss b/res/rml/overlay.rcss index 98f8bfa27a..879ce7cee1 100644 --- a/res/rml/overlay.rcss +++ b/res/rml/overlay.rcss @@ -1,17 +1,13 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { overflow: visible; width: 100%; height: 100%; margin: 0; padding: 0; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; - font-size: 20dp; - color: #E0DBC8; + font-size: var(--font-size-2xl); + color: var(--color-text); display: flex; flex-direction: column; justify-content: flex-end; @@ -24,16 +20,22 @@ fps, pipeline-progress, toast { position: absolute; - border: 1dp #92875B; - background-color: rgba(21, 22, 16, 80%); + border-width: 1dp; + background-color: rgba(var(--color-surface-rgb), 80%); +} + +fps, +pipeline-progress { + border-color: var(--color-border); } toast { + border-color: var(--toast-border-color); top: 40dp; right: 40dp; display: flex; flex-flow: column; - border-radius: 14dp; + border-radius: var(--radius-window); overflow: hidden; backdrop-filter: blur(5dp); box-shadow: 0 0 15dp 3dp; @@ -41,8 +43,8 @@ toast { transform: scale(0.9); transform-origin: center; transition: filter transform 0.2s cubic-in-out; - padding: 18dp 24dp; - gap: 8dp; + padding: 18dp var(--space-xl); + gap: var(--space-sm); } toast[open] { @@ -50,31 +52,19 @@ toast[open] { transform: scale(1); } -/*toast:hover { - cursor: pointer; - background-color: rgba(61, 59, 36, 80%); -} - -toast:active { - background-color: rgba(45, 43, 26, 80%); -}*/ - -b { - font-weight: bold; -} - toast heading { display: flex; gap: 18dp; align-items: center; - font-family: "Fira Sans Condensed"; - font-size: 18dp; + font-family: var(--font-family-heading); + font-size: var(--font-size-xl); font-weight: bold; + line-height: 1.2; text-transform: uppercase; - color: #92875B; + color: var(--toast-heading-color); } -toast heading > span { +toast heading > toast-title { flex: 1 0 auto; } @@ -82,13 +72,13 @@ toast heading > row { flex: 1 0 auto; display: flex; align-items: center; - gap: 4dp; + gap: var(--space-xs); } toast message { display: flex; flex-flow: column; - gap: 8dp; + gap: var(--space-sm); } toast message row { @@ -99,8 +89,38 @@ toast message row.muted { opacity: 0.5; } +toast.mod-installed row { + align-items: center; + gap: var(--space-md); +} + +mod-icon { + flex: 0 0 42dp; + width: 42dp; + height: 42dp; + background-color: rgba(var(--color-control-rgb), 45%); + font-size: var(--font-size-4xl); +} + +mod-info { + display: flex; + flex-flow: column; + min-width: 0; + gap: var(--space-xs); +} + +mod-name { + color: var(--color-white); +} + +toast.mod-installed small.version { + margin-left: var(--space-sm); +} + progress { height: 4dp; + border-radius: 0; + background-color: transparent; position: absolute; left: 0; bottom: 0; @@ -108,7 +128,7 @@ progress { } progress fill { - background-color: rgba(194, 164, 45, 80%); + border-radius: 0; } pipeline-progress { @@ -119,8 +139,8 @@ pipeline-progress { z-index: 100; min-width: 260dp; max-width: 90%; - padding: 10dp 16dp 12dp; - border-radius: 7dp; + padding: 10dp var(--space-lg) var(--space-md); + border-radius: var(--radius-control); overflow: hidden; filter: opacity(0); transition: filter 0.2s linear-in-out; @@ -134,8 +154,8 @@ pipeline-progress[open] { pipeline-status { display: flex; align-items: center; - gap: 8dp; - font-size: 18dp; + gap: var(--space-sm); + font-size: var(--font-size-xl); font-weight: normal; white-space: nowrap; } @@ -145,26 +165,16 @@ icon.pipeline-spinner { height: 1.2em; line-height: 1.2em; font-size: 1.2em; - color: #C2A42D; + color: var(--color-accent); text-align: center; transform-origin: center; animation: 1s linear infinite pipeline-spinner-spin; } -toast.achievement { - border: 1dp #C2A42D; -} - -toast.achievement heading { - color: #C2A42D; -} - +toast.achievement, toast.warning { - border: 1dp #C2A42D; -} - -toast.warning heading { - color: #C2A42D; + --toast-border-color: var(--color-accent); + --toast-heading-color: var(--color-accent); } toast.controller-warning { @@ -181,8 +191,8 @@ toast.controller-warning[open] { transform: translateX(-50%) scale(1); } -toast.controller-warning heading { - color: #C2A42D; +toast.controller-warning { + --toast-heading-color: var(--color-accent); } toast.menu-notification { @@ -208,48 +218,19 @@ toast.menu-notification message row { gap: 6dp; } -icon { - font-family: "Material Symbols Rounded"; - font-weight: normal; - display: inline-block; - vertical-align: middle; -} - -icon.arrow-forward { +toast icon { width: 1.2em; height: 1.2em; font-size: 1.2em; - decorator: text("" center center); -} - -icon.trophy { - width: 1.2em; - height: 1.2em; - font-size: 1.2em; - decorator: text("" center center); -} - -icon.controller { - width: 1.2em; - height: 1.2em; - font-size: 1.2em; - decorator: text("" center center); -} - -icon.warning { - width: 1.2em; - height: 1.2em; - font-size: 1.2em; - decorator: text("" center center); } fps { display: none; z-index: 99; - font-size: 18dp; + font-size: var(--font-size-xl); font-weight: bold; - padding: 9dp 12dp; - border-radius: 7dp; + padding: 9dp var(--space-md); + border-radius: var(--radius-control); pointer-events: none; white-space: nowrap; } @@ -260,12 +241,12 @@ speedrun-timer { bottom: 0; right: 0; z-index: 99; - background-color: rgba(0, 0, 0, 65%); - padding: 2dp 4dp; + background-color: rgba(var(--color-black-rgb), 65%); + padding: var(--space-2xs) var(--space-xs); pointer-events: none; - font-family: "Noto Mono"; - font-size: 16dp; - color: #ffffff; + font-family: var(--font-family-monospace); + font-size: var(--font-size-md); + color: var(--color-white); white-space: nowrap; } @@ -329,7 +310,7 @@ logo img { left: 0; width: 100%; height: 100%; - filter: drop-shadow(#0008 0 0 14dp); + filter: drop-shadow(rgba(var(--color-black-rgb), 53.333333%) 0 0 14dp); transform-origin: center; } diff --git a/res/rml/popover.rcss b/res/rml/popover.rcss index 1db4f64a05..6b892970c8 100644 --- a/res/rml/popover.rcss +++ b/res/rml/popover.rcss @@ -1,8 +1,6 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { + --button-background: rgba(var(--color-control-rgb), 35%); + width: 100%; height: 100%; z-index: 10; @@ -12,14 +10,15 @@ popover { position: absolute; display: flex; flex-flow: column; - font-family: "Fira Sans"; - font-size: 14dp; - color: #E0DBC8; - border-radius: 14dp; - border: 2dp #92875B; - background-color: rgba(21, 22, 16, 96%); + font-family: var(--font-family-body); + font-size: var(--font-size-sm); + color: var(--color-text); + border-radius: var(--radius-window); + border-width: 2dp; + border-color: var(--color-border); + background-color: rgba(var(--color-surface-rgb), 96%); backdrop-filter: blur(5dp); - box-shadow: 0 6dp 24dp 2dp rgba(0, 0, 0, 55%); + box-shadow: 0 6dp 24dp 2dp rgba(var(--color-black-rgb), 55%); filter: opacity(0); transform: scale(0.95); transform-origin: center; @@ -43,8 +42,8 @@ color-sv { position: relative; width: 240dp; height: 150dp; - border-radius: 8dp; - box-shadow: rgba(146, 135, 91, 50%) 0 0 0 1dp; + border-radius: var(--radius-panel); + box-shadow: rgba(var(--color-border-rgb), 50%) 0 0 0 1dp; drag: drag; focus: auto; } @@ -55,8 +54,8 @@ color-alpha { position: relative; width: 240dp; height: 14dp; - border-radius: 7dp; - box-shadow: rgba(146, 135, 91, 50%) 0 0 0 1dp; + border-radius: var(--radius-control); + box-shadow: rgba(var(--color-border-rgb), 50%) 0 0 0 1dp; drag: drag; focus: auto; } @@ -64,13 +63,13 @@ color-alpha { color-sv:focus-visible, color-hue:focus-visible, color-alpha:focus-visible { - box-shadow: #C2A42D 0 0 0 2dp; + box-shadow: var(--color-accent) 0 0 0 2dp; } color-sv.adjusting, color-hue.adjusting, color-alpha.adjusting { - box-shadow: #FFFFFF 0 0 0 3dp; + box-shadow: var(--color-white) 0 0 0 3dp; } color-hue { @@ -82,20 +81,21 @@ color-cursor { position: absolute; width: 14dp; height: 14dp; - border-radius: 7dp; - border: 2dp #ffffff; - box-shadow: 0 0 4dp 1dp rgba(0, 0, 0, 70%); + border-radius: var(--radius-control); + border-width: 2dp; + border-color: var(--color-white); + box-shadow: 0 0 4dp 1dp rgba(var(--color-black-rgb), 70%); pointer-events: none; } color-heading { display: block; - margin-top: 2dp; - font-family: "Fira Sans Condensed"; + margin-top: var(--space-2xs); + font-family: var(--font-family-heading); font-weight: bold; - font-size: 13dp; + font-size: var(--font-size-xs); text-transform: uppercase; - color: rgba(224, 219, 200, 55%); + color: rgba(var(--color-text-rgb), 55%); } color-presets { @@ -114,71 +114,102 @@ button.color-swatch-button { width: 25dp; height: 25dp; padding: 0; - border-radius: 7dp; + border-radius: var(--radius-control); } -color-chip { - display: block; - flex: 0 0 20dp; - width: 20dp; - height: 20dp; - border-radius: 5dp; - box-shadow: rgba(255, 255, 255, 45%) 0 0 0 1dp; -} - -button.color-swatch-button color-chip { +button.color-swatch-button color-swatch { width: 25dp; height: 25dp; - border-radius: 7dp; -} - -color-chip.empty, -color-swatch.empty { - background-color: rgba(224, 219, 200, 12%); - decorator: linear-gradient(135deg, rgba(224, 219, 200, 0) 45%, - rgba(194, 164, 45, 70%) 48%, rgba(194, 164, 45, 70%) 52%, - rgba(224, 219, 200, 0) 55%); + border-radius: var(--radius-control); } color-footer { display: flex; align-items: center; gap: 6dp; - padding-top: 2dp; + padding-top: var(--space-2xs); } button { - background-color: rgba(17, 16, 10, 35%); - padding: 4dp 8dp; - border-radius: 8dp; - box-shadow: rgba(146, 135, 91, 30%) 0 0 0 1dp; - color: #E0DBC8; - cursor: pointer; - focus: auto; -} - -button:hover, -button:focus-visible { - background-color: rgba(204, 184, 119, 20%); - box-shadow: #C2A42D 0 0 0 2dp; -} - -button:active { - background-color: rgba(204, 184, 119, 40%); + padding: var(--space-xs) var(--space-sm); + border-radius: var(--radius-panel); + font-size: var(--font-size-sm); + box-shadow: rgba(var(--color-border-rgb), 30%) 0 0 0 1dp; + opacity: 1; } color-value { display: block; flex: 1 1 auto; text-align: right; - font-family: "Noto Mono"; - font-size: 12dp; - color: #FFFFFF; + font-family: var(--font-family-monospace); + font-size: var(--font-size-2xs); + color: var(--color-white); cursor: pointer; focus: auto; } color-value:hover, color-value:focus-visible { - color: #C2A42D; + color: var(--color-accent); +} + +popover.context-menu { + min-width: 200dp; + max-width: 90%; + max-height: 90%; + overflow-y: auto; + padding: 6dp; + gap: 2dp; + transform-origin: left top; +} + +.context-menu button { + display: flex; + align-items: center; + gap: 10dp; + padding: 8dp 12dp; + flex: 0 0 auto; + white-space: nowrap; + --button-background: transparent; + background-color: var(--button-background); + box-shadow: none; + transition: background-color 0.1s linear-in-out; +} + +.context-menu button:selected { + --button-background: var(--button-background-selected); +} + +.context-menu button:selected icon { + color: var(--color-accent); +} + +.context-menu button:not(:disabled):hover, +.context-menu button:not(:disabled):focus-visible, +.context-menu button:not(:disabled):active { + box-shadow: none; +} + +.context-menu button:disabled { + background-color: transparent; + box-shadow: none; + opacity: 0.4; + cursor: unavailable; +} + +.context-menu icon { + display: block; + flex: 0 0 1em; + width: 1em; + height: 1em; + line-height: 1; +} + +menu-separator { + display: block; + flex: 0 0 1dp; + height: 1dp; + margin: 4dp 6dp; + background-color: var(--color-border); } diff --git a/res/rml/popup.rcss b/res/rml/popup.rcss index effc80344d..dd19f776b0 100644 --- a/res/rml/popup.rcss +++ b/res/rml/popup.rcss @@ -1,17 +1,13 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { overflow: visible; width: 100%; height: 100%; margin: 0; padding: 0; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; - font-size: 18dp; - color: #E0DBC8; + font-size: var(--font-size-xl); + color: var(--color-text); } button { @@ -23,11 +19,12 @@ popup { width: 100%; display: flex; align-items: stretch; - height: 64dp; - background-color: rgba(21, 22, 16, 80%); - border-bottom: 2dp #92875B; + height: var(--toolbar-height); + background-color: rgba(var(--color-surface-rgb), 80%); + border-bottom-width: 2dp; + border-bottom-color: var(--color-border); backdrop-filter: blur(5dp); - transform: translateY(-64dp); + transform: translateY(var(--toolbar-hidden-offset)); transition: transform 0.2s cubic-in-out; } @@ -41,5 +38,5 @@ popup tab-bar { popup tab-bar tab { opacity: 0.35; - color: #E0DBC8; + color: var(--color-text); } diff --git a/res/rml/prelaunch.rcss b/res/rml/prelaunch.rcss index 6cc5da65ae..a680d4ce79 100644 --- a/res/rml/prelaunch.rcss +++ b/res/rml/prelaunch.rcss @@ -1,32 +1,40 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { + --color-prelaunch-accent: #FEE685; + --color-prelaunch-muted: #A6A09B; + --color-disc-error: #FFC9C9; + --color-disc-mismatch: #FFD6A7; + --color-ready: #D8F999; + --menu-button-decorator: horizontal-gradient(rgba(var(--color-black-rgb), 0%) rgba(var(--color-black-rgb), 0%)); + --menu-button-decorator-hover: horizontal-gradient(#FEE685FF #FEE68500); + width: 100%; height: 100%; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; - font-size: 20dp; - color: #FFFFFF; + font-size: var(--font-size-2xl); + color: var(--color-white); filter: opacity(0); transition: filter 1s 0.2s linear-in-out; z-index: -1; } -.gradient { +body.mirrored { + --menu-button-decorator-hover: horizontal-gradient(#FEE68500 #FEE685FF); +} + +prelaunch-gradient { position: absolute; width: 100%; height: 100%; /* The color gradient from the Figma bands really badly. A fully black gradient does as well, but not as badly. */ - decorator: horizontal-gradient(#000000FF #00000000); + decorator: horizontal-gradient(rgba(var(--color-black-rgb), 100%) rgba(var(--color-black-rgb), 0%)); } -body.mirrored .gradient { - decorator: horizontal-gradient(#00000000 #000000FF); +body.mirrored prelaunch-gradient { + decorator: horizontal-gradient(rgba(var(--color-black-rgb), 0%) rgba(var(--color-black-rgb), 100%)); } -.background { +prelaunch-background { position: absolute; width: 100%; height: 100%; @@ -39,11 +47,11 @@ body[open] { filter: opacity(1); } -body[open] .background { +body[open] prelaunch-background { opacity: 1; } -body.disc-ready .background { +body.disc-ready prelaunch-background { opacity: 0; } @@ -84,7 +92,7 @@ hero { display: flex; flex-direction: column; align-items: flex-start; - gap: 4dp; + gap: var(--space-xs); } body.mirrored hero { @@ -96,8 +104,8 @@ hero img { } eyebrow { - font-family: "Alegreya SC"; - font-size: 32dp; + font-family: var(--font-family-display); + font-size: var(--font-size-6xl); } @media (min-width: 1216dp) { @@ -107,36 +115,36 @@ eyebrow { } } -eyebrow span { +eyebrow studio-name { font-weight: bold; } -#menu-list { +menu-list { display: flex; flex-direction: column; - gap: 12dp; + gap: var(--space-md); align-items: flex-start; } #menu-list button { width: 428dp; height: 54dp; - padding: 8dp 16dp; - border-radius: 8dp; + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius-panel); text-align: left; text-transform: uppercase; - font-family: "Fira Sans Condensed"; - font-size: 32dp; + font-family: var(--font-family-heading); + font-size: var(--font-size-6xl); font-weight: normal; cursor: pointer; /* Define a fully transparent gradient as the default state, otherwise a white flash occurs */ - decorator: horizontal-gradient(#00000000 #00000000); + decorator: var(--menu-button-decorator); } #menu-list button:disabled { opacity: 0.75; cursor: default; - decorator: horizontal-gradient(#00000000 #00000000); + decorator: var(--menu-button-decorator); } #menu-list button.anim-done { @@ -157,7 +165,7 @@ eyebrow span { width: 100%; height: 100%; overflow: hidden; - border-radius: 8dp; + border-radius: var(--radius-panel); pointer-events: none; z-index: 0; } @@ -169,7 +177,7 @@ eyebrow span { left: 0; width: 100%; height: 100%; - padding: 8dp 16dp; + padding: var(--space-sm) var(--space-lg); opacity: 0; text-overflow: ellipsis; white-space: nowrap; @@ -189,8 +197,8 @@ eyebrow span { height: 54dp; align-items: center; justify-content: center; - color: #FFFFFF; - font-family: "Material Symbols Rounded"; + color: var(--color-white); + font-family: var(--font-family-icons); font-weight: normal; font-size: 30dp; z-index: 1; @@ -213,8 +221,8 @@ eyebrow span { #menu-list button:hover, #menu-list button:focus-visible { - color: black; - decorator: horizontal-gradient(#FEE685FF #FEE68500); + color: var(--color-black); + decorator: var(--menu-button-decorator-hover); } body.mirrored #menu-list { @@ -225,9 +233,12 @@ body.mirrored #menu-list button { text-align: right; } -body.mirrored #menu-list button:hover, -body.mirrored #menu-list button:focus-visible { - decorator: horizontal-gradient(#FEE68500 #FEE685FF); +body.mirrored #menu-list button.mod-update-link { + justify-content: flex-end; +} + +#menu-list update-badge { + font-size: var(--font-size-md); } disc-info { @@ -237,8 +248,8 @@ disc-info { bottom: 72dp; display: flex; flex-direction: column; - gap: 12dp; - font-size: 24dp; + gap: var(--space-md); + font-size: var(--font-size-4xl); font-effect: glow(0dp 4dp 0dp 4dp black); text-align: left; } @@ -256,9 +267,9 @@ version-info { bottom: 72dp; display: flex; flex-direction: column; - gap: 12dp; + gap: var(--space-md); text-align: right; - font-size: 24dp; + font-size: var(--font-size-4xl); font-effect: glow(0dp 4dp 0dp 4dp black); text-align: right; } @@ -272,40 +283,38 @@ body.mirrored version-info { #disc-status { display: flex; align-items: center; - gap: 8dp; + gap: var(--space-sm); } #disc-status[status=good] { - color: #D8F999; + color: var(--color-ready); } #disc-status[status=bad] { - color: #FFC9C9; + color: var(--color-disc-error); } #disc-status[status=verifying] { - color: #FFFFFF; + color: var(--color-white); } #disc-status[status=mismatch] { - color: #FFD6A7; + color: var(--color-disc-mismatch); } #disc-status[status=unknown] { - color: rgba(224, 219, 200, 65%); + color: rgba(var(--color-text-rgb), 65%); } #disc-status[status=pending] { - color: #FEE685; + color: var(--color-prelaunch-accent); } #disc-status icon { display: none; width: 24dp; height: 24dp; - font-family: "Material Symbols Rounded"; - font-weight: normal; - font-size: 24dp; + font-size: var(--font-size-4xl); } #disc-status[status] icon { @@ -337,24 +346,24 @@ body.mirrored version-info { } #disc-version { - font-size: 20dp; + font-size: var(--font-size-2xl); } -.update { +update-status { display: none; - color: #A6A09B; + color: var(--color-prelaunch-muted); align-items: center; justify-content: flex-end; - gap: 8dp; - font-size: 20dp; + gap: var(--space-sm); + font-size: var(--font-size-2xl); } -.update[state=checking], -.update[state=failed] { +update-status[state=checking], +update-status[state=failed] { display: block; } -.update[state=available] { +update-status[state=available] { display: flex; } @@ -364,33 +373,31 @@ body.mirrored version-info { padding: 0dp; border-width: 0dp; background-color: transparent; - color: #D8F999; + color: var(--color-ready); cursor: pointer; text-transform: uppercase; font-weight: bold; - decorator: horizontal-gradient(#00000000 #00000000); + decorator: var(--menu-button-decorator); } -.update[state=available] #update-download { +update-status[state=available] #update-download { display: flex; align-items: center; - gap: 2dp; + gap: var(--space-2xs); } #update-download icon { display: block; width: 18dp; height: 18dp; - font-family: "Material Symbols Rounded"; - font-weight: normal; decorator: text("" center center); } -.detail { - color: #A6A09B; +disc-version { + color: var(--color-prelaunch-muted); } -body.mirrored .update { +body.mirrored update-status { justify-content: flex-start; } @@ -436,12 +443,20 @@ body.animate-in .intro-item { /* Mobile layout */ @media (max-height: 640dp) { - .gradient { - decorator: horizontal-gradient(#00000000 #000000FF); + body { + --menu-button-decorator-hover: horizontal-gradient(#FEE68500 #FEE685FF); } - body.mirrored .gradient { - decorator: horizontal-gradient(#000000FF #00000000); + body.mirrored { + --menu-button-decorator-hover: horizontal-gradient(#FEE685FF #FEE68500); + } + + prelaunch-gradient { + decorator: horizontal-gradient(rgba(var(--color-black-rgb), 0%) rgba(var(--color-black-rgb), 100%)); + } + + body.mirrored prelaunch-gradient { + decorator: horizontal-gradient(rgba(var(--color-black-rgb), 100%) rgba(var(--color-black-rgb), 0%)); } menu { @@ -453,7 +468,7 @@ body.animate-in .intro-item { flex-direction: row; align-items: center; justify-content: space-between; - gap: 16dp; + gap: var(--space-lg); } body.mirrored menu { @@ -466,7 +481,7 @@ body.animate-in .intro-item { flex: 1 1 0; min-width: 0; max-width: 48%; - margin-left: 32dp; + margin-left: var(--space-2xl); } body.mirrored hero { @@ -490,9 +505,8 @@ body.animate-in .intro-item { text-align: right; } - #menu-list button:hover, - #menu-list button:focus-visible { - decorator: horizontal-gradient(#FEE68500 #FEE685FF); + #menu-list button.mod-update-link { + justify-content: flex-end; } body.mirrored #menu-list { @@ -503,9 +517,8 @@ body.animate-in .intro-item { text-align: left; } - body.mirrored #menu-list button:hover, - body.mirrored #menu-list button:focus-visible { - decorator: horizontal-gradient(#FEE685FF #FEE68500); + body.mirrored #menu-list button.mod-update-link { + justify-content: flex-start; } eyebrow { @@ -518,8 +531,8 @@ body.animate-in .intro-item { bottom: 32dp; top: auto; text-align: right; - font-size: 16dp; - gap: 8dp; + font-size: var(--font-size-md); + gap: var(--space-sm); } #disc-status { @@ -527,11 +540,11 @@ body.animate-in .intro-item { } #disc-status icon { - font-size: 20dp; + font-size: var(--font-size-2xl); } #disc-version { - font-size: 16dp; + font-size: var(--font-size-md); } version-info { @@ -540,12 +553,12 @@ body.animate-in .intro-item { bottom: auto; top: 32dp; text-align: right; - font-size: 16dp; - gap: 8dp; + font-size: var(--font-size-md); + gap: var(--space-sm); } - .update { - font-size: 16dp; + update-status { + font-size: var(--font-size-md); } body.mirrored disc-info { diff --git a/res/rml/saves.rcss b/res/rml/saves.rcss new file mode 100644 index 0000000000..ed1d45a405 --- /dev/null +++ b/res/rml/saves.rcss @@ -0,0 +1,86 @@ +window.saves pane.list { + flex: 0 0 320dp; +} + +window.saves pane.list > header { + display: flex; + align-items: center; + gap: var(--space-sm); + focus: none; +} + +window.saves pane.list > header section-heading { + flex: 1 1 0; + min-width: 0; +} + +window.saves select-button key icon { + margin-right: var(--space-sm); +} + +window.saves pane.detail { + gap: var(--space-md); +} + +save-header { + display: block; +} + +save-header heading { + font-family: var(--font-family-heading); + font-size: var(--font-size-6xl); + line-height: 1.2; +} + +save-overview { + display: block; + padding: 14dp 16dp; + border-radius: var(--radius-panel); + background-color: rgba(var(--color-control-rgb), 30%); + box-shadow: rgba(var(--color-border-rgb), 30%) 0 0 0 1dp; +} + +save-overview.present { + box-shadow: rgba(var(--color-success-rgb), 40%) 0 0 0 1dp; +} + +save-overview.present heading { + color: var(--color-success); +} + +save-overview small { + display: block; + margin-top: var(--space-2xs); +} + +window.saves button:not(.icon-button) { + text-align: left; +} + +@media (max-width: 720dp) { + window.saves pane.list { + flex: 0 0 42%; + } +} + +@media (max-height: 640dp) { + window.saves pane.list { + flex: 1 1 0; + } +} + +save-mod { + display: flex; + align-items: center; + gap: var(--space-md); + padding: var(--space-sm) 0; +} + +save-mod details { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-width: 0; + gap: var(--space-xs); + word-break: break-all; +} diff --git a/res/rml/tabbing.rcss b/res/rml/tabbing.rcss index 8f42dd84d5..99f87bd8c3 100644 --- a/res/rml/tabbing.rcss +++ b/res/rml/tabbing.rcss @@ -18,17 +18,18 @@ tab-bar scrollbarhorizontal sliderbar { tab-bar tab { flex: 0 0 auto; - padding: 0 24dp; - line-height: 64dp; + padding: 0 var(--space-xl); + line-height: var(--toolbar-height); white-space: nowrap; - decorator: vertical-gradient(#c2a42d00 #c2a42d00); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 0%) rgba(var(--color-accent-rgb), 0%)); transition: decorator 0.1s linear-in-out, opacity 0.1s linear-in-out; cursor: pointer; } tab-bar tab:selected { opacity: 1; - border-bottom: 4dp #C2A42D; + border-bottom-width: 4dp; + border-bottom-color: var(--color-accent); font-effect: glow(0dp 4dp 0dp 4dp black); } @@ -36,17 +37,21 @@ tab-bar tab:focus-visible, tab-bar tab:hover { opacity: 1; font-effect: glow(0dp 4dp 0dp 4dp black); - decorator: vertical-gradient(#c2a42d00 #c2a42d26); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 0) rgba(var(--color-accent-rgb), 38)); } tab-bar tab:active { - decorator: vertical-gradient(#c2a42d10 #c2a42d40); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 16) rgba(var(--color-accent-rgb), 64)); +} + +tab-bar update-badge { + font-effect: none; } tab-bar[closable] tab-end-spacer { display: block; - flex: 0 0 64dp; - width: 64dp; + flex: 0 0 var(--toolbar-height); + width: var(--toolbar-height); pointer-events: none; } @@ -56,13 +61,13 @@ window > close { position: fixed; top: 8dp; right: 8dp; - z-index: 1; + z-index: 2; width: 48dp; height: 48dp; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; - font-size: 24dp; - color: rgba(224, 219, 200, 70%); + font-size: var(--font-size-4xl); + color: rgba(var(--color-text-rgb), 70%); backdrop-filter: blur(2dp); border-radius: 6dp; decorator: text("" center center); @@ -74,8 +79,8 @@ tab-bar[closable] close:hover, tab-bar[closable] close:focus-visible, window > close:hover, window > close:focus-visible { - color: #fff; - background-color: rgba(194, 164, 45, 24%); + color: var(--color-white); + background-color: rgba(var(--color-accent-rgb), 24%); } window > close { @@ -85,6 +90,6 @@ window > close { tab-bar[closable] close:active, window > close:active { - color: #fff; - background-color: rgba(194, 164, 45, 40%); + color: var(--color-white); + background-color: rgba(var(--color-accent-rgb), 40%); } diff --git a/res/rml/theme.rcss b/res/rml/theme.rcss new file mode 100644 index 0000000000..18adb8d294 --- /dev/null +++ b/res/rml/theme.rcss @@ -0,0 +1,74 @@ +body { + --font-family-body: Fira Sans; + --font-family-heading: Fira Sans Condensed; + --font-family-monospace: Noto Mono; + --font-family-icons: Material Symbols Rounded; + --font-family-display: Alegreya SC; + + --font-size-3xs: 11dp; + --font-size-2xs: 12dp; + --font-size-xs: 13dp; + --font-size-sm: 14dp; + --font-size-base: 15dp; + --font-size-md: 16dp; + --font-size-lg: 17dp; + --font-size-xl: 18dp; + --font-size-2xl: 20dp; + --font-size-3xl: 22dp; + --font-size-4xl: 24dp; + --font-size-5xl: 28dp; + --font-size-6xl: 32dp; + + --space-2xs: 2dp; + --space-xs: 4dp; + --space-sm: 8dp; + --space-md: 12dp; + --space-lg: 16dp; + --space-xl: 24dp; + --space-2xl: 32dp; + + --radius-small: 4dp; + --radius-control: 7dp; + --radius-panel: 8dp; + --radius-window: 14dp; + + --toolbar-height: 64dp; + --toolbar-hidden-offset: -64dp; + + --color-text-rgb: 224, 219, 200; + --color-text: rgb(var(--color-text-rgb)); + --color-accent-rgb: 194, 164, 45; + --color-accent: rgb(var(--color-accent-rgb)); + --color-border-rgb: 146, 135, 91; + --color-border: rgb(var(--color-border-rgb)); + --color-surface-rgb: 21, 22, 16; + --color-interactive-rgb: 204, 184, 119; + --color-control-rgb: 17, 16, 10; + --color-neutral-rgb: 217, 217, 217; + --color-neutral: rgb(var(--color-neutral-rgb)); + --color-white-rgb: 255, 255, 255; + --color-white: rgb(var(--color-white-rgb)); + --color-black-rgb: 0, 0, 0; + --color-black: rgb(var(--color-black-rgb)); + + --color-success-rgb: 68, 204, 85; + --color-success: rgb(var(--color-success-rgb)); + --color-info-rgb: 111, 183, 239; + --color-info: rgb(var(--color-info-rgb)); + --color-warning-rgb: 255, 168, 38; + --color-warning: rgb(var(--color-warning-rgb)); + --color-error-rgb: 204, 68, 68; + --color-error: rgb(var(--color-error-rgb)); + + --color-danger-border: #852221; + --color-danger-heading: #B3261E; + + --button-color: var(--color-text); + --button-background: rgba(var(--color-control-rgb), 20%); + --button-background-hover: rgba(var(--color-interactive-rgb), 20%); + --button-background-selected: rgba(var(--color-interactive-rgb), 40%); + --button-background-active: rgba(var(--color-interactive-rgb), 40%); + + --toast-border-color: var(--color-border); + --toast-heading-color: var(--color-border); +} diff --git a/res/rml/touch_controls.rcss b/res/rml/touch_controls.rcss index 4c8057d6c0..85320315bf 100644 --- a/res/rml/touch_controls.rcss +++ b/res/rml/touch_controls.rcss @@ -1,16 +1,28 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { + --color-oil-border: rgba(42, 32, 18, 82%); + --color-oil-background: rgba(18, 14, 10, 70%); + --color-oil-fill: rgb(255, 232, 74); + --color-button-a: rgba(34, 112, 123, 62%); + --color-button-b: rgba(161, 61, 66, 58%); + --color-button-x: rgba(83, 115, 151, 56%); + --color-button-y: rgba(113, 91, 150, 54%); + --color-stick-background: rgba(18, 20, 24, 35%); + --color-stick-knob: rgba(238, 236, 226, 55%); + + --button-color: rgba(248, 244, 232, 90%); + --button-background: rgba(22, 24, 28, 48%); + --button-background-active: rgba(63, 78, 90, 68%); + --button-border-color: rgba(var(--color-white-rgb), 22%); + --button-border-color-active: rgba(var(--color-white-rgb), 48%); + width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; - color: rgba(248, 244, 232, 90%); + color: var(--button-color); z-index: 1; filter: opacity(0); transition: filter 0.2s linear-in-out; @@ -30,12 +42,11 @@ button { justify-content: center; decorator: none; padding: 0; - border: 1dp rgba(255, 255, 255, 22%); - background-color: rgba(22, 24, 28, 48%); - color: rgba(248, 244, 232, 90%); + border-width: 1dp; + border-color: var(--button-border-color); + background-color: var(--button-background); + color: var(--button-color); text-align: center; - /* backdrop-filter: blur(7dp); */ - /* box-shadow: 0 6dp 18dp rgba(0, 0, 0, 28%); */ transform-origin: center; transition: background-color border-color filter transform 0.08s linear-in-out, opacity 0.2s linear-in-out; @@ -43,8 +54,8 @@ button { button.pressed, button.active { - background-color: rgba(63, 78, 90, 68%); - border-color: rgba(255, 255, 255, 48%); + background-color: var(--button-background-active); + border-color: var(--button-border-color-active); filter: brightness(1.18); } @@ -68,9 +79,9 @@ button icon { button icon glyph { display: block; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; - font-size: 24dp; + font-size: var(--font-size-4xl); line-height: 1; } @@ -92,9 +103,9 @@ button icon glyph { position: absolute; } -.trigger-l.active { - background-color: rgba(57, 116, 133, 74%); - border-color: rgba(128, 222, 234, 72%); +.trigger-l { + --button-background-active: rgba(57, 116, 133, 74%); + --button-border-color-active: rgba(128, 222, 234, 72%); } .trigger, @@ -103,12 +114,14 @@ button icon glyph { } .trigger { - font-size: 22dp; + font-size: var(--font-size-3xl); } .button-z { - background-color: rgba(118, 79, 158, 58%); - border-color: rgba(203, 170, 255, 36%); + --button-background: rgba(118, 79, 158, 58%); + --button-background-active: rgba(139, 91, 187, 82%); + --button-border-color: rgba(203, 170, 255, 36%); + --button-border-color-active: rgba(220, 194, 255, 70%); } .midna-icon { @@ -121,7 +134,7 @@ button icon glyph { .button-z.has-icon span, .face.has-item span { position: absolute; - font-size: 13dp; + font-size: var(--font-size-xs); line-height: 1; } @@ -130,20 +143,14 @@ button icon glyph { bottom: 7dp; } -.button-z.pressed { - background-color: rgba(139, 91, 187, 82%); - border-color: rgba(220, 194, 255, 70%); -} - action-bar { position: absolute; display: flex; align-items: center; - border: 1dp rgba(255, 255, 255, 22%); + border-width: 1dp; + border-color: var(--button-border-color); border-radius: 23dp; - background-color: rgba(22, 24, 28, 48%); - /* backdrop-filter: blur(7dp); */ - /* box-shadow: 0 -6dp 18dp rgba(0, 0, 0, 28%); */ + background-color: var(--button-background); overflow: hidden; opacity: 1; transform-origin: center; @@ -175,7 +182,7 @@ action-bar:hidden separator { } .utility.pressed { - background-color: rgba(63, 78, 90, 68%); + background-color: var(--button-background-active); } .utility.pressed, @@ -185,7 +192,7 @@ action-bar:hidden separator { .skip { z-index: 1; - border-color: rgba(255, 255, 255, 36%); + border-color: rgba(var(--color-white-rgb), 36%); } separator { @@ -193,7 +200,7 @@ separator { flex: 0 0 1dp; width: 1dp; height: 24dp; - background-color: rgba(255, 255, 255, 18%); + background-color: rgba(var(--color-white-rgb), 18%); opacity: 1; transition: opacity 0.2s linear-in-out; } @@ -201,7 +208,7 @@ separator { .face { position: absolute; border-radius: 29dp; - font-size: 24dp; + font-size: var(--font-size-4xl); overflow: visible; } @@ -219,10 +226,10 @@ separator { min-width: 17dp; height: 15dp; padding: 1dp 3dp; - border-radius: 7dp; - background-color: rgba(0, 0, 0, 52%); - color: rgba(255, 255, 255, 92%); - font-size: 12dp; + border-radius: var(--radius-control); + background-color: rgba(var(--color-black-rgb), 52%); + color: rgba(var(--color-white-rgb), 92%); + font-size: var(--font-size-2xs); line-height: 13dp; text-align: center; } @@ -233,11 +240,10 @@ separator { bottom: -5dp; width: 34dp; height: 8dp; - padding: 2dp; - border: 1dp rgba(42, 32, 18, 82%); - border-radius: 4dp; - background-color: rgba(18, 14, 10, 70%); - /* box-shadow: 0 2dp 6dp rgba(0, 0, 0, 35%); */ + padding: var(--space-2xs); + border: 1dp var(--color-oil-border); + border-radius: var(--radius-small); + background-color: var(--color-oil-background); } oil-fill { @@ -245,31 +251,31 @@ oil-fill { width: 0%; height: 100%; border-radius: 2dp; - background-color: rgb(255, 232, 74); + background-color: var(--color-oil-fill); } .face.has-item span { right: 6dp; bottom: 6dp; - color: rgba(255, 255, 255, 88%); + color: rgba(var(--color-white-rgb), 88%); } .face.a { border-radius: 37dp; font-size: 31dp; - background-color: rgba(34, 112, 123, 62%); + background-color: var(--color-button-a); } .face.b { - background-color: rgba(161, 61, 66, 58%); + background-color: var(--color-button-b); } .face.x { - background-color: rgba(83, 115, 151, 56%); + background-color: var(--color-button-x); } .face.y { - background-color: rgba(113, 91, 150, 54%); + background-color: var(--color-button-y); } button.control.docked-top, @@ -306,10 +312,9 @@ touch-stick { width: 124dp; height: 124dp; border-radius: 62dp; - background-color: rgba(18, 20, 24, 35%); - border: 1dp rgba(255, 255, 255, 20%); - /* backdrop-filter: blur(7dp); */ - /* box-shadow: 0 8dp 24dp rgba(0, 0, 0, 24%); */ + background-color: var(--color-stick-background); + border-width: 1dp; + border-color: rgba(var(--color-white-rgb), 20%); opacity: 0; pointer-events: none; transition: opacity 0.18s linear-in-out; @@ -326,7 +331,8 @@ stick-ring { width: 88dp; height: 88dp; border-radius: 44dp; - border: 1dp rgba(255, 255, 255, 18%); + border-width: 1dp; + border-color: rgba(var(--color-white-rgb), 18%); } stick-knob { @@ -334,6 +340,7 @@ stick-knob { width: 48dp; height: 48dp; border-radius: 24dp; - background-color: rgba(238, 236, 226, 55%); - border: 1dp rgba(255, 255, 255, 45%); + background-color: var(--color-stick-knob); + border-width: 1dp; + border-color: rgba(var(--color-white-rgb), 45%); } diff --git a/res/rml/touch_controls_editor.rcss b/res/rml/touch_controls_editor.rcss index 2ca99d935a..dbf0ae74e4 100644 --- a/res/rml/touch_controls_editor.rcss +++ b/res/rml/touch_controls_editor.rcss @@ -1,5 +1,10 @@ body.touch-editor { - background-color: rgba(4, 6, 8, 34%); + --color-editor-backdrop: rgba(4, 6, 8, 34%); + --color-editor-handle: rgba(34, 37, 42, 86%); + --color-editor-accent-rgb: 255, 232, 128; + --color-editor-highlight-rgb: 255, 244, 190; + + background-color: var(--color-editor-backdrop); z-index: 8; } @@ -14,7 +19,7 @@ body.touch-editor .control:hover, body.touch-editor action-bar:hover, body.touch-editor .control.editor-selected, body.touch-editor action-bar.editor-selected { - border-color: rgba(255, 232, 128, 80%); + border-color: rgba(var(--color-editor-accent-rgb), 80%); filter: brightness(1.15); } @@ -27,8 +32,9 @@ selection-frame { display: none; position: absolute; z-index: 20; - border: 2dp rgba(255, 232, 128, 88%); - background-color: rgba(255, 232, 128, 7%); + border-width: 2dp; + border-color: rgba(var(--color-editor-accent-rgb), 88%); + background-color: rgba(var(--color-editor-accent-rgb), 7%); pointer-events: none; } @@ -41,9 +47,10 @@ resize-handle { position: absolute; width: 22dp; height: 22dp; - border: 2dp rgba(255, 244, 190, 96%); + border-width: 2dp; + border-color: rgba(var(--color-editor-highlight-rgb), 96%); border-radius: 11dp; - background-color: rgba(34, 37, 42, 86%); + background-color: var(--color-editor-handle); pointer-events: auto; } @@ -90,6 +97,12 @@ resize-handle.corner.bottom { } editor-toolbar { + --button-background: rgba(17, 19, 24, 88%); + --button-background-hover: rgba(78, 85, 96, 92%); + --button-border-color: rgba(var(--color-white-rgb), 26%); + --button-border-color-hover: rgba(var(--color-editor-highlight-rgb), 92%); + --button-color: rgba(255, 250, 232, 94%); + display: flex; position: absolute; left: 24dp; @@ -98,7 +111,7 @@ editor-toolbar { z-index: 30; height: 48dp; margin-top: -24dp; - gap: 8dp; + gap: var(--space-sm); justify-content: center; pointer-events: auto; } @@ -108,12 +121,13 @@ editor-toolbar button.editor-command { min-width: 96dp; height: 48dp; padding: 0 14dp; - border-radius: 8dp; - border: 1dp rgba(255, 255, 255, 26%); - background-color: rgba(17, 19, 24, 88%); - color: rgba(255, 250, 232, 94%); - font-family: "Fira Sans"; - font-size: 18dp; + border-radius: var(--radius-panel); + border-width: 1dp; + border-color: var(--button-border-color); + background-color: var(--button-background); + color: var(--button-color); + font-family: var(--font-family-body); + font-size: var(--font-size-xl); line-height: 48dp; opacity: 1; cursor: pointer; @@ -127,12 +141,12 @@ editor-toolbar button.editor-command span { } editor-toolbar button.editor-command.primary { - border-color: rgba(255, 232, 128, 70%); - background-color: rgba(96, 82, 38, 90%); + --button-border-color: rgba(var(--color-editor-accent-rgb), 70%); + --button-background: rgba(96, 82, 38, 90%); } editor-toolbar button.editor-command:hover, editor-toolbar button.editor-command:focus-visible { - border-color: rgba(255, 244, 190, 92%); - background-color: rgba(78, 85, 96, 92%); + border-color: var(--button-border-color-hover); + background-color: var(--button-background-hover); } diff --git a/res/rml/tuner.rcss b/res/rml/tuner.rcss index 86dd2043f6..5e0d96e855 100644 --- a/res/rml/tuner.rcss +++ b/res/rml/tuner.rcss @@ -1,90 +1,87 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { overflow: visible; width: 100%; height: 100%; margin: 0; padding: 0; - font-family: "Fira Sans Condensed"; - font-size: 24dp; - color: #FFFFFF; + font-family: var(--font-family-heading); + font-size: var(--font-size-4xl); + color: var(--color-white); display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch; } -.tuner-root { +tuner-root { width: 100%; min-height: 45%; display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch; - decorator: vertical-gradient(#00000000 #151610F2); + decorator: vertical-gradient(rgba(var(--color-black-rgb), 0%) rgba(var(--color-surface-rgb), 242)); filter: opacity(0); transition: filter 0.2s linear-in-out; } -.tuner-root[open] { +tuner-root[open] { filter: opacity(1); } -.tuner { +graphics-tuner { width: 100%; max-width: 1216dp; margin-left: auto; margin-right: auto; display: flex; flex-direction: column; - gap: 24dp; + gap: var(--space-xl); padding: 48dp 64dp; } @media (max-height: 800dp) { - .tuner-root { + tuner-root { min-height: 38%; } - .tuner { - gap: 16dp; - padding: 32dp 48dp; + graphics-tuner { + gap: var(--space-lg); + padding: var(--space-2xl) 48dp; } } -.header { +tuner-header { display: flex; justify-content: space-between; align-items: center; - gap: 24dp; + gap: var(--space-xl); } -.carousel-container { +carousel-container { flex: 1 1 auto; display: flex; justify-content: flex-end; min-width: 0; } -.description { - font-size: 18dp; +tuner-description { + font-size: var(--font-size-xl); line-height: 22dp; - color: rgba(255, 255, 255, 50%); + color: rgba(var(--color-white-rgb), 50%); } -.divider { +tuner-divider { margin: 1dp 0; - border-top: 1dp rgba(217, 217, 217, 50%); + border-top-width: 1dp; + border-top-color: rgba(var(--color-neutral-rgb), 50%); } -.footer { +tuner-footer { display: flex; justify-content: space-between; align-items: center; - gap: 24dp; + gap: var(--space-xl); } footer-button { @@ -94,12 +91,12 @@ footer-button { border: 0; padding: 0; background-color: transparent; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; - font-size: 20dp; + font-size: var(--font-size-2xl); line-height: 24dp; text-transform: uppercase; - color: #FFFFFF; + color: var(--color-white); opacity: 1; cursor: pointer; } @@ -112,20 +109,20 @@ footer-button.reset { text-align: right; } -.stepped-carousel { +stepped-carousel { display: flex; align-items: center; justify-content: center; - gap: 16dp; + gap: var(--space-lg); width: auto; min-width: 246dp; padding: 0; background-color: transparent; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; } -.stepped-carousel-value { +stepped-carousel-value { line-height: 29dp; min-width: 166dp; text-align: center; @@ -142,6 +139,6 @@ footer-button.reset { background-color: transparent; opacity: 1; cursor: pointer; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; } diff --git a/res/rml/window.rcss b/res/rml/window.rcss index 98b63dfcd9..f2340b624f 100644 --- a/res/rml/window.rcss +++ b/res/rml/window.rcss @@ -1,21 +1,13 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { display: flex; width: 100%; height: 100%; padding: 64dp; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; font-style: normal; - font-size: 15dp; - color: #E0DBC8; -} - -b { - font-weight: bold; + font-size: var(--font-size-base); + color: var(--color-text); } window { @@ -27,12 +19,13 @@ window { max-width: 1088dp; max-height: 768dp; margin: auto; - border-radius: 14dp; + border-radius: var(--radius-window); overflow: hidden; - border: 2dp #92875B; + border-width: 2dp; + border-color: var(--color-border); backdrop-filter: blur(5dp); box-shadow: 0 0 25dp 5dp; - background-color: rgba(21, 22, 16, 90%); + background-color: rgba(var(--color-surface-rgb), 90%); filter: opacity(0); transform: scale(0.9); transform-origin: center; @@ -61,7 +54,7 @@ window[open] { @media (max-height: 640dp) { body { - padding: 16dp; + padding: var(--space-lg); } window { box-shadow: none; @@ -70,7 +63,7 @@ window[open] { @media (max-width: 768dp) { body { - padding: 16dp; + padding: var(--space-lg); } window.modal { width: 100%; @@ -79,13 +72,14 @@ window[open] { } window tab-bar { - flex: 0 0 64dp; - height: 64dp; - background-color: rgba(217, 217, 217, 10%); - font-family: "Fira Sans Condensed"; + flex: 0 0 var(--toolbar-height); + height: var(--toolbar-height); + background-color: rgba(var(--color-neutral-rgb), 10%); + font-family: var(--font-family-heading); font-weight: bold; - font-size: 18dp; - border-bottom: 2dp #92875B; + font-size: var(--font-size-xl); + border-bottom-width: 2dp; + border-bottom-color: var(--color-border); } window tab-bar tab { @@ -106,14 +100,15 @@ window content pane { flex: 1 1 0; min-width: 0; min-height: 0; - padding: 24dp; - gap: 8dp; + padding: var(--space-xl); + gap: var(--space-sm); overflow: hidden auto; - font-size: 20dp; + font-size: var(--font-size-2xl); } window content pane:not(:last-of-type) { - border-right: 1dp #92875B; + border-right-width: 1dp; + border-right-color: var(--color-border); } window content pane > * { @@ -129,7 +124,7 @@ ui-list { } window content pane > ui-list, -.modal-content pane > ui-list { +modal-content pane > ui-list { flex: 1 1 0; min-width: 0; min-height: 0; @@ -146,7 +141,7 @@ ui-list-viewport { ui-list-content { display: flex; flex-flow: column; - gap: 8dp; + gap: var(--space-sm); min-width: 0; } @@ -156,7 +151,7 @@ ui-list-content > button.ui-list-row { ui-list-empty { display: block; - padding: 16dp; + padding: var(--space-lg); text-align: center; opacity: 0.45; } @@ -176,58 +171,24 @@ window content pane > ui-list:last-child { window content pane > ui-list ui-list-content, window content pane > ui-list ui-list-empty { - padding-left: 24dp; - padding-right: 24dp; + padding-left: var(--space-xl); + padding-right: var(--space-xl); } window content pane > ui-list:first-child ui-list-content, window content pane > ui-list:first-child ui-list-empty { - padding-top: 24dp; + padding-top: var(--space-xl); } window content pane > ui-list:last-child ui-list-content, window content pane > ui-list:last-child ui-list-empty { - padding-bottom: 24dp; + padding-bottom: var(--space-xl); } window content pane:last-of-type > div { line-height: 1.625; } -.data-folder-current { - display: block; - font-size: 16dp; - color: rgba(224, 219, 200, 65%); -} - -scrollbarvertical { - width: 8dp; - margin: 4dp 4dp 4dp 0; -} - -scrollbarvertical sliderarrowdec, -scrollbarvertical sliderarrowinc { - width: 0; - height: 0; -} - -scrollbarvertical slidertrack { - width: 8dp; -} - -scrollbarvertical sliderbar { - width: 8dp; - min-height: 24dp; - background-color: rgba(224, 219, 200, 45%); - border-radius: 2dp; - transition: background-color 0.2s cubic-in-out; -} - -scrollbarvertical sliderbar:hover, -scrollbarvertical sliderbar:active { - background-color: rgba(194, 164, 45, 80%); -} - scrollbarhorizontal { height: 0; } @@ -244,51 +205,16 @@ scrollbarhorizontal sliderbar { height: 0; } -.section-heading { - font-family: "Fira Sans Condensed"; +section-heading { + font-family: var(--font-family-heading); font-weight: bold; text-transform: uppercase; - font-size: 22dp; + font-size: var(--font-size-3xl); opacity: 0.25; } -.section-heading:not(:first-of-type) { - padding-top: 12dp; -} - -button { - text-align: center; - background-color: rgba(17, 16, 10, 20%); - opacity: 0.9; - padding: 8dp 16dp; - border-radius: 14dp; - box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp; - font-size: 20dp; - transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out; - cursor: pointer; - focus: auto; -} - -button:not(:disabled):hover, -button:not(:disabled):focus-visible { - background-color: rgba(204, 184, 119, 20%); - box-shadow: #C2A42D 0 0 0 2dp; -} - -button:not(:disabled):selected { - opacity: 1; - background-color: rgba(204, 184, 119, 40%); -} - -button:not(:disabled):active { - opacity: 1; - background-color: rgba(204, 184, 119, 40%); - box-shadow: #C2A42D 0 0 0 2dp; -} - -button:disabled { - opacity: 0.35; - cursor: default; +section-heading:not(:first-of-type) { + padding-top: var(--space-md); } button.modal-btn { @@ -296,228 +222,72 @@ button.modal-btn { text-align: center; } -select-button { - display: flex; - align-items: center; - gap: 8dp; - background-color: rgba(17, 16, 10, 20%); - opacity: 0.9; - padding: 8dp 16dp; - border-radius: 14dp; - box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp; - transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out; - cursor: pointer; - focus: auto; -} - -select-button:not(:disabled):hover, -select-button:not(:disabled):focus-visible { - background-color: rgba(204, 184, 119, 20%); - box-shadow: #C2A42D 0 0 0 2dp; -} - -select-button:not(:disabled):selected { - opacity: 1; - background-color: rgba(204, 184, 119, 40%); -} - -select-button:not(:disabled):active { - opacity: 1; - background-color: rgba(204, 184, 119, 40%); - box-shadow: #C2A42D 0 0 0 2dp; -} - -select-button:disabled { - opacity: 0.35; - cursor: default; -} - -select-button key { - font-family: "Fira Sans Condensed"; - font-weight: bold; - font-size: 18dp; - text-transform: uppercase; - flex: 0 1 auto; -} - -select-button value { - flex: 1 1 auto; - text-align: right; - font-size: 20dp; -} - -select-button value.modified { - font-weight: bold; -} - -select-button input { - text-align: right; - font-size: 20dp; -} - -select-button.group-button icon { - flex: 0 0 24dp; - margin-left: auto; - width: 24dp; - height: 24dp; - font-size: 24dp; - color: inherit; - decorator: text("" center center); -} - -select-button.group-button value { - display: none; -} - -select-button.color-input value { - min-width: 0; - font-family: "Noto Mono"; - font-size: 15dp; -} - -select-button.color-input color-swatch { - display: block; - flex: 0 0 48dp; - width: 48dp; - height: 24dp; - border-radius: 7dp; - box-shadow: rgba(255, 255, 255, 45%) 0 0 0 1dp; -} - -select-button.color-input color-swatch.empty { - background-color: rgba(224, 219, 200, 12%); - decorator: linear-gradient(135deg, rgba(224, 219, 200, 0) 45%, rgba(194, 164, 45, 70%) 48%, rgba(194, 164, 45, 70%) 52%, rgba(224, 219, 200, 0) 55%); -} - -icon { - width: 1em; - height: 1em; - font-family: "Material Symbols Rounded"; - font-weight: normal; - display: inline-block; - vertical-align: middle; -} - -icon.warning { - decorator: text("" center center); -} - -icon.error { - decorator: text("" center center); -} - -icon.verifying { - decorator: text("" center center); -} - -icon.celebration { - decorator: text("" center center); -} - -icon.question-mark { - decorator: text("" center center); -} - -.achievement-total { +achievement-total { position: absolute; top: 0; - right: 64dp; - height: 64dp; - line-height: 64dp; - font-family: "Fira Sans Condensed"; + right: var(--toolbar-height); + height: var(--toolbar-height); + line-height: var(--toolbar-height); + font-family: var(--font-family-heading); font-weight: bold; - font-size: 16dp; - color: rgba(224, 219, 200, 55%); + font-size: var(--font-size-md); + color: rgba(var(--color-text-rgb), 55%); pointer-events: none; } -.achievement-row { +achievement-row { display: flex; align-items: flex-start; gap: 10dp; - padding: 12dp 0; - border-bottom: 1dp rgba(146, 135, 91, 30%); + padding: var(--space-md) 0; + border-bottom-width: 1dp; + border-bottom-color: rgba(var(--color-border-rgb), 30%); } -.achievement-info { +achievement-info { display: block; flex: 1 1 0; min-width: 0; } -.achievement-header { +achievement-header { display: flex; align-items: center; } -.achievement-name { +achievement-name { flex: 1; font-weight: bold; } -.achievement-name.unlocked { - color: #ffa826; -} - -.achievement-badge { - font-size: 14dp; - opacity: 0.7; -} - -.achievement-badge.unlocked { - color: #44cc55; - opacity: 1; -} - -.achievement-badge.locked { - color: #cc4444; - opacity: 1; +achievement-name.unlocked { + color: var(--color-warning); } .achievement-desc { display: block; - color: rgba(224, 219, 200, 55%); - font-size: 16dp; - margin: 4dp 0 0 0; + color: rgba(var(--color-text-rgb), 55%); + font-size: var(--font-size-md); + margin: var(--space-xs) 0 0 0; } -.achievement-progress { +window progress { + margin: 6dp 0 var(--space-2xs) 0; +} + +achievement-progress { display: block; - font-size: 13dp; - color: rgba(224, 219, 200, 45%); -} - -progress { - display: block; - width: 100%; - height: 6dp; - border-radius: 3dp; - background-color: rgba(255, 255, 255, 10%); - margin: 6dp 0 2dp 0; -} - -progress fill { - background-color: rgba(194, 164, 45, 80%); - border-radius: 3dp; -} - -progress.progress-done fill { - background-color: #44aa22; -} - -progress.progress-ongoing fill { - background-color: #2255bb; + font-size: var(--font-size-xs); + color: rgba(var(--color-text-rgb), 45%); } button.achievement-clear { flex: 0 0 auto; align-self: center; - font-size: 14dp; - padding: 2dp 8dp; opacity: 0.45; } -.preset-grid { +preset-grid { display: flex; flex-direction: row; gap: 20dp; @@ -526,24 +296,24 @@ button.achievement-clear { width: 100%; } -.preset-col { +preset-option { display: flex; flex-flow: column; - gap: 12dp; + gap: var(--space-md); flex: 1 1 0; } -.preset-desc { +preset-description { display: block; - font-size: 16dp; + font-size: var(--font-size-md); text-align: center; } -.modal-dialog { +modal-dialog { display: flex; flex-direction: column; align-items: flex-start; - padding: 24dp; + padding: var(--space-xl); gap: 20dp; flex: 0 1 auto; min-height: 0; @@ -553,55 +323,55 @@ button.achievement-clear { } window.modal.danger { - border: 2dp #852221; + border: 2dp var(--color-danger-border); } -.modal-header { +modal-header { display: flex; flex-direction: row; align-items: center; justify-content: space-between; width: 100%; flex: 0 0 auto; - gap: 16dp; + gap: var(--space-lg); } -.modal-header icon { - font-size: 24dp; - color: #92875B; +modal-header icon { + font-size: var(--font-size-4xl); + color: var(--color-border); } -.modal-title { +modal-title { display: block; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; text-transform: uppercase; - font-size: 18dp; - color: #92875B; + font-size: var(--font-size-xl); + color: var(--color-border); flex: 1 1 auto; } -window.modal.danger .modal-title, -window.modal.danger .modal-header icon { - color: #B3261E; +window.modal.danger modal-title, +window.modal.danger modal-header icon { + color: var(--color-danger-heading); } -.modal-body { +modal-body { display: block; width: 100%; flex: 0 0 auto; min-width: 0; - font-size: 20dp; - color: #FFFFFF; + font-size: var(--font-size-2xl); + color: var(--color-white); font-weight: normal; } -.modal-body span.tip { - font-size: 14dp; - color: #92875B; +modal-body modal-tip { + font-size: var(--font-size-sm); + color: var(--color-border); } -.modal-content { +modal-content { display: none; width: 100%; flex: 1 1 auto; @@ -609,87 +379,261 @@ window.modal.danger .modal-header icon { overflow: hidden; } -.modal-content.active { +modal-content.active { display: flex; flex-direction: column; } -.modal-content pane { +modal-content pane { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; width: 100%; - gap: 8dp; + gap: var(--space-sm); overflow: hidden auto; } -.modal-content pane > * { +modal-content pane > * { flex: 0 0 auto; } -.verification-progress { +window.modal.drop-install { + max-height: 720dp; +} + +window.modal.drop-install package-row { + padding-right: 0; +} + +package-row { + display: flex; + flex-direction: row; + align-items: flex-start; + position: relative; + width: 100%; + gap: 10dp; + padding: var(--space-md) 0; + border-bottom-width: 1dp; + border-bottom-color: rgba(var(--color-border-rgb), 30%); +} + +package-row:last-child { + border-bottom-width: 0; +} + +package-row > mod-icon { + display: none; + flex: 0 0 36dp; + width: 36dp; + height: 36dp; + margin-top: var(--space-2xs); + background-color: rgba(var(--color-control-rgb), 45%); + font-size: var(--font-size-4xl); +} + +package-row > mod-icon.visible { + display: block; +} + +package-row > mod-icon.has-image { + background-color: transparent; +} + +package-row.paused > mod-icon, +package-row.retrying > mod-icon, +package-row.failed > mod-icon { + filter: grayscale(1); + opacity: 0.55; +} + +package-row > section { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-width: 0; + margin: 0; + padding: 0; +} + +package-row > section > header { + display: flex; + width: 100%; + gap: var(--space-sm); + align-items: center; + margin: 0; + padding: 0 0 6dp 0; +} + +package-row h3 { + display: flex; + align-items: baseline; + flex: 1 1 auto; + min-width: 0; + gap: var(--space-xs); + margin: 0; + padding: 0; + overflow: hidden; +} + +package-row h3 > span { + display: block; + flex: 0 1 auto; + min-width: 0; + font-weight: bold; + color: var(--color-white); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +package-row h3 > small { + display: block; + flex: 0 0 auto; + font-size: var(--font-size-xs); + font-weight: normal; + color: rgba(var(--color-text-rgb), 50%); +} + +package-row header > small { + display: block; + flex: 0 0 auto; + font-size: var(--font-size-sm); + color: rgba(var(--color-text-rgb), 60%); +} + +package-row.downloading header > small { + color: var(--color-accent); +} + +package-row.retrying header > small { + color: var(--color-warning); +} + +package-row.failed header > small, +package-row.failed footer > small { + color: var(--color-error); +} + +package-row.installed header > small, +package-row.installed footer > small { + color: var(--color-success); +} + +package-row progress { + width: 100%; + height: 6dp; + margin: 0 0 var(--space-sm) 0; + border-radius: 3dp; +} + +package-row footer { + display: flex; + align-items: flex-start; + width: 100%; + min-width: 0; + gap: var(--space-sm); + margin: 0; + padding: 0; +} + +package-row footer > small { + display: block; + flex: 1 1 0; + min-width: 0; + font-size: var(--font-size-xs); + line-height: 1; + color: rgba(var(--color-text-rgb), 45%); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +package-row nav { + display: flex; + flex: 0 0 auto; + gap: 6dp; + margin: 0; + padding: 0; +} + +package-row.installed { + align-items: center; +} + +package-row.installed > mod-icon { + margin-top: 0; +} + +package-row.installed > section { + padding-right: 38dp; +} + +package-row.installed header > small { + display: none; +} + +package-row.installed nav { + position: absolute; + top: 17dp; + right: 0; +} + +verification-progress { display: flex; flex-direction: column; gap: 10dp; width: 100%; } -.verification-file { - display: block; - font-size: 17dp; - color: #FFFFFF; +verification-progress file-path { + font-size: var(--font-size-lg); + color: var(--color-white); } -progress.verification-progress-bar { +verification-progress progress { height: 8dp; - margin: 2dp 0 0 0; + margin: var(--space-2xs) 0 0 0; } -.verification-detail { - display: block; - font-size: 14dp; - color: rgba(224, 219, 200, 65%); -} - -.modal-actions { +modal-actions { display: flex; flex-direction: row; flex-wrap: nowrap; align-items: stretch; - gap: 12dp; + gap: var(--space-md); width: 100%; flex: 0 0 auto; - padding-top: 4dp; + padding-top: var(--space-xs); } -.modal-actions-vertical { +modal-actions.vertical { flex-direction: column; align-items: stretch; } -.modal-actions-vertical button.modal-btn { +modal-actions.vertical button.modal-btn { flex: 0 0 auto; width: 100%; } @media (max-height: 640dp) { - .modal-dialog { - padding: 16dp; - gap: 12dp; + modal-dialog { + padding: var(--space-lg); + gap: var(--space-md); } - .modal-body { - font-size: 17dp; + modal-body { + font-size: var(--font-size-lg); } } @media (max-width: 640dp) { - .modal-actions { + modal-actions { flex-direction: column; } - .modal-actions button.modal-btn { + modal-actions button.modal-btn { flex: 0 0 auto; width: 100%; } diff --git a/sdk/include/mods/api.h b/sdk/include/mods/api.h index 3fde6c9f6a..3015130ae3 100644 --- a/sdk/include/mods/api.h +++ b/sdk/include/mods/api.h @@ -42,6 +42,8 @@ extern "C" { #define MOD_ABI_VERSION 1u #define MOD_ERROR_MESSAGE_SIZE 512u +#define DUSKLIGHT_SERVICE_ID_PREFIX "dev.twilitrealm.dusklight." + typedef struct ModContext ModContext; typedef enum ModResult { diff --git a/sdk/include/mods/meta.hpp b/sdk/include/mods/meta.hpp index ef85d827c2..6028a3df9c 100644 --- a/sdk/include/mods/meta.hpp +++ b/sdk/include/mods/meta.hpp @@ -13,21 +13,31 @@ * modmeta records. Each IMPORT_SERVICE/EXPORT_SERVICE/DEFINE_HOOK use places one * constant-initialized record object in the metadata section. */ +#if defined(_MSVC_LANG) && !defined(__clang__) +#define MOD_META_NO_ASAN +#else +#if defined(__has_attribute) && __has_attribute(no_sanitize) +#define MOD_META_NO_ASAN __attribute__((no_sanitize("address"))) +#else +#define MOD_META_NO_ASAN +#endif +#endif + #if defined(_WIN32) #pragma section("modmeta$a", read, write) #pragma section("modmeta$d", read, write) #pragma section("modmeta$z", read, write) #if defined(__clang__) -#define MOD_META_RECORD __declspec(allocate("modmeta$d")) __attribute__((used)) +#define MOD_META_RECORD __declspec(allocate("modmeta$d")) __attribute__((used)) MOD_META_NO_ASAN #else #define MOD_META_RECORD __declspec(allocate("modmeta$d")) #endif #elif defined(__APPLE__) -#define MOD_META_RECORD __attribute__((section("__DATA,__modmeta"), used)) +#define MOD_META_RECORD __attribute__((section("__DATA,__modmeta"), used)) MOD_META_NO_ASAN #elif defined(__has_attribute) && __has_attribute(retain) -#define MOD_META_RECORD __attribute__((section("modmeta"), used, retain)) +#define MOD_META_RECORD __attribute__((section("modmeta"), used, retain)) MOD_META_NO_ASAN #else -#define MOD_META_RECORD __attribute__((section("modmeta"), used)) +#define MOD_META_RECORD __attribute__((section("modmeta"), used)) MOD_META_NO_ASAN #endif /* Section bounds for the mod_meta descriptor */ diff --git a/sdk/include/mods/runtime.h b/sdk/include/mods/runtime.h new file mode 100644 index 0000000000..25719e2eb9 --- /dev/null +++ b/sdk/include/mods/runtime.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/* + * Lifecycle service for mods delegated to a runtime named by mod.json. The host passes the + * runtime's context as ctx and the delegated mod's context as subject. + */ +typedef struct ModRuntimeService { + ServiceHeader header; + + ModResult (*activate)(ModContext* ctx, ModContext* subject, ModError* out_error); + ModResult (*update)(ModContext* ctx, ModContext* subject, ModError* out_error); + ModResult (*deactivate)(ModContext* ctx, ModContext* subject, ModError* out_error); +} ModRuntimeService; diff --git a/sdk/include/mods/svc/actor.h b/sdk/include/mods/svc/actor.h new file mode 100644 index 0000000000..0048b70332 --- /dev/null +++ b/sdk/include/mods/svc/actor.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +#define ACTOR_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "actor" +#define ACTOR_SERVICE_MAJOR 1u +#define ACTOR_SERVICE_MINOR 0u + +typedef int16_t ProfileName; +typedef uint32_t ActorId; +typedef uint64_t ActorHandle; + +typedef struct { + const char name[8]; // Canonical stage name. Must be unique among active mod actors. Matching a + // game actor name intentionally overrides stage lookup for that actor. + + uint16_t priority_group; /* priorityGroup is the priority for when execute will be called on the + actor. Here are the main groups: + 0: The room manager actor + 1: Game scenes + 2: Various room change actors + 3: Most objects to be executed before Link + 4: Some bosses, canoe, epona, spinner, chests + 5: Link's actor + 6: Boomerang, midna + 7: Most objects, actors, bosses, triggers to be executed after link (most actors go here) + 8: Various objects and enemies + 9: Various actors + 10: Timer, Scene Exit actor + 11: Grass, Suspend Actors + */ + + size_t process_size; // Size of the actor class (use sizeof(my_actor_class)) + int16_t draw_priority; // an enum value that is prefixed with fpcDwPi. Select an existing value + // from the fpcDwPi to pick a draw priority matching the actor you wish + // to match priorities with. + uint32_t status; // Flags from fopAc_Status_e enum (all have fopAcStts_UNK_0x40000_e, a lot + // have fopAcStts_UNK_0x4000_e, add fopAcStts_CULL_e to enable culling) + uint8_t group; // The actor type. An enum value from fopAc_Group_e (fopAc_ACTOR_e, + // fopAc_ENEMY_e, fopAc_NPC_e) + uint8_t cull_type; // Enum value from fopAc_Cull_e + int (*create_function)( + void*); // Called after the actor is spawned, return type is a enum value of cPhs_Step + int (*delete_function)(void*); // Releases resources; returns 1 when deletion is complete + int (*execute_function)(void*); // Called once per game tick, the actor's priorityGroup + // determines when it will run relative to other actors + int (*is_delete_function)(void*); // Returns 1 when normal deletion may begin + int (*draw_function)(void*); // Called to draw the actor +} ActorProfileDesc; + +typedef struct { + uint32_t parameters; // The parameters to be passed to the actor + int8_t argument; // The argument to be passed to the actor (acts as an extra byte for a parameter) + int8_t room_num; // The room to spawn the actor in + struct { + float x; + float y; + float z; + } position; + struct { + int16_t x; + int16_t y; + int16_t z; + } angle; + struct { + float x; + float y; + float z; + } scale; + int (*create_function)(void*); // Optional: A custom function to run when the actor is created. +} ActorSpawnParams; + +typedef struct ActorService { + ServiceHeader header; + ModResult (*register_actor)(ModContext* ctx, const ActorProfileDesc* desc, + ProfileName* outProfileName, ActorHandle* outActorHandle); + ModResult (*unregister_actor)(ModContext* ctx, ActorHandle handle); + ModResult (*create_actor_from_name)( + ModContext* ctx, const char* name, const ActorSpawnParams* params, ActorId* outId); + ModResult (*create_actor)( + ModContext* ctx, ProfileName name, const ActorSpawnParams* params, ActorId* outId); + ModResult (*create_child_actor_from_name)(ModContext* ctx, const char* name, ActorId parentID, + const ActorSpawnParams* params, ActorId* outId); + ModResult (*create_child_actor)(ModContext* ctx, ProfileName name, ActorId parentID, + const ActorSpawnParams* params, ActorId* outId); + ModResult (*get_actor_id)(ModContext* ctx, ProfileName name, ActorId* outId); + ModResult (*get_actor_room_num)(ModContext* ctx, ActorId actorId, int8_t* outRoomNum); + /* Returns MOD_UNAVAILABLE if the actor cannot be queued for deletion immediately. */ + ModResult (*delete_actor)(ModContext* ctx, ActorId actorId); +} ActorService; + +#ifdef __cplusplus +#include "mods/service.hpp" + +template <> +struct mods::ServiceTraits { + static constexpr const char* id = ACTOR_SERVICE_ID; + static constexpr uint16_t major_version = ACTOR_SERVICE_MAJOR; + static constexpr uint16_t minor_version = ACTOR_SERVICE_MINOR; +}; +#endif diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h new file mode 100644 index 0000000000..f225fef1e3 --- /dev/null +++ b/sdk/include/mods/svc/audio_res.h @@ -0,0 +1,434 @@ +#pragma once + +#include + +#ifdef __cplusplus +#include +#endif + +#define AUDIO_RES_SERVICE_ID "dev.twilitrealm.dusklight.audio_res" +#define AUDIO_RES_SERVICE_MAJOR 1u +#define AUDIO_RES_SERVICE_MINOR 0u + +/* + * Defines APIs for replacing and adding audio resources. + * + * TP's audio engine (JAudioV2) is extremely complex. For more of an overview through its + * functionality, please see docs/jaudio.md in the repo. + */ + +/* + * WSYS structs + */ + +/** + * "Default" key for wave definitions. Key like musical key, not map keys or whatever. + */ +#define AUDIO_RES_DEFAULT_KEY 0x3C + +/** + * Defines TP's raw wave banks, which can be modified. + */ +typedef enum AudioWaveBank : uint8_t { + AUDIO_WAVE_BANK_SOUND_EFFECTS = 0, + AUDIO_WAVE_BANK_MUSIC_SAMPLES = 1, +} AudioWaveBank; + +/** + * Defines formats for raw sample data the DSP can play back. + */ +typedef enum AudioWaveFormat : uint8_t { + /** + * 16 samples per 9 bytes custom Nintendo ADPCM. + */ + AUDIO_WAVE_FORMAT_ADPCM4 = 0, + + /** + * 16 samples per 5 bytes custom Nintendo ADPCM. + */ + AUDIO_WAVE_FORMAT_ADPCM2 = 1, + + /** + * 1-byte-per-sample simple PCM. + */ + AUDIO_WAVE_FORMAT_PCM8 = 2, + + /** + * 2-byte-per-sample simple PCM. + */ + AUDIO_WAVE_FORMAT_PCM16 = 3, +} AudioWaveFormat; + +/** + * Handle to reference wave replacements/additions by mods. + */ +typedef uint64_t AudioWaveHandle; + +/** + * Data needed to load a raw audio data file. + * + * @see AudioWaveInfo + */ +typedef struct AudioRawWave { + /** + * Format of the raw sample data. + */ + AudioWaveFormat format; + + /** + * Sample rate, in Hertz. + */ + float sample_rate; + + /** + * Last audio sample before ADPCM loop start point. (unused if not looping) + */ + int16_t sample_value_last; + + /** + * Second-to-last audio sample before ADPCM loop start point. (unused if not looping) + */ + int16_t sample_value_penult; +} AudioRawWave; + +/** + * Metadata needed to play a wave. + */ +typedef struct AudioWaveInfo { + /** + * Base key (musical key). Set to AUDIO_RES_DEFAULT_KEY if you don't care. + */ + uint8_t base_key; + + /** + * If true, the sample loops. + */ + bool loop; + + /** + * Sample number where loop resumes when end is reached. + * Does nothing if not looping. + */ + uint32_t loop_start_sample; + + /** + * End of the wave. Stops playback if not looping. + * Automatically clamped to the sample count from the file, if greater. + */ + uint32_t loop_end_sample; + + /** + * If provided, specifies that the passed file is "raw" and only contains sample data. + * This must be used if you want to ship ADPCM samples, as other containers do not support that. + */ + AudioRawWave const* raw_wave; +} AudioWaveInfo; + +/* + * BST structs + */ + +/** + * Game defined categories (groups) for sound effects. + */ +typedef enum SoundEffectCategory : uint8_t { + SE_CATEGORY_SYSTEM_SE, + SE_CATEGORY_PLAYER_VOICE, + SE_CATEGORY_PLAYER_SE, + SE_CATEGORY_FOOTNOTE_SE, + SE_CATEGORY_COLLISION_SE, + SE_CATEGORY_CHARA_VOICE, + SE_CATEGORY_CHARA_SE, + SE_CATEGORY_ENEMY_SE, + SE_CATEGORY_OBJECT_SE, + SE_CATEGORY_ENV_SE +} SoundEffectCategory; + +/** + * Data that can be defined for a sound effect in the sound table. + */ +typedef struct AudioSoundTableEffectInfo { + /** + * Priority relative to other sound effects. Can affect culling and such if + * many effects are playing. + */ + uint8_t priority; + + /** + * Volume multiplier for this sound effect. Clamped to range 0-2. + */ + float volume; + + /** + * Pitch multiplier for this sound effect. + */ + float pitch; + + /** + * If true, the effect is always treated as max priority, regardless of factors like distance. + */ + bool always_max_priority; + + /** + * Don't calculate volume changes by distance. + */ + bool ignore_distance_volume; + + /** + * Don't FX Mix (reverb) changes by distance. + */ + bool ignore_distance_fx_mix; + + /** + * Don't calculate panning (left/right balance). + */ + bool ignore_pan; + + /** + * Don't calculate dolby (front/back balance). + */ + bool ignore_dolby; + + /** + * 0-15 value controlling volume randomization strength. + */ + uint8_t random_volume; + + /** + * 0-15 value controlling pitch randomization strength. + */ + uint8_t random_pitch; + + /** + * 0-15 value controlling Doppler effect strength. + */ + uint8_t doppler_power; + + /** + * Value from 0-15 selecting "volume distance" class. This effectively selects a fixed curve for + * parameters like volume by distance. + */ + uint8_t volume_dist_class; + + /** + * Limit minimum volume of sound (after distance drop-off) to 0.2. + */ + bool clamp_min_volume; + + /** + * Treat this sound as "far away" or "culled" at "max distance." + * Affects things like automatic stopping. + */ + bool cull_at_max_distance; +} AudioSoundTableEffectInfo; + +/** + * Stereo panning parameters for stream channels. + */ +typedef enum StreamPan : uint8_t { + STREAM_PAN_CENTER, + STREAM_PAN_LEFT, + STREAM_PAN_RIGHT, +} StreamPan; + +/** + * Maximum amount of channels a streamed track can have. + */ +#define STREAM_MAX_CHILDREN 6 + +/** + * Data that can be defined for a stream in the sound table. + */ +typedef struct AudioSoundTableStreamInfo { + /** + * Unsure if used. + */ + uint8_t priority; + + /** + * Volume multiplier for this stream. Clamped to range 0-2. + */ + float volume; + + /** + * For each channel in the loaded .ast file, specifies the panning position for said channel. + */ + StreamPan pan_parameters[STREAM_MAX_CHILDREN]; + + /** + * Whether this stream automatically stops on scene change. + */ + bool stop_on_scene_change; +} AudioSoundTableStreamInfo; + +/** + * Handle to reference sound table replacements/additions by mods. + */ +typedef uint64_t AudioSoundTableHandle; + +/** + * Defines APIs for replacing and adding audio resources. + */ +typedef struct AudioResService { + ServiceHeader header; + + /* + * WSYS API + */ + + /** + * Default wave info if none is provided: default key, no loop, not raw audio. + */ + AudioWaveInfo const* default_wave_info; + + /** + * Replace an existing audio wave in the game. This replacement will follow mod order + * prioritization. + * + * The sound effect will remain permanently resident in memory. + * + * Sound effects can be provided as raw samples (if raw_wave data is provided in wave_info), + * WAVE (.wav) file, or OGG Opus (.opus, if Dusklight is compiled with support). Container + * formats are detected based on header, not based on file name. + * + * @param ctx Pointer to your mod's context. + * @param bank Which wave bank to replace a sound effect in. + * @param wave_id ID of the wave to replace. This does *not* directly correlate to sound table + * entries or JAISound values in any way! + * @param file_name Path of the audio file to load in the mod's data, e.g. res/foo.opus. This + * does *not* need to be an overlay file! + * @param wave_info Optional: metadata for the new wave. If not provided will use reasonable + * defaults (@ref default_wave_info). + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used + * with @ref remove_wave. + */ + ModResult (*replace_wave)( + ModContext* ctx, + AudioWaveBank bank, + uint16_t wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle); + + /** + * Add a new audio wave to the game. The service allocates the placed ID for you. + * + * The sound effect will remain permanently resident in memory. + * + * Sound effects can be provided as raw samples (if raw_wave data is provided in wave_info), + * WAVE (.wav) file, or OGG Opus (.opus, if Dusklight is compiled with support). Container + * formats are detected based on header, not based on file name. + * + * @param ctx Pointer to your mod's context. + * @param bank Which wave bank to replace a sound effect in. + * @param file_name Path of the audio file to load in the mod's data, e.g. res/foo.opus. This does *not* need to be an overlay file! + * @param wave_info Optional: metadata for the new wave. If not provided will use reasonable defaults (@ref default_wave_info). + * @param out_handle Optional: pointer receives the handle for the addition. This can be used with @ref remove_wave. + * @param out_wave_id Receives the allocated ID in the wave bank. + */ + ModResult (*add_wave)( + ModContext* ctx, + AudioWaveBank bank, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle, + uint16_t* out_wave_id); + + /** + * Remove a wave addition/replacement previously created by this mod. + * + * @param ctx Pointer to your mod's context. + * @param handle The handle identifying which wave to remove. + */ + ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle); + + /* + * BST API + */ + + /** + * Default sound effect info if none is provided: default volume, pitch, medium priority. + */ + AudioSoundTableEffectInfo const* default_effect_info; + + /** + * Replace a sound effect's parameters in the sound table. This replacement will follow mod order + * prioritization. + * + * @param ctx Pointer to your mod's context. + * @param category_id Category of the sound effect. + * @param effect_id ID of the effect. + * @param info Optional: new parameters for the sound. Falls back to @ref default_effect_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + */ + ModResult (*replace_sound_table_effect)( + ModContext* ctx, + SoundEffectCategory category_id, + uint16_t effect_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle); + + /** + * Add a sound effect to the sound table. The service allocates the placed ID for you. + * + * @param ctx Pointer to your mod's context. + * @param category_id Category of the sound effect. + * @param info Optional: new parameters for the sound. Falls back to @ref default_effect_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + * @param out_effect_id Receives the allocated ID of the sound effect. + */ + ModResult (*add_sound_table_effect)( + ModContext* ctx, + SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id); + + /** + * Default sound effect info if none is provided: default volume, medium priority, stereo channels, *no* stop on scene change. + */ + AudioSoundTableStreamInfo const* default_stream_info; + + /** + * Replace a sound effect's parameters in the sound table. This replacement will follow mod order + * prioritization. + * + * @param ctx Pointer to your mod's context. + * @param stream_id ID of the stream. + * @param file_path Path of the .ast on disc. If you're providing one yourself, use an overlay! + * @param info Optional: new parameters for the stream. Falls back to @ref default_stream_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + */ + ModResult (*replace_sound_table_stream)( + ModContext* ctx, + uint16_t stream_id, + char const* file_path, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle); + + /** + * Add a sound effect to the sound table. The service allocates the placed ID for you. + * + * @param ctx Pointer to your mod's context. + * @param file_path Path of the .ast on disc. If you're providing one yourself, use an overlay! + * @param info Optional: new parameters for the stream. Falls back to @ref default_stream_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + * @param out_stream_id Receives the allocated ID of the stream. + */ + ModResult (*add_sound_table_stream)( + ModContext* ctx, + char const* file_path, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_stream_id); + + /** + * Remove a sound table addition/replacement previously created by this mod. + * + * @param ctx Pointer to your mod's context. + * @param handle The handle identifying which wave to remove. + */ + ModResult (*remove_sound_table)(ModContext* ctx, AudioSoundTableHandle handle); +} AudioResService; + +MOD_DECLARE_SERVICE(AudioResService, svc_audio_res, AUDIO_RES_SERVICE_ID, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR); diff --git a/sdk/include/mods/svc/camera.h b/sdk/include/mods/svc/camera.h index 1dccecfba6..a901c2b739 100644 --- a/sdk/include/mods/svc/camera.h +++ b/sdk/include/mods/svc/camera.h @@ -6,7 +6,7 @@ #include #endif -#define CAMERA_SERVICE_ID "dev.twilitrealm.dusklight.camera" +#define CAMERA_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "camera" #define CAMERA_SERVICE_MAJOR 1u #define CAMERA_SERVICE_MINOR 1u diff --git a/sdk/include/mods/svc/config.h b/sdk/include/mods/svc/config.h index fb04f6e9ea..55a0bca131 100644 --- a/sdk/include/mods/svc/config.h +++ b/sdk/include/mods/svc/config.h @@ -6,7 +6,7 @@ #include #endif -#define CONFIG_SERVICE_ID "dev.twilitrealm.dusklight.config" +#define CONFIG_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "config" #define CONFIG_SERVICE_MAJOR 1u #define CONFIG_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/file.h b/sdk/include/mods/svc/file.h index 4d5c82b5a1..0383c54066 100644 --- a/sdk/include/mods/svc/file.h +++ b/sdk/include/mods/svc/file.h @@ -6,7 +6,7 @@ #include #endif -#define FILE_SERVICE_ID "dev.twilitrealm.dusklight.file" +#define FILE_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "file" #define FILE_SERVICE_MAJOR 1u #define FILE_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/flow.h b/sdk/include/mods/svc/flow.h index a9028df0a4..1099d86fd9 100644 --- a/sdk/include/mods/svc/flow.h +++ b/sdk/include/mods/svc/flow.h @@ -6,7 +6,7 @@ #include #endif -#define FLOW_SERVICE_ID "dev.twilitrealm.dusklight.flow" +#define FLOW_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "flow" #define FLOW_SERVICE_MAJOR 1u #define FLOW_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/game.h b/sdk/include/mods/svc/game.h index 32de49903f..61a10968e6 100644 --- a/sdk/include/mods/svc/game.h +++ b/sdk/include/mods/svc/game.h @@ -15,7 +15,7 @@ * 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_ID DUSKLIGHT_SERVICE_ID_PREFIX "game" #define GAME_SERVICE_MAJOR 2u #define GAME_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/game_mode.h b/sdk/include/mods/svc/game_mode.h index 8ccc5d64f1..1cf0d75815 100644 --- a/sdk/include/mods/svc/game_mode.h +++ b/sdk/include/mods/svc/game_mode.h @@ -3,7 +3,7 @@ #include #include -#define GAME_MODE_SERVICE_ID "dev.twilitrealm.dusklight.gamemode" +#define GAME_MODE_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "gamemode" #define GAME_MODE_SERVICE_MAJOR 1u #define GAME_MODE_SERVICE_MINOR 0u @@ -25,7 +25,7 @@ typedef struct { uint32_t struct_size; const char* game_mode_id; const char* full_name; - const char save_name[32]; // Empty uses default (gczelda2); max 31 chars + const char save_name[32]; // Empty uses gczelda2; [A-Za-z0-9._-], except . and .. void* user_data; // Pointer will be passed to all callbacks GameModeCallback on_activated; // Called when the game mode is selected GameModeCallback on_deactivated; // Called when the game mode is deselected diff --git a/sdk/include/mods/svc/gfx.h b/sdk/include/mods/svc/gfx.h index 0eee026984..787958716b 100644 --- a/sdk/include/mods/svc/gfx.h +++ b/sdk/include/mods/svc/gfx.h @@ -31,7 +31,7 @@ * should be released in mod_shutdown. The device outlives all mods. */ -#define GFX_SERVICE_ID "dev.twilitrealm.dusklight.gfx" +#define GFX_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "gfx" #define GFX_SERVICE_MAJOR 1u #define GFX_SERVICE_MINOR 2u diff --git a/sdk/include/mods/svc/hook.h b/sdk/include/mods/svc/hook.h index 288db3cc14..30b199b386 100644 --- a/sdk/include/mods/svc/hook.h +++ b/sdk/include/mods/svc/hook.h @@ -20,7 +20,7 @@ * (file-local statics included). */ -#define HOOK_SERVICE_ID "dev.twilitrealm.dusklight.hook" +#define HOOK_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "hook" #define HOOK_SERVICE_MAJOR 1u #define HOOK_SERVICE_MINOR 1u diff --git a/sdk/include/mods/svc/hook.hpp b/sdk/include/mods/svc/hook.hpp index eab3115d01..7ef02538b3 100644 --- a/sdk/include/mods/svc/hook.hpp +++ b/sdk/include/mods/svc/hook.hpp @@ -136,8 +136,7 @@ struct NamedHook : HookImpl, R, A...> {}; * mods::hook::add_pre(on_link_execute); * * DEFINE_HOOK_SYMBOL names may be the platform mangled name (dlopen convention, no Mach-O - * leading underscore) or the demangled qualified display name; overloaded display names are - * ambiguous and need the mangled form. + * leading underscore), a qualified display name, or a source.cpp#qualified_name alias. */ #if defined(__GNUC__) && !defined(__clang__) && defined(__ELF__) #define DEFINE_HOOK(target, alias) \ diff --git a/sdk/include/mods/svc/host.h b/sdk/include/mods/svc/host.h index 45d89eea3e..2b751f34c3 100644 --- a/sdk/include/mods/svc/host.h +++ b/sdk/include/mods/svc/host.h @@ -11,7 +11,7 @@ * Always available; every other service can be reached from it. */ -#define HOST_SERVICE_ID "dev.twilitrealm.dusklight.host" +#define HOST_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "host" #define HOST_SERVICE_MAJOR 2u #define HOST_SERVICE_MINOR 2u diff --git a/sdk/include/mods/svc/http.h b/sdk/include/mods/svc/http.h index fd4d9a98db..2c880b3b80 100644 --- a/sdk/include/mods/svc/http.h +++ b/sdk/include/mods/svc/http.h @@ -6,7 +6,7 @@ #include #endif -#define HTTP_SERVICE_ID "dev.twilitrealm.dusklight.http" +#define HTTP_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "http" #define HTTP_SERVICE_MAJOR 1u #define HTTP_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/item.h b/sdk/include/mods/svc/item.h index ff4f31b547..58cf7743e4 100644 --- a/sdk/include/mods/svc/item.h +++ b/sdk/include/mods/svc/item.h @@ -6,7 +6,7 @@ #include #endif -#define ITEM_SERVICE_ID "dev.twilitrealm.dusklight.item" +#define ITEM_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "item" #define ITEM_SERVICE_MAJOR 2u #define ITEM_SERVICE_MINOR 3u diff --git a/sdk/include/mods/svc/log.h b/sdk/include/mods/svc/log.h index 1040710492..2401b4dccf 100644 --- a/sdk/include/mods/svc/log.h +++ b/sdk/include/mods/svc/log.h @@ -11,7 +11,7 @@ * (prefixed with its ID). */ -#define LOG_SERVICE_ID "dev.twilitrealm.dusklight.log" +#define LOG_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "log" #define LOG_SERVICE_MAJOR 1u #define LOG_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/message.h b/sdk/include/mods/svc/message.h index b170e6f09b..d3833d904e 100644 --- a/sdk/include/mods/svc/message.h +++ b/sdk/include/mods/svc/message.h @@ -6,7 +6,7 @@ #include #endif -#define MESSAGE_SERVICE_ID "dev.twilitrealm.dusklight.message" +#define MESSAGE_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "message" #define MESSAGE_SERVICE_MAJOR 1u #define MESSAGE_SERVICE_MINOR 1u diff --git a/sdk/include/mods/svc/net.h b/sdk/include/mods/svc/net.h new file mode 100644 index 0000000000..9202ff50ad --- /dev/null +++ b/sdk/include/mods/svc/net.h @@ -0,0 +1,145 @@ +#pragma once + +#include + +#ifdef __cplusplus +#include +#endif + +#define NET_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "net" +#define NET_SERVICE_MAJOR 1u +#define NET_SERVICE_MINOR 0u + +/** 0 is never a valid handle. */ +typedef uint64_t NetHandle; + +typedef enum NetError { + NET_ERROR_NONE = 0, + NET_ERROR_INVALID_ENDPOINT = 1, + NET_ERROR_RESOLVE = 2, + NET_ERROR_TIMEOUT = 3, + NET_ERROR_REFUSED = 4, + NET_ERROR_UNREACHABLE = 5, + NET_ERROR_RESET = 6, + NET_ERROR_ADDRESS_IN_USE = 7, + NET_ERROR_PERMISSION = 8, + NET_ERROR_TOO_LARGE = 9, + NET_ERROR_CANCELED = 10, + NET_ERROR_NETWORK = 11, +} NetError; + +#define NET_ENDPOINT_MAX 80 +/** NUL-terminated tcp://host:port or udp://host:port endpoint. */ +typedef struct NetEndpoint { + char text[NET_ENDPOINT_MAX]; +} NetEndpoint; + +typedef struct NetConnectDesc { + uint32_t struct_size; + /** TCP endpoint. Hostnames and IP literals are accepted. */ + const char* endpoint; + /** 0 defaults to 10 seconds. Resolution is included. */ + uint32_t connect_timeout_ms; + /** 0 defaults to 5 seconds for flush and peer EOF. */ + uint32_t close_timeout_ms; + /** 0 defaults to 1 MiB. Maximum of 8 MiB. */ + size_t max_send_queue_bytes; + bool no_delay; + /** Sampled when each event is polled. */ + void* user_data; +} NetConnectDesc; + +#define NET_CONNECT_DESC_INIT {sizeof(NetConnectDesc), NULL, 0u, 0u, 0u, true, NULL} + +typedef struct NetListenDesc { + uint32_t struct_size; + /** TCP endpoint with an IP literal. Port 0 requests an ephemeral port. */ + const char* bind; + uint32_t close_timeout_ms; + size_t max_send_queue_bytes; + bool no_delay; + void* user_data; +} NetListenDesc; + +#define NET_LISTEN_DESC_INIT {sizeof(NetListenDesc), NULL, 0u, 0u, true, NULL} + +typedef struct NetDatagramDesc { + uint32_t struct_size; + /** UDP endpoint with an IP literal. Port 0 requests an ephemeral port. */ + const char* bind; + size_t max_send_queue_bytes; + void* user_data; +} NetDatagramDesc; + +#define NET_DATAGRAM_DESC_INIT {sizeof(NetDatagramDesc), NULL, 0u, NULL} + +typedef enum NetEventType { + NET_EVENT_NONE = 0, + NET_EVENT_CONNECTED = 1, + NET_EVENT_ACCEPTED = 2, + NET_EVENT_STREAM_DATA = 3, + NET_EVENT_DATAGRAM = 4, + NET_EVENT_DROPPED = 5, + NET_EVENT_RESOLVED = 6, + NET_EVENT_CLOSED = 7, +} NetEventType; + +typedef struct NetEvent { + uint32_t struct_size; + NetEventType type; + /** Source handle. A CLOSED or RESOLVED handle is invalid after poll_event returns it. */ + NetHandle handle; + void* user_data; + NetHandle accepted; + /** Peer, datagram source, or resolved endpoint as applicable. */ + NetEndpoint endpoint; + /** Valid until this mod's next poll_event call or deactivation. */ + const void* data; + size_t size; + uint32_t dropped; + NetError error; + /** Never NULL. Valid until this mod's next poll_event call or deactivation. */ + const char* error_message; +} NetEvent; + +#define NET_EVENT_INIT \ + {sizeof(NetEvent), NET_EVENT_NONE, 0u, NULL, 0u, {{0}}, NULL, 0u, 0u, NET_ERROR_NONE, ""} + +typedef struct NetStats { + uint32_t struct_size; + size_t queued_send_bytes; + uint64_t inbound_dropped; + uint64_t send_failures; + uint64_t bytes_sent; + uint64_t bytes_received; +} NetStats; + +#define NET_STATS_INIT {sizeof(NetStats), 0u, 0u, 0u, 0u, 0u} + +typedef struct NetService { + ServiceHeader header; + + /** Starts an asynchronous TCP connection. */ + ModResult (*connect)(ModContext* ctx, const NetConnectDesc* desc, NetHandle* out_handle); + /** Opens a TCP listener and returns its local endpoint. */ + ModResult (*listen)(ModContext* ctx, const NetListenDesc* desc, NetHandle* out_handle, + NetEndpoint* out_local, NetError* out_error); + /** Opens a UDP socket and returns its local endpoint. */ + ModResult (*open_datagram)(ModContext* ctx, const NetDatagramDesc* desc, NetHandle* out_handle, + NetEndpoint* out_local, NetError* out_error); + /** Resolves a TCP or UDP endpoint asynchronously. */ + ModResult (*resolve)( + ModContext* ctx, const char* endpoint, void* user_data, NetHandle* out_handle); + /** Returns MOD_OK and NET_EVENT_NONE when the queue is empty. */ + ModResult (*poll_event)(ModContext* ctx, NetEvent* out_event); + /** Copies bytes to a connected stream's outbound queue. */ + ModResult (*send)(ModContext* ctx, NetHandle stream, const void* data, size_t size); + /** Copies one datagram for a literal UDP destination. The maximum size is 65,507 bytes. */ + ModResult (*send_to)( + ModContext* ctx, NetHandle socket, const char* endpoint, const void* data, size_t size); + ModResult (*set_user_data)(ModContext* ctx, NetHandle handle, void* user_data); + ModResult (*stats)(ModContext* ctx, NetHandle handle, NetStats* out_stats); + ModResult (*close)(ModContext* ctx, NetHandle handle); +} NetService; + +MOD_DECLARE_SERVICE(NetService, svc_net, NET_SERVICE_ID, NET_SERVICE_MAJOR, NET_SERVICE_MINOR); diff --git a/sdk/include/mods/svc/net.hpp b/sdk/include/mods/svc/net.hpp new file mode 100644 index 0000000000..44edf4001b --- /dev/null +++ b/sdk/include/mods/svc/net.hpp @@ -0,0 +1,189 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace mods::net { + +class Socket { +public: + Socket() = default; + Socket(NetHandle handle, ModResult result) : mHandle{handle}, mResult{result} {} + ~Socket() { reset(); } + + Socket(const Socket&) = delete; + Socket& operator=(const Socket&) = delete; + Socket(Socket&& other) noexcept { *this = std::move(other); } + Socket& operator=(Socket&& other) noexcept { + if (this != &other) { + reset(); + mHandle = std::exchange(other.mHandle, 0); + mResult = other.mResult; + } + return *this; + } + + explicit operator bool() const { return mResult == MOD_OK && mHandle != 0; } + + ModResult result() const { return mResult; } + NetHandle handle() const { return mHandle; } + + ModResult send(std::span bytes) const { + return svc_net != nullptr && mHandle != 0 ? + svc_net->send(mod_ctx, mHandle, bytes.data(), bytes.size()) : + MOD_UNAVAILABLE; + } + + ModResult send_to(std::string_view endpoint, std::span bytes) const { + if (svc_net == nullptr || mHandle == 0) { + return MOD_UNAVAILABLE; + } + const std::string endpointText{endpoint}; + return svc_net->send_to(mod_ctx, mHandle, endpointText.c_str(), bytes.data(), bytes.size()); + } + + std::optional stats() const { + if (svc_net == nullptr || mHandle == 0) { + return std::nullopt; + } + NetStats value = NET_STATS_INIT; + if (svc_net->stats(mod_ctx, mHandle, &value) != MOD_OK) { + return std::nullopt; + } + return value; + } + + ModResult set_user_data(void* userData) const { + return svc_net != nullptr && mHandle != 0 ? + svc_net->set_user_data(mod_ctx, mHandle, userData) : + MOD_UNAVAILABLE; + } + + ModResult close() { + if (mHandle == 0) { + return mResult == MOD_OK ? MOD_OK : MOD_UNAVAILABLE; + } + mResult = svc_net != nullptr ? svc_net->close(mod_ctx, mHandle) : MOD_UNAVAILABLE; + mHandle = 0; + return mResult; + } + + void detach() { mHandle = 0; } + +private: + void reset() { (void)close(); } + + NetHandle mHandle = 0; + ModResult mResult = MOD_UNAVAILABLE; +}; + +struct BindOutcome { + std::string local; + NetError error = NET_ERROR_NONE; +}; + +inline Socket connect(std::string_view endpoint, NetConnectDesc options = NET_CONNECT_DESC_INIT) { + if (svc_net == nullptr) { + return {0, MOD_UNAVAILABLE}; + } + const std::string endpointText{endpoint}; + options.struct_size = sizeof(options); + options.endpoint = endpointText.c_str(); + NetHandle handle = 0; + const ModResult result = svc_net->connect(mod_ctx, &options, &handle); + return {handle, result}; +} + +inline Socket listen(std::string_view bind, BindOutcome* out = nullptr, + NetListenDesc options = NET_LISTEN_DESC_INIT) { + if (svc_net == nullptr) { + return {0, MOD_UNAVAILABLE}; + } + const std::string bindText{bind}; + options.struct_size = sizeof(options); + options.bind = bindText.c_str(); + NetHandle handle = 0; + NetEndpoint local{}; + NetError error = NET_ERROR_NONE; + const ModResult result = svc_net->listen(mod_ctx, &options, &handle, &local, &error); + if (out != nullptr) { + *out = {.local = local.text, .error = error}; + } + return {handle, result}; +} + +inline Socket open_datagram(std::string_view bind, BindOutcome* out = nullptr, + NetDatagramDesc options = NET_DATAGRAM_DESC_INIT) { + if (svc_net == nullptr) { + return {0, MOD_UNAVAILABLE}; + } + const std::string bindText{bind}; + options.struct_size = sizeof(options); + options.bind = bindText.c_str(); + NetHandle handle = 0; + NetEndpoint local{}; + NetError error = NET_ERROR_NONE; + const ModResult result = svc_net->open_datagram(mod_ctx, &options, &handle, &local, &error); + if (out != nullptr) { + *out = {.local = local.text, .error = error}; + } + return {handle, result}; +} + +inline Socket resolve(std::string_view endpoint, void* userData = nullptr) { + if (svc_net == nullptr) { + return {0, MOD_UNAVAILABLE}; + } + const std::string endpointText{endpoint}; + NetHandle handle = 0; + const ModResult result = svc_net->resolve(mod_ctx, endpointText.c_str(), userData, &handle); + return {handle, result}; +} + +inline Socket adopt(NetHandle accepted) { + return {accepted, accepted != 0 ? MOD_OK : MOD_INVALID_ARGUMENT}; +} + +struct Event { + NetEventType type = NET_EVENT_NONE; + NetHandle handle = 0; + void* userData = nullptr; + NetHandle accepted = 0; + std::string_view endpoint; + std::span data; + uint32_t dropped = 0; + NetError error = NET_ERROR_NONE; + std::string_view message; +}; + +inline bool poll(Event& out) { + out = {}; + if (svc_net == nullptr) { + return false; + } + NetEvent raw = NET_EVENT_INIT; + if (svc_net->poll_event(mod_ctx, &raw) != MOD_OK || raw.type == NET_EVENT_NONE) { + return false; + } + out.type = raw.type; + out.handle = raw.handle; + out.userData = raw.user_data; + out.accepted = raw.accepted; + out.endpoint = raw.endpoint.text; + if (raw.data != nullptr && raw.size != 0) { + out.data = {static_cast(raw.data), raw.size}; + } + out.dropped = raw.dropped; + out.error = raw.error; + out.message = raw.error_message != nullptr ? raw.error_message : ""; + return true; +} + +} // namespace mods::net diff --git a/sdk/include/mods/svc/overlay.h b/sdk/include/mods/svc/overlay.h index ea46bc8798..ee4318ea4f 100644 --- a/sdk/include/mods/svc/overlay.h +++ b/sdk/include/mods/svc/overlay.h @@ -6,7 +6,7 @@ #include #endif -#define OVERLAY_SERVICE_ID "dev.twilitrealm.dusklight.overlay" +#define OVERLAY_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "overlay" #define OVERLAY_SERVICE_MAJOR 1u #define OVERLAY_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/resource.h b/sdk/include/mods/svc/resource.h index 60b609d2a2..474c781b5b 100644 --- a/sdk/include/mods/svc/resource.h +++ b/sdk/include/mods/svc/resource.h @@ -12,9 +12,9 @@ * for temporary storage. */ -#define RESOURCE_SERVICE_ID "dev.twilitrealm.dusklight.resource" +#define RESOURCE_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "resource" #define RESOURCE_SERVICE_MAJOR 1u -#define RESOURCE_SERVICE_MINOR 0u +#define RESOURCE_SERVICE_MINOR 1u /* * A loaded resource, allocated by the service. Return every successful load with free; @@ -44,6 +44,9 @@ typedef struct ResourceService { * already-freed buffer. */ void (*free)(ModContext* ctx, ResourceBuffer* buffer); + + bool (*file_exists)(ModContext* ctx, char const* relative_path); + bool (*directory_exists)(ModContext* ctx, char const* relative_path); } ResourceService; MOD_DECLARE_SERVICE(ResourceService, svc_resource, RESOURCE_SERVICE_ID, RESOURCE_SERVICE_MAJOR, diff --git a/sdk/include/mods/svc/save.h b/sdk/include/mods/svc/save.h index 0d6943f96a..0bee85e4bf 100644 --- a/sdk/include/mods/svc/save.h +++ b/sdk/include/mods/svc/save.h @@ -6,7 +6,7 @@ #include #endif -#define SAVE_SERVICE_ID "dev.twilitrealm.dusklight.save" +#define SAVE_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "save" #define SAVE_SERVICE_MAJOR 1u #define SAVE_SERVICE_MINOR 0u @@ -19,8 +19,9 @@ typedef uint64_t SaveObserverHandle; /* * Per-slot mod storage. * - * Blobs are scoped to the calling mod and saved alongside each slot. Current-slot calls return - * MOD_UNAVAILABLE when no slot is active. + * Blobs are scoped to the calling mod, active CARD save file, and slot. Current-slot calls + * return MOD_UNAVAILABLE when no slot is active. + * Names must contain 1-256 bytes, excluding the NUL terminator. * * Callbacks run on the game thread. Observer registrations are removed when the calling mod is * detached. @@ -54,7 +55,7 @@ typedef struct SaveService { ModResult (*unobserve_saves)(ModContext* ctx, SaveObserverHandle handle); - /* Read the calling mod's blob from any slot. Uses the get_blob buffer contract. */ + /* Read the calling mod's blob from any slot in the active save file. */ ModResult (*peek_blob)( ModContext* ctx, uint32_t slot, const char* name, void* buf, size_t* inout_size); diff --git a/sdk/include/mods/svc/stage.h b/sdk/include/mods/svc/stage.h index a9f8198919..9dbd94cff1 100644 --- a/sdk/include/mods/svc/stage.h +++ b/sdk/include/mods/svc/stage.h @@ -6,7 +6,7 @@ #include #endif -#define STAGE_SERVICE_ID "dev.twilitrealm.dusklight.stage" +#define STAGE_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "stage" #define STAGE_SERVICE_MAJOR 1u #define STAGE_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/texture.h b/sdk/include/mods/svc/texture.h index dac73fbade..0de813cd04 100644 --- a/sdk/include/mods/svc/texture.h +++ b/sdk/include/mods/svc/texture.h @@ -6,7 +6,7 @@ #include #endif -#define TEXTURE_SERVICE_ID "dev.twilitrealm.dusklight.texture" +#define TEXTURE_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "texture" #define TEXTURE_SERVICE_MAJOR 1u #define TEXTURE_SERVICE_MINOR 0u diff --git a/sdk/include/mods/svc/ui.h b/sdk/include/mods/svc/ui.h index 31bff18c58..300f803721 100644 --- a/sdk/include/mods/svc/ui.h +++ b/sdk/include/mods/svc/ui.h @@ -8,20 +8,18 @@ #include #endif -#define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui" +#define UI_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "ui" #define UI_SERVICE_MAJOR 2u -#define UI_SERVICE_MINOR 2u +#define UI_SERVICE_MINOR 3u /* * UI primitives: a panel inside the host Mods window, mod-owned windows, dialogs, toasts, * 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. + * callbacks). Handles are opaque IDs; a stale or unknown handle fails with MOD_INVALID_ARGUMENT. + * A panel or tab rebuild destroys the previous build's elements, so re-acquire handles in each + * build callback and use them only until the next rebuild. Strings are UTF-8. */ /* 0 is never a valid handle. */ @@ -30,6 +28,7 @@ typedef uint64_t UiDialogHandle; typedef uint64_t UiElementHandle; typedef uint64_t UiStyleHandle; typedef uint64_t UiMenuTabHandle; +typedef uint64_t UiContextMenuHandle; typedef enum UiStyleScope { UI_SCOPE_PRELAUNCH = 0, /* the pre-launch menu */ @@ -54,18 +53,21 @@ typedef enum UiControlKind { UI_CONTROL_SELECT = 4, /* one of `options`; the value is the option index */ UI_CONTROL_COLOR = 5, /* RGB/RGBA color string with a picker */ UI_CONTROL_GROUP = 6, /* navigation row (on_pressed) */ - UI_CONTROL_FILE_PICKER = 7, /* file/folder picker with an opaque string location */ + /* Minor version 2 */ + UI_CONTROL_FILE_PICKER = 7, /* file/folder picker */ + /* Minor version 3 */ + UI_CONTROL_ICON_BUTTON = 8, /* icon button with a tooltip */ + UI_CONTROL_DROPDOWN = 9, /* dropdown select; 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, COLOR and FILE_PICKER = string. Float vars are not bindable; use - * callbacks. */ + /* The control reads and writes `config_var` (a ConfigService handle) directly. Persistence, + * change notifications and the modified indicator are bound automatically. The var type must + * match the control kind: TOGGLE = bool, NUMBER, SELECT and DROPDOWN = int, STRING, COLOR and + * FILE_PICKER = string. Float vars are not bindable; use callbacks. */ UI_BINDING_CONFIG_VAR = 1, } UiControlBinding; @@ -74,10 +76,12 @@ typedef enum UiStringSetMode { UI_STRING_SET_ON_CHANGE = 1, /* invokes `set` on every text change (e.g. while typing) */ } UiStringSetMode; -/* Tagged by the control's kind: TOGGLE reads bool_value, NUMBER and SELECT read int_value, STRING, - * COLOR and FILE_PICKER read 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. */ +/* + * Value for each type of control: + * - bool_value: TOGGLE + * - int_value: NUMBER, SELECT and DROPDOWN + * - string_value: STRING, COLOR and FILE_PICKER + */ typedef struct UiControlValue { uint32_t struct_size; bool bool_value; @@ -101,11 +105,11 @@ typedef struct UiControlDesc { /* 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 and GROUP */ + UiControlBinding binding; /* ignored for BUTTON, GROUP, and ICON_BUTTON */ ConfigVarHandle config_var; /* UI_BINDING_CONFIG_VAR */ - UiControlGetFn get; /* UI_BINDING_CALLBACKS (all kinds but BUTTON/GROUP) */ - UiControlSetFn set; /* UI_BINDING_CALLBACKS (all kinds but BUTTON/GROUP) */ - UiPressedFn on_pressed; /* BUTTON/GROUP only. Required for both. */ + UiControlGetFn get; /* UI_BINDING_CALLBACKS (all kinds but BUTTON/GROUP/ICON_BUTTON) */ + UiControlSetFn set; /* UI_BINDING_CALLBACKS (all kinds but BUTTON/GROUP/ICON_BUTTON) */ + UiPressedFn on_pressed; /* BUTTON/GROUP/ICON_BUTTON: required. */ UiPredicateFn is_disabled; /* optional */ /* Optional override for the modified indicator. CONFIG_VAR controls derive it from value != * default when this is NULL. */ @@ -119,9 +123,7 @@ typedef struct UiControlDesc { 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. */ + /* SELECT/DROPDOWN: option labels (plain text). At least one is required. */ const char* const* options; size_t option_count; int32_t max_length; /* STRING: maximum input length; < 1 means unlimited */ @@ -129,18 +131,71 @@ typedef struct UiControlDesc { const char* const* color_presets; size_t color_preset_count; bool color_alpha; /* COLOR: use RRGGBBAA values instead of RRGGBB */ - UiPredicateFn is_selected; /* BUTTON/GROUP: optional selected state */ + UiPredicateFn is_selected; /* BUTTON/GROUP/ICON_BUTTON: optional selected state */ UiStringSetMode string_set_mode; /* STRING: when to invoke the setter */ /* FILE_PICKER: optional file filters and folder selection mode. */ const FileFilter* file_filters; size_t file_filter_count; bool directory_mode; + const char* icon; /* ICON_BUTTON: supported Material Symbols name */ + /* DROPDOWN: option_count values (optional). Labels and flags are copied. */ + const bool* option_enabled; + /* Optional plain-text tooltip. ICON_BUTTON uses label if not specified. */ + const char* tooltip; } 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, NULL, 0u, false, NULL, \ - UI_STRING_SET_ON_COMMIT, NULL, 0u, false} + UI_STRING_SET_ON_COMMIT, NULL, 0u, false, NULL, NULL, NULL} + +typedef struct UiControlOption { + uint32_t struct_size; + const char* label; + bool enabled; +} UiControlOption; + +#define UI_CONTROL_OPTION_INIT {sizeof(UiControlOption), NULL, true} + +typedef void (*UiContextMenuActionFn)(ModContext* ctx, void* user_data); + +typedef struct UiContextMenuItem { + uint32_t struct_size; + const char* label; /* required, plain text */ + const char* icon; /* optional Material Symbols name */ + UiContextMenuActionFn on_pressed; /* NULL disables the item */ + void* user_data; + bool enabled; + bool selected; + bool destructive; + bool separator_before; +} UiContextMenuItem; + +#define UI_CONTEXT_MENU_ITEM_INIT \ + {sizeof(UiContextMenuItem), NULL, NULL, NULL, NULL, true, false, false, false} + +typedef struct UiContextMenuDesc { + uint32_t struct_size; + const UiContextMenuItem* items; /* at least one; copied */ + size_t item_count; +} UiContextMenuDesc; + +#define UI_CONTEXT_MENU_DESC_INIT {sizeof(UiContextMenuDesc), NULL, 0u} + +typedef enum UiRowAlign { + UI_ROW_ALIGN_START = 0, + UI_ROW_ALIGN_END = 1, + UI_ROW_ALIGN_CENTER = 2, + UI_ROW_ALIGN_SPACE_BETWEEN = 3, +} UiRowAlign; + +typedef struct UiRowDesc { + uint32_t struct_size; + UiRowAlign align; + bool wrap; +} UiRowDesc; + +#define UI_ROW_DESC_INIT {sizeof(UiRowDesc), UI_ROW_ALIGN_START, false} typedef uint64_t UiListHandle; @@ -302,7 +357,7 @@ typedef struct UiService { /* 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 + /* Content builders. `pane` is a panel, tab/dialog pane, or row 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)( @@ -374,6 +429,30 @@ typedef struct UiService { /* Replace all items in a list with a new set. */ ModResult (*list_set_items)( ModContext* ctx, UiListHandle list, const UiListItem* items, size_t item_count); + + /* Minor version 3 */ + + /* Horizontal container. parent must be a pane or row. */ + ModResult (*pane_add_row)( + ModContext* ctx, UiElementHandle parent, const UiRowDesc* desc, UiElementHandle* out_row); + + /* Update a control's label. */ + ModResult (*control_set_label)(ModContext* ctx, UiElementHandle control, const char* label); + /* ICON_BUTTON only. Unknown or empty icon names return MOD_INVALID_ARGUMENT. */ + ModResult (*control_set_icon)(ModContext* ctx, UiElementHandle control, const char* icon); + /* DROPDOWN only. Empty options disables the control. */ + ModResult (*control_set_options)(ModContext* ctx, UiElementHandle control, + const UiControlOption* options, size_t option_count); + ModResult (*control_set_tooltip)(ModContext* ctx, UiElementHandle control, const char* tooltip); + + ModResult (*elem_set_visible)(ModContext* ctx, UiElementHandle elem, bool visible); + /* Focus a visible, enabled element/container. MOD_UNAVAILABLE if it cannot receive focus. */ + ModResult (*elem_focus)(ModContext* ctx, UiElementHandle elem); + + /* Creates a context menu. Anchor must be a visible and enabled element. */ + ModResult (*context_menu_push)(ModContext* ctx, UiElementHandle anchor, + const UiContextMenuDesc* desc, UiContextMenuHandle* out_menu); + ModResult (*context_menu_close)(ModContext* ctx, UiContextMenuHandle menu); } UiService; MOD_DECLARE_SERVICE(UiService, svc_ui, UI_SERVICE_ID, UI_SERVICE_MAJOR, UI_SERVICE_MINOR); diff --git a/sdk/include/mods/svc/websocket.h b/sdk/include/mods/svc/websocket.h new file mode 100644 index 0000000000..abd36410ef --- /dev/null +++ b/sdk/include/mods/svc/websocket.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +#include +#endif + +#define WEBSOCKET_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "websocket" +#define WEBSOCKET_SERVICE_MAJOR 1u +#define WEBSOCKET_SERVICE_MINOR 0u + +/** Generational connection handle. Zero is never valid. */ +typedef uint64_t WebSocketHandle; + +/** Connection outcome. Callers must tolerate values added by later service minors. */ +typedef enum WebSocketError { + WEBSOCKET_ERROR_NONE = 0, + WEBSOCKET_ERROR_INVALID_URL = 1, + WEBSOCKET_ERROR_UNSUPPORTED_SCHEME = 2, + WEBSOCKET_ERROR_TIMEOUT = 3, + WEBSOCKET_ERROR_TOO_LARGE = 4, + WEBSOCKET_ERROR_CANCELED = 5, + WEBSOCKET_ERROR_NETWORK = 6, + WEBSOCKET_ERROR_PROTOCOL = 7, + WEBSOCKET_ERROR_HANDSHAKE = 8, +} WebSocketError; + +typedef enum WebSocketMessageKind { + WEBSOCKET_MESSAGE_TEXT = 0, + WEBSOCKET_MESSAGE_BINARY = 1, +} WebSocketMessageKind; + +typedef struct WebSocketConnectDesc { + uint32_t struct_size; + /** wss:// URL, or ws:// for localhost, 127.0.0.1, or [::1]. */ + const char* url; + /** Request headers. WebSocket handshake headers and User-Agent are reserved. */ + const HttpHeader* headers; + uint32_t header_count; + const char* const* protocols; + uint32_t protocol_count; + uint32_t connect_timeout_ms; + uint32_t close_timeout_ms; + uint32_t keepalive_interval_ms; + /** 0 defaults to 1 MiB. Maximum of 16 MiB. */ + size_t max_message_bytes; + /** Passed in every event. */ + void* user_data; +} WebSocketConnectDesc; + +#define WEBSOCKET_CONNECT_DESC_INIT \ + {sizeof(WebSocketConnectDesc), NULL, NULL, 0u, NULL, 0u, 0u, 0u, 0u, 0u, NULL} + +typedef enum WebSocketEventType { + WEBSOCKET_EVENT_NONE = 0, + WEBSOCKET_EVENT_OPEN = 1, + WEBSOCKET_EVENT_MESSAGE = 2, + WEBSOCKET_EVENT_CLOSED = 3, +} WebSocketEventType; + +typedef struct WebSocketEvent { + uint32_t struct_size; + WebSocketEventType type; + WebSocketHandle ws; + void* user_data; + + const char* protocol; + /** Handshake response headers for OPEN or a handshake-rejected CLOSED event. */ + const HttpHeader* headers; + uint32_t header_count; + + WebSocketMessageKind message_kind; + /** Valid until this mod's next poll_event call or deactivation. */ + const void* data; + size_t size; + + WebSocketError error; + /** Never NULL. Valid until this mod's next poll_event call or deactivation. */ + const char* error_message; + int32_t handshake_status; + uint16_t close_code; + const char* close_reason; +} WebSocketEvent; + +#define WEBSOCKET_EVENT_INIT \ + {sizeof(WebSocketEvent), WEBSOCKET_EVENT_NONE, 0u, NULL, "", NULL, 0u, WEBSOCKET_MESSAGE_TEXT, \ + NULL, 0u, WEBSOCKET_ERROR_NONE, "", 0, 0u, ""} + +typedef struct WebSocketService { + ServiceHeader header; + + /** Starts a connection. */ + ModResult (*connect)( + ModContext* ctx, const WebSocketConnectDesc* desc, WebSocketHandle* out_handle); + /** Returns MOD_OK and WEBSOCKET_EVENT_NONE when the queue is empty. */ + ModResult (*poll_event)(ModContext* ctx, WebSocketEvent* out_event); + /** Copies a message into the outbound queue. */ + ModResult (*send)(ModContext* ctx, WebSocketHandle ws, WebSocketMessageKind kind, + const void* data, size_t size); + /** Code 0 defaults to 1000; accepted: 1000, 1001, and 3000-4999. */ + ModResult (*close)(ModContext* ctx, WebSocketHandle ws, uint16_t code, const char* reason); +} WebSocketService; + +MOD_DECLARE_SERVICE(WebSocketService, svc_websocket, WEBSOCKET_SERVICE_ID, WEBSOCKET_SERVICE_MAJOR, + WEBSOCKET_SERVICE_MINOR); diff --git a/sdk/include/mods/svc/websocket.hpp b/sdk/include/mods/svc/websocket.hpp new file mode 100644 index 0000000000..57d8b37005 --- /dev/null +++ b/sdk/include/mods/svc/websocket.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mods::ws { + +struct Options { + std::string url; + std::vector headers; + std::vector protocols; + uint32_t connectTimeoutMs = 0; + uint32_t closeTimeoutMs = 0; + uint32_t keepaliveIntervalMs = 0; + size_t maxMessageBytes = 0; + void* userData = nullptr; +}; + +class Connection { +public: + Connection() = default; + Connection(WebSocketHandle handle, ModResult result) : mHandle{handle}, mResult{result} {} + ~Connection() { reset(); } + + Connection(const Connection&) = delete; + Connection& operator=(const Connection&) = delete; + Connection(Connection&& other) noexcept { *this = std::move(other); } + Connection& operator=(Connection&& other) noexcept { + if (this != &other) { + reset(); + mHandle = std::exchange(other.mHandle, 0); + mResult = other.mResult; + } + return *this; + } + + explicit operator bool() const { return mResult == MOD_OK && mHandle != 0; } + + ModResult result() const { return mResult; } + WebSocketHandle handle() const { return mHandle; } + + ModResult send(WebSocketMessageKind kind, std::span bytes) const { + return svc_websocket != nullptr && mHandle != 0 ? + svc_websocket->send(mod_ctx, mHandle, kind, bytes.data(), bytes.size()) : + MOD_UNAVAILABLE; + } + + ModResult send_text(std::string_view text) const { + return svc_websocket != nullptr && mHandle != 0 ? + svc_websocket->send( + mod_ctx, mHandle, WEBSOCKET_MESSAGE_TEXT, text.data(), text.size()) : + MOD_UNAVAILABLE; + } + + ModResult send_binary(std::span bytes) const { + return send(WEBSOCKET_MESSAGE_BINARY, bytes); + } + + ModResult close(uint16_t code = 1000, std::string_view reason = {}) { + if (svc_websocket == nullptr || mHandle == 0) { + return MOD_UNAVAILABLE; + } + const std::string reasonText{reason}; + mResult = svc_websocket->close(mod_ctx, mHandle, code, reasonText.c_str()); + if (mResult == MOD_OK) { + mHandle = 0; + } + return mResult; + } + + void detach() { mHandle = 0; } + +private: + void reset() { + if (mHandle != 0) { + (void)close(1001, "Connection owner released"); + mHandle = 0; + } + } + + WebSocketHandle mHandle = 0; + ModResult mResult = MOD_UNAVAILABLE; +}; + +inline Connection connect(const Options& options) { + if (svc_websocket == nullptr || options.headers.size() > std::numeric_limits::max() || + options.protocols.size() > std::numeric_limits::max()) + { + return {0, svc_websocket == nullptr ? MOD_UNAVAILABLE : MOD_INVALID_ARGUMENT}; + } + + std::vector headers; + headers.reserve(options.headers.size()); + for (const auto& header : options.headers) { + headers.push_back({.name = header.name.c_str(), .value = header.value.c_str()}); + } + std::vector protocols; + protocols.reserve(options.protocols.size()); + for (const auto& protocol : options.protocols) { + protocols.push_back(protocol.c_str()); + } + + WebSocketConnectDesc desc = WEBSOCKET_CONNECT_DESC_INIT; + desc.url = options.url.c_str(); + desc.headers = headers.empty() ? nullptr : headers.data(); + desc.header_count = static_cast(headers.size()); + desc.protocols = protocols.empty() ? nullptr : protocols.data(); + desc.protocol_count = static_cast(protocols.size()); + desc.connect_timeout_ms = options.connectTimeoutMs; + desc.close_timeout_ms = options.closeTimeoutMs; + desc.keepalive_interval_ms = options.keepaliveIntervalMs; + desc.max_message_bytes = options.maxMessageBytes; + desc.user_data = options.userData; + + WebSocketHandle handle = 0; + const ModResult result = svc_websocket->connect(mod_ctx, &desc, &handle); + return {handle, result}; +} + +struct Event { + WebSocketEventType type = WEBSOCKET_EVENT_NONE; + WebSocketHandle handle = 0; + void* userData = nullptr; + std::string_view protocol; + std::span headers; + WebSocketMessageKind messageKind = WEBSOCKET_MESSAGE_TEXT; + std::span data; + WebSocketError error = WEBSOCKET_ERROR_NONE; + std::string_view message; + int handshakeStatus = 0; + uint16_t closeCode = 0; + std::string_view closeReason; +}; + +inline bool poll(Event& out) { + out = {}; + if (svc_websocket == nullptr) { + return false; + } + WebSocketEvent raw = WEBSOCKET_EVENT_INIT; + if (svc_websocket->poll_event(mod_ctx, &raw) != MOD_OK || raw.type == WEBSOCKET_EVENT_NONE) { + return false; + } + out.type = raw.type; + out.handle = raw.ws; + out.userData = raw.user_data; + out.protocol = raw.protocol != nullptr ? raw.protocol : ""; + if (raw.headers != nullptr && raw.header_count != 0) { + out.headers = {raw.headers, raw.header_count}; + } + out.messageKind = raw.message_kind; + if (raw.data != nullptr && raw.size != 0) { + out.data = {static_cast(raw.data), raw.size}; + } + out.error = raw.error; + out.message = raw.error_message != nullptr ? raw.error_message : ""; + out.handshakeStatus = raw.handshake_status; + out.closeCode = raw.close_code; + out.closeReason = raw.close_reason != nullptr ? raw.close_reason : ""; + return true; +} + +} // namespace mods::ws diff --git a/sdk/include/mods/svc/window.h b/sdk/include/mods/svc/window.h index 7cee8a5abe..843e388ed8 100644 --- a/sdk/include/mods/svc/window.h +++ b/sdk/include/mods/svc/window.h @@ -8,7 +8,7 @@ #include -#define WINDOW_SERVICE_ID "dev.twilitrealm.dusklight.window" +#define WINDOW_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "window" #define WINDOW_SERVICE_MAJOR 1u #define WINDOW_SERVICE_MINOR 0u diff --git a/sdk/luau/dusklight.d.luau b/sdk/luau/dusklight.d.luau new file mode 100644 index 0000000000..9d6a7ddad0 --- /dev/null +++ b/sdk/luau/dusklight.d.luau @@ -0,0 +1,258 @@ +--!strict + +export type IntegerInput = integer | number +export type ConfigValue = boolean | integer | number | string +export type ConfigVar = { + get: (self: ConfigVar) -> T, + set: (self: ConfigVar, value: T) -> (), + subscribe: (self: ConfigVar, callback: (value: T, previous: T) -> ()) -> ConfigSubscription, + unregister: (self: ConfigVar) -> (), +} +export type ConfigSubscription = { + unsubscribe: (self: ConfigSubscription) -> (), +} +export type ConfigDesc = { + name: string, + type: "bool" | "int" | "float" | "string", + default: ConfigValue?, +} +export type ConfigModule = { + register: (desc: ConfigDesc) -> ConfigVar, + subscribe: (variable: ConfigVar, callback: (value: T, previous: T) -> ()) -> ConfigSubscription, +} + +export type OverlayHandle = { remove: (self: OverlayHandle) -> () } +export type OverlayModule = { + add_file: (discPath: string, bundlePath: string) -> OverlayHandle, + add_buffer: (discPath: string, data: string) -> OverlayHandle, +} +export type TextureHandle = { unregister: (self: TextureHandle) -> () } +export type TextureKey = { + texture_hash: integer, + tlut_hash: integer?, + width: IntegerInput, + height: IntegerInput, + gx_format: IntegerInput, + has_tlut: boolean?, +} +export type TextureData = { + data: string, + width: IntegerInput?, + height: IntegerInput?, + mip_count: IntegerInput?, + gx_format: IntegerInput?, +} +export type TextureModule = { + hash_wildcard: integer, + tlut_wildcard: integer, + register_file: (bundlePath: string) -> TextureHandle, + register_data: (key: TextureKey, data: TextureData) -> TextureHandle, +} + +export type UiRowDesc = {align: ("start" | "center" | "end" | "space_between")?, wrap: boolean?} +export type UiControlOption = {label: string, enabled: boolean?} +export type UiContextMenuItem = { + label: string, icon: string?, on_pressed: (() -> ())?, enabled: boolean?, + selected: boolean?, destructive: boolean?, separator_before: boolean?, +} +export type UiContextMenu = {close: (self: UiContextMenu) -> ()} +export type UiElement = { + add_control: (self: UiElement, desc: UiControlDesc) -> UiElement, + add_group: (self: UiElement, target: UiElement, desc: UiGroupDesc) -> UiElement, + add_list: (self: UiElement, desc: UiListDesc) -> UiList, + add_progress: (self: UiElement, value: number) -> UiElement, + add_rml: (self: UiElement, rml: string) -> UiElement, + add_row: (self: UiElement, desc: UiRowDesc) -> UiElement, + add_section: (self: UiElement, title: string) -> (), + add_text: (self: UiElement, text: string) -> UiElement, + focus: (self: UiElement) -> boolean, + set_class: (self: UiElement, name: string, active: boolean) -> (), + set_icon: (self: UiElement, icon: string) -> (), + set_label: (self: UiElement, label: string) -> (), + set_options: (self: UiElement, options: {UiControlOption}) -> (), + set_progress: (self: UiElement, value: number) -> (), + set_rml: (self: UiElement, rml: string) -> (), + set_text: (self: UiElement, text: string) -> (), + set_tooltip: (self: UiElement, text: string?) -> (), + set_visible: (self: UiElement, visible: boolean) -> (), +} +export type UiControlDesc = { + kind: "button" | "toggle" | "number" | "string" | "select" | "color" | "group" | "file_picker" | "icon_button" | "dropdown", + label: string, + help_rml: string?, + config_var: ConfigVar?, + get: (() -> ConfigValue)?, + set: ((value: ConfigValue) -> ())?, + on_pressed: (() -> ())?, + is_disabled: (() -> boolean)?, + is_modified: (() -> boolean)?, + is_selected: (() -> boolean)?, + min: IntegerInput?, + max: IntegerInput?, + step: IntegerInput?, + prefix: string?, + suffix: string?, + options: {string}?, + max_length: IntegerInput?, + color_presets: {string}?, + color_alpha: boolean?, + string_set_mode: ("commit" | "change")?, + file_filters: {{name: string, pattern: string}}?, + directory_mode: boolean?, + icon: string?, + tooltip: string?, + option_enabled: {boolean}?, +} +export type UiGroupDesc = { label: string, build: (pane: UiElement) -> () } +export type UiListItem = { key: IntegerInput, label: string } +export type UiListDesc = { + items: {UiListItem}?, + on_pressed: (list: UiList, key: integer) -> (), + is_selected: ((list: UiList, key: integer) -> boolean)?, + is_disabled: ((list: UiList, key: integer) -> boolean)?, +} +export type UiList = { set_items: (self: UiList, items: {UiListItem}) -> () } +export type UiWindow = { close: (self: UiWindow) -> () } +export type UiDialog = { + close: (self: UiDialog) -> (), + set_body: (self: UiDialog, rml: string) -> (), + set_icon: (self: UiDialog, icon: string) -> (), +} +export type UiStyle = { unregister: (self: UiStyle) -> () } +export type UiMenuTab = { unregister: (self: UiMenuTab) -> () } +export type UiTabDesc = { + title: string, + build: (window: UiWindow, left: UiElement, right: UiElement) -> (), + update: (() -> ())?, +} +export type UiModule = { + context_menu_push: (anchor: UiElement, desc: {items: {UiContextMenuItem}}) -> UiContextMenu, + register_mods_panel: (desc: {build: (panel: UiElement) -> (), update: (() -> ())?}) -> (), + window_push: (desc: {tabs: {UiTabDesc}, rcss: string?, on_closed: ((window: UiWindow) -> ())?}) -> UiWindow, + dialog_push: (desc: { + title: string, + body_rml: string, + variant: ("normal" | "warning" | "danger")?, + icon: string?, + actions: {{label: string, on_pressed: ((dialog: UiDialog) -> ())?, keep_open: boolean?, is_disabled: (() -> boolean)?}}, + on_dismiss: ((dialog: UiDialog) -> ())?, + build: ((pane: UiElement) -> ())?, + }) -> UiDialog, + is_any_document_visible: () -> boolean, + register_styles: (scope: string, rcss: string) -> UiStyle, + register_styles_file: (scope: string, path: string) -> UiStyle, + register_menu_tab: (desc: {label: string, on_selected: () -> ()}) -> UiMenuTab, + push_toast: (desc: {type: string?, title_rml: string?, body_rml: string?, duration_ms: IntegerInput?}) -> (), + get_clipboard_text: () -> string, + set_clipboard_text: (text: string) -> (), +} + +export type LogModule = { + write: (level: "trace" | "debug" | "info" | "warn" | "error", message: string) -> (), + trace: (message: string) -> (), + debug: (message: string) -> (), + info: (message: string) -> (), + warn: (message: string) -> (), + error: (message: string) -> (), +} +export type HostModule = { + version: string, + mod_id: () -> string, + mod_name: () -> string, + mod_version: () -> string, + mod_dir: () -> string, + data_dir: () -> string, + on_update: (callback: () -> ()) -> (), + on_shutdown: (callback: () -> ()) -> (), + fail: (message: string) -> never, +} +export type ResourceModule = { load: (relativePath: string) -> string } + +-- +-- audio_res +-- + +export type AudioWaveFormat = "adpcm4" | "adpcm2" | "pcm8" | "pcm16" + +export type AudioRawWave = { + format: AudioWaveFormat, + sample_rate: number, + sample_value_last: number | nil, + sample_value_penult: number | nil +} + +export type AudioWaveInfo = { + base_key: number | nil, + loop: boolean | nil, + loop_start_sample: number | nil, + loop_end_sample: number | nil, + raw_wave: AudioRawWave | nil +} + +export type AudioWaveHandle = { + unregister: (self: AudioWaveHandle) -> (), +} + +export type AudioWaveBank = "sound_effects" | "music_samples" + +export type AudioSoundTableEffectInfo = { + priority: number, + volume: number, + pitch: number, + always_max_priority: boolean, + ignore_distance_volume: boolean, + ignore_distance_fx_mix: boolean, + ignore_pan: boolean, + ignore_dolby: boolean, + random_volume: number, + random_pitch: number, + doppler_power: number, + volume_dist_class: number, + clamp_min_volume: boolean, + cull_at_max_distance: boolean, +} + +export type SoundEffectCategory = "system_se" | "player_voice" | "player_se" | "footnote_se" | "collision_se" | "chara_voice" | "chara_se" | "enemy_se" | "object_se" | "env_se" + +export type AudioSoundTableHandle = { + unregister: (self: AudioSoundTableHandle) -> () +} + +export type StreamPan = "center" | "left" | "right" + +export type AudioSoundTableStreamInfo = { + priority: number, + volume: number, + pan_parameters: {StreamPan}, -- max STREAM_MAX_CHILDREN elems + stop_on_scene_change: boolean, +} + +export type AudioResModule = { + -- wsys + DEFAULT_KEY: number, + default_wave_info: () -> AudioWaveInfo, + replace_wave: (bank: AudioWaveBank, wave_id: number, file: string, info: AudioWaveInfo | nil) -> AudioWaveHandle, + add_wave: (bank: AudioWaveBank, file: string, info: AudioWaveInfo | nil) -> (number, AudioWaveHandle), + + -- bst + default_effect_info: () -> AudioSoundTableEffectInfo, + replace_sound_table_effect: (category: SoundEffectCategory, effect_id: number, info: AudioSoundTableEffectInfo | nil) -> AudioSoundTableHandle, + add_sound_table_effect: (category: SoundEffectCategory, info: AudioSoundTableEffectInfo | nil) -> (number, AudioSoundTableHandle), + + STREAM_MAX_CHILDREN: number, + default_stream_info: () -> AudioSoundTableStreamInfo, + replace_sound_table_stream: (stream_id: number, file_path: string, info: AudioSoundTableStreamInfo | nil) -> AudioSoundTableHandle, + add_sound_table_stream: (file_path: string, info: AudioSoundTableStreamInfo | nil) -> (number, AudioSoundTableHandle), +} + + + +declare function require(path: "dusklight.log"): LogModule +declare function require(path: "dusklight.host"): HostModule +declare function require(path: "dusklight.config"): ConfigModule +declare function require(path: "dusklight.resource"): ResourceModule +declare function require(path: "dusklight.overlay"): OverlayModule +declare function require(path: "dusklight.texture"): TextureModule +declare function require(path: "dusklight.ui"): UiModule +declare function require(path: "dusklight.audio_res"): AudioResModule +declare function require(path: string): any diff --git a/sdk/luau/template/mod.json b/sdk/luau/template/mod.json new file mode 100644 index 0000000000..85a13a6459 --- /dev/null +++ b/sdk/luau/template/mod.json @@ -0,0 +1,8 @@ +{ + "id": "com.example.my_script_mod", + "name": "My Script Mod", + "version": "1.0.0", + "author": "Your Name", + "description": "A Luau script mod", + "runtime": "dev.twilitrealm.luau@1.0" +} diff --git a/sdk/luau/template/res/main.luau b/sdk/luau/template/res/main.luau new file mode 100644 index 0000000000..102988c164 --- /dev/null +++ b/sdk/luau/template/res/main.luau @@ -0,0 +1,8 @@ +local log = require("dusklight.log") +local host = require("dusklight.host") + +log.info("My Script Mod initialized") + +host.on_shutdown(function() + log.info("My Script Mod shutting down") +end) diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index dda1a4b2d0..831d356d2f 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -36,14 +36,14 @@ Z2Audible::Z2Audible(const JGeometry::TVec3& pos, const JGeometry::TVec3= 0); - JUT_ASSERT(81, index < 1); + JUT_ASSERT(81, index < 1); // Z2_AUDIO_PLAYERS return &mChannel[index].mParams; } void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundParams& inParams, int index) { JUT_ASSERT(89, index >= 0); - JUT_ASSERT(90, index < 1); + JUT_ASSERT(90, index < 1); // Z2_AUDIO_PLAYERS Z2AudibleChannel* channel = &mChannel[index]; - u8 iStack_94 = (mParam.field_0x0.half.f1 & 0xf00) >> 8; - if (iStack_94 == 0) { + u8 volSomething = (mParam.field_0x0.half.f1 & SOUND_VOL_SOMETHING_MASK_SHIFTED) >> 8; + if (volSomething == 0) { channel->mParams.combine(outParams, inParams); return; } @@ -91,7 +91,7 @@ void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundPa f32 dVar9 = inParams.mVolume * local_b8.length(); f32 dVar10 = 1.0f; - f32 dVar12 = Z2Calc::linearTransform(iStack_94, 0.0f, 15.0f, 1.0f, 0.3f, true); + f32 dVar12 = Z2Calc::linearTransform(volSomething, 0.0f, 15.0f, 1.0f, 0.3f, true); if (inParams.mVolume > dVar12 && dVar9 > 0.001f) { dVar10 = Z2Calc::getParamByExp(dVar9, 0.3f, 0.001f, 0.1f, dVar12, 1.0f, Z2Calc::CURVE_POSITIVE); @@ -141,18 +141,18 @@ void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundPa Z2AudibleChannel* Z2Audible::getChannel(int index) { JUT_ASSERT(220, index >= 0); - JUT_ASSERT(221, index < 1); + JUT_ASSERT(221, index < 1); // Z2_AUDIO_PLAYERS return &mChannel[index]; } u32 Z2Audible::getDistVolBit() { u16 uVar1 = getAudibleParam()->field_0x0.half.f1; if (uVar1 != 0) { - if ((uVar1 & 7) != 0) { - return uVar1 & 7; + if ((uVar1 & SOUND_VOL_DIST_BIT_MASK_SHIFTED) != 0) { + return uVar1 & SOUND_VOL_DIST_BIT_MASK_SHIFTED; } - if ((uVar1 & 0x70) != 0) { - return ((int)(uVar1 & 0x70) >> 4) + 7; + if ((uVar1 & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED) != 0) { + return ((int)(uVar1 & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED) >> 4) + 7; } } @@ -166,15 +166,15 @@ Z2Audience3DSetting::Z2Audience3DSetting() { } void Z2Audience3DSetting::init() { - field_0x40 = Z2Param::MIN_DISTANCE_VOLUME; - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; - field_0x54 = Z2Param::DISTANCE_FX_PARAM; - field_0x50 = 0.0f; - field_0x4c = Z2Param::DOLBY_CENTER_VALUE; - field_0x58 = 0.5f; - field_0x64 = 0xff; - field_0x5c = Z2Param::SONIC_SPEED; + mMinDistanceVolume = Z2Param::MIN_DISTANCE_VOLUME; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mMaxDistanceFxMix = Z2Param::DISTANCE_FX_PARAM; + mMinDistanceFxMix = 0.0f; + mDolbyCenterValue = Z2Param::DOLBY_CENTER_VALUE; + mPanFactor = 0.5f; + mMaxDistancePriority = 0xff; + mSonicSpeed = Z2Param::SONIC_SPEED; field_0x60 = 1.5f; initVolumeDist(); @@ -184,8 +184,8 @@ void Z2Audience3DSetting::init() { void Z2Audience3DSetting::initVolumeDist() { if (mVolumeDistInit) return; - field_0x0[0] = Z2Param::DISTANCE_MAX; - field_0x3c = Z2Param::MAX_VOLUME_DISTANCE; + mDistanceMaxes[0] = Z2Param::DISTANCE_MAX; + mMaxVolumeDistance = Z2Param::MAX_VOLUME_DISTANCE; calcVolumeFactorAll(); calcPriorityFactorAll(); @@ -197,12 +197,12 @@ void Z2Audience3DSetting::initVolumeDist() { static f32 cNearFarRatio = Z2Param::MAX_VOLUME_DISTANCE / Z2Param::DISTANCE_MAX; void Z2Audience3DSetting::updateVolumeDist(f32 param_0) { - field_0x0[0] = param_0; + mDistanceMaxes[0] = param_0; if (param_0 > Z2Param::DISTANCE_MAX) { - field_0x3c = cNearFarRatio * param_0; + mMaxVolumeDistance = cNearFarRatio * param_0; } else { - field_0x3c = Z2Param::MAX_VOLUME_DISTANCE; + mMaxVolumeDistance = Z2Param::MAX_VOLUME_DISTANCE; } calcVolumeFactorAll(); @@ -214,29 +214,29 @@ void Z2Audience3DSetting::updateVolumeDist(f32 param_0) { void Z2Audience3DSetting::initDolbyDist() { if (!mDolbyDistInit) { - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; - field_0x68 = -field_0x4c / field_0x44; - field_0x6c = (1.0f - field_0x4c) / field_0x48; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + field_0x68 = -mDolbyCenterValue / mDolbyFrontDistanceMax; + field_0x6c = (1.0f - mDolbyCenterValue) / mDolbyBehindDistanceMax; mDolbyDistInit = true; } } void Z2Audience3DSetting::updateDolbyDist(f32 param_0, f32 param_1) { if (param_0 > -1.0f * Z2Param::DOLBY_FLONT_DISTANCE_MAX) { - field_0x44 = -1.0f * param_0; + mDolbyFrontDistanceMax = -1.0f * param_0; } else { - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; } - if (param_1 < 2.0f * field_0x44) { - param_1 = 2.0f * field_0x44; + if (param_1 < 2.0f * mDolbyFrontDistanceMax) { + param_1 = 2.0f * mDolbyFrontDistanceMax; } if (param_1 > Z2Param::DOLBY_BEHIND_DISTANCE_MAX) { - field_0x48 = param_1; + mDolbyBehindDistanceMax = param_1; } else { - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; } mDolbyDistInit = false; @@ -254,28 +254,28 @@ Z2AudioCamera::Z2AudioCamera() { } void Z2AudioCamera::init() { - JGeometry::TPosition3f32 aTStack_38; - aTStack_38.identity(); + JGeometry::TPosition3f32 viewMatrix; + viewMatrix.identity(); JGeometry::TVec3 VStack_44; VStack_44.set(100000.0f, 100000.0f, 100000.0f); - setCameraState(aTStack_38, *(Vec*)&VStack_44, true); + setCameraState(viewMatrix, *(Vec*)&VStack_44, true); } -void Z2AudioCamera::setCameraState(f32 const (*param_0)[4], Vec& pos, bool param_2) { - field_0x0.set(param_0); - if (param_2) { +void Z2AudioCamera::setCameraState(f32 const (*viewMatrix)[4], Vec& pos, bool initial) { + mViewMatrix.set(viewMatrix); + if (initial) { mPos.set(pos); - field_0x48.set(mPos); + mLastPos.set(mPos); mVel.zero(); } else { - field_0x48.set(mPos); + mLastPos.set(mPos); mPos.set(pos); - mVel.sub(mPos, field_0x48); + mVel.sub(mPos, mLastPos); } } -void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f32 param_3, - f32 param_4, bool param_5, bool param_6) { +void Z2AudioCamera::setCameraState(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f32 param_3, + f32 param_4, bool param_5, bool initial) { JGeometry::TVec3 aTStack_c0; VECSubtract(¶m_2, &pos, (Vec*)&aTStack_c0); mCamDist = aTStack_c0.length(); @@ -312,7 +312,7 @@ void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f3 Z2GetAudience()->getSetting()->updateDolbyDist(mCamDist, mCamDist); } } else { - Vec aTStack_cc = {param_0[0][0], param_0[0][1], param_0[0][2]}; + Vec aTStack_cc = {viewMatrix[0][0], viewMatrix[0][1], viewMatrix[0][2]}; Mtx rotMtx; MTXRotAxisRad(rotMtx, &aTStack_cc, (M_PI / 180.0f) * (-1.0f * dVar10)); JGeometry::TVec3 aTStack_d8; @@ -324,7 +324,7 @@ void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f3 } } - setCameraState(param_0, pos, param_6); + setCameraState(viewMatrix, pos, initial); } void Z2AudioCamera::convertAbsToRel(Z2Audible* audible, int channelNum) { @@ -337,17 +337,17 @@ void Z2AudioCamera::convertAbsToRel(Z2Audible* audible, int channelNum) { return; } - Z2AudibleRelPos* relPos = &channel->field_0x14; - MTXMultVec(field_0x0, (Vec*)&audible->getPos(), (Vec*)&relPos->field_0x00); - relPos->field_0xC = relPos->field_0x00.length(); + Z2AudibleRelPos* relPos = &channel->mRelPos; + MTXMultVec(mViewMatrix, (Vec*)&audible->getPos(), (Vec*)&relPos->mCameraRelative); + relPos->mTrueDistance = relPos->mCameraRelative.length(); - JGeometry::TVec3 aTStack_38(relPos->field_0x00); + JGeometry::TVec3 aTStack_38(relPos->mCameraRelative); aTStack_38.z += mVolCenterZ; - relPos->field_0x10 = aTStack_38.length(); + relPos->mCenterDistance = aTStack_38.length(); } bool Z2AudioCamera::convertAbsToRel(Vec& src, Vec* dst) const { - MTXMultVec(field_0x0, &src, dst); + MTXMultVec(mViewMatrix, &src, dst); return isInSight(*dst); } @@ -395,7 +395,7 @@ void Z2SpotMic::calcVolumeFactor(int camID) { void Z2SpotMic::setMicState(Z2AudioCamera* camera, int camID) { JUT_ASSERT(622, camID >= 0); - JUT_ASSERT(623, camID < 1); + JUT_ASSERT(623, camID < 1); // Z2_AUDIO_PLAYERS if (mMicOn && mPosPtr != 0 && camera != NULL) { clearMicState(camID); @@ -406,8 +406,8 @@ void Z2SpotMic::setMicState(Z2AudioCamera* camera, int camID) { aVStack_58.z += camera->getVolCenterZ(); f32 dVar10 = aVStack_58.length(); - f32 dVar11 = Z2GetAudience()->getSetting()->field_0x0[0]; - f32 dVar12 = Z2GetAudience()->getSetting()->field_0x3c; + f32 dVar11 = Z2GetAudience()->getSetting()->mDistanceMaxes[0]; + f32 dVar12 = Z2GetAudience()->getSetting()->mMaxVolumeDistance; if (dVar10 > dVar11) { field_0x18[camID] = field_0xc; } else { @@ -443,21 +443,21 @@ f32 Z2SpotMic::calcMicDist(Z2Audible* audible) { return aTStack_1c.length(); } -u32 Z2SpotMic::calcMicPriority(f32 param_0) { - if (param_0 > field_0x4) { - return Z2GetAudience()->getSetting()->field_0x64; +u32 Z2SpotMic::calcMicPriority(f32 micDistance) { + if (micDistance > field_0x4) { + return Z2GetAudience()->getSetting()->mMaxDistancePriority; } - if (param_0 < field_0x0) { + if (micDistance < field_0x0) { return 0; } - return field_0x1c * (param_0 - field_0x0); + return field_0x1c * (micDistance - field_0x0); } f32 Z2SpotMic::calcMicVolume(f32 param_0, int camID, f32 param_2) { JUT_ASSERT(687, camID >= 0); - JUT_ASSERT(688, camID < 1); + JUT_ASSERT(688, camID < 1); // Z2_AUDIO_PLAYERS if (mMicOn == false) { return param_2; @@ -497,7 +497,7 @@ f32 Z2SpotMic::calcMicVolume(f32 param_0, int camID, f32 param_2) { } Z2Audience::Z2Audience() : JASGlobalInstance(true), field_0x4(1.0f), field_0x8(0x7f) { - mNumPlayers = 1; + mNumPlayers = Z2_AUDIO_PLAYERS; mUsingOffMicVol = false; mAudioCamera[0].init(); mAudioCamera[0].setMainCamera(true); @@ -512,24 +512,25 @@ bool Z2Audience::isActive() const { return Z2Audible::getTotalMemCount() != Z2Audible::getFreeMemCount(); } -void Z2Audience::setAudioCamera(f32 (*param_0)[4], Vec& pos, Vec& param_2, f32 param_3, - f32 param_4, bool param_5, int camID, bool param_7) { +void Z2Audience::setAudioCamera(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f32 param_3, + f32 param_4, bool param_5, int camID, bool initial) { JUT_ASSERT(687, camID >= 0); JUT_ASSERT(688, camID < mNumPlayers); - mAudioCamera[camID].setCameraState(param_0, pos, param_2, param_3, param_4, param_5, param_7); + mAudioCamera[camID].setCameraState(viewMatrix, pos, param_2, param_3, param_4, param_5, initial); mLinkMic->setMicState(&mAudioCamera[camID], camID); } JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID soundID, - const JGeometry::TVec3* param_2, u32 param_3) { + const JGeometry::TVec3* param_2, u32 param_3 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const* replacement)) { u32 channelNum = param_3 | ~((1 << mNumPlayers) + -1); if (channelNum == 0xFFFFFFFF) { JUT_WARN(826, "%s", "You masked all audible channels !"); return NULL; } - JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID); - bool x = params.field_0x0.bytes.b0_0 != 0; + JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID IF_DUSK_ARG(replacement)); + bool x = params.field_0x0.bytes.mDopplerPower != 0; Z2Audible* audible = JKR_NEW Z2Audible(pos, param_2, channelNum, x); if (audible == NULL) { @@ -543,13 +544,13 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID if (channel != NULL) { u32 distVolBit = audible->getDistVolBit(); mAudioCamera[i].convertAbsToRel(audible, i); - calcDeltaPriority_(channel->field_0x14.field_0x10, distVolBit, false); + calcDeltaPriority_(channel->mRelPos.mCenterDistance, distVolBit, false); } } - for (int i = 0; i < 1; i++) { + for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) { if (mSpotMic[i].isOn()) { - audible->field_0x64[i] = mSpotMic[i].calcMicDist(audible); + audible->mMicDistances[i] = mSpotMic[i].calcMicDist(audible); } } @@ -569,7 +570,7 @@ Z2Audible::~Z2Audible() {} u32 Z2Audience::calcPriority(JAIAudible* audible) { Z2Audible* Z2audible = (Z2Audible*)audible; - if (!Z2audible->getAudibleParam()->field_0x0.bytes.b0_4) { + if (!Z2audible->getAudibleParam()->field_0x0.bytes.mCalculatePriority) { for (int i = 0; i < mNumPlayers; i++) { mAudioCamera[i].convertAbsToRel(Z2audible, i); } @@ -584,17 +585,17 @@ u32 Z2Audience::calcPriority(JAIAudible* audible) { mAudioCamera[i].convertAbsToRel(Z2audible, i); u32 distvolBit = Z2audible->getDistVolBit(); const JAUAudibleParam* puVar1 = Z2audible->getAudibleParam(); - deltaPriority[i] = calcDeltaPriority_(channel->field_0x14.field_0x10, distvolBit, puVar1->field_0x0.bytes.b0_7); + deltaPriority[i] = calcDeltaPriority_(channel->mRelPos.mCenterDistance, distvolBit, puVar1->field_0x0.bytes.mCullAtMaxDistance); if (deltaPriority[i] < rv) { rv = deltaPriority[i]; } } } - for (int i = 0; i < 1; i++) { + for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) { if (mSpotMic[i].isOn()) { - Z2audible->field_0x64[i] = mSpotMic[i].calcMicDist(Z2audible); - u32 micPriority = mSpotMic[i].calcMicPriority(Z2audible->field_0x64[i]); + Z2audible->mMicDistances[i] = mSpotMic[i].calcMicDist(Z2audible); + u32 micPriority = mSpotMic[i].calcMicPriority(Z2audible->mMicDistances[i]); if (micPriority < rv) { rv = micPriority; } @@ -619,31 +620,31 @@ void Z2Audience::mixChannelOut(const JASSoundParams& outParams, JAIAudible* audi return; } - JASSoundParams local_60; + JASSoundParams calcParams; u32 distVolBit = Z2audible->getDistVolBit(); - if (Z2audible->getAudibleParam()->field_0x0.bytes.b0_5) { - local_60.mVolume = calcVolume_(channel->field_0x14.field_0x10, distVolBit); - f32 dVar9 = mLinkMic->calcMicVolume(Z2audible->field_0x64[0], channelNum, local_60.mVolume); + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcDistanceVolume) { + calcParams.mVolume = calcVolume_(channel->mRelPos.mCenterDistance, distVolBit); + f32 dVar9 = mLinkMic->calcMicVolume(Z2audible->mMicDistances[0], channelNum, calcParams.mVolume); if (dVar9 < 0.0f) { - dVar9 = calcOffMicSound(local_60.mVolume); + dVar9 = calcOffMicSound(calcParams.mVolume); } - local_60.mVolume = dVar9; + calcParams.mVolume = dVar9; JAUAudibleParam* params = Z2audible->getAudibleParam(); - if ((params->field_0x0.bytes.b1_2_7 & 8) && (local_60.mVolume <= 0.2f)) { - local_60.mVolume = 0.2f; + if ((params->field_0x0.bytes.mClampMinVolume & 8) && (calcParams.mVolume <= 0.2f)) { + calcParams.mVolume = 0.2f; } } else { - local_60.mVolume = 1.0f; + calcParams.mVolume = 1.0f; } - if (Z2audible->getAudibleParam()->field_0x0.bytes.b1_1 && mNumPlayers == 1) { - local_60.mDolby = calcRelPosDolby(*(Vec*)&channel->field_0x14.field_0x00, channelNum); + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcDolby && mNumPlayers == 1) { + calcParams.mDolby = calcRelPosDolby(*(Vec*)&channel->mRelPos.mCameraRelative, channelNum); } else { - local_60.mDolby = 0.5f; + calcParams.mDolby = 0.5f; } - if (Z2audible->getAudibleParam()->field_0x0.bytes.b1_0) { + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcPan) { if (mNumPlayers > 2) { f32 fVar1; if (channelNum & 1) { @@ -651,24 +652,24 @@ void Z2Audience::mixChannelOut(const JASSoundParams& outParams, JAIAudible* audi } else { fVar1 = 0.0f; } - local_60.mPan = fVar1; + calcParams.mPan = fVar1; } else { - local_60.mPan = calcRelPosPan(*(Vec*)&channel->field_0x14.field_0x00, channelNum); + calcParams.mPan = calcRelPosPan(*(Vec*)&channel->mRelPos.mCameraRelative, channelNum); } } else { - local_60.mPan = 0.5f; + calcParams.mPan = 0.5f; } - local_60.mPitch = calcPitch_(channel, Z2audible, &mAudioCamera[channelNum]); - f32 dVar9; - if (Z2audible->getAudibleParam()->field_0x0.bytes.b0_6) { - dVar9 = calcFxMix_(channel->field_0x14.field_0xC, distVolBit); + calcParams.mPitch = calcPitch_(channel, Z2audible, &mAudioCamera[channelNum]); + f32 fxMix; + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcFxMix) { + fxMix = calcFxMix_(channel->mRelPos.mTrueDistance, distVolBit); } else { - dVar9 = 0.0f; + fxMix = 0.0f; } - local_60.mFxMix = dVar9; - local_60.clamp(); - Z2audible->setOuterParams(outParams, local_60, channelNum); + calcParams.mFxMix = fxMix; + calcParams.clamp(); + Z2audible->setOuterParams(outParams, calcParams, channelNum); } @@ -688,18 +689,18 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) { aTStack_3c.y *= 1.5f; f32 len = aTStack_3c.length(); - if (len > mSetting.field_0x0[0] * param_1) { - return mSetting.field_0x40; + if (len > mSetting.mDistanceMaxes[0] * param_1) { + return mSetting.mMinDistanceVolume; } - if (len < mSetting.field_0x3c) { + if (len < mSetting.mMaxVolumeDistance) { return 1.0f; } return JGeometry::TUtil::clamp( - 1.0f + ((mSetting.field_0x40 - 1.0f) / - ((mSetting.field_0x0[0] * param_1) - mSetting.field_0x3c)) * - (len - mSetting.field_0x3c), + 1.0f + ((mSetting.mMinDistanceVolume - 1.0f) / + ((mSetting.mDistanceMaxes[0] * param_1) - mSetting.mMaxVolumeDistance)) * + (len - mSetting.mMaxVolumeDistance), 0.0f, 1.0f); } @@ -717,7 +718,7 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) { return 0.5f; } - dVar6 = (0.5f + mSetting.field_0x58 * (local_54.x / dVar6)); + dVar6 = (0.5f + mSetting.mPanFactor * (local_54.x / dVar6)); if (local_54.z <= 0.0f) { f32 fovySin = mAudioCamera[camID].getFovySin(); if (dVar6 < 0.5f) { @@ -745,64 +746,74 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) { f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) { f32 fVar1 = param_0.z + mAudioCamera[camID].getDolbyCenterZ(); #if TARGET_PC - if (dusk::audio::EnableHrtf) { + const auto mode = dusk::getSettings().audio.outputMode.getValue(); + if (mode >= dusk::AudioOutputMode::StereoHeadphones) { // Normalize the direction so result is purely front/back orientation, // independent of how far away the sound is - f32 lenSq = param_0.x * param_0.x + param_0.y * param_0.y + param_0.z * param_0.z; + f32 lenSq = param_0.x * param_0.x + param_0.z * param_0.z; + if (mode == dusk::AudioOutputMode::StereoHeadphones) { + // original HRTF math + lenSq += param_0.y * param_0.y; + } if (lenSq < 0.0001f) { return 0.5f; } f32 zNorm = param_0.z / sqrtf(lenSq); f32 t = (zNorm + 1.0f) * 0.5f; - return 0.5f - 0.5f * cosf(t * static_cast(M_PI)); + if (mode == dusk::AudioOutputMode::StereoHeadphones) { + // original HRTF math + return 0.5f - 0.5f * cosf(t * static_cast(M_PI)); + } else { + return t; + } } #endif - if (fVar1 > mSetting.field_0x48) { + if (fVar1 > mSetting.mDolbyBehindDistanceMax) { return 1.0f; } - if (fVar1 < mSetting.field_0x44) { + if (fVar1 < mSetting.mDolbyFrontDistanceMax) { return 0.0f; } if (fVar1 < 0.0f) { return Z2Calc::getParamByExp( - fVar1, mSetting.field_0x44, 0.0f, + fVar1, mSetting.mDolbyFrontDistanceMax, 0.0f, 0.3f, 0.0f, - mSetting.field_0x4c, Z2Calc::CURVE_POSITIVE); + mSetting.mDolbyCenterValue, Z2Calc::CURVE_POSITIVE); } return Z2Calc::getParamByExp( - fVar1, 0.0f, mSetting.field_0x48, - 0.3f, mSetting.field_0x4c, + fVar1, 0.0f, mSetting.mDolbyBehindDistanceMax, + 0.3f, mSetting.mDolbyCenterValue, 1.0f, Z2Calc::CURVE_NEGATIVE); } f32 Z2Audience::calcVolume_(f32 param_0, int distVolBit) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - return mSetting.field_0x40; + if (param_0 > mSetting.mDistanceMaxes[distVolBit]) { + return mSetting.mMinDistanceVolume; } - if (param_0 < mSetting.field_0x3c) { + if (param_0 < mSetting.mMaxVolumeDistance) { return 1.0f; } - return JGeometry::TUtil::clamp((mSetting.field_0x70[distVolBit] * (param_0 - mSetting.field_0x3c)) + 1.0f, 0.0f, 1.0f); + return JGeometry::TUtil::clamp((mSetting.mVolumeFactor[distVolBit] * (param_0 - mSetting.mMaxVolumeDistance)) + 1.0f, 0.0f, 1.0f); } -u32 Z2Audience::calcDeltaPriority_(f32 param_0, int distVolBit, bool param_2) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - if (param_2) { +u32 Z2Audience::calcDeltaPriority_(f32 distance, int distVolBit, bool cullMaxDistance) const { + if (distance > mSetting.mDistanceMaxes[distVolBit]) { + if (cullMaxDistance) { return -1; } - return mSetting.field_0x64; + return mSetting.mMaxDistancePriority; } - if (param_0 < mSetting.field_0x3c) { + if (distance < mSetting.mMaxVolumeDistance) { return 0; } - return mSetting.field_0xac[distVolBit] * (param_0 - mSetting.field_0x3c); + return mSetting.mPriorityFactor[distVolBit] * (distance - mSetting.mMaxVolumeDistance); } f32 Z2Audience::calcPitchDoppler_(const JGeometry::TVec3& param_0, @@ -810,24 +821,24 @@ f32 Z2Audience::calcPitchDoppler_(const JGeometry::TVec3& param_0, const JGeometry::TVec3& param_2, f32 param_3) const { f32 dVar8 = param_0.dot(param_1); f32 dVar7 = param_0.dot(param_2); - return (mSetting.field_0x5c + param_3 * dVar8) / (mSetting.field_0x5c + param_3 * dVar7); + return (mSetting.mSonicSpeed + param_3 * dVar8) / (mSetting.mSonicSpeed + param_3 * dVar7); } -f32 Z2Audience::calcFxMix_(f32 param_0, int distVolBit) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - return mSetting.field_0x54; +f32 Z2Audience::calcFxMix_(f32 distance, int distVolBit) const { + if (distance > mSetting.mDistanceMaxes[distVolBit]) { + return mSetting.mMaxDistanceFxMix; } - if (param_0 < mSetting.field_0x3c) { - return mSetting.field_0x50; + if (distance < mSetting.mMaxVolumeDistance) { + return mSetting.mMinDistanceFxMix; } - return mSetting.field_0x50 + mSetting.field_0xe8[distVolBit] * (param_0 - mSetting.field_0x3c); + return mSetting.mMinDistanceFxMix + mSetting.mFxMixFactor[distVolBit] * (distance - mSetting.mMaxVolumeDistance); } f32 Z2Audience::calcPitch_(Z2AudibleChannel* channel, const Z2Audible* audible, const Z2AudioCamera* camera) const { JAUAudibleParam audParam = *audible->getAudibleParam(); - if (audParam.field_0x0.bytes.b0_0) { + if (audParam.field_0x0.bytes.mDopplerPower) { JGeometry::TVec3 aTStack_4c; - aTStack_4c.normalize(channel->field_0x14.field_0x00); + aTStack_4c.normalize(channel->mRelPos.mCameraRelative); JAUAudibleParam audParam = *audible->getAudibleParam(); f32 doppler = audParam.getDopplerPower(); diff --git a/src/Z2AudioLib/Z2SceneMgr.cpp b/src/Z2AudioLib/Z2SceneMgr.cpp index 1b2152fcb7..d8f52ce0de 100644 --- a/src/Z2AudioLib/Z2SceneMgr.cpp +++ b/src/Z2AudioLib/Z2SceneMgr.cpp @@ -75,7 +75,7 @@ void Z2SceneMgr::setFadeOutStart(u8 fadeType) { Z2GetSeqMgr()->bgmAllMute(33, 3.0f / 10.0f); Z2GetSeMgr()->seMoveVolumeAll(0.0f, 33); Z2GetSeqMgr()->setBattleBgmOff(true); - load1stWait = 40; + load1stWait = IF_DUSK(dusk::getSettings().game.fastTransitions ? 1 :) 40; timer = -1; } @@ -1642,7 +1642,7 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) { JSUList* stream_list = Z2GetSoundMgr()->getStreamMgr()->getStreamList(); JSULink* stream; for (stream = stream_list->getFirst(); stream != NULL; stream = stream->getNext()) { - if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != 0x71) { + if (bVar2 || sound_table->getTypeID(stream->getObject()->getID() IF_DUSK_ARG(stream->getObject()->getReplacement())) != SOUND_TYPEID_STREAM) { stream->getObject()->stop(Z2Param::SCENE_CHANGE_BGM_FADEOUT_TIME); } } @@ -1777,7 +1777,7 @@ void Z2SceneMgr::_load1stWaveInner_1() { if (field_0x1a && Z2GetSeqMgr()->checkBgmPlaying()) { OS_REPORT("[Z2SceneMgr::load1stDynamicWave]bgm StopCount = %d\n", 15); Z2GetSeqMgr()->bgmStop(15, 0); - load1stWait = -15; + load1stWait = IF_DUSK(dusk::getSettings().game.fastTransitions.getValue() ? -1 :) -15; } else { _load1stWaveInner_2(); } diff --git a/src/Z2AudioLib/Z2SoundHandles.cpp b/src/Z2AudioLib/Z2SoundHandles.cpp index de7e265ae7..99e11e0784 100644 --- a/src/Z2AudioLib/Z2SoundHandles.cpp +++ b/src/Z2AudioLib/Z2SoundHandles.cpp @@ -89,7 +89,7 @@ Z2SoundHandlePool* Z2SoundHandles::getLowPrioSound(JAISoundID soundID) { return handle; } - u32 prio = soundInfo->getPriority((*handle)->getID()); + u32 prio = soundInfo->getPriority((*handle)->getID() IF_DUSK_ARG((*handle)->getReplacement())); if (prio < low_prio) { low_prio = prio; rethandle = handle; @@ -102,7 +102,7 @@ Z2SoundHandlePool* Z2SoundHandles::getLowPrioSound(JAISoundID soundID) { } #endif - if (soundInfo->getPriority(soundID) >= low_prio) { + if (soundInfo->getPriority(soundID IF_DUSK_ARG(nullptr)) >= low_prio) { return rethandle; } diff --git a/src/Z2AudioLib/Z2SoundInfo.cpp b/src/Z2AudioLib/Z2SoundInfo.cpp index cb67b4c805..394034e322 100644 --- a/src/Z2AudioLib/Z2SoundInfo.cpp +++ b/src/Z2AudioLib/Z2SoundInfo.cpp @@ -1,11 +1,13 @@ #include "Z2AudioLib/Z2SoundInfo.h" -#include "JSystem/JAudio2/JAISeq.h" + #include "JSystem/JAudio2/JAISe.h" +#include "JSystem/JAudio2/JAISeq.h" #include "JSystem/JAudio2/JAISoundChild.h" #include "JSystem/JAudio2/JAIStream.h" #include "JSystem/JAudio2/JAUSoundTable.h" #include "JSystem/JUtility/JUTAssert.h" #include "Z2AudioLib/Z2Calc.h" +#include "dusk/mods/svc/audio_res/bst.hpp" struct JAUStdSoundTableType { static DUSK_GAME_DATA const u32 STRM_CH_SHIFT; @@ -18,12 +20,12 @@ struct JAUStdSoundTableType { u16 Z2SoundInfo::getBgmSeqResourceID(JAISoundID soundID) const { JUT_ASSERT(20, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(nullptr)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(nullptr)); if (data != NULL) { switch ((typeID & 0xf0)) { - case 0x60: + case SOUND_TYPEID_SEQUENCE: return (u16)data->mResourceId; } } @@ -48,83 +50,101 @@ int Z2SoundInfo::getCategory(JAISoundID soundID) const { return soundID.id_.info.type.parts.groupID; } -u32 Z2SoundInfo::getPriority(JAISoundID soundID) const { +u32 Z2SoundInfo::getPriority(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JUT_ASSERT(63, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); - if (data != NULL && (typeID & 0x40) != 0) { +#if TARGET_PC + // This function gets called at least once in a context where we can't easily pass in the replacement. + // So check it here too. + // It's only a tiny priority difference, highly unlikely to cause any issues. + std::shared_ptr replacement_shared; + if (!replacement) { + replacement_shared = dusk::mods::svc::audio_res::bst::get_override_for(soundID); + replacement = replacement_shared.get(); + } +#endif + + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); + + if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { return data->mPriority; } return 0; } -JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) { +#define SW_BIT DUSK_IF_ELSE(sw_bit, getSwBit(soundID)) + +JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) { JAUAudibleParam audibleParam; JUT_ASSERT(82, isValid()); int iVar1, uVar7; - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + IF_DUSK(int sw_bit;) + + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID) { - case 81: - audibleParam.field_0x0.bytes.b0_0 = (u32)getSwBit(soundID) >> 8; - if ((getSwBit(soundID) & 1) != 0) { - audibleParam.field_0x0.bytes.b0_4 = 0; + case SOUND_TYPEID_SOUND_EFFECT: + IF_DUSK(sw_bit = getSwBit(soundID, replacement);) + + audibleParam.field_0x0.bytes.mDopplerPower = (u32)SW_BIT >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((SW_BIT & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { - audibleParam.field_0x0.bytes.b0_4 = 1; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & 2) != 0) { - audibleParam.field_0x0.bytes.b0_5 = 0; + if ((SW_BIT & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { - audibleParam.field_0x0.bytes.b0_5 = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } - if ((getSwBit(soundID) & 4) != 0) { - audibleParam.field_0x0.bytes.b0_6 = 0; + if ((SW_BIT & SOUND_SW_IGNORE_FX_MIX) != 0) { + audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { - audibleParam.field_0x0.bytes.b0_6 = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & 0x800000) != 0) { - audibleParam.field_0x0.bytes.b0_7 = 1; + if ((SW_BIT & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { - audibleParam.field_0x0.bytes.b0_7 = 0; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & 0x1000) != 0) { - audibleParam.field_0x0.bytes.b1_0 = 0; + if ((SW_BIT & SOUND_SW_IGNORE_PAN) != 0) { + audibleParam.field_0x0.bytes.mCalcPan = 0; } else { - audibleParam.field_0x0.bytes.b1_0 = 1; + audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & 0x2000) != 0) { - audibleParam.field_0x0.bytes.b1_1 = 0; + if ((SW_BIT & SOUND_SW_IGNORE_DOLBY) != 0) { + audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { - audibleParam.field_0x0.bytes.b1_1 = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & 0x80000) != 0) { + if ((SW_BIT & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & 0x7; - iVar1 += (getSwBit(soundID) >> 16) & 0x70; - iVar1 += (getSwBit(soundID) >> 16) & 0xf00; - audibleParam.field_0x0.bytes.b1_2_7 = uVar7; + iVar1 = (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; default: - audibleParam.field_0x0.bytes.b0_0 = 0; - audibleParam.field_0x0.bytes.b0_4 = 1; - audibleParam.field_0x0.bytes.b0_5 = 1; - audibleParam.field_0x0.bytes.b0_6 = 1; - audibleParam.field_0x0.bytes.b0_7 = 0; - audibleParam.field_0x0.bytes.b1_0 = 1; - audibleParam.field_0x0.bytes.b1_1 = 1; - audibleParam.field_0x0.bytes.b1_2_7 = 0; + audibleParam.field_0x0.bytes.mDopplerPower = 0; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; + audibleParam.field_0x0.bytes.mCalcPan = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; + audibleParam.field_0x0.bytes.mClampMinVolume = 0; audibleParam.field_0x0.half.f1 = 0; break; } @@ -132,60 +152,64 @@ JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) { return audibleParam; } -u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const { +u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JAUAudibleParam audibleParam; JUT_ASSERT(184, isValid()); int iVar1, uVar7; - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + IF_DUSK(int sw_bit); + + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID) { - case 81: - audibleParam.field_0x0.bytes.b0_0 = (u32)getSwBit(soundID) >> 8; - if ((getSwBit(soundID) & 1) != 0) { - audibleParam.field_0x0.bytes.b0_4 = 0; + case SOUND_TYPEID_SOUND_EFFECT: + IF_DUSK(sw_bit = getSwBit(soundID, replacement);) + + audibleParam.field_0x0.bytes.mDopplerPower = (u32)SW_BIT >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((SW_BIT & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { - audibleParam.field_0x0.bytes.b0_4 = 1; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & 2) != 0) { - audibleParam.field_0x0.bytes.b0_5 = 0; + if ((SW_BIT & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { - audibleParam.field_0x0.bytes.b0_5 = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } - if ((getSwBit(soundID) & 4) != 0) { - audibleParam.field_0x0.bytes.b0_6 = 0; + if ((SW_BIT & SOUND_SW_IGNORE_FX_MIX) != 0) { + audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { - audibleParam.field_0x0.bytes.b0_6 = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & 0x800000) != 0) { - audibleParam.field_0x0.bytes.b0_7 = 1; + if ((SW_BIT & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { - audibleParam.field_0x0.bytes.b0_7 = 0; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & 0x1000) != 0) { - audibleParam.field_0x0.bytes.b1_0 = 0; + if ((SW_BIT & SOUND_SW_IGNORE_PAN) != 0) { + audibleParam.field_0x0.bytes.mCalcPan = 0; } else { - audibleParam.field_0x0.bytes.b1_0 = 1; + audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & 0x2000) != 0) { - audibleParam.field_0x0.bytes.b1_1 = 0; + if ((SW_BIT & SOUND_SW_IGNORE_DOLBY) != 0) { + audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { - audibleParam.field_0x0.bytes.b1_1 = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & 0x80000) != 0) { + if ((SW_BIT & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & 0x7; - iVar1 += (getSwBit(soundID) >> 16) & 0x70; - iVar1 += (getSwBit(soundID) >> 16) & 0xf00; - audibleParam.field_0x0.bytes.b1_2_7 = uVar7; + iVar1 = (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; default: @@ -197,30 +221,32 @@ u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const { return audibleParam.field_0x0.half.f0; } -void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr) const { +#undef SW_BIT + +void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const { getSoundInfo_(soundID, sePtr); JUT_ASSERT(292, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); if (data == NULL) { return; } switch(typeID) { - case 81: - sePtr->getProperty().field_0x8 *= data->field_0x8; - u32 uStack_6c = (getSwBit(soundID) & 0xf0) >> 4; - if (uStack_6c > 8) { - sePtr->getProperty().field_0x8 += Z2Calc::linearTransform(uStack_6c, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1(); + case SOUND_TYPEID_SOUND_EFFECT: + sePtr->getProperty().mPitch *= data->mPitch; + u32 pitchParam = (getSwBit(soundID IF_DUSK_ARG(replacement)) & SOUND_SW_RANDOM_PITCH_MASK) >> SOUND_SW_RANDOM_PITCH_OFFSET; + if (pitchParam > 8) { + sePtr->getProperty().mPitch += Z2Calc::linearTransform(pitchParam, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1(); } else { - sePtr->getProperty().field_0x8 += (uStack_6c / 48.0f) * Z2Calc::getRandom_0_1(); + sePtr->getProperty().mPitch += (pitchParam / 48.0f) * Z2Calc::getRandom_0_1(); } - u32 uVar1 = (u32)getSwBit(soundID) >> 0x1c; + u32 uVar1 = (u32)getSwBit(soundID IF_DUSK_ARG(replacement)) >> SOUND_SW_RANDOM_VOLUME_OFFSET; if (uVar1 != 0) { f32 dVar18 = (uVar1 / 15.0f) * Z2Calc::getRandom_0_1(); - sePtr->getProperty().field_0x0 -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18); + sePtr->getProperty().mVolume -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18); } break; } @@ -230,23 +256,23 @@ void Z2SoundInfo::getSeqInfo(JAISoundID soundID, JAISeq* seqPtr) const { getSoundInfo_(soundID, seqPtr); } -DUSK_GAME_DATA const u32 JAUStdSoundTableType::STRM_CH_SHIFT = 2; +DUSK_GAME_DATA const u32 JAUStdSoundTableType::STRM_CH_SHIFT = STRM_CH_SHIFT_; -void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const { +void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const { int numChild; - JAUSoundTableItem* data; + JAUSoundTableItem DUSK_CONST* data; getSoundInfo_(soundID, streamPtr); JUT_ASSERT(349, isValid()); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID & 0xf0) { - case 0x70: + case SOUND_TYPEID_STREAM: u16 uVar1; s32 iVar4; - data = JASGlobalInstance::getInstance()->getData(soundID); + data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); JUT_ASSERT(356, data); - uVar1 = data->mResourceId; + uVar1 = data->mStreamPanParameters; numChild = streamPtr->getNumChild(); iVar4 = 0; for (; iVar4 < numChild && uVar1 != 0; uVar1 >>= JAUStdSoundTableType::STRM_CH_SHIFT, iVar4++) { @@ -255,13 +281,13 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const JAISoundChild* child = streamPtr->getChild(iVar4); if (child != NULL) { switch (uVar2) { - case 1: + case STRM_CH_CENTER: child->mMove.params_.mPan = 0.5f; break; - case 2: + case STRM_CH_LEFT: child->mMove.params_.mPan = 0.0f; break; - case 3: + case STRM_CH_RIGHT: child->mMove.params_.mPan = 1.0f; break; } @@ -271,37 +297,43 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const } } -const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID) { +const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)) { JUT_ASSERT(387, isValid()); - JAUSoundTableItem* data; + JAUSoundTableItem DUSK_CONST* data; const void* resource; - switch (JASGlobalInstance::getInstance()->getTypeID(soundID) & 0xf0) { - case 0x70: - data = JASGlobalInstance::getInstance()->getData(soundID); + switch (JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)) & 0xf0) { + case SOUND_TYPEID_STREAM: +#if TARGET_PC + if (replacement) { + return replacement->file_path.c_str(); + } +#endif + + data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); JUT_ASSERT(394, data); resource = JASGlobalInstance::getInstance()->getResource(); JUT_ASSERT(398, resource); - return JAUStdSoundTableType::StringOffset::getString(resource, data->field_0x4); + return JAUStdSoundTableType::StringOffset::getString(resource, data->mStreamFilePath); default: return NULL; } } -s32 Z2SoundInfo::getStreamFileEntry(JAISoundID soundID) { - const char* path = getStreamFilePath(soundID); +s32 Z2SoundInfo::getStreamFileEntry(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)) { + const char* path = getStreamFilePath(soundID IF_DUSK_ARG(replacement)); return !path ? -1 : DVDConvertPathToEntrynum(path); } -int Z2SoundInfo::getSwBit(JAISoundID soundID) const { +int Z2SoundInfo::getSwBit(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JUT_ASSERT(418, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); if (data != NULL) { switch(typeID) { - case 81: - return data->field_0x4; + case SOUND_TYPEID_SOUND_EFFECT: + return data->mSwBit; } } @@ -310,10 +342,10 @@ int Z2SoundInfo::getSwBit(JAISoundID soundID) const { void Z2SoundInfo::getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const { JUT_ASSERT(440, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(soundPtr->getReplacement())); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); - if (data != NULL && (typeID & 0x40) != 0) { - soundPtr->getProperty().field_0x0 = (1.0f / 127.0f) * data->field_0x1; + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(soundPtr->getReplacement())); + if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { + soundPtr->getProperty().mVolume = (1.0f / 127.0f) * data->mVolume; } } diff --git a/src/Z2AudioLib/Z2SoundMgr.cpp b/src/Z2AudioLib/Z2SoundMgr.cpp index 6978a61585..43bd6c7d5c 100644 --- a/src/Z2AudioLib/Z2SoundMgr.cpp +++ b/src/Z2AudioLib/Z2SoundMgr.cpp @@ -1,14 +1,15 @@ #include "Z2AudioLib/Z2SoundMgr.h" -#include "Z2AudioLib/Z2Param.h" -#include "Z2AudioLib/Z2SceneMgr.h" -#include "Z2AudioLib/Z2SpeechMgr2.h" -#include "Z2AudioLib/Z2SeqMgr.h" -#include "Z2AudioLib/Z2SeMgr.h" -#include "Z2AudioLib/Z2SoundInfo.h" #include "JSystem/JAudio2/JASCalc.h" #include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JAUSectionHeap.h" +#include "Z2AudioLib/Z2Param.h" +#include "Z2AudioLib/Z2SceneMgr.h" +#include "Z2AudioLib/Z2SeMgr.h" +#include "Z2AudioLib/Z2SeqMgr.h" +#include "Z2AudioLib/Z2SoundInfo.h" +#include "Z2AudioLib/Z2SpeechMgr2.h" #include "d/d_com_inf_game.h" +#include "dusk/mods/svc/audio_res/bst.hpp" #if PLATFORM_WII || PLATFORM_SHIELD #include "Z2AudioCS/Z2AudioCS.h" @@ -113,19 +114,25 @@ Z2SoundMgr::Z2SoundMgr() : seqMgr_(true), streamMgr_(true) { - soundID_.setAnonymous(); + bgmMuter.setAnonymous(); } bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { DUSK_AUDIO_SKIP(true); + IF_DUSK(using namespace dusk::mods::svc::audio_res::bst); int soundType = Z2GetSoundInfo()->getSoundType(soundID); switch (soundType) { case 0: { - if (Z2GetSoundInfo()->getSwBit(soundID) & 8) { + #if TARGET_PC + { + auto replace_se = get_override_for_se(soundID); + #endif + + if (Z2GetSoundInfo()->getSwBit(soundID IF_DUSK_ARG(replace_se.get())) & SOUND_SW_MUTE_BGM) { OS_REPORT("[Z2SoundMgr::startSound] se seq Mute! id = %08x\n", *(u32*)&soundID); Z2GetSeqMgr()->bgmAllMute(3, 0.3f); - soundID_ = soundID; + bgmMuter = soundID; } #if PLATFORM_WII || PLATFORM_SHIELD @@ -136,15 +143,16 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG } #endif - bool result = seMgr_.startSound(soundID, handle, posPtr); + bool result = seMgr_.startSound(soundID, handle, posPtr IF_DUSK_ARG(std::move(replace_se))); return result; + IF_DUSK_BLOCK_END } case 1: if (soundID == Z2BGM_LUTERA_DEMO) { Z2GetSeqMgr()->bgmStart(Z2BGM_LUTERA2, 0, 0); Z2GetSeqMgr()->unMuteSceneBgm(0); Z2GetSeqMgr()->changeBgmStatus(0); - return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr); + return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr IF_DUSK_ARG(nullptr)); } else { bool loaded = false; JAUSectionHeap* sectionHeap = JASGlobalInstance::getInstance(); @@ -172,15 +180,21 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG return seqMgr_.startSound(soundID, handle, posPtr); } case 2: +#if TARGET_PC + { + auto replace_stream = get_override_for_stream(soundID); +#endif + if (soundID == 0x2000001 || soundID == 0x200004d) { streamMgr_.stop(180); - return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr); + return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr IF_DUSK_ARG(nullptr)); } if (soundID == 0x2000005) { seMgr_.getCategory(9)->pause(false); seMgr_.getCategory(9)->getParams()->moveVolume(1.0f, 45); } - return streamMgr_.startSound(soundID, handle, posPtr); + return streamMgr_.startSound(soundID, handle, posPtr IF_DUSK_ARG(std::move(replace_stream))); + IF_DUSK_BLOCK_END default: char error[64]; SAFE_SPRINTF(error, "Unknown Sound-Type id :%08x\n", (u32)soundID); @@ -192,11 +206,11 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG void Z2SoundMgr::calc() { seMgr_.calc(); - if (!soundID_.isAnonymous() && !isPlayingSoundID(soundID_)) { + if (!bgmMuter.isAnonymous() && !isPlayingSoundID(bgmMuter)) { if (Z2GetSceneMgr()->isSceneExist()) { Z2GetSeqMgr()->bgmAllUnMute(3); } - soundID_.setAnonymous(); + bgmMuter.setAnonymous(); } seqMgr_.calc(); streamMgr_.calc(); diff --git a/src/Z2AudioLib/Z2SoundObject.cpp b/src/Z2AudioLib/Z2SoundObject.cpp index 23e8171736..c45a638b3c 100644 --- a/src/Z2AudioLib/Z2SoundObject.cpp +++ b/src/Z2AudioLib/Z2SoundObject.cpp @@ -70,7 +70,7 @@ void Z2SoundObjBase::dispose() { for (i = getFirst(); i != NULL; i = i->getNext()) { handle = i->getObject(); if (handle != NULL && *handle) { - if ((Z2GetSoundInfo()->getSwBit((*handle)->getID()) & 0x8000) != 0) { + if ((Z2GetSoundInfo()->getSwBit((*handle)->getID() IF_DUSK_ARG((*handle)->getReplacement())) & SOUND_SW_POOL_FLAG_2) != 0) { handle->releaseSound(); } else { (*handle)->stop(); @@ -82,7 +82,7 @@ void Z2SoundObjBase::dispose() { } bool Z2SoundObjBase::stopOK(Z2SoundHandlePool& handlePool) { - if ((Z2GetSoundInfo()->getSwBit(handlePool->getID()) & 0x8000) != 0) { + if ((Z2GetSoundInfo()->getSwBit(handlePool->getID() IF_DUSK_ARG(handlePool->getReplacement())) & SOUND_SW_POOL_FLAG_2) != 0) { return false; } else { return true; @@ -112,7 +112,7 @@ Z2SoundHandlePool* Z2SoundObjBase::startSound(JAISoundID soundID, u32 mapinfo, s Z2SoundHandlePool* handle = getHandleSoundID(soundID); if (handle != NULL) { - if ((Z2GetSoundInfo()->getSwBit(soundID) & 0x4000) != 0) { + if ((Z2GetSoundInfo()->getSwBit(soundID IF_DUSK_ARG((*handle)->getReplacement())) & SOUND_SW_POOL_FLAG_1) != 0) { handle = NULL; } else { return NULL; @@ -222,7 +222,7 @@ Z2SoundHandlePool* Z2SoundObjBase::startCollisionSE(u32 hitID, u32 mapinfo, Z2So if (30 <= mapinfo && mapinfo <= 52) { Z2Audible* audible = (Z2Audible*)(*handle)->getAudible(); if (audible != NULL) { - audible->getAudibleParam()->field_0x0.bytes.b1_2_7 = 8; + audible->getAudibleParam()->field_0x0.bytes.mClampMinVolume = 8; } } } diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 45829188db..ef4fdca240 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -53,11 +53,24 @@ #if TARGET_PC #include "dusk/action_bindings.h" -#include "dusk/frame_interpolation.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/sight.h" #include "dusk/settings.h" #include "res/Object/Alink.h" #include #include + +static const int IRON_BALL_CHAIN_COUNT = 102; +static const int HS_CHAIN_ANCHOR_COUNT = 4; + +namespace { +struct AlinkInterp { + dusk::interp::Samples ib_pos; + dusk::interp::Samples ib_angle; + dusk::interp::Samples ib_hand; + dusk::interp::Samples hs_chain; +}; +} // namespace #endif static int daAlink_Create(fopAc_ac_c* i_this); @@ -5991,7 +6004,7 @@ void daAlink_c::setItemMatrix(int param_0) { mDoMtx_stack_c::XrotS(-0x8000); #ifdef TARGET_PC - if (dusk::frame_interp::is_enabled()) { + if (dusk::interp::is_enabled()) { Mtx boot_mtx; mDoMtx_concat(mpLinkModel->getAnmMtx(0x18), mDoMtx_stack_c::get(), boot_mtx); mpLinkBootModels[1]->setAnmMtx(1, boot_mtx); @@ -14806,10 +14819,13 @@ void daAlink_c::deleteEquipItem(BOOL i_isPlaySound, BOOL i_isDeleteKantera) { mIronBallChainAngle = NULL; field_0x3848 = NULL; #if TARGET_PC - mIBChainInterpPrevValid = false; - mIBChainInterpCurrValid = false; - mHsChainInterpPrevValid = false; - mHsChainInterpCurrValid = false; + { + auto& interp = dusk::interp::get(this); + interp.ib_pos.reset(); + interp.ib_angle.reset(); + interp.ib_hand.reset(); + interp.hs_chain.reset(); + } #endif field_0x0774 = NULL; field_0x0778 = NULL; @@ -19782,36 +19798,21 @@ int daAlink_c::draw() { dComIfGd_getOpaListDark()->entryImm(mpHookChain, 0); #if TARGET_PC - if (dusk::frame_interp::is_enabled()) { + if (dusk::interp::should_capture()) { + auto& interp = dusk::interp::get(this); if (mEquipItem == dItemNo_IRONBALL_e && mIronBallChainPos != NULL && mIronBallChainAngle != NULL) { - if (mIBChainInterpCurrValid) { - memcpy(mIBChainInterpPrevPos, mIBChainInterpCurrPos, IRON_BALL_CHAIN_COUNT * sizeof(cXyz)); - memcpy(mIBChainInterpPrevAngle, mIBChainInterpCurrAngle, IRON_BALL_CHAIN_COUNT * sizeof(csXyz)); - mIBChainInterpPrevHandRoot = mIBChainInterpCurrHandRoot; - mIBChainInterpPrevValid = true; - } - - memcpy(mIBChainInterpCurrPos, mIronBallChainPos, IRON_BALL_CHAIN_COUNT * sizeof(cXyz)); - memcpy(mIBChainInterpCurrAngle, mIronBallChainAngle, IRON_BALL_CHAIN_COUNT * sizeof(csXyz)); - mIBChainInterpCurrHandRoot = mHookshotTopPos; - mIBChainInterpCurrValid = true; - - dusk::frame_interp::add_interpolation_callback(&ironBallChainInterpCallback, this); + interp.ib_pos.capture(mIronBallChainPos, IRON_BALL_CHAIN_COUNT); + interp.ib_angle.capture(mIronBallChainAngle, IRON_BALL_CHAIN_COUNT); + interp.ib_hand.capture(&mHookshotTopPos, 1); } else { - if (mHsChainInterpCurrValid) { - mHsChainInterpPrevTop = mHsChainInterpCurrTop; - mHsChainInterpPrevRoot = mHsChainInterpCurrRoot; - mHsChainInterpPrevSubRoot = mHsChainInterpCurrSubRoot; - mHsChainInterpPrevSubTop = mHsChainInterpCurrSubTop; - mHsChainInterpPrevValid = true; - } - mHsChainInterpCurrTop = mHookshotTopPos; - mHsChainInterpCurrRoot = mHeldItemRootPos; - mHsChainInterpCurrSubRoot = field_0x3810; - mHsChainInterpCurrSubTop = mIronBallBgChkPos; - mHsChainInterpCurrValid = true; + cXyz hsAnchors[HS_CHAIN_ANCHOR_COUNT]; + hsAnchors[0] = mHookshotTopPos; + hsAnchors[1] = mHeldItemRootPos; + hsAnchors[2] = field_0x3810; + hsAnchors[3] = mIronBallBgChkPos; + interp.hs_chain.capture(hsAnchors, HS_CHAIN_ANCHOR_COUNT); } } #endif diff --git a/src/d/actor/d_a_alink_cut.inc b/src/d/actor/d_a_alink_cut.inc index ba3c0ba89e..ffb41cd722 100644 --- a/src/d/actor/d_a_alink_cut.inc +++ b/src/d/actor/d_a_alink_cut.inc @@ -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); } @@ -817,11 +823,16 @@ BOOL daAlink_c::checkDownAttackState() { } BOOL daAlink_c::checkCutLargeTurnState() const { - #if TARGET_PC - if (dusk::getSettings().game.alwaysGreatspin) { +#if TARGET_PC + const auto alwaysGreatspinMode = dusk::getSettings().game.alwaysGreatspin.getValue(); + if (alwaysGreatspinMode == dusk::AlwaysGreatspinMode::ALWAYS) { return TRUE; } - #endif + if (alwaysGreatspinMode == dusk::AlwaysGreatspinMode::AFTER_SKILL && + (dComIfGs_isEventBit(dSv_event_flag_c::F_0344) || checkNoResetFlg3(FLG3_TRANING_CUT_LARGE_TURN))) { + return TRUE; + } +#endif return ((dComIfGs_isEventBit(dSv_event_flag_c::F_0344) || checkNoResetFlg3(FLG3_TRANING_CUT_LARGE_TURN)) && dComIfGs_getLife() == dComIfGs_getMaxLifeGauge() diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index f16c8a6887..aaa38f544e 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -2387,7 +2387,7 @@ int daAlink_c::procCoGetItemInit() { if (var_r31 != 0) { mProcVar3.field_0x300e = -0x8000; mProcVar4.field_0x3010 = 1; - } else if (mDemo.getParam1() == 2) { + } else if (mDemo.getParam1() == DUSK_IF_ELSE(dusk::mods::item_give_tag("prayer_reward"), 2)) { mProcVar3.field_0x300e = 0; } else { mProcVar3.field_0x300e = shape_angle.y - (fopCamM_GetAngleY(dComIfGp_getCamera(field_0x317c)) + 0x8000); diff --git a/src/d/actor/d_a_alink_effect.inc b/src/d/actor/d_a_alink_effect.inc index db64d25b81..724d9d01ab 100644 --- a/src/d/actor/d_a_alink_effect.inc +++ b/src/d/actor/d_a_alink_effect.inc @@ -8,7 +8,10 @@ #include "d/actor/d_a_alink.h" #include "d/d_com_inf_game.h" #include "d/d_k_wmark.h" + +#if TARGET_PC #include "helpers/gx_helper.h" +#endif DUSK_GAME_DATA const EffParamProc daAlink_c::m_fEffParamProc[] = { &daAlink_c::setEffectFrontRollParam, @@ -2107,6 +2110,12 @@ void daAlink_blur_c::draw() { J3DShape::resetVcdVatCache(); } +#if TARGET_PC +daAlink_lockCursor_c::~daAlink_lockCursor_c() { + dusk::interp::erase_owned_samples(this); +} +#endif + BOOL daAlink_lockCursor_c::create() { JKRArchive* arc = dComIfG_getObjectResInfo(l_arcName)->getArchive(); @@ -2214,6 +2223,11 @@ void daAlink_lockCursor_c::update() { } cLib_chaseUC(&field_0x4, 0x80, 30); +#if TARGET_PC + const dusk::interp::SightAnimation frame = {mLockCursorAngle, field_0x30, + static_cast(field_0x4), static_cast(field_0x1c->getFrameMax())}; + dusk::interp::get>(this).capture(&frame, 1); +#endif } void daAlink_lockCursor_c::draw() { @@ -2221,28 +2235,28 @@ void daAlink_lockCursor_c::draw() { return; } - field_0x28->setFrame(mLockCursorAngle); +#if TARGET_PC + auto& samples = dusk::interp::get>(this); + const dusk::interp::SightAnimation current = { + mLockCursorAngle, field_0x30, static_cast(field_0x4), static_cast(field_0x1c->getFrameMax())}; + const auto frame = samples.read(0, current); +#endif + field_0x28->setFrame(DUSK_IF_ELSE(frame.bck, mLockCursorAngle)); - f32 var_f30; - if (mLockCursorAngle > 21.0f) { - var_f30 = 21.0f; - } else { - var_f30 = mLockCursorAngle; - } - - field_0x24->setFrame(var_f30); - field_0x1c->setFrame(field_0x30); + field_0x24->setFrame(DUSK_IF_ELSE(frame.bck, mLockCursorAngle) > 21.0f + ? 21.0f : DUSK_IF_ELSE(frame.bck, mLockCursorAngle)); + field_0x1c->setFrame(DUSK_IF_ELSE(frame.brk, field_0x30)); mScrn->animation(); field_0xc->scale(0.6f, 0.6f); field_0xc->translate(mPosX, mPosY); f32 var_f31; - if (!(mLockCursorAngle < 15.0f)) { - if (mLockCursorAngle < 21.0f) { - var_f31 = ((mLockCursorAngle - 15.0f) * 40.0f) * 0.16666667f; + if (!(DUSK_IF_ELSE(frame.bck, mLockCursorAngle) < 15.0f)) { + if (DUSK_IF_ELSE(frame.bck, mLockCursorAngle) < 21.0f) { + var_f31 = ((DUSK_IF_ELSE(frame.bck, mLockCursorAngle) - 15.0f) * 40.0f) * 0.16666667f; } else { - var_f31 = ((field_0x4 * 0.00390625f) + 0.5f) * 40.0f; + var_f31 = ((DUSK_IF_ELSE(frame.alpha, field_0x4) * 0.00390625f) + 0.5f) * 40.0f; } mCursor0->translate(0.0f, -var_f31); @@ -2264,6 +2278,7 @@ BOOL daAlink_sight_c::create() { void daAlink_sight_c::draw() { if (mLockFlag) { + IF_DUSK(setSight(false);) mLockCursor.setPos(mProjMtx[0][3], mProjMtx[1][3]); mLockCursor.draw(); } else { @@ -2276,5 +2291,6 @@ void daAlink_sight_c::onLockFlg() { return; } mLockFlag = true; + IF_DUSK(dusk::interp::erase_owned_samples(&mLockCursor);) mLockCursor.initFrame(); } diff --git a/src/d/actor/d_a_alink_hook.inc b/src/d/actor/d_a_alink_hook.inc index 660cf3b2ce..631632e0b4 100644 --- a/src/d/actor/d_a_alink_hook.inc +++ b/src/d/actor/d_a_alink_hook.inc @@ -8,9 +8,15 @@ #include "d/actor/d_a_obj_swhang.h" #include "d/actor/d_a_obj_chandelier.h" #include "JSystem/J3DGraphBase/J3DMaterial.h" -#include "dusk/frame_interpolation.h" + +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/samples.h" #include "dusk/settings.h" +static const int HS_CHAIN_MAX_LINKS = 600; +#endif + enum { HS_MODE_NONE_e, HS_MODE_READY_e, @@ -19,10 +25,6 @@ enum { HS_MODE_RETURN_e = 6, }; -#if TARGET_PC -static const int HS_CHAIN_MAX_LINKS = 600; -#endif - void daAlink_c::hsChainShape_c::draw() { static const int dummy = 0; @@ -34,9 +36,7 @@ void daAlink_c::hsChainShape_c::draw() { j3dSys.setVtxPos(modelData->getVtxPosArray(), modelData->getVtxNum()); j3dSys.setVtxNrm(modelData->getVtxNrmArray(), modelData->getNrmNum()); j3dSys.setVtxCol(modelData->getVtxColorArray(0), modelData->getColNum()); -#if TARGET_PC - j3dSys.setTexture(modelData->getTexture()); -#endif + IF_DUSK(j3dSys.setTexture(modelData->getTexture())); J3DShape::resetVcdVatCache(); material->loadSharedDL(); @@ -56,8 +56,18 @@ void daAlink_c::hsChainShape_c::draw() { GXLoadLightObjImm(chainLight->getLightObj(), GX_LIGHT0); if (alink->checkIronBallEquip()) { - cXyz* ppos = alink->getIronBallChainPos(); - csXyz* pangle = alink->getIronBallChainAngle(); +#if TARGET_PC + auto& samples = dusk::interp::get(alink); + cXyz chainPositions[IRON_BALL_CHAIN_COUNT]; + csXyz chainAngles[IRON_BALL_CHAIN_COUNT]; + for (int i = 0; i < IRON_BALL_CHAIN_COUNT; ++i) { + chainPositions[i] = samples.ib_pos.read(i, alink->getIronBallChainPos()[i]); + chainAngles[i] = samples.ib_angle.read(i, alink->getIronBallChainAngle()[i]); + } + cXyz chainHand = samples.ib_hand.read(0, alink->getIronBallChainHandRootPos()); +#endif + cXyz* ppos = DUSK_IF_ELSE(chainPositions, alink->getIronBallChainPos()); + csXyz* pangle = DUSK_IF_ELSE(chainAngles, alink->getIronBallChainAngle()); ppos++; pangle++; @@ -87,19 +97,19 @@ void daAlink_c::hsChainShape_c::draw() { s16 spE; if (i == 0) { - sp48 = alink->getIronBallChainPos() + alink->getIronBallHandChainNum(); - spC8 = alink->getIronBallChainHandRootPos() - *sp48; + sp48 = DUSK_IF_ELSE(chainPositions, alink->getIronBallChainPos()) + alink->getIronBallHandChainNum(); + spC8 = DUSK_IF_ELSE(chainHand, alink->getIronBallChainHandRootPos()) - *sp48; - sp44 = alink->getIronBallChainAngle() + alink->getIronBallHandChainNum(); + sp44 = DUSK_IF_ELSE(chainAngles, alink->getIronBallChainAngle()) + alink->getIronBallHandChainNum(); spE = 0x3000; } else { - sp48 = alink->getIronBallChainPos(); - mDoMtx_stack_c::transS(*(alink->getIronBallChainPos() + 1)); - mDoMtx_stack_c::ZXYrotM(*(alink->getIronBallChainAngle() + 1)); + sp48 = DUSK_IF_ELSE(chainPositions, alink->getIronBallChainPos()); + mDoMtx_stack_c::transS(*(DUSK_IF_ELSE(chainPositions, alink->getIronBallChainPos()) + 1)); + mDoMtx_stack_c::ZXYrotM(*(DUSK_IF_ELSE(chainAngles, alink->getIronBallChainAngle()) + 1)); mDoMtx_stack_c::multVec(&l_ironBallChainVec, &spC8); spC8 -= *sp48; - sp44 = alink->getIronBallChainAngle() + 1; + sp44 = DUSK_IF_ELSE(chainAngles, alink->getIronBallChainAngle()) + 1; spE = -0x3000; } @@ -137,25 +147,14 @@ void daAlink_c::hsChainShape_c::draw() { } } else { #if TARGET_PC - cXyz hsInterpTop, hsInterpRoot, hsInterpSubRoot, hsInterpSubTop; - if (dusk::frame_interp::is_enabled() && alink->mHsChainInterpPrevValid && alink->mHsChainInterpCurrValid) { - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - hsInterpTop = alink->mHsChainInterpPrevTop + (alink->mHsChainInterpCurrTop - alink->mHsChainInterpPrevTop) * alpha; - hsInterpRoot = alink->mHsChainInterpPrevRoot + (alink->mHsChainInterpCurrRoot - alink->mHsChainInterpPrevRoot) * alpha; - hsInterpSubRoot = alink->mHsChainInterpPrevSubRoot + (alink->mHsChainInterpCurrSubRoot - alink->mHsChainInterpPrevSubRoot) * alpha; - hsInterpSubTop = alink->mHsChainInterpPrevSubTop + (alink->mHsChainInterpCurrSubTop - alink->mHsChainInterpPrevSubTop) * alpha; - } else { - hsInterpTop = alink->getHsChainTopPos(); - hsInterpRoot = alink->getHsChainRootPos(); - hsInterpSubRoot = alink->getHsSubChainRootPos(); - hsInterpSubTop = alink->getHsSubChainTopPos(); - } - const cXyz& chainRootPos = hsInterpRoot; - const cXyz& chainTopPos = hsInterpTop; -#else - const cXyz& chainRootPos = alink->getHsChainRootPos(); - const cXyz& chainTopPos = alink->getHsChainTopPos(); + auto& samples = dusk::interp::get(alink).hs_chain; + cXyz hsInterpTop = samples.read(0, alink->getHsChainTopPos()); + cXyz hsInterpRoot = samples.read(1, alink->getHsChainRootPos()); + cXyz hsInterpSubRoot = samples.read(2, alink->getHsSubChainRootPos()); + cXyz hsInterpSubTop = samples.read(3, alink->getHsSubChainTopPos()); #endif + const cXyz& chainRootPos = DUSK_IF_ELSE(hsInterpRoot, alink->getHsChainRootPos()); + const cXyz& chainTopPos = DUSK_IF_ELSE(hsInterpTop, alink->getHsChainTopPos()); cXyz maxDistance = chainRootPos - chainTopPos; f32 maxDistanceF = maxDistance.abs(); @@ -185,11 +184,9 @@ void daAlink_c::hsChainShape_c::draw() { } (void)0; -#if TARGET_PC - int chainLinks = 0; -#endif + IF_DUSK(int chainLinks = 0); - while (maxDistanceF > var_f30 IF_DUSK(&&chainLinks < HS_CHAIN_MAX_LINKS)) { + while (maxDistanceF > var_f30 IF_DUSK(&& chainLinks < HS_CHAIN_MAX_LINKS)) { temp_f27 = var_f28 * cM_fsin(sp34 * var_f30); s16 spC = cM_atan2s(temp_f27 - var_f26, 5.0f); sp64.x = sp6C.x + spC; @@ -212,19 +209,12 @@ void daAlink_c::hsChainShape_c::draw() { var_f26 = temp_f27; var_f30 += fabsf(cM_scos(spC)) * 5.0f; -#if TARGET_PC - chainLinks++; -#endif + IF_DUSK(chainLinks++); } } -#if TARGET_PC - const cXyz& subChainRootPos = hsInterpSubRoot; - const cXyz& subChainTopPos = hsInterpSubTop; -#else - const cXyz& subChainRootPos = alink->getHsSubChainRootPos(); - const cXyz& subChainTopPos = alink->getHsSubChainTopPos(); -#endif + const cXyz& subChainRootPos = DUSK_IF_ELSE(hsInterpSubRoot, alink->getHsSubChainRootPos()); + const cXyz& subChainTopPos = DUSK_IF_ELSE(hsInterpSubTop, alink->getHsSubChainTopPos()); maxDistance = subChainRootPos - subChainTopPos; maxDistanceF = maxDistance.abs(); @@ -235,11 +225,9 @@ void daAlink_c::hsChainShape_c::draw() { sp98 = subChainTopPos; sp6C.set(maxDistance.atan2sY_XZ(), maxDistance.atan2sX_Z(), 0); -#if TARGET_PC - int subChainLinks = 0; -#endif + IF_DUSK(int subChainLinks = 0); - while (maxDistanceF > var_f30 IF_DUSK(&&subChainLinks < HS_CHAIN_MAX_LINKS)) { + while (maxDistanceF > var_f30 IF_DUSK(&& subChainLinks < HS_CHAIN_MAX_LINKS)) { mDoMtx_stack_c::copy(j3dSys.getViewMtx()); mDoMtx_stack_c::transM(sp98); mDoMtx_stack_c::ZXYrotM(sp6C); @@ -252,39 +240,12 @@ void daAlink_c::hsChainShape_c::draw() { sp98 += maxDistance * 5.0f; ANGLE_ADD_2(sp6C.z, 0x3000); var_f30 += 5.0f; -#if TARGET_PC - subChainLinks++; -#endif + IF_DUSK(subChainLinks++); } } } } -#if TARGET_PC -static void ironBallChainInterpCallback(bool isSimFrame, void* pUserWork) { - static_cast(pUserWork)->onIronBallChainInterpCallback(); -} - -void daAlink_c::onIronBallChainInterpCallback() { - if (!mIBChainInterpPrevValid || !mIBChainInterpCurrValid) { - return; - } - if (mIronBallChainPos == NULL || mIronBallChainAngle == NULL) { - return; - } - - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - - for (int i = 0; i < IRON_BALL_CHAIN_COUNT; i++) { - mIronBallChainPos[i] = mIBChainInterpPrevPos[i] + (mIBChainInterpCurrPos[i] - mIBChainInterpPrevPos[i]) * alpha; - mIronBallChainAngle[i].x = mIBChainInterpPrevAngle[i].x + (s16)((s16)(mIBChainInterpCurrAngle[i].x - mIBChainInterpPrevAngle[i].x) * alpha); - mIronBallChainAngle[i].y = mIBChainInterpPrevAngle[i].y + (s16)((s16)(mIBChainInterpCurrAngle[i].y - mIBChainInterpPrevAngle[i].y) * alpha); - mIronBallChainAngle[i].z = mIBChainInterpPrevAngle[i].z + (s16)((s16)(mIBChainInterpCurrAngle[i].z - mIBChainInterpPrevAngle[i].z) * alpha); - } - mHookshotTopPos = mIBChainInterpPrevHandRoot + (mIBChainInterpCurrHandRoot - mIBChainInterpPrevHandRoot) * alpha; -} -#endif - void daAlink_c::hookshotAtHitCallBack(dCcD_GObjInf* i_atObjInf, fopAc_ac_c* i_tgActor, dCcD_GObjInf* i_tgObjInf) { if (i_tgActor != NULL && fopAcM_IsActor(i_tgActor) && !i_tgObjInf->ChkTgHookshotThrough()) { diff --git a/src/d/actor/d_a_alink_horse.inc b/src/d/actor/d_a_alink_horse.inc index 7389decb73..f271ed401e 100644 --- a/src/d/actor/d_a_alink_horse.inc +++ b/src/d/actor/d_a_alink_horse.inc @@ -340,18 +340,31 @@ void daAlink_c::setHorseStirrup() { mDoMtx_stack_c::copy(mpLinkModel->getAnmMtx(field_0x30bc)); mDoMtx_stack_c::transM(-2.0f, -11.0f, 1.5f); mDoMtx_stack_c::ZXYrotM(0, -0x8000, 0x4000); +#if TARGET_PC + horse->m_model->setAnmMtx(0x17, mDoMtx_stack_c::get()); +#else mDoMtx_copy(mDoMtx_stack_c::get(), horse->getLeftStirrupMtx()); +#endif } if (field_0x2fab & 2) { mDoMtx_stack_c::copy(mpLinkModel->getAnmMtx(field_0x30be)); mDoMtx_stack_c::transM(-2.0f, 11.0f, 1.5f); mDoMtx_stack_c::ZrotM(-0x4000); +#if TARGET_PC + horse->m_model->setAnmMtx(0x19, mDoMtx_stack_c::get()); +#else mDoMtx_copy(mDoMtx_stack_c::get(), horse->getRightStirrupMtx()); +#endif } if (field_0x2fab & 3) { horse->calcWeightEnvMtx(); +#if TARGET_PC + for (u16 i = 0; i < horse->m_model->getModelData()->getWEvlpMtxNum(); i++) { + dusk::interp::record_final_mtx(horse->m_model->getWeightAnmMtx(i)); + } +#endif } int hand_type = getReinHandType(); diff --git a/src/d/actor/d_a_b_gm.cpp b/src/d/actor/d_a_b_gm.cpp index 2b65b8095b..7b0b86168d 100644 --- a/src/d/actor/d_a_b_gm.cpp +++ b/src/d/actor/d_a_b_gm.cpp @@ -17,6 +17,10 @@ #include "f_op/f_op_msg_mng.h" #include "Z2AudioLib/Z2Instances.h" +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#endif + #define ANM_EYE_TEST 6 #define ANM_GM_BEAM 7 #define ANM_GOMA_ATTACK_01 8 @@ -1037,6 +1041,7 @@ static void demo_camera(b_gm_class* i_this) { spBC.set(0.0f, 0.0f, 2418.0f); daPy_getPlayerActorClass()->setPlayerPosAndAngle(&spBC, 0x8000, 0); + IF_DUSK(dusk::interp::request_presentation_sync()); dComIfGp_getEvent()->startCheckSkipEdge(i_this); @@ -1048,6 +1053,7 @@ static void demo_camera(b_gm_class* i_this) { if (i_this->mDemoModeTimer == 1) { i_this->mDemoCamCenter.set(-400.0f, 130.0f, 1883.0f); i_this->mDemoCamEye.set(0.0f, 197.0f, 2680.0f); + IF_DUSK(dusk::interp::request_presentation_sync()); } cLib_addCalc2(&i_this->mDemoCamCenter.x, 400.0f, 0.05f, i_this->field_0x1cdc * 4.0f); @@ -1068,6 +1074,7 @@ static void demo_camera(b_gm_class* i_this) { i_this->mDemoCamCenter.set(-1090.0f, 227.0f, -1070.0f); i_this->mDemoCamEye.set(-688.0f, 155.0f, -680.0f); + IF_DUSK(dusk::interp::request_presentation_sync()); } break; case 3: @@ -1095,6 +1102,7 @@ static void demo_camera(b_gm_class* i_this) { i_this->field_0x1ce4 = player->shape_angle.y; i_this->mDemoCamCenter.set(-973.0f, 192.0f, -991.0f); i_this->mDemoCamEye.set(-1368.0f, 158.0f, -1397.0f); + IF_DUSK(dusk::interp::request_presentation_sync()); i_this->field_0x1cdc = 0.0f; i_this->field_0x6f5 = 0; i_this->field_0x1cfc = 2; @@ -1184,6 +1192,7 @@ static void demo_camera(b_gm_class* i_this) { if (i_this->mDemoModeTimer == 0) { i_this->mDemoCamCenter = spA4; i_this->mDemoCamEye = spB0; + IF_DUSK(dusk::interp::request_presentation_sync()); } else { cLib_addCalc2(&i_this->mDemoCamCenter.x, spA4.x, 0.1f, 5.0f); cLib_addCalc2(&i_this->mDemoCamCenter.z, spA4.z, 0.1f, 5.0f); @@ -1223,6 +1232,7 @@ static void demo_camera(b_gm_class* i_this) { if (i_this->mDemoModeTimer == 0) { i_this->mDemoCamCenter = spA4; i_this->mDemoCamEye = spB0; + IF_DUSK(dusk::interp::request_presentation_sync()); } else { cLib_addCalc2(&i_this->mDemoCamCenter.x, spA4.x, 0.1f, 5.0f); cLib_addCalc2(&i_this->mDemoCamCenter.z, spA4.z, 0.1f, 5.0f); @@ -1259,6 +1269,11 @@ static void demo_camera(b_gm_class* i_this) { Z2GetAudioMgr()->bgmStreamPrepare(0x200004E); Z2GetAudioMgr()->bgmStreamPlay(); case 31: +#if TARGET_PC + if (i_this->mDemoModeTimer == 0) { + dusk::interp::request_presentation_sync(); + } +#endif if (i_this->mDemoModeTimer < 10) { cMtx_YrotS(*calc_mtx, cM_atan2s(-a_this->current.pos.x, -a_this->current.pos.z)); spC8.x = 1100.0f; @@ -1309,6 +1324,7 @@ static void demo_camera(b_gm_class* i_this) { i_this->mDemoMode = 32; i_this->mDemoModeTimer = 0; i_this->mDemoCamFovy = 65.0f; + IF_DUSK(dusk::interp::request_presentation_sync()); case 32: cMtx_YrotS(*calc_mtx, player->shape_angle.y + 8000); spC8.x = 0.0f; @@ -1406,6 +1422,7 @@ static void demo_camera(b_gm_class* i_this) { a_this->current.pos = i_this->field_0x1cec; a_this->eyePos = i_this->field_0x1cec; + IF_DUSK(dusk::interp::request_presentation_sync()); i_this->field_0x1ce4 = -25000; i_this->field_0x1ce8 = -2500; @@ -1528,6 +1545,7 @@ static void demo_camera(b_gm_class* i_this) { if (i_this->mDemoModeTimer == 0) { i_this->mDemoCamCenter = spA4; i_this->mDemoCamEye = spB0; + IF_DUSK(dusk::interp::request_presentation_sync()); } else { cLib_addCalc2(&i_this->mDemoCamCenter.x, spA4.x, 0.1f, 5.0f); cLib_addCalc2(&i_this->mDemoCamCenter.z, spA4.z, 0.1f, 5.0f); @@ -1537,6 +1555,7 @@ static void demo_camera(b_gm_class* i_this) { if (i_this->mDemoModeTimer == 60) { i_this->mDemoMode = 41; + IF_DUSK(i_this->mDemoCamSyncTicks = 2); i_this->mDemoModeTimer = 40; i_this->mDemoCamFovy = 55.0f; camera->mCamera.SetTrimSize(3); @@ -1589,6 +1608,7 @@ static void demo_camera(b_gm_class* i_this) { } camera->mCamera.Reset(i_this->mDemoCamCenter, i_this->mDemoCamEye); + IF_DUSK(dusk::interp::request_presentation_sync()); camera->mCamera.Start(); camera->mCamera.SetTrimSize(0); dComIfGp_event_reset(); @@ -1606,6 +1626,12 @@ static void demo_camera(b_gm_class* i_this) { camera->mCamera.Set(center, eye, i_this->mDemoCamFovy, 0); i_this->mDemoModeTimer++; } +#if TARGET_PC + if (i_this->mDemoCamSyncTicks > 0) { + dusk::interp::request_presentation_sync(); + i_this->mDemoCamSyncTicks--; + } +#endif } static int daB_GM_Execute(b_gm_class* i_this) { diff --git a/src/d/actor/d_a_b_gnd.cpp b/src/d/actor/d_a_b_gnd.cpp index 50978a6691..4586cda039 100644 --- a/src/d/actor/d_a_b_gnd.cpp +++ b/src/d/actor/d_a_b_gnd.cpp @@ -17,10 +17,10 @@ #include "Z2AudioLib/Z2Instances.h" -#include "dusk/frame_interpolation.h" -#include "dusk/settings.h" #if TARGET_PC #include "dusk/achievements.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/settings.h" #endif class daB_GND_HIO_c : public JORReflexible { @@ -285,30 +285,6 @@ static int h_nodeCallBack(J3DJoint* i_joint, int param_2) { return 1; } -#if TARGET_PC -static void b_gnd_rein_interp_callback(bool isSimFrame, void* pUserWork) { - b_gnd_class* i_this = (b_gnd_class*)pUserWork; - if (!i_this->mReinsInterpPrevValid || !i_this->mReinsInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - for (int r = 0; r < 2; r++) { - cXyz* dst = i_this->mHorseReins[r].getPos(0); - for (int i = 0; i < 16; i++) { - const cXyz& p0 = i_this->mReinsInterpPrev[r][i]; - const cXyz& p1 = i_this->mReinsInterpCurr[r][i]; - dst[i] = p0 + (p1 - p0) * alpha; - } - } - cXyz* dst = i_this->field_0x21e8.getPos(0); - for (int i = 0; i < 2; i++) { - const cXyz& p0 = i_this->mReinsTexInterpPrev[i]; - const cXyz& p1 = i_this->mReinsTexInterpCurr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - static int daB_GND_Draw(b_gnd_class* i_this) { fopAc_ac_c* a_this = (fopAc_ac_c*)i_this; @@ -396,21 +372,6 @@ static int daB_GND_Draw(b_gnd_class* i_this) { i_this->field_0x21e8.update(2, l_color, &a_this->tevStr); dComIfGd_set3DlineMat(&i_this->field_0x21e8); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mReinsInterpCurrValid) { - memcpy(i_this->mReinsInterpPrev, i_this->mReinsInterpCurr, sizeof(i_this->mReinsInterpCurr)); - memcpy(i_this->mReinsTexInterpPrev, i_this->mReinsTexInterpCurr, sizeof(i_this->mReinsTexInterpCurr)); - i_this->mReinsInterpPrevValid = true; - } - for (int r = 0; r < 2; r++) { - memcpy(i_this->mReinsInterpCurr[r], i_this->mHorseReins[r].getPos(0), 16 * sizeof(cXyz)); - } - memcpy(i_this->mReinsTexInterpCurr, i_this->field_0x21e8.getPos(0), 2 * sizeof(cXyz)); - i_this->mReinsInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&b_gnd_rein_interp_callback, i_this); - } -#endif } return 1; @@ -1234,16 +1195,12 @@ static void b_gnd_h_end(b_gnd_class* i_this) { if (i_this->mDemoCamMode < 32) { i_this->mDemoCamMode = 32; -#if TARGET_PC - i_this->mDemoCamSyncTicks = 2; -#endif + IF_DUSK(i_this->mDemoCamSyncTicks = 2); } else { i_this->mDemoCamMode = 34; i_this->mDemoCamTimer = 0; i_this->mMoveMode = 2; -#if TARGET_PC - i_this->mDemoCamSyncTicks = 2; -#endif + IF_DUSK(i_this->mDemoCamSyncTicks = 2); } } break; @@ -3792,7 +3749,7 @@ static void demo_camera(b_gnd_class* i_this) { i_this->mDemoCamSyncTicks = 2; } if (i_this->mDemoCamSyncTicks > 0) { - dusk::frame_interp::request_presentation_sync(); + dusk::interp::request_presentation_sync(); i_this->mDemoCamSyncTicks--; } #endif diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp index a9d6d0db4f..aa9fb8a163 100644 --- a/src/d/actor/d_a_balloon_2D.cpp +++ b/src/d/actor/d_a_balloon_2D.cpp @@ -6,7 +6,6 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep #include "d/actor/d_a_balloon_2D.h" -#include "dusk/frame_interpolation.h" #include "JSystem/J2DGraph/J2DGrafContext.h" #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J2DGraph/J2DTextBox.h" @@ -21,6 +20,13 @@ #include "m_Do/m_Do_lib.h" #include +#if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" + +#include +#endif + class daBalloon2D_HIO_c : public mDoHIO_entry_c { public: inline daBalloon2D_HIO_c() { @@ -267,12 +273,12 @@ int daBalloon2D_c::draw() { int daBalloon2D_c::execute() { +#if TARGET_PC + updateOnWide(); +#else setAllAlpha(); setComboAlpha(); - - #if TARGET_PC - updateOnWide(); - #endif +#endif setHIO(false); return 1; @@ -280,6 +286,10 @@ int daBalloon2D_c::execute() { void daBalloon2D_c::drawMeter() { update(); +#if TARGET_PC + setAllAlpha(); + setComboAlpha(); +#endif mScreen->draw(0.0f, 0.0f, dComIfGp_getCurrentGrafPort()); drawAddScore(); } @@ -341,7 +351,7 @@ void daBalloon2D_c::setComboNum(u8 comboNum) { field_0x5a8[5]->changeTexture(resTimg, 0); resTimg = (ResTIMG*)dComIfGp_getMain2DArchive()->getResource('TIMG', dMeter2Info_getNumberTextureName(comboNum % 10)); field_0x5a8[6]->changeTexture(resTimg, 0); - setComboAlpha(); + IF_NOT_DUSK(setComboAlpha()); } void daBalloon2D_c::setBalloonSize(u8 balloonSize) { @@ -394,10 +404,14 @@ void daBalloon2D_c::setAllAlpha() { mAllAlpha = field_0x578->getAlphaRate(); if (isVisible()) { if (mAllAlpha != 1.0f) { +#if TARGET_PC + dusk::vdt::present_addCalc2(&mAllAlpha, 1.0f, 0.4f, 0.5f, 0.1f); +#else cLib_addCalc2(&mAllAlpha, 1.0f, 0.4f, 0.5f); if (fabsf(mAllAlpha - 1.0f) < 0.1f) { mAllAlpha = 1.0f; } +#endif field_0x578->setAlphaRate(mAllAlpha); field_0x580->setAlphaRate(l_HOSTIO.m.m5DNumberAlpha * mAllAlpha); field_0x594->setAlphaRate(l_HOSTIO.m.m2DNumberComboAlpha * mAllAlpha); @@ -406,10 +420,14 @@ void daBalloon2D_c::setAllAlpha() { field_0x5a0->setAlphaRate(l_HOSTIO.m.mBalloonSmallAlpha * mAllAlpha); } } else if (mAllAlpha != 1.0f) { +#if TARGET_PC + dusk::vdt::present_addCalc2(&mAllAlpha, 0.0f, 0.4f, 0.5f, 0.1f); +#else cLib_addCalc2(&mAllAlpha, 0.0f, 0.4f, 0.5f); if (fabsf(mAllAlpha) < 0.1f) { mAllAlpha = 0.0f; } +#endif field_0x578->setAlphaRate(mAllAlpha); field_0x580->setAlphaRate(l_HOSTIO.m.m5DNumberAlpha * mAllAlpha); field_0x594->setAlphaRate(l_HOSTIO.m.m2DNumberComboAlpha * mAllAlpha); @@ -424,10 +442,14 @@ void daBalloon2D_c::setComboAlpha() { mComboAlpha = field_0x5a4->getAlphaRate() * field_0x578->getAlphaRate(); if (mComboNum != 0) { if (mComboAlpha != 1.0f) { +#if TARGET_PC + dusk::vdt::present_addCalc2(&mComboAlpha, 1.0f, 0.4f, 0.5f, 0.1f); +#else cLib_addCalc2(&mComboAlpha, 1.0f, 0.4f, 0.5f); if (fabsf(mComboAlpha - 1.0f) < 0.1f) { mComboAlpha = 1.0f; } +#endif field_0x5a4->setAlphaRate(mComboAlpha); } } else if (mComboAlpha != 0.0f) { @@ -439,26 +461,20 @@ void daBalloon2D_c::setComboAlpha() { void daBalloon2D_c::drawAddScore() { for (s32 i = 19; i >= 0; i--) { if (field_0x5f8[i].field_0xe != 0) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - field_0x5f8[i].field_0xe--; - } + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0x5f8[i].field_0xe, 0.0f, 1.0f), field_0x5f8[i].field_0xe--); s32 score3; s32 score2; s32 score = field_0x5f8[i].field_0xc; +#if TARGET_PC + s16 temp0 = (s16)(fmodf(field_0x5f8[i].field_0xe, 60.0f) * 1024.0f); +#else s16 temp0 = (field_0x5f8[i].field_0xe % 60) * 1024; +#endif u8 local_88 = 0xff; f32 dVar11 = 30.0f; f32 dVar9 = 30.0f; -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f; - field_0x5f8[i].field_0x0.y -= 1.0f; - } + field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f IF_DUSK(* dusk::game_clock::original_frames()); + field_0x5f8[i].field_0x0.y -= 1.0f IF_DUSK(* dusk::game_clock::original_frames()); if (field_0x5f8[i].field_0xe < 10) { f32 fVar5 = field_0x5f8[i].field_0xe / 10.0f; local_88 = fVar5 * 255.0f; diff --git a/src/d/actor/d_a_bg.cpp b/src/d/actor/d_a_bg.cpp index 110e2aef15..051abc4172 100644 --- a/src/d/actor/d_a_bg.cpp +++ b/src/d/actor/d_a_bg.cpp @@ -624,9 +624,7 @@ int daBg_c::create() { dComIfGp_roomControl_onStatusFlag(roomNo, 0x10); OS_REPORT(" room%d\n", roomNo); -#if TARGET_PC - draw_interp_frame = true; -#endif + IF_DUSK(draw_interp_frame = true); return cPhs_COMPLEATE_e; } diff --git a/src/d/actor/d_a_bg_obj.cpp b/src/d/actor/d_a_bg_obj.cpp index da18ce4501..84a8995cb8 100644 --- a/src/d/actor/d_a_bg_obj.cpp +++ b/src/d/actor/d_a_bg_obj.cpp @@ -5,15 +5,18 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep -#include -#include -#include -#include "JSystem/J3DGraphBase/J3DMaterial.h" -#include "SSystem/SComponent/c_math.h" #include "d/actor/d_a_bg_obj.h" +#include "JSystem/J3DGraphBase/J3DMaterial.h" +#include +#include +#include #include "d/actor/d_a_set_bgobj.h" #include "d/d_s_play.h" +#include "SSystem/SComponent/c_math.h" + +#if TARGET_PC #include "helpers/string.hpp" +#endif static const char* getBmdName(int param_0, int param_1) { static char l_bmdName[16]; diff --git a/src/d/actor/d_a_boomerang.cpp b/src/d/actor/d_a_boomerang.cpp index 9deb7b46b8..96453cb221 100644 --- a/src/d/actor/d_a_boomerang.cpp +++ b/src/d/actor/d_a_boomerang.cpp @@ -14,6 +14,35 @@ #include "Z2AudioLib/Z2Instances.h" #include "SSystem/SComponent/c_math.h" +#if TARGET_PC +#include "dusk/interp/sight.h" + +namespace { +struct SightFrame { + cXyz position; + dusk::interp::SightAnimation animation; +}; + +void lerp(SightFrame& out, const SightFrame& a, const SightFrame& b, f32 step) { + dusk::interp::lerp(out.position, a.position, b.position, step); + dusk::interp::lerp(out.animation, a.animation, b.animation, step); +} + +struct SightSamples { + dusk::interp::Samples slots[BOOMERANG_LOCK_MAX + 1]; +}; + +SightFrame sight_frame(const daBoomerang_sight_c& sight, int i) { + return {sight.m_pos[i], {sight.field_0x98[i], sight.field_0xb0[i], + static_cast(sight.m_alpha[i]), static_cast(sight.m_cursorYellow2Brk->getFrameMax())}}; +} +} // namespace + +daBoomerang_sight_c::~daBoomerang_sight_c() { + dusk::interp::erase_owned_samples(this); +} +#endif + int daBoomerang_sight_c::createHeap() { void* tmpData; JKRArchive* archive = dComIfG_getObjectResInfo(daAlink_c::getAlinkArcName())->getArchive(); @@ -158,6 +187,7 @@ static const u32 l_lockSeFlg[BOOMERANG_LOCK_MAX] = { }; void daBoomerang_sight_c::initialize() { + IF_DUSK(dusk::interp::erase_owned_samples(this);) m_cursorYellowAllPane = m_cursorYellowScrn->search(MULTI_CHAR('n_all')); m_cursorYellow0Pane = m_cursorYellowScrn->search(MULTI_CHAR('cursor0')); m_cursorYellow1Pane = m_cursorYellowScrn->search(MULTI_CHAR('cursor1')); @@ -300,6 +330,7 @@ int daBoomerang_sight_c::playAnime(int param_0, int param_1) { } void daBoomerang_sight_c::initFrame(int i_no) { + IF_DUSK(dusk::interp::get(this).slots[i_no].reset();) m_alpha[i_no] = 0; field_0x98[i_no] = 4.0f; field_0xb0[i_no] = 0.0f; @@ -328,6 +359,10 @@ void daBoomerang_sight_c::copyNumData(int i_no) { cXyz temp_pos = m_pos[i_no]; m_pos[i_no] = m_pos[next_no]; m_pos[next_no] = temp_pos; +#if TARGET_PC + auto& slots = dusk::interp::get(this).slots; + std::swap(slots[i_no], slots[next_no]); +#endif } void daBoomerang_sight_c::setSight(const cXyz* i_pos, int i_no) { @@ -337,7 +372,13 @@ void daBoomerang_sight_c::setSight(const cXyz* i_pos, int i_no) { } Vec proj; - mDoLib_project(&m_pos[i_no], &proj); +#if TARGET_PC + auto& samples = dusk::interp::get(this).slots[i_no]; + const SightFrame current = sight_frame(*this, i_no); + samples.capture(¤t, 1); + cXyz position = samples.read(0, current).position; +#endif + mDoLib_project(DUSK_IF_ELSE(&position, &m_pos[i_no]), &proj); m_proj_posX[i_no] = proj.x; m_proj_posY[i_no] = proj.y; } @@ -357,11 +398,16 @@ void daBoomerang_sight_c::draw() { for (int i = 0; i < 6; i++, alpha_p++) { if (*alpha_p != 0) { - m_cursorYellowBck->setFrame(field_0x98[i]); - m_cursorYellowBpk->setFrame(field_0x98[i] > 21.0f ? 21.0f : field_0x98[i]); +#if TARGET_PC + setSight(NULL, i); + const auto frame = dusk::interp::get(this).slots[i].read(0, sight_frame(*this, i)).animation; +#endif + m_cursorYellowBck->setFrame(DUSK_IF_ELSE(frame.bck, field_0x98[i])); + m_cursorYellowBpk->setFrame(DUSK_IF_ELSE(frame.bck, field_0x98[i]) > 21.0f + ? 21.0f : DUSK_IF_ELSE(frame.bck, field_0x98[i])); if (i == 5) { - m_cursorYellow2Brk->setFrame(field_0xb0[i]); + m_cursorYellow2Brk->setFrame(DUSK_IF_ELSE(frame.brk, field_0xb0[i])); cursor0_pane = m_cursorYellow0Pane; cursor1_pane = m_cursorYellow1Pane; cursor2_pane = m_cursorYellow2Pane; @@ -369,7 +415,7 @@ void daBoomerang_sight_c::draw() { cursorAll_pane = m_cursorYellowAllPane; var_f31 = 80.0f; } else if (i == 0 && m_redSight) { - m_cursorRed2Brk->setFrame(field_0xb0[i]); + m_cursorRed2Brk->setFrame(DUSK_IF_ELSE(frame.brk, field_0xb0[i])); cursor0_pane = m_cursorRed0Pane; cursor1_pane = m_cursorRed1Pane; cursor2_pane = m_cursorRed2Pane; @@ -377,7 +423,7 @@ void daBoomerang_sight_c::draw() { cursorAll_pane = m_cursorRedAllPane; var_f31 = 35.0f; } else { - m_cursorOrange2Brk->setFrame(field_0xb0[i]); + m_cursorOrange2Brk->setFrame(DUSK_IF_ELSE(frame.brk, field_0xb0[i])); cursor0_pane = m_cursorOrange0Pane; cursor1_pane = m_cursorOrange1Pane; cursor2_pane = m_cursorOrange2Pane; @@ -389,15 +435,15 @@ void daBoomerang_sight_c::draw() { screen->animation(); cursorAll_pane->scale(0.6f, 0.6f); cursorAll_pane->translate(m_proj_posX[i], m_proj_posY[i]); - field_0x98[i] = field_0x98[i]; + IF_NOT_DUSK(field_0x98[i] = field_0x98[i]); - if (!(field_0x98[i] < 15.0f)) { - if (field_0x98[i] < 21.0f) { - var_f30 = var_f31 * (field_0x98[i] - 15.0f) * 0.16666667f; + if (!(DUSK_IF_ELSE(frame.bck, field_0x98[i]) < 15.0f)) { + if (DUSK_IF_ELSE(frame.bck, field_0x98[i]) < 21.0f) { + var_f30 = var_f31 * (DUSK_IF_ELSE(frame.bck, field_0x98[i]) - 15.0f) * 0.16666667f; } else if (i == 5) { - var_f30 = var_f31 * (*alpha_p * 0.00390625f + 0.5f); + var_f30 = var_f31 * (DUSK_IF_ELSE(frame.alpha, *alpha_p) * 0.00390625f + 0.5f); } else { - var_f30 = var_f31 * (*alpha_p * 0.0019607844f + 0.5f); + var_f30 = var_f31 * (DUSK_IF_ELSE(frame.alpha, *alpha_p) * 0.0019607844f + 0.5f); } cursor0_pane->translate(0.0f, -var_f30); @@ -406,6 +452,10 @@ void daBoomerang_sight_c::draw() { } screen->draw(0.0f, 0.0f, ctx); +#if TARGET_PC + } else { + dusk::interp::get(this).slots[i].reset(); +#endif } } } diff --git a/src/d/actor/d_a_demo00.cpp b/src/d/actor/d_a_demo00.cpp index 60a99d4d54..b89965a8ce 100644 --- a/src/d/actor/d_a_demo00.cpp +++ b/src/d/actor/d_a_demo00.cpp @@ -1103,14 +1103,8 @@ inline int daDemo00_c::execute() { case 2: { u16 sp0A = sp0E & 0x3FFF; if ((sp0E & 0xC000) == 0) { -#if !MOVIE_SUPPORT - // If movie support isn't available, automatically reset. - // TPHD-esque. Maybe not the best solution, but it works. - dComIfGp_event_reset(); -#else fopAcM_create(fpcNm_MOVIE_PLAYER_e, sp0A, NULL, fopAcM_GetRoomNo(this), NULL, NULL, 0xFF); mDoGph_gInf_c::fadeOut(1.0f); -#endif } else { switch (sp0A) { case 0: diff --git a/src/d/actor/d_a_door_shutter.cpp b/src/d/actor/d_a_door_shutter.cpp index 3b7cdcc5f2..b7a7286903 100644 --- a/src/d/actor/d_a_door_shutter.cpp +++ b/src/d/actor/d_a_door_shutter.cpp @@ -18,8 +18,9 @@ #include #if TARGET_PC -#include -#include +#include "dusk/autosave.h" + +#include "f_ap/f_ap_game.h" #endif char DUSK_CONST* daDoor20_c::getStopBmdName() { diff --git a/src/d/actor/d_a_e_bee.cpp b/src/d/actor/d_a_e_bee.cpp index 09909f9e0a..3cfadae812 100644 --- a/src/d/actor/d_a_e_bee.cpp +++ b/src/d/actor/d_a_e_bee.cpp @@ -15,6 +15,10 @@ #include "SSystem/SComponent/c_math.h" #include "Z2AudioLib/Z2Instances.h" +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#endif + static bool hio_set; static daE_Bee_HIO_c l_HIO; @@ -50,6 +54,26 @@ static int daE_Bee_Draw(e_bee_class* i_this) { return 1; } +#if TARGET_PC +static void bee_interp(bee_s* i_bee) { + if (!dusk::interp::is_enabled()) { + return; + } + + J3DModel* models[] = { + i_bee->mpModel1, + i_bee->mpModel2, + i_bee->mpModel3, + i_bee->mpModel4, + }; + MtxP mtx = mDoMtx_stack_c::get(); + for (int i = 0; i < 4; i++) { + models[i]->setBaseTRMtx(mtx); + models[i]->calc(); + } +} +#endif + static void bee_mtxset(bee_s* i_bee) { mDoMtx_stack_c::transS(i_bee->mPos.x, i_bee->mPos.y, i_bee->mPos.z); mDoMtx_stack_c::YrotM(i_bee->mAngle.y); @@ -62,6 +86,7 @@ static void bee_mtxset(bee_s* i_bee) { } else { i_bee->mpModel2->setBaseTRMtx(mDoMtx_stack_c::get()); } + IF_DUSK(bee_interp(i_bee)); } static void bee_ground_ang_set(bee_s* i_bee) { @@ -332,6 +357,7 @@ static void bee_nest_action(e_bee_class* i_this, bee_s* i_bee, s8 i_nestHealth) i_bee->mpModel4->setBaseTRMtx(mDoMtx_stack_c::get()); } } + IF_DUSK(bee_interp(i_bee)); if (i_nestHealth == 1) { i_bee->mAction = bee_s::ACT_FLY; diff --git a/src/d/actor/d_a_e_db.cpp b/src/d/actor/d_a_e_db.cpp index dcc3849756..65f91843a3 100644 --- a/src/d/actor/d_a_e_db.cpp +++ b/src/d/actor/d_a_e_db.cpp @@ -10,10 +10,6 @@ #include "f_op/f_op_kankyo_mng.h" #include "f_op/f_op_actor_enemy.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - class daE_DB_HIO_c : public JORReflexible { public: daE_DB_HIO_c(); @@ -70,22 +66,6 @@ static BOOL leaf_anm_init(e_db_class* i_this, int i_anm, f32 i_morf, u8 i_mode, return FALSE; } -#if TARGET_PC -static void daE_DB_interp_callback(bool isSimFrame, void* pUserWork) { - e_db_class* i_this = (e_db_class*)pUserWork; - if (!i_this->mStalkLineInterpPrevValid || !i_this->mStalkLineInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - cXyz* dst = i_this->stalkLine.getPos(0); - for (int i = 0; i < 12; i++) { - const cXyz& p0 = i_this->mStalkLineInterpPrev[i]; - const cXyz& p1 = i_this->mStalkLineInterpCurr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - static int daE_DB_Draw(e_db_class* i_this) { fopAc_ac_c* actor = &i_this->enemy; @@ -115,17 +95,6 @@ static int daE_DB_Draw(e_db_class* i_this) { static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF}; i_this->stalkLine.update(12, l_color, &actor->tevStr); dComIfGd_set3DlineMat(&i_this->stalkLine); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mStalkLineInterpCurrValid) { - memcpy(i_this->mStalkLineInterpPrev, i_this->mStalkLineInterpCurr, sizeof(i_this->mStalkLineInterpCurr)); - i_this->mStalkLineInterpPrevValid = true; - } - memcpy(i_this->mStalkLineInterpCurr, i_this->stalkLine.getPos(0), 12 * sizeof(cXyz)); - i_this->mStalkLineInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&daE_DB_interp_callback, i_this); - } -#endif for (int i = 1; i < 11; i++) { if (i_this->thornModel[i] != NULL) { diff --git a/src/d/actor/d_a_e_gb.cpp b/src/d/actor/d_a_e_gb.cpp index 09b6556548..a710c53511 100644 --- a/src/d/actor/d_a_e_gb.cpp +++ b/src/d/actor/d_a_e_gb.cpp @@ -15,6 +15,10 @@ #include "f_op/f_op_camera_mng.h" #include +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#endif + enum Head_Action { /* 0x0 */ HEAD_ACTION_WAIT, /* 0x1 */ HEAD_ACTION_ATTACK_1, @@ -1129,6 +1133,7 @@ static void demo_camera(e_gb_class* i_this) { i_this->keyYRot = KREG_S(4) + 0x5000; i_this->demoCamCenter.set(-5689.0f, 4747.0f, 15000.0f); i_this->demoCamEye.set(-5700.0f, 4740.0f, 14644.0f); + IF_DUSK(dusk::interp::request_presentation_sync()); i_this->demoCamCenterTarget.set(-5689.0f, 4740.0f, 14643.0f); i_this->demoCamEyeTarget.set(-5723.0f, 4733.0f, 14293.0f); i_this->demoCamEyeTargetDist.x = fabsf(i_this->demoCamEyeTarget.x - i_this->demoCamEye.x); @@ -1156,6 +1161,7 @@ static void demo_camera(e_gb_class* i_this) { if (i_this->demoCounter != 170) break; i_this->demoCamCenter.set(-5690.0f, 4432.0f, 9610.0f); i_this->demoCamEye.set(-5709.0f, 4445.0f, 9961.0f); + IF_DUSK(dusk::interp::request_presentation_sync()); i_this->demoCamCenterTarget.set(-5741.0f, 4432.0f, 10530.0f); i_this->demoCamEyeTarget.set(-5760.0f, 4496.0f, 10896.0f); i_this->demoCamEyeTargetDist.x = fabsf(i_this->demoCamEyeTarget.x - i_this->demoCamEye.x); @@ -1199,6 +1205,7 @@ static void demo_camera(e_gb_class* i_this) { mDoAud_seStart(Z2SE_EN_GF_OPEN, &actor->home.pos, 0, 0); i_this->demoCamCenter.set(-5706.0f, 4526.0f, 11379.0f); i_this->demoCamEye.set(-5687.0f, 4505.0f, 11008.0f); + IF_DUSK(dusk::interp::request_presentation_sync()); i_this->demoCamCenterTarget.set(-5624.0f, 4439.0f, 9818.0f); i_this->demoCamEyeTarget.set(-5604.0f, 4418.0f, 9447.0f); i_this->demoCamEyeTargetDist.x = fabsf(i_this->demoCamEyeTarget.x - i_this->demoCamEye.x); @@ -1270,6 +1277,11 @@ static void demo_camera(e_gb_class* i_this) { i_this->demoCamEye.x += YREG_F(13); i_this->demoCamEye.y += YREG_F(14) + 300.0f; i_this->demoCamEye.z += YREG_F(15) + -600.0f; +#if TARGET_PC + if (i_this->demoCounter == 0) { + dusk::interp::request_presentation_sync(); + } +#endif if (i_this->demoCounter == 100) { i_this->demoMode = 100; @@ -1278,6 +1290,7 @@ static void demo_camera(e_gb_class* i_this) { case 100: camera->mCamera.Reset(i_this->demoCamCenter, i_this->demoCamEye); + IF_DUSK(dusk::interp::request_presentation_sync()); camera->mCamera.Start(); camera->mCamera.SetTrimSize(0); dComIfGp_event_reset(); diff --git a/src/d/actor/d_a_e_hb.cpp b/src/d/actor/d_a_e_hb.cpp index f0dfbda3a6..b39f4750a1 100644 --- a/src/d/actor/d_a_e_hb.cpp +++ b/src/d/actor/d_a_e_hb.cpp @@ -9,10 +9,6 @@ #include "d/actor/d_a_e_hb_leaf.h" #include "f_op/f_op_actor_enemy.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - enum daE_HB_ACTION { ACTION_STAY, ACTION_APPEAR, @@ -68,22 +64,6 @@ static BOOL leaf_anm_init(e_hb_class* i_this, int i_anm, f32 i_morf, u8 i_mode, return FALSE; } -#if TARGET_PC -static void daE_HB_interp_callback(bool isSimFrame, void* pUserWork) { - e_hb_class* i_this = (e_hb_class*)pUserWork; - if (!i_this->mStalkLineInterpPrevValid || !i_this->mStalkLineInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - cXyz* dst = i_this->stalkLine.getPos(0); - for (int i = 0; i < 12; i++) { - const cXyz& p0 = i_this->mStalkLineInterpPrev[i]; - const cXyz& p1 = i_this->mStalkLineInterpCurr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - static int daE_HB_Draw(e_hb_class* i_this) { fopAc_ac_c* actor = &i_this->enemy; @@ -102,17 +82,6 @@ static int daE_HB_Draw(e_hb_class* i_this) { static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF}; i_this->stalkLine.update(12, l_color, &actor->tevStr); dComIfGd_set3DlineMat(&i_this->stalkLine); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mStalkLineInterpCurrValid) { - memcpy(i_this->mStalkLineInterpPrev, i_this->mStalkLineInterpCurr, sizeof(i_this->mStalkLineInterpCurr)); - i_this->mStalkLineInterpPrevValid = true; - } - memcpy(i_this->mStalkLineInterpCurr, i_this->stalkLine.getPos(0), 12 * sizeof(cXyz)); - i_this->mStalkLineInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&daE_HB_interp_callback, i_this); - } -#endif for (int i = 1; i < 11; i++) { if (i_this->thornModel[i] != NULL) { diff --git a/src/d/actor/d_a_e_mb.cpp b/src/d/actor/d_a_e_mb.cpp index 1fff337b33..9eaef0cc1c 100644 --- a/src/d/actor/d_a_e_mb.cpp +++ b/src/d/actor/d_a_e_mb.cpp @@ -12,8 +12,6 @@ #include "d/d_bomb.h" #include "c/c_damagereaction.h" #include "Z2AudioLib/Z2Instances.h" -#include "dusk/frame_interpolation.h" -#include "dusk/settings.h" #define ACTION_STANDBY 0 #define ACTION_WALK1 1 @@ -65,22 +63,6 @@ static void anm_init(e_mb_class* i_this, int i_anmID, f32 i_morf, u8 i_attr, f32 i_this->mAnm = i_anmID; } -#if TARGET_PC -static void e_mb_rope_interp_callback(bool isSimFrame, void* pUserWork) { - e_mb_class* i_this = (e_mb_class*)pUserWork; - if (!i_this->mRopeInterpPrevValid || !i_this->mRopeInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - cXyz* dst = i_this->mRopeMat.getPos(0); - for (int i = 0; i < 16; i++) { - const cXyz& p0 = i_this->mRopeInterpPrev[i]; - const cXyz& p1 = i_this->mRopeInterpCurr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - static int daE_MB_Draw(e_mb_class* i_this) { fopAc_ac_c* a_this = (fopAc_ac_c*)i_this; @@ -104,17 +86,6 @@ static int daE_MB_Draw(e_mb_class* i_this) { static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF}; i_this->mRopeMat.update(16, l_color, &a_this->tevStr); dComIfGd_set3DlineMat(&i_this->mRopeMat); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mRopeInterpCurrValid) { - memcpy(i_this->mRopeInterpPrev, i_this->mRopeInterpCurr, sizeof(i_this->mRopeInterpCurr)); - i_this->mRopeInterpPrevValid = true; - } - memcpy(i_this->mRopeInterpCurr, i_this->mRopeMat.getPos(0), 16 * sizeof(cXyz)); - i_this->mRopeInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&e_mb_rope_interp_callback, i_this); - } -#endif return 1; } diff --git a/src/d/actor/d_a_e_s1.cpp b/src/d/actor/d_a_e_s1.cpp index cd48a103e1..3c7c6700e5 100644 --- a/src/d/actor/d_a_e_s1.cpp +++ b/src/d/actor/d_a_e_s1.cpp @@ -14,8 +14,6 @@ #include "d/d_s_play.h" #include "f_op/f_op_actor_enemy.h" #include "f_op/f_op_camera_mng.h" -#include "dusk/frame_interpolation.h" -#include "dusk/settings.h" #include class daE_S1_HIO_c { @@ -101,32 +99,6 @@ static void anm_init(e_s1_class* i_this, int i_resNo, f32 i_morf, u8 i_attr, f32 i_this->mAnm = i_resNo; } -#if TARGET_PC -static void daE_S1_interp_callback(bool isSimFrame, void* pUserWork) { - e_s1_class* i_this = (e_s1_class*)pUserWork; - if (!i_this->mHairInterpPrevValid || !i_this->mHairInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - for (int s = 0; s < e_s1_class::HAIR_STRAND_COUNT; s++) { - cXyz* dst = i_this->mLineMat.getPos(s); - for (int i = 0; i < e_s1_class::HAIR_SEGMENT_COUNT; i++) { - int idx = s * e_s1_class::HAIR_SEGMENT_COUNT + i; - const cXyz& p0 = i_this->mHairInterpPrev[idx]; - const cXyz& p1 = i_this->mHairInterpCurr[idx]; - dst[i] = p0 + (p1 - p0) * alpha; - } - } - GXColor line_color; - line_color.r = JREG_S(0) + 5; - line_color.g = JREG_S(1) + 10; - line_color.b = JREG_S(2) + 10; - line_color.a = 0xFF; - - i_this->mLineMat.update(16, line_color, &i_this->tevStr); -} -#endif - static int daE_S1_Draw(e_s1_class* i_this) { if (i_this->field_0x306c != 0) { return 1; @@ -160,22 +132,6 @@ static int daE_S1_Draw(e_s1_class* i_this) { i_this->mLineMat.update(16, line_color, &i_this->tevStr); dComIfGd_set3DlineMatDark(&i_this->mLineMat); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mHairInterpCurrValid) { - memcpy(i_this->mHairInterpPrev, i_this->mHairInterpCurr, sizeof(i_this->mHairInterpCurr)); - i_this->mHairInterpPrevValid = true; - } - for (int s = 0; s < e_s1_class::HAIR_STRAND_COUNT; s++) { - cXyz* src = i_this->mLineMat.getPos(s); - memcpy(&i_this->mHairInterpCurr[s * e_s1_class::HAIR_SEGMENT_COUNT], src, - e_s1_class::HAIR_SEGMENT_COUNT * sizeof(cXyz)); - } - i_this->mHairInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&daE_S1_interp_callback, i_this); - } -#endif - dComIfGd_setList(); return 1; } @@ -2193,11 +2149,6 @@ static int daE_S1_Create(fopAc_ac_c* i_this) { return cPhs_ERROR_e; } -#if TARGET_PC - a_this->mHairInterpPrevValid = false; - a_this->mHairInterpCurrValid = false; -#endif - OS_REPORT("//////////////E_S1 SET 2 !!\n"); if (path_no != 0xFF) { diff --git a/src/d/actor/d_a_e_sm2.cpp b/src/d/actor/d_a_e_sm2.cpp index 808301527e..5c0d9838ea 100644 --- a/src/d/actor/d_a_e_sm2.cpp +++ b/src/d/actor/d_a_e_sm2.cpp @@ -14,10 +14,6 @@ #include "f_op/f_op_camera_mng.h" #include -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - class daE_SM2_HIO_c : public fOpAcm_HIO_entry_c { public: daE_SM2_HIO_c(); @@ -80,62 +76,9 @@ static int nodeCallBack(J3DJoint* i_joint, int param_1) { return 1; } -#if TARGET_PC -static void daE_SM2_interp_callback(bool isSimFrame, void* pUserWork) { - e_sm2_class* i_this = static_cast(pUserWork); - if (i_this == NULL) { - return; - } - - fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy; - g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr); - - if (!i_this->isPiece) { - if (i_this->modelMorf == NULL) { - return; - } - J3DModel* model = i_this->modelMorf->getModel(); - if (model == NULL) { - return; - } - g_env_light.setLightTevColorType_MAJI(model, &actor->tevStr); - - J3DMaterial* material = model->getModelData()->getMaterialNodePointer(0); - material->getTevKColor(1)->r = i_this->color_R; - material->getTevKColor(1)->g = i_this->color_G; - material->getTevKColor(1)->b = i_this->color_B; - material->getTevKColor(1)->a = 217.0f * i_this->color_alpha; - - if (i_this->pbtk != NULL) { - i_this->pbtk->entry(model->getModelData()); - } - } else { - if (i_this->pieceModelMorf == NULL) { - return; - } - J3DModel* model = i_this->pieceModelMorf->getModel(); - if (model == NULL) { - return; - } - - J3DMaterial* material = model->getModelData()->getMaterialNodePointer(0); - material->getTevKColor(1)->r = i_this->color_R; - material->getTevKColor(1)->g = i_this->color_G; - material->getTevKColor(1)->b = i_this->color_B; - material->getTevKColor(1)->a = 217.0f * i_this->color_alpha; - - g_env_light.setLightTevColorType_MAJI(model, &actor->tevStr); - } -} -#endif - static int daE_SM2_Draw(e_sm2_class* i_this) { fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy; -#if TARGET_PC - dusk::frame_interp::add_interpolation_callback(&daE_SM2_interp_callback, i_this); -#endif - g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr); J3DModel* model; diff --git a/src/d/actor/d_a_e_wb.cpp b/src/d/actor/d_a_e_wb.cpp index b5a38eff65..cf9e4ccbf7 100644 --- a/src/d/actor/d_a_e_wb.cpp +++ b/src/d/actor/d_a_e_wb.cpp @@ -18,10 +18,12 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "res/Object/Always.h" -#include "dusk/dusk.h" -#include "dusk/frame_interpolation.h" #include +#if TARGET_PC +#include "dusk/dusk.h" +#include "dusk/interp/frame_interpolation.h" +#endif class daE_WB_HIO_c : public JORReflexible { public: @@ -186,30 +188,6 @@ static bool hio_set; static daE_WB_HIO_c l_HIO; -#if TARGET_PC -static void e_wb_rein_interp_callback(bool isSimFrame, void* pUserWork) { - e_wb_class* i_this = (e_wb_class*)pUserWork; - if (!i_this->himo_interp_prev_valid || !i_this->himo_interp_curr_valid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - for (int r = 0; r < 2; r++) { - cXyz* dst = i_this->himo_mat[r].getPos(0); - for (int i = 0; i < 16; i++) { - const cXyz& p0 = i_this->himo_mat_interp_prev[r][i]; - const cXyz& p1 = i_this->himo_mat_interp_curr[r][i]; - dst[i] = p0 + (p1 - p0) * alpha; - } - } - cXyz* dst = i_this->himo_tex.getPos(0); - for (int i = 0; i < 2; i++) { - const cXyz& p0 = i_this->himo_tex_interp_prev[i]; - const cXyz& p1 = i_this->himo_tex_interp_curr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - static void himo_control1(e_wb_class* i_this, cXyz* i_pos, int i_no, s8 param_3) { fopEn_enemy_c* enemy = &i_this->enemy; cXyz mae, ato; @@ -534,21 +512,6 @@ static int daE_WB_Draw(e_wb_class* i_this) { dComIfGd_set3DlineMat(&i_this->himo_mat[1]); i_this->himo_tex.update(2, l_color, &actor->tevStr); dComIfGd_set3DlineMat(&i_this->himo_tex); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->himo_interp_curr_valid) { - memcpy(i_this->himo_mat_interp_prev, i_this->himo_mat_interp_curr, sizeof(i_this->himo_mat_interp_curr)); - memcpy(i_this->himo_tex_interp_prev, i_this->himo_tex_interp_curr, sizeof(i_this->himo_tex_interp_curr)); - i_this->himo_interp_prev_valid = true; - } - for (int r = 0; r < 2; r++) { - memcpy(i_this->himo_mat_interp_curr[r], i_this->himo_mat[r].getPos(0), 16 * sizeof(cXyz)); - } - memcpy(i_this->himo_tex_interp_curr, i_this->himo_tex.getPos(0), 2 * sizeof(cXyz)); - i_this->himo_interp_curr_valid = true; - dusk::frame_interp::add_interpolation_callback(&e_wb_rein_interp_callback, i_this); - } -#endif } return 1; @@ -4542,9 +4505,7 @@ static void demo_camera(e_wb_class* i_this) { i_this->demo_cam_way_spd.z = fabsf(i_this->demo_cam_way.z - i_this->demo_cam_ctr.z); i_this->demo_cam_morf = 0; pla->setPlayerPosAndAngle(&pla->current.pos, pla->shape_angle.y - 4000, 0); -#if TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); } if (i_this->demo_timer == 345) { daPy_getPlayerActorClass()->setThrowDamage(boss->enemy.shape_angle.y - 8000 + TREG_S(8), @@ -4791,9 +4752,7 @@ static void demo_camera(e_wb_class* i_this) { i_this->demo_cam_eye.x += 300.0f + VREG_F(8); i_this->demo_cam_eye.y += 150.0f + VREG_F(9); i_this->demo_cam_eye.z -= 1400.0f + VREG_F(10); -#if TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); } } else { i_this->demo_cam_eye = enemy->current.pos; @@ -5054,7 +5013,7 @@ static void demo_camera(e_wb_class* i_this) { i_this->demo_cam_sync_ticks = 2; } if (i_this->demo_cam_sync_ticks > 0) { - dusk::frame_interp::request_presentation_sync(); + dusk::interp::request_presentation_sync(); i_this->demo_cam_sync_ticks--; } #endif diff --git a/src/d/actor/d_a_e_yd.cpp b/src/d/actor/d_a_e_yd.cpp index 52a5be7fdc..bdc7c527e2 100644 --- a/src/d/actor/d_a_e_yd.cpp +++ b/src/d/actor/d_a_e_yd.cpp @@ -12,10 +12,6 @@ #include "d/d_cc_uty.h" #include "f_op/f_op_actor_enemy.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - class daE_YD_HIO_c { public: daE_YD_HIO_c(); @@ -77,22 +73,6 @@ static s32 leaf_anm_init(e_yd_class* i_this, int param_1, f32 param_2, u8 param_ return false; } -#if TARGET_PC -static void daE_YD_interp_callback(bool isSimFrame, void* pUserWork) { - e_yd_class* i_this = (e_yd_class*)pUserWork; - if (!i_this->mLineMatInterpPrevValid || !i_this->mLineMatInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - cXyz* dst = i_this->mLineMat.getPos(0); - for (int i = 0; i < 12; i++) { - const cXyz& p0 = i_this->mLineMatInterpPrev[i]; - const cXyz& p1 = i_this->mLineMatInterpCurr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - static s32 daE_YD_Draw(e_yd_class* i_this) { static GXColor l_color = { 0x14, 0x0F, 0x00, 0xFF }; @@ -106,17 +86,6 @@ static s32 daE_YD_Draw(e_yd_class* i_this) { i_this->mpMorf->entryDL(); i_this->mLineMat.update(12, l_color, &i_this->actor.tevStr); dComIfGd_set3DlineMat(&i_this->mLineMat); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mLineMatInterpCurrValid) { - memcpy(i_this->mLineMatInterpPrev, i_this->mLineMatInterpCurr, sizeof(i_this->mLineMatInterpCurr)); - i_this->mLineMatInterpPrevValid = true; - } - memcpy(i_this->mLineMatInterpCurr, i_this->mLineMat.getPos(0), 12 * sizeof(cXyz)); - i_this->mLineMatInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&daE_YD_interp_callback, i_this); - } -#endif for (s32 i = 1; i < 11; i++) { if (i_this->field_0x77c[i] != 0) { g_env_light.setLightTevColorType_MAJI(i_this->field_0x77c[i], &i_this->actor.tevStr); diff --git a/src/d/actor/d_a_e_yg.cpp b/src/d/actor/d_a_e_yg.cpp index 71756d6f57..e6bcd05098 100644 --- a/src/d/actor/d_a_e_yg.cpp +++ b/src/d/actor/d_a_e_yg.cpp @@ -10,8 +10,6 @@ #include "f_op/f_op_kankyo_mng.h" #include "d/actor/d_a_obj_carry.h" #include "Z2AudioLib/Z2Instances.h" -#include "dusk/frame_interpolation.h" -#include "dusk/settings.h" #include "f_op/f_op_actor_enemy.h" enum E_yg_RES_File_ID { @@ -136,33 +134,6 @@ static BOOL pl_check(e_yg_class* i_this, f32 i_dist) { return FALSE; } -#if TARGET_PC -static void daE_YG_interp_callback(bool isSimFrame, void* pUserWork) { - e_yg_class* i_this = (e_yg_class*)pUserWork; - fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->actor; - if (!i_this->mTentacleInterpPrevValid || !i_this->mTentacleInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - for (int s = 0; s < e_yg_class::TENTACLE_STRAND_COUNT; s++) { - cXyz* dst = i_this->mLineMat.getPos(s); - for (int i = 0; i < e_yg_class::TENTACLE_SEGMENT_COUNT; i++) { - int idx = s * e_yg_class::TENTACLE_SEGMENT_COUNT + i; - const cXyz& p0 = i_this->mTentacleInterpPrev[idx]; - const cXyz& p1 = i_this->mTentacleInterpCurr[idx]; - dst[i] = p0 + (p1 - p0) * alpha; - } - } - GXColor color; - color.r = JREG_S(0) + 20; - color.g = JREG_S(1) + 20; - color.b = JREG_S(2) + 20; - color.a = 0xFF; - - i_this->mLineMat.update(10, color, &actor->tevStr); -} -#endif - static int daE_YG_Draw(e_yg_class* i_this) { if (i_this->mDispFlag) { return 1; @@ -190,22 +161,6 @@ static int daE_YG_Draw(e_yg_class* i_this) { i_this->mLineMat.update(10, color, &actor->tevStr); dComIfGd_set3DlineMatDark(&i_this->mLineMat); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mTentacleInterpCurrValid) { - memcpy(i_this->mTentacleInterpPrev, i_this->mTentacleInterpCurr, sizeof(i_this->mTentacleInterpCurr)); - i_this->mTentacleInterpPrevValid = true; - } - for (int s = 0; s < e_yg_class::TENTACLE_STRAND_COUNT; s++) { - cXyz* src = i_this->mLineMat.getPos(s); - memcpy(&i_this->mTentacleInterpCurr[s * e_yg_class::TENTACLE_SEGMENT_COUNT], src, - e_yg_class::TENTACLE_SEGMENT_COUNT * sizeof(cXyz)); - } - i_this->mTentacleInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&daE_YG_interp_callback, i_this); - } -#endif - dComIfGd_setList(); return 1; @@ -1424,11 +1379,6 @@ static cPhs_Step daE_YG_Create(fopAc_ac_c* actor) { return cPhs_ERROR_e; } -#if TARGET_PC - i_this->mTentacleInterpPrevValid = false; - i_this->mTentacleInterpCurrValid = false; -#endif - if (!hio_set) { i_this->mIsFirstSpawn = 1; hio_set = true; diff --git a/src/d/actor/d_a_e_yh.cpp b/src/d/actor/d_a_e_yh.cpp index fb00a4bab1..55f175d78e 100644 --- a/src/d/actor/d_a_e_yh.cpp +++ b/src/d/actor/d_a_e_yh.cpp @@ -12,10 +12,6 @@ #include "f_op/f_op_actor_enemy.h" #include "f_op/f_op_kankyo_mng.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - class daE_YH_HIO_c : public JORReflexible { public: daE_YH_HIO_c(); @@ -89,22 +85,6 @@ static BOOL leaf_anm_init(e_yh_class* i_this, int param_2, f32 param_3, u8 param return FALSE; } -#if TARGET_PC -static void daE_YH_interp_callback(bool isSimFrame, void* pUserWork) { - e_yh_class* i_this = (e_yh_class*)pUserWork; - if (!i_this->mLineInterpPrevValid || !i_this->mLineInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - cXyz* dst = i_this->mLine.getPos(0); - for (int i = 0; i < 12; i++) { - const cXyz& p0 = i_this->mLineInterpPrev[i]; - const cXyz& p1 = i_this->mLineInterpCurr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - static int daE_YH_Draw(e_yh_class* i_this) { fopAc_ac_c* a_this = (fopAc_ac_c*)i_this; @@ -134,17 +114,6 @@ static int daE_YH_Draw(e_yh_class* i_this) { i_this->mLine.update(12, l_color, &a_this->tevStr); dComIfGd_set3DlineMat(&i_this->mLine); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mLineInterpCurrValid) { - memcpy(i_this->mLineInterpPrev, i_this->mLineInterpCurr, sizeof(i_this->mLineInterpCurr)); - i_this->mLineInterpPrevValid = true; - } - memcpy(i_this->mLineInterpCurr, i_this->mLine.getPos(0), 12 * sizeof(cXyz)); - i_this->mLineInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&daE_YH_interp_callback, i_this); - } -#endif for (int i = 1; i < 11; i++) { if (i_this->mModels[i] != NULL) { diff --git a/src/d/actor/d_a_horse.cpp b/src/d/actor/d_a_horse.cpp index 72b1fb1e7b..b738460bb8 100644 --- a/src/d/actor/d_a_horse.cpp +++ b/src/d/actor/d_a_horse.cpp @@ -20,23 +20,6 @@ #include #include -#if TARGET_PC -#include "dusk/dusk.h" -#include "dusk/frame_interpolation.h" - -namespace { -// FRAME INTERP NOTE: Sim tick control point snapshots for interpolation -constexpr int kHorseReinSimMax = 75; -cXyz s_horseReinSimPrev[kHorseReinSimMax]; -cXyz s_horseReinSimCurr[kHorseReinSimMax]; -int s_horseReinSimNumPrev; -int s_horseReinSimNumCurr; -bool s_horseReinSimPrevValid; -bool s_horseReinSimCurrValid; -uint64_t s_horseReinSimRolledSeq; -} // namespace -#endif - #define ANM_HS_BACK_WALK 6 #define ANM_HS_WALK_START 7 #define ANM_HS_EXCITEMENT 8 @@ -3033,24 +3016,6 @@ void daHorse_c::copyReinPos() { for (i = rein->field_0x8[0] - 1; i >= 0; i--, pos_p++) { *pos_p = rein->field_0x0[0][i]; } -#if TARGET_PC - if (field_0x1204 > 0) { - const uint64_t simSeq = dusk::frame_interp::sim_tick_seq(); - if (simSeq != s_horseReinSimRolledSeq) { - s_horseReinSimRolledSeq = simSeq; - if (s_horseReinSimCurrValid && s_horseReinSimNumCurr > 0) { - memcpy(s_horseReinSimPrev, s_horseReinSimCurr, s_horseReinSimNumCurr * sizeof(cXyz)); - s_horseReinSimNumPrev = s_horseReinSimNumCurr; - s_horseReinSimPrevValid = true; - } - } - memcpy(s_horseReinSimCurr, m_reinLine.getPos(0), field_0x1204 * sizeof(cXyz)); - s_horseReinSimNumCurr = field_0x1204; - s_horseReinSimCurrValid = true; - } else { - s_horseReinSimCurrValid = false; - } -#endif } void daHorse_c::setReinPosHandSubstance(int param_0) { @@ -3162,30 +3127,6 @@ void daHorse_c::setReinPosNormalSubstance() { copyReinPos(); } -#if TARGET_PC -void daHorse_c::lerpControlPoints(f32 alpha) { - // FRAME INTERP NOTE: Currently only lerping points for Epona's reins. Need a more global solution. - if (!dusk::frame_interp::is_enabled() || !s_horseReinSimPrevValid || !s_horseReinSimCurrValid) { - return; - } - const int nCurr = s_horseReinSimNumCurr; - const int nPrev = s_horseReinSimNumPrev; - if (nCurr <= 0) { - return; - } - int n = nPrev < nCurr ? nPrev : nCurr; - if (n <= 0 || n > kHorseReinSimMax) { - return; - } - cXyz* dst = m_reinLine.getPos(0); - for (int i = 0; i < n; i++) { - const cXyz& p0 = s_horseReinSimPrev[i]; - const cXyz& p1 = s_horseReinSimCurr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - void daHorse_c::bgCheck() { if (m_procID != PROC_LARGE_DAMAGE_e) { static DUSK_CONSTEXPR cXyz localCenterPos(0.0f, 100.0f, 0.0f); diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index cc83028b9c..4f175e794f 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -11,9 +11,10 @@ #include "d/d_com_inf_game.h" #if TARGET_PC -#include #include "dusk/dvd_asset.hpp" -#include "dusk/frame_interpolation.h" +#include "dusk/interp/frame_interpolation.h" + +#include #include @@ -211,8 +212,8 @@ void daMant_packet_c::draw() { MtxP src25 = model->getAnmMtx(25); Mtx joint_34_scratch; Mtx joint_25_scratch; - MtxP joint_34 = dusk::frame_interp::lookup_replacement(src34, joint_34_scratch) ? joint_34_scratch : src34; - MtxP joint_25 = dusk::frame_interp::lookup_replacement(src25, joint_25_scratch) ? joint_25_scratch : src25; + MtxP joint_34 = dusk::interp::lookup_replacement(src34, joint_34_scratch) ? joint_34_scratch : src34; + MtxP joint_25 = dusk::interp::lookup_replacement(src25, joint_25_scratch) ? joint_25_scratch : src25; cXyz presented_anchor_a; cXyz presented_anchor_b; @@ -231,7 +232,7 @@ void daMant_packet_c::draw() { } } - const f32 step = dusk::frame_interp::get_interpolation_step(); + const f32 step = dusk::interp::get_interpolation_step(); for (int i = 0; i < 169; ++i) { cXyz curr_local; MTXMultVec(curr_frame_inverse, &curr_pos[i], &curr_local); diff --git a/src/d/actor/d_a_mg_rod.cpp b/src/d/actor/d_a_mg_rod.cpp index 6cf1e76024..53dba36b74 100644 --- a/src/d/actor/d_a_mg_rod.cpp +++ b/src/d/actor/d_a_mg_rod.cpp @@ -26,7 +26,6 @@ #include #if TARGET_PC -#include "dusk/frame_interpolation.h" #include "dusk/mods/item.hpp" #include "dusk/settings.h" #include "dusk/version.hpp" @@ -183,25 +182,6 @@ static int Worm_nodeCallBack(J3DJoint* i_joint, int param_1) { return 1; } -#if TARGET_PC -static void dmg_rod_interp_callback(bool isSimFrame, void* pUserWork) { - dmg_rod_class* i_this = (dmg_rod_class*)pUserWork; - if (!i_this->mLineInterpPrevValid || !i_this->mLineInterpCurrValid) { - return; - } - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - const int count = i_this->kind == MG_ROD_KIND_LURE ? MG_ROD_LURE_LINE_LEN : MG_ROD_UKI_LINE_LEN; - cXyz* dst = i_this->linemat.getPos(0); - for (int i = 0; i < count; i++) { - const cXyz& p0 = i_this->mLineInterpPrev[i]; - const cXyz& p1 = i_this->mLineInterpCurr[i]; - dst[i] = p0 + (p1 - p0) * alpha; - } - static GXColor l_color = {0xFF, 0xFF, 0x96, 0xFF}; - i_this->linemat.update(count, l_color, &i_this->actor.tevStr); -} -#endif - static int dmg_rod_Draw(dmg_rod_class* i_this) { int unused; fopAc_ac_c* actor = &i_this->actor; @@ -242,18 +222,6 @@ static int dmg_rod_Draw(dmg_rod_class* i_this) { i_this->linemat.update(MG_ROD_LURE_LINE_LEN, l_color, &i_this->actor.tevStr); dComIfGd_set3DlineMat(&i_this->linemat); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mLineInterpCurrValid) { - memcpy(i_this->mLineInterpPrev, i_this->mLineInterpCurr, MG_ROD_LURE_LINE_LEN * sizeof(cXyz)); - i_this->mLineInterpPrevValid = true; - } - memcpy(i_this->mLineInterpCurr, i_this->linemat.getPos(0), MG_ROD_LURE_LINE_LEN * sizeof(cXyz)); - i_this->mLineInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&dmg_rod_interp_callback, i_this); - } -#endif - model = i_this->rod_modelMorf->getModel(); g_env_light.setLightTevColorType_MAJI(model, &i_this->actor.tevStr); i_this->rod_modelMorf->entryDL(); @@ -278,18 +246,6 @@ static int dmg_rod_Draw(dmg_rod_class* i_this) { i_this->linemat.update(MG_ROD_UKI_LINE_LEN, l_color, &i_this->actor.tevStr); dComIfGd_set3DlineMat(&i_this->linemat); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mLineInterpCurrValid) { - memcpy(i_this->mLineInterpPrev, i_this->mLineInterpCurr, MG_ROD_UKI_LINE_LEN * sizeof(cXyz)); - i_this->mLineInterpPrevValid = true; - } - memcpy(i_this->mLineInterpCurr, i_this->linemat.getPos(0), MG_ROD_UKI_LINE_LEN * sizeof(cXyz)); - i_this->mLineInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&dmg_rod_interp_callback, i_this); - } -#endif - for (int i = 0; i < 15; i++) { g_env_light.setLightTevColorType_MAJI(i_this->rod_uki_model[i], &actor->tevStr); mDoExt_modelUpdateDL(i_this->rod_uki_model[i]); @@ -6481,11 +6437,6 @@ static int dmg_rod_Create(fopAc_ac_c* i_this) { return cPhs_ERROR_e; } -#if TARGET_PC - rod->mLineInterpPrevValid = false; - rod->mLineInterpCurrValid = false; -#endif - OS_REPORT("//////////////MG_ROD SET 2 !!\n"); if (!hio_set) { rod->HIOInit = TRUE; diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index a50753cdf0..f58b6e68d4 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -14,7 +14,10 @@ #include "d/d_msg_object.h" #include "d/d_s_play.h" #include "d/d_debug_viewer.h" -#include "dusk/frame_interpolation.h" + +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#endif static f32 dummy_lit_3777(int idx, u8 foo) { Vec dummy_vec = {0.0f, 0.0f, 0.0f}; @@ -1106,10 +1109,10 @@ void daMidna_c::setBodyPartMatrix() { mpModel->setAnmMtx(i, mpShadowModel->getAnmMtx(i)); } mpModel->calcWeightEnvelopeMtx(); -#ifdef TARGET_PC +#if TARGET_PC // FRAME INTERP NOTE: Record weight envelopes for Midna here, as they are otherwise missed causing distortion for (u16 i = 0; i < mpModel->getModelData()->getWEvlpMtxNum(); i++) { - dusk::frame_interp::record_final_mtx(mpModel->getWeightAnmMtx(i)); + dusk::interp::record_final_mtx(mpModel->getWeightAnmMtx(i)); } #endif } @@ -2164,6 +2167,13 @@ void daMidna_c::setAnm() { } if (anm == ANM_S_APPEAR || anm == ANM_S_APPEARBL) { +#if TARGET_PC + mpShadowModel->forgetMtx(); + mpShadowMaskBmd->forgetMtx(); + mpShadowHandsBmd->forgetMtx(); + mpShadowHairhandBmd->forgetMtx(); + mpGokouBmd->forgetMtx(); +#endif mSound.startCreatureSound(Z2SE_MIDNA_APPEAR, 0, -1); } else if (anm == ANM_S_RETURN || anm == ANM_RETURN) { mSound.startCreatureSound(Z2SE_MIDNA_DISAPPEAR, 0, -1); diff --git a/src/d/actor/d_a_mirror.cpp b/src/d/actor/d_a_mirror.cpp index 486a035590..123279e8af 100644 --- a/src/d/actor/d_a_mirror.cpp +++ b/src/d/actor/d_a_mirror.cpp @@ -13,13 +13,6 @@ #include #include #include "m_Do/m_Do_lib.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - -#ifndef __MWERKS__ -#include "helpers/math.h" -#endif static BOOL daMirror_c_createHeap(fopAc_ac_c* i_this) { return ((daMirror_c*)i_this)->createHeap(); @@ -30,7 +23,7 @@ static DUSK_CONSTEXPR char DUSK_CONST* l_arcName = "Mirror"; static DUSK_CONSTEXPR char DUSK_CONST* l_arcName2 = "MR-Table"; dMirror_packet_c::dMirror_packet_c() { -#ifdef TARGET_PC +#if TARGET_PC GXInitTexObj(&mTexObj, nullptr, 0, 0, static_cast(-1), GX_MAX_TEXWRAPMODE, GX_MAX_TEXWRAPMODE, GX_FALSE); #endif diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index 5f4290611f..aabe220578 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -14,34 +14,21 @@ #pragma optimization_level 4 #pragma optimize_for_size off -#include -#include +#include "JSystem/JKernel/JKRExpHeap.h" #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASDriverIF.h" -#include "JSystem/JKernel/JKRExpHeap.h" -#include "Z2AudioLib/Z2Instances.h" #include "d/actor/d_a_movie_player.h" - -#include - +#include "Z2AudioLib/Z2Instances.h" #include "f_op/f_op_overlap_mng.h" +#include + +#if TARGET_PC +#include "dusk/layout.hpp" +#include "dusk/os.h" +#include "helpers/gx_helper.h" #include "JSystem/JAudio2/JASCriticalSection.h" -#if TARGET_PC -#include "helpers/gx_helper.h" -#include "dusk/os.h" -#include "dusk/layout.hpp" -#if MOVIE_SUPPORT -#include "turbojpeg.h" -#endif -#endif - -inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) { - return *(BE(s32)*)readBuf->ptr; -} - -#if TARGET_PC // idk what OS_THREAD_ATTR_DETACH does, and it stops OSThreadJoin() // probably the difference doesn't matter since we are using OS threads anyways. #define OS_THREAD_ATTR 0 @@ -49,7 +36,12 @@ inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) { #define OS_THREAD_ATTR OS_THREAD_ATTR_DETACH #endif -#if defined(__cplusplus) && !TARGET_PC +inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) { + return *(BE(s32)*)readBuf->ptr; +} + +#if !TARGET_PC +#if defined(__cplusplus) extern "C" { #endif @@ -217,7 +209,6 @@ static void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr) { info->encodeData++; } -#if !TARGET_PC ATTRIBUTE_ALIGN(32) static u8 THPStatistics[1120]; ATTRIBUTE_ALIGN(32) static THPHuffmanTab* Ydchuff; @@ -2587,104 +2578,9 @@ static void __THPHuffDecodeDCTCompV(__REGISTER THPFileInfo* info, THPCoeff* bloc #else // !TARGET_PC static daMP_THPPlayer daMP_ActivePlayer; - -#if MOVIE_SUPPORT -static std::vector FixedJpegData; -static tjhandle JpegDecompressHandle; - -static const std::vector& FixJpeg(const std::span data) { - FixedJpegData.resize(0); - FixedJpegData.reserve(data.size()); - - size_t startOfScanLocation = 0; - for (; startOfScanLocation < data.size() - 1; startOfScanLocation++) { - if (data[startOfScanLocation] == 0xFF && data[startOfScanLocation + 1] == 0xDA) { - goto sosFound; - } - } - - CRASH("Unable to find SOS marker!"); - - sosFound: - - startOfScanLocation += 2; // TODO: Skip entire SOS header? - - size_t endOfImage = data.size() - 1; - for (; endOfImage > startOfScanLocation; endOfImage--) { - if (data[endOfImage] == 0xFF && data[endOfImage + 1] == 0xD9) { - goto eoiFound; - } - } - - CRASH("Unable to find EOI marker!"); - eoiFound: - - // Copy data before SOS - for (size_t i = 0; i < startOfScanLocation; i++) { - FixedJpegData.push_back(data[i]); - } - - // Copy data inside SOS, fixing up lacking of "byte shuffling" - for (size_t i = startOfScanLocation; i < endOfImage; i++) { - u8 value = data[i]; - FixedJpegData.push_back(value); - if (value == 0xFF) { - FixedJpegData.push_back(0x00); - } - } - - // Copy data after SOS. - for (size_t i = endOfImage; i < data.size(); i++) { - FixedJpegData.push_back(data[i]); - } - - return FixedJpegData; -} - -static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, void* tileV, void*) { - assert(JpegDecompressHandle); - - const auto handle = JpegDecompressHandle; - const auto fixedData = FixJpeg(std::span(static_cast(file), fileSize)); - - auto ret = tj3DecompressHeader(handle, fixedData.data(), fixedData.size()); - if (ret == -1) { - OSReport_Error("Parsing JPEG header failed: %s", tj3GetErrorStr(handle)); - return 1; - } - - if (tj3Get(handle, TJPARAM_JPEGWIDTH) != daMP_ActivePlayer.videoInfo.xSize) { - OSReport_Error("Invalid width in video frame!"); - return 1; - } - - if (tj3Get(handle, TJPARAM_JPEGHEIGHT) != daMP_ActivePlayer.videoInfo.ySize) { - OSReport_Error("Invalid height in video frame!"); - return 1; - } - - ret = tj3Set(handle, TJPARAM_SUBSAMP, TJSAMP_420); - if (ret != 0) { - OSReport_Error("Failed to set subsampling mode: %s", tj3GetErrorStr(handle)); - return 1; - } - - u8* planes[3] = {static_cast(tileY), static_cast(tileU), static_cast(tileV)}; - ret = tj3DecompressToYUVPlanes8(handle, fixedData.data(), fixedData.size(), planes, nullptr); - if (ret != 0) { - OSReport_Error("Image decompression failed: %s", tj3GetErrorStr(handle)); - return 1; - } - - return 0; -} -#else // MOVIE_SUPPORT -static s32 THPVideoDecode(void*, size_t, void*, void*, void*, void*) { - return 1; // Immediate error. -} -#endif #endif +#if !TARGET_PC static BOOL THPInit() { #if !TARGET_PC u8* base; @@ -2712,9 +2608,10 @@ static BOOL THPInit() { return TRUE; } -#if defined(__cplusplus) && !TARGET_PC +#if defined(__cplusplus) } #endif +#endif // !TARGET_PC #if !TARGET_PC // Defined earlier in file. static daMP_THPPlayer daMP_ActivePlayer; @@ -2937,9 +2834,6 @@ static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) { case 0: { if ((daMP_ActivePlayer.videoError = THPVideoDecode( tile, -#if TARGET_PC - *tileOffsets, -#endif textureSet->ytexture, textureSet->utexture, textureSet->vtexture, daMP_ActivePlayer.thpWork))) { @@ -3434,26 +3328,19 @@ static void daMP_THPGXYuv2RgbDraw(u8* y_data, u8* u_data, u8* v_data, s16 x, TGXTexObj tobj0; TGXTexObj tobj1; TGXTexObj tobj2; -#if TARGET_PC -#define FMT (GXTexFmt)GX_TF_R8_PC -#else -#define FMT GX_TF_I8 -#endif - GXInitTexObj(&tobj0, y_data, textureWidth, textureHeight, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObj(&tobj0, y_data, textureWidth, textureHeight, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj0, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj0, GX_TEXMAP0); - GXInitTexObj(&tobj1, u_data, textureWidth >> 1, textureHeight >> 1, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObj(&tobj1, u_data, textureWidth >> 1, textureHeight >> 1, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj1, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj1, GX_TEXMAP1); - GXInitTexObj(&tobj2, v_data, textureWidth >> 1, textureHeight >> 1, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObj(&tobj2, v_data, textureWidth >> 1, textureHeight >> 1, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj2, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj2, GX_TEXMAP2); -#undef FMT - GXBegin(GX_QUADS, GX_VTXFMT7, 4); GXPosition3s16(x, y, 0); GXTexCoord2u16(0, 0); @@ -3774,13 +3661,6 @@ static BOOL daMP_THPPlayerOpen(char const* filename, BOOL onMemory) { } static BOOL daMP_THPPlayerClose() { -#if TARGET_PC && MOVIE_SUPPORT - tj3Destroy(JpegDecompressHandle); - JpegDecompressHandle = nullptr; - - FixedJpegData.clear(); -#endif - if (daMP_ActivePlayer.open && daMP_ActivePlayer.state == 0) { daMP_ActivePlayer.open = 0; DVDClose(&daMP_ActivePlayer.fileInfo); @@ -3832,11 +3712,6 @@ static BOOL daMP_THPPlayerSetBuffer(u8* buffer) { ysize = ALIGN_NEXT(daMP_ActivePlayer.videoInfo.xSize * daMP_ActivePlayer.videoInfo.ySize, 32); uvsize = ALIGN_NEXT(daMP_ActivePlayer.videoInfo.xSize * daMP_ActivePlayer.videoInfo.ySize / 4, 32); -#if TARGET_PC - assert(ysize >= tj3YUVPlaneSize(0, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420)); - assert(uvsize >= tj3YUVPlaneSize(1, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420)); - assert(uvsize >= tj3YUVPlaneSize(2, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420)); -#endif for (i = 0; i < ARRAY_SIZE(daMP_ActivePlayer.textureSet); i++) { daMP_ActivePlayer.textureSet[i].ytexture = ptr; @@ -4329,15 +4204,6 @@ static BOOL daMP_ActivePlayer_Init(char const* moviePath) { daMP_THPPlayerSetBuffer((u8*)daMP_buffer); -#if TARGET_PC && MOVIE_SUPPORT - assert(JpegDecompressHandle == nullptr); - JpegDecompressHandle = tj3Init(TJINIT_DECOMPRESS); - if (JpegDecompressHandle == nullptr) { - OSReport_Error("Failed to create turbojpeg handle: %s", tj3GetErrorStr(nullptr)); - return 0; - } -#endif - if (!daMP_THPPlayerPrepare(0, 0, daMP_audioInfo.sndNumTracks != 1 ? OSGetTick() % daMP_audioInfo.sndNumTracks : 0)) { OSReport("Fail to prepare\n"); #if DEBUG @@ -4373,10 +4239,6 @@ static void daMP_ActivePlayer_Main() { } } -#if TARGET_PC && 0 -#include "imgui.h" -#endif - static void daMP_ActivePlayer_Draw() { #if TARGET_PC u16 width = JUTVideo::getManager()->getFbWidth(); diff --git a/src/d/actor/d_a_npc_gra.cpp b/src/d/actor/d_a_npc_gra.cpp index d57ce3ab2b..6f6293bed7 100644 --- a/src/d/actor/d_a_npc_gra.cpp +++ b/src/d/actor/d_a_npc_gra.cpp @@ -12,10 +12,11 @@ #include "d/actor/d_a_npc_gra.h" #include "d/actor/d_a_tag_gra.h" #include "Z2AudioLib/Z2Instances.h" +#include + #if TARGET_PC #include "mods/items.h" #endif -#include DUSK_GAME_DATA const daNpc_grA_HIOParam daNpc_grA_Param_c::m = { {90.0f, -4.0f, 1.0f, 850.0f, 255.0f, 280.0f, 40.0f, 100.0f, 0.0f, 0.0f, 20.0f, diff --git a/src/d/actor/d_a_npc_grc.cpp b/src/d/actor/d_a_npc_grc.cpp index 47ea7490c0..77aeb8016d 100644 --- a/src/d/actor/d_a_npc_grc.cpp +++ b/src/d/actor/d_a_npc_grc.cpp @@ -8,10 +8,11 @@ #include "d/actor/d_a_npc_grc.h" #include "d/actor/d_a_npc.h" #include "Z2AudioLib/Z2Instances.h" +#include + #if TARGET_PC #include "mods/items.h" #endif -#include enum grC_RES_File_ID { /* BCK */ diff --git a/src/d/actor/d_a_npc_hanjo.cpp b/src/d/actor/d_a_npc_hanjo.cpp index cbd3d9dcf9..2b5284e69d 100644 --- a/src/d/actor/d_a_npc_hanjo.cpp +++ b/src/d/actor/d_a_npc_hanjo.cpp @@ -15,7 +15,9 @@ #include "Z2AudioLib/Z2Instances.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif static DUSK_CONSTEXPR int l_bmdData[4][2] = { {14, 1}, {26, 2}, diff --git a/src/d/actor/d_a_npc_kkri.cpp b/src/d/actor/d_a_npc_kkri.cpp index 97e375e4ec..9082c9c473 100644 --- a/src/d/actor/d_a_npc_kkri.cpp +++ b/src/d/actor/d_a_npc_kkri.cpp @@ -7,10 +7,11 @@ #include "d/actor/d_a_npc_kkri.h" #include "d/actor/d_a_e_ym.h" +#include + #if TARGET_PC #include "mods/items.h" #endif -#include static DUSK_CONSTEXPR int l_bmdData[2][2] = { {35, 1}, diff --git a/src/d/actor/d_a_npc_ne.cpp b/src/d/actor/d_a_npc_ne.cpp index edce270a2c..530654400f 100644 --- a/src/d/actor/d_a_npc_ne.cpp +++ b/src/d/actor/d_a_npc_ne.cpp @@ -18,9 +18,12 @@ #include "f_op/f_op_kankyo_mng.h" #include "c/c_damagereaction.h" #include "Z2AudioLib/Z2Instances.h" -#include "dusk/frame_interpolation.h" #include +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#endif + static home_path_pnt home_path[38] = { {0, {561.0f, 87.0f, -1110.0f}}, {1, {306.0f, 87.0f, -849.0f}}, @@ -2656,9 +2659,7 @@ static void demo_camera(npc_ne_class* i_this) { i_this->mCameraFovY = 55.0f; camera->mCamera.SetTrimSize(3); daPy_getPlayerActorClass()->changeOriginalDemo(); -#ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); // fallthrough case 2: @@ -2687,9 +2688,7 @@ static void demo_camera(npc_ne_class* i_this) { if (i_this->mDemoCounter == 0) { i_this->mCameraCenter1.set(387.0f, 133.0f, -866.0f); i_this->mCameraEye1.set(284.0f, 208.0f, -585.0f); -#ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); } if (i_this->mDemoCounter == 12) { @@ -2726,9 +2725,7 @@ static void demo_camera(npc_ne_class* i_this) { i_this->mCameraFovY = 45.0f; camera->mCamera.SetTrimSize(3); daPy_getPlayerActorClass()->changeOriginalDemo(); -#ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); // fallthrough case 11: @@ -2809,14 +2806,10 @@ static void demo_camera(npc_ne_class* i_this) { MtxPosition(&vec, &i_this->mCameraEye2); i_this->mCameraEye2 += player->current.pos; player->changeDemoParam2(2); -#ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); } else if (i_this->mDemoCounter == 120) { player->changeDemoParam2(0); -#ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); } } } @@ -2869,9 +2862,7 @@ static void demo_camera(npc_ne_class* i_this) { i_this->mCameraCenter1 = _this->current.pos; i_this->mCameraCenter1.y += 20.0f; i_this->mCameraFovY = 55.0f; -#ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); } camera->mCamera.Set(i_this->mCameraCenter1, i_this->mCameraEye1, diff --git a/src/d/actor/d_a_npc_saru.cpp b/src/d/actor/d_a_npc_saru.cpp index 7f3f9e1126..4f98b7f6e9 100644 --- a/src/d/actor/d_a_npc_saru.cpp +++ b/src/d/actor/d_a_npc_saru.cpp @@ -11,7 +11,9 @@ #include "d/actor/d_a_e_ym.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif enum saru_TW_RES_File_ID { /* BMDR */ diff --git a/src/d/actor/d_a_npc_shop0.cpp b/src/d/actor/d_a_npc_shop0.cpp index e61ea44f4e..eaa0b1d603 100644 --- a/src/d/actor/d_a_npc_shop0.cpp +++ b/src/d/actor/d_a_npc_shop0.cpp @@ -8,7 +8,9 @@ #include "d/actor/d_a_npc_shop0.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif static int createHeapCallBack(fopAc_ac_c* i_this) { return static_cast(i_this)->createHeap(); diff --git a/src/d/actor/d_a_npc_toby.cpp b/src/d/actor/d_a_npc_toby.cpp index 53a39a3e91..d42787e2a2 100644 --- a/src/d/actor/d_a_npc_toby.cpp +++ b/src/d/actor/d_a_npc_toby.cpp @@ -14,9 +14,12 @@ #include "d/actor/d_a_obj_automata.h" #include "d/d_msg_object.h" #include "d/actor/d_a_obj_scannon.h" -#include "dusk/frame_interpolation.h" #include +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#endif + DUSK_GAME_DATA const daNpc_Toby_HIOParam daNpc_Toby_Param_c::m = { 160.0f, -3.0f, @@ -1399,7 +1402,7 @@ int daNpc_Toby_c::cutRepairSCannon(int arg0) { old.pos = current.pos; setAngle(cM_deg2s(5.0f * f32(mPath.getArg0()))); mEventTimer = mPath.getArg2(); - dusk::frame_interp::request_presentation_sync(); + IF_DUSK(dusk::interp::request_presentation_sync()); } } else if (!mHide) { mHide = 1; diff --git a/src/d/actor/d_a_npc_yelia.cpp b/src/d/actor/d_a_npc_yelia.cpp index 44b408b35c..d826abd947 100644 --- a/src/d/actor/d_a_npc_yelia.cpp +++ b/src/d/actor/d_a_npc_yelia.cpp @@ -9,7 +9,9 @@ #include "d/actor/d_a_demo_item.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif static DUSK_CONSTEXPR daNpc_GetParam1 l_bmdData[3] = { {3, 1}, diff --git a/src/d/actor/d_a_npc_ykw.cpp b/src/d/actor/d_a_npc_ykw.cpp index 7b84e1a1bb..f79b71b7e2 100644 --- a/src/d/actor/d_a_npc_ykw.cpp +++ b/src/d/actor/d_a_npc_ykw.cpp @@ -20,7 +20,9 @@ #include "m_Do/m_Do_ext.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif #if DEBUG class daNpc_ykW_HIO_c : public mDoHIO_entry_c { diff --git a/src/d/actor/d_a_obj_fchain.cpp b/src/d/actor/d_a_obj_fchain.cpp index b4d473c424..e8cbb10168 100644 --- a/src/d/actor/d_a_obj_fchain.cpp +++ b/src/d/actor/d_a_obj_fchain.cpp @@ -10,10 +10,18 @@ #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" #include "SSystem/SComponent/c_math.h" #include "d/d_com_inf_game.h" -#include "dusk/frame_interpolation.h" -#include "dusk/settings.h" #include +#if TARGET_PC +#include "dusk/interp/samples.h" + +static const int CHAIN_COUNT = 22; +struct ChainInterp { + dusk::interp::Samples positions; + dusk::interp::Samples angles; +}; +#endif + static char const l_arcName[] = "Fchain"; int daObjFchain_c::createHeap() { @@ -67,10 +75,6 @@ int daObjFchain_c::create() { local_48++; } rv = cPhs_COMPLEATE_e; -#if TARGET_PC - mChainInterpPrevValid = false; - mChainInterpCurrValid = false; -#endif break; } return rv; @@ -256,6 +260,17 @@ void daObjFchain_shape_c::draw() { daObjFchain_c* i_this = (daObjFchain_c*)getUserArea(); cXyz* pPos = i_this->getPos(); csXyz* pAngle = i_this->getAngle(); +#if TARGET_PC + cXyz positions[CHAIN_COUNT]; + csXyz angles[CHAIN_COUNT]; + auto& samples = dusk::interp::get(i_this); + for (int i = 0; i < CHAIN_COUNT; ++i) { + positions[i] = samples.positions.read(i, pPos[i]); + angles[i] = samples.angles.read(i, pAngle[i]); + } + pPos = positions; + pAngle = angles; +#endif J3DModelData* modelData = i_this->getModelData(); J3DMaterial* material = modelData->getMaterialNodePointer(0); dKy_tevstr_c* tevStr = &i_this->tevStr; @@ -295,26 +310,6 @@ void daObjFchain_shape_c::draw() { } } -#if TARGET_PC -static void fchain_interp_callback(bool isSimFrame, void* pUserWork) { - static_cast(pUserWork)->onInterpCallback(); -} - -void daObjFchain_c::onInterpCallback() { - if (!mChainInterpPrevValid || !mChainInterpCurrValid) { - return; - } - - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - - for (int i = 0; i < CHAIN_COUNT; i++) { - const cXyz& p0 = mChainInterpPrev[i]; - const cXyz& p1 = mChainInterpCurr[i]; - field_0x694[i] = p0 + (p1 - p0) * alpha; - } -} -#endif - int daObjFchain_c::draw() { if (field_0x584 != 0) { g_env_light.settingTevStruct(0, ¤t.pos, &tevStr); @@ -325,16 +320,9 @@ int daObjFchain_c::draw() { dComIfGd_getOpaListDark()->entryImm(&mShape, 0); #if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (mChainInterpCurrValid) { - memcpy(mChainInterpPrev, mChainInterpCurr, sizeof(mChainInterpCurr)); - mChainInterpPrevValid = true; - } - - memcpy(mChainInterpCurr, field_0x694, sizeof(mChainInterpCurr)); - mChainInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&fchain_interp_callback, this); - } + auto& samples = dusk::interp::get(this); + samples.positions.capture(getPos(), CHAIN_COUNT); + samples.angles.capture(getAngle(), CHAIN_COUNT); #endif } return 1; diff --git a/src/d/actor/d_a_obj_gra2.cpp b/src/d/actor/d_a_obj_gra2.cpp index a5fa226ee7..97036418fc 100644 --- a/src/d/actor/d_a_obj_gra2.cpp +++ b/src/d/actor/d_a_obj_gra2.cpp @@ -5,14 +5,13 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep -#include "d/actor/d_a_npc4.h" #include "d/actor/d_a_obj_gra2.h" +#include "d/actor/d_a_npc4.h" #include "d/actor/d_a_tag_gra.h" #include "d/d_bg_w.h" #include "d/d_cc_uty.h" -#include "d/d_com_inf_actor.h" #include "d/d_com_inf_game.h" -#include "helpers/string.hpp" +#include "d/d_com_inf_actor.h" #if DEBUG #include "d/d_debug_viewer.h" #endif @@ -21,6 +20,10 @@ #include "Z2AudioLib/Z2Instances.h" #include +#if TARGET_PC +#include "helpers/string.hpp" +#endif + class daObj_GrA_Param_c { public: virtual ~daObj_GrA_Param_c() {} diff --git a/src/d/actor/d_a_obj_groundwater.cpp b/src/d/actor/d_a_obj_groundwater.cpp index 59f310678d..5877de3b80 100644 --- a/src/d/actor/d_a_obj_groundwater.cpp +++ b/src/d/actor/d_a_obj_groundwater.cpp @@ -9,6 +9,10 @@ #include "d/d_com_inf_game.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include "dusk/interp/material.h" +#endif + static daGrdWater_HIO_c l_HIO; static daGrdWater_c::modeFunc l_mode_func[5] = { @@ -299,13 +303,17 @@ int daGrdWater_c::Draw() { if (material->getTexGenBlock()->getTexMtx(0) != NULL) { J3DTexMtxInfo* mtxInfo = &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (mtxInfo != NULL) { +#if TARGET_PC + dusk::interp::material::set_view_projection(mtxInfo, 1.0f, 1.0f, -0.01f, 0.0f); +#else Mtx afStack_50; C_MTXLightPerspective(afStack_50, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0.0f); - #if WIDESCREEN_SUPPORT +#if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(afStack_50); - #endif +#endif mtxInfo->setEffectMtx(afStack_50); +#endif modelData2->simpleCalcMaterial(0, (MtxP)j3dDefaultMtx); } } diff --git a/src/d/actor/d_a_obj_item.cpp b/src/d/actor/d_a_obj_item.cpp index e06eb37e50..1c9cc1d5a8 100644 --- a/src/d/actor/d_a_obj_item.cpp +++ b/src/d/actor/d_a_obj_item.cpp @@ -16,10 +16,6 @@ #include "f_op/f_op_camera_mng.h" #include "m_Do/m_Do_mtx.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - static f32 Reflect(cXyz* i_vec, cBgS_PolyInfo const& i_polyinfo, f32 i_scale) { cM3dGPla plane; @@ -35,27 +31,6 @@ static f32 Reflect(cXyz* i_vec, cBgS_PolyInfo const& i_polyinfo, f32 i_scale) { return 0.0f; } -#if TARGET_PC -static void d_a_obj_item_interp_callback(bool isSimFrame, void* pUserWork) { - daItem_c* item = static_cast(pUserWork); - if (item == NULL || item->mpModel == NULL || !item->chkDraw()) { - return; - } - item->setTevStr(); - if (item->mpBrkAnm != NULL) { - s8 tevFrm = item->getTevFrm(); - if (tevFrm != -1) { - item->mpBrkAnm->entry(item->mpModel->getModelData(), tevFrm); - } else { - item->mpBrkAnm->entry(item->mpModel->getModelData()); - } - } - if (item->chkFlag(4)) { - fopAcM_setEffectMtx(item, item->mpModel->getModelData()); - } -} -#endif - const daItemBase_data& daItemBase_c::getData() { return m_data; } @@ -206,7 +181,7 @@ void daItem_c::CreateInit() { initBaseMtx(); animPlay(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f); - if (m_itemNo == dItemNo_BOOMERANG_e IF_DUSK(&&!mItemOverridden)) { + if (DUSK_IF_ELSE(mOriginalItemNo, m_itemNo) == dItemNo_BOOMERANG_e) { itemGetNextExecute(); } else if ((m_itemNo == dItemNo_ORANGE_RUPEE_e || m_itemNo == dItemNo_SILVER_RUPEE_e) && mSparkleEmtr.getEmitter() == NULL) @@ -411,10 +386,6 @@ int daItem_c::_daItem_draw() { return 1; } -#if TARGET_PC - dusk::frame_interp::add_interpolation_callback(&d_a_obj_item_interp_callback, this); -#endif - if (chkDraw()) { return DrawBase(); } @@ -563,7 +534,7 @@ void daItem_c::procWaitGetDemoEvent() { dComIfGp_event_setItemPartnerId(m_item_id); } } else { - if (m_itemNo == dItemNo_BOOMERANG_e IF_DUSK(&&!mItemOverridden)) { + if (DUSK_IF_ELSE(mOriginalItemNo, m_itemNo) == dItemNo_BOOMERANG_e) { fopAcM_orderItemEvent(this, 0, 0); eventInfo.onCondition(dEvtCnd_CANGETITEM_e); return; @@ -787,32 +758,44 @@ void daItem_c::mode_wait() { mAcch.SetGrndNone(); } - switch (m_itemNo) { - case dItemNo_HEART_e: - itemActionForHeart(); - break; - case dItemNo_ARROW_10_e: - case dItemNo_ARROW_20_e: - case dItemNo_ARROW_30_e: - case dItemNo_ARROW_1_e: - case dItemNo_PACHINKO_SHOT_e: - case dItemNo_LIGHT_ARROW_e: - itemActionForArrow(); - break; - case dItemNo_BOOMERANG_e: +#if TARGET_PC + if (mOriginalItemNo == dItemNo_BOOMERANG_e) { itemActionForBoomerang(); - break; - case dItemNo_GREEN_RUPEE_e: - case dItemNo_BLUE_RUPEE_e: - case dItemNo_YELLOW_RUPEE_e: - case dItemNo_RED_RUPEE_e: - case dItemNo_PURPLE_RUPEE_e: - case dItemNo_ORANGE_RUPEE_e: - case dItemNo_SILVER_RUPEE_e: - default: - itemActionForRupee(); - break; + } else { +#endif + switch (m_itemNo) { + case dItemNo_HEART_e: + itemActionForHeart(); + break; + case dItemNo_ARROW_10_e: + case dItemNo_ARROW_20_e: + case dItemNo_ARROW_30_e: + case dItemNo_ARROW_1_e: + case dItemNo_PACHINKO_SHOT_e: + case dItemNo_LIGHT_ARROW_e: + itemActionForArrow(); + break; + case dItemNo_BOOMERANG_e: + // The boomerang check is already handled above, so if we got here, it's guaranteed to be + // an override. Fallthrough to the rupee action +#if !TARGET_PC + itemActionForBoomerang(); + break; +#endif + case dItemNo_GREEN_RUPEE_e: + case dItemNo_BLUE_RUPEE_e: + case dItemNo_YELLOW_RUPEE_e: + case dItemNo_RED_RUPEE_e: + case dItemNo_PURPLE_RUPEE_e: + case dItemNo_ORANGE_RUPEE_e: + case dItemNo_SILVER_RUPEE_e: + default: + itemActionForRupee(); + break; + } +#if TARGET_PC } +#endif if (field_0x9c0 == 0 && mAcch.ChkWaterHit() && mAcch.m_wtr.GetHeight() > current.pos.y) { mode_water_init(); @@ -868,55 +851,64 @@ void daItem_c::itemGetNextExecute() { setFlag(FLAG_INIT_GET_ITEM_e); BOOL haveItem = false; - switch (m_itemNo) { - case dItemNo_HEART_e: - case dItemNo_GREEN_RUPEE_e: - case dItemNo_ARROW_10_e: - case dItemNo_ARROW_20_e: - case dItemNo_ARROW_30_e: - case dItemNo_ARROW_1_e: - procInitSimpleGetDemo(); - itemGet(); - break; - case dItemNo_BLUE_RUPEE_e: - case dItemNo_YELLOW_RUPEE_e: - case dItemNo_RED_RUPEE_e: - case dItemNo_PURPLE_RUPEE_e: - case dItemNo_ORANGE_RUPEE_e: - case dItemNo_SILVER_RUPEE_e: - case dItemNo_PACHINKO_SHOT_e: - if (daPy_getPlayerActorClass()->checkCanoeRide() || - daPy_getPlayerActorClass()->checkHorseRide()) - { - if (checkItemGet(m_itemNo, 1)) { - haveItem = true; - } - procInitSimpleGetDemo(); - itemGet(); - - if (!haveItem) { - dComIfGs_offItemFirstBit(m_itemNo); - } - } else if (!checkItemGet(m_itemNo, 1)) { - procInitGetDemoEvent(); - } else { - procInitSimpleGetDemo(); - itemGet(); - } - break; - case dItemNo_BOOMERANG_e: - procInitGetDemoEvent(); - break; - default: #if TARGET_PC - if (mItemOverridden) { + // Always call demo event for the original boomerang check + if (mOriginalItemNo == dItemNo_BOOMERANG_e) { + procInitGetDemoEvent(); + } else { +#endif + switch (m_itemNo) { + case dItemNo_HEART_e: + case dItemNo_GREEN_RUPEE_e: + case dItemNo_ARROW_10_e: + case dItemNo_ARROW_20_e: + case dItemNo_ARROW_30_e: + case dItemNo_ARROW_1_e: + procInitSimpleGetDemo(); + itemGet(); + break; + case dItemNo_BLUE_RUPEE_e: + case dItemNo_YELLOW_RUPEE_e: + case dItemNo_RED_RUPEE_e: + case dItemNo_PURPLE_RUPEE_e: + case dItemNo_ORANGE_RUPEE_e: + case dItemNo_SILVER_RUPEE_e: + case dItemNo_PACHINKO_SHOT_e: + if (daPy_getPlayerActorClass()->checkCanoeRide() || + daPy_getPlayerActorClass()->checkHorseRide()) + { + if (checkItemGet(m_itemNo, 1)) { + haveItem = true; + } + procInitSimpleGetDemo(); + itemGet(); + + if (!haveItem) { + dComIfGs_offItemFirstBit(m_itemNo); + } + } else if (!checkItemGet(m_itemNo, 1)) { + procInitGetDemoEvent(); + } else { + procInitSimpleGetDemo(); + itemGet(); + } + break; + case dItemNo_BOOMERANG_e: procInitGetDemoEvent(); break; - } + default: +#if TARGET_PC + if (mItemOverridden) { + procInitGetDemoEvent(); + break; + } #endif - // "[daItem_c] Get process not defined[%d]\n" - OS_REPORT_ERROR("[daItem_c]ゲット処理が定義されていません[%d]\n", m_itemNo); + // "[daItem_c] Get process not defined[%d]\n" + OS_REPORT_ERROR("[daItem_c]ゲット処理が定義されていません[%d]\n", m_itemNo); + } +#if TARGET_PC } +#endif fopAcM_onItem(this, mItemBitNo); mCcCyl.SetTgType(0); @@ -968,6 +960,13 @@ void daItem_c::itemGet() { execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this)); break; case dItemNo_BOOMERANG_e: +#if TARGET_PC + if (mItemOverridden) { + mDoAud_seStart(Z2SE_CONSUMP_ITEM_GET, NULL, 0, 0); + execItemGet(m_itemNo, mItemGiveTag, this); + break; + } +#endif break; case dItemNo_ARROW_10_e: case dItemNo_ARROW_20_e: @@ -1297,7 +1296,7 @@ void daItem_c::initSpeed(BOOL i_noTypeChk) { u8 type = daItem_prm::getType(this); if (!i_noTypeChk) { - if (type == TYPE_WAIT_e || type == TYPE_BOOM_HIT_e || m_itemNo == dItemNo_BOOMERANG_e) { + if (type == TYPE_WAIT_e || type == TYPE_BOOM_HIT_e || DUSK_IF_ELSE(mOriginalItemNo, m_itemNo) == dItemNo_BOOMERANG_e) { y_speed = 0.0f; speedf = 0.0f; } else if (type == TYPE_LAUNCH_NO_RND_e || type == TYPE_FIXED_PLACE_e) { diff --git a/src/d/actor/d_a_obj_keyhole.cpp b/src/d/actor/d_a_obj_keyhole.cpp index d56f39f40b..8b120f77e3 100644 --- a/src/d/actor/d_a_obj_keyhole.cpp +++ b/src/d/actor/d_a_obj_keyhole.cpp @@ -10,9 +10,6 @@ #include "d/d_s_play.h" #include "d/actor/d_a_player.h" #include "Z2AudioLib/Z2Instances.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif daObj_Keyhole_HIO_c::daObj_Keyhole_HIO_c() { id = -1; @@ -56,21 +53,6 @@ static int daObj_Keyhole_Draw(obj_keyhole_class* i_this) { for (int i = 0; i < 6; i++) { kh_chain_s* chain_s = &i_this->chain_s[i]; for (int j = 0; j < i_this->chain_num; j++) { -#if TARGET_PC - if (dusk::frame_interp::is_enabled() && i_this->mChainInterpPrevValid && i_this->mChainInterpCurrValid) { - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - Mtx mtx; - const f32* p0 = (const f32*)i_this->mChainInterpPrev[i][j]; - const f32* p1 = (const f32*)i_this->mChainInterpCurr[i][j]; - f32* dst = (f32*)mtx; - for (int k = 0; k < 12; k++) { - dst[k] = p0[k] + (p1[k] - p0[k]) * alpha; - } - chain_s->model[j]->setBaseTRMtx(mtx); - g_env_light.setLightTevColorType_MAJI(chain_s->model[j], &actor->tevStr); - mDoExt_modelUpdateDL(chain_s->model[j]); - } else -#endif dComIfGp_entrySimpleModel(chain_s->model[j], fopAcM_GetRoomNo(actor)); } } @@ -388,21 +370,6 @@ static void chain_move(obj_keyhole_class* i_this) { ANGLE_ADD(sp8, TREG_S(0) + 0x3D00); } } - -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (i_this->mChainInterpCurrValid) { - memcpy(i_this->mChainInterpPrev, i_this->mChainInterpCurr, sizeof(i_this->mChainInterpCurr)); - i_this->mChainInterpPrevValid = true; - } - for (int i = 0; i < 6; i++) { - for (int j = 0; j < i_this->chain_num; j++) { - MTXCopy(i_this->chain_s[i].model[j]->getBaseTRMtx(), i_this->mChainInterpCurr[i][j]); - } - } - i_this->mChainInterpCurrValid = true; - } -#endif } static void open(obj_keyhole_class* i_this) { @@ -783,11 +750,6 @@ static int daObj_Keyhole_Create(fopAc_ac_c* a_this) { return cPhs_ERROR_e; } -#if TARGET_PC - i_this->mChainInterpPrevValid = false; - i_this->mChainInterpCurrValid = false; -#endif - OS_REPORT("//////////////OBJ_KEYHOLE SET 2 !!\n"); if (i_this->arg0 == 3) { diff --git a/src/d/actor/d_a_obj_klift00.cpp b/src/d/actor/d_a_obj_klift00.cpp index fbccb64edb..bec1c33aba 100644 --- a/src/d/actor/d_a_obj_klift00.cpp +++ b/src/d/actor/d_a_obj_klift00.cpp @@ -11,8 +11,13 @@ #include "d/d_bg_w.h" #include "d/d_cc_uty.h" #include "d/d_com_inf_game.h" -#include "dusk/frame_interpolation.h" -#include "dusk/settings.h" + +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/samples.h" + +using KLiftInterp = dusk::interp::Samples; +#endif struct daObjKLift00_HIO_c : public mDoHIO_entry_c { daObjKLift00_HIO_c(); @@ -141,7 +146,14 @@ cPhs_Step daObjKLift00_c::create1st() { static const int l_bmdidx[3] = {5, 6, 4}; -void daObjKLift00_c::setMtx() { +void daObjKLift00_c::setMtx(IF_DUSK(bool presentation)) { +#if TARGET_PC + const auto* samples = presentation ? &dusk::interp::get(this) : nullptr; + const auto chainPosition = [&](int i) { + const cXyz& position = mChainPositions[i].mCurrentPos; + return samples != nullptr ? samples->read(i, position) : position; + }; +#endif Mtx nonFoundationChainRotationMatrix; mDoMtx_stack_c::transS(current.pos.x, current.pos.y, current.pos.z); mDoMtx_stack_c::YrotM(shape_angle.y); @@ -149,13 +161,16 @@ void daObjKLift00_c::setMtx() { if(!getNoBaseDisp()) mpChainBase->setBaseTRMtx(mDoMtx_stack_c::get()); + IF_DUSK_BLOCK(!presentation) MTXCopy(mDoMtx_stack_c::get(), mCullMtx); + IF_DUSK_BLOCK_END s16 negativeHomeYAngle = -home.angle.y; // Compute rotation matrices to make the chains' rotation match their change in position for(int i = 0; i < mNumChainModels; i++) { - cXyz vectorToLowerChain = mChainPositions[i + 1].mCurrentPos - mChainPositions[i].mCurrentPos; + cXyz vectorToLowerChain = DUSK_IF_ELSE(chainPosition(i + 1) - chainPosition(i), + mChainPositions[i + 1].mCurrentPos - mChainPositions[i].mCurrentPos); cXyz crossProductXAxis; VECCrossProduct(&cXyz::BaseZ, &vectorToLowerChain, &crossProductXAxis); @@ -178,7 +193,7 @@ void daObjKLift00_c::setMtx() { } negativeHomeYAngle += static_cast(0x4000); - mDoMtx_stack_c::transS(mChainPositions[i].mCurrentPos); + mDoMtx_stack_c::transS(DUSK_IF_ELSE(chainPosition(i), mChainPositions[i].mCurrentPos)); mDoMtx_stack_c::concat(nonFoundationChainRotationMatrix); mDoMtx_stack_c::ZrotM(negativeHomeYAngle); @@ -187,7 +202,9 @@ void daObjKLift00_c::setMtx() { } Mtx foundationChainRotationMatrix; - cXyz vectorFromSecondLowestChainToFoundation = mChainPositions[mNumChainModels].mCurrentPos - mChainPositions[mNumChainModels - 1].mCurrentPos; + cXyz vectorFromSecondLowestChainToFoundation = + DUSK_IF_ELSE(chainPosition(mNumChainModels) - chainPosition(mNumChainModels - 1), + mChainPositions[mNumChainModels].mCurrentPos - mChainPositions[mNumChainModels - 1].mCurrentPos); cXyz crossProduct; Vec inverseBaseY = {0.0f, -1.0f, 0.0f}; if(!cM3d_IsZero(vectorFromSecondLowestChainToFoundation.getSquareMag())) { @@ -208,12 +225,14 @@ void daObjKLift00_c::setMtx() { MTXIdentity(foundationChainRotationMatrix); } - mDoMtx_stack_c::transS(mChainPositions[mNumChainModels].mCurrentPos); + mDoMtx_stack_c::transS(DUSK_IF_ELSE(chainPosition(mNumChainModels), mChainPositions[mNumChainModels].mCurrentPos)); mDoMtx_stack_c::concat(foundationChainRotationMatrix); mDoMtx_stack_c::YrotM(-negativeHomeYAngle); mpLiftPlatform->setBaseTRMtx(mDoMtx_stack_c::get()); mDoMtx_stack_c::scaleM(scale.x, scale.y, scale.z); + IF_DUSK_BLOCK(!presentation) MTXCopy(mDoMtx_stack_c::get(), mNewBgMtx); + IF_DUSK_BLOCK_END } void daObjKLift00_c::rideActor(fopAc_ac_c* riding_actor) { @@ -297,11 +316,6 @@ int daObjKLift00_c::Create() { if(getLock()) mStopSwingingFrames = 5; -#if TARGET_PC - mChainInterpPrevValid = false; - mChainInterpCurrValid = false; -#endif - return 1; } @@ -443,35 +457,16 @@ int daObjKLift00_c::Execute(Mtx** i_mtx) { return 1; } -#if TARGET_PC -static void klift00_interp_callback(bool isSimFrame, void* pUserWork) { - static_cast(pUserWork)->onInterpCallback(); -} - -void daObjKLift00_c::onInterpCallback() { - if (!mChainInterpPrevValid || !mChainInterpCurrValid) { - return; - } - - const f32 alpha = dusk::frame_interp::get_interpolation_step(); - cXyz savedPositions[64]; - - for (int i = 0; i < mNumChains; i++) { - savedPositions[i] = mChainPositions[i].mCurrentPos; - const cXyz& p0 = mChainInterpPrev[i]; - const cXyz& p1 = mChainInterpCurr[i]; - mChainPositions[i].mCurrentPos = p0 + (p1 - p0) * alpha; - } - - setMtx(); - - for (int i = 0; i < mNumChains; i++) { - mChainPositions[i].mCurrentPos = savedPositions[i]; - } -} -#endif - int daObjKLift00_c::Draw() { +#if TARGET_PC + if (dusk::interp::is_presentation_active()) { + setMtx(true); + } else if (dusk::interp::should_capture()) { + dusk::interp::get(this).capture(mNumChains, [&](int i) { + return mChainPositions[i].mCurrentPos; + }); + } +#endif g_env_light.settingTevStruct(16, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr); @@ -492,22 +487,6 @@ int daObjKLift00_c::Draw() { dComIfGd_setList(); -#if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - if (mChainInterpCurrValid) { - memcpy(mChainInterpPrev, mChainInterpCurr, mNumChains * sizeof(cXyz)); - mChainInterpPrevValid = true; - } - - for (int i = 0; i < mNumChains; i++) { - mChainInterpCurr[i] = mChainPositions[i].mCurrentPos; - } - - mChainInterpCurrValid = true; - dusk::frame_interp::add_interpolation_callback(&klift00_interp_callback, this); - } -#endif - return 1; } diff --git a/src/d/actor/d_a_obj_lv4chandelier.cpp b/src/d/actor/d_a_obj_lv4chandelier.cpp index 7847b37358..56f9aa93c1 100644 --- a/src/d/actor/d_a_obj_lv4chandelier.cpp +++ b/src/d/actor/d_a_obj_lv4chandelier.cpp @@ -12,10 +12,6 @@ #include "d/d_cc_d.h" #include "d/d_cc_uty.h" -#ifndef __MWERKS__ -#include "helpers/math.h" -#endif - #if DEBUG class daObjLv4Chan_HIO_c : public mDoHIO_entry_c { public: diff --git a/src/d/actor/d_a_obj_lv8Lift.cpp b/src/d/actor/d_a_obj_lv8Lift.cpp index 9ac8206f6b..38809bbfee 100644 --- a/src/d/actor/d_a_obj_lv8Lift.cpp +++ b/src/d/actor/d_a_obj_lv8Lift.cpp @@ -10,10 +10,6 @@ #include "d/d_path.h" #include "d/d_bg_w.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - daL8Lift_HIO_c::daL8Lift_HIO_c() { mStopDisappearTime = 30; mStartMoveTime = 60; @@ -384,44 +380,7 @@ void daL8Lift_c::setNextPoint() { mCurrentPoint = next_point; } -#if TARGET_PC -void daL8Lift_interp_callback(bool isSimFrame, void* pUserWork) { - daL8Lift_c* lift = static_cast(pUserWork); - if (lift == NULL || lift->mpModel == NULL) { - return; - } - - g_env_light.settingTevStruct(0x10, &lift->current.pos, &lift->tevStr); - g_env_light.setLightTevColorType_MAJI(lift->mpModel, &lift->tevStr); - - J3DModelData* modelData = lift->mpModel->getModelData(); - J3DMaterial* materialp = modelData->getMaterialNodePointer(0); - - if (materialp->getTexGenBlock()->getTexMtx(1) != NULL) { - J3DTexMtxInfo* mtx_info = &materialp->getTexGenBlock()->getTexMtx(1)->getTexMtxInfo(); - if (mtx_info != NULL) { - Mtx m; - C_MTXLightOrtho(m, 100.0f, -100.0f, -100.0f, 100.0f, 1.0f, 1.0f, 0.0f, 0.0f); - mDoMtx_stack_c::XrotS(0x4000); - mDoMtx_stack_c::transM(-lift->current.pos.x, -lift->current.pos.y, -lift->current.pos.z); - cMtx_concat(m, mDoMtx_stack_c::get(), mtx_info->mEffectMtx); - } - } - - lift->mBtk.entry(modelData); - - J3DGXColor* color = materialp->getTevKColor(1); - color->r = l_HIO.mColorR; - color->g = l_HIO.mColorG; - color->b = l_HIO.mColorB; -} -#endif - int daL8Lift_c::Draw() { -#if TARGET_PC - dusk::frame_interp::add_interpolation_callback(&daL8Lift_interp_callback, this); -#endif - g_env_light.settingTevStruct(16, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr); J3DModelData* modelData = mpModel->getModelData(); diff --git a/src/d/actor/d_a_obj_lv8OptiLift.cpp b/src/d/actor/d_a_obj_lv8OptiLift.cpp index e367b89b36..dc49f3f69b 100644 --- a/src/d/actor/d_a_obj_lv8OptiLift.cpp +++ b/src/d/actor/d_a_obj_lv8OptiLift.cpp @@ -11,10 +11,6 @@ #include "d/d_com_inf_game.h" #include "d/d_path.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif - daOptiLift_HIO_c::daOptiLift_HIO_c() { mStopDisappearTime = 30; mStartMoveTime = 30; @@ -416,44 +412,7 @@ void daOptiLift_c::setNextPoint() { mCurrentPoint = next_point; } -#if TARGET_PC -static void daOptiLift_interp_callback(bool isSimFrame, void* pUserWork) { - daOptiLift_c* lift = static_cast(pUserWork); - if (lift == NULL || lift->mpModel == NULL) { - return; - } - - g_env_light.settingTevStruct(0x10, &lift->current.pos, &lift->tevStr); - g_env_light.setLightTevColorType_MAJI(lift->mpModel, &lift->tevStr); - - J3DModelData* modelData = lift->mpModel->getModelData(); - J3DMaterial* materialp = modelData->getMaterialNodePointer(0); - - if (materialp->getTexGenBlock()->getTexMtx(1) != NULL) { - J3DTexMtxInfo* mtx_info = &materialp->getTexGenBlock()->getTexMtx(1)->getTexMtxInfo(); - if (mtx_info != NULL) { - Mtx m; - C_MTXLightOrtho(m, 100.0f, -100.0f, -100.0f, 100.0f, 1.0f, 1.0f, 0.0f, 0.0f); - mDoMtx_stack_c::XrotS(0x4000); - mDoMtx_stack_c::transM(-lift->current.pos.x, -lift->current.pos.y, -lift->current.pos.z); - cMtx_concat(m, mDoMtx_stack_c::get(), mtx_info->mEffectMtx); - } - } - - lift->mBtk.entry(modelData); - - J3DGXColor* color = materialp->getTevKColor(1); - color->r = l_HIO.mColorR; - color->g = l_HIO.mColorG; - color->b = l_HIO.mColorB; -} -#endif - int daOptiLift_c::Draw() { -#if TARGET_PC - dusk::frame_interp::add_interpolation_callback(&daOptiLift_interp_callback, this); -#endif - g_env_light.settingTevStruct(0x10, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr); diff --git a/src/d/actor/d_a_obj_master_sword.cpp b/src/d/actor/d_a_obj_master_sword.cpp index a1ef6b1709..444b7356e3 100644 --- a/src/d/actor/d_a_obj_master_sword.cpp +++ b/src/d/actor/d_a_obj_master_sword.cpp @@ -11,9 +11,10 @@ #include "d/d_meter2_info.h" #if TARGET_PC -#include "d/d_item.h" #include "dusk/mods/item.hpp" #include "mods/items.h" + +#include "d/d_item.h" #endif DUSK_GAME_DATA daObjMasterSword_Attr_c const daObjMasterSword_c::mAttr = {1.0f}; diff --git a/src/d/actor/d_a_obj_sekizoa.cpp b/src/d/actor/d_a_obj_sekizoa.cpp index f8493ec385..fece4d9773 100644 --- a/src/d/actor/d_a_obj_sekizoa.cpp +++ b/src/d/actor/d_a_obj_sekizoa.cpp @@ -8,11 +8,12 @@ #include "d/actor/d_a_tag_kmsg.h" #include #include - -#include "helpers/string.hpp" #include "f_op/f_op_actor_mng.h" #include "f_op/f_op_msg.h" +#if TARGET_PC +#include "helpers/string.hpp" +#endif static DUSK_CONSTEXPR daNpc_GetParam1 l_bmdData[9] = { {36, 1}, {36, 1}, {35, 1}, {37, 1}, {38, 1}, {3, 2}, {3, 2}, {4, 2}, {5, 2}, diff --git a/src/d/actor/d_a_obj_wchain.cpp b/src/d/actor/d_a_obj_wchain.cpp index a8f5c39086..5ac92ff23a 100644 --- a/src/d/actor/d_a_obj_wchain.cpp +++ b/src/d/actor/d_a_obj_wchain.cpp @@ -13,6 +13,20 @@ #include "JSystem/J3DGraphBase/J3DMaterial.h" #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" +#if TARGET_PC +#include "dusk/interp/samples.h" + +namespace { +constexpr int CHAIN_COUNT = 16; +struct WchainInterp { + dusk::interp::Samples positions; + dusk::interp::Samples angles; + dusk::interp::Samples rotations; +}; + +} +#endif + static char const l_arcName[7] = "Wchain"; int daObjWchain_c::createHeap() { @@ -309,6 +323,23 @@ void daObjWchain_shape_c::draw() { cXyz* pos = chain->getChainPos(); csXyz* angle = chain->getChainAngle(); s16* rotation = chain->getChainAngleZ(); +#if TARGET_PC + auto& samples = dusk::interp::get(chain); + cXyz positions[CHAIN_COUNT]; + csXyz angles[CHAIN_COUNT]; + s16 rotations[CHAIN_COUNT]; + s16 yaw = chain->shape_angle.y; + for (int i = 0; i < CHAIN_COUNT; ++i) { + positions[i] = samples.positions.read(i, pos[i]); + angles[i] = samples.angles.read(i, angle[i]); + csXyz orientation = samples.rotations.read(i, csXyz(rotation[i], yaw, 0)); + rotations[i] = orientation.x; + } + yaw = samples.rotations.read(0, csXyz(rotation[0], yaw, 0)).y; + pos = positions; + angle = angles; + rotation = rotations; +#endif J3DModelData* model_data = chain->getChainModelData(); J3DMaterial* material = model_data->getMaterialNodePointer(0); dKy_tevstr_c& tevstr = chain->tevStr; @@ -335,21 +366,22 @@ void daObjWchain_shape_c::draw() { mDoMtx_stack_c::copy(j3dSys.getViewMtx()); mDoMtx_stack_c::transM(*pos); mDoMtx_stack_c::ZrotM(*rotation); - mDoMtx_stack_c::ZXYrotM(angle->x, chain->shape_angle.y, angle->z); + mDoMtx_stack_c::ZXYrotM(angle->x, DUSK_IF_ELSE(yaw, chain->shape_angle.y), angle->z); mDoMtx_stack_c::transM(0.0f, 0.0f, -8.75f); GXLoadPosMtxImm(mDoMtx_stack_c::get(), 0); GXLoadNrmMtxImm(mDoMtx_stack_c::get(), 0); material->getShape()->simpleDrawCache(); } cXyz roof_pos(chain->getRoofPos().x, chain->getRealRoofY(), chain->getRoofPos().z); - cXyz delta = roof_pos - *chain->getChainPos(); + IF_DUSK(roof_pos = samples.positions.read(CHAIN_COUNT, roof_pos)); + cXyz delta = roof_pos - DUSK_IF_ELSE(positions[0], *chain->getChainPos()); f32 len = delta.abs(); if (len > 17.5f) { - cXyz pos = *chain->getChainPos(); + cXyz pos = DUSK_IF_ELSE(positions[0], *chain->getChainPos()); csXyz angle( delta.atan2sY_XZ(), - chain->getChainAngle()->y, - chain->getChainAngle()->z + 0x3000 + DUSK_IF_ELSE(angles[0].y, chain->getChainAngle()->y), + DUSK_IF_ELSE(angles[0].z, chain->getChainAngle()->z) + 0x3000 ); delta *= (17.5f / len); for (; len > 17.5f; len -= 17.5f, pos += delta, angle.z += 0x3000) { @@ -371,6 +403,16 @@ int daObjWchain_c::draw() { mDoExt_modelUpdateDL(mpHandleModel); g_env_light.setLightTevColorType_MAJI(mpChainModelData, &tevStr); dComIfGd_getOpaList()->entryImm(&mShape, 0); +#if TARGET_PC + auto& samples = dusk::interp::get(this); + samples.positions.capture(CHAIN_COUNT + 1, [&](int i) { + return i < CHAIN_COUNT ? mChainPos[i] : cXyz(mRoofPos.x, mRealRoofY, mRoofPos.z); + }); + samples.angles.capture(mChainAngle, CHAIN_COUNT); + samples.rotations.capture(CHAIN_COUNT, [&](int i) { + return csXyz(mChainRotation[i], shape_angle.y, 0); + }); +#endif return 1; } diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index 8c97bffb6f..b91b7d012c 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -15,6 +15,10 @@ #include "d/actor/d_a_midna.h" #include "d/actor/d_a_spinner.h" +#if TARGET_PC +#include "dusk/interp/samples.h" +#endif + bool daPy_frameCtrl_c::checkAnmEnd() { if (getEndFlg() != 0 && getNowSetFlg() == 0) { return true; @@ -371,11 +375,16 @@ JKRHeap* daPy_anmHeap_c::setAnimeHeap() { #include "assets/l_sightDL__d_a_player.h" #endif +#if TARGET_PC +daPy_sightPacket_c::~daPy_sightPacket_c() { + dusk::interp::erase_owned_samples(this); +} +#endif + void daPy_sightPacket_c::draw() { ZoneScoped; -#if !TARGET_PC - TGXTexObj texObj; -#endif + IF_DUSK(setSight(false);) + IF_NOT_DUSK(TGXTexObj texObj); j3dSys.reinitGX(); GXSetNumIndStages(0); @@ -441,13 +450,21 @@ void daPy_sightPacket_c::draw() { J3DShape::resetVcdVatCache(); } -void daPy_sightPacket_c::setSight() { +void daPy_sightPacket_c::setSight(IF_DUSK(bool registerPacket)) { Vec proj; mDoLib_project(&mPos, &proj); +#if TARGET_PC + auto& positions = dusk::interp::get>(this); + const cXyz screen(proj); + positions.capture(&screen, 1); + proj = positions.read(0, screen); +#endif mDoMtx_stack_c::transS(proj.x, proj.y, proj.z); mDoMtx_stack_c::scaleM(32.0f, 32.0f, 32.0f); mDoMtx_copy(mDoMtx_stack_c::get(), mProjMtx); + IF_DUSK_BLOCK(registerPacket) dComIfGd_set2DXlu(this); + IF_DUSK_BLOCK_END } void daPy_sightPacket_c::setSightImage(ResTIMG* i_img) { diff --git a/src/d/actor/d_a_tag_evt.cpp b/src/d/actor/d_a_tag_evt.cpp index 780fc1d38c..92bc7b2494 100644 --- a/src/d/actor/d_a_tag_evt.cpp +++ b/src/d/actor/d_a_tag_evt.cpp @@ -8,7 +8,9 @@ #include "f_op/f_op_actor_mng.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif static DUSK_CONST char* l_evtNameList[] = { NULL, diff --git a/src/d/actor/d_a_tag_msg.cpp b/src/d/actor/d_a_tag_msg.cpp index e023319a9f..2f5ccd2761 100644 --- a/src/d/actor/d_a_tag_msg.cpp +++ b/src/d/actor/d_a_tag_msg.cpp @@ -11,7 +11,9 @@ #include "d/d_debug_viewer.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif static int createHeapCallBack(fopAc_ac_c* i_this) { daTag_Msg_c* msg = (daTag_Msg_c*)i_this; diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp index b9edd72fa1..982bda141a 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -11,7 +11,6 @@ #include "d/d_pane_class_alpha.h" #include "d/d_s_logo.h" #include "d/d_s_play.h" -#include "dusk/version.hpp" #include "f_op/f_op_msg_mng.h" #include "f_op/f_op_overlap_mng.h" #include "f_op/f_op_scene_mng.h" @@ -19,9 +18,11 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" -#ifdef TARGET_PC -#include "dusk/frame_interpolation.h" -#include "dusk/settings.h" +#if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" +#include "dusk/version.hpp" #endif class daTit_HIO_c : public JORReflexible { @@ -152,6 +153,8 @@ int daTitle_c::create() { g_daTitHIO.id = mDoHIO_CREATE_CHILD("タイトルロゴ", &g_daTitHIO); + IF_DUSK(draw_interp_frame = true); + return phase_state; } @@ -169,20 +172,14 @@ int daTitle_c::Execute() { return 1; } -#ifdef TARGET_PC - if (!dusk::frame_interp::is_enabled()) { -#endif - dMenu_Collect3D_c::setViewPortOffsetY(0.0f); -#ifdef TARGET_PC - } -#endif + IF_NOT_DUSK(dMenu_Collect3D_c::setViewPortOffsetY(0.0f)); if (mDoRst::isReset()) { return 1; } (this->*daTitleProc[mProcID])(); - KeyWaitAnm(); + IF_NOT_DUSK(KeyWaitAnm()); #if VERSION == VERSION_SHIELD_DEBUG KeyWaitPosMove(); @@ -192,6 +189,45 @@ int daTitle_c::Execute() { } void daTitle_c::KeyWaitAnm() { +#if TARGET_PC + if (field_0x5f9 == 0) { + return; + } + + const f32 duration = g_daTitHIO.mArrow; + if (duration <= 0.0f) { + field_0x600->alphaAnimeStart(0.0f); + field_0x600->setAlpha(255); + return; + } + + f32 remaining = dusk::game_clock::original_frames(); + f32 frame = field_0x600->getAlphaTimer(); + if (field_0x5fa != 0) { + const f32 step = std::min(remaining, std::max(0.0f, duration - frame)); + frame += step; + remaining -= step; + if (frame < duration) { + field_0x600->alphaAnimeStart(frame); + field_0x600->setAlpha(255.0f * frame / duration); + return; + } + field_0x5fa = 0; + frame = 0.0f; + field_0x604 = std::max(1.0f, 2.0f * g_daTitHIO.field_0x1a - 1.0f); + } + + const f32 holdStep = std::min(remaining, field_0x604); + field_0x604 -= holdStep; + remaining -= holdStep; + const f32 period = duration + std::max(0.0f, g_daTitHIO.field_0x1a - 1.0f); + frame = std::fmod(frame + remaining, period); + field_0x600->alphaAnimeStart(frame); + const f32 pulse = std::min(frame, duration); + const f32 half = std::max(1.0f, std::floor(duration / 2.0f)); + const f32 rate = std::min(1.0f, std::min(pulse, duration - pulse) / half); + field_0x600->setAlpha(255.0f - 127.0f * rate); +#else if (field_0x5f9 != 0) { if (field_0x604 == 0) { if (field_0x5fa != 0) { @@ -212,6 +248,7 @@ void daTitle_c::KeyWaitAnm() { field_0x604--; } } +#endif } #if VERSION == VERSION_SHIELD_DEBUG @@ -353,11 +390,7 @@ void daTitle_c::fastLogoDispInit() { mWaitTimer = 30; mProcID = 5; -#ifdef TARGET_PC - if (dusk::frame_interp::is_enabled()) { - dusk::frame_interp::request_presentation_sync(); - } -#endif + IF_DUSK(dusk::interp::request_presentation_sync()); } void daTitle_c::fastLogoDisp() { @@ -385,6 +418,15 @@ int daTitle_c::getDemoPrm() { } int daTitle_c::Draw() { +#if TARGET_PC + if (field_0x600 != NULL && dusk::game_clock::is_presentation_frame()) { + if (!fopOvlpM_IsPeek() && !mDoRst::isReset()) { + KeyWaitAnm(); + } + field_0x600->presentAlphaAnime(); + } +#endif + J3DModelData* modelData = mpModel->getModelData(); cMtx_trans(mpModel->getBaseTRMtx(), IREG_F(7), IREG_F(8), IREG_F(9) + -430.0f); mpModel->getBaseScale()->x = -1.0f; @@ -394,6 +436,7 @@ int daTitle_c::Draw() { mBrk.entry(modelData); mBtk.entry(modelData); + IF_DUSK(dMenu_Collect3D_c::setViewPortOffsetY(0.0f)); dComIfGd_setListItem3D(); mDoExt_modelUpdateDL(mpModel); dComIfGd_setList(); diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 9e3d77efd0..e42cc7ded0 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -5,7 +5,9 @@ #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" #include "SSystem/SComponent/c_counter.h" -#include "dusk/frame_interpolation.h" +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" +#endif const u16 l_J_Ohana00_64TEX__width = 63; const u16 l_J_Ohana00_64TEX__height = 63; @@ -1163,16 +1165,12 @@ void dFlower_packet_c::draw() { GXSetChanAmbColor(GX_COLOR0A0, sp64); if (!cLib_checkBit(sp44->m_state, 4) && !cLib_checkBit(sp44->m_state, 0x40)) { -#ifdef TARGET_PC - Mtx flower_mtx; - if (dusk::frame_interp::lookup_replacement(&sp44->m_modelMtx, flower_mtx)) { - cMtx_concat(j3dSys.getViewMtx(), flower_mtx, flower_mtx); - GXLoadPosMtxImm(flower_mtx, 0); - } else +#if TARGET_PC + Mtx flowerMtx; + GXLoadPosMtxImm(get_model_mtx(sp44->m_modelMtx, flowerMtx), 0); +#else + GXLoadPosMtxImm(sp44->m_modelMtx, 0); #endif - { - GXLoadPosMtxImm(sp44->m_modelMtx, 0); - } GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); #if TARGET_PC @@ -1322,16 +1320,12 @@ void dFlower_packet_c::draw() { sp30++; if (!cLib_checkBit(sp34->m_state, 4) && cLib_checkBit(sp34->m_state, 0x40)) { -#ifdef TARGET_PC - Mtx flower_mtx; - if (dusk::frame_interp::lookup_replacement(&sp34->m_modelMtx, flower_mtx)) { - cMtx_concat(j3dSys.getViewMtx(), flower_mtx, flower_mtx); - GXLoadPosMtxImm(flower_mtx, 0); - } else +#if TARGET_PC + Mtx flowerMtx; + GXLoadPosMtxImm(get_model_mtx(sp34->m_modelMtx, flowerMtx), 0); +#else + GXLoadPosMtxImm(sp34->m_modelMtx, 0); #endif - { - GXLoadPosMtxImm(sp34->m_modelMtx, 0); - } GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); #if TARGET_PC GXLoadTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, GX_TEXMAP0); @@ -1461,9 +1455,13 @@ void dFlower_packet_c::update() { mDoMtx_stack_c::copy(temp_r28); mDoMtx_stack_c::scaleM(temp_f31, temp_f31, temp_f31); +#if TARGET_PC + cMtx_copy(temp_r28, data_p->m_modelMtx); +#else cMtx_concat(j3dSys.getViewMtx(), temp_r28, data_p->m_modelMtx); +#endif #ifdef TARGET_PC - dusk::frame_interp::record_final_mtx(temp_r28, data_p->m_modelMtx); + dusk::interp::record_final_mtx(temp_r28, data_p->m_modelMtx); #endif } } diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index 6c7a898c56..fd911083f4 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -11,15 +11,10 @@ #include "d/d_camera.h" #include "f_op/f_op_camera_mng.h" -#include "dusk/frame_interpolation.h" - -const u16 l_M_Hijiki00TEX__width = 31; -const u16 l_M_Hijiki00TEX__height = 31; -const u16 l_M_kusa05_RGBATEX__width = 31; -const u16 l_M_kusa05_RGBATEX__height = 31; - #if TARGET_PC #include "dusk/dvd_asset.hpp" +#include "dusk/interp/frame_interpolation.h" + using GameVersion = dusk::version::GameVersion; template @@ -61,6 +56,11 @@ DEFINE_GRASS_ASSET(l_M_Hijiki00TEX, u8, 0x800, { #include "assets/l_M_Hijiki00TEX.h" // ALIGN 32 #endif +const u16 l_M_Hijiki00TEX__width = 31; +const u16 l_M_Hijiki00TEX__height = 31; +const u16 l_M_kusa05_RGBATEX__width = 31; +const u16 l_M_kusa05_RGBATEX__height = 31; + static u8 l_pos[960] = { 0x3F, 0x4A, 0x56, 0xEF, 0xC2, 0x20, 0x00, 0x00, 0x41, 0xFB, 0x17, 0xE4, 0x41, 0xAA, 0xBB, 0xEA, 0xC2, 0x20, 0x00, 0x00, 0xC1, 0xB7, 0x03, 0x7A, 0x42, 0x55, 0x8D, 0x6F, 0x43, 0x13, 0x16, 0x3F, @@ -608,11 +608,12 @@ dGrass_packet_c::dGrass_packet_c() { #if TARGET_PC static MtxP get_model_mtx(Mtx modelMtx, Mtx storage) { - if (dusk::frame_interp::lookup_replacement(modelMtx, storage)) { + if (dusk::interp::lookup_replacement(modelMtx, storage)) { cMtx_concat(j3dSys.getViewMtx(), storage, storage); - return storage; + } else { + cMtx_concat(j3dSys.getViewMtx(), modelMtx, storage); } - return modelMtx; + return storage; } static void transform_positions( @@ -1193,16 +1194,12 @@ void dGrass_packet_c::draw() { GXSetChanAmbColor(GX_COLOR0A0, sp38); if (!cLib_checkBit(var_r29->field_0x01, 2)) { -#ifdef TARGET_PC - Mtx grass_mtx; - if (dusk::frame_interp::lookup_replacement(reinterpret_cast(&var_r29->m_modelMtx), grass_mtx)) { - cMtx_concat(j3dSys.getViewMtx(), grass_mtx, grass_mtx); - GXLoadPosMtxImm(grass_mtx, 0); - } else +#if TARGET_PC + Mtx grassMtx; + GXLoadPosMtxImm(get_model_mtx(var_r29->m_modelMtx, grassMtx), 0); +#else + GXLoadPosMtxImm(var_r29->m_modelMtx, 0); #endif - { - GXLoadPosMtxImm(var_r29->m_modelMtx, 0); - } GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) { if (var_r29->field_0x02 < -1) { @@ -1445,7 +1442,11 @@ void dGrass_packet_c::update() { } } +#if TARGET_PC + cMtx_copy(mDoMtx_stack_c::get(), data_p->m_modelMtx); +#else cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), data_p->m_modelMtx); +#endif } else { mDoMtx_stack_c::transS(data_p->m_pos.x, data_p->m_pos.y, data_p->m_pos.z); mDoMtx_stack_c::YrotM(i * 3535); @@ -1456,11 +1457,13 @@ void dGrass_packet_c::update() { f32 scale = ((((s16)data_p->m_pos.x * 3535) & 0xFFF) / 4096.0f) * 0.3f + 0.7f; mDoMtx_stack_c::scaleM(scale, scale, scale); +#if TARGET_PC + cMtx_copy(mDoMtx_stack_c::get(), data_p->m_modelMtx); +#else cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), data_p->m_modelMtx); - } -#ifdef TARGET_PC - dusk::frame_interp::record_final_mtx(mDoMtx_stack_c::get(), data_p->m_modelMtx); #endif + } + IF_DUSK(dusk::interp::record_final_mtx(mDoMtx_stack_c::get(), data_p->m_modelMtx)); } } data_p++; diff --git a/src/d/d_attention.cpp b/src/d/d_attention.cpp index beea40e806..49c7465d48 100644 --- a/src/d/d_attention.cpp +++ b/src/d/d_attention.cpp @@ -164,6 +164,7 @@ dAttention_c::dAttention_c(fopAc_ac_c* i_player, u32 i_padNo) { mPadNo = i_padNo; mLockTargetID = fpcM_ERROR_PROCESS_ID_e; + IF_DUSK(mTargetActorID = fpcM_ERROR_PROCESS_ID_e); field_0x32e = 0; field_0x32f = 0; @@ -1455,6 +1456,13 @@ if (dusk::getSettings().game.recordingMode) { } #endif +#if TARGET_PC + if (mTargetActorID != fopAcM_GetID(lockon_actor) || + (fopAcM_GetName(lockon_actor) == fpcNm_Tag_Wljump_e && + mDrawAttnPos != lockon_actor->attention_info.position)) { + draw[0].mModel[draw[0].mDrawType]->forgetMtx(); + } +#endif draw[0].draw(lockon_actor->attention_info.position, inv_m); if (mLockonCount >= 2 && draw[1].field_0x173 == 2) { @@ -1483,6 +1491,12 @@ if (dusk::getSettings().game.recordingMode) { fopAc_ac_c* actor = fopAcM_SearchByID(mTargetActorID); if (actor != NULL) { +#if TARGET_PC + if (fopAcM_GetName(actor) == fpcNm_Tag_Wljump_e && + mDrawAttnPos != actor->attention_info.position) { + draw[0].mModel[draw[0].mDrawType]->forgetMtx(); + } +#endif draw[0].draw(actor->attention_info.position, inv_m); mDrawAttnPos = actor->attention_info.position; } else { @@ -1506,6 +1520,7 @@ void dAttention_c::lockSoundStart(u32 i_sfxID) { void dAttDraw_c::setAnm(u8 i_drawType, f32 i_anmSpeed) { mDrawType = i_drawType; + IF_DUSK(mModel[mDrawType]->forgetMtx()); mNoticeCursorBck[mDrawType].reset(); mNoticeCursorBck[mDrawType].setPlaySpeed(i_anmSpeed); mNoticeCursorBpk[mDrawType].reset(); diff --git a/src/d/d_bg_parts.cpp b/src/d/d_bg_parts.cpp index 07c91f8807..50eb634c25 100644 --- a/src/d/d_bg_parts.cpp +++ b/src/d/d_bg_parts.cpp @@ -8,7 +8,9 @@ #include "JSystem/JKernel/JKRSolidHeap.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif void dBgp_c::material_c::draw() { material_c* material = this; diff --git a/src/d/d_bg_s.cpp b/src/d/d_bg_s.cpp index 51811d9538..e8643f83fe 100644 --- a/src/d/d_bg_s.cpp +++ b/src/d/d_bg_s.cpp @@ -11,12 +11,14 @@ #include "d/d_com_inf_game.h" #include "f_op/f_op_actor_mng.h" +#if TARGET_PC || DEBUG #include "d/d_debug_viewer.h" #include "d/d_bg_s_capt_poly.h" +#endif #if TARGET_PC -#include "helpers/offset_ptr.h" #include "dusk/settings.h" +#include "helpers/offset_ptr.h" #endif #if DEBUG @@ -30,7 +32,6 @@ int g_sph_counter; int g_capt_poly_counter; #endif - void dBgS_HIO::genMessage(JORMContext* mctx) { #if DEBUG mctx->genLabel("zelda地形チェック", 0); @@ -314,7 +315,7 @@ f32 cBgS::GroundCross(cBgS_GndChk* pgndchk) { } template <> -void be_swap(cBgD_Vtx_t& val) { +constexpr void be_swap(cBgD_Vtx_t& val) noexcept { be_swap(val.x); be_swap(val.y); be_swap(val.z); diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index b9ea46e58e..17c6f77267 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -11,15 +11,14 @@ #include "d/d_msg_string.h" #include "m_Do/m_Do_controller_pad.h" -#ifdef TARGET_PC -#include +#if TARGET_PC +#include "dusk/autosave.h" #include "dusk/game_mode.hpp" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/livesplit.h" #include "dusk/speedrun.h" -#endif - #include "dusk/version.hpp" +#endif dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) { mArchive = i_archive; diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index b38a336e02..b33a232a78 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -29,15 +29,18 @@ #endif #if TARGET_PC +#include "dusk/action_bindings.h" #include "dusk/camera_operators.hpp" #include "dusk/commands.hpp" -#include "dusk/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/camera.h" +#include "dusk/interp/frame_interpolation.h" #include "dusk/logging.h" -#include "dusk/action_bindings.h" #include "dusk/mouse.h" #include "dusk/settings.h" #include "dusk/touch_camera.h" -#include "imgui.h" + +#include #endif namespace { @@ -1534,6 +1537,12 @@ void dCamera_c::CalcTrimSize() { OS_REPORT("%06d: camera: trim: keep\n", mFrameCounter); } +#if TARGET_PC + if (dusk::isLetterboxingDisabled(dComIfGp_event_runCheck())) { + mTrimHeight = 0.0f; + } +#endif + if (mCurState == 1) { mCurState = 0; } else if (mCurState == 2) { @@ -10502,13 +10511,13 @@ bool dCamera_c::eventCamera(s32 param_0) { #endif #if TARGET_PC - if (dusk::frame_interp::is_enabled()) { + if (dusk::interp::is_enabled()) { switch (var_r29) { case 3: case 4: case 5: case 12: - dusk::frame_interp::request_presentation_sync(); + dusk::interp::request_presentation_sync(); break; default: DuskLog.debug( @@ -10590,9 +10599,11 @@ bool dCamera_c::eventCamera(s32 param_0) { if (getEvIntData(&sp24, "LoadPos") != 0) { if ((sp24 == 0) || (sp24 == 1)) { popInfo(&mSavedViewStack[sp24]); + IF_DUSK(dusk::interp::request_presentation_sync()); } if (sp24 == 2) { popInfo(&mSavedView); + IF_DUSK(dusk::interp::request_presentation_sync()); } } } @@ -10787,6 +10798,7 @@ s16 dCamera_c::runEventRecoveryTrans() { void dCamera_c::EventRecoverNotime() { mRecovery.field_0x4 = 1; Reset(mRecovery.field_0x8.mCenter, mRecovery.field_0x8.mEye, mRecovery.field_0x8.mFovy, mRecovery.field_0x8.mBank); + IF_DUSK(dusk::interp::request_presentation_sync()); } int dCamera_c::Set(cXyz i_center, cXyz i_eye) { @@ -11358,7 +11370,7 @@ void widezoom_correction(camera_process_class* i_this, float trim_height) { trim_width = FB_WIDTH_BASE / 2.0f * (1.0f - target_ar_real / current_ar); } - if (dusk::frame_interp::is_sim_frame()) { + if (dusk::game_clock::is_sim_frame()) { constexpr auto base_ar = static_cast(FB_WIDTH_BASE) / static_cast(FB_HEIGHT_BASE); const auto ar_corr = base_ar / std::min(current_ar, target_ar_real); @@ -11396,8 +11408,8 @@ static int camera_execute(camera_process_class* i_this) { #ifdef TARGET_PC widezoom_correction(i_this, i_this->mCamera.TrimHeight()); - if (dusk::frame_interp::is_enabled()) { - dusk::frame_interp::add_interpolation_callback([](bool _, void* pUserWork) { + if (dusk::interp::is_enabled()) { + dusk::interp::add_interpolation_callback([](void* pUserWork) { const auto i_this = static_cast(pUserWork); const auto camera = &i_this->mCamera; @@ -11406,7 +11418,7 @@ static int camera_execute(camera_process_class* i_this) { if (camera->mCurState != 2 && trim_size >= 0 && trim_size <= 3) { // derive trim height at previous tick using current camera state const auto target = get_target_trim_height(i_this); - const auto step = dusk::frame_interp::get_interpolation_step(); + const auto step = dusk::interp::get_interpolation_step(); const auto cur = camera->TrimHeight(); const auto prev = (4.0f * cur - target) / 3.0f; const auto trim_height = prev + (cur - prev) * step; @@ -11417,10 +11429,10 @@ static int camera_execute(camera_process_class* i_this) { } // record new camera for our sim frame - dusk::frame_interp::record_camera(i_this, get_camera_id(i_this)); + dusk::interp::record_camera(i_this, get_camera_id(i_this)); // interpolate the view now so that this sim frame's view matrix matches what // we'll be rendering with later - dusk::frame_interp::interp_view(&i_this->view); + dusk::interp::interp_view(&i_this->view); #endif view_setup(i_this); diff --git a/src/d/d_com_inf_game.cpp b/src/d/d_com_inf_game.cpp index ecd0b796a0..8b3f0bde1d 100644 --- a/src/d/d_com_inf_game.cpp +++ b/src/d/d_com_inf_game.cpp @@ -19,17 +19,18 @@ #include "d/d_timer.h" #include "f_op/f_op_msg_mng.h" #include "f_op/f_op_scene_mng.h" -#include "m_Do/m_Do_MemCard.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include #include -#include "helpers/string.hpp" #if TARGET_PC #include "dusk/settings.h" +#include "helpers/string.hpp" + +#include "m_Do/m_Do_MemCard.h" #endif void dComIfG_play_c::ct() { @@ -1245,12 +1246,12 @@ BOOL dComIfG_resetToOpening(scene_class* i_scene) { } #endif - #ifdef TARGET_PC +#if TARGET_PC if (!mDoMemCd_isCardCommNone()) { return 0; } g_mDoMemCd_control.SaveSync(); - #endif +#endif dComIfG_changeOpeningScene(i_scene, fpcNm_OPENING_SCENE_e); mDoAud_bgmStop(30); diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index cf4a830799..d50c0500f3 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -14,14 +14,19 @@ #include "m_Do/m_Do_mtx.h" #if TARGET_PC -#include -#include -#include "JSystem/JKernel/JKRHeap.h" -#include "absl/container/flat_hash_map.h" -#include "tracy/Tracy.hpp" -#include "dusk/frame_interpolation.h" -#include "helpers/gx_helper.h" +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" #include "dusk/logging.h" +#include "helpers/gx_helper.h" + +#include "d/d_menu_collect.h" + +#include "JSystem/JKernel/JKRHeap.h" + +#include +#include + +#include static const void* getInterpKey(const void* base, int idx) { return reinterpret_cast(reinterpret_cast(base) ^ idx); @@ -1078,7 +1083,7 @@ void dDlst_shadowReal_c::reset() { void dDlst_shadowReal_c::imageDraw(Mtx param_0) { #ifdef TARGET_PC Mtx render_proj_mtx; - if (dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 2), render_proj_mtx)) { + if (dusk::interp::lookup_replacement(getInterpKey(mpModels[0], 2), render_proj_mtx)) { GXSetProjection(render_proj_mtx, GX_ORTHOGRAPHIC); } else #endif @@ -1099,7 +1104,7 @@ void dDlst_shadowReal_c::imageDraw(Mtx param_0) { shape_pkt = (*models)->getShapePacket(j); #ifdef TARGET_PC Mtx view_mtx; - if (dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx)) { + if (dusk::interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx)) { shape_pkt->setBaseMtxPtr(&view_mtx); } else #endif @@ -1128,8 +1133,8 @@ void dDlst_shadowReal_c::draw() { GXSetCurrentMtx(GX_PNMTX0); #ifdef TARGET_PC Mtx view_mtx, recv_proj_mtx; - const auto have_view_mtx = dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx); - const auto have_recv_proj_mtx = dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 3), recv_proj_mtx); + const auto have_view_mtx = dusk::interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx); + const auto have_recv_proj_mtx = dusk::interp::lookup_replacement(getInterpKey(mpModels[0], 3), recv_proj_mtx); if (have_view_mtx && have_recv_proj_mtx) { cMtx_concat(recv_proj_mtx, view_mtx, recv_proj_mtx); GXLoadTexMtxImm(recv_proj_mtx, GX_TEXMTX0, GX_MTX3x4); @@ -1297,9 +1302,9 @@ u8 dDlst_shadowReal_c::setShadowRealMtx(cXyz* param_0, cXyz* param_1, f32 param_ #ifdef TARGET_PC const auto keybase = mpModels[0]; - dusk::frame_interp::record_final_mtx(mViewMtx, getInterpKey(keybase, 1)); - dusk::frame_interp::record_final_mtx(mRenderProjMtx, getInterpKey(keybase, 2)); - dusk::frame_interp::record_final_mtx(mReceiverProjMtx, getInterpKey(keybase, 3)); + dusk::interp::record_final_mtx(mViewMtx, getInterpKey(keybase, 1)); + dusk::interp::record_final_mtx(mRenderProjMtx, getInterpKey(keybase, 2)); + dusk::interp::record_final_mtx(mReceiverProjMtx, getInterpKey(keybase, 3)); #endif cMtx_concat(mReceiverProjMtx, mViewMtx, mReceiverProjMtx); return r29; @@ -1361,6 +1366,16 @@ bool dDlst_shadowReal_c::add(J3DModel* i_model) { return true; } +#if TARGET_PC +static MtxP get_simple_shadow_mtx(Mtx worldMtx, const void* key, Mtx storage) { + if (!dusk::interp::lookup_replacement(key, storage)) { + cMtx_copy(worldMtx, storage); + } + cMtx_concat(j3dSys.getViewMtx(), storage, storage); + return storage; +} +#endif + void dDlst_shadowSimple_c::draw() { static GXColor l_color = {0, 0, 0, 64}; l_color.a = mAlpha; @@ -1368,31 +1383,23 @@ void dDlst_shadowSimple_c::draw() { GXSetTevColor(GX_TEVREG0, l_color); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_INDEX8); -#ifdef TARGET_PC - Mtx volume_mtx; - if (dusk::frame_interp::lookup_replacement(mVolumeMtxKey, volume_mtx)) { - cMtx_concat(j3dSys.getViewMtx(), volume_mtx, volume_mtx); - GXLoadPosMtxImm(volume_mtx, GX_PNMTX0); - } else +#if TARGET_PC + Mtx volumeMtx; + GXLoadPosMtxImm(get_simple_shadow_mtx(mVolumeMtx, mVolumeMtxKey, volumeMtx), GX_PNMTX0); +#else + GXLoadPosMtxImm(mVolumeMtx, GX_PNMTX0); #endif - { - GXLoadPosMtxImm(mVolumeMtx, GX_PNMTX0); - } GXSetCurrentMtx(GX_PNMTX0); GXCallDisplayList(l_frontMat, 0x40); GXCallDisplayList(l_shadowVolumeDL, 0x40); GXCallDisplayList(l_backSubMat, 0x20); GXCallDisplayList(l_shadowVolumeDL, 0x40); -#ifdef TARGET_PC - Mtx shadow_mtx; - if (dusk::frame_interp::lookup_replacement(mMtxKey, shadow_mtx)) { - cMtx_concat(j3dSys.getViewMtx(), shadow_mtx, shadow_mtx); - GXLoadPosMtxImm(shadow_mtx, GX_PNMTX1); - } else +#if TARGET_PC + Mtx shadowMtx; + GXLoadPosMtxImm(get_simple_shadow_mtx(mMtx, mMtxKey, shadowMtx), GX_PNMTX1); +#else + GXLoadPosMtxImm(mMtx, GX_PNMTX1); #endif - { - GXLoadPosMtxImm(mMtx, GX_PNMTX1); - } GXSetCurrentMtx(GX_PNMTX1); if (mpTexObj != NULL) { @@ -1447,9 +1454,11 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa mDoMtx_stack_c::scaleM(param_2, f30 + f30 + 16.0f, param_2 * param_5); #if TARGET_PC mVolumeMtxKey = getInterpKey(param_0, 0x1); - dusk::frame_interp::record_final_mtx(mDoMtx_stack_c::get(), mVolumeMtxKey); -#endif + dusk::interp::record_final_mtx(mDoMtx_stack_c::get(), mVolumeMtxKey); + cMtx_copy(mDoMtx_stack_c::get(), mVolumeMtx); +#else cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), mVolumeMtx); +#endif f32 f31 = JMAFastSqrt(1.0f - param_3->x * param_3->x); f32 f29; f32 f28; @@ -1474,11 +1483,13 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa mDoMtx_stack_c::get()[2][3] = param_0->z; mDoMtx_stack_c::YrotM(param_4); mDoMtx_stack_c::scaleM(param_2, 1.0f, param_2 * param_5); -#ifdef TARGET_PC +#if TARGET_PC mMtxKey = getInterpKey(param_0, 0x2); - dusk::frame_interp::record_final_mtx(mDoMtx_stack_c::get(), mMtxKey); -#endif + dusk::interp::record_final_mtx(mDoMtx_stack_c::get(), mMtxKey); + cMtx_copy(mDoMtx_stack_c::get(), mMtx); +#else cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), mMtx); +#endif mpTexObj = param_6; } @@ -1639,7 +1650,7 @@ void dDlst_shadowControl_c::draw(Mtx param_0) { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); #ifdef TARGET_PC Mtx draw_mtx; - if (dusk::frame_interp::lookup_replacement(param_0, draw_mtx)) { + if (dusk::interp::lookup_replacement(param_0, draw_mtx)) { GXLoadPosMtxImm(draw_mtx, GX_PNMTX0); } else { #endif @@ -1922,6 +1933,7 @@ dDlst_list_c::~dDlst_list_c() { } void dDlst_list_c::reset() { + IF_DUSK(dMenu_Collect3D_c::setViewPortOffsetY(-100.0f)); J3DDrawBuffer** buffer = mDrawBuffers; for (int i = 0; i < 21; i++) { J3DDrawBuffer* tmp = *buffer; @@ -1988,7 +2000,7 @@ void dDlst_list_c::drawXluListItem3d() { } int dDlst_list_c::set(dDlst_base_c**& p_start, dDlst_base_c**& p_end, dDlst_base_c* p_newDlst) { - if (p_start >= p_end) { + if (p_start >= p_end IF_DUSK(|| !dusk::game_clock::is_sim_frame())) { return 0; } *p_start = p_newDlst; @@ -2057,7 +2069,7 @@ void dDlst_list_c::wipeIn(f32 i_wipeSpeed) { void dDlst_list_c::calcWipe() { if (mWipe) { - mWipeRate += mWipeSpeed; + mWipeRate += mWipeSpeed IF_DUSK(* dusk::game_clock::original_frames()); if (mWipeRate < 0.0f) { mWipeRate = 0.0f; } else if (mWipeRate > 1.0f) { @@ -2071,10 +2083,10 @@ void dDlst_list_c::calcWipe() { } #if TARGET_PC -void dDlst_list_c::refresh3DlineMats(const cXyz& eye) { +void dDlst_list_c::refresh3DlineMats() { for (int i = 0; i < 3; i++) { for (mDoExt_3DlineMat_c* mat = m3DLineMatSortPacket[i].getFirstMat(); mat != NULL; mat = mat->field_0x4) { - mat->refreshGeometryForPresentationEye(eye); + mat->refreshGeometryForPresentation(); } } } diff --git a/src/d/d_ev_camera.cpp b/src/d/d_ev_camera.cpp index 2650c2d05e..d46ee8a94b 100644 --- a/src/d/d_ev_camera.cpp +++ b/src/d/d_ev_camera.cpp @@ -13,12 +13,14 @@ #include "d/actor/d_a_alink.h" #include +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" + #include "helpers/string.hpp" -#ifdef __MWERKS__ -#define LOAD_4BYTE_STRING_LITERAL(x) (*(u32*)(x)) -#else #define LOAD_4BYTE_STRING_LITERAL(x) read_big_endian_u32(x) +#else +#define LOAD_4BYTE_STRING_LITERAL(x) (*(u32*)(x)) #endif namespace { @@ -1832,6 +1834,7 @@ bool dCamera_c::restorePosEvCamera() { mViewCache.mDirection.Val(restorePos->field_0x48.mEye - restorePos->field_0x48.mCenter); mViewCache.mEye = mViewCache.mCenter + mViewCache.mDirection.Xyz(); mViewCache.mFovy = restorePos->field_0x48.mFovy; + IF_DUSK(dusk::interp::request_presentation_sync()); } } @@ -2683,6 +2686,11 @@ bool dCamera_c::loadEvCamera() { } if (mCurCamStyleTimer < load->mTimer) { +#if TARGET_PC + if (mCurCamStyleTimer == 0 && load->mTimer == 1) { + dusk::interp::request_presentation_sync(); + } +#endif f32 fVar5; if (load->mType != 0) { mEventData.field_0xf0.Step(); @@ -3488,6 +3496,11 @@ bool dCamera_c::fixedFramesEvCamera() { mViewCache.mEye = fframes_p->field_0x10; mViewCache.mFovy = fframes_p->field_0x28; mViewCache.mDirection.Val(mViewCache.mEye - mViewCache.mCenter); +#if TARGET_PC + if (mCurCamStyleTimer == 0) { + dusk::interp::request_presentation_sync(); + } +#endif if (fframes_p->field_0x0 && mCurCamStyleTimer < fframes_p->mTimer) { return 0; } @@ -3541,6 +3554,11 @@ bool dCamera_c::bSplineEvCamera() { } if (bSpline->field_0x1c == 0 && mEventData.field_0xf0.Step()) { +#if TARGET_PC + if (mCurCamStyleTimer == 0) { + dusk::interp::request_presentation_sync(); + } +#endif cXyz tmpcenters = mEventData.field_0xf0.Calc(bSpline->mCenters); cXyz tmpeyes = mEventData.field_0xf0.Calc(bSpline->mEyes); diff --git a/src/d/d_event.cpp b/src/d/d_event.cpp index b17ebf61f0..4200b1ed75 100644 --- a/src/d/d_event.cpp +++ b/src/d/d_event.cpp @@ -13,7 +13,9 @@ #include "SSystem/SComponent/c_counter.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif namespace { static u8 event_debug_evnt() { diff --git a/src/d/d_event_manager.cpp b/src/d/d_event_manager.cpp index da50ec7d96..70b818c922 100644 --- a/src/d/d_event_manager.cpp +++ b/src/d/d_event_manager.cpp @@ -15,7 +15,9 @@ #include "SSystem/SComponent/c_counter.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif #if DEBUG static dEvM_HIO_c l_HIO; @@ -337,10 +339,13 @@ bool dEvent_manager_c::setObjectArchive(DUSK_CONST char* arcname) { rt = dComIfG_getObjectRes(arcname, DataFileName); #if TARGET_PC if (rt != nullptr && strcmp(arcname, "Prayer") == 0) { - // pointer to Prayer event `011get_item` prm0 in it's event_list.dat + // pointer to Prayer event `011get_item` prm0 in its event_list.dat u8* itemNo = static_cast(rt) + 0x927; *itemNo = dusk::mods::item_check("prayer_reward", dItemNo_KAKERA_HEART_e, NULL); + // set the proper item give tag + u32* prm1 = reinterpret_cast(static_cast(rt) + 0x928); + *prm1 = dusk::mods::item_give_tag("prayer_reward"); } #endif int base_status = mEventList[BASE_ACTOR].init((char*)rt, -1); diff --git a/src/d/d_file_sel_info.cpp b/src/d/d_file_sel_info.cpp index 70bd101aa0..377754b834 100644 --- a/src/d/d_file_sel_info.cpp +++ b/src/d/d_file_sel_info.cpp @@ -14,8 +14,10 @@ #include #include -#include "helpers/string.hpp" +#if TARGET_PC #include "dusk/version.hpp" +#include "helpers/string.hpp" +#endif dFile_info_c::dFile_info_c(JKRArchive* i_archive, u8 param_1) { mArchive = i_archive; diff --git a/src/d/d_file_sel_warning.cpp b/src/d/d_file_sel_warning.cpp index fc17e05a0d..b76998c038 100644 --- a/src/d/d_file_sel_warning.cpp +++ b/src/d/d_file_sel_warning.cpp @@ -11,7 +11,11 @@ #include "d/d_file_sel_warning.h" #include "d/d_msg_string.h" #include "d/d_pane_class.h" + +#if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" +#endif typedef void (dFile_warning_c::*procFunc)(); static procFunc fileWarningProc[] = {&dFile_warning_c::modeWait, &dFile_warning_c::modeMove}; @@ -58,6 +62,10 @@ void dFile_warning_c::screenSet() { mFileWarn.Scr->setAnimation(field_0x24); field_0x24->setFrame(2849.0f); mFileWarn.Scr->animation(); +#if TARGET_PC + field_0x28 = 2849; + field_0x2c = 2849; +#endif mFileWarn.mFont = mDoExt_getMesgFont(); mpRootPane = JKR_NEW CPaneMgr(mFileWarn.Scr, MULTI_CHAR('Nm_02'), 0, NULL); @@ -113,6 +121,7 @@ bool dFile_warning_c::baseMoveAnm() { bool rt; if (field_0x28 != field_0x2c) { +#if !TARGET_PC if (field_0x28 < field_0x2c) { field_0x28 += 2; @@ -129,6 +138,7 @@ bool dFile_warning_c::baseMoveAnm() { field_0x24->setFrame(field_0x28); mFileWarn.Scr->animation(); +#endif rt = false; } @@ -146,6 +156,19 @@ bool dFile_warning_c::baseMoveAnm() { return rt; } +#if TARGET_PC +void dFile_warning_c::presentAnims() { + if (field_0x28 == field_0x2c) { + return; + } + dusk::vdt::present_toward(field_0x28, (f32)field_0x2c, field_0x24); + mFileWarn.Scr->animation(); + if (mPosY != 0.0f) { + mpRootPane->translate(mpRootPane->getTranslateX(), mPosY - field_0x34); + } +} +#endif + void dFile_warning_c::openInit() { field_0x28 = 2849; field_0x2c = 2859; diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index 3f64b8b239..480e10fdf7 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -23,13 +23,14 @@ #include "m_Do/m_Do_graphic.h" #include -#include "dusk/utilities.hpp" -#include "dusk/version.hpp" - #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/game_mode.hpp" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/mods/svc/save.hpp" +#include "dusk/utilities.hpp" +#include "dusk/version.hpp" #include "helpers/string.hpp" namespace { @@ -358,6 +359,8 @@ static DataSelProcFunc DataSelProc[] = { }; void dFile_select_c::_move() { + IF_DUSK(mNameMoveRequested = false); + #if DEBUG if (g_fsHIO.title_mesg_check) { titleMsgCheck(); @@ -394,6 +397,7 @@ void dFile_select_c::_move() { (this->*DataSelProc[mDataSelProc])(); +#if !TARGET_PC selFileWakuAnm(); bookIconAnm(); dataDelEffAnm(); @@ -409,6 +413,7 @@ void dFile_select_c::_move() { copyBookIconAnm(); mCpSel.Scr->animation(); } +#endif mpFileWarning->_move(); mpFileSelect3d->_move(); @@ -478,6 +483,15 @@ void dFile_select_c::errorMsgCheck() { #endif void dFile_select_c::selFileWakuAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mSelFileBpkFrame, mFileSelBpk, 2.0f); + dusk::vdt::present_looping(mSelFileBtk05Frame, mFileSel05Btk, 2.0f); + dusk::vdt::present_looping(mYnSelBpkFrame, mYnSelBpk, 2.0f); + dusk::vdt::present_looping(mYnSelBtkFrame, mYnSelBtk, 2.0f); + dusk::vdt::present_looping(m3mBpkFrame, m3mBpk, 2.0f); + dusk::vdt::present_looping(m3mBtkFrame, m3mBtk, 2.0f); + dusk::vdt::present_looping(mSelDtBtkFrame, mSelDtBtk, 2.0f); +#else mSelFileBpkFrame += 2; if (mSelFileBpkFrame >= mFileSelBpk->getFrameMax()) mSelFileBpkFrame -= mFileSelBpk->getFrameMax(); @@ -512,9 +526,15 @@ void dFile_select_c::selFileWakuAnm() { if (mSelDtBtkFrame >= mSelDtBtk->getFrameMax()) mSelDtBtkFrame -= mSelDtBtk->getFrameMax(); mSelDtBtk->setFrame(mSelDtBtkFrame); +#endif } void dFile_select_c::bookIconAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mSelFileBookBpkFrame, mSelFileBookBpk, 2.0f); + dusk::vdt::present_looping(mSelFileBookBtkFrame, mSelFileBookBtk, 2.0f); + dusk::vdt::present_looping(mSelFileBookBrkFrame, mSelFileBookBrk, 2.0f); +#else mSelFileBookBpkFrame += 2; if (mSelFileBookBpkFrame >= mSelFileBookBpk->getFrameMax()) mSelFileBookBpkFrame -= mSelFileBookBpk->getFrameMax(); @@ -529,9 +549,14 @@ void dFile_select_c::bookIconAnm() { if (mSelFileBookBrkFrame >= mSelFileBookBrk->getFrameMax()) mSelFileBookBrkFrame -= mSelFileBookBrk->getFrameMax(); mSelFileBookBrk->setFrame(mSelFileBookBrkFrame); +#endif } void dFile_select_c::selCopyFileWakuAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mCpSelBpkFrame, mCpSelBpk, 2.0f); + dusk::vdt::present_looping(mCpSel03BtkFrame, mCpSel03Btk, 2.0f); +#else mCpSelBpkFrame += 2; if (mCpSelBpkFrame >= mCpSelBpk->getFrameMax()) mCpSelBpkFrame -= mCpSelBpk->getFrameMax(); @@ -541,9 +566,15 @@ void dFile_select_c::selCopyFileWakuAnm() { if (mCpSel03BtkFrame >= mCpSel03Btk->getFrameMax()) mCpSel03BtkFrame -= mCpSel03Btk->getFrameMax(); mCpSel03Btk->setFrame(mCpSel03BtkFrame); +#endif } void dFile_select_c::copyBookIconAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mCpSelBookBpkFrame, mCpSelBookBpk, 2.0f); + dusk::vdt::present_looping(mCpSelBookBtkFrame, mCpSelBookBtk, 2.0f); + dusk::vdt::present_looping(mCpSelBookBrkFrame, mCpSelBookBrk, 2.0f); +#else mCpSelBookBpkFrame += 2; if (mCpSelBookBpkFrame >= mCpSelBookBpk->getFrameMax()) mCpSelBookBpkFrame -= mCpSelBookBpk->getFrameMax(); @@ -558,10 +589,15 @@ void dFile_select_c::copyBookIconAnm() { if (mCpSelBookBrkFrame >= mCpSelBookBrk->getFrameMax()) mCpSelBookBrkFrame -= mCpSelBookBrk->getFrameMax(); mCpSelBookBrk->setFrame(mCpSelBookBrkFrame); +#endif } void dFile_select_c::dataDelEffAnm() { if (field_0x0208 != 0) { +#if TARGET_PC + dusk::vdt::present_looping(mCpDtEffBrkFrame, mCpDtEffBrk, 2.0f); + dusk::vdt::present_looping(mDtEffBtkFrame, mDtEffBtk, 2.0f); +#else mCpDtEffBrkFrame += 2; if (mCpDtEffBrkFrame >= mCpDtEffBrk->getFrameMax()) mCpDtEffBrkFrame -= mCpDtEffBrk->getFrameMax(); @@ -571,11 +607,16 @@ void dFile_select_c::dataDelEffAnm() { if (mDtEffBtkFrame >= mDtEffBtk->getFrameMax()) mDtEffBtkFrame -= mDtEffBtk->getFrameMax(); mDtEffBtk->setFrame(mDtEffBtkFrame); +#endif } } void dFile_select_c::dataCopyEffAnm() { if (field_0x0209 != 0) { +#if TARGET_PC + dusk::vdt::present_looping(mCpDtEffBrkFrame, mCpDtEffBrk, 2.0f); + dusk::vdt::present_looping(mCpEffBtkFrame, mCpEffBtk, 2.0f); +#else mCpDtEffBrkFrame += 2; if (mCpDtEffBrkFrame >= mCpDtEffBrk->getFrameMax()) mCpDtEffBrkFrame -= mCpDtEffBrk->getFrameMax(); @@ -585,6 +626,7 @@ void dFile_select_c::dataCopyEffAnm() { if (mCpEffBtkFrame >= mCpEffBtk->getFrameMax()) mCpEffBtkFrame -= mCpEffBtk->getFrameMax(); mCpEffBtk->setFrame(mCpEffBtkFrame); +#endif } } @@ -603,6 +645,7 @@ void dFile_select_c::selectDataBaseMoveAnmInitSet(int i_frame, int i_frameMax) { bool dFile_select_c::selectDataBaseMoveAnm() { bool ret; if (mBaseMoveAnmFrame != mBaseMoveAnmFrameMax) { +#if !TARGET_PC if (mBaseMoveAnmFrame < mBaseMoveAnmFrameMax) { mBaseMoveAnmFrame += 2; if (mBaseMoveAnmFrame > mBaseMoveAnmFrameMax) @@ -615,6 +658,7 @@ bool dFile_select_c::selectDataBaseMoveAnm() { mBaseMoveAnm->setFrame(mBaseMoveAnmFrame); mBaseMovePane->getPanePtr()->animationTransform(); +#endif ret = false; } else { if (mBaseMoveAnmFrame == 33) { @@ -754,6 +798,7 @@ void dFile_select_c::dataSelectInit() { isKetteiTxtDisp = ketteiTxtDispAnm(); if (field_0x00e0[mSelectNum] != SelEndFrameTbl[mSelectNum]) { +#if !TARGET_PC field_0x00e0[mSelectNum] += 2; if (field_0x00e0[mSelectNum] > SelEndFrameTbl[mSelectNum]) @@ -761,6 +806,7 @@ void dFile_select_c::dataSelectInit() { mBaseMoveAnm->setFrame(field_0x00e0[mSelectNum]); mSelFilePanes[mSelectNum]->getPanePtr()->animationTransform(); +#endif check = false; } } @@ -1059,6 +1105,7 @@ bool dFile_select_c::selectDataMoveAnm() { bool ret; if (field_0x00e0[mSelectNum] != field_0x00ec) { +#if !TARGET_PC if (field_0x00e0[mSelectNum] < field_0x00ec) { field_0x00e0[mSelectNum] += 2; @@ -1078,6 +1125,7 @@ bool dFile_select_c::selectDataMoveAnm() { mSelFilePanes[i]->getPanePtr()->animationTransform(); } mBaseSubPane->animationTransform(); +#endif } if (field_0x00e0[mSelectNum] == field_0x00ec) { @@ -1134,6 +1182,7 @@ void dFile_select_c::dataSelectMoveAnime() { iVar6 = selectWakuAlpahAnm(mLastSelectNum); if (field_0x00e0[mLastSelectNum] != SelStartFrameTbl[mLastSelectNum]) { +#if !TARGET_PC field_0x00e0[mLastSelectNum] -= 2; if (field_0x00e0[mLastSelectNum] < SelStartFrameTbl[mLastSelectNum]) @@ -1141,6 +1190,7 @@ void dFile_select_c::dataSelectMoveAnime() { field_0x0088->setFrame(field_0x00e0[mLastSelectNum]); mSelFilePanes[mLastSelectNum]->getPanePtr()->animationTransform(); +#endif bVar1 = false; } } @@ -1152,6 +1202,7 @@ void dFile_select_c::dataSelectMoveAnime() { iVar5 = mSelFilePane_Book_l[mSelectNum]->alphaAnime(g_fsHIO.base_effect_appear_frames, 0, 0xff, 1); if (field_0x00e0[mSelectNum] != SelEndFrameTbl[mSelectNum]) { +#if !TARGET_PC field_0x00e0[mSelectNum] += 2; if (field_0x00e0[mSelectNum] > SelEndFrameTbl[mSelectNum]) @@ -1159,6 +1210,7 @@ void dFile_select_c::dataSelectMoveAnime() { mBaseMoveAnm->setFrame(field_0x00e0[mSelectNum]); mSelFilePanes[mSelectNum]->getPanePtr()->animationTransform(); +#endif bVar2 = false; } } @@ -1428,7 +1480,7 @@ void dFile_select_c::menuSelectStart() { mDataSelProc = DATASELPROC_NEXT_MODE_WAIT; dComIfGs_setDataNum(mSelectNum); #if TARGET_PC - dusk::mods::svc::save_slot_loaded(mSelectNum, &mSaveData[mSelectNum]); + dusk::mods::svc::save_slot_loaded(mSelectNum); const dusk::gamemode::GameMode* gameMode = dusk::gamemode::getGameModeManager().getCurrentGameMode(); @@ -1489,6 +1541,7 @@ bool dFile_select_c::menuMoveAnm() { bool ret; if (field_0x0358 != field_0x035c) { +#if !TARGET_PC if (field_0x0358 < field_0x035c) { field_0x0358 += 2; @@ -1503,6 +1556,7 @@ bool dFile_select_c::menuMoveAnm() { m3mBck->setFrame(field_0x0358); m3mMenuPane->animationTransform(); +#endif ret = false; } else { m3mMenuPane->setAnimation((J2DAnmTransform*)NULL); @@ -1562,6 +1616,7 @@ void dFile_select_c::menuSelectMoveAnm() { if (mSelectMenuNum != 0xFF && field_0x034c[mSelectMenuNum] != MenuSelStartFrameTbl[mSelectMenuNum]) { +#if !TARGET_PC if (field_0x034c[mSelectMenuNum] > MenuSelStartFrameTbl[mSelectMenuNum]) { field_0x034c[mSelectMenuNum] -= 2; @@ -1576,6 +1631,7 @@ void dFile_select_c::menuSelectMoveAnm() { m3mBck->setFrame(field_0x034c[mSelectMenuNum]); m3mSelPane[mSelectMenuNum]->getPanePtr()->animationTransform(); +#endif tmp1 = false; } @@ -1584,6 +1640,7 @@ void dFile_select_c::menuSelectMoveAnm() { if (mLastSelectMenuNum != 0xFF) { if (field_0x034c[mLastSelectMenuNum] != MenuSelEndFrameTbl[mLastSelectMenuNum]) { +#if !TARGET_PC if (field_0x034c[mLastSelectMenuNum] < MenuSelEndFrameTbl[mLastSelectMenuNum]) { field_0x034c[mLastSelectMenuNum] += 2; @@ -1598,6 +1655,7 @@ void dFile_select_c::menuSelectMoveAnm() { } m3mBck2->setFrame(field_0x034c[mLastSelectMenuNum]); m3mSelPane[mLastSelectMenuNum]->getPanePtr()->animationTransform(); +#endif tmp2 = false; } @@ -1719,14 +1777,14 @@ void dFile_select_c::nameToDataSelectMove() { } void dFile_select_c::nameInputFade() { - mFadeTimer--; + IF_NOT_DUSK(mFadeTimer--); - #if PLATFORM_GCN +#if !TARGET_PC && PLATFORM_GCN u8 alpha = (1.0f - (mFadeTimer / 15.0f)) * 255.0f; mpFadePict->setAlpha(alpha); - #endif +#endif - if (mFadeTimer == 0) { + if (mFadeTimer DUSK_IF_ELSE(<=, ==) 0) { char name[32]; headerTxtSet(900, 1, 1); dMeter2Info_getString(899, name, NULL); @@ -1747,14 +1805,14 @@ void dFile_select_c::nameInputFade() { void dFile_select_c::nameInput2Move() { if (!mDoRst::isReset()) { - mFadeTimer--; + IF_NOT_DUSK(mFadeTimer--); - #if PLATFORM_GCN +#if !TARGET_PC && PLATFORM_GCN u8 alpha = (mFadeTimer / 15.0f) * 255.0f; mpFadePict->setAlpha(alpha); - #endif +#endif - if (mFadeTimer == 0) { + if (mFadeTimer DUSK_IF_ELSE(<=, ==) 0) { mpName->showIcon(); mDataSelProc = DATASELPROC_NAME_INPUT2; } @@ -1806,14 +1864,14 @@ void dFile_select_c::nameInput2() { } void dFile_select_c::backNameInputMove0() { - mFadeTimer--; + IF_NOT_DUSK(mFadeTimer--); - #if PLATFORM_GCN +#if !TARGET_PC && PLATFORM_GCN u8 alpha = (1.0f - (mFadeTimer / 15.0f)) * 255.0f; mpFadePict->setAlpha(alpha); - #endif +#endif - if (mFadeTimer == 0) { + if (mFadeTimer DUSK_IF_ELSE(<=, ==) 0) { headerTxtSet(901, 1, 1); mpName->setNextNameStr(dComIfGs_getPlayerName()); mpName->initial(); @@ -1832,14 +1890,14 @@ void dFile_select_c::backNameInputMove0() { void dFile_select_c::backNameInputMove() { if (!mDoRst::isReset()) { - mFadeTimer--; + IF_NOT_DUSK(mFadeTimer--); - #if PLATFORM_GCN +#if !TARGET_PC && PLATFORM_GCN u8 alpha = (mFadeTimer / 15.0f) * 255.0f; mpFadePict->setAlpha(alpha); - #endif +#endif - if (mFadeTimer == 0) { + if (mFadeTimer DUSK_IF_ELSE(<=, ==) 0) { modoruTxtChange(1); mDataSelProc = DATASELPROC_NAME_INPUT_WAIT; } @@ -2085,12 +2143,14 @@ void dFile_select_c::copyDataToSelectMoveAnm() { iVar7 = mCpSelPane_book[field_0x026c]->alphaAnime(g_fsHIO.base_effect_appear_frames, 0xff, 0, 1); iVar6 = copySelectWakuAlpahAnm(field_0x026c); if (field_0x02b4[field_0x026c] != 109) { +#if !TARGET_PC field_0x02b4[field_0x026c] += 2; if (field_0x02b4[field_0x026c] > 109) { field_0x02b4[field_0x026c] = 109; } mCpSelBck2->setFrame(field_0x02b4[field_0x026c]); mCpSelPane[field_0x026c]->getPanePtr()->animationTransform(); +#endif bVar1 = false; } } @@ -2100,12 +2160,14 @@ void dFile_select_c::copyDataToSelectMoveAnm() { if (field_0x026b != 0xff) { iVar5 = mCpSelPane_book[field_0x026b]->alphaAnime(g_fsHIO.base_effect_appear_frames, 0, 0xff, 1); if (field_0x02b4[field_0x026b] != 99) { +#if !TARGET_PC field_0x02b4[field_0x026b] -= 2; if (field_0x02b4[field_0x026b] < 99) { field_0x02b4[field_0x026b] = 99; } mCpSelBck->setFrame(field_0x02b4[field_0x026b]); mCpSelPane[field_0x026b]->getPanePtr()->animationTransform(); +#endif bVar2 = false; } } @@ -2260,6 +2322,7 @@ void dFile_select_c::yesnoMenuMoveAnmInitSet(int param_1, int param_2) { bool dFile_select_c::yesnoMenuMoveAnm() { bool rv; if (field_0x0100 != field_0x0104) { +#if !TARGET_PC if (field_0x0100 < field_0x0104) { field_0x0100 += 2; if (field_0x0100 > field_0x0104) { @@ -2275,6 +2338,7 @@ bool dFile_select_c::yesnoMenuMoveAnm() { mYnSelBck3->setFrame(field_0x0100); mYnSelPane[0]->getPanePtr()->animationTransform(); mYnSelPane[1]->getPanePtr()->animationTransform(); +#endif rv = false; } else { mYnSelPane[0]->getPanePtr()->setAnimation((J2DAnmTransform*)NULL); @@ -2338,6 +2402,7 @@ bool dFile_select_c::yesnoSelectMoveAnm() { bool bVar1 = true; if (field_0x0269 != 0xff && field_0x00f8[field_0x0269] != YnSelStartFrameTbl[field_0x0269]) { +#if !TARGET_PC if (field_0x00f8[field_0x0269] < YnSelStartFrameTbl[field_0x0269]) { field_0x00f8[field_0x0269] += 2; if (field_0x00f8[field_0x0269] > YnSelStartFrameTbl[field_0x0269]) { @@ -2352,11 +2417,13 @@ bool dFile_select_c::yesnoSelectMoveAnm() { mYnSelBck->setFrame(field_0x00f8[field_0x0269]); mYnSelPane[field_0x0269]->getPanePtr()->animationTransform(); +#endif bVar1 = false; } bool bVar2 = true; if (field_0x0268 != 0xff && field_0x00f8[field_0x0268] != YnSelEndFrameTbl[field_0x0268]) { +#if !TARGET_PC if (field_0x00f8[field_0x0268] < YnSelEndFrameTbl[field_0x0268]) { field_0x00f8[field_0x0268] += 2; if (field_0x00f8[field_0x0268] > YnSelEndFrameTbl[field_0x0268]) { @@ -2371,6 +2438,7 @@ bool dFile_select_c::yesnoSelectMoveAnm() { mYnSelBck2->setFrame(field_0x00f8[field_0x0268]); mYnSelPane[field_0x0268]->getPanePtr()->animationTransform(); +#endif bVar2 = false; } @@ -3578,9 +3646,7 @@ void dFile_select_c::screenSet() { timg, NULL); mpFadePict->setBlackWhite(black, white); mpFadePict->setAlpha(0); -#ifdef TARGET_PC - mFadeDlst.mpPict = mpFadePict; -#endif + IF_DUSK(mFadeDlst.mpPict = mpFadePict); #endif } @@ -3783,6 +3849,10 @@ void dFile_select_c::screenSet3Menu() { m3mMenuPane = m3mSel.Scr3m->search(MULTI_CHAR('wmenu_n')); m3mMenuPane->setAnimation(m3mBck); +#if TARGET_PC + field_0x0358 = 799; + field_0x035c = 799; +#endif m3mBck->setFrame(799.0f); m3mMenuPane->animationTransform(); @@ -4397,14 +4467,157 @@ void dFile_select_c::fileSelectWide() { break; } } + +void dFile_select_c::presentLayoutAnims() { + dusk::vdt::present_toward(mBaseMoveAnmFrame, (f32)mBaseMoveAnmFrameMax, mBaseMoveAnm, + mBaseMovePane != NULL ? mBaseMovePane->getPanePtr() : NULL); + + if (mBaseSubPane != NULL && mBaseSubPane->mTransform == mBaseMoveAnm && mSelectNum != 0xFF) { + dusk::vdt::present_toward(field_0x00e0[mSelectNum], (f32)field_0x00ec, mBaseMoveAnm); + for (int i = 0; i < 3; i++) { + J2DPane* pane = mSelFilePanes[i]->getPanePtr(); + if (pane != NULL && pane->mTransform != NULL) { + pane->animationTransform(); + } + } + mBaseSubPane->animationTransform(); + } else { + for (int i = 0; i < 3; i++) { + J2DPane* pane = mSelFilePanes[i]->getPanePtr(); + dusk::vdt::present_selected(field_0x00e0[i], pane, mBaseMoveAnm, (f32)SelEndFrameTbl[i], + field_0x0088, (f32)SelStartFrameTbl[i]); + } + } + + dusk::vdt::present_toward(field_0x0358, (f32)field_0x035c, m3mBck, m3mMenuPane); + + for (int i = 0; i < 3; i++) { + J2DPane* pane = m3mSelPane[i]->getPanePtr(); + dusk::vdt::present_selected(field_0x034c[i], pane, m3mBck, (f32)MenuSelStartFrameTbl[i], + m3mBck2, (f32)MenuSelEndFrameTbl[i]); + } + + if (field_0x0100 != field_0x0104) { + dusk::vdt::present_shared(field_0x0100, (f32)field_0x0104, mYnSelBck3, + {mYnSelPane[0]->getPanePtr(), mYnSelPane[1]->getPanePtr()}); + } + + for (int i = 0; i < 2; i++) { + J2DPane* pane = mYnSelPane[i]->getPanePtr(); + dusk::vdt::present_selected(field_0x00f8[i], pane, mYnSelBck, (f32)YnSelStartFrameTbl[i], + mYnSelBck2, (f32)YnSelEndFrameTbl[i]); + } + + for (int i = 0; i < 2; i++) { + if (mCpSelPane[i] == NULL) { + continue; + } + J2DPane* pane = mCpSelPane[i]->getPanePtr(); + dusk::vdt::present_selected(field_0x02b4[i], pane, mCpSelBck, 99.0f, mCpSelBck2, 109.0f); + } + + dusk::vdt::present_toward(field_0x0130, (f32)field_0x0134, field_0x0090, mErrorMsgPane); + + if (mBaseSubPane != NULL && mBaseSubPane->mTransform == field_0x009c) { + dusk::vdt::present_toward(field_0x0110, (f32)field_0x0114, field_0x009c, mBaseSubPane); + } + + if (mNameMoveRequested) { + dusk::vdt::present_toward(field_0x0120, (f32)field_0x0124, field_0x0094, mNameBasePane); + } +} + +void dFile_select_c::presentNameInputFade() { + bool fadeOut; + switch (mDataSelProc) { + case DATASELPROC_NAME_INPUT_FADE: + case DATASELPROC_BACK_NAME_INPUT_MOVE0: + fadeOut = true; + break; + case DATASELPROC_NAME_INPUT2_MOVE: + case DATASELPROC_BACK_NAME_INPUT_MOVE: + if (mDoRst::isReset()) { + return; + } + fadeOut = false; + break; + default: + return; + } + + mFadeTimer -= dusk::game_clock::original_frames(); + if (mFadeTimer < 0.0f) { + mFadeTimer = 0.0f; + } + + f32 t = mFadeTimer / 15.0f; + u8 alpha = fadeOut ? (1.0f - t) * 255.0f : t * 255.0f; + mpFadePict->setAlpha(alpha); +} + +void dFile_select_c::presentAnims() { + if (mpFileWarning != NULL) { + mpFileWarning->presentAnims(); + } + + presentNameInputFade(); + selFileWakuAnm(); + bookIconAnm(); + dataDelEffAnm(); + dataCopyEffAnm(); + fileSel.Scr->animation(); + mYnSel.ScrYn->animation(); + m3mSel.Scr3m->animation(); + mSelDt.ScrDt->animation(); + if (mCpSel.isShow) { + selCopyFileWakuAnm(); + copyBookIconAnm(); + mCpSel.Scr->animation(); + } + presentLayoutAnims(); + for (int i = 0; i < 3; ++i) { + mSelFilePanes[i]->presentAnime(); + mDeleteEfPane[i]->presentAlphaAnime(); + mCopyEfPane[i]->presentAlphaAnime(); + mSelFileMoyoPane[i]->presentAnime(); + mSelFileGoldPane[i]->presentAnime(); + mSelFileGold2Pane[i]->presentAnime(); + mSelFilePane_Book_l[i]->presentAnime(); + mFileInfoDatBasePane[i]->presentAlphaAnime(); + mFileInfoNoDatBasePane[i]->presentAlphaAnime(); + m3mSelPane_mo[i]->presentAnime(); + m3mSelPane_g[i]->presentAnime(); + m3mSelPane_gr[i]->presentAnime(); + m3mSelTextPane[i]->presentAnime(); + } + for (int i = 0; i < 2; ++i) { + mErrorMsgTxtPane[i]->presentAlphaAnime(); + mYnSelPane_m[i]->presentAlphaAnime(); + mYnSelPane_g[i]->presentAlphaAnime(); + mYnSelPane_gr[i]->presentAlphaAnime(); + mYnSelTxtPane[i]->presentAnime(); + mHeaderTxtPane[i]->presentAlphaAnime(); + mCpSelPane_moyo[i]->presentAnime(); + mCpSelPane_gold[i]->presentAnime(); + mCpSelPane_gold2[i]->presentAnime(); + mCpSelPane_book[i]->presentAnime(); + } + mBbtnPane->presentAlphaAnime(); + mAbtnPane->presentAlphaAnime(); + mModoruTxtPane->presentAlphaAnime(); + mKetteiTxtPane->presentAlphaAnime(); + fileSelectWide(); +} #endif void dFile_select_c::_draw() { - #if TARGET_PC - fileSelectWide(); - #endif - if (!mHasDrawn) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + presentAnims(); + } +#endif + dComIfGd_set2DOpa(&fileSel); for (int i = 0; i < 3; i++) { @@ -4432,15 +4645,15 @@ void dFile_select_c::_draw() { dComIfGd_set2DOpa(mSelIcon); dComIfGd_set2DOpa(mSelIcon2); - #if PLATFORM_GCN - #if TARGET_PC +#if PLATFORM_GCN +#if TARGET_PC dComIfGd_set2DOpaTop(&mFadeDlst); - #else +#else mpFadePict->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), false, false, false); - #endif - #endif +#endif +#endif } } @@ -4506,6 +4719,7 @@ void dFile_select_c::errorMoveAnmInitSet(int param_1, int param_2) { bool dFile_select_c::errorMoveAnm() { bool ret; if (field_0x0130 != field_0x0134) { +#if !TARGET_PC if (field_0x0130 < field_0x0134) { field_0x0130 += 2; @@ -4520,6 +4734,7 @@ bool dFile_select_c::errorMoveAnm() { field_0x0090->setFrame(field_0x0130); mErrorMsgPane->animationTransform(); +#endif ret = false; } else { mErrorMsgPane->setAnimation((J2DAnmTransform*)NULL); @@ -5798,6 +6013,7 @@ bool dFile_select_c::fileInfoScaleAnm() { bool ret; if (field_0x0110 != field_0x0114) { +#if !TARGET_PC if (field_0x0110 < field_0x0114) { field_0x0110 += 2; @@ -5812,6 +6028,7 @@ bool dFile_select_c::fileInfoScaleAnm() { field_0x009c->setFrame(field_0x0110); mBaseSubPane->animationTransform(); +#endif ret = false; } @@ -5824,6 +6041,13 @@ bool dFile_select_c::fileInfoScaleAnm() { } void dFile_select_c::nameMoveAnmInitSet(int param_1, int param_2) { +#if TARGET_PC + f32 startFrame = param_1; + if (mNameBasePane->mTransform == field_0x0094 && field_0x0124 == param_1) { + startFrame = field_0x0120; + } + mNameMoveRequested = false; +#endif if (param_1 == 3359) { field_0x0128 = true; } @@ -5831,15 +6055,17 @@ void dFile_select_c::nameMoveAnmInitSet(int param_1, int param_2) { mpName->hideIcon(); } mNameBasePane->setAnimation(field_0x0094); - field_0x0120 = param_1; + field_0x0120 = DUSK_IF_ELSE(startFrame, param_1); field_0x0124 = param_2; field_0x0094->setFrame(field_0x0120); mNameBasePane->animationTransform(); } bool dFile_select_c::nameMoveAnm() { + IF_DUSK(mNameMoveRequested = true); bool ret; if (field_0x0120 != field_0x0124) { +#if !TARGET_PC if (field_0x0120 < field_0x0124) { field_0x0120 += 2; @@ -5855,6 +6081,7 @@ bool dFile_select_c::nameMoveAnm() { } field_0x0094->setFrame(field_0x0120); mNameBasePane->animationTransform(); +#endif ret = false; } else { mNameBasePane->setAnimation((J2DAnmTransform*)0); @@ -5988,6 +6215,7 @@ void dFile_select3D_c::_move() { } void dFile_select3D_c::draw() { + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame()) if (mpModel) { dComIfGd_setListItem3D(); g_env_light.settingTevStruct(13, &field_0x03a4, &mTevstr); @@ -5996,6 +6224,7 @@ void dFile_select3D_c::draw() { mDoExt_modelUpdateDL(mpModel); dComIfGd_setList(); } + IF_DUSK_BLOCK_END } void dFile_select3D_c::setJ3D(char const* param_0, char const* param_1, char const* param_2) { diff --git a/src/d/d_gameover.cpp b/src/d/d_gameover.cpp index c7dcde357c..2dfd843f6f 100644 --- a/src/d/d_gameover.cpp +++ b/src/d/d_gameover.cpp @@ -14,7 +14,10 @@ #include "JSystem/J2DGraph/J2DGrafContext.h" #include +#if TARGET_PC +#include "dusk/game_clock.h" #include "helpers/gx_helper.h" +#endif class dGov_HIO_c : public mDoHIO_entry_c { public: @@ -190,6 +193,9 @@ int dGameover_c::_create() { } (this->*init_process[mProc])(); + + IF_DUSK(base.draw_interp_frame = true); + return cPhs_COMPLEATE_e; return phase; @@ -333,9 +339,11 @@ void dGameover_c::deleteWait_init() {} void dGameover_c::deleteWait_proc() {} int dGameover_c::_draw() { + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame() && (mIsDemoSave && mProc >= PROC_DISP_WAIT)) if (dgo_capture_c != NULL && dComIfGp_isPauseFlag()) { dComIfGd_set2DOpa(dgo_capture_c); } + IF_DUSK_BLOCK_END if (mIsDemoSave && mProc >= PROC_DISP_WAIT) { if (dgo_screen_c != NULL) { diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index f9d77837f9..3b7f4d9ad4 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -32,11 +32,11 @@ #include "JSystem/JKernel/JKRSolidHeap.h" #include #include + #if TARGET_PC -#include "dusk/imgui/ImGuiBloomWindow.hpp" -#include "dusk/settings.h" -#include "dusk/frame_interpolation.h" #include "dusk/game_clock.h" +#include "dusk/interp/material.h" +#include "dusk/imgui/ImGuiBloomWindow.hpp" static f32 timeScale = 1.0f; #endif @@ -1800,9 +1800,7 @@ void dScnKy_env_light_c::setLight_palno_get(u8* prev_envr_id_p, u8* next_envr_id u8 psel_idx = 0; int i; int sp14 = 0; -#if TARGET_PC - const f32 timeScale = (pattern_ratio_p == &g_env_light.pat_ratio) ? ::timeScale : 1.0f; -#endif + IF_DUSK(const f32 timeScale = pattern_ratio_p == &g_env_light.pat_ratio ? ::timeScale : 1.0f); if (*init_timer_p != 0) { (*init_timer_p)++; @@ -2344,10 +2342,7 @@ void dScnKy_env_light_c::setLight() { u8 next_pal_start_id; u8 prev_pal_end_id; u8 next_pal_end_id; -#if TARGET_PC - const f32 deltaTime = dusk::game_clock::consume_interval(this); - timeScale = deltaTime / dusk::game_clock::kSimPeriod; -#endif + IF_DUSK(timeScale = dusk::game_clock::original_frames()); setLight_palno_get(&g_env_light.PrevCol, &g_env_light.UseCol, &g_env_light.wether_pat0, &g_env_light.wether_pat1, &prev_pal_start_id, &prev_pal_end_id, &next_pal_start_id, &next_pal_end_id, &color_ratio, &start_pat_pal_id, @@ -4485,6 +4480,7 @@ static void setLightTevColorType_MAJI_sub(J3DMaterial* material_p, dKy_tevstr_c* } } } + IF_DUSK(dusk::interp::material::record_light_view(material_p)); } } @@ -8275,9 +8271,7 @@ static int dKy_Create(void* i_this) { kankyo_class* kankyo = (kankyo_class*)i_this; BOOL next_time_set = false; -#if TARGET_PC - kankyo->base.draw_interp_frame = true; -#endif + IF_DUSK(kankyo->base.draw_interp_frame = true); stage_envr_info_class* stage_envr_p = dComIfGp_getStageEnvrInfo(); if (stage_envr_p != NULL && dComIfGp_getStartStageRoomNo() != -1) { diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 02dcd94d68..d682736690 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -13,9 +13,34 @@ #include "m_Do/m_Do_lib.h" #include -#include "dusk/version.hpp" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/samples.h" +#include "dusk/interp/user_interface.h" +#include "dusk/version.hpp" + +namespace { +dusk::interp::Samples s_rain; +dusk::interp::Samples s_snow_pos; +dusk::interp::Samples s_snow_base; +dusk::interp::Samples s_housi_world; +dusk::interp::Samples s_housi_scale; +dusk::interp::Samples s_cloud; +dusk::interp::Samples s_vrkumo; +} // namespace + +namespace dusk::interp { +void clear_weather_samples() { + s_rain = {}; + s_snow_pos = {}; + s_snow_base = {}; + s_housi_world = {}; + s_housi_scale = {}; + s_cloud = {}; + s_vrkumo = {}; +} +} // namespace dusk::interp #endif static void vectle_calc(DOUBLE_POS* i_pos, cXyz* o_out) { @@ -129,24 +154,73 @@ static GXTexObj* load_cached_tex(CachedTexObjs& cache, ResTIMG* img, GXTexMap } #endif +#if TARGET_PC +static void dKyr_place_sun(camera_class* camera, cXyz* o_sunpos) { + cXyz lightDir; + u32 stage_type = dStage_stagInfo_GetSTType(dComIfGp_getStage()->getStagInfo()); + if (g_env_light.base_light.mColor.r == 0 && stage_type != ST_ROOM) { + dKyr_get_vectle_calc(&camera->view.lookat.eye, &g_env_light.base_light.mPosition, + &lightDir); + } else { + dKyr_get_vectle_calc(&camera->view.lookat.eye, &g_env_light.sun_light_pos, &lightDir); + } + o_sunpos->x = camera->view.lookat.eye.x + 8000.0f * lightDir.x; + o_sunpos->y = camera->view.lookat.eye.y + 8000.0f * lightDir.y; + o_sunpos->z = camera->view.lookat.eye.z + 8000.0f * lightDir.z; +} + +static void dKyr_place_lenzflare(camera_class* camera, cXyz* sunpos, cXyz* o_positions) { + cXyz eyeVect; + cXyz sunDirSmth; + cXyz camFwd; + + dKy_set_eyevect_calc(camera, &eyeVect, 4000.0f, 4000.0f); + dKyr_get_vectle_calc(&eyeVect, sunpos, &sunDirSmth); + o_positions[0] = *sunpos; + o_positions[1] = *sunpos; + + dKyr_get_vectle_calc(&camera->view.lookat.eye, &camera->view.lookat.center, &camFwd); + + for (int i = 2; i < 8; i++) { + if (i == 2) { + f32 size = 250.0f + 600.0f * sunDirSmth.abs(camFwd); + o_positions[i].x = sunpos->x - sunDirSmth.x * size * i; + o_positions[i].y = sunpos->y - sunDirSmth.y * size * i; + o_positions[i].z = sunpos->z - sunDirSmth.z * size * i; + } else { + f32 size = 250.0f + 110.0f * sunDirSmth.abs(camFwd); + o_positions[i].x = sunpos->x - (4100.0f * sunDirSmth.x + sunDirSmth.x * size * i); + o_positions[i].y = sunpos->y - (4100.0f * sunDirSmth.y + sunDirSmth.y * size * i); + o_positions[i].z = sunpos->z - (4100.0f * sunDirSmth.z + sunDirSmth.z * size * i); + } + } +} +#endif + void dKyr_lenzflare_move() { dKankyo_sun_Packet* sun_packet = g_env_light.mpSunPacket; dKankyo_sunlenz_Packet* lenz_packet = g_env_light.mpSunLenzPacket; camera_process_class* camera = dComIfGp_getCamera(0); +#if !TARGET_PC cXyz eyeVect; cXyz field_0x3c; cXyz sunDirSmth; cXyz camFwd; +#endif if (sun_packet->mVisibility < 0.0001f) { return; } +#if TARGET_PC + dKyr_place_lenzflare(camera, sun_packet->mPos, lenz_packet->mPositions); +#else dKy_set_eyevect_calc(camera, &eyeVect, 4000.0f, 4000.0f); dKyr_get_vectle_calc(&eyeVect, sun_packet->mPos, &sunDirSmth); lenz_packet->mPositions[0] = sun_packet->mPos[0]; lenz_packet->mPositions[1] = sun_packet->mPos[0]; +#endif cXyz vect; cXyz proj; @@ -162,6 +236,7 @@ void dKyr_lenzflare_move() { lenz_packet->field_0x94 *= S2DEG_CONSTANT; // convert from short angle to degrees lenz_packet->field_0x94 += 180.0f; +#if !TARGET_PC dKyr_get_vectle_calc(&camera->view.lookat.eye, &camera->view.lookat.center, &camFwd); for (int i = 2; i < 8; i++) { @@ -177,6 +252,7 @@ void dKyr_lenzflare_move() { lenz_packet->mPositions[i].z = sun_packet->mPos[0].z - (4100.0f * sunDirSmth.z + sunDirSmth.z * size * i); } } +#endif } static BOOL dKyr_moon_arrival_check() { @@ -205,6 +281,10 @@ void dKyr_sun_move() { u32 stage_type = dStage_stagInfo_GetSTType(dComIfGp_getStage()->getStagInfo()); +#if TARGET_PC + dKyr_place_sun(camera_p2, &sun_packet->mPos[0]); + dKyr_get_vectle_calc(&camera_p2->view.lookat.eye, &sun_packet->mPos[0], &lightDir); +#else if (g_env_light.base_light.mColor.r == 0 && stage_type != ST_ROOM) { dKyr_get_vectle_calc(&camera_p2->view.lookat.eye, &g_env_light.base_light.mPosition, &lightDir); @@ -215,6 +295,7 @@ void dKyr_sun_move() { sun_packet->mPos[0].x = camera_p2->view.lookat.eye.x + 8000.0f * lightDir.x; sun_packet->mPos[0].y = camera_p2->view.lookat.eye.y + 8000.0f * lightDir.y; sun_packet->mPos[0].z = camera_p2->view.lookat.eye.z + 8000.0f * lightDir.z; +#endif f32 horizon_y = (sun_packet->mPos[0].y - camera_p2->view.lookat.eye.y) / 8000.0f; if (horizon_y < 0.0f) { @@ -477,6 +558,7 @@ void dKyr_rain_init() { g_env_light.mpRainPacket->mRainEff[i].mStatus = 0; } g_env_light.mpRainPacket->raincnt = 0; + IF_DUSK(s_rain.reset()); } static void rain_bg_chk(dKankyo_rain_Packet* i_packet, int i_idx) { @@ -901,6 +983,12 @@ void dKyr_rain_move() { rain_packet->mRainEff[i].mAlpha = var_f31 * (1.0f + cM_rndFX(0.5f)); } } + +#if TARGET_PC + s_rain.capture(rain_packet->raincnt, [&](int i) { + return rain_packet->mRainEff[i].mBasePos + rain_packet->mRainEff[i].mPosition; + }); +#endif } static BOOL d_krain_cut_turn_check() { @@ -974,6 +1062,10 @@ void dKyr_housi_move() { } if (housi_packet->mHousiCount == 0) { +#if TARGET_PC + s_housi_world.reset(); + s_housi_scale.reset(); +#endif return; } @@ -1263,6 +1355,15 @@ void dKyr_housi_move() { f32 temp_f25 = var_f1_8 / 2000.0f; effect->field_0x48 = 1.0f - (temp_f25 * temp_f25); } + +#if TARGET_PC + s_housi_world.capture(housi_packet->mHousiCount, [&](int i) { + return housi_packet->mHousiEff[i].mBasePos + housi_packet->mHousiEff[i].mPosition; + }); + s_housi_scale.capture(housi_packet->mHousiCount, [&](int i) { + return housi_packet->mHousiEff[i].mScale; + }); +#endif } void dKyr_snow_init() { @@ -1279,6 +1380,10 @@ void dKyr_snow_init() { g_env_light.mpSnowPacket->mSnowEff[i].mStatus = 0; } +#if TARGET_PC + s_snow_pos.reset(); + s_snow_base.reset(); +#endif g_env_light.mpSnowPacket->field_0x6d88 = 0; g_env_light.mpSnowPacket->field_0x6d74 = camera->view.lookat.eye; g_env_light.mpSnowPacket->field_0x6d80 = 0.0f; @@ -1313,6 +1418,10 @@ void dKyr_snow_move() { } if (snow_packet->field_0x6d88 == 0) { +#if TARGET_PC + s_snow_pos.reset(); + s_snow_base.reset(); +#endif return; } @@ -1557,6 +1666,15 @@ void dKyr_snow_move() { } } } + +#if TARGET_PC + s_snow_pos.capture(snow_packet->field_0x6d88, [&](int i) { + return snow_packet->mSnowEff[i].mPosition; + }); + s_snow_base.capture(snow_packet->field_0x6d88, [&](int i) { + return snow_packet->mSnowEff[i].mBasePos; + }); +#endif } void dKyr_star_init() { @@ -1840,6 +1958,12 @@ void cloud_shadow_move() { cLib_addCalc(&packet->mCloudEff[i].mAlpha, alpha_max * alpha_target, 0.1f, 0.1f, 0.001f); } } + +#if TARGET_PC + s_cloud.capture(packet->mCount, [&](int i) { + return packet->mCloudEff[i].mBasePos + packet->mCloudEff[i].mPosition; + }); +#endif } void vrkumo_move() { @@ -2058,6 +2182,12 @@ void vrkumo_move() { max_alpha *= spC; cLib_addCalc(&vrkumo_packet->mVrkumoEff[i].mAlpha, max_alpha, 0.5f, alpha_step, 0.001f); } + +#if TARGET_PC + s_vrkumo.capture(100, [&](int i) { + return vrkumo_packet->mVrkumoEff[i].mPosition; + }); +#endif } static void dKr_cullVtx_Set(IF_DUSK(bool const vtxColor = false)) { @@ -2414,15 +2544,17 @@ void dKyr_drawSun(Mtx drawMtx, cXyz* ppos, GXColor& unused, u8** tex) { sunpos.y = ppos->y; sunpos.z = ppos->z; + IF_DUSK(dKyr_place_sun(camera, &sunpos)); + u32 stage_type = dStage_stagInfo_GetSTType(dComIfGp_getStage()->getStagInfo()); if (g_env_light.base_light.mColor.r == 0 && stage_type != ST_ROOM) { if (g_env_light.daytime > 285.0f || g_env_light.daytime < 105.0f) { draw_moon = false; } - spB4.x = ppos->x; - spB4.y = ppos->y; - spB4.z = ppos->z; + spB4.x = DUSK_IF_ELSE(sunpos.x, ppos->x); + spB4.y = DUSK_IF_ELSE(sunpos.y, ppos->y); + spB4.z = DUSK_IF_ELSE(sunpos.z, ppos->z); } else { if (strcmp(dComIfGp_getStartStageName(), "F_SP200") == 0 && dComIfG_play_c::getLayerNo(0) == 0) { spB4 = envlight->moon_pos; @@ -2588,7 +2720,8 @@ void dKyr_drawSun(Mtx drawMtx, cXyz* ppos, GXColor& unused, u8** tex) { }; if (strcmp(dComIfGp_getStartStageName(), "F_SP200") != 0) { - dKyr_get_vectle_calc(&camera->view.lookat.eye, &camera->view.lookat.center, &camfwd); + dKyr_get_vectle_calc(&camera->view.lookat.eye, + &camera->view.lookat.center, &camfwd); f32 cam_distXZ = JMAFastSqrt((camfwd.x * camfwd.x) + (camfwd.z * camfwd.z)); f32 cam_theta = atan2f(camfwd.x, camfwd.z); f32 cam_phi = atan2f(camfwd.y, cam_distXZ); @@ -2755,8 +2888,8 @@ void dKyr_drawLenzflare(Mtx drawMtx, cXyz* ppos, GXColor& param_2, u8** tex) { dKankyo_sun_Packet* sun_packet = g_env_light.mpSunPacket; camera_class* camera = (camera_class*)dComIfGp_getCamera(0); - static s16 S_rot_work1 = 0; - static s16 S_rot_work2 = 0; + static DUSK_IF_ELSE(f32, s16) S_rot_work1 = 0; + static DUSK_IF_ELSE(f32, s16) S_rot_work2 = 0; #if TARGET_PC || VERSION == VERSION_GCN_JPN IF_DUSK_BLOCK(dusk::version::isRegionJpn()) @@ -2783,6 +2916,13 @@ void dKyr_drawLenzflare(Mtx drawMtx, cXyz* ppos, GXColor& param_2, u8** tex) { f32 spA8 = sun_packet->mVisibility * sun_packet->mVisibility; if (!(sun_visibility < 0.1f)) { +#if TARGET_PC + cXyz sunpos; + cXyz positions[8]; + dKyr_place_sun(camera, &sunpos); + dKyr_place_lenzflare(camera, &sunpos, positions); + ppos = positions; +#endif dKy_set_eyevect_calc2(camera, &spFC, 8000.0f, 8000.0f); GXColor color_reg0; @@ -2835,10 +2975,10 @@ void dKyr_drawLenzflare(Mtx drawMtx, cXyz* ppos, GXColor& param_2, u8** tex) { GXSetCurrentMtx(GX_PNMTX0); if (sun_packet->field_0x6c > 0.0f) { - spC = S_rot_work1 - 0x7F6; - spA = S_rot_work2 + 0x416B; - S_rot_work1 += 8; - S_rot_work2 -= 14; + spC = (s16)S_rot_work1 - 0x7F6; + spA = (s16)S_rot_work2 + 0x416B; + S_rot_work1 += 8 IF_DUSK(* dusk::game_clock::original_frames()); + S_rot_work2 -= 14 IF_DUSK(* dusk::game_clock::original_frames()); if (dComIfGd_getView() != NULL) { MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); @@ -2962,25 +3102,25 @@ void dKyr_drawLenzflare(Mtx drawMtx, cXyz* ppos, GXColor& param_2, u8** tex) { spE4.y = sp9C; spE4.z = 0.0f; cMtx_multVec(camMtx, &spE4, &spD8); - pos[0].x = sun_packet->mPos[0].x + spD8.x; - pos[0].y = sun_packet->mPos[0].y + spD8.y; - pos[0].z = sun_packet->mPos[0].z + spD8.z; + pos[0].x = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).x + spD8.x; + pos[0].y = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).y + spD8.y; + pos[0].z = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).z + spD8.z; spE4.x = sp98; spE4.y = sp94; spE4.z = 0.0f; cMtx_multVec(camMtx, &spE4, &spD8); - pos[1].x = sun_packet->mPos[0].x + spD8.x; - pos[1].y = sun_packet->mPos[0].y + spD8.y; - pos[1].z = sun_packet->mPos[0].z + spD8.z; + pos[1].x = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).x + spD8.x; + pos[1].y = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).y + spD8.y; + pos[1].z = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).z + spD8.z; spE4.x = sp90; spE4.y = sp8C; spE4.z = 0.0f; cMtx_multVec(camMtx, &spE4, &spD8); - pos[2].x = sun_packet->mPos[0].x + spD8.x; - pos[2].y = sun_packet->mPos[0].y + spD8.y; - pos[2].z = sun_packet->mPos[0].z + spD8.z; + pos[2].x = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).x + spD8.x; + pos[2].y = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).y + spD8.y; + pos[2].z = DUSK_IF_ELSE(sunpos, sun_packet->mPos[0]).z + spD8.z; GXBegin(GX_TRIANGLES, GX_VTXFMT0, 3); GXPosition3f32(pos[0].x, pos[0].y, pos[0].z); @@ -3267,6 +3407,7 @@ void dKyr_drawRain(Mtx drawMtx, u8** tex) { sp3C.x = rain_packet->mRainEff[i].mBasePos.x + rain_packet->mRainEff[i].mPosition.x; sp3C.y = rain_packet->mRainEff[i].mBasePos.y + rain_packet->mRainEff[i].mPosition.y; sp3C.z = rain_packet->mRainEff[i].mBasePos.z + rain_packet->mRainEff[i].mPosition.z; + IF_DUSK(sp3C = s_rain.read(i, sp3C, 300.0f);) f32 dist = 0.1f + (sp3C.abs(camera->view.lookat.eye) / 1500.0f); if (dist > 1.0f) { @@ -3369,7 +3510,7 @@ void dKyr_drawSibuki(Mtx drawMtx, u8** tex) { alpha = 200.0f; } - cLib_addCalc(&rain_packet->mSibukiAlpha, alpha, 0.2f, 30.0f, 0.001f); + DUSK_IF_ELSE(dusk::vdt::present_addCalc, cLib_addCalc)(&rain_packet->mSibukiAlpha, alpha, 0.2f, 30.0f, 0.001f); dKy_set_eyevect_calc(camera, &eyevect, 7000.0f, 4000.0f); cXyz camdir; dKyr_get_vectle_calc(&camera->view.lookat.eye, &camera->view.lookat.center, &camdir); @@ -3621,7 +3762,7 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { GXSetNumIndStages(0); dKr_cullVtx_Set(IF_DUSK(true)); - rot += 1.2f; + rot += 1.2f IF_DUSK(* dusk::game_clock::original_frames()); MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); @@ -3634,6 +3775,13 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { GXBegin(GX_QUADS, GX_VTXFMT0, vertCount); #endif +#if TARGET_PC + f32 housi_counter = (f32)g_Counter.mCounter0; + if (dusk::interp::is_enabled()) { + housi_counter += dusk::interp::get_interpolation_step() - 1.0f; + } +#endif + for (int j = 0; j < housi_packet->mHousiCount; j++) { fopAc_ac_c* player = dComIfGp_getPlayer(0); @@ -3643,6 +3791,7 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { housi_packet->mHousiEff[j].mBasePos.y + housi_packet->mHousiEff[j].mPosition.y; spD0.z = housi_packet->mHousiEff[j].mBasePos.z + housi_packet->mHousiEff[j].mPosition.z; + IF_DUSK(spD0 = s_housi_world.read(j, spD0, 300.0f);) if (i == 1 && j == 0) { #if TARGET_PC @@ -3687,10 +3836,9 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { var_f27 = housi_packet->mHousiEff[j].field_0x48 * 18.0f; } - f32 temp_f28 = - (var_f27 * 0.2f) * cM_fsin(housi_packet->mHousiEff[j].mScale.x * 5.0f); - f32 temp_f30 = - (var_f27 * 0.2f) * cM_fcos(housi_packet->mHousiEff[j].mScale.y * 6.0f); + IF_DUSK(cXyz scale = s_housi_scale.read(j, housi_packet->mHousiEff[j].mScale, 1.0f)); + f32 temp_f28 = (var_f27 * 0.2f) * cM_fsin(DUSK_IF_ELSE(scale.x, housi_packet->mHousiEff[j].mScale.x) * 5.0f); + f32 temp_f30 = (var_f27 * 0.2f) * cM_fcos(DUSK_IF_ELSE(scale.y, housi_packet->mHousiEff[j].mScale.y) * 6.0f); if (dKy_darkworld_check() == 1 || isPalaceOfTwilight == 1) { cXyz sp7C[] = { @@ -3704,7 +3852,7 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { cXyz spAC; cXyz spA0; - f32 temp_f26_2 = cM_ssin((f32)j * 123.0f + (f32)(g_Counter.mCounter0 * 600)); + f32 temp_f26_2 = cM_ssin((f32)j * 123.0f + DUSK_IF_ELSE(housi_counter * 600.0f, (f32)(g_Counter.mCounter0 * 600))); cXyz* temp_r3 = &sp7C[k]; spAC.x = temp_r3->x * (8.0f * (1.0f + (temp_f26_2 * 0.3f))); @@ -3736,11 +3884,11 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { 0.2f + (housi_packet->mHousiEff[j].field_0x34 * (fabsf(cM_ssin((f32)j * 213.0f + - (f32)(g_Counter.mCounter0 * 330))) * + DUSK_IF_ELSE(housi_counter * 330.0f, (f32)(g_Counter.mCounter0 * 330)))) * 0.8f)); } else { var_f24 = cM_ssin((f32)j * 123.0f + - (f32)(g_Counter.mCounter0 * 80)); + DUSK_IF_ELSE(housi_counter * 80.0f, (f32)(g_Counter.mCounter0 * 80))); } f32 var_f2; @@ -3751,10 +3899,10 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { var_f24 = housi_packet->mHousiEff[j].field_0x34 * fabsf(cM_ssin((f32)j * 250.0f + - (f32)(g_Counter.mCounter0 * 88))); + DUSK_IF_ELSE(housi_counter * 88.0f, (f32)(g_Counter.mCounter0 * 88)))); } else { var_f24 = cM_ssin((f32)j * 685.0f + - (f32)(g_Counter.mCounter0 * 20)); + DUSK_IF_ELSE(housi_counter * 20.0f, (f32)(g_Counter.mCounter0 * 20))); } } else { var_f2 = 6.0f; @@ -3770,7 +3918,7 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { housi_packet->mHousiEff[j].mStatus == 3) { housi_packet->mHousiEff[j].field_0x38 += - 483.0f * (0.5f + (var_f24 * 0.5f)); + 483.0f * (0.5f + (var_f24 * 0.5f)) IF_DUSK(* dusk::game_clock::original_frames()); housi_packet->mHousiEff[j].field_0x44 = (s16)housi_packet->mHousiEff[j].field_0x38; @@ -3780,19 +3928,21 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { } else { if (housi_packet->mHousiEff[j].mStatus == 2) { if (g_env_light.fishing_hole_season == 3) { - housi_packet->mHousiEff[j].field_0x38 += var_f24 * 30.0f; + housi_packet->mHousiEff[j].field_0x38 += var_f24 * 30.0f IF_DUSK(* dusk::game_clock::original_frames()); } else { housi_packet->mHousiEff[j].field_0x38 += - var_f24 * 100.0f; + var_f24 * 100.0f IF_DUSK(* dusk::game_clock::original_frames()); } } if (housi_packet->mHousiEff[j].field_0x38 > 32765.0f) { - cLib_addCalc(&housi_packet->mHousiEff[j].field_0x44, - -16384.0f, 0.1f, 500.0f, 0.0001f); + DUSK_IF_ELSE(dusk::vdt::present_addCalc, cLib_addCalc)( + &housi_packet->mHousiEff[j].field_0x44, + -16384.0f, 0.1f, 500.0f, 0.0001f); } else { - cLib_addCalc(&housi_packet->mHousiEff[j].field_0x44, - 16384.0f, 0.1f, 500.0f, 0.0001f); + DUSK_IF_ELSE(dusk::vdt::present_addCalc, cLib_addCalc)( + &housi_packet->mHousiEff[j].field_0x44, + 16384.0f, 0.1f, 500.0f, 0.0001f); } mDoMtx_stack_c::YrotM(housi_packet->mHousiEff[j].field_0x38); @@ -3973,7 +4123,7 @@ void dKyr_drawSnow(Mtx drawMtx, u8** tex) { Mtx rotMtx; MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); - rot += 5.0f + cM_rndFX(2.0f); + rot += 5.0f DUSK_IF_ELSE(* dusk::game_clock::original_frames(), + cM_rndFX(2.0f)); if (rot > 719.0f) { rot = 0.0f; } @@ -3983,7 +4133,8 @@ void dKyr_drawSnow(Mtx drawMtx, u8** tex) { GXSetCurrentMtx(GX_PNMTX0); if (g_env_light.field_0xe92 == 0 && sp4C == 0) { - cLib_addCalc(&S_fubuki_ratio, 0.0f, 0.25f, 0.1f, 0.001f); + DUSK_IF_ELSE(dusk::vdt::present_addCalc, cLib_addCalc)( + &S_fubuki_ratio, 0.0f, 0.25f, 0.1f, 0.001f); if (S_fubuki_ratio > 0.0f) { spC = 2; } @@ -3991,7 +4142,8 @@ void dKyr_drawSnow(Mtx drawMtx, u8** tex) { if (g_env_light.field_0xe92) { spC = 2; } - cLib_addCalc(&S_fubuki_ratio, 1.0f, 0.25f, 0.1f, 0.001f); + DUSK_IF_ELSE(dusk::vdt::present_addCalc, cLib_addCalc)( + &S_fubuki_ratio, 1.0f, 0.25f, 0.1f, 0.001f); } f32 sp50; @@ -4020,12 +4172,14 @@ void dKyr_drawSnow(Mtx drawMtx, u8** tex) { f32 sp40, sp3C; if (j == 0) { sp7C = snow_packet->mSnowEff[i].mPosition; + IF_DUSK(sp7C = s_snow_pos.read(i, sp7C, 200.0f);) sp40 = snow_packet->mSnowEff[i].mScale * snow_packet->mSnowEff[i].mScale; sp3C = (snow_packet->mSnowEff[i].mScale - sp40) + snow_packet->mSnowEff[i].mScale; color_reg0.a = 180.0f * ((snow_packet->mSnowEff[i].mScale * 0.8f) + temp_f29); } else { sp7C = snow_packet->mSnowEff[i].mBasePos; + IF_DUSK(sp7C = s_snow_base.read(i, sp7C, 300.0f);) sp40 = snow_packet->mSnowEff[i].field_0x30 * snow_packet->mSnowEff[i].field_0x30; sp3C = (snow_packet->mSnowEff[i].field_0x30 - sp40) + snow_packet->mSnowEff[i].field_0x30; @@ -4307,7 +4461,7 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { GXLoadPosMtxImm(drawMtx, GX_PNMTX0); GXSetCurrentMtx(GX_PNMTX0); - rot += 0.65f; + rot += 0.65f IF_DUSK(* dusk::game_clock::original_frames()); if (rot > 719.0f) { rot = 0.0f; } @@ -4599,7 +4753,7 @@ void drawCloudShadow(Mtx drawMtx, u8** tex) { MTXConcat(camMtx, rotMtx, camMtx); GXLoadPosMtxImm(drawMtx, GX_PNMTX0); - rot -= 0.45f; + rot -= 0.45f IF_DUSK(* dusk::game_clock::original_frames()); if (rot < 0.0f) { rot = 719.0f; } @@ -4633,7 +4787,7 @@ void drawCloudShadow(Mtx drawMtx, u8** tex) { #endif cMtx_concat(sp120, j3dSys.getViewMtx(), spF0); - rot += 2.0f; + rot += 2.0f IF_DUSK(* dusk::game_clock::original_frames()); MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); @@ -4688,6 +4842,7 @@ void drawCloudShadow(Mtx drawMtx, u8** tex) { sp5C.x = cloud_packet->mCloudEff[i].mBasePos.x + cloud_packet->mCloudEff[i].mPosition.x; sp5C.y = cloud_packet->mCloudEff[i].mBasePos.y + cloud_packet->mCloudEff[i].mPosition.y; sp5C.z = cloud_packet->mCloudEff[i].mBasePos.z + cloud_packet->mCloudEff[i].mPosition.z; + IF_DUSK(sp5C = s_cloud.read(i, sp5C, 300.0f);) sp74.x = -size; sp74.y = size; @@ -4964,7 +5119,7 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { sp50 = cM_fsin(j + (0.0001f * howa_loop_cnt)); sp50 *= vrkumo_packet->mVrkumoEff[k].mDistFalloff; - howa_loop_cnt += 1.5f * sp58; + howa_loop_cnt += 1.5f * sp58 IF_DUSK(* dusk::game_clock::original_frames()); sp68 += (0.05f * sp68 * sp50); sp64 = sp68 + (sp68 * vrkumo_packet->mVrkumoEff[k].mHeight); @@ -5001,6 +5156,7 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { sp60 = sp68 * (0.2f + (0.2f * (k / 100.0f))); sp5C = sp68 * (0.55f + (0.3f * (k / 100.0f))); spFC = vrkumo_packet->mVrkumoEff[k].mPosition; + IF_DUSK(spFC = s_vrkumo.read(k, spFC, 1000.0f);) spA4 = 0.0f; spA0 = 0.0f; @@ -5635,7 +5791,7 @@ void dKyr_odour_draw(Mtx drawMtx, u8** tex) { C_MTXLightPerspective(sp120, window_cam->view.fovy, window_cam->view.aspect, scale, -scale, 0.5f, 0.5f); cMtx_concat(sp120, j3dSys.getViewMtx(), spF0); - rot += 2.0f; + rot += 2.0f IF_DUSK(* dusk::game_clock::original_frames()); MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); @@ -6207,12 +6363,7 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { dKyr_set_btitex(&texobj, (ResTIMG*)tex[1]); #endif -#if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - rot += 0.7f; - } + rot += 0.7f IF_DUSK(* dusk::game_clock::original_frames()); MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); @@ -6451,12 +6602,7 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); #endif -#if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - rot += 1.0f; - } + rot += 1.0f IF_DUSK(* dusk::game_clock::original_frames()); MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); @@ -6574,7 +6720,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { } else { sp5C = 0.0f; } - cLib_addCalc(&effect->field_0x2c, sp5C, 0.5f, 0.1f, 0.01f); + DUSK_IF_ELSE(dusk::vdt::present_addCalc, cLib_addCalc)( + &effect->field_0x2c, sp5C, 0.5f, 0.1f, 0.01f); daPy_py_c* player = (daPy_py_c*)dComIfGp_getPlayer(0); if (player != NULL && player->getKandelaarFlamePos() != NULL) { diff --git a/src/d/d_map_path_dmap.cpp b/src/d/d_map_path_dmap.cpp index f9d2f4d5f9..020f818cf1 100644 --- a/src/d/d_map_path_dmap.cpp +++ b/src/d/d_map_path_dmap.cpp @@ -12,6 +12,36 @@ #include "d/d_meter_HIO.h" #include "float.h" +#if TARGET_PC +#include "dusk/interp/samples.h" + +namespace { +struct MapPlayer { + dusk::interp::Samples position; + dusk::interp::Samples angle; + fpc_ProcID id = fpcM_ERROR_PROCESS_ID_e; + int room = -1; +}; + +MapPlayer& map_player() { + static MapPlayer samples; + auto* player = daPy_getPlayerActorClass(); + const int room = dComIfGp_roomControl_getStayNo(); + const fpc_ProcID id = player != nullptr ? fopAcM_GetID(player) : fpcM_ERROR_PROCESS_ID_e; + if (samples.id != id || samples.room != room) { + samples = {}; + samples.id = id; + samples.room = room; + } + if (player != nullptr) { + samples.position.capture(&player->current.pos, 1); + samples.angle.capture(&player->shape_angle, 1); + } + return samples; +} +} // namespace +#endif + bool dMapInfo_n::chkGetCompass() { return dComIfGs_isDungeonItemCompass() ? true : false; } @@ -60,7 +90,7 @@ Vec dMapInfo_n::getMapPlayerPos() { BE(Vec) pos; fopAc_ac_c* player = daPy_getPlayerActorClass(); if (player != NULL) { - pos = player->current.pos; + pos = DUSK_IF_ELSE(map_player().position.read(0, player->current.pos, 2000.0f), player->current.pos); } else { pos.x = 0.0f; pos.y = 0.0f; @@ -82,7 +112,7 @@ s16 dMapInfo_n::getMapPlayerAngleY() { daPy_py_c* player = daPy_getPlayerActorClass(); if (player != NULL) { - angle = player->shape_angle.y; + angle = DUSK_IF_ELSE(map_player().angle.read(0, player->shape_angle).y, player->shape_angle.y); } dStage_FileList2_dt_c* fileList2_p = dStage_roomControl_c::getFileList2(stayNo); diff --git a/src/d/d_menu_calibration.cpp b/src/d/d_menu_calibration.cpp index 23f81121bc..e596609292 100644 --- a/src/d/d_menu_calibration.cpp +++ b/src/d/d_menu_calibration.cpp @@ -14,7 +14,9 @@ #include "m_Do/m_Do_controller_pad.h" #include +#if TARGET_PC #include "helpers/string.hpp" +#endif // Need 0xC bytes of padding with no symbol between dMenu_Calibration_c::__vtable and the end of // .data @@ -25,7 +27,7 @@ public: virtual void virt_func_0() = 0; }; class dummy_child_class : dummy_abstract_class { -#ifdef TARGET_PC +#if TARGET_PC virtual void virt_func_0() {} #else virtual void virt_func_0(); diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index 32c0a360c2..dbb90e9b01 100644 --- a/src/d/d_menu_collect.cpp +++ b/src/d/d_menu_collect.cpp @@ -38,6 +38,8 @@ #include "m_Do/m_Do_mtx.h" #if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/utilities.hpp" #endif @@ -999,10 +1001,14 @@ void dMenu_Collect2D_c::animationSet() { void dMenu_Collect2D_c::btkAnimeLoop0(J2DAnmTextureSRTKey* i_SRTKey) { if (i_SRTKey != NULL) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(mFrame, 1.0f, i_SRTKey->getFrameMax()); +#else mFrame++; if (mFrame >= i_SRTKey->getFrameMax()) { mFrame -= i_SRTKey->getFrameMax(); } +#endif i_SRTKey->setFrame(mFrame); } else { mFrame = 0.0f; @@ -1016,6 +1022,11 @@ void dMenu_Collect2D_c::btkAnimeLoop0(J2DAnmTextureSRTKey* i_SRTKey) { } void dMenu_Collect2D_c::setBackAlpha() { +#if TARGET_PC + const f32 target = mProcess >= 1 && mProcess <= 18 ? 1.0f : 0.0f; + dusk::vdt::advance_toward_frame(mBackAlpha, target, 0.2f); + mpBlackTex->setAlpha(mBackAlpha * 150.0f); +#else f32 alpha = mpBlackTex->getAlpha() / 150.0f; switch (mProcess) { @@ -1056,6 +1067,7 @@ void dMenu_Collect2D_c::setBackAlpha() { } mpBlackTex->setAlpha(alpha * 150.0f); +#endif } // Not sure if this works without gotos @@ -2435,17 +2447,22 @@ void dMenu_Collect2D_c::_move() { if (mProcess != last_process) { (this->*init[mProcess])(); } +#if !TARGET_PC btkAnimeLoop0(mpAnmKey); mpScreen->animation(); setBackAlpha(); +#endif setHIO(false); } void dMenu_Collect2D_c::_draw() { - #if TARGET_PC +#if TARGET_PC + btkAnimeLoop0(mpAnmKey); + mpScreen->animation(); + setBackAlpha(); menuCollectWide(); - #endif +#endif J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); grafPort->setup2D(); @@ -3056,7 +3073,7 @@ DUSK_GAME_DATA f32 dMenu_Collect3D_c::mViewOffsetY = -100.0f; void dMenu_Collect3D_c::setupItem3D(Mtx param_0) { GXSetViewport(0.0f, mViewOffsetY, FB_WIDTH, FB_HEIGHT, 0.0f, 1.0f); - mViewOffsetY = -100.0f; + IF_NOT_DUSK(mViewOffsetY = -100.0f); Mtx44 projection; C_MTXPerspective(projection, 45.0f, mDoGph_gInf_c::getAspect(), 1.0f, 100000.0f); GXSetProjection(projection, GX_PERSPECTIVE); @@ -3157,6 +3174,8 @@ void dMenu_Collect_c::_move() { void dMenu_Collect_c::draw() { dComIfGd_set2DOpa(mpCollect2D); + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame()) mpCollect3D->draw(); + IF_DUSK_BLOCK_END mpCollect2D->drawTop(); } diff --git a/src/d/d_menu_dmap.cpp b/src/d/d_menu_dmap.cpp index 4557edfb4c..476c9a0f5c 100644 --- a/src/d/d_menu_dmap.cpp +++ b/src/d/d_menu_dmap.cpp @@ -21,16 +21,18 @@ #include "d/d_msg_string.h" #include "d/d_meter_haihai.h" #include "d/d_menu_window.h" -#include "dusk/settings.h" #include "f_op/f_op_msg_mng.h" #include "m_Do/m_Do_graphic.h" #include +#if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/menus.h" +#include "dusk/interp/user_interface.h" +#include "dusk/settings.h" #include "dusk/version.hpp" #include "helpers/string.hpp" - -#if TARGET_PC -#include "dusk/frame_interpolation.h" #endif #if (PLATFORM_WII || PLATFORM_SHIELD) @@ -241,12 +243,14 @@ void dMenu_DmapBg_c::mapScreenInit() { void dMenu_DmapBg_c::mapScreenAnime() { if (0.0f == field_0xd94 && 0.0f == field_0xd98) { for (int i = 0; i < 1; i++) { +#if !TARGET_PC field_0xdc8[i] += 0.4f; if (field_0xdc8[i] >= field_0xd28[i]->getFrameMax()) { field_0xdc8[i] -= field_0xd28[i]->getFrameMax(); } field_0xd28[i]->setFrame(field_0xdc8[i]); mMapScreen[i]->animation(); +#endif } } } @@ -278,7 +282,9 @@ bool dMenu_DmapBg_c::iconScaleAnm() { return true; } - f32 dVar7 = fopMsgM_valueIncrease(field_0xdd1, field_0xdd2, 4); + IF_DUSK(dusk::vdt::advance_toward_frame(field_0xdd2, field_0xdd1, 1.0f)); + f32 dVar7 = DUSK_IF_ELSE(dusk::vdt::present_sine_ease(field_0xdd1, field_0xdd2), + fopMsgM_valueIncrease(field_0xdd1, field_0xdd2, 4)); f32 dVar8; f32 dVar1; if (field_0xdac > field_0xdb0) { @@ -292,7 +298,7 @@ bool dMenu_DmapBg_c::iconScaleAnm() { iconScale(1, dVar7, dVar8, dVar1); - field_0xdd2++; + IF_NOT_DUSK(field_0xdd2++); if (field_0xdd2 >= field_0xdd1) { rv = true; } @@ -862,6 +868,11 @@ dMenu_DmapBg_c::~dMenu_DmapBg_c() { } void dMenu_DmapBg_c::setAllAlphaRate(f32 i_rate, bool param_2) { +#if TARGET_PC + if (!param_2 && i_rate == 1.0f && field_0xd9c == 1.0f) { + return; + } +#endif field_0xd9c = i_rate; if (param_2) { @@ -924,8 +935,8 @@ void dMenu_DmapBg_c::addGoldFrameAlphaRate() { rate = 1.0f; setGoldAnimation(true); } else { - field_0xdd7++; - if (field_0xdd7 == g_fmapHIO.mDisplayFrameNum) { + DUSK_IF_ELSE(field_0xdd7 += dusk::game_clock::original_frames(), field_0xdd7++); + if (field_0xdd7 DUSK_IF_ELSE(>=, ==) g_fmapHIO.mDisplayFrameNum) { setGoldAnimation(true); } rate = (f32)(field_0xdd7 * field_0xdd7) / (f32)(g_fmapHIO.mDisplayFrameNum * g_fmapHIO.mDisplayFrameNum); @@ -1113,7 +1124,7 @@ void dMenu_DmapBg_c::draw() { -35.0f + (local_224.x - local_218.x), -35.0f + (local_224.y - local_218.y)); #if TARGET_PC - if (!dusk::frame_interp::is_enabled()) { + if (!dusk::interp::is_enabled()) { field_0xdda = 0; } #else @@ -1218,7 +1229,7 @@ void dMenu_DmapBg_c::update() { } if (mpBackTexture != NULL && field_0xdbc < 255.0f) { - field_0xdbc += 25.5f; + field_0xdbc += 25.5f IF_DUSK(* dusk::game_clock::original_frames()); if (field_0xdbc > 255.0f) { field_0xdbc = 255.0f; } @@ -1648,6 +1659,7 @@ void dMenu_Dmap_c::getIconPos(s8 i_floorNo, f32 param_2) { } dMenu_Dmap_c::~dMenu_Dmap_c() { + IF_DUSK(dusk::interp::erase_owned_samples(this)); s32 iVar2 = field_0xe8->getTotalFreeSize(); s32 iVar4 = field_0xe8->getTotalFreeSize(); @@ -2009,7 +2021,7 @@ void dMenu_Dmap_c::_move() { var_f31 = 0.70588237f; } - cLib_addCalc2(&field_0x110, var_f31, 0.4f, 0.5f); + DUSK_IF_ELSE(dusk::vdt::present_addCalc2, cLib_addCalc2)(&field_0x110, var_f31, 0.4f, 0.5f IF_DUSK_ARG(0.1f)); mpDrawBg->mpBlack->setAlphaRate(field_0x110); } @@ -2025,9 +2037,10 @@ void dMenu_Dmap_c::_move() { setMapTexture(); mapBgAnime(); mpDrawBg->calcCursor(); - mpDrawBg->addGoldFrameAlphaRate(); + IF_NOT_DUSK(mpDrawBg->addGoldFrameAlphaRate()); mDoExt_setCurrentHeap(prev_heap); + IF_DUSK(dusk::interp::capture_menu_pose(this, {field_0x104, field_0x108, field_0x10c})); } void dMenu_Dmap_c::setMapTexture() { @@ -2036,13 +2049,13 @@ void dMenu_Dmap_c::setMapTexture() { } void dMenu_Dmap_c::mapBgAnime() { - mpDrawBg->mapScreenAnime(); + IF_NOT_DUSK(mpDrawBg->mapScreenAnime()); } void dMenu_Dmap_c::mapControl() { u8 temp_r27 = field_0x17e; if (m_process == 0 || field_0x183 == 0) { - mMapCtrl->move(); + IF_NOT_DUSK(mMapCtrl->move()); getIconPos(mMapCtrl->getDispFloorNo(), mMapCtrl->getMapBlendPer()); getIconPos(mMapCtrl->getDispFloor2No(), 1.0f - mMapCtrl->getMapBlendPer()); @@ -2068,6 +2081,7 @@ void dMenu_Dmap_c::mapControl() { if (stick_value >= sp28 && field_0x181 != 2) { var_r28 = true; +#if !TARGET_PC f32 var_f31 = mMapCtrl->getStageMapSizeX(); if (var_f31 < mMapCtrl->getStageMapSizeZ()) { var_f31 = mMapCtrl->getStageMapSizeZ(); @@ -2085,9 +2099,10 @@ void dMenu_Dmap_c::mapControl() { f32 sp14 = temp_f28 * cM_scos(stick_angle); mMapCtrl->setPlusZoomCenterX(IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -sp18 :) sp18); mMapCtrl->setPlusZoomCenterZ(sp14); +#endif } - mMapCtrl->move(); + IF_NOT_DUSK(mMapCtrl->move()); getIconPos(mMapCtrl->getDispFloorNo(), mMapCtrl->getMapBlendPer()); getIconPos(mMapCtrl->getDispFloor2No(), 1.0f - mMapCtrl->getMapBlendPer()); @@ -2187,6 +2202,7 @@ bool dMenu_Dmap_c::isOpen() { mpDrawBg->setAllAlphaRate(field_0x10c, var_r27); mpDrawBg->setGoldFrameAlphaRate(0.0f); + IF_DUSK(dusk::interp::capture_menu_pose(this, {field_0x104, field_0x108, field_0x10c})); return var_r28; } @@ -2226,15 +2242,112 @@ bool dMenu_Dmap_c::isClose() { field_0x10c = temp_f31; mpDrawBg->setAllAlphaRate(field_0x10c, var_r29); mpDrawBg->decGoldFrameAlphaRate(); + IF_DUSK(dusk::interp::capture_menu_pose(this, {field_0x104, field_0x108, field_0x10c})); return var_r30; } -void dMenu_Dmap_c::_draw() { - if (mMapCtrl != NULL) { - mMapCtrl->draw(); +#if TARGET_PC +void dMenu_Dmap_c::presentAnims() { + if (mMapCtrl == NULL || mpDrawBg == NULL) { + return; + } + for (int i = 0; i < 8; ++i) { + mSelFloor[i]->presentAnime(); + } + for (int i = 0; i < 3; ++i) { + field_0x88[i]->presentAnime(); + } + + if (field_0x10c == 1.0f) { + mpDrawBg->addGoldFrameAlphaRate(); + + if (0.0f == mpDrawBg->field_0xd94 && 0.0f == mpDrawBg->field_0xd98) { + for (int i = 0; i < 1; i++) { + dusk::vdt::present_looping(mpDrawBg->field_0xdc8[i], mpDrawBg->field_0xd28[i], + 0.4f); + mpDrawBg->mMapScreen[i]->animation(); + } + } + mpDrawBg->clearIconInfo(); + presentMapView(); + if (field_0x17e == 1 || field_0x17e == 2) { + mpDrawBg->iconScaleAnm(); + } + } +} + +void dMenu_Dmap_c::presentMapView() { + const bool can_stick_scroll = m_process != 0 && field_0x183 != 0 && mMapCtrl->isEndZoomIn(); + f32 disp_center_x = mMapCtrl->getDispCenterX(); + f32 disp_center_z = mMapCtrl->getDispCenterZ(); + bool scrolled = false; + if (can_stick_scroll) { + f32 sp28 = g_fmapHIO.mScrollSpeedSlowBound < g_fmapHIO.mScrollSpeedFastBound ? + g_fmapHIO.mScrollSpeedSlowBound : + g_fmapHIO.mScrollSpeedFastBound; + f32 sp24 = g_fmapHIO.mScrollSpeedSlowBound > g_fmapHIO.mScrollSpeedFastBound ? + g_fmapHIO.mScrollSpeedSlowBound : + g_fmapHIO.mScrollSpeedFastBound; + f32 stick_value = mpCStick->getValueStick(); + s16 stick_angle = mpCStick->getAngleStick(); + if (stick_value >= sp28 && field_0x181 != 2) { + scrolled = true; + f32 var_f31 = mMapCtrl->getStageMapSizeX(); + if (var_f31 < mMapCtrl->getStageMapSizeZ()) { + var_f31 = mMapCtrl->getStageMapSizeZ(); + } + f32 var_f29 = stick_value < sp24 ? g_fmapHIO.mScrollSpeedDungeonMapSlow : + g_fmapHIO.mScrollSpeedDungeonMapFast; + f32 temp_f28 = (var_f29 / 100.0f) * var_f31 * dusk::game_clock::original_frames(); + f32 sp18 = temp_f28 * cM_ssin(stick_angle); + f32 sp14 = temp_f28 * cM_scos(stick_angle); + mMapCtrl->setPlusZoomCenterX(dusk::getSettings().game.enableMirrorMode ? -sp18 : sp18); + mMapCtrl->setPlusZoomCenterZ(sp14); + } + } + mMapCtrl->move(); + if (can_stick_scroll) { + if (scrolled && (disp_center_x != mMapCtrl->getDispCenterX() || + disp_center_z != mMapCtrl->getDispCenterZ())) + { + Z2GetAudioMgr()->seStartLevel(Z2SE_SY_MAP_SCROLL, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); + } + if (mpDrawBg->mpMeterHaihai != NULL && isMapMoveState() && + (getCMessageNum() == 0x37B || getCMessageNum() == 0x569)) + { + mpDrawBg->field_0xdda = 0; + if (mMapCtrl->getDispCenterX() > mMapCtrl->getZoomCenterMinX()) { + mpDrawBg->field_0xdda |= 1; + } + if (mMapCtrl->getDispCenterX() < mMapCtrl->getZoomCenterMaxX()) { + mpDrawBg->field_0xdda |= 4; + } + if (mMapCtrl->getDispCenterZ() > mMapCtrl->getZoomCenterMinZ()) { + mpDrawBg->field_0xdda |= 8; + } + if (mMapCtrl->getDispCenterZ() < mMapCtrl->getZoomCenterMaxZ()) { + mpDrawBg->field_0xdda |= 2; + } + mpDrawBg->mpMeterHaihai->_execute(0); + } + } + getIconPos(mMapCtrl->getDispFloorNo(), mMapCtrl->getMapBlendPer()); + getIconPos(mMapCtrl->getDispFloor2No(), 1.0f - mMapCtrl->getMapBlendPer()); +} +#endif + +void dMenu_Dmap_c::_draw() { + IF_DUSK(const auto pose = dusk::interp::read_menu_pose(this, {field_0x104, field_0x108, field_0x10c})); + if (mMapCtrl != NULL) { + IF_NOT_DUSK(mMapCtrl->draw()); if (mpDrawBg != NULL) { - mpDrawBg->setAllTrans(field_0x104, field_0x108); + IF_DUSK_BLOCK(dusk::game_clock::is_presentation_frame()) +#if TARGET_PC + mpDrawBg->setAllAlphaRate(pose.alpha, false); + presentAnims(); +#endif + mpDrawBg->setAllTrans(DUSK_IF_ELSE(pose.x, field_0x104), DUSK_IF_ELSE(pose.y, field_0x108)); if (mpDrawBg->mpBackTexture != NULL) { f32 var_f31 = 100.0f * mpDrawBg->field_0xd88; @@ -2270,8 +2383,10 @@ void dMenu_Dmap_c::_draw() { sp14[1] = -(field_0x138 + 100.0f * (sp20 * mMapCtrl->getPixelPerCm())); Vec spC; +#if !TARGET_PC spC.x = mpDrawBg->mMapScreen[0]->search(MULTI_CHAR('center_n'))->getGlbBounds().i.x + (mpDrawBg->mMapScreen[0]->search(MULTI_CHAR('center_n'))->getWidth() / 2); spC.y = mpDrawBg->mMapScreen[0]->search(MULTI_CHAR('center_n'))->getGlbBounds().i.y + (mpDrawBg->mMapScreen[0]->search(MULTI_CHAR('center_n'))->getHeight() / 2); +#endif CPaneMgr sp70; //!@bug It's unclear what this is supposed to be, but a stack pointer being converted to a bool is probably not intended. @@ -2283,9 +2398,15 @@ void dMenu_Dmap_c::_draw() { mpDrawBg->mpBackTexture->move(sp14[0] + (spC.x - (sp1C[0] / 2)), sp14[1] + (spC.y - (sp1C[1] / 2))); mpDrawBg->mpBackTexture->resize(sp1C[0], sp1C[1]); } + IF_DUSK_BLOCK_END - dComIfGd_set2DOpa(mpDrawBg); + IF_NOT_DUSK(dComIfGd_set2DOpa(mpDrawBg)); } + +#if TARGET_PC + mMapCtrl->draw(); + dComIfGd_set2DOpa(mpDrawBg); +#endif } } @@ -2760,7 +2881,7 @@ void dMenu_Dmap_c::zoomIn_init_proc() { void dMenu_Dmap_c::zoomIn_proc() { bool temp_r30 = mMapCtrl->isEndZoomIn(); - bool temp_r29 = mpDrawBg->iconScaleAnm(); + bool temp_r29 = DUSK_IF_ELSE(mpDrawBg->field_0xdd2 >= mpDrawBg->field_0xdd1, mpDrawBg->iconScaleAnm()); bool temp_r28 = true; if (temp_r30 == true && temp_r29 == true && temp_r28 == true) { @@ -2775,8 +2896,8 @@ void dMenu_Dmap_c::zoomIn_proc() { void dMenu_Dmap_c::zoomOut_init_proc() { #if TARGET_PC - if (dusk::frame_interp::is_enabled()) { - mpDrawBg->resetScrollArrowMask(); + if (dusk::interp::is_enabled()) { + mpDrawBg->field_0xdda = 0; } #endif Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); @@ -2794,7 +2915,7 @@ void dMenu_Dmap_c::zoomOut_init_proc() { void dMenu_Dmap_c::zoomOut_proc() { bool temp_r30 = mMapCtrl->isEndZoomOut(); - bool temp_r29 = mpDrawBg->iconScaleAnm(); + bool temp_r29 = DUSK_IF_ELSE(mpDrawBg->field_0xdd2 >= mpDrawBg->field_0xdd1, mpDrawBg->iconScaleAnm()); bool temp_r28 = true; if (temp_r30 == true && temp_r29 == true && temp_r28 == true) { diff --git a/src/d/d_menu_dmap_map.cpp b/src/d/d_menu_dmap_map.cpp index bc63706617..3abb6c13c1 100644 --- a/src/d/d_menu_dmap_map.cpp +++ b/src/d/d_menu_dmap_map.cpp @@ -11,8 +11,9 @@ #include "d/d_menu_dmap_map.h" #include "f_op/f_op_msg_mng.h" #include "m_Do/m_Do_graphic.h" + #if TARGET_PC -#include +#include "dusk/interp/user_interface.h" #endif struct dMdm_HIO_prm_res_dst_s { @@ -461,7 +462,7 @@ void dMenu_StageMapCtrl_c::setPlusNowStayFloorNo(s8 param_0, u8 param_1) { } void dMenu_StageMapCtrl_c::moveFloor() { - if ((field_0xf3 == 0) && (field_0xf1 == 0)) { + if ((field_0xf3 DUSK_IF_ELSE(<= , ==) 0) && (field_0xf1 DUSK_IF_ELSE(<= , ==) 0)) { if (field_0xea != 0) { field_0xf3 = field_0xf0; field_0x90 = 0.0f; @@ -488,7 +489,7 @@ void dMenu_StageMapCtrl_c::moveFloor() { field_0xea = 0; } - } else if (field_0xf1 != 0 && field_0xea != 0) { + } else if (field_0xf1 DUSK_IF_ELSE(>, !=) 0 && field_0xea != 0) { if (field_0xea > 0) { if (field_0xe7 < field_0xed) { if ((field_0xe7 == std::floor(field_0xc4) && field_0xe8 == field_0xe7 + field_0xea) || field_0x90 > 0.5f) { @@ -520,22 +521,22 @@ void dMenu_StageMapCtrl_c::moveFloor() { field_0xeb = field_0xe7; } - if (field_0xf1 != 0) { + if (field_0xf1 DUSK_IF_ELSE(>, !=) 0) { f32 temp_f31 = (f32)(field_0xf0 - field_0xf3) / (f32)field_0xf0; field_0xeb = getFloorNo(field_0xc4); field_0xec = getFloorNo(field_0xc0); field_0x90 = getBlendPer(temp_f31 - std::floor(temp_f31)); - field_0xf1--; - if (field_0xf1 == 0) { + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0xf1, 0.0f, 1.0f), field_0xf1--); + if (field_0xf1 DUSK_IF_ELSE(<=, ==) 0) { field_0xe8 = field_0xe7; } } - if (field_0xf3 != 0) { - field_0xf3--; - if (field_0xf3 != 0 && field_0xf1 != 0) { + if (field_0xf3 DUSK_IF_ELSE(>, !=) 0) { + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0xf3, 0.0f, 1.0f), field_0xf3--); + if (field_0xf3 DUSK_IF_ELSE(>, !=) 0 && field_0xf1 DUSK_IF_ELSE(>, !=) 0) { f32 temp_f30 = (f32)(field_0xf0 - field_0xf3) / (f32)field_0xf0; field_0xeb = getFloorNo(field_0xc4); field_0xec = getFloorNo(field_0xc0); @@ -804,12 +805,14 @@ void dMenu_StageMapCtrl_c::zoomCalcSet(f32 param_0) { } void dMenu_StageMapCtrl_c::zoomIn_proc() { - f32 temp_f1 = fopMsgM_valueIncrease(field_0xf2, field_0xf2 - field_0xf4, 4); + IF_DUSK(dusk::vdt::advance_toward_frame(field_0xf4, 0.0f, 1.0f)); + f32 temp_f1 = DUSK_IF_ELSE(dusk::vdt::present_sine_ease(field_0xf2, field_0xf2 - field_0xf4), + fopMsgM_valueIncrease(field_0xf2, field_0xf2 - field_0xf4, 4)); field_0xd8 = temp_f1; zoomCalcSet(temp_f1); - field_0xf4--; - if (field_0xf4 < 0) { + IF_NOT_DUSK(field_0xf4--); + if (field_0xf4 DUSK_IF_ELSE(<=, <) 0) { field_0xf5 = 2; } } @@ -843,7 +846,7 @@ void dMenu_StageMapCtrl_c::zoomWait_init_proc() { } bool dMenu_DmapMapCtrl_c::isEnableZoomMove() const { - return field_0xf3 == 0 && (!getDisableZoomMoveFlgX() || !getDisableZoomMoveFlgZ()); + return field_0xf3 DUSK_IF_ELSE(<=, ==) 0 && (!getDisableZoomMoveFlgX() || !getDisableZoomMoveFlgZ()); } void dMenu_StageMapCtrl_c::zoomWait_proc() { @@ -887,12 +890,14 @@ void dMenu_StageMapCtrl_c::zoomOut_init_proc() { } void dMenu_StageMapCtrl_c::zoomOut_proc() { - f32 temp_f1 = fopMsgM_valueIncrease(field_0xf2, field_0xf2 - field_0xf4, 4); + IF_DUSK(dusk::vdt::advance_toward_frame(field_0xf4, 0.0f, 1.0f)); + f32 temp_f1 = DUSK_IF_ELSE(dusk::vdt::present_sine_ease(field_0xf2, field_0xf2 - field_0xf4), + fopMsgM_valueIncrease(field_0xf2, field_0xf2 - field_0xf4, 4)); zoomCalcSet(1.0f - temp_f1); field_0xd8 = 1.0f - temp_f1; - field_0xf4--; - if (field_0xf4 < 0) { + IF_NOT_DUSK(field_0xf4--); + if (field_0xf4 DUSK_IF_ELSE(<=, <) 0) { field_0xf5 = 0; } } @@ -990,7 +995,7 @@ void dMenu_StageMapCtrl_c::_delete() { bool dMenu_StageMapCtrl_c::isEnableZoomIn() { bool var_r30 = 0; - if (field_0xf3 == 0 && dStage_roomControl_c::getFileList2(field_0xe6) != NULL) { + if (field_0xf3 DUSK_IF_ELSE(<=, ==) 0 && dStage_roomControl_c::getFileList2(field_0xe6) != NULL) { var_r30 = 1; } @@ -998,7 +1003,7 @@ bool dMenu_StageMapCtrl_c::isEnableZoomIn() { } bool dMenu_StageMapCtrl_c::isEnableZoomOut() { - return field_0xf3 == 0; + return field_0xf3 DUSK_IF_ELSE(<=, ==) 0; } void dMenu_StageMapCtrl_c::setPlusZoomCenterX(f32 param_0) { diff --git a/src/d/d_menu_fishing.cpp b/src/d/d_menu_fishing.cpp index 2579d328fb..84e62b2385 100644 --- a/src/d/d_menu_fishing.cpp +++ b/src/d/d_menu_fishing.cpp @@ -16,8 +16,10 @@ #include "m_Do/m_Do_graphic.h" #include -#include "helpers/string.hpp" +#if TARGET_PC #include "dusk/version.hpp" +#include "helpers/string.hpp" +#endif typedef void (dMenu_Fishing_c::*initFunc)(); DUSK_GAME_DATA initFunc map_init_process[] = { diff --git a/src/d/d_menu_fmap.cpp b/src/d/d_menu_fmap.cpp index c2d642a6d4..2615ad0b01 100644 --- a/src/d/d_menu_fmap.cpp +++ b/src/d/d_menu_fmap.cpp @@ -1,33 +1,36 @@ #include "d/dolzel.h" // IWYU pragma: keep -#include -#include +#include "d/d_menu_fmap.h" +#include "SSystem/SComponent/c_math.h" #include "JSystem/JKernel/JKRAramArchive.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "JSystem/JKernel/JKRMemArchive.h" -#include "SSystem/SComponent/c_math.h" -#include "d/actor/d_a_midna.h" -#include "d/actor/d_a_player.h" +#include +#include +#include "f_op/f_op_msg_mng.h" #include "d/d_com_inf_game.h" #include "d/d_lib.h" -#include "d/d_menu_fmap.h" #include "d/d_menu_fmap2D.h" #include "d/d_menu_fmap_map.h" #include "d/d_menu_window.h" -#include "d/d_meter2_draw.h" -#include "d/d_meter2_info.h" #include "d/d_meter_HIO.h" #include "d/d_meter_map.h" +#include "d/d_meter2_draw.h" +#include "d/d_meter2_info.h" #include "d/d_msg_object.h" #include "d/d_msg_scrn_explain.h" #include "d/d_stage.h" -#include "dusk/version.hpp" -#include "f_op/f_op_msg_mng.h" -#include "helpers/string.hpp" +#include "d/actor/d_a_player.h" +#include "d/actor/d_a_midna.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/menus.h" +#include "dusk/interp/user_interface.h" #include "dusk/memory.h" +#include "dusk/version.hpp" +#include "helpers/string.hpp" #endif static dMf_HIO_c g_fmHIO; @@ -276,6 +279,7 @@ dMenu_Fmap_c::dMenu_Fmap_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i } dMenu_Fmap_c::~dMenu_Fmap_c() { + IF_DUSK(dusk::interp::erase_owned_samples(this)); mpHeap->getTotalFreeSize(); if (mpFieldDat != NULL) { mpHeap->free(mpFieldDat); @@ -494,28 +498,54 @@ void dMenu_Fmap_c::_delete() { /* empty function */ } +#if TARGET_PC +namespace { +struct FmapZoomView { + u8 process = 0xff; + f32 level = 0.0f; + f32 blend = 0.0f; +}; + +void lerp(FmapZoomView& out, const FmapZoomView& lhs, const FmapZoomView& rhs, f32 step) { + out = {rhs.process, dusk::interp::lerp(lhs.level, rhs.level, step), + dusk::interp::lerp(lhs.blend, rhs.blend, step)}; +} + +struct FmapZoomSamples { + dusk::interp::Samples views; + FmapZoomView end; + bool needsRestore = false; +}; +} +#endif + void dMenu_Fmap_c::_move() { + IF_DUSK(resetZoomEnd()); u8 process = mProcess; field_0x310 = 0; mpDraw2DBack->clearIconInfo(); (this->*move_process[mProcess])(); - - mpDraw2DTop->_execute(); + + IF_NOT_DUSK(mpDraw2DTop->_execute()); mpDraw2DBack->setBaseBackAlpha(g_fmHIO.mBaseBackAlpha); - mpDraw2DBack->btkAnimeLoop(g_fmHIO.mBackAnimeStep); + IF_NOT_DUSK(mpDraw2DBack->btkAnimeLoop(g_fmHIO.mBackAnimeStep)); mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); - mpDraw2DTop->btkAnimeLoop(g_fmHIO.mTopAnimeStep); + IF_NOT_DUSK(mpDraw2DTop->btkAnimeLoop(g_fmHIO.mTopAnimeStep)); if (mProcess != process) { + IF_DUSK(captureZoomEnd(process)); (this->*init_process[mProcess])(); + IF_DUSK(resetRenderState()); } +#if !TARGET_PC if (mProcess == PROC_PORTAL_WARP_FORBID) { mpDraw2DBack->calcBackAlpha(true); } else { mpDraw2DBack->calcBackAlpha(false); } +#endif mpDraw2DBack->setAllTrans(mTransX, mTransY); mpDraw2DTop->setAllTrans(mTransX, mTransY); @@ -524,6 +554,7 @@ void dMenu_Fmap_c::_move() { drawDebugStage(); } +#if !TARGET_PC if (mSpotTextureFadeAlpha != 1.0f) { cLib_addCalc2(&mSpotTextureFadeAlpha, 1.0f, 0.4f, 0.5f); if (fabsf(mSpotTextureFadeAlpha - 1.0f) < 0.1f) { @@ -531,6 +562,7 @@ void dMenu_Fmap_c::_move() { } mpDraw2DBack->setSpotTextureFadeAlpha(mSpotTextureFadeAlpha); } +#endif #if TARGET_PC u8 region = mpDraw2DBack->getSelectRegion(); @@ -540,30 +572,120 @@ void dMenu_Fmap_c::_move() { mpDraw2DTop->mSelectRegionNo = 0xFF; } + captureRenderState(); #endif } +#if TARGET_PC +void dMenu_Fmap_c::captureRenderState() { + dusk::interp::capture_menu_pose(this, {mTransX, mTransY, mAlphaRatio}); + auto& zoom = dusk::interp::get(this); + if (zoom.end.process == 0xff) { + const FmapZoomView view{mProcess, (f32)mZoomLevel, field_0x1ec}; + zoom.views.capture(&view, 1); + } +} + +void dMenu_Fmap_c::captureZoomEnd(u8 process) { + switch (process) { + case PROC_ZOOM_ALL_TO_REGION: + case PROC_ZOOM_REGION_TO_ALL: + case PROC_ZOOM_REGION_TO_SPOT: + case PROC_ZOOM_SPOT_TO_REGION: + case PROC_PORTAL_DEMO1: + case PROC_PORTAL_DEMO3: + case PROC_YAMIBOSS_DEMO4: + case PROC_TABLE_DEMO1: + case PROC_TABLE_DEMO2: + case PROC_HOWL_DEMO1: + break; + default: + return; + } + + auto& zoom = dusk::interp::get(this); + zoom.end = {process, (f32)mZoomLevel, field_0x1ec}; + zoom.views.capture(&zoom.end, 1); +} + +void dMenu_Fmap_c::resetRenderState() { + if (dusk::interp::get(this).end.process != 0xff) { + dusk::interp::get(this).poses.reset(); + } else { + dusk::interp::erase_owned_samples(this); + } +} + +void dMenu_Fmap_c::resetZoomEnd() { + auto& zoom = dusk::interp::get(this); + if (zoom.needsRestore) { + presentZoomView(zoom.end.process, zoom.end.level, zoom.end.blend); + } + if (zoom.end.process != 0xff) { + zoom.views.reset(); + const FmapZoomView view{mProcess, (f32)mZoomLevel, field_0x1ec}; + zoom.views.capture(&view, 1); + } + zoom.end.process = 0xff; + zoom.needsRestore = false; +} +#endif + void dMenu_Fmap_c::_draw() { + IF_DUSK(const auto pose = dusk::interp::read_menu_pose(this, {mTransX, mTransY, mAlphaRatio})); +#if TARGET_PC + auto& zoom = dusk::interp::get(this); + const auto [renderProcess, zoomLevel, zoomBlend] = + zoom.views.read(0, {mProcess, (f32)mZoomLevel, field_0x1ec}); + const bool finishingZoom = renderProcess != mProcess; +#endif if (mpDraw2DBack != NULL && mpDraw2DTop != NULL) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + mpDraw2DTop->setAllAlphaRate(pose.alpha, false); + presentAnims(); + presentZoomView(renderProcess, zoomLevel, zoomBlend); + zoom.needsRestore |= finishingZoom; + mpDraw2DBack->setAllTrans(pose.x, pose.y); + mpDraw2DTop->setAllTrans(pose.x, pose.y); + mpDraw2DBack->setAllAlphaRate(pose.alpha, false); + mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); + if (field_0x305) { + mpMenuFmapMap->presentRendering(mpWorldData, mStartStageNo, + mpDraw2DBack->getRenderingPosX(), + mpDraw2DBack->getRenderingPosZ(), + mpDraw2DBack->getRenderingScale(), + mpDraw2DBack->getMapZoomRate()); + } + mpDraw2DBack->clearIconInfo(); + } +#endif if (field_0x305) { + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame()) mpMenuFmapMap->setRendering(mpWorldData, mStartStageNo, mpDraw2DBack->getRenderingPosX(), mpDraw2DBack->getRenderingPosZ(), mpDraw2DBack->getRenderingScale(), mpDraw2DBack->getMapZoomRate()); mpDraw2DBack->setStageInfo(mSpotNum, mpMenuFmapMap); - drawIcon(field_0x1ec, false); - if (mProcess == PROC_ZOOM_REGION_TO_SPOT || mProcess == PROC_ZOOM_SPOT_TO_REGION - || mProcess == PROC_YAMIBOSS_DEMO4 || mProcess == PROC_LIGHT_DEMO1 - || mProcess == PROC_TABLE_DEMO2 || mProcess == PROC_HOWL_DEMO1) + IF_DUSK_BLOCK_END + drawIcon(DUSK_IF_ELSE(zoomBlend, field_0x1ec), false); + if (DUSK_IF_ELSE(renderProcess, mProcess) == PROC_ZOOM_REGION_TO_SPOT || + DUSK_IF_ELSE(renderProcess, mProcess) == PROC_ZOOM_SPOT_TO_REGION || + DUSK_IF_ELSE(renderProcess, mProcess) == PROC_YAMIBOSS_DEMO4 || + DUSK_IF_ELSE(renderProcess, mProcess) == PROC_LIGHT_DEMO1 || + DUSK_IF_ELSE(renderProcess, mProcess) == PROC_TABLE_DEMO2 || + DUSK_IF_ELSE(renderProcess, mProcess) == PROC_HOWL_DEMO1) { - f32 scale = 1.0f - field_0x1ec; + f32 scale = 1.0f - DUSK_IF_ELSE(zoomBlend, field_0x1ec); mpDraw2DBack->iconScale(0, scale, scale, 1.0f - scale); } } else { drawPortalIcon(); - if (mProcess == PROC_ZOOM_ALL_TO_REGION || mProcess == PROC_ZOOM_REGION_TO_ALL) { - f32 scale = 1.0f - (f32)mZoomLevel / 10.0f; + if (DUSK_IF_ELSE(renderProcess, mProcess) == PROC_ZOOM_ALL_TO_REGION || + DUSK_IF_ELSE(renderProcess, mProcess) == PROC_ZOOM_REGION_TO_ALL) + { + f32 scale = 1.0f - DUSK_IF_ELSE(zoomLevel, (f32)mZoomLevel) / 10.0f; mpDraw2DBack->iconScale(0, scale, scale, 1.0f - scale); } } @@ -574,6 +696,61 @@ void dMenu_Fmap_c::_draw() { } } +#if TARGET_PC +void dMenu_Fmap_c::presentAnims() { + if (mAlphaRatio == 1.0f) { + if (mProcess == PROC_ALL_MAP) { + mpDraw2DBack->allmap_move2(mpStick); + } else if (mProcess == PROC_REGION_MAP) { + mpDraw2DBack->regionMapMove(mpStick); + } else if (mProcess == PROC_SPOT_MAP) { + mpDraw2DBack->stageMapMove(mpStick, 1, true); + } else if (mProcess == PROC_PORTAL_WARP_MAP || mProcess == PROC_PORTAL_DEMO5) { + mpDraw2DBack->regionMapMove(mpStick); + } + + mpDraw2DTop->_execute(); + mpDraw2DBack->calcBackAlpha(mProcess == PROC_PORTAL_WARP_FORBID); + if (mSpotTextureFadeAlpha != 1.0f) { + dusk::vdt::present_addCalc2(&mSpotTextureFadeAlpha, 1.0f, 0.4f, 0.5f, 0.1f); + mpDraw2DBack->setSpotTextureFadeAlpha(mSpotTextureFadeAlpha); + } + } + if (mpDraw2DBack != NULL && mpDraw2DTop != NULL) { + mpDraw2DBack->btkAnimeLoop(g_fmHIO.mBackAnimeStep); + mpDraw2DTop->btkAnimeLoop(g_fmHIO.mTopAnimeStep); + mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); + } +} + +void dMenu_Fmap_c::presentZoomView(u8 process, f32 zoomLevel, f32 zoomBlend) { + switch (process) { + case PROC_ZOOM_ALL_TO_REGION: + case PROC_ZOOM_REGION_TO_ALL: + case PROC_PORTAL_DEMO1: + case PROC_PORTAL_DEMO3: + mpDraw2DBack->zoomMapCalc(std::clamp(zoomLevel / 10.0f, 0.0f, 1.0f)); + break; + case PROC_TABLE_DEMO1: + if (zoomLevel > 0) { + mpDraw2DBack->zoomMapCalc(zoomLevel / 10.0f); + } + break; + case PROC_ZOOM_REGION_TO_SPOT: + case PROC_ZOOM_SPOT_TO_REGION: + case PROC_YAMIBOSS_DEMO4: + mpDraw2DBack->zoomMapCalc2(zoomBlend); + break; + case PROC_TABLE_DEMO2: + case PROC_HOWL_DEMO1: + if (zoomLevel > 0) { + mpDraw2DBack->zoomMapCalc2(zoomBlend); + } + break; + } +} +#endif + u8 dMenu_Fmap_c::getNextStatus(u8* param_0) { u8 ret = 8; *param_0 = 0; @@ -774,16 +951,15 @@ void dMenu_Fmap_c::all_map_proc() { } } else { u8 region1 = mpDraw2DBack->getSelectRegion(); - mpDraw2DBack->allmap_move2(mpStick); + IF_NOT_DUSK(mpDraw2DBack->allmap_move2(mpStick)); u8 region2 = mpDraw2DBack->getSelectRegion(); - if (region1 != region2 || mResetAreaName) { + if (DUSK_IF_ELSE(region_change, region1 != region2) || mResetAreaName) { mResetAreaName = false; if (mpDraw2DBack->getSelectRegion() != 0xff && mpDraw2DBack->isShowRegion(mpDraw2DBack->getSelectRegion())) { setAreaName(mTitleName[mpDraw2DBack->getSelectRegion()]); mpDraw2DBack->setSpotCursor(0); - } else { setAreaNameZero(); } @@ -933,7 +1109,7 @@ void dMenu_Fmap_c::region_map_proc() { if (g_fmapHIO.mDisplayReferenceArea) { mpDraw2DBack->zoomMapCalc(1.0f); } - mpDraw2DBack->regionMapMove(mpStick); + IF_NOT_DUSK(mpDraw2DBack->regionMapMove(mpStick)); int stage_no, room_no; f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF() @@ -1152,9 +1328,8 @@ void dMenu_Fmap_c::zoom_spot_to_region_init() { mZoomLevel = 10; field_0x1ec = 1.0f; #if TARGET_PC - // Frame interp note: field_0x122d used to be set every draw, causing flickering. Do it here instead. - if (dusk::frame_interp::is_enabled()) { - mpDraw2DBack->resetScrollArrowMask(); + if (dusk::interp::is_enabled()) { + mpDraw2DBack->field_0x122d = 0; } #endif Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); @@ -1216,7 +1391,7 @@ void dMenu_Fmap_c::spot_map_proc() { } else if (dMw_A_TRIGGER() && !dMeter2Info_isTouchKeyCheck(0xc) && dMeter2Info_getMeterClass()->getMeterDrawPtr()->getInsideObjCheck() != 1) { - mpDraw2DBack->stageMapMove(mpStick, 1, true); + IF_NOT_DUSK(mpDraw2DBack->stageMapMove(mpStick, 1, true)); } else if (dMw_Z_TRIGGER() && mpDraw2DTop->isWarpAccept()) { #if TARGET_PC || VERSION >= VERSION_GCN_JPN IF_DUSK_BLOCK(dusk::version::isRegionJpn()) @@ -1265,7 +1440,7 @@ void dMenu_Fmap_c::spot_map_proc() { mpDraw2DBack->zoomMapCalc2(field_0x1ec); } - mpDraw2DBack->stageMapMove(mpStick, 1, true); + IF_NOT_DUSK(mpDraw2DBack->stageMapMove(mpStick, 1, true)); int stage_no, room_no; f32 pos_x = mpDraw2DBack->getMapAreaGlobalCenterPosX() - mDoGph_gInf_c::getMinXF() @@ -1273,7 +1448,7 @@ void dMenu_Fmap_c::spot_map_proc() { f32 pos_y = mpDraw2DBack->getMapAreaGlobalCenterPosY() - mDoGph_gInf_c::getHeightF() * 0.5f; mpMenuFmapMap->getPointStagePathInnerNo(getNowFmapRegionData(), pos_x, pos_y, mStayStageNo, &stage_no, &room_no); - + if (mStageCursor != stage_no || mResetAreaName) { mStageCursor = stage_no; mRoomCursor = room_no; @@ -1759,10 +1934,15 @@ bool dMenu_Fmap_c::isOpen() { mpDraw2DTop->setAllAlphaRate(mAlphaRatio, init); mpDraw2DBack->setSpotTextureFadeAlpha(mSpotTextureFadeAlpha); +#if TARGET_PC + mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); + captureRenderState(); +#endif return ret; } bool dMenu_Fmap_c::isClose() { + IF_DUSK(resetZoomEnd()); bool ret = true; bool bVar2 = false; @@ -1800,6 +1980,10 @@ bool dMenu_Fmap_c::isClose() { mpDraw2DTop->setAllTrans(mTransX, mTransY); mpDraw2DTop->setAllAlphaRate(mAlphaRatio, bVar2); +#if TARGET_PC + mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); + captureRenderState(); +#endif return ret; } @@ -2476,7 +2660,7 @@ void dMenu_Fmap_c::setAreaNameZero() { } void dMenu_Fmap_c::portalWarpMapMove(STControl* i_stick) { - mpDraw2DBack->regionMapMove(i_stick); + IF_NOT_DUSK(mpDraw2DBack->regionMapMove(i_stick)); dMenu_Fmap_portal_data_c* portal_dat = mpPortalDat; dMenu_Fmap_portal_data_c::data* portals = portal_dat->mData; f32 arrow_x = mpDraw2DBack->getArrowPos2DX(); diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index cd2e09c6d7..e33dc65dcf 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -17,15 +17,15 @@ #include "d/d_msg_scrn_explain.h" #include "m_Do/m_Do_graphic.h" #include "d/actor/d_a_midna.h" -#if TARGET_PC -#include "dusk/frame_interpolation.h" -#include "dusk/ui/touch_controls.hpp" -#endif #include +#if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" +#include "dusk/ui/touch_controls.hpp" #include "dusk/version.hpp" -#if TARGET_PC void dMenu_Fmap2DBack_c::fMapBackWide() { mpBaseScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); mpBaseScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); @@ -360,13 +360,8 @@ void dMenu_Fmap2DBack_c::draw() { scrollAreaDraw(); } -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - blinkMove(30); - moveLightDropAnime(); - } + blinkMove(30); + moveLightDropAnime(); setCenterPosX(field_0x11dc, 1); drawIcon(mTransX, mTransZ, mAlphaRate, field_0xfa8 * mSpotTextureFadeAlpha); @@ -401,15 +396,10 @@ void dMenu_Fmap2DBack_c::draw() { (mArrowPos3DZ + control_ypos + fVar3) - fVar5, &mArrowPos2DX, &mArrowPos2DY); -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - field_0x11e0 -= g_fmapHIO.mCursorSpeed; + field_0x11e0 -= g_fmapHIO.mCursorSpeed IF_DUSK(* dusk::game_clock::original_frames()); - if (field_0x11e0 < 0.0f) { - field_0x11e0 += 360.0f; - } + if (field_0x11e0 < 0.0f) { + field_0x11e0 += 360.0f; } mpPointParent->getPanePtr()->rotate(mpPointParent->getSizeX() / 2.0f, @@ -450,7 +440,7 @@ void dMenu_Fmap2DBack_c::draw() { if (field_0x122d) { mpMeterHaihai->drawHaihai(field_0x122d); #if TARGET_PC - if (!dusk::frame_interp::is_enabled()) { + if (!dusk::interp::is_enabled()) { field_0x122d = 0; } #else @@ -1025,8 +1015,8 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) { } f32 speed = (sp24 / 100.0f) * zoomRate; - f32 delta_y = speed * cM_ssin(angle); - f32 delta_x = speed * cM_scos(angle); + f32 delta_y = speed * cM_ssin(angle) IF_DUSK(* dusk::game_clock::original_frames()); + f32 delta_x = speed * cM_scos(angle) IF_DUSK(* dusk::game_clock::original_frames()); #ifdef TARGET_PC if (dusk::getSettings().game.enableMirrorMode) { @@ -1871,20 +1861,20 @@ void dMenu_Fmap2DBack_c::calcBlink() { g_fmapHIO.mMapBlink[i].mUnselectedRegion.mBlinkSpeed); #if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - field_0x1218++; - if (field_0x1218 >= selected_blink_speed) { - field_0x1218 = 0; - } - - field_0x121a++; - if (field_0x121a >= unselected_blink_speed) { - field_0x121a = 0; - } + dusk::vdt::advance_looping_frame(field_0x1218, 1.0f, selected_blink_speed); + dusk::vdt::advance_looping_frame(field_0x121a, 1.0f, unselected_blink_speed); +#else + field_0x1218++; + if (field_0x1218 >= selected_blink_speed) { + field_0x1218 = 0; } + field_0x121a++; + if (field_0x121a >= unselected_blink_speed) { + field_0x121a = 0; + } +#endif + f32 t_selected = 0.0f; f32 t_unselected = 0.0f; @@ -1909,6 +1899,9 @@ void dMenu_Fmap2DBack_c::calcBlink() { } void dMenu_Fmap2DBack_c::calcBackAlpha(bool param_0) { +#if TARGET_PC + dusk::vdt::present_addCalc2(&mBackAlpha, param_0 ? 1.0f : 0.0f, 0.4f, 0.5f, 0.1f); +#else if (param_0) { if (mBackAlpha != 1.0f) { cLib_addCalc2(&mBackAlpha, 1.0f, 0.4f, 0.5f); @@ -1924,14 +1917,19 @@ void dMenu_Fmap2DBack_c::calcBackAlpha(bool param_0) { } } } +#endif } void dMenu_Fmap2DBack_c::btkAnimeLoop(f32 i_step) { if (mpBaseAnm) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(mAnmFrame, i_step, mpBaseAnm->getFrameMax()); +#else mAnmFrame += i_step; if (mAnmFrame >= mpBaseAnm->getFrameMax()) { mAnmFrame -= mpBaseAnm->getFrameMax(); } +#endif mpBaseAnm->setFrame(mAnmFrame); } else { mAnmFrame = 0.0f; @@ -1975,8 +1973,8 @@ void dMenu_Fmap2DBack_c::regionMapMove(STControl* i_stick) { base_speed = g_fmapHIO.mScrollSpeedRegionFast; } f32 speed = base_speed / 100.0f * local_78; - f32 speed_y = speed * cM_ssin(angle); - f32 speed_x = speed * cM_scos(angle); + f32 speed_y = speed * cM_ssin(angle) IF_DUSK(* dusk::game_clock::original_frames()); + f32 speed_x = speed * cM_scos(angle) IF_DUSK(* dusk::game_clock::original_frames()); control_xpos += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -speed_y :) speed_y; control_ypos += speed_x; } @@ -2045,8 +2043,8 @@ void dMenu_Fmap2DBack_c::stageMapMove(STControl* i_stick, u8 param_1, bool param base_speed = g_fmapHIO.mScrollSpeedRegionZoomFast; } f32 speed = base_speed / 100.0f * local_78; - f32 speed_x = speed * cM_ssin(angle); - f32 speed_z = speed * cM_scos(angle); + f32 speed_x = speed * cM_ssin(angle) IF_DUSK(* dusk::game_clock::original_frames()); + f32 speed_z = speed * cM_scos(angle) IF_DUSK(* dusk::game_clock::original_frames()); mStageTransX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -speed_x :) speed_x; mStageTransZ += speed_z; } else if (!param_2) { @@ -2112,6 +2110,11 @@ void dMenu_Fmap2DBack_c::stageMapMove(STControl* i_stick, u8 param_1, bool param } void dMenu_Fmap2DBack_c::setAllAlphaRate(f32 i_rate, bool i_init) { +#if TARGET_PC + if (!i_init && i_rate == 1.0f && mAlphaRate == 1.0f) { + return; + } +#endif mAlphaRate = i_rate; if (i_init) { mpBaseRoot->setBackupAlpha(); @@ -2306,6 +2309,7 @@ dMenu_Fmap2DTop_c::dMenu_Fmap2DTop_c(JKRExpHeap* i_heap, STControl* i_stick) { mpHeap = i_heap; mTransX = 0.0f; mTransY = 0.0f; + IF_DUSK(mAlphaRate = 0.0f;) mpPortalBin = NULL; mpScrnExplain = NULL; @@ -2795,6 +2799,11 @@ void dMenu_Fmap2DTop_c::_execute() { } void dMenu_Fmap2DTop_c::setAllAlphaRate(f32 i_rate, bool i_init) { +#if TARGET_PC + if (!i_init && i_rate == 1.0f && mAlphaRate == 1.0f) { + return; + } +#endif mAlphaRate = i_rate; if (i_init) { mpTitleRoot->setBackupAlpha(); @@ -2852,10 +2861,14 @@ void dMenu_Fmap2DTop_c::draw() { void dMenu_Fmap2DTop_c::btkAnimeLoop(J2DAnmTextureSRTKey* i_anm, f32 i_delta) { if (i_anm) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(mAnmFrame, i_delta, i_anm->getFrameMax()); +#else mAnmFrame += i_delta; if (mAnmFrame >= i_anm->getFrameMax()) { mAnmFrame -= i_anm->getFrameMax(); } +#endif i_anm->setFrame(mAnmFrame); } else { mAnmFrame = 0.0f; @@ -3114,9 +3127,9 @@ void dMenu_Fmap2DTop_c::setArrowAlphaRatio(u8 i_mask, f32 i_rate) { } void dMenu_Fmap2DTop_c::setAlphaAnimeMin(CPaneMgrAlpha* i_pane) { - s16 timer = i_pane->getAlphaTimer(); + DUSK_IF_ELSE(f32, s16) timer = i_pane->getAlphaTimer(); if (timer > 0 || i_pane->getAlphaRate() != 0.0f) { - timer--; + DUSK_IF_ELSE(timer -= dusk::game_clock::original_frames(), timer--); if (timer < 0) { timer = 0; } @@ -3126,12 +3139,24 @@ void dMenu_Fmap2DTop_c::setAlphaAnimeMin(CPaneMgrAlpha* i_pane) { } void dMenu_Fmap2DTop_c::setAlphaAnimeMid(CPaneMgrAlpha* i_pane) { - s16 timer = i_pane->getAlphaTimer(); + DUSK_IF_ELSE(f32, s16) timer = i_pane->getAlphaTimer(); if (timer != 3 || i_pane->getAlphaRate() != 0.25f) { if (timer > 3) { - timer--; +#if TARGET_PC + timer -= dusk::game_clock::original_frames(); + if (timer < 3) { + timer = 3; + } +#endif + IF_NOT_DUSK(timer--); } else if (timer < 3) { - timer++; +#if TARGET_PC + timer += dusk::game_clock::original_frames(); + if (timer > 3) { + timer = 3; + } +#endif + IF_NOT_DUSK(timer++); } i_pane->alphaAnimeStart(timer); i_pane->setAlphaRate(timer / 6.0f * 0.5f); @@ -3139,9 +3164,9 @@ void dMenu_Fmap2DTop_c::setAlphaAnimeMid(CPaneMgrAlpha* i_pane) { } void dMenu_Fmap2DTop_c::setAlphaAnimeMax(CPaneMgrAlpha* i_pane) { - s16 timer = i_pane->getAlphaTimer(); + DUSK_IF_ELSE(f32, s16) timer = i_pane->getAlphaTimer(); if (timer < 5 || i_pane->getAlphaRate() != 1.0f) { - timer++; + DUSK_IF_ELSE(timer += dusk::game_clock::original_frames(), timer++); if (timer > 5) { timer = 5; } diff --git a/src/d/d_menu_fmap_map.cpp b/src/d/d_menu_fmap_map.cpp index b2477b8b06..c1e96bc56a 100644 --- a/src/d/d_menu_fmap_map.cpp +++ b/src/d/d_menu_fmap_map.cpp @@ -8,11 +8,12 @@ #include "d/d_debug_viewer.h" #include "d/d_menu_fmap_map.h" #include "m_Do/m_Do_graphic.h" -#if TARGET_PC -#include -#endif #include +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif + static u8 twoValueLineInterpolation(u8 i_value1, u8 i_value2, f32 i_param) { return (u8)((f32)i_value1 + i_param * ((f32)i_value2 - (f32)i_value1)); } @@ -480,6 +481,10 @@ dMenu_FmapMap_c::~dMenu_FmapMap_c() { } void dMenu_FmapMap_c::_create(u16 i_texWidth, u16 i_texHeight, u16 param_2, u16 param_3, void* i_res) { +#if TARGET_PC + mResetFlashFrame = true; + mPaletteFrameInitialized = false; +#endif m_res = (dMfm_prm_res_s*)i_res; m_palette = m_res->palette_data; field_0xcc = 0x6c; @@ -507,10 +512,55 @@ void dMenu_FmapMap_c::_delete() { DUSK_GAME_DATA const dMfm_HIO_prm_res_src_s dMfm_HIO_prm_res_src_s::m_other = {30}; +#if TARGET_PC +void dMenu_FmapMap_c::presentAnims() { + const f32 duration = dMfm_HIO_prm_res_src_s::m_other.mFlashDuration; + if (mResetFlashFrame) { + mFlashFrame = 0.0f; + mResetFlashFrame = false; + } else { + dusk::vdt::advance_looping_frame(mFlashFrame, 1.0f, duration + 1.0f); + } + mFlashTimer = std::max(0.0f, duration - mFlashFrame); + + const int periods[] = {m_res->field_0x168, m_res->field_0x16a}; + for (int i = 0; i < 2; ++i) { + if (periods[i] <= 0) { + mPaletteFrame[i] = 0.0f; + } else if (!mPaletteFrameInitialized) { + mPaletteFrame[i] = g_Counter.mCounter0 % periods[i]; + } else { + dusk::vdt::advance_looping_frame(mPaletteFrame[i], 1.0f, periods[i]); + } + } + mPaletteFrameInitialized = true; +} + +void dMenu_FmapMap_c::presentRendering(dMenu_Fmap_world_data_c* i_worldData, int i_startStageNo, + f32 i_posX, f32 i_posY, f32 i_scale, f32 i_zoomRate) +{ + mZoomRate = i_zoomRate; + mpWorldData = i_worldData; + mStartStageNo = i_startStageNo; + mPosX = i_posX; + mPosZ = i_posY; + mCmPerTexel = i_scale; + field_0x8 = mCmPerTexel * field_0x20 * mDoGph_gInf_c::getScale(); + field_0xc = mCmPerTexel * field_0x22; +} +#endif + void dMenu_FmapMap_c::draw() { { int unused; } f32 f30 = 0.0f; +#if TARGET_PC + presentAnims(); + const f32 half = dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2; + if (half > 0.0f) { + f30 = std::clamp(std::fabs(mFlashTimer - half) / half, 0.0f, 1.0f); + } +#else if (mFlashTimer < dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2) { f30 = (int)(dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2 - mFlashTimer) / @@ -520,16 +570,19 @@ void dMenu_FmapMap_c::draw() { (int)(mFlashTimer - dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2) / (f32)(int)(dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2); } +#endif setPointColor(1.0f - f30); GXColor color; f32 f31; - f31 = getRateWithFrameCount(m_res->field_0x168); + f31 = DUSK_IF_ELSE(dusk::vdt::clamped_fraction(mPaletteFrame[0], m_res->field_0x168), + getRateWithFrameCount(m_res->field_0x168)); f31 = cM_ssin(f31 * 0x10000 - 0x8000) * 0.5f + 0.5f; twoColorLineInterporation(m_res->field_0xd8, m_res->field_0xdc, f31, color); setFmapPaletteColor(PALETTE_19, color); - f31 = getRateWithFrameCount(m_res->field_0x16a); + f31 = DUSK_IF_ELSE(dusk::vdt::clamped_fraction(mPaletteFrame[1], m_res->field_0x16a), + getRateWithFrameCount(m_res->field_0x16a)); f31 = cM_ssin(f31 * 0x10000 - 0x8000) * 0.5f + 0.5f; twoColorLineInterporation(m_res->field_0xe0, m_res->field_0xe4, f31, color); setFmapPaletteColor(PALETTE_1A, color); @@ -680,15 +733,18 @@ void dMenu_FmapMap_c::setRendering(dMenu_Fmap_world_data_c* i_worldData, int i_s f32 i_posX, f32 i_posY, f32 i_scale, f32 i_zoomRate) { mZoomRate = i_zoomRate; if (mFlash != mLastFlash || mStageCursor != mLastStageCursor) { + IF_DUSK(mResetFlashFrame = true); mFlashTimer = dMfm_HIO_prm_res_src_s::m_other.mFlashDuration; mLastFlash = mFlash; mLastStageCursor = mStageCursor; } else { +#if !TARGET_PC if (mFlashTimer != 0) { mFlashTimer--; } else { mFlashTimer = dMfm_HIO_prm_res_src_s::m_other.mFlashDuration; } +#endif } entry(i_worldData, i_startStageNo, i_posX, i_posY, i_scale); } diff --git a/src/d/d_menu_insect.cpp b/src/d/d_menu_insect.cpp index e7917532a2..828e39384d 100644 --- a/src/d/d_menu_insect.cpp +++ b/src/d/d_menu_insect.cpp @@ -22,10 +22,10 @@ #include #include -#include "dusk/version.hpp" - #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/menu_pointer.h" +#include "dusk/version.hpp" #endif typedef void (dMenu_Insect_c::*initFunc)(); @@ -165,6 +165,11 @@ void dMenu_Insect_c::_move() { void dMenu_Insect_c::_draw() { if (mpArchive != NULL) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + mpExpParent->presentAnime(); + } +#endif J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); mpBlackTex->setAlpha(0xff); @@ -194,6 +199,7 @@ void dMenu_Insect_c::_draw() { // operations applied which cannot easily be reverse engineered mpSelect_c->translate(g_drawHIO.mInsectListScreen.mConfirmOptionPosX_4x3 + 486.0f, g_drawHIO.mInsectListScreen.mConfirmOptionPosY_4x3 + 209.0f); + IF_DUSK(mpSelect_c->presentAnims()); mpSelect_c->draw(0.0f, 0.0f); mpIconScreen->draw(0.0f, 0.0f, grafPort); } diff --git a/src/d/d_menu_item_explain.cpp b/src/d/d_menu_item_explain.cpp index 9ef85ed32e..0ca70af9e7 100644 --- a/src/d/d_menu_item_explain.cpp +++ b/src/d/d_menu_item_explain.cpp @@ -5,26 +5,30 @@ #include "d/dolzel.h" // IWYU pragma: keep -#include -#include +#include "d/d_menu_item_explain.h" #include "JSystem/J2DGraph/J2DTextBox.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "JSystem/JUtility/JUTTexture.h" +#include +#include #include "d/actor/d_a_player.h" #include "d/d_com_inf_game.h" #include "d/d_item.h" #include "d/d_kantera_icon_meter.h" #include "d/d_lib.h" -#include "d/d_menu_item_explain.h" +#include "d/d_select_cursor.h" #include "d/d_meter2_info.h" #include "d/d_meter_HIO.h" -#include "d/d_msg_scrn_3select.h" -#include "d/d_msg_scrn_arrow.h" #include "d/d_msg_string.h" -#include "d/d_select_cursor.h" -#include "dusk/version.hpp" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" +#include "d/d_msg_scrn_3select.h" +#include "d/d_msg_scrn_arrow.h" + +#if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/version.hpp" +#endif typedef void (dMenu_ItemExplain_c::*initFunc)(); static initFunc init_process[] = { @@ -352,6 +356,7 @@ void dMenu_ItemExplain_c::draw(J2DOrthoGraph* i_graph) { // were likely either chosen by hand or had multiple arithmetic // operations applied which cannot easily be reverse engineered mpSelect_c->translate(486.0f, 209.0f); + IF_DUSK(mpSelect_c->presentAnims()); mpSelect_c->draw(0.0f, 0.0f); } if (mpArrow != NULL) { @@ -440,7 +445,7 @@ void dMenu_ItemExplain_c::open_init() { } void dMenu_ItemExplain_c::open_proc() { - mAlphaRatio += 2.0f; + mAlphaRatio += 2.0f IF_DUSK(* dusk::game_clock::original_frames()); if (mAlphaRatio >= 201.0f) { mAlphaRatio = 201.0f; mStatus = 2; diff --git a/src/d/d_menu_letter.cpp b/src/d/d_menu_letter.cpp index a1349e9fe5..55a240abac 100644 --- a/src/d/d_menu_letter.cpp +++ b/src/d/d_menu_letter.cpp @@ -17,8 +17,9 @@ #include "d/d_msg_scrn_arrow.h" #include "d/d_lib.h" -#ifdef TARGET_PC +#if TARGET_PC #include "dusk/achievements.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/ui/touch_controls.hpp" #include "dusk/version.hpp" @@ -29,9 +30,7 @@ static void enable_turn_page_controls(bool enabled) { dusk::ui::set_control_override(dusk::ui::Control::L, controlOverride); dusk::ui::set_control_override(dusk::ui::Control::R, controlOverride); } -#endif -#if TARGET_PC #define D_MENU_LETTER_LINE_MAX (dusk::version::isRegionJpn() ? 9 : 12) #elif VERSION == VERSION_GCN_JPN #define D_MENU_LETTER_LINE_MAX 9 @@ -226,6 +225,7 @@ void dMenu_Letter_c::_draw() { if (mpArchive == NULL) { return; } + IF_DUSK(presentAnims()); J2DGrafContext* grafContext = dComIfGp_getCurrentGrafPort(); u8 prevAlpha = mpBlackTex->getAlpha(); mpBlackTex->setAlpha(0xff); @@ -531,7 +531,7 @@ void dMenu_Letter_c::slide_right_init() { } void dMenu_Letter_c::slide_right_move() { - cLib_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f); + IF_NOT_DUSK(cLib_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f)); if (fabsf(field_0x358) < 0.1f) { field_0x358 = 0.0f; mProcess = 0; @@ -552,7 +552,7 @@ void dMenu_Letter_c::slide_left_init() { } void dMenu_Letter_c::slide_left_move() { - cLib_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f); + IF_NOT_DUSK(cLib_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f)); if (fabsf(field_0x358) < 0.1f) { field_0x358 = 0.0f; mProcess = 0; @@ -607,7 +607,7 @@ void dMenu_Letter_c::read_open_init() { void dMenu_Letter_c::read_open_move() { s16 openLetterFrame = g_drawHIO.mLetterSelectScreen.mOpenFrame[dMeter_drawLetterHIO_c::LETTER_FRAME]; - field_0x36a++; + IF_NOT_DUSK(field_0x36a++); if (field_0x36a >= openLetterFrame) { mProcess = 4; for (int i = 0; i < 2; i++) { @@ -695,7 +695,7 @@ void dMenu_Letter_c::read_next_fadeout_init() { void dMenu_Letter_c::read_next_fadeout_move() { s16 closeWindowFrame = g_drawHIO.mLetterSelectScreen.mCloseFrame[dMeter_drawLetterHIO_c::WINDOW_FRAME]; - field_0x36a--; + IF_NOT_DUSK(field_0x36a--); if (field_0x36a <= 0) { mProcess = 6; for (int i = 0; i < 2; i++) { @@ -744,7 +744,7 @@ void dMenu_Letter_c::read_next_fadein_init() { void dMenu_Letter_c::read_next_fadein_move() { s16 openLetterFrame = g_drawHIO.mLetterSelectScreen.mOpenFrame[dMeter_drawLetterHIO_c::LETTER_FRAME]; - field_0x36a++; + IF_NOT_DUSK(field_0x36a++); if (field_0x36a >= openLetterFrame) { mProcess = 4; for (int i = 0; i < 2; i++) { @@ -773,7 +773,7 @@ void dMenu_Letter_c::read_close_init() { void dMenu_Letter_c::read_close_move() { s16 closeLetterFrame = g_drawHIO.mLetterSelectScreen.mCloseFrame[dMeter_drawLetterHIO_c::LETTER_FRAME]; - field_0x36a--; + IF_NOT_DUSK(field_0x36a--); if (field_0x36a <= 0) { mProcess = 0; for (int i = 0; i < 2; i++) { @@ -792,6 +792,41 @@ void dMenu_Letter_c::read_close_move() { } } +#if TARGET_PC +void dMenu_Letter_c::presentAnims() { + if (mProcess == 1 || mProcess == 2) { + dusk::vdt::present_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f, 0.1f); + return; + } + + const auto& hio = g_drawHIO.mLetterSelectScreen; + const bool opening = mProcess == 3 || mProcess == 6; + const bool page = mProcess == 5 || mProcess == 6; + if (!opening && mProcess != 5 && mProcess != 7) { + return; + } + const f32 duration = opening ? hio.mOpenFrame[dMeter_drawLetterHIO_c::LETTER_FRAME] : + hio.mCloseFrame[page ? dMeter_drawLetterHIO_c::WINDOW_FRAME : dMeter_drawLetterHIO_c::LETTER_FRAME]; + dusk::vdt::advance_toward_frame(field_0x36a, opening ? duration : 0.0f, 1.0f); + const f32 alpha = duration <= 0.0f ? (opening ? 1.0f : 0.0f) : + dusk::vdt::clamped_fraction(field_0x36a, duration); + for (int i = 0; i < 2; i++) { + if (page) { + field_0x2ec[i]->setAlphaRate(alpha); + if (field_0x2f4[i] != NULL) { + field_0x2f4[i]->setAlphaRate(alpha); + } + } else { + mpTextParent[i]->scale(hio.mLetterWindowScale * alpha, hio.mLetterWindowScale * alpha); + mpTextParent[i]->setAlphaRate(alpha); + } + } + if (!page) { + mpBlackTex->setAlpha(hio.mWindowBGAlpha * alpha); + } +} +#endif + void dMenu_Letter_c::screenSetMenu() { static const u64 tag_sub0[6] = { MULTI_CHAR('menu_t0s'), MULTI_CHAR('menu_t1s'), MULTI_CHAR('menu_t2s'), MULTI_CHAR('menu_t3s'), MULTI_CHAR('menu_t4s'), MULTI_CHAR('menu_t5s'), diff --git a/src/d/d_menu_map_common.cpp b/src/d/d_menu_map_common.cpp index c18f5825ff..c4c1485826 100644 --- a/src/d/d_menu_map_common.cpp +++ b/src/d/d_menu_map_common.cpp @@ -6,6 +6,10 @@ #include "d/d_select_cursor.h" #include "global.h" +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif + struct map_icon_size_t { f32 size_x; f32 size_y; @@ -584,10 +588,16 @@ void dMenuMapCommon_c::setBlendRatio(u8 i_iconNo, f32 param_2, f32 param_3) { } void dMenuMapCommon_c::blinkMove(s16 param_1) { +#if TARGET_PC + f32 phase = mBlinkTimer + 15.0f; + dusk::vdt::advance_looping_frame(phase, 1.0f, std::max(0, (int)param_1) + 15.0f); + mBlinkTimer = phase - 15.0f; +#else mBlinkTimer++; if (mBlinkTimer >= param_1) { S16_SUB(mBlinkTimer, param_1 + 15); } +#endif if (mBlinkTimer < 0) { mBlinkAlpha = 1.0f; @@ -610,10 +620,20 @@ void dMenuMapCommon_c::moveLightDropAnime() { u8 flash_start_alpha = g_fmapHIO.mMapIconHIO.mLightDropFlashStartAlphaOut[bVar6]; u8 flash_end_alpha = g_fmapHIO.mMapIconHIO.mLightDropFlashEndAlphaOut[bVar6]; +#if TARGET_PC + if (flash_frame_num <= 0) { + mLightDropFlashTimer = 0.0f; + _c80 = flash_start_alpha; + _c7c = flash_start_scale; + return; + } + dusk::vdt::advance_looping_frame(mLightDropFlashTimer, 1.0f, flash_frame_num); +#else mLightDropFlashTimer++; if (mLightDropFlashTimer >= flash_frame_num) { mLightDropFlashTimer -= flash_frame_num; } +#endif f32 fVar7; if (mLightDropFlashTimer <= flash_frame_num / 2.0f) { diff --git a/src/d/d_menu_option.cpp b/src/d/d_menu_option.cpp index 9d6f0816e2..fa10149e08 100644 --- a/src/d/d_menu_option.cpp +++ b/src/d/d_menu_option.cpp @@ -25,11 +25,13 @@ #include #include "JSystem/JAudio2/JASDriverIF.h" -#include "dusk/version.hpp" #if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/ui/touch_controls.hpp" +#include "dusk/version.hpp" #endif typedef void (dMenu_Option_c::*initFunc)(); @@ -568,7 +570,7 @@ skip: } (this->*process[OPTION_PROC(field_0x3ef)])(); - mpSelectScreen->animation(); + IF_NOT_DUSK(mpSelectScreen->animation()); if (oldValue != field_0x3ef) { (this->*init[OPTION_PROC(field_0x3ef)])(); } @@ -578,6 +580,11 @@ skip: void dMenu_Option_c::_draw() { if (mpArchive != NULL) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + presentAnims(); + } +#endif J2DGrafContext* ctx = dComIfGp_getCurrentGrafPort(); #if (PLATFORM_WII || PLATFORM_SHIELD) if (mpCalibration != NULL && field_0x3f4 != 5 && field_0x3f4 != 0 && field_0x3f4 != 4) { @@ -667,6 +674,10 @@ bool dMenu_Option_c::checkRightTrigger() { } void dMenu_Option_c::setAnimation() { +#if TARGET_PC + dusk::vdt::present_looping(field_0x3c0, field_0x2c, 2.0f); + dusk::vdt::present_looping(field_0x3c4, field_0x30, 2.0f); +#else s16 frameMax; field_0x3c0 += 2; @@ -682,6 +693,7 @@ void dMenu_Option_c::setAnimation() { field_0x3c4 = field_0x3c4 - frameMax; } field_0x30->setFrame(field_0x3c4); +#endif } bool dMenu_Option_c::_open() { @@ -876,7 +888,7 @@ void dMenu_Option_c::vib_init() { void dMenu_Option_c::vib_move() { bool upTrigger = mpStick->checkUpTrigger(); - bool downTrigger = mpStick->checkDownTrigger(); + IF_NOT_DUSK(bool downTrigger =) mpStick->checkDownTrigger(); bool leftTrigger = checkLeftTrigger(); bool rightTrigger = checkRightTrigger(); @@ -891,10 +903,14 @@ void dMenu_Option_c::vib_move() { field_0x3ef = PROC_ATTEN_e; #endif Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); - } else if (downTrigger) { + } +#ifndef TARGET_PC + else if (downTrigger) { field_0x3ef = OPTION_SELECT(PROC_SOUND_e); Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); - } else if (leftTrigger) { + } +#endif + else if (leftTrigger) { if (isRumbleSupported()) { if (field_0x3ea == 0) { field_0x3ea = 1; @@ -1010,6 +1026,7 @@ void dMenu_Option_c::change_init() { } void dMenu_Option_c::change_move() { +#if !TARGET_PC f32 x = 0.0f; if (field_0x3da > 0) { @@ -1018,15 +1035,16 @@ void dMenu_Option_c::change_move() { field_0x3da++; } u8 index; +#endif switch (OPTION_PROC(field_0x3f5)) { case PROC_ATTEN_e: - index = field_0x3f5; + IF_NOT_DUSK(index = field_0x3f5); if (field_0x3da == 0) { setAttenString(); } break; case PROC_RUBY_e: - index = field_0x3f5; + IF_NOT_DUSK(index = field_0x3f5); if (field_0x3da == 0) { IF_DUSK_BLOCK(dusk::version::isRegionJpn()) setRubyString(); @@ -1034,18 +1052,19 @@ void dMenu_Option_c::change_move() { } break; case PROC_VIB_e: - index = field_0x3f5; + IF_NOT_DUSK(index = field_0x3f5); if (field_0x3da == 0) { setVibString(); } break; case PROC_SOUND_e: - index = field_0x3f5; + IF_NOT_DUSK(index = field_0x3f5); if (field_0x3da == 0) { setSoundString(); } break; } +#if !TARGET_PC if (field_0x3da > 0) { f32 initPosX = (5 - field_0x3da) / 5.0f; if (mpMenuText[index][3] != NULL) { @@ -1065,6 +1084,7 @@ void dMenu_Option_c::change_move() { mpMenuText[index][i]->paneTrans(x + field_0x3b4, 0.0f); } } +#endif if (field_0x3da == 0) { for (int i = 0; i < 6; i++) { for (int j = 2; j < 6; j++) { @@ -1098,18 +1118,20 @@ void dMenu_Option_c::confirm_open_move() { u32 status = mpWarning->getStatus(); bool yesNoMenuMove = yesnoMenuMoveAnm(); +#if !TARGET_PC if (field_0x374 != 1.0f) { cLib_addCalc2(&field_0x374, 1.0f, 0.4f, 0.5f); if (fabsf(field_0x374 - 1.0f) < 0.1f) { field_0x374 = 1.0f; } } +#endif if (status == 1 && yesNoMenuMove == 1 && field_0x374 == 1.0f) { yesnoCursorShow(); field_0x3ef = OPTION_SELECT(PROC_CONFIRM_MOVE_MOVE_e); } mpWarning->_move(); - setAnimation(); + IF_NOT_DUSK(setAnimation()); } void dMenu_Option_c::confirm_move_init() { @@ -1139,13 +1161,11 @@ void dMenu_Option_c::confirm_move_move() { field_0x3ef = OPTION_SELECT(PROC_CONFIRM_CLOSE_MOVE_e); dMeter2Info_set2DVibrationM(); mpWarning->_move(); - setAnimation(); return; } yesnoSelectAnmSet(); field_0x3ef = OPTION_SELECT(PROC_CONFIRM_SELECT_MOVE_e); mpWarning->_move(); - setAnimation(); return; } if (clicked) { @@ -1153,7 +1173,6 @@ void dMenu_Option_c::confirm_move_move() { field_0x3ef = OPTION_SELECT(PROC_CONFIRM_CLOSE_MOVE_e); dMeter2Info_set2DVibrationM(); mpWarning->_move(); - setAnimation(); return; } } @@ -1188,7 +1207,7 @@ void dMenu_Option_c::confirm_move_move() { } } mpWarning->_move(); - setAnimation(); + IF_NOT_DUSK(setAnimation()); } void dMenu_Option_c::confirm_select_init() { @@ -1221,14 +1240,13 @@ void dMenu_Option_c::confirm_select_move() { field_0x3ef = OPTION_SELECT(PROC_CONFIRM_CLOSE_MOVE_e); dMeter2Info_set2DVibrationM(); mpWarning->_move(); - setAnimation(); return; } #endif field_0x3ef = OPTION_SELECT(PROC_CONFIRM_MOVE_MOVE_e); } mpWarning->_move(); - setAnimation(); + IF_NOT_DUSK(setAnimation()); } void dMenu_Option_c::confirm_close_init() { @@ -1243,12 +1261,14 @@ void dMenu_Option_c::confirm_close_init() { void dMenu_Option_c::confirm_close_move() { u32 status = mpWarning->getStatus(); yesnoMenuMoveAnm(); +#if !TARGET_PC if (field_0x374 != 0.0f) { cLib_addCalc2(&field_0x374, 0.0f, 0.4f, 0.5); if (fabsf(field_0x374) < 0.1f) { field_0x374 = 0.0f; } } +#endif if (status == 1 && status == 1 && field_0x374 == 0.0f) { if (field_0x3f7 == 1) { if (field_0x3f9 == 1) { @@ -1286,7 +1306,7 @@ void dMenu_Option_c::confirm_close_move() { } } mpWarning->_move(); - setAnimation(); + IF_NOT_DUSK(setAnimation()); } void dMenu_Option_c::tv_open1_move() { @@ -1369,8 +1389,7 @@ void dMenu_Option_c::calibration_close2_move() { void dMenu_Option_c::menuVisible() { for (int i = 0; i < 6; i++) { - if (i < OPTION_SELECT(PROC_CHANGE_MOVE_e)) - { + if (i < OPTION_SELECT(DUSK_IF_ELSE(PROC_SOUND_e, PROC_CHANGE_MOVE_e))) { menuShow(i); } else { menuHide(i); @@ -2478,7 +2497,7 @@ bool dMenu_Option_c::isRumbleSupported() { #if TARGET_PC bool dMenu_Option_c::pointerConfirmSelect() { dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options); - for (u8 i = 0; i < (dusk::version::isRegionJpn() ? 4 : 3); ++i) { + for (u8 i = 0; i < (dusk::version::isRegionJpn() ? 3 : 2); ++i) { if (dusk::menu_pointer::hit_pane(mpMenuPane[i], 8.0f)) { dusk::menu_pointer::set_hover_target(i); return false; @@ -2502,7 +2521,7 @@ bool dMenu_Option_c::pointerConfirmSelect() { bool dMenu_Option_c::dpdMenuMove() { #if TARGET_PC dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options); - for (u8 i = 0; i < (dusk::version::isRegionJpn() ? 4 : 3); ++i) { + for (u8 i = 0; i < (dusk::version::isRegionJpn() ? 3 : 2); ++i) { if (!dusk::menu_pointer::hit_pane(mpMenuPane[i], 8.0f)) { continue; } @@ -2582,20 +2601,6 @@ bool dMenu_Option_c::dpdMenuMove() { -1.0f, 0); } return true; - case PROC_SOUND_e: - if (field_0x3e9 == 0) { - field_0x3e9 = 2; - } else { - field_0x3e9--; - } - field_0x3da = 5; - mDoAud_setOutputMode(dMo_soundMode[field_0x3e9]); - setSoundMode(dMo_soundMode[field_0x3e9]); - field_0x3ef = OPTION_SELECT(PROC_CHANGE_MOVE_e); - field_0x3f5 = OPTION_SELECT(PROC_SOUND_e); - Z2GetAudioMgr()->seStart(Z2SE_SY_OPTION_SWITCH, NULL, 0, 0, 1.0f, 1.0f, -1.0f, - -1.0f, 0); - return true; } } #endif @@ -2671,6 +2676,7 @@ void dMenu_Option_c::yesnoMenuMoveAnmInitSet(int param_0, int param_1) { bool dMenu_Option_c::yesnoMenuMoveAnm() { bool ret; if (field_0x3c8[2] != field_0x3c8[3]) { +#if !TARGET_PC if (field_0x3c8[2] < field_0x3c8[3]) { field_0x3c8[2] += 2; if (field_0x3c8[2] > field_0x3c8[3]) { @@ -2685,6 +2691,7 @@ bool dMenu_Option_c::yesnoMenuMoveAnm() { field_0x20->setFrame(field_0x3c8[2]); mpYesNoSelBase_c[0]->getPanePtr()->animationTransform(); mpYesNoSelBase_c[1]->getPanePtr()->animationTransform(); +#endif ret = 0; } if (field_0x3c8[2] == field_0x3c8[3]) { @@ -2709,6 +2716,101 @@ static s32 OptYnSelStartFrameTbl[2] = {1251, 1236}; static s32 OptYnSelEndFrameTbl[2] = {1236, 1251}; +#if TARGET_PC +void dMenu_Option_c::presentAnims() { + if (field_0x3da != 0.0f) { + dusk::vdt::advance_toward_frame(field_0x3da, 0.0f, 1.0f); + const u8 index = field_0x3f5; + if (field_0x3da == 0.0f) { + switch (OPTION_PROC(field_0x3f5)) { + case PROC_ATTEN_e: + setAttenString(); + break; + case PROC_RUBY_e: + IF_DUSK_BLOCK(dusk::version::isRegionJpn()) + setRubyString(); + IF_DUSK_BLOCK_END + break; + case PROC_VIB_e: + setVibString(); + break; + case PROC_SOUND_e: + setSoundString(); + break; + } + for (int i = 0; i < 6; i++) { + for (int j = 2; j < 6; j++) { + if (mpMenuText[i][j] != NULL) { + mpMenuText[i][j]->hide(); + } + } + } + for (int i = 0; i < 2; i++) { + if (mpMenuText[index][i] != NULL) { + mpMenuText[index][i]->show(); + mpMenuText[index][i]->paneTrans(field_0x3b4, 0.0f); + } + } + } else { + f32 x = 0.0f; + if (field_0x3da > 0) { + f32 initPosX = (5 - field_0x3da) / 5.0f; + if (mpMenuText[index][3] != NULL) { + x = mpMenuText[index][3]->getInitPosX() - mpMenuText[index][0]->getInitPosX(); + } + x *= initPosX; + } else if (field_0x3da < 0) { + f32 initPosX = (field_0x3da + 5) / 5.0f; + if (mpMenuText[index][5] != NULL) { + x = mpMenuText[index][5]->getInitPosX() - mpMenuText[index][0]->getInitPosX(); + } + x *= initPosX; + } + for (int i = 0; i < 6; i++) { + if (mpMenuText[index][i] != NULL) { + mpMenuText[index][i]->show(); + mpMenuText[index][i]->paneTrans(x + field_0x3b4, 0.0f); + } + } + } + } + setAnimation(); + if (OPTION_PROC(field_0x3ef) == PROC_CONFIRM_OPEN_MOVE_e) { + dusk::vdt::present_addCalc2(&field_0x374, 1.0f, 0.4f, 0.5f, 0.1f); + } else if (OPTION_PROC(field_0x3ef) == PROC_CONFIRM_CLOSE_MOVE_e) { + dusk::vdt::present_addCalc2(&field_0x374, 0.0f, 0.4f, 0.5f, 0.1f); + } + if (mpSelectScreen != NULL) { + mpSelectScreen->animation(); + } + presentLayoutAnims(); + for (int i = 0; i < 2; ++i) { + mpYesNoTxt_c[i]->presentAnime(); + mpYesNoCurWaku_c[i]->presentAnime(); + mpYesNoCurWakuG0_c[i]->presentAnime(); + mpYesNoCurWakuG1_c[i]->presentAnime(); + } +} + +void dMenu_Option_c::presentLayoutAnims() { + if (mpWarning != NULL) { + mpWarning->presentAnims(); + } + + if (field_0x3c8[2] != field_0x3c8[3]) { + dusk::vdt::present_shared(field_0x3c8[2], field_0x3c8[3], field_0x20, + {mpYesNoSelBase_c[0] != NULL ? mpYesNoSelBase_c[0]->getPanePtr() : NULL, + mpYesNoSelBase_c[1] != NULL ? mpYesNoSelBase_c[1]->getPanePtr() : NULL}); + } + + for (int i = 0; i < 2; i++) { + J2DPane* pane = mpYesNoSelBase_c[i] != NULL ? mpYesNoSelBase_c[i]->getPanePtr() : NULL; + dusk::vdt::present_selected(field_0x3c8[i], pane, field_0x24, (f32)OptYnSelStartFrameTbl[i], + field_0x28, (f32)OptYnSelEndFrameTbl[i]); + } +} +#endif + u8 dMenu_Option_c::yesnoSelectMoveAnm() { u8 ret = 0; bool bVar1; @@ -2716,6 +2818,7 @@ u8 dMenu_Option_c::yesnoSelectMoveAnm() { if (field_0x3fa != 0xff) { if (field_0x3c8[field_0x3fa] != OptYnSelStartFrameTbl[field_0x3fa]) { +#if !TARGET_PC if (field_0x3c8[field_0x3fa] < OptYnSelStartFrameTbl[field_0x3fa]) { field_0x3c8[field_0x3fa] += 2; if (field_0x3c8[field_0x3fa] > OptYnSelStartFrameTbl[field_0x3fa]) { @@ -2729,6 +2832,7 @@ u8 dMenu_Option_c::yesnoSelectMoveAnm() { } field_0x24->setFrame(field_0x3c8[field_0x3fa]); mpYesNoSelBase_c[field_0x3fa]->getPanePtr()->animationTransform(); +#endif } if (field_0x3c8[field_0x3fa] == OptYnSelStartFrameTbl[field_0x3fa]) { bVar1 = true; @@ -2740,6 +2844,7 @@ u8 dMenu_Option_c::yesnoSelectMoveAnm() { } if (field_0x3f9 != 0xff) { if (field_0x3c8[field_0x3f9] != OptYnSelEndFrameTbl[field_0x3f9]) { +#if !TARGET_PC if (field_0x3c8[field_0x3f9] < OptYnSelEndFrameTbl[field_0x3f9]) { field_0x3c8[field_0x3f9] += 2; if (field_0x3c8[field_0x3f9] > OptYnSelEndFrameTbl[field_0x3f9]) { @@ -2753,6 +2858,7 @@ u8 dMenu_Option_c::yesnoSelectMoveAnm() { } field_0x28->setFrame(field_0x3c8[field_0x3f9]); mpYesNoSelBase_c[field_0x3f9]->getPanePtr()->animationTransform(); +#endif } if (field_0x3c8[field_0x3f9] == OptYnSelEndFrameTbl[field_0x3f9]) { bVar2 = true; diff --git a/src/d/d_menu_ring.cpp b/src/d/d_menu_ring.cpp index ed79d40524..6adb3945af 100644 --- a/src/d/d_menu_ring.cpp +++ b/src/d/d_menu_ring.cpp @@ -29,14 +29,13 @@ #include -#include "dusk/version.hpp" - #if TARGET_PC -#include "dusk/frame_interpolation.h" -#include "dusk/game_clock.h" +#include "dusk/interp/menus.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/settings.h" #include "dusk/ui/touch_controls.hpp" +#include "dusk/version.hpp" #endif typedef void (dMenu_Ring_c::*initFunc)(); @@ -102,7 +101,7 @@ dMenu_Ring_c::dMenu_Ring_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i mCenterPosX = 0.0f; mCenterPosY = -FB_HEIGHT_BASE; } - if (mRingOrigin == 3) { + IF_DUSK(else) if (mRingOrigin == 3) { mCenterPosX = FB_WIDTH_BASE; mCenterPosY = 0.0f; } else if (mRingOrigin == 1) { @@ -191,23 +190,9 @@ dMenu_Ring_c::dMenu_Ring_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i field_0x682 = 0xc000; break; } -#if TARGET_PC - mCursorInterpPrevX = 0.0f; - mCursorInterpPrevY = 0.0f; - mCursorInterpCurrX = 0.0f; - mCursorInterpCurrY = 0.0f; - mCursorInterpPrevAngle = 0; - mCursorInterpCurrAngle = 0; - mCursorInterpPrevAngular = false; - mCursorInterpCurrAngular = false; - mCursorInterpInit = false; - mPointerTouchPressHoveredCurrent = false; -#endif + IF_DUSK(mPointerTouchPressHoveredCurrent = false); for (int i = 0; i < 4; i++) { field_0x674[i] = 0; -#if TARGET_PC - mSelectItemSlideElapsed[i] = 0.0f; -#endif field_0x518[i] = 0.0f; field_0x528[i] = 0.0f; field_0x538[i] = 0.0f; @@ -568,6 +553,7 @@ dMenu_Ring_c::dMenu_Ring_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i } dMenu_Ring_c::~dMenu_Ring_c() { + IF_DUSK(dusk::interp::erase_owned_samples(this)); mpHeap->getTotalFreeSize(); dMeter2Info_setItemExplainWindowStatus(0); for (int i = 0; i < 4; i++) { @@ -702,9 +688,32 @@ void dMenu_Ring_c::_move() { mRingCursorScale = g_ringHIO.mCursorScale; mpDrawCursor->setScale(g_ringHIO.mCursorScale); } + IF_DUSK(captureRenderState()); } +#if TARGET_PC +namespace { +struct RingSelectionSamples { + dusk::interp::Samples frames; +}; +} + +void dMenu_Ring_c::captureRenderState() { + dusk::interp::capture_menu_pose(this, {mCenterPosX, mCenterPosY, mAlphaRate}); + dusk::interp::get(this).frames.capture(4, [&](int i) { + return (f32)field_0x674[i]; + }); +} +#endif + void dMenu_Ring_c::_draw() { +#if TARGET_PC + const auto [mCenterPosX, mCenterPosY, mAlphaRate] = + dusk::interp::read_menu_pose(this, {this->mCenterPosX, this->mCenterPosY, this->mAlphaRate}); + if (mDrawFlag == 0) { + dusk::vdt::advance_looping_frame(field_0x684, 1.0f, g_ringHIO.mItemAlphaFlashDuration); + } +#endif J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); grafPort->setup2D(); if (mDrawFlag == 0) { @@ -757,71 +766,7 @@ void dMenu_Ring_c::_draw() { } else { drawSelectItem(); drawItem2(); -#if TARGET_PC - f32 simX = 0.0f; - f32 simY = 0.0f; - bool restoreSimPos = false; - if (dusk::frame_interp::is_enabled() && mAlphaRate >= 1.0f) { - simX = mpDrawCursor->getPositionX(); - simY = mpDrawCursor->getPositionY(); - - const bool isAngular = (mStatus == STATUS_MOVE) && !mDirectSelectActive; - - if (dusk::frame_interp::get_ui_tick_pending()) { - mCursorInterpPrevX = mCursorInterpCurrX; - mCursorInterpPrevY = mCursorInterpCurrY; - mCursorInterpPrevAngle = mCursorInterpCurrAngle; - mCursorInterpPrevAngular = mCursorInterpCurrAngular; - - mCursorInterpCurrX = simX; - mCursorInterpCurrY = simY; - mCursorInterpCurrAngle = field_0x66e; - mCursorInterpCurrAngular = isAngular; - - // reset prev = curr for first render pass or - // when angle modes prev/curr differ - // to prevent arrival jitter - if (!mCursorInterpInit || - mCursorInterpPrevAngular != mCursorInterpCurrAngular) { - mCursorInterpPrevX = mCursorInterpCurrX; - mCursorInterpPrevY = mCursorInterpCurrY; - mCursorInterpPrevAngle = mCursorInterpCurrAngle; - mCursorInterpPrevAngular = mCursorInterpCurrAngular; - mCursorInterpInit = true; - } - } - if (mCursorInterpInit) { - const f32 step = dusk::frame_interp::get_interpolation_step(); - if (mCursorInterpPrevAngular && mCursorInterpCurrAngular) { - const s16 delta = mCursorInterpCurrAngle - mCursorInterpPrevAngle; - const s16 lerpedAngle = mCursorInterpPrevAngle + (s16)(delta * step); - - // yoinked from stick_move_proc() - const f32 x = g_ringHIO.mItemRingPosX + FB_WIDTH_BASE / 2 + - mRingRadiusH * cM_ssin(lerpedAngle); - const f32 y = g_ringHIO.mItemRingPosY + FB_HEIGHT_BASE / 2 + - mRingRadiusV * cM_scos(lerpedAngle); - mpDrawCursor->setPos(x, y); - } else { - mpDrawCursor->setPos( - mCursorInterpPrevX + (mCursorInterpCurrX - mCursorInterpPrevX) * step, - mCursorInterpPrevY + (mCursorInterpCurrY - mCursorInterpPrevY) * step - ); - } - restoreSimPos = true; - } - } else { - mCursorInterpInit = false; - } -#endif mpDrawCursor->draw(); -#if TARGET_PC - // prevents offsetting at destination on the next frame - // since stick_wait_proc doesn't call setPos and we clobbered mPositionX/Y - if (restoreSimPos) { - mpDrawCursor->setPos(simX, simY); - } -#endif mpItemExplain->trans(mCenterPosX, mCenterPosY); mpItemExplain->draw((J2DOrthoGraph*)grafPort); drawFlag0(); @@ -852,7 +797,7 @@ bool dMenu_Ring_c::isOpen() { mCenterPosX = (1.0f - mAlphaRate) * -FB_WIDTH_BASE; mCenterPosY = 0.0f; } - if (mOpenCloseFrames >= g_ringHIO.mOpenFrames) { + if (mOpenCloseFrames >= g_ringHIO.mOpenFrames) { // Opening is finished, set to g_ringHIO.mCloseFrames as a // preparation for when the player closes the item wheel mOpenCloseFrames = g_ringHIO.mCloseFrames; @@ -869,6 +814,7 @@ bool dMenu_Ring_c::isOpen() { mpDrawCursor->setParam(1.0f, 1.0f, 0.1f, 0.6f, 0.5f); } + IF_DUSK(captureRenderState()); return opened; } @@ -932,6 +878,7 @@ bool dMenu_Ring_c::isClose() { mpDrawCursor->setParam(1.0f, 1.0f, 0.1f, 0.6f, 0.5f); } + IF_DUSK(captureRenderState()); return closed; } @@ -1224,9 +1171,6 @@ void dMenu_Ring_c::setJumpItem(bool i_useVibrationM) { field_0x6b8[0] != dComIfGs_getMixItemIndex(0)) { field_0x674[0] = 1; -#if TARGET_PC - mSelectItemSlideElapsed[0] = 0.0f; -#endif } } else if (field_0x6b3 == 1) { field_0x538[0] = g_ringHIO.mUnselectItemScale; @@ -1235,9 +1179,6 @@ void dMenu_Ring_c::setJumpItem(bool i_useVibrationM) { field_0x6b8[1] != dComIfGs_getMixItemIndex(1)) { field_0x674[1] = 1; -#if TARGET_PC - mSelectItemSlideElapsed[1] = 0.0f; -#endif } } if (field_0x674[0] == 1) { @@ -1461,10 +1402,15 @@ void dMenu_Ring_c::setMixItem() { } void dMenu_Ring_c::drawItem() { +#if TARGET_PC + const auto [mCenterPosX, mCenterPosY, mAlphaRate] = + dusk::interp::read_menu_pose(this, {this->mCenterPosX, this->mCenterPosY, this->mAlphaRate}); +#else field_0x684++; if (field_0x684 >= g_ringHIO.mItemAlphaFlashDuration) { field_0x684 = 0; } +#endif s32 halfFlashDuration = g_ringHIO.mItemAlphaFlashDuration / 2; f32 fVar16; if (field_0x684 < halfFlashDuration) { @@ -1518,6 +1464,10 @@ void dMenu_Ring_c::drawItem() { } void dMenu_Ring_c::drawItem2() { +#if TARGET_PC + const auto [mCenterPosX, mCenterPosY, mAlphaRate] = + dusk::interp::read_menu_pose(this, {this->mCenterPosX, this->mCenterPosY, this->mAlphaRate}); +#endif s32 idx = mCurrentSlot; if (mStatus == STATUS_WAIT || mStatus == STATUS_EXPLAIN || mStatus == STATUS_EXPLAIN_FORCE) { J2DDrawFrame(mItemSlotPosX[idx] - 24.0f + mCenterPosX, mItemSlotPosY[idx] - 24.0f + mCenterPosY, @@ -1805,18 +1755,25 @@ void dMenu_Ring_c::setSelectItem(int i_idx, u8 i_itemNo) { mpSelectItemTexBuf[i_idx][field_0x6be[i_idx]][0]->height / 48.0f * texScale; } +#if TARGET_PC +void dMenu_Ring_c::advanceSelectItem() { + mpDrawCursor->setPos(mpDrawCursor->getPositionX(), mpDrawCursor->getPositionY()); + for (int i = 0; i < 4; ++i) { + if (field_0x674[i] >= 10) { + setSelectItemForce(i); + } else if (field_0x674[i] != 0) { + ++field_0x674[i]; + } + } + captureRenderState(); +} +#endif + void dMenu_Ring_c::drawSelectItem() { + IF_DUSK(const auto mAlphaRate = dusk::interp::read_menu_pose(this, {mCenterPosX, mCenterPosY, this->mAlphaRate}).alpha); for (int i = 0; i < 4; i++) { if (field_0x674[i] != 0) { -#if TARGET_PC - mSelectItemSlideElapsed[i] += dusk::game_clock::consume_interval(this); - const f32 u = std::min(mSelectItemSlideElapsed[i] / (dusk::game_clock::kSimPeriod * 10.0f), 1.0f); - if (u >= 1.0f) { - setSelectItemForce(i); - } else { -#else - if (field_0x674[i] < 10) { -#endif + if (DUSK_IF_ELSE(true, field_0x674[i] < 10)) { #if TARGET_PC f32 initSizeX; f32 initSizeY; @@ -1853,7 +1810,9 @@ void dMenu_Ring_c::drawSelectItem() { #endif #if TARGET_PC - f32 fVar14 = 0.1f + 0.8f * u; + const auto& samples = dusk::interp::get(this).frames; + const f32 frame = field_0x674[i] == 1 ? 1.0f : samples.read(i, (f32)field_0x674[i]); + f32 fVar14 = std::clamp(frame, 1.0f, 9.0f) / 10.0f; #else f32 fVar14 = field_0x674[i] / 10.0f; #endif @@ -1892,9 +1851,6 @@ void dMenu_Ring_c::setSelectItemForce(int i_idx) { if (field_0x674[i_idx] != 0) { dComIfGs_setSelectItemIndex(i_idx, field_0x6b4[i_idx]); field_0x674[i_idx] = 0; -#if TARGET_PC - mSelectItemSlideElapsed[i_idx] = 0.0f; -#endif } } else if (field_0x674[i_idx] != 0) { for (int i = 0; i < 2; i++) { @@ -1902,9 +1858,6 @@ void dMenu_Ring_c::setSelectItemForce(int i_idx) { dComIfGs_setSelectItemIndex(i, field_0x6b4[i]); } field_0x674[i_idx] = 0; -#if TARGET_PC - mSelectItemSlideElapsed[i_idx] = 0.0f; -#endif } } @@ -2117,6 +2070,7 @@ void dMenu_Ring_c::setCombineBomb(int param_0) { } void dMenu_Ring_c::drawNumber(int i_itemNum, int i_itemMaxNum, f32 i_posX, f32 i_posY) { + IF_DUSK(const auto mAlphaRate = dusk::interp::read_menu_pose(this, {mCenterPosX, mCenterPosY, this->mAlphaRate}).alpha); if (i_itemNum > 100) { i_itemNum = 100; } diff --git a/src/d/d_menu_save.cpp b/src/d/d_menu_save.cpp index 48f3f953ea..82107388f3 100644 --- a/src/d/d_menu_save.cpp +++ b/src/d/d_menu_save.cpp @@ -14,7 +14,6 @@ #include "d/d_msg_scrn_explain.h" #include "d/d_msg_string.h" #include "d/d_select_cursor.h" -#include "dusk/version.hpp" #include "f_op/f_op_msg_mng.h" #include "m_Do/m_Do_MemCard.h" #include "m_Do/m_Do_MemCardRWmng.h" @@ -23,10 +22,12 @@ #include "m_Do/m_Do_graphic.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" -#include "dusk/settings.h" #include "dusk/mods/svc/save.hpp" +#include "dusk/settings.h" +#include "dusk/version.hpp" #endif static int SelStartFrameTbl[3] = { @@ -102,6 +103,8 @@ dMenu_save_c::dMenu_save_c() { } mpSelectMoveBase = NULL; + IF_DUSK(mYesNoSelAnmCol = 0); + for (int i = 0; i < 2; i++) { mpNoYes[i] = NULL; mNoYesBase[i] = NULL; @@ -809,9 +812,7 @@ void dMenu_save_c::_move() { } (this->*MenuSaveProc[mMenuProc])(); -#if !TARGET_PC - saveSelAnm(); -#endif + IF_NOT_DUSK(saveSelAnm()); if (mWarning != NULL) { mWarning->_move(); @@ -829,46 +830,47 @@ void dMenu_save_c::saveSelAnm() { void dMenu_save_c::selFileWakuAnm() { #if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - mFileWakuAnmFrame += 2; - if (mFileWakuAnmFrame >= mpFileWakuAnm->getFrameMax()) { - mFileWakuAnmFrame -= mpFileWakuAnm->getFrameMax(); - } + dusk::vdt::present_looping(mFileWakuAnmFrame, mpFileWakuAnm, 2.0f); + dusk::vdt::present_looping(mFileWakuRotAnmFrame, mpFileWakuRotAnm, 2.0f); +#else + mFileWakuAnmFrame += 2; + if (mFileWakuAnmFrame >= mpFileWakuAnm->getFrameMax()) { + mFileWakuAnmFrame -= mpFileWakuAnm->getFrameMax(); + } - mFileWakuRotAnmFrame += 2; - if (mFileWakuRotAnmFrame >= mpFileWakuRotAnm->getFrameMax()) { - mFileWakuRotAnmFrame -= mpFileWakuRotAnm->getFrameMax(); - } + mFileWakuRotAnmFrame += 2; + if (mFileWakuRotAnmFrame >= mpFileWakuRotAnm->getFrameMax()) { + mFileWakuRotAnmFrame -= mpFileWakuRotAnm->getFrameMax(); } mpFileWakuAnm->setFrame(mFileWakuAnmFrame); mpFileWakuRotAnm->setFrame(mFileWakuRotAnmFrame); +#endif } void dMenu_save_c::bookIconAnm() { #if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - field_0x154 += 2; - if (field_0x154 >= field_0x150->getFrameMax()) { - field_0x154 -= field_0x150->getFrameMax(); - } + dusk::vdt::present_looping(field_0x154, field_0x150, 2.0f); + dusk::vdt::present_looping(field_0x15c, field_0x158, 2.0f); + dusk::vdt::present_looping(field_0x164, field_0x160, 2.0f); +#else + field_0x154 += 2; + if (field_0x154 >= field_0x150->getFrameMax()) { + field_0x154 -= field_0x150->getFrameMax(); + } - field_0x15c += 2; - if (field_0x15c >= field_0x158->getFrameMax()) { - field_0x15c -= field_0x158->getFrameMax(); - } + field_0x15c += 2; + if (field_0x15c >= field_0x158->getFrameMax()) { + field_0x15c -= field_0x158->getFrameMax(); + } - field_0x164 += 2; - if (field_0x164 >= field_0x160->getFrameMax()) { - field_0x164 -= field_0x160->getFrameMax(); - } + field_0x164 += 2; + if (field_0x164 >= field_0x160->getFrameMax()) { + field_0x164 -= field_0x160->getFrameMax(); } field_0x150->setFrame(field_0x154); field_0x158->setFrame(field_0x15c); field_0x160->setFrame(field_0x164); +#endif } void dMenu_save_c::memCardWatch() { @@ -1452,6 +1454,11 @@ void dMenu_save_c::memCardDataSaveWait() { mCmdState = g_mDoMemCd_control.SaveSync(); if (mCmdState != 0) { +#if TARGET_PC + if (mCmdState == 1) { + dusk::mods::svc::save_slot_written(mSelectedFile); + } +#endif printf("save cmdState %d\n", mCmdState); mMenuProc = PROC_MEMCARD_DATA_SAVE_WAIT2; } @@ -1469,10 +1476,6 @@ void dMenu_save_c::memCardDataSaveWait2() { dComIfGs_setDataNum(mSelectedFile); dComIfGs_setNoFile(0); -#if TARGET_PC - dusk::mods::svc::save_slot_written(mSelectedFile, mSaveBuffer + mSelectedFile * QUEST_LOG_SIZE); -#endif - if (mUseType == TYPE_WHITE_EVENT || mUseType == TYPE_BLACK_EVENT) { headerTxtSet(0x530); // Saved. mWarning->closeInit(); @@ -1840,6 +1843,7 @@ void dMenu_save_c::openSaveSelect3() { var_r29 = ketteiTxtDispAnm(); if (field_0x74[mSelectedFile] != SelEndFrameTbl[mSelectedFile]) { +#if !TARGET_PC field_0x74[mSelectedFile] += 2; if (field_0x74[mSelectedFile] > SelEndFrameTbl[mSelectedFile]) { @@ -1848,6 +1852,7 @@ void dMenu_save_c::openSaveSelect3() { field_0x40->setFrame(field_0x74[mSelectedFile]); mpSelData[mSelectedFile]->getPanePtr()->animationTransform(); +#endif var_r28 = false; } } @@ -2055,6 +2060,7 @@ void dMenu_save_c::saveSelectMoveAnime() { selWakuAnmComplete = selectWakuAlpahAnm(mLastSelFile); if (field_0x74[mLastSelFile] != SelStartFrameTbl[mLastSelFile]) { +#if !TARGET_PC field_0x74[mLastSelFile] -= 2; if (field_0x74[mLastSelFile] < SelStartFrameTbl[mLastSelFile]) { @@ -2063,6 +2069,7 @@ void dMenu_save_c::saveSelectMoveAnime() { field_0x44->setFrame(field_0x74[mLastSelFile]); mpSelData[mLastSelFile]->getPanePtr()->animationTransform(); +#endif var_r29 = false; } } @@ -2073,6 +2080,7 @@ void dMenu_save_c::saveSelectMoveAnime() { var_r28 = mpBookWaku[mSelectedFile]->alphaAnime(g_msHIO.mEffectDispFrames, 0, 0xFF, 1); if (field_0x74[mSelectedFile] != SelEndFrameTbl[mSelectedFile]) { +#if !TARGET_PC field_0x74[mSelectedFile] += 2; if (field_0x74[mSelectedFile] > SelEndFrameTbl[mSelectedFile]) { @@ -2081,6 +2089,7 @@ void dMenu_save_c::saveSelectMoveAnime() { field_0x40->setFrame(field_0x74[mSelectedFile]); mpSelData[mSelectedFile]->getPanePtr()->animationTransform(); +#endif var_r27 = false; } } @@ -2195,6 +2204,8 @@ void dMenu_save_c::saveMoveDisp2() { } void dMenu_save_c::yesnoSelectAnmSet(u8 param_0) { + IF_DUSK(mYesNoSelAnmCol = param_0); + if (mYesNoPrevCursor != 0xFF) { yesnoWakuAlpahAnmInit(mYesNoPrevCursor, 255, 0, g_msHIO.mSelectFrames); mpNoYes[mYesNoPrevCursor]->getPanePtr()->setAnimation(field_0x40); @@ -2532,6 +2543,7 @@ void dMenu_save_c::selectDataBaseMoveAnmInitSet(int param_0, int param_1) { bool dMenu_save_c::selectDataBaseMoveAnm() { if (mDataBaseMoveAnmFrame != mDataBaseMoveFrameMax) { +#if !TARGET_PC if (mDataBaseMoveAnmFrame < mDataBaseMoveFrameMax) { mDataBaseMoveAnmFrame += 2; @@ -2548,6 +2560,7 @@ bool dMenu_save_c::selectDataBaseMoveAnm() { field_0x40->setFrame(mDataBaseMoveAnmFrame); mpSelectMoveBase->getPanePtr()->animationTransform(); +#endif return false; } else { #if TARGET_PC @@ -2611,6 +2624,7 @@ void dMenu_save_c::selectDataMoveAnmInitSet(int param_0, int param_1) { bool dMenu_save_c::selectDataMoveAnm() { if (field_0x74[mSelectedFile] != field_0x80) { +#if !TARGET_PC if (field_0x74[mSelectedFile] < field_0x80) { field_0x74[mSelectedFile] += 2; @@ -2629,6 +2643,7 @@ bool dMenu_save_c::selectDataMoveAnm() { for (int i = 0; i < 3; i++) { mpSelData[i]->getPanePtr()->animationTransform(); } +#endif return false; } else { @@ -2666,6 +2681,7 @@ void dMenu_save_c::yesnoMenuMoveAnmInitSet(int anmFrame, int frameMax, u8 param_ bool dMenu_save_c::yesnoMenuMoveAnm() { if (mYesNoMoveAnmFrame != mYesNoMoveAnmMax) { +#if !TARGET_PC if (mYesNoMoveAnmFrame < mYesNoMoveAnmMax) { mYesNoMoveAnmFrame += 2; @@ -2683,6 +2699,7 @@ bool dMenu_save_c::yesnoMenuMoveAnm() { field_0x48->setFrame(mYesNoMoveAnmFrame); mpNoYes[0]->getPanePtr()->animationTransform(); mpNoYes[1]->getPanePtr()->animationTransform(); +#endif return false; } else { mpNoYes[0]->getPanePtr()->setAnimation((J2DAnmTransformKey*)NULL); @@ -2717,6 +2734,7 @@ bool dMenu_save_c::yesnoSelectMoveAnm(u8 param_0) { bool var_r30 = true; if (mYesNoPrevCursor != 0xFF) { if (field_0x8c[mYesNoPrevCursor] != YnSelStartFrameTbl[mYesNoPrevCursor][param_0]) { +#if !TARGET_PC if (field_0x8c[mYesNoPrevCursor] < YnSelStartFrameTbl[mYesNoPrevCursor][param_0]) { field_0x8c[mYesNoPrevCursor] += 2; @@ -2733,6 +2751,7 @@ bool dMenu_save_c::yesnoSelectMoveAnm(u8 param_0) { field_0x40->setFrame(field_0x8c[mYesNoPrevCursor]); mpNoYes[mYesNoPrevCursor]->getPanePtr()->animationTransform(); +#endif var_r30 = false; } } @@ -2740,6 +2759,7 @@ bool dMenu_save_c::yesnoSelectMoveAnm(u8 param_0) { bool var_r7 = true; if (mYesNoCursor != 0xFF) { if (field_0x8c[mYesNoCursor] != YnSelEndFrameTbl[mYesNoCursor][param_0]) { +#if !TARGET_PC if (field_0x8c[mYesNoCursor] < YnSelEndFrameTbl[mYesNoCursor][param_0]) { field_0x8c[mYesNoCursor] += 2; @@ -2756,6 +2776,7 @@ bool dMenu_save_c::yesnoSelectMoveAnm(u8 param_0) { field_0x44->setFrame(field_0x8c[mYesNoCursor]); mpNoYes[mYesNoCursor]->getPanePtr()->animationTransform(); +#endif var_r7 = false; } } @@ -2802,6 +2823,7 @@ void dMenu_save_c::errorMoveAnmInitSet(int param_0, int param_1) { bool dMenu_save_c::errorMoveAnm() { if (field_0xb8 != field_0xbc) { +#if !TARGET_PC if (field_0xb8 < field_0xbc) { field_0xb8 += 2; @@ -2818,6 +2840,7 @@ bool dMenu_save_c::errorMoveAnm() { field_0x4c->setFrame(field_0xb8); field_0xb4->animationTransform(); +#endif return 0; } else { field_0x4c->setFrame(field_0xbc); @@ -3076,22 +3099,85 @@ void dMenu_save_c::menuSaveWide() { } #endif } + +void dMenu_save_c::presentLayoutAnims() { + if (mWarning != NULL) { + mWarning->presentAnims(); + } + + dusk::vdt::present_toward(mDataBaseMoveAnmFrame, (f32)mDataBaseMoveFrameMax, field_0x40, mpSelectMoveBase != NULL ? mpSelectMoveBase->getPanePtr() : NULL); + + bool sharedSel = true; + for (int i = 0; i < 3; i++) { + J2DPane* pane = mpSelData[i] != NULL ? mpSelData[i]->getPanePtr() : NULL; + if (pane == NULL || pane->mTransform != field_0x40) { + sharedSel = false; + break; + } + } + if (sharedSel && mSelectedFile != 0xFF) { + dusk::vdt::present_toward(field_0x74[mSelectedFile], (f32)field_0x80, field_0x40); + for (int i = 0; i < 3; i++) { + mpSelData[i]->getPanePtr()->animationTransform(); + } + } else { + for (int i = 0; i < 3; i++) { + J2DPane* pane = mpSelData[i] != NULL ? mpSelData[i]->getPanePtr() : NULL; + dusk::vdt::present_selected(field_0x74[i], pane, field_0x40, (f32)SelEndFrameTbl[i], + field_0x44, (f32)SelStartFrameTbl[i]); + } + } + + if (mYesNoMoveAnmFrame != mYesNoMoveAnmMax) { + dusk::vdt::present_shared(mYesNoMoveAnmFrame, (f32)mYesNoMoveAnmMax, field_0x48, + {mpNoYes[0] != NULL ? mpNoYes[0]->getPanePtr() : NULL, + mpNoYes[1] != NULL ? mpNoYes[1]->getPanePtr() : NULL}); + } + + for (int i = 0; i < 2; i++) { + J2DPane* pane = mpNoYes[i] != NULL ? mpNoYes[i]->getPanePtr() : NULL; + dusk::vdt::present_selected(field_0x8c[i], pane, field_0x40, (f32)YnSelStartFrameTbl[i][mYesNoSelAnmCol], + field_0x44, (f32)YnSelEndFrameTbl[i][mYesNoSelAnmCol]); + } + + dusk::vdt::present_toward(field_0xb8, (f32)field_0xbc, field_0x4c, field_0xb4); + + for (int i = 0; i < 3; ++i) { + mpBookWaku[i]->presentAnime(); + mpSelWakuMoyo[i]->presentAnime(); + mpSelWakuGold[i]->presentAnime(); + mpSelWakuGold2[i]->presentAnime(); + } + for (int i = 0; i < 2; ++i) { + mNoYesBase[i]->presentAnime(); + mNoYesGold[i]->presentAnime(); + mNoYesGold2[i]->presentAnime(); + mpNoYesTxt[i]->presentAnime(); + mpErrTxtPane[i]->presentAlphaAnime(); + mpHeaderTxtPane[i]->presentAlphaAnime(); + } + mpBBtnIcon->presentAlphaAnime(); + mpABtnIcon->presentAlphaAnime(); + mpBackTxt->presentAlphaAnime(); + mpConfirmTxt->presentAlphaAnime(); +} #endif void dMenu_save_c::_draw2() { - if (field_0x21a1 == 0) { #if TARGET_PC + if (mDisplayMenu && dusk::game_clock::is_presentation_frame()) { saveSelAnm(); + presentLayoutAnims(); + menuSaveWide(); + } #endif + + if (field_0x21a1 == 0) { if (mpScrnExplain != NULL) { dComIfGd_set2DOpa(&mMenuSaveExplain); } if (mDisplayMenu) { - #if TARGET_PC - menuSaveWide(); - #endif - dComIfGd_set2DOpa(&mSaveSel); for (int i = 0; i < 3; i++) { diff --git a/src/d/d_menu_skill.cpp b/src/d/d_menu_skill.cpp index 90d4a3f92b..fddfa61b51 100644 --- a/src/d/d_menu_skill.cpp +++ b/src/d/d_menu_skill.cpp @@ -21,6 +21,7 @@ #include "dusk/version.hpp" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #endif @@ -146,6 +147,7 @@ void dMenu_Skill_c::_move() { void dMenu_Skill_c::_draw() { if (mpArchive != NULL) { + IF_DUSK(presentAnims()); J2DGrafContext* context = dComIfGp_getCurrentGrafPort(); u8 alpha = mpBlackTex->mAlpha; mpBlackTex->setAlpha(0xff); @@ -362,7 +364,7 @@ void dMenu_Skill_c::read_open_init() { void dMenu_Skill_c::read_open_move() { s16 openSkillDescFrame = g_drawHIO.mSkillListScreen.mOpenFrame[dMeter_drawSkillHIO_c::SKILL_DESC]; - mProcFrame++; + IF_NOT_DUSK(mProcFrame++); if (mProcFrame >= openSkillDescFrame) { mProcess = PROC_OPEN_MOVE; mpTextParent->setAlphaRate(1.0f); @@ -402,7 +404,7 @@ void dMenu_Skill_c::read_close_init() { void dMenu_Skill_c::read_close_move() { s16 closeSkillDescFrame = g_drawHIO.mSkillListScreen.mCloseFrame[dMeter_drawSkillHIO_c::SKILL_DESC]; - mProcFrame--; + IF_NOT_DUSK(mProcFrame--); if (mProcFrame <= 0) { mProcess = PROC_CLOSE_MOVE; mpTextParent->setAlphaRate(0.0f); @@ -414,6 +416,26 @@ void dMenu_Skill_c::read_close_move() { } } +#if TARGET_PC +void dMenu_Skill_c::presentAnims() { + const auto& hio = g_drawHIO.mSkillListScreen; + f32 duration; + if (mProcess == PROC_WAIT_MOVE) { + duration = hio.mOpenFrame[dMeter_drawSkillHIO_c::SKILL_DESC]; + dusk::vdt::advance_toward_frame(mProcFrame, duration, 1.0f); + } else if (mProcess == PROC_MOVE_MOVE) { + duration = hio.mCloseFrame[dMeter_drawSkillHIO_c::SKILL_DESC]; + dusk::vdt::advance_toward_frame(mProcFrame, 0.0f, 1.0f); + } else { + return; + } + const f32 alpha = duration <= 0.0f ? (mProcess == PROC_WAIT_MOVE ? 1.0f : 0.0f) : + dusk::vdt::clamped_fraction(mProcFrame, duration); + mpTextParent->setAlphaRate(alpha); + mpBlackTex->setAlpha(hio.mWindowBGalpha * alpha); +} +#endif + void dMenu_Skill_c::screenSetMenu() { static const u64 tag_sub0[7] = { MULTI_CHAR('menu_t0s'), MULTI_CHAR('menu_t1s'), MULTI_CHAR('menu_t2s'), MULTI_CHAR('menu_t3s'), MULTI_CHAR('menu_t4s'), MULTI_CHAR('menu_t5s'), MULTI_CHAR('menu_t51'), diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index eb67822899..aee078fb1c 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -26,8 +26,8 @@ #include "f_op/f_op_overlap_mng.h" #include "m_Do/m_Do_controller_pad.h" -#ifdef TARGET_PC -#include "dusk/frame_interpolation.h" +#if TARGET_PC +#include "dusk/interp/frame_interpolation.h" #endif class dDlst_MENU_CAPTURE_c : public dDlst_base_c { @@ -126,7 +126,7 @@ public: void setCaptureFlag() { mFlag = 1; #ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); + dusk::interp::request_presentation_sync(); #endif } @@ -1589,10 +1589,18 @@ int dMw_c::_create() { field_0x144 = 3; dMeter2Info_setWindowStatus(0); + + IF_DUSK(base.draw_interp_frame = true); + return cPhs_COMPLEATE_e; } int dMw_c::_execute() { +#if TARGET_PC + if (mpMenuRing != NULL) { + mpMenuRing->advanceSelectItem(); + } +#endif if (field_0x151 != 0) { field_0x151--; } diff --git a/src/d/d_meter2.cpp b/src/d/d_meter2.cpp index a90124ebb6..80dd19c7b1 100644 --- a/src/d/d_meter2.cpp +++ b/src/d/d_meter2.cpp @@ -36,9 +36,12 @@ f32 dGetUserHudScale() { } } // namespace + +#include "d_meter2_vdt.inc" #endif int dMeter2_c::_create() { + IF_DUSK(mPresentationTargets = {}); stage_stag_info_class* stag_info = dComIfGp_getStageStagInfo(); if (dStage_stagInfo_GetUpButton(stag_info) == 1) { mpHeap = fopMsgM_createExpHeap(HEAP_SIZE(0x5A400, 0xA0000), NULL); @@ -256,10 +259,19 @@ int dMeter2_c::_create() { mpHeap->getTotalFreeSize(); field_0x11c = NULL; mDoExt_setCurrentHeap(heap); + + IF_DUSK(base.draw_interp_frame = true); + return cPhs_COMPLEATE_e; } int dMeter2_c::_execute() { +#if TARGET_PC + if (mpMeterDraw != NULL) { + mpMeterDraw->beginExecution(); + } +#endif + JKRHeap* heap = mDoExt_setCurrentHeap(mpHeap); if (!dComIfGs_isCollectMirror(0) @@ -321,6 +333,13 @@ int dMeter2_c::_execute() { dComIfGp_setBottleStatus(0, 0); dComIfGp_setCStickStatus(0, 0, 0); +#if TARGET_PC + if (mpMeterDraw != NULL) { + mPresentationTargets.ready = true; + mpMeterDraw->endExecution(); + } +#endif + mDoExt_setCurrentHeap(heap); return 1; } @@ -820,11 +839,12 @@ void dMeter2_c::moveKantera() { } if (draw_kantera == true) { - mpMeterDraw->drawKantera(mMaxOil, mNowOil, x_pos, y_pos); + IF_NOT_DUSK(mpMeterDraw->drawKantera(mMaxOil, mNowOil, x_pos, y_pos)); } alphaAnimeKantera(); dComIfGp_setItemNowOil(mNowOil); + IF_DUSK(mpMeterDraw->publishKantera(mNowOil, mMaxOil)); } void dMeter2_c::moveOxygen() { @@ -941,11 +961,12 @@ void dMeter2_c::moveOxygen() { } if (draw_oxygen == true) { - mpMeterDraw->drawOxygen(mMaxOxygen, mNowOxygen, x_pos, y_pos); + IF_NOT_DUSK(mpMeterDraw->drawOxygen(mMaxOxygen, mNowOxygen, x_pos, y_pos)); } alphaAnimeOxygen(); dComIfGp_setNowOxygen(mNowOxygen); + IF_DUSK(mpMeterDraw->publishOxygen(mNowOxygen, mMaxOxygen)); } void dMeter2_c::moveLightDrop() { @@ -1002,6 +1023,12 @@ void dMeter2_c::moveLightDrop() { alpha = g_drawHIO.mLightDrop.mVesselAlpha[0]; } +#if TARGET_PC + mPresentationTargets.vesselX = pos_x; + mPresentationTargets.vesselY = pos_y; + mPresentationTargets.vesselScale = scale; + mPresentationTargets.vesselAlpha = alpha; +#else if (mVesselPosX != pos_x) { cLib_addCalc2(&mVesselPosX, pos_x, 1.0f, 10.0f); draw_lightdrop = true; @@ -1033,6 +1060,7 @@ void dMeter2_c::moveLightDrop() { mVesselAlpha = alpha; } } +#endif if (draw_lightdrop == true) { mpMeterDraw->drawLightDrop(mLightDropNum, mNeedLightDropNum, mVesselPosX, mVesselPosY, @@ -1384,6 +1412,12 @@ void dMeter2_c::moveButtonA() { var_f31 = 1.0f; } +#if TARGET_PC + for (int i = 0; i < 2; i++) { + mPresentationTargets.buttonAX[i] = pos_x[i]; + mPresentationTargets.buttonAY[i] = pos_y[i]; + } +#else for (int i = 0; i < 2; i++) { if (mButtonATalkPosX[i] != pos_x[i]) { cLib_addCalc2(&mButtonATalkPosX[i], pos_x[i], 1.0f, 10.0f); @@ -1409,6 +1443,7 @@ void dMeter2_c::moveButtonA() { field_0x144 = var_f31; } } +#endif if (field_0x200 != dMsgObject_isTalkNowCheck()) { field_0x200 = dMsgObject_isTalkNowCheck(); @@ -1589,6 +1624,12 @@ void dMeter2_c::moveButtonB() { draw_buttonB = true; } +#if TARGET_PC + for (int i = 0; i < 2; i++) { + mPresentationTargets.buttonBX[i] = pos_x[i]; + mPresentationTargets.buttonBY[i] = pos_y[i]; + } +#else for (int i = 0; i < 2; i++) { if (field_0x148[i] != pos_x[i]) { cLib_addCalc2(&field_0x148[i], pos_x[i], 1.0f, 10.0f); @@ -1614,6 +1655,7 @@ void dMeter2_c::moveButtonB() { field_0x158 = var_f31; } } +#endif if (g_drawHIO.mItemScaleAdjustON && field_0x4bc != g_drawHIO.mItemScalePercent) { field_0x4bc = g_drawHIO.mItemScalePercent; @@ -2199,6 +2241,10 @@ void dMeter2_c::moveButtonCross() { temp_f30 = mButtonCrossOFFPosX + (((f32)field_0x1b4 / (f32)g_drawHIO.mButtonCrossMoveFrame) * (mButtonCrossONPosX - mButtonCrossOFFPosX)); +#if TARGET_PC + mPresentationTargets.crossX = temp_f30; + mPresentationTargets.crossY = var_f31; +#else if (field_0x15c != var_f31) { cLib_addCalc2(&field_0x15c, var_f31, 0.5f, 50.0f); if ((f32)fabs(field_0x15c - var_f31) < 0.5f) { @@ -2206,6 +2252,7 @@ void dMeter2_c::moveButtonCross() { } draw_cross = true; } +#endif if (draw_cross == true) { mpMeterDraw->drawButtonCross(temp_f30, field_0x15c); @@ -2850,7 +2897,15 @@ void dMeter2_c::alphaAnimeLightDrop() { if (!isShowLightDrop()) { mpMeterDraw->setAlphaLightDropAnimeMin(); } else { +#if TARGET_PC + const bool dropGetPath = + dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) > 1 && + dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) != 0xFF; + + mpMeterDraw->showLightDrop(true, dropGetPath); +#else mpMeterDraw->setAlphaLightDropAnimeMax(); +#endif } mpMeterDraw->setAlphaLightDropChange(false); @@ -2994,36 +3049,34 @@ void dMeter2_c::alphaAnimeButtonCross() { { mpMeterDraw->setAlphaButtonCrossAnimeMin(); - if ((!dComIfGp_event_chkEventFlag(0x40) || dMeter2Info_isGameStatus(2) || - (mStatus & 0x100)) && - field_0x190 > 0) - { - field_0x190--; + if ((!dComIfGp_event_chkEventFlag(0x40) || dMeter2Info_isGameStatus(2) || (mStatus & 0x100)) IF_NOT_DUSK(&& field_0x190 > 0)) { + DUSK_IF_ELSE(mpMeterDraw->showMap(false), field_0x190--); } } else if (dMeter2Info_isSub2DStatus(1) || dMeter2Info_isFloatingMessageVisible()) { mpMeterDraw->setAlphaButtonCrossAnimeMin(); +#if TARGET_PC + mpMeterDraw->showMap(true); +#else if (field_0x190 < 5) { field_0x190++; } +#endif } else { #if TARGET_PC - if (dusk::getSettings().game.enableTouchControls) { - mpMeterDraw->setAlphaButtonCrossAnimeMin(); - } else { - mpMeterDraw->setAlphaButtonCrossAnimeMax(); - } + mpMeterDraw->showCross(!dusk::getSettings().game.enableTouchControls); + mpMeterDraw->showMap(true); #else mpMeterDraw->setAlphaButtonCrossAnimeMax(); -#endif if (field_0x190 < 5) { field_0x190++; } +#endif } if (mpMap != NULL) { - mpMap->setMapAlpha((field_0x190 * 255.0f) / 5.0f); + IF_NOT_DUSK(mpMap->setMapAlpha((field_0x190 * 255.0f) / 5.0f)); } } diff --git a/src/d/d_meter2_draw.cpp b/src/d/d_meter2_draw.cpp index a1425355ab..9771e030c4 100644 --- a/src/d/d_meter2_draw.cpp +++ b/src/d/d_meter2_draw.cpp @@ -20,17 +20,26 @@ #include "d/d_msg_class.h" #include "d/d_msg_object.h" #include "d/d_pane_class.h" -#include "dusk/frame_interpolation.h" #include -#include "dusk/version.hpp" - #if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" #include "dusk/ui/icon_provider.hpp" +#include "dusk/version.hpp" + +#include "d/d_meter2.h" + #include namespace { +f32 s_button_a_scale = 1.0f; +f32 s_button_a_text_scale = 1.0f; +int s_button_b_kind; +f32 s_button_b_scale = 1.0f; +f32 s_button_b_text_scale = 1.0f; // Reads the user HUD scale setting, clamped to a safe range. f32 dGetUserHudScale() { @@ -56,12 +65,14 @@ void dAnchorHudScale(CPaneMgr* i_pane, HudCorner i_corner, f32* io_x, f32* io_y, *io_x += dirX * i_pane->getInitSizeX() * half * i_pull; *io_y += dirY * i_pane->getInitSizeY() * half; } - } // namespace + +#include "d_meter2_draw_vdt.inc" #endif dMeter2Draw_c::dMeter2Draw_c(JKRExpHeap* mp_heap) { #if TARGET_PC + setExecuting(false); // correct hio data here because we can't do runtime disc checks in sinit data constructors if (dusk::version::isRegionJpn()) { g_drawHIO.mButtonATextSpacing = -2.0f; @@ -607,6 +618,7 @@ void dMeter2Draw_c::init() { initButton(); initButtonCross(); field_0x772 = 0; + IF_DUSK(resetRows()); } void dMeter2Draw_c::exec(u32 i_status) { @@ -691,6 +703,15 @@ void dMeter2Draw_c::exec(u32 i_status) { } void dMeter2Draw_c::draw() { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + advance_fade_rows(); + if (dMeter2_c* meter = dMeter2Info_getMeterClass()) { + meter->presentAnims(); + } + } +#endif + J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort(); graf_ctx->setup2D(); @@ -707,16 +728,12 @@ void dMeter2Draw_c::draw() { drawKanteraScreen(1); drawKanteraScreen(2); -#if TARGET_PC - if (!touchControlsEnabled) { -#endif + IF_DUSK_BLOCK(!touchControlsEnabled) for (int i = 0; i < 2; i++) { if (mpItemXY[i] != NULL) { for (int j = 0; j < 3; j++) { f32 temp_f30 = mItemParams[i].num_scale * 16.0f; -#if TARGET_PC - temp_f30 *= dGetUserHudScale(); -#endif + IF_DUSK(temp_f30 *= dGetUserHudScale()); Vec vtx0 = mpItemXY[i]->getPanePtr()->getGlbVtx(0); Vec vtx3 = mpItemXY[i]->getPanePtr()->getGlbVtx(3); @@ -758,9 +775,7 @@ void dMeter2Draw_c::draw() { } } } -#if TARGET_PC - } -#endif + IF_DUSK_BLOCK_END if (mpLightDropParent->getAlphaRate() != 0.0f) { f32 var_f28 = g_drawHIO.mLightDrop.mPikariScaleNormal; @@ -769,40 +784,33 @@ void dMeter2Draw_c::draw() { if (field_0x756 >= 0) { var_f29 = g_drawHIO.mLightDrop.mDropPikariAnimSpeed_Completed; int temp_r5_2 = g_drawHIO.mLightDrop.mPikariInterval * 15; -#ifdef TARGET_PC - // FRAME INTERP NOTE: Set even if not advancing - var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (field_0x756 <= temp_r5_2) { - int temp_r4 = (field_0x756 % g_drawHIO.mLightDrop.mPikariInterval); - int temp_r3_5 = field_0x756 / g_drawHIO.mLightDrop.mPikariInterval; + if (field_0x756 <= temp_r5_2) { + int temp_r4 = (IF_DUSK((int))field_0x756 % g_drawHIO.mLightDrop.mPikariInterval); + int temp_r3_5 = IF_DUSK((int))field_0x756 / g_drawHIO.mLightDrop.mPikariInterval; - if (temp_r4 == 0 && field_0x62c[temp_r3_5] == 0.0f) { - field_0x62c[temp_r3_5] = 18.0f; + if (temp_r4 == 0 && field_0x62c[temp_r3_5] == 0.0f) { + field_0x62c[temp_r3_5] = 18.0f; + } + + var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; + DUSK_IF_ELSE(field_0x756 += dusk::game_clock::original_frames(), field_0x756++); + } else { + int temp_r5_3 = temp_r5_2 + 1; + + if (field_0x756 DUSK_IF_ELSE(< temp_r5_3 + 1, == temp_r5_3)) { + if (field_0x62c[15] == 0.0f) { + DUSK_IF_ELSE(field_0x756 += dusk::game_clock::original_frames(), field_0x756++); } - var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; - field_0x756++; - } else { - int temp_r5_3 = temp_r5_2 + 1; - - if (field_0x756 == temp_r5_3) { - if (field_0x62c[15] == 0.0f) { - field_0x756++; - } - var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; - } else if (field_0x756 >= g_drawHIO.mLightDrop.field_0x54 + temp_r5_3) { - for (int i = 0; i < 16; i++) { - field_0x62c[i] = 18.0f - var_f29; - field_0x66c[i] = 18.0f - g_drawHIO.mLightDrop.mPikariLoopAnimSpeed; - } - - field_0x756 = -1; - } else { - field_0x756++; + } else if (field_0x756 >= g_drawHIO.mLightDrop.field_0x54 + temp_r5_3) { + for (int i = 0; i < 16; i++) { + field_0x62c[i] = 18.0f - var_f29; + field_0x66c[i] = 18.0f - g_drawHIO.mLightDrop.mPikariLoopAnimSpeed; } + + field_0x756 = -1; + } else { + DUSK_IF_ELSE(field_0x756 += dusk::game_clock::original_frames(), field_0x756++); } } } @@ -988,8 +996,7 @@ void dMeter2Draw_c::initMagic() { g_drawHIO.mMagicMeterPosY + offsetY); setAlphaMagicChange(true); - drawKantera(dComIfGs_getMaxOil(), dComIfGs_getOil(), g_drawHIO.mLanternMeterPosX + offsetX, - g_drawHIO.mNoMagicPosY + (g_drawHIO.mLanternMeterPosY + offsetY)); + IF_NOT_DUSK(drawKantera(dComIfGs_getMaxOil(), dComIfGs_getOil(), g_drawHIO.mLanternMeterPosX + offsetX, g_drawHIO.mNoMagicPosY + (g_drawHIO.mLanternMeterPosY + offsetY))); setAlphaKanteraChange(true); } @@ -1469,7 +1476,7 @@ void dMeter2Draw_c::drawPikari(CPaneMgr* i_pane, f32* i_framep, f32 i_scale, void dMeter2Draw_c::drawPikari(f32 i_posX, f32 i_posY, f32* i_framep, f32 i_scale, JUtility::TColor i_moyaBlack, JUtility::TColor i_moyaWhite, JUtility::TColor i_moyabsBlack, JUtility::TColor i_moyabsWhite, - f32 param_8, u8 param_9) { + f32 param_8, u8 param_9 IF_DUSK_ARG(f32 elapsedFrames)) { f32 var_f31 = 28.0f; if (param_9 == 4) { @@ -1494,25 +1501,45 @@ void dMeter2Draw_c::drawPikari(f32 i_posX, f32 i_posY, f32* i_framep, f32 i_scal if (param_9 != 3 && param_9 != 4 && param_9 != 5 && dMsgObject_isTalkNowCheck()) { *i_framep = 0.0f; } else { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - *i_framep += param_8; - if (*i_framep > var_f31) { - if (param_9 == 1 || param_9 == 2 || param_9 == 3) { - *i_framep = 18.0f; - } else { - *i_framep = 0.0f; - } - } +#if TARGET_PC + const f32 previous = *i_framep; + const f32 frames = elapsedFrames >= 0.0f ? elapsedFrames : dusk::game_clock::original_frames(); + const f32 next = previous + param_8 * frames; + const bool looping = param_9 == 1 || param_9 == 2 || param_9 == 3; + *i_framep = next > var_f31 ? (looping ? 18.0f : 0.0f) : next; - if (*i_framep == 18.0f && param_9 == 1) { + if (previous <= 18.0f && next > 18.0f) { + switch (param_9) { + case 1: mDoAud_seStart(Z2SE_NAVI_BLINK, NULL, 0, 0); - } else if (*i_framep == 18.0f && param_9 == 2) { + break; + case 2: mDoAud_seStart(Z2SE_SY_ITEM_COMBINE_ICON, NULL, 0, 0); + break; + case 3: + if (i_framep == &field_0x738) { + mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 0); + dMeter2Info_set2DVibration(); + } + break; } } +#else + *i_framep += param_8; + if (*i_framep > var_f31) { + if (param_9 == 1 || param_9 == 2 || param_9 == 3) { + *i_framep = 18.0f; + } else { + *i_framep = 0.0f; + } + } + + if (*i_framep == 18.0f && param_9 == 1) { + mDoAud_seStart(Z2SE_NAVI_BLINK, NULL, 0, 0); + } else if (*i_framep == 18.0f && param_9 == 2) { + mDoAud_seStart(Z2SE_SY_ITEM_COMBINE_ICON, NULL, 0, 0); + } +#endif playPikariBckAnimation(*i_framep); playPikariBpkAnimation(*i_framep); @@ -1658,6 +1685,7 @@ void dMeter2Draw_c::drawLife(s16 i_maxLife, s16 i_life, f32 i_posX, f32 i_posY) } void dMeter2Draw_c::setAlphaLifeChange(bool param_0) { + IF_DUSK_BLOCK(!isExecuting()) bool var_r31 = false; bool var_r30 = false; bool var_r29 = false; @@ -1703,20 +1731,29 @@ void dMeter2Draw_c::setAlphaLifeChange(bool param_0) { if (var_r31 || var_r28 || param_0) { mpBigHeart->setAlphaRate(mBigHeartAlpha * mLifeParentAlpha); } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaLifeAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowLife, false); +#else if (mpLifeParent->getAlphaRate() != 0.0f) { mpLifeParent->setAlphaRate(g_drawHIO.mParentAlpha); setAlphaAnimeMin(mpLifeParent, 5); } +#endif } void dMeter2Draw_c::setAlphaLifeAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowLife, true); +#else if (mpLifeParent->getAlphaRate() != g_drawHIO.mParentAlpha) { mpLifeParent->setAlphaRate(g_drawHIO.mParentAlpha); setAlphaAnimeMax(mpLifeParent, 5); } +#endif } void dMeter2Draw_c::drawKanteraScreen(u8 i_meterType) { @@ -1842,7 +1879,7 @@ static f32 dummyLiteralOrder() { return 5.0f; } -void dMeter2Draw_c::drawKantera(s32 i_max, s32 i_oil, f32 i_posX, f32 i_posY) { +void dMeter2Draw_c::drawKantera(s32 i_max, DUSK_IF_ELSE(f32, s32) i_oil, f32 i_posX, f32 i_posY) { f32 var_f6 = mpMagicFrameR->getInitPosX() - mpMagicFrameL->getInitPosX(); f32 var_f7 = 0.0f; f32 var_f4 = 0.0f; @@ -1865,6 +1902,7 @@ void dMeter2Draw_c::drawKantera(s32 i_max, s32 i_oil, f32 i_posX, f32 i_posY) { } void dMeter2Draw_c::setAlphaKanteraChange(bool i_forceSet) { + IF_DUSK_BLOCK(!isExecuting()) bool meter_parent_alpha_set = false; bool meter_alpha_set = false; bool meter_frame_alpha_set = false; @@ -1892,9 +1930,13 @@ void dMeter2Draw_c::setAlphaKanteraChange(bool i_forceSet) { mpMagicFrameL->setAlphaRate(mLanternMeterFrameAlpha * field_0x7b0); mpMagicFrameR->setAlphaRate(mLanternMeterFrameAlpha * field_0x7b0); } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaKanteraAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowKantera, false); +#else if (field_0x742[1] <= 0) { mMeterAlphaRate[1] = 0.0f; } else { @@ -1905,9 +1947,13 @@ void dMeter2Draw_c::setAlphaKanteraAnimeMin() { mMeterAlphaRate[1] = (field_0x742[1] / 5.0f) * g_drawHIO.mParentAlpha; } +#endif } void dMeter2Draw_c::setAlphaKanteraAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowKantera, true); +#else if (field_0x742[1] >= 5) { mMeterAlphaRate[1] = g_drawHIO.mParentAlpha; } else { @@ -1918,9 +1964,10 @@ void dMeter2Draw_c::setAlphaKanteraAnimeMax() { mMeterAlphaRate[1] = (field_0x742[1] / 5.0f) * g_drawHIO.mParentAlpha; } +#endif } -void dMeter2Draw_c::drawOxygen(s32 i_max, s32 i_oxygen, f32 i_posX, f32 i_posY) { +void dMeter2Draw_c::drawOxygen(s32 i_max, DUSK_IF_ELSE(f32, s32) i_oxygen, f32 i_posX, f32 i_posY) { f32 var_f6 = mpMagicFrameR->getInitPosX() - mpMagicFrameL->getInitPosX(); f32 var_f7 = 0.0f; f32 var_f4 = 0.0f; @@ -1943,6 +1990,7 @@ void dMeter2Draw_c::drawOxygen(s32 i_max, s32 i_oxygen, f32 i_posX, f32 i_posY) } void dMeter2Draw_c::setAlphaOxygenChange(bool i_forceSet) { + IF_DUSK_BLOCK(!isExecuting()) bool meter_parent_alpha_set = false; bool meter_alpha_set = false; bool meter_frame_alpha_set = false; @@ -1970,9 +2018,13 @@ void dMeter2Draw_c::setAlphaOxygenChange(bool i_forceSet) { mpMagicFrameL->setAlphaRate(mOxygenMeterFrameAlpha * field_0x7bc); mpMagicFrameR->setAlphaRate(mOxygenMeterFrameAlpha * field_0x7bc); } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaOxygenAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowOxygen, false); +#else if (field_0x742[2] <= 0) { mMeterAlphaRate[2] = 0.0f; } else { @@ -1983,9 +2035,13 @@ void dMeter2Draw_c::setAlphaOxygenAnimeMin() { mMeterAlphaRate[2] = (field_0x742[2] / 5.0f) * g_drawHIO.mParentAlpha; } +#endif } void dMeter2Draw_c::setAlphaOxygenAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowOxygen, true); +#else if (field_0x742[2] >= 5) { mMeterAlphaRate[2] = g_drawHIO.mParentAlpha; } else { @@ -1996,6 +2052,7 @@ void dMeter2Draw_c::setAlphaOxygenAnimeMax() { mMeterAlphaRate[2] = (field_0x742[2] / 5.0f) * g_drawHIO.mParentAlpha; } +#endif } void dMeter2Draw_c::drawLightDrop(u8 i_num, u8 i_needNum, f32 i_posX, f32 i_posY, f32 i_vesselScale, @@ -2044,23 +2101,14 @@ void dMeter2Draw_c::drawLightDrop(u8 i_num, u8 i_needNum, f32 i_posX, f32 i_posY } } + IF_DUSK_BLOCK(!isExecuting()) field_0x6fc = param_5; mLightDropVesselScale = i_vesselScale; -#if TARGET_PC - const f32 lightDropUserScale = dGetUserHudScale(); - const f32 lightDropScale = mLightDropVesselScale * field_0x6f8 * lightDropUserScale; - mpLightDropParent->scale(lightDropScale, lightDropScale); - - f32 lightDropPosX = i_posX; - f32 lightDropPosY = i_posY; - dAnchorHudScale(mpLightDropParent, HudCorner::TopRight, &lightDropPosX, &lightDropPosY); - mpLightDropParent->paneTrans(lightDropPosX, lightDropPosY); -#else - mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8, - mLightDropVesselScale * field_0x6f8); - + mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8 IF_DUSK(* dGetUserHudScale()), + mLightDropVesselScale * field_0x6f8 IF_DUSK(* dGetUserHudScale())); + IF_DUSK(dAnchorHudScale(mpLightDropParent, HudCorner::TopRight, &i_posX, &i_posY)); mpLightDropParent->paneTrans(i_posX, i_posY); -#endif + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaLightDropChange(bool unused) {} @@ -2082,6 +2130,9 @@ f32 dMeter2Draw_c::getNowLightDropRateCalc() { } void dMeter2Draw_c::setAlphaLightDropAnimeMin() { +#if TARGET_PC + showLightDrop(false); +#else if (mpLightDropParent->getAlphaRate() != 0.0f) { mpLightDropParent->setAlphaRate(g_drawHIO.mParentAlpha * field_0x6fc); setAlphaAnimeMin(mpLightDropParent, 5); @@ -2101,14 +2152,20 @@ void dMeter2Draw_c::setAlphaLightDropAnimeMin() { } } } +#endif } void dMeter2Draw_c::setAlphaLightDropAnimeMax() { +#if TARGET_PC + const bool dropGetPath = dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) > 1 && + dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) != 0xFF; + + showLightDrop(true, dropGetPath); +#else f32 temp_f31 = g_drawHIO.mParentAlpha * field_0x6fc; if (dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) > 1 && - dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) != 0xFF) - { + dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) != 0xFF) { if (mpLightDropParent->getAlphaTimer() == 0) { Z2GetAudioMgr()->seStart(Z2SE_SY_LIGHT_POT_EQUIP, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); @@ -2133,13 +2190,8 @@ void dMeter2Draw_c::setAlphaLightDropAnimeMax() { field_0x6f8 = 1.0f; } -#if TARGET_PC - const f32 dropAnimScale = mLightDropVesselScale * field_0x6f8 * dGetUserHudScale(); - mpLightDropParent->scale(dropAnimScale, dropAnimScale); -#else mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8, - mLightDropVesselScale * field_0x6f8); -#endif + mLightDropVesselScale * field_0x6f8); if (g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum == mpLightDropParent->getAlphaTimer()) { dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 0xFF); @@ -2171,6 +2223,7 @@ void dMeter2Draw_c::setAlphaLightDropAnimeMax() { mpSIParts[i][2]->setAlphaRate(mpSIParent[1]->getAlphaRate() * 0.3f); } } +#endif } void dMeter2Draw_c::drawRupee(s16 i_rupeeNum) { @@ -2245,6 +2298,7 @@ void dMeter2Draw_c::drawRupee(s16 i_rupeeNum) { } void dMeter2Draw_c::setAlphaRupeeChange(bool param_0) { + IF_DUSK_BLOCK(!isExecuting()) bool set_parent = false; bool set_rupeekey = false; bool set_rupee = false; @@ -2297,9 +2351,13 @@ void dMeter2Draw_c::setAlphaRupeeChange(bool param_0) { } } } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaRupeeAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowRupee, false); +#else f32 alphas[3]; alphas[0] = g_drawHIO.mRupeeAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); alphas[1] = g_drawHIO.mRupeeFrameAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); @@ -2311,9 +2369,13 @@ void dMeter2Draw_c::setAlphaRupeeAnimeMin() { setAlphaAnimeMin(mpRupeeParent[i], 5); } } +#endif } void dMeter2Draw_c::setAlphaRupeeAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowRupee, true); +#else f32 alphas[3]; alphas[0] = g_drawHIO.mRupeeAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); alphas[1] = g_drawHIO.mRupeeFrameAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); @@ -2325,6 +2387,7 @@ void dMeter2Draw_c::setAlphaRupeeAnimeMax() { setAlphaAnimeMax(mpRupeeParent[i], 5); } } +#endif } void dMeter2Draw_c::drawKey(s16 i_keyNum) { @@ -2359,6 +2422,7 @@ void dMeter2Draw_c::drawKey(s16 i_keyNum) { } void dMeter2Draw_c::setAlphaKeyChange(bool param_0) { + IF_DUSK_BLOCK(!isExecuting()) bool set_parent = false; bool set_rupeekey = false; bool set_key = false; @@ -2393,24 +2457,33 @@ void dMeter2Draw_c::setAlphaKeyChange(bool param_0) { mpKeyTexture[i]->setAlphaRate(field_0x7e0 * (field_0x7e4 * (mKeyNumAlpha * mKeyAlpha))); } } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaKeyAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowKey, false); +#else f32 alpha = g_drawHIO.mKeyAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); if (mpKeyParent->getAlphaRate() != 0.0f) { mpKeyParent->setAlphaRate(alpha); setAlphaAnimeMin(mpKeyParent, 5); } +#endif } void dMeter2Draw_c::setAlphaKeyAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowKey, true); +#else f32 alpha = g_drawHIO.mKeyAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); if (mpKeyParent->getAlphaRate() != alpha) { mpKeyParent->setAlphaRate(alpha); setAlphaAnimeMax(mpKeyParent, 5); } +#endif } void dMeter2Draw_c::drawButtonA(u8 i_action, f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, @@ -2470,11 +2543,16 @@ void dMeter2Draw_c::drawButtonA(u8 i_action, f32 i_posX, f32 i_posY, f32 i_textP } } +#if TARGET_PC + s_button_a_scale = var_f31; + s_button_a_text_scale = var_f30; +#else mpButtonA->scale(var_f31 * i_scale, var_f31 * i_scale); mpButtonA->paneTrans(i_posX, i_posY); mpTextA->scale(var_f30 * i_scale, var_f30 * i_scale); mpTextA->paneTrans(g_drawHIO.mButtonATextPosX + i_textPosX, g_drawHIO.mButtonATextPosY + i_textPosY IF_DUSK(+ (dusk::version::isLessThanWiiJpn() ? 6.0f : 0.0f))); +#endif } void dMeter2Draw_c::drawButtonB(u8 i_action, bool param_1, f32 i_posX, f32 i_posY, f32 i_textPosX, @@ -2561,9 +2639,15 @@ void dMeter2Draw_c::drawButtonB(u8 i_action, bool param_1, f32 i_posX, f32 i_pos mpScreen->search(MULTI_CHAR('item_b_n'))->hide(); } +#if TARGET_PC + s_button_b_kind = var_r31; + s_button_b_scale = var_f31; + s_button_b_text_scale = var_f30; +#endif mpItemB->getPanePtr()->rotate(mpItemB->getSizeX() * 0.5f, mpItemB->getSizeY() * 0.5f, ROTATE_Z, g_drawHIO.mButtonBItemRotation[var_r31]); +#if !TARGET_PC field_0x730 = var_f31 * i_scale; mpButtonB->scale(field_0x730 * field_0x734, field_0x730 * field_0x734); mpButtonB->paneTrans(g_drawHIO.mButtonBPosX + i_posX, g_drawHIO.mButtonBPosY + i_posY); @@ -2580,6 +2664,7 @@ void dMeter2Draw_c::drawButtonB(u8 i_action, bool param_1, f32 i_posX, f32 i_pos mpTextB->scale(var_f30 * i_scale, var_f30 * i_scale); mpTextB->paneTrans(g_drawHIO.mButtonBFontPosX + i_textPosX, g_drawHIO.mButtonBFontPosY + i_textPosY); +#endif } void dMeter2Draw_c::drawButtonR(u8 unused0, u8 i_action, bool unused1, bool unused2) { @@ -2831,27 +2916,20 @@ f32 dMeter2Draw_c::getButtonCrossParentInitTransY() { } void dMeter2Draw_c::drawButtonCross(f32 i_posX, f32 i_posY) { -#if TARGET_PC - const f32 buttonCrossUserScale = dGetUserHudScale(); - const f32 buttonCrossScale = g_drawHIO.mButtonCrossScale * buttonCrossUserScale; - mpButtonCrossParent->scale(buttonCrossScale, buttonCrossScale); -#else - mpButtonCrossParent->scale(g_drawHIO.mButtonCrossScale, g_drawHIO.mButtonCrossScale); -#endif + IF_DUSK_BLOCK(!isExecuting()) + mpButtonCrossParent->scale(g_drawHIO.mButtonCrossScale IF_DUSK(* dGetUserHudScale()), + g_drawHIO.mButtonCrossScale IF_DUSK(* dGetUserHudScale())); mpTextI->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); mpTextM->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); - -#if TARGET_PC - f32 buttonCrossPosX = i_posX; - f32 buttonCrossPosY = i_posY; - dAnchorHudScale(mpButtonCrossParent, HudCorner::BottomLeft, &buttonCrossPosX, &buttonCrossPosY); - mpButtonCrossParent->paneTrans(buttonCrossPosX, buttonCrossPosY); -#else + IF_DUSK(dAnchorHudScale(mpButtonCrossParent, HudCorner::BottomLeft, &i_posX, &i_posY)); mpButtonCrossParent->paneTrans(i_posX, i_posY); -#endif + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaButtonCrossAnimeMin() { +#if TARGET_PC + showCross(false); +#else if (mpButtonCrossParent->getAlphaRate() != 0.0f) { mpButtonCrossParent->setAlphaRate(g_drawHIO.mParentAlpha); setAlphaAnimeMin(mpButtonCrossParent, 5); @@ -2859,6 +2937,7 @@ void dMeter2Draw_c::setAlphaButtonCrossAnimeMin() { setAlphaButtonCrossItemAnimeMin(); setAlphaButtonCrossMapAnimeMin(); +#endif } void dMeter2Draw_c::setAlphaButtonCrossAnimeMax() { @@ -2937,6 +3016,7 @@ void dMeter2Draw_c::setAlphaButtonCrossMapAnimeMax() { } void dMeter2Draw_c::setAlphaButtonChange(bool param_0) { + IF_DUSK_BLOCK(!isExecuting()) bool set_parent = false; bool set_buttonA = false; bool set_buttonB = false; @@ -3132,9 +3212,13 @@ void dMeter2Draw_c::setAlphaButtonChange(bool param_0) { static_cast(mpXYText[4][1]->getPanePtr()) ->setFontColor(g_drawHIO.mButtonXYTextColor, g_drawHIO.mButtonXYTextColor); } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaButtonAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonParent, false); +#else if (mpButtonParent->getAlphaRate() != 0.0f) { mpButtonParent->setAlphaRate(g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); setAlphaAnimeMin(mpButtonParent, 5); @@ -3143,9 +3227,13 @@ void dMeter2Draw_c::setAlphaButtonAnimeMin() { mpUzu->setAlphaRate(mButtonBaseAlpha * mpButtonParent->getAlphaRate()); } } +#endif } void dMeter2Draw_c::setAlphaButtonAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonParent, true); +#else if (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha != mpButtonParent->getAlphaRate()) { mpButtonParent->setAlphaRate(g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); setAlphaAnimeMax(mpButtonParent, 5); @@ -3160,9 +3248,13 @@ void dMeter2Draw_c::setAlphaButtonAnimeMax() { dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 2); } } +#endif } void dMeter2Draw_c::setAlphaButtonAAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonA, false); +#else if (mpButtonA->getAlphaRate() != 0.0f) { mpButtonA->setAlphaRate(g_drawHIO.mButtonAAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha)); @@ -3175,9 +3267,13 @@ void dMeter2Draw_c::setAlphaButtonAAnimeMin() { setAlphaAnimeMin(mpAText[i], 5); } } +#endif } void dMeter2Draw_c::setAlphaButtonAAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonA, true); +#else if (g_drawHIO.mButtonAAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) != mpButtonA->getAlphaRate()) { @@ -3192,9 +3288,13 @@ void dMeter2Draw_c::setAlphaButtonAAnimeMax() { setAlphaAnimeMax(mpAText[i], 5); } } +#endif } void dMeter2Draw_c::setAlphaButtonBAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonB, false); +#else if (mpButtonB->getAlphaRate() != 0.0f) { mpButtonB->setAlphaRate(g_drawHIO.mButtonBAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha)); @@ -3216,9 +3316,13 @@ void dMeter2Draw_c::setAlphaButtonBAnimeMin() { setAlphaAnimeMin(mpBText[i], 5); } } +#endif } void dMeter2Draw_c::setAlphaButtonBAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonB, true); +#else if (mpButtonB->getAlphaRate() != g_drawHIO.mButtonBAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha)) { @@ -3242,83 +3346,116 @@ void dMeter2Draw_c::setAlphaButtonBAnimeMax() { setAlphaAnimeMax(mpBText[i], 5); } } +#endif } void dMeter2Draw_c::setButtonIconAAlpha(u8 unused0, u32 unused1, bool unused2) { - if (mpButtonA->isVisible()) { - u8 alpha = g_drawHIO.mButtonAAlpha * - (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * - (f32)mpButtonA->getInitAlpha(); - f32 alpha_rate = mpButtonParent->getAlphaRate(); + if (DUSK_IF_ELSE(isExecuting() ? mpButtonA->isVisible() : s_follow_a.live, mpButtonA->isVisible())) { +#if TARGET_PC + if (isExecuting()) { + follow_button_a(true, !dMeter2Info_isUseButton(1) && !dComIfGp_isDoSetFlag(4), !dMeter2Info_isUseButton(1)); + } else +#endif + { + u8 alpha = g_drawHIO.mButtonAAlpha * + (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * + (f32)mpButtonA->getInitAlpha(); + f32 alpha_rate = DUSK_IF_ELSE(button_parent_rate(this), mpButtonParent->getAlphaRate()); - if (!dMeter2Info_isUseButton(1) && !dComIfGp_isDoSetFlag(4)) { - alpha = g_drawHIO.mButtonXYBaseDimAlpha; - } - - mpButtonA->setAlpha(alpha * alpha_rate); - - for (int i = 0; i < 5; i++) { - u8 alpha = mpAText[i]->getInitAlpha(); - if (!dMeter2Info_isUseButton(1)) { - alpha = (f32)alpha * ((f32)g_drawHIO.field_0x42c / 255.0f); + if (DUSK_IF_ELSE(s_follow_a.dim, !dMeter2Info_isUseButton(1) && !dComIfGp_isDoSetFlag(4))) { + alpha = g_drawHIO.mButtonXYBaseDimAlpha; } - mpAText[i]->setAlpha(alpha * alpha_rate); + mpButtonA->setAlpha(alpha * alpha_rate); + + for (int i = 0; i < 5; i++) { + u8 alpha = mpAText[i]->getInitAlpha(); + if (DUSK_IF_ELSE(s_follow_a.dim_txt, !dMeter2Info_isUseButton(1))) { + alpha = (f32)alpha * ((f32)g_drawHIO.field_0x42c / 255.0f); + } + + mpAText[i]->setAlpha(alpha * alpha_rate); + } } +#if TARGET_PC + } else { + if (isExecuting()) { + follow_button_a(false, false, false); + } +#endif } } void dMeter2Draw_c::setButtonIconBAlpha(u8 unused0, u32 unused1, bool param_2) { - if (mpItemB->isVisible() || mpLightB->isVisible() || mpButtonB->isVisible()) { - f32 temp_f30 = - g_drawHIO.mButtonBAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + if (DUSK_IF_ELSE(isExecuting() ? (mpItemB->isVisible() || mpLightB->isVisible() || mpButtonB->isVisible()) + : s_follow_b.live, mpItemB->isVisible() || mpLightB->isVisible() || mpButtonB->isVisible())) + { +#if TARGET_PC + if (isExecuting()) { + follow_button_b(true, !(dMeter2Info_isUseButton(2) & 1) && !dMeter2Info_isSub2DStatus(1), !dMeter2Info_isUseButton(2) && !dMeter2Info_isSub2DStatus(1), param_2, mpItemB->isVisible()); + } else +#endif + { + f32 temp_f30 = + g_drawHIO.mButtonBAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); - f32 var_f2; - if (mpItemB->isVisible()) { - var_f2 = mItemBBaseAlpha[0]; - } else { - var_f2 = mItemBBaseAlpha[1]; - } - - u8 var_r25 = mpItemB->getInitAlpha(); - u8 var_r26 = var_f2 * (temp_f30 * (f32)mpLightB->getInitAlpha()); - u8 var_r27 = temp_f30 * (f32)mpButtonB->getInitAlpha(); - f32 temp_f31 = mpButtonParent->getAlphaRate(); - - if (!(dMeter2Info_isUseButton(2) & 1) && !dMeter2Info_isSub2DStatus(1)) { - var_r25 = g_drawHIO.mButtonXYItemDimAlpha; - var_r26 = g_drawHIO.mButtonXYItemDimAlpha; - var_r27 = g_drawHIO.mButtonXYBaseDimAlpha; - } - - if (!param_2) { - var_r26 = 0; - } - - mpItemB->setAlpha(var_r25 * temp_f31); - mpLightB->setAlpha(var_r26 * temp_f31); - mpButtonB->setAlpha(var_r27 * temp_f31); - - for (int i = 0; i < 5; i++) { - u8 alpha = mpBText[i]->getInitAlpha(); - if (!dMeter2Info_isUseButton(2) && !dMeter2Info_isSub2DStatus(1)) { - alpha = (f32)alpha * ((f32)g_drawHIO.field_0x42c / 255.0f); + f32 var_f2; + if (DUSK_IF_ELSE(s_follow_b.item_visible, mpItemB->isVisible())) { + var_f2 = mItemBBaseAlpha[0]; + } else { + var_f2 = mItemBBaseAlpha[1]; } - mpBText[i]->setAlpha(alpha * temp_f31); + u8 var_r25 = mpItemB->getInitAlpha(); + u8 var_r26 = var_f2 * (temp_f30 * (f32)mpLightB->getInitAlpha()); + u8 var_r27 = temp_f30 * (f32)mpButtonB->getInitAlpha(); + f32 temp_f31 = DUSK_IF_ELSE(button_parent_rate(this), mpButtonParent->getAlphaRate()); + + if (DUSK_IF_ELSE(s_follow_b.dim, !(dMeter2Info_isUseButton(2) & 1) && !dMeter2Info_isSub2DStatus(1))) { + var_r25 = g_drawHIO.mButtonXYItemDimAlpha; + var_r26 = g_drawHIO.mButtonXYItemDimAlpha; + var_r27 = g_drawHIO.mButtonXYBaseDimAlpha; + } + + if (!DUSK_IF_ELSE(s_follow_b.light_on, param_2)) { + var_r26 = 0; + } + + mpItemB->setAlpha(var_r25 * temp_f31); + mpLightB->setAlpha(var_r26 * temp_f31); + mpButtonB->setAlpha(var_r27 * temp_f31); + + for (int i = 0; i < 5; i++) { + u8 alpha = mpBText[i]->getInitAlpha(); + if (DUSK_IF_ELSE(s_follow_b.dim_txt, !dMeter2Info_isUseButton(2) && !dMeter2Info_isSub2DStatus(1))) { + alpha = (f32)alpha * ((f32)g_drawHIO.field_0x42c / 255.0f); + } + + mpBText[i]->setAlpha(alpha * temp_f31); + } } +#if TARGET_PC + } else { + if (isExecuting()) { + follow_button_b(false, false, false, false, false); + } +#endif } } void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { +#if TARGET_PC + bool midona_live = false; + bool midona_hold = false; + f32 midona_z_target = 0.0f; +#endif mpButtonMidona->scale(g_drawHIO.mMidnaIconScale, g_drawHIO.mMidnaIconScale); mpButtonMidona->paneTrans(g_drawHIO.mMidnaIconPosX, g_drawHIO.mMidnaIconPosY); if (mpButtonMidona->isVisible()) { - f32 temp_f30 = - g_drawHIO.mMidnaIconAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + IF_NOT_DUSK(f32 temp_f30 = g_drawHIO.mMidnaIconAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha)); f32 var_f29 = (f32)mpButtonMidona->getInitAlpha() / 255.0f; - f32 temp_f31 = mpButtonParent->getAlphaRate(); + f32 temp_f31 = DUSK_IF_ELSE(fade_row_factor(s_fade_rows[kFadeRowButtonParent]), mpButtonParent->getAlphaRate()); bool var_r31 = 1; if (getCanoeFishing() || @@ -3340,12 +3477,14 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { field_0x738 = 18.0f; } +#if !TARGET_PC if (field_0x738 == 18.0f) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 0); dMeter2Info_set2DVibration(); } +#endif - mButtonZAlpha = var_f29; + IF_NOT_DUSK(mButtonZAlpha = var_f29); var_r31 = 0; } @@ -3354,6 +3493,7 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { field_0x738 = 0.0f; } +#if !TARGET_PC if (mButtonZAlpha != var_f29) { cLib_addCalc2(&mButtonZAlpha, var_f29, 0.4f, 0.5f); @@ -3361,24 +3501,34 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { mButtonZAlpha = var_f29; } } +#endif } +#if TARGET_PC + midona_live = true; + midona_hold = var_r31 == 0; + midona_z_target = var_f29; +#else mpButtonMidona->setAlpha(temp_f30 * (255.0f * mButtonZAlpha * temp_f31)); +#endif } else { - mButtonZAlpha = 0.0f; + IF_NOT_DUSK(mButtonZAlpha = 0.0f); } f32 var_f29_2 = (g_drawHIO.mButtonZAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * (f32)mpButtonXY[2]->getInitAlpha()) / 255.0f; - f32 temp_f30_2 = mpButtonParent->getAlphaRate(); + IF_NOT_DUSK(f32 temp_f30_2 = mpButtonParent->getAlphaRate()); if (param_0 & 0x1000000) { var_f29_2 = 0.0f; } else if (!dMeter2Info_isUseButton(0x800)) { var_f29_2 = (f32)g_drawHIO.mButtonXYBaseDimAlpha / 255.0f; } +#if TARGET_PC + chase_midona(midona_live, midona_hold, midona_z_target, var_f29_2); +#else if (field_0x724 != var_f29_2) { cLib_addCalc2(&field_0x724, var_f29_2, 0.4f, 0.5f); @@ -3388,91 +3538,102 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { } mpButtonXY[2]->setAlpha(255.0f * field_0x724 * temp_f30_2); - -#if TARGET_PC - dusk::ui::update_midna_icon_texture(mpButtonMidona != NULL ? mpButtonMidona->getPanePtr() : NULL); #endif + + IF_DUSK(dusk::ui::update_midna_icon_texture(mpButtonMidona != NULL ? mpButtonMidona->getPanePtr() : NULL)); } void dMeter2Draw_c::setButtonIconAlpha(int i_no, u8 unused0, u32 unused1, bool unused2) { JUT_ASSERT(0, i_no < SELECT_MAX_e); - if (mpItemXY[i_no]->isVisible() || mpLightXY[i_no]->isVisible() || - mpButtonXY[i_no]->isVisible()) - { - f32 var_f30 = 1.0f; + if (DUSK_IF_ELSE(isExecuting() ? (mpItemXY[i_no]->isVisible() || mpLightXY[i_no]->isVisible() || mpButtonXY[i_no]->isVisible()) + : s_follow_xy[i_no].live, mpItemXY[i_no]->isVisible() || mpLightXY[i_no]->isVisible() || mpButtonXY[i_no]->isVisible())) { +#if TARGET_PC + if (isExecuting()) { + follow_button_xy(i_no, true, !dMeter2Info_isUseButton(i_no == 0 ? 4 : 8), getFishingType() != 0, mpItemXY[i_no]->isVisible()); + } else +#endif + { + f32 var_f30 = 1.0f; - int var_r26 = 1; - if (mpItemXY[i_no]->isVisible()) { - var_r26 = 0; - } + int var_r26 = 1; + if (DUSK_IF_ELSE(s_follow_xy[i_no].item_visible, mpItemXY[i_no]->isVisible())) { + var_r26 = 0; + } - f32 var_f2; - if (i_no == 0) { - var_f2 = - g_drawHIO.mButtonXAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); - var_f30 = g_drawHIO.mButtonXItemBaseAlpha[var_r26]; - } else if (i_no == 1) { - var_f2 = - g_drawHIO.mButtonYAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); - var_f30 = g_drawHIO.mButtonYItemBaseAlpha[var_r26]; - } else { - var_f2 = - g_drawHIO.mButtonZAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); - } + f32 var_f2; + if (i_no == 0) { + var_f2 = + g_drawHIO.mButtonXAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + var_f30 = g_drawHIO.mButtonXItemBaseAlpha[var_r26]; + } else if (i_no == 1) { + var_f2 = + g_drawHIO.mButtonYAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + var_f30 = g_drawHIO.mButtonYItemBaseAlpha[var_r26]; + } else { + var_f2 = + g_drawHIO.mButtonZAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + } - u8 var_r28 = mpItemXY[i_no]->getInitAlpha(); - u8 var_r27 = var_f30 * (var_f2 * (f32)mpLightXY[i_no]->getInitAlpha()); - u8 var_r26_2 = var_f2 * (f32)mpButtonXY[i_no]->getInitAlpha(); - f32 temp_f31 = mpButtonParent->getAlphaRate(); + u8 var_r28 = mpItemXY[i_no]->getInitAlpha(); + u8 var_r27 = var_f30 * (var_f2 * (f32)mpLightXY[i_no]->getInitAlpha()); + u8 var_r26_2 = var_f2 * (f32)mpButtonXY[i_no]->getInitAlpha(); + f32 temp_f31 = DUSK_IF_ELSE(button_parent_rate(this), mpButtonParent->getAlphaRate()); - if (i_no == 0) { - if (!dMeter2Info_isUseButton(4)) { - if (getFishingType()) { - var_r28 = 0; - } else { - var_r28 = g_drawHIO.mButtonXYItemDimAlpha; + if (i_no == 0) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].dim, !dMeter2Info_isUseButton(4))) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].fishing, getFishingType())) { + var_r28 = 0; + } else { + var_r28 = g_drawHIO.mButtonXYItemDimAlpha; + } + + var_r27 = g_drawHIO.mButtonXYItemDimAlpha; + var_r26_2 = g_drawHIO.mButtonXYBaseDimAlpha; + } + } else if (i_no == 1) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].dim, !dMeter2Info_isUseButton(8))) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].fishing, getFishingType())) { + var_r28 = 0; + } else { + var_r28 = g_drawHIO.mButtonXYItemDimAlpha; + } + + var_r27 = g_drawHIO.mButtonXYItemDimAlpha; + var_r26_2 = g_drawHIO.mButtonXYBaseDimAlpha; + } + } + + mpItemXY[i_no]->setAlpha((f32)var_r28 * temp_f31); + mpLightXY[i_no]->setAlpha((f32)var_r27 * temp_f31); + mpButtonXY[i_no]->setAlpha((f32)var_r26_2 * temp_f31); + + int var_r26_3 = 1; + if (i_no == 0) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].dim, !dMeter2Info_isUseButton(4))) { + var_r26_3 = 0; + } + } else if (i_no == 1) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].dim, !dMeter2Info_isUseButton(8))) { + var_r26_3 = 0; + } + } + + for (int i = 0; i < 5; i++) { + u8 var_r0 = mpXYText[i][i_no]->getInitAlpha(); + if (!var_r26_3) { + var_r0 = (f32)var_r0 * (g_drawHIO.field_0x42c / 255.0f); } - var_r27 = g_drawHIO.mButtonXYItemDimAlpha; - var_r26_2 = g_drawHIO.mButtonXYBaseDimAlpha; - } - } else if (i_no == 1) { - if (!dMeter2Info_isUseButton(8)) { - if (getFishingType()) { - var_r28 = 0; - } else { - var_r28 = g_drawHIO.mButtonXYItemDimAlpha; - } - - var_r27 = g_drawHIO.mButtonXYItemDimAlpha; - var_r26_2 = g_drawHIO.mButtonXYBaseDimAlpha; + mpXYText[i][i_no]->setAlpha((f32)var_r0 * temp_f31); } } - - mpItemXY[i_no]->setAlpha((f32)var_r28 * temp_f31); - mpLightXY[i_no]->setAlpha((f32)var_r27 * temp_f31); - mpButtonXY[i_no]->setAlpha((f32)var_r26_2 * temp_f31); - - int var_r26_3 = 1; - if (i_no == 0) { - if (!dMeter2Info_isUseButton(4)) { - var_r26_3 = 0; - } - } else if (i_no == 1) { - if (!dMeter2Info_isUseButton(8)) { - var_r26_3 = 0; - } - } - - for (int i = 0; i < 5; i++) { - u8 var_r0 = mpXYText[i][i_no]->getInitAlpha(); - if (!var_r26_3) { - var_r0 = (f32)var_r0 * (g_drawHIO.field_0x42c / 255.0f); - } - - mpXYText[i][i_no]->setAlpha((f32)var_r0 * temp_f31); +#if TARGET_PC + } else { + if (isExecuting()) { + follow_button_xy(i_no, false, false, false, false); } +#endif } } @@ -3637,6 +3798,24 @@ void dMeter2Draw_c::changeTextureItemXY(int i_no, u8 i_itemNo) { } void dMeter2Draw_c::setAlphaAnimeMin(CPaneMgrAlpha* i_pane, s16 i_min) { +#if TARGET_PC + if (i_pane == NULL) { + return; + } + if (i_min <= 0) { + i_pane->alphaAnimeStart(0.0f); + i_pane->setAlphaRate(0.0f); + return; + } + const f32 alphaRate = i_pane->getAlphaRate(); + f32 alphaTimer = i_pane->getAlphaTimer(); + if (alphaTimer > i_min) { + alphaTimer = i_min; + } + dusk::vdt::advance_toward_frame(alphaTimer, 0.0f, 1.0f); + i_pane->alphaAnimeStart(alphaTimer); + i_pane->setAlphaRate(alphaRate * dusk::vdt::clamped_fraction(alphaTimer, i_min)); +#else f32 alpha_rate = i_pane->getAlphaRate(); s16 alpha_timer = i_pane->getAlphaTimer(); if (alpha_timer > i_min) { @@ -3656,9 +3835,23 @@ void dMeter2Draw_c::setAlphaAnimeMin(CPaneMgrAlpha* i_pane, s16 i_min) { i_pane->alphaAnimeStart(alpha_timer); i_pane->setAlphaRate(alpha_rate * ((f32)alpha_timer / (f32)i_min)); } +#endif } void dMeter2Draw_c::setAlphaAnimeMax(CPaneMgrAlpha* i_pane, s16 i_max) { +#if TARGET_PC + if (i_pane == NULL) { + return; + } + if (i_max <= 0) { + return; + } + const f32 alphaRate = i_pane->getAlphaRate(); + f32 alphaTimer = i_pane->getAlphaTimer(); + dusk::vdt::advance_toward_frame(alphaTimer, i_max, 1.0f); + i_pane->alphaAnimeStart(alphaTimer); + i_pane->setAlphaRate(alphaRate * dusk::vdt::clamped_fraction(alphaTimer, i_max)); +#else f32 alpha_rate = i_pane->getAlphaRate(); s16 alpha_timer = i_pane->getAlphaTimer(); @@ -3675,6 +3868,7 @@ void dMeter2Draw_c::setAlphaAnimeMax(CPaneMgrAlpha* i_pane, s16 i_max) { i_pane->alphaAnimeStart(alpha_timer); i_pane->setAlphaRate(alpha_rate * ((f32)alpha_timer / (f32)i_max)); } +#endif } void dMeter2Draw_c::setItemNum(u8 i_button, u8 i_num, u8 i_max) { @@ -3772,7 +3966,7 @@ void dMeter2Draw_c::drawKanteraMeter(u8 i_button, f32 i_alphaRate) { } u8 dMeter2Draw_c::isButtonVisible() { - return (mpButtonParent->getAlphaRate() == 0.0f) ^ 1; + return (DUSK_IF_ELSE(fade_row_factor(s_fade_rows[kFadeRowButtonParent]), mpButtonParent->getAlphaRate()) == 0.0f) ^ 1; } void dMeter2Draw_c::setItemParamX(u8 i_itemNo) { @@ -4198,7 +4392,7 @@ bool dMeter2Draw_c::isBButtonShow(bool param_0) { } s16 dMeter2Draw_c::getButtonTimer() { - return mpButtonParent->getAlphaTimer(); + return DUSK_IF_ELSE((s16)s_fade_rows[kFadeRowButtonParent].timer, mpButtonParent->getAlphaTimer()); } // unused diff --git a/src/d/d_meter2_draw_vdt.inc b/src/d/d_meter2_draw_vdt.inc new file mode 100644 index 0000000000..2e8bb1e8d1 --- /dev/null +++ b/src/d/d_meter2_draw_vdt.inc @@ -0,0 +1,581 @@ +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" + +#include "d/d_com_inf_game.h" +#include "d/d_meter2_info.h" +#include "d/d_meter_HIO.h" +#include "d/d_meter_map.h" +#include "d/d_pane_class.h" + +namespace { + +struct FadeRow { + bool used; + bool show; + bool follow; + bool got_free; + bool got_follow; + f32 timer; + f32 duration; + f32 full; +}; + +struct FollowA { + bool live; + bool dim; + bool dim_txt; +}; + +struct FollowB { + bool live; + bool dim; + bool dim_txt; + bool light_on; + bool item_visible; +}; + +struct FollowXY { + bool live; + bool dim; + bool fishing; + bool item_visible; +}; + +struct MidonaChase { + bool used; + bool live; + bool hold; + f32 z_target; + f32 xy_target; +}; + +struct GaugeFill { + bool used; + s32 prev; + s32 now; + s32 max; +}; + +enum FadeRowId { + kFadeRowLife, + kFadeRowKantera, + kFadeRowOxygen, + kFadeRowLightDrop, + kFadeRowRupee, + kFadeRowKey, + kFadeRowButtonParent, + kFadeRowButtonA, + kFadeRowButtonB, + kFadeRowCross, + kFadeRowCrossItem, + kFadeRowCrossMap, + kFadeRowMap, + kFadeRowCount +}; + +FadeRow s_fade_rows[kFadeRowCount]; +FollowA s_follow_a; +FollowB s_follow_b; +FollowXY s_follow_xy[2]; +MidonaChase s_midona; +GaugeFill s_kantera_fill; +GaugeFill s_oxygen_fill; +bool s_light_drop_get; + +f32 fade_row_factor(FadeRow const& r) { + if (r.duration <= 0.0f) { + return r.show ? 1.0f : 0.0f; + } + return dusk::vdt::clamped_fraction(r.timer, r.duration); +} + +void publish_fade_row(FadeRow& c, bool show, f32 duration, f32 full) { + c.used = true; + c.duration = duration > 0.0f ? duration : 1.0f; + if (c.timer > c.duration) { + c.timer = c.duration; + } + c.full = full; + c.show = show; + c.got_free = true; +} + +void publish_follow_row(FadeRow& c) { + c.used = true; + c.got_follow = true; +} + +f32 button_parent_rate(dMeter2Draw_c* d) { + if (s_fade_rows[kFadeRowButtonParent].used) { + return s_fade_rows[kFadeRowButtonParent].full * fade_row_factor(s_fade_rows[kFadeRowButtonParent]); + } + if (d->mpButtonParent != NULL) { + return d->mpButtonParent->getAlphaRate(); + } + return 0.0f; +} + +void reset_follow_intent(FadeRow& r) { + r.got_free = false; + r.got_follow = false; +} + +void resolve_follow_row(FadeRow& r, FadeRow const& parent) { + if (r.got_follow) { + r.follow = true; + } else if (r.follow && r.got_free) { + r.follow = false; + r.timer = fade_row_factor(parent) * r.duration; + } +} + +void step_fade_row(FadeRow& c) { + if (c.used && !c.follow && (c.show ? c.timer < c.duration : c.timer > 0.0f)) { + dusk::vdt::advance_toward_frame(c.timer, c.show ? c.duration : 0.0f, 1.0f); + } +} + +f32 const kFadeDuration = 5.0f; + +f32 fade_full(dMeter2Draw_c* d, FadeRowId id) { + f32 const parent = g_drawHIO.mParentAlpha; + f32 const buttons = parent * g_drawHIO.mMainHUDButtonsAlpha; + f32 const rupee_key = parent * g_drawHIO.mRupeeKeyAlpha; + switch (id) { + case kFadeRowLife: + case kFadeRowKantera: + case kFadeRowOxygen: + case kFadeRowCross: + return parent; + case kFadeRowLightDrop: + return parent * d->field_0x6fc; + case kFadeRowRupee: + return g_drawHIO.mRupeeAlpha * rupee_key; + case kFadeRowKey: + return g_drawHIO.mKeyAlpha * rupee_key; + case kFadeRowButtonParent: + return buttons; + case kFadeRowButtonA: + return g_drawHIO.mButtonAAlpha * buttons; + case kFadeRowButtonB: + return g_drawHIO.mButtonBAlpha * buttons; + case kFadeRowCrossItem: + return parent * g_drawHIO.mButtonCrossITEMAlpha; + case kFadeRowCrossMap: + return parent * g_drawHIO.mButtonCrossMAPAlpha; + case kFadeRowMap: + return 1.0f; + default: + return 0.0f; + } +} + +void apply_parent_rate(CPaneMgrAlpha* pane, FadeRow const& row) { + if (!row.used || pane == NULL || pane->getPanePtr() == NULL) { + return; + } + const f32 rate = row.full * fade_row_factor(row); + if (pane->getAlphaRate() != rate) { + pane->setAlphaRate(rate); + } +} + +void apply_meter_rate(dMeter2Draw_c* d, int index, FadeRow const& row) { + if (!row.used) { + return; + } + d->mMeterAlphaRate[index] = row.full * fade_row_factor(row); +} + +void apply_cross_text(CPaneMgr* text, CPaneMgr** juji, FadeRow const& row) { + if (!row.used) { + return; + } + const f32 factor = fade_row_factor(row); + if (text != NULL) { + text->setAlphaRate(row.full * factor); + } + const f32 juji_rate = g_drawHIO.mParentAlpha * factor; + for (int i = 0; i < 5; i++) { + if (juji[i] != NULL) { + juji[i]->setAlphaRate(juji_rate); + } + } +} + +void apply_light_drop(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowLightDrop].used || d->mpLightDropParent == NULL) { + return; + } + + const f32 parent_rate = s_fade_rows[kFadeRowLightDrop].full * + fade_row_factor(s_fade_rows[kFadeRowLightDrop]); + if (d->mpLightDropParent->getAlphaRate() != parent_rate) { + d->mpLightDropParent->setAlphaRate(parent_rate); + } + + f32 si_parent_rate[2]; + for (int i = 0; i < 2; i++) { + si_parent_rate[i] = g_drawHIO.mLightDrop.mVesselAlpha[i + 2] * parent_rate; + if (d->mpSIParent[i] != NULL) { + d->mpSIParent[i]->setAlphaRate(si_parent_rate[i]); + } + } + + if (s_light_drop_get && s_fade_rows[kFadeRowLightDrop].show) { + if (g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum > 1) { + const f32 duration = g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum; + const f32 remainder = duration - s_fade_rows[kFadeRowLightDrop].timer; + const f32 scale = g_drawHIO.mLightDrop.mDropGetScale - 1.0f; + d->field_0x6f8 = 1.0f + (scale * remainder) / (duration - 1.0f); + } else { + d->field_0x6f8 = 1.0f; + } + } + + const int drop_num = dComIfGs_getLightDropNum(dComIfGp_getStartStageDarkArea()); + const f32 si1 = si_parent_rate[1]; + for (int i = 0; i < 16; i++) { + const f32 rate = i < drop_num ? si1 : si1 * 0.3f; + if (d->mpSIParts[i][1] != NULL) { + d->mpSIParts[i][1]->setAlphaRate(rate); + } + if (d->mpSIParts[i][2] != NULL) { + d->mpSIParts[i][2]->setAlphaRate(rate); + } + } +} + +void apply_rupee(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowRupee].used) { + return; + } + + const f32 factor = fade_row_factor(s_fade_rows[kFadeRowRupee]); + const f32 parent_rate[3] = { + s_fade_rows[kFadeRowRupee].full * factor, + g_drawHIO.mRupeeFrameAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha) * factor, + g_drawHIO.mRupeeFrameAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha) * factor, + }; + + for (int i = 0; i < 3; i++) { + if (d->mpRupeeParent[i] != NULL && d->mpRupeeParent[i]->getAlphaRate() != parent_rate[i]) { + d->mpRupeeParent[i]->setAlphaRate(parent_rate[i]); + } + } +} + +void apply_button_parent(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowButtonParent].used || d->mpButtonParent == NULL) { + return; + } + + apply_parent_rate(d->mpButtonParent, s_fade_rows[kFadeRowButtonParent]); + const f32 parent_rate = s_fade_rows[kFadeRowButtonParent].full * + fade_row_factor(s_fade_rows[kFadeRowButtonParent]); + if (d->mpUzu != NULL) { + d->mpUzu->setAlphaRate(d->mButtonBaseAlpha * parent_rate); + } +} + +void apply_button_a(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowButtonA].used || d->mpButtonA == NULL) { + return; + } + + if (s_fade_rows[kFadeRowButtonA].follow) { + d->setButtonIconAAlpha(0, 0, false); + return; + } + + const f32 factor = fade_row_factor(s_fade_rows[kFadeRowButtonA]); + d->mpButtonA->setAlphaRate(s_fade_rows[kFadeRowButtonA].full * factor); + const f32 text_rate = (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * factor; + for (int i = 0; i < 5; i++) { + if (d->mpAText[i] != NULL) { + d->mpAText[i]->setAlphaRate(text_rate); + } + } +} + +void apply_button_b(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowButtonB].used || d->mpButtonB == NULL) { + return; + } + + if (s_fade_rows[kFadeRowButtonB].follow) { + d->setButtonIconBAlpha(0, 0, false); + return; + } + + const f32 factor = fade_row_factor(s_fade_rows[kFadeRowButtonB]); + const f32 button_rate = s_fade_rows[kFadeRowButtonB].full * factor; + const f32 text_rate = (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * factor; + d->mpButtonB->setAlphaRate(button_rate); + if (d->mpLightB != NULL) { + d->mpLightB->setAlphaRate(button_rate); + } + if (d->mpItemB != NULL) { + d->mpItemB->setAlphaRate(text_rate); + } + for (int i = 0; i < 5; i++) { + if (d->mpBText[i] != NULL) { + d->mpBText[i]->setAlphaRate(text_rate); + } + } +} + +void advance_fade_rows() { + for (int i = 0; i < kFadeRowCount; i++) { + step_fade_row(s_fade_rows[i]); + } +} + +void write_fade_row(dMeter2Draw_c* d, FadeRowId id, bool show) { + if (id >= kFadeRowCount) { + return; + } + publish_fade_row(s_fade_rows[id], show, kFadeDuration, fade_full(d, id)); + if (id == kFadeRowButtonParent && show) { + if (fade_row_factor(s_fade_rows[kFadeRowButtonParent]) >= 1.0f) { + if (dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) == 1) { + dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 2); + } + } + } +} + +void follow_button_a(bool live, bool dim, bool dim_txt) { + s_follow_a = {live, dim, dim_txt}; + if (live) { + publish_follow_row(s_fade_rows[kFadeRowButtonA]); + } +} + +void follow_button_b(bool live, bool dim, bool dim_txt, bool light_on, bool item_visible) { + s_follow_b = {live, dim, dim_txt, light_on, item_visible}; + if (live) { + publish_follow_row(s_fade_rows[kFadeRowButtonB]); + } +} + +void follow_button_xy(int i_no, bool live, bool dim, bool fishing, bool item_visible) { + if (i_no < 0 || i_no > 1) { + return; + } + s_follow_xy[i_no] = {live, dim, fishing, item_visible}; +} + +void chase_midona(bool live, bool hold, f32 z_target, f32 xy_target) { + s_midona = {true, live, hold, z_target, xy_target}; +} + +void seed_gauge(GaugeFill& g, s32 now) { + g.prev = now; +} + +void publish_gauge(GaugeFill& g, s32 now, s32 max) { + g.used = true; + g.now = now; + g.max = max; +} + +f32 gauge_draw(GaugeFill const& g) { + const f32 step = dusk::interp::get_interpolation_step(); + return (f32)g.prev + ((f32)g.now - (f32)g.prev) * step; +} +} // namespace + +f32 dMeter2Draw_c::getMeterGaugeAlphaRate(u8 i_no) { + if (i_no == 1) { + return fade_row_factor(s_fade_rows[kFadeRowKantera]) * g_drawHIO.mParentAlpha; + } + if (i_no == 2) { + return fade_row_factor(s_fade_rows[kFadeRowOxygen]) * g_drawHIO.mParentAlpha; + } + return mMeterAlphaRate[i_no]; +} + +void dMeter2Draw_c::beginExecution() { + mExecuting = true; + reset_follow_intent(s_fade_rows[kFadeRowButtonA]); + reset_follow_intent(s_fade_rows[kFadeRowButtonB]); + seed_gauge(s_kantera_fill, dComIfGp_getItemNowOil()); + seed_gauge(s_oxygen_fill, dComIfGp_getNowOxygen()); +} + +void dMeter2Draw_c::endExecution() { + resolve_follow_row(s_fade_rows[kFadeRowButtonA], s_fade_rows[kFadeRowButtonParent]); + resolve_follow_row(s_fade_rows[kFadeRowButtonB], s_fade_rows[kFadeRowButtonParent]); + mExecuting = false; +} + +void dMeter2Draw_c::publishKantera(s32 now, s32 max) { + publish_gauge(s_kantera_fill, now, max); +} + +void dMeter2Draw_c::publishOxygen(s32 now, s32 max) { + publish_gauge(s_oxygen_fill, now, max); +} + +void dMeter2Draw_c::present() { + apply_parent_rate(this->mpLifeParent, s_fade_rows[kFadeRowLife]); + if (s_fade_rows[kFadeRowLife].used) { + this->setAlphaLifeChange(false); + } + + apply_meter_rate(this, 1, s_fade_rows[kFadeRowKantera]); + if (s_fade_rows[kFadeRowKantera].used) { + this->setAlphaKanteraChange(true); + } + if (s_kantera_fill.used) { + drawKantera(s_kantera_fill.max, gauge_draw(s_kantera_fill), + g_drawHIO.mLanternMeterPosX, g_drawHIO.mLanternMeterPosY); + } + + apply_meter_rate(this, 2, s_fade_rows[kFadeRowOxygen]); + if (s_fade_rows[kFadeRowOxygen].used) { + this->setAlphaOxygenChange(false); + } + if (s_oxygen_fill.used) { + drawOxygen(s_oxygen_fill.max, gauge_draw(s_oxygen_fill), + g_drawHIO.mOxygenMeterPosX, g_drawHIO.mOxygenMeterPosY); + } + + apply_light_drop(this); + if (s_fade_rows[kFadeRowLightDrop].used) { + this->setAlphaLightDropChange(false); + } + + apply_rupee(this); + if (s_fade_rows[kFadeRowRupee].used) { + this->setAlphaRupeeChange(false); + } + + apply_parent_rate(this->mpKeyParent, s_fade_rows[kFadeRowKey]); + if (s_fade_rows[kFadeRowKey].used) { + this->setAlphaKeyChange(false); + } + + apply_button_parent(this); + apply_button_a(this); + apply_button_b(this); + setButtonIconAlpha(0, 0, 0, false); + setButtonIconAlpha(1, 0, 0, false); + if (s_fade_rows[kFadeRowButtonParent].used) { + this->setAlphaButtonChange(false); + } + + apply_parent_rate(this->mpButtonCrossParent, s_fade_rows[kFadeRowCross]); + apply_cross_text(this->mpTextI, this->mpJujiI, s_fade_rows[kFadeRowCrossItem]); + apply_cross_text(this->mpTextM, this->mpJujiM, s_fade_rows[kFadeRowCrossMap]); + + if (!s_midona.used || this->mpButtonMidona == NULL) { + return; + } + + const f32 parent_rate = button_parent_rate(this); + if (s_midona.live) { + if (s_midona.hold) { + this->mButtonZAlpha = s_midona.z_target; + } else { + dusk::vdt::present_addCalc2(&this->mButtonZAlpha, s_midona.z_target, 0.4f, 0.5f, 0.1f); + } + const f32 full = g_drawHIO.mMidnaIconAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + this->mpButtonMidona->setAlpha((u8)(full * (255.0f * this->mButtonZAlpha * parent_rate))); + } else { + this->mButtonZAlpha = 0.0f; + } + dusk::vdt::present_addCalc2(&this->field_0x724, s_midona.xy_target, 0.4f, 0.5f, 0.1f); + this->mpButtonXY[2]->setAlpha((u8)(255.0f * this->field_0x724 * parent_rate)); +} + +void dMeter2Draw_c::presentVessel(f32 i_posX, f32 i_posY, f32 i_scale) { + mLightDropVesselScale = i_scale; + const f32 lightDropScale = mLightDropVesselScale * field_0x6f8 * dGetUserHudScale(); + mpLightDropParent->scale(lightDropScale, lightDropScale); + dAnchorHudScale(mpLightDropParent, HudCorner::TopRight, &i_posX, &i_posY); + mpLightDropParent->paneTrans(i_posX, i_posY); +} + +void dMeter2Draw_c::presentButtonA(f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, f32 i_scale) { + mpButtonA->scale(s_button_a_scale * i_scale, s_button_a_scale * i_scale); + mpButtonA->paneTrans(i_posX, i_posY); + mpTextA->scale(s_button_a_text_scale * i_scale, s_button_a_text_scale * i_scale); + mpTextA->paneTrans(g_drawHIO.mButtonATextPosX + i_textPosX, + g_drawHIO.mButtonATextPosY + i_textPosY + (dusk::version::isLessThanWiiJpn() ? 6.0f : 0.0f)); +} + +void dMeter2Draw_c::presentButtonB(f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, f32 i_scale) { + const int kind = s_button_b_kind; + field_0x730 = s_button_b_scale * i_scale; + mpButtonB->scale(field_0x730 * field_0x734, field_0x730 * field_0x734); + mpButtonB->paneTrans(g_drawHIO.mButtonBPosX + i_posX, g_drawHIO.mButtonBPosY + i_posY); + + field_0x728 = g_drawHIO.mButtonBItemScale[kind] * i_scale; + mpItemB->scale(field_0x728 * field_0x734, field_0x728 * field_0x734); + mpItemB->paneTrans(field_0x6dc + (g_drawHIO.mButtonBItemPosX[kind] + i_posX), field_0x6e0 + (g_drawHIO.mButtonBItemPosY[kind] + i_posY)); + + field_0x72c = g_drawHIO.mItemBBaseScale[0] * i_scale; + mpLightB->scale(field_0x72c * field_0x734, field_0x72c * field_0x734); + mpLightB->paneTrans(g_drawHIO.mItemBBasePosX[0] + i_posX, g_drawHIO.mItemBBasePosY[0] + i_posY); + + mpTextB->scale(s_button_b_text_scale * i_scale, s_button_b_text_scale * i_scale); + mpTextB->paneTrans(g_drawHIO.mButtonBFontPosX + i_textPosX, + g_drawHIO.mButtonBFontPosY + i_textPosY + (dusk::version::isLessThanWiiJpn() ? 4.0f : 0.0f)); +} + +void dMeter2Draw_c::presentMapAlpha(dMeterMap_c* map) { + if (map == NULL) { + return; + } + if (!s_fade_rows[kFadeRowMap].used) { + map->setMapAlpha(0); + return; + } + map->setMapAlpha((u8)(fade_row_factor(s_fade_rows[kFadeRowMap]) * 255.0f)); +} + +void dMeter2Draw_c::showLightDrop(bool show, bool get_path) { + if (show && get_path) { + if (s_fade_rows[kFadeRowLightDrop].timer <= 0.0f) { + Z2GetAudioMgr()->seStart(Z2SE_SY_LIGHT_POT_EQUIP, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); + } + if (fade_row_factor(s_fade_rows[kFadeRowLightDrop]) >= 1.0f) { + dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 0xFF); + } + } + const f32 duration = get_path ? (f32)g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum : kFadeDuration; + publish_fade_row(s_fade_rows[kFadeRowLightDrop], show, duration, fade_full(this, kFadeRowLightDrop)); + s_light_drop_get = get_path; +} + +void dMeter2Draw_c::showCross(bool show) { + write_fade_row(this, kFadeRowCross, show); + write_fade_row(this, kFadeRowCrossItem, show && dMeter2Info_isItemOpenCheck()); + write_fade_row(this, kFadeRowCrossMap, show && dMeter2Info_isMapOpenCheck()); +} + +void dMeter2Draw_c::showMap(bool show) { + write_fade_row(this, kFadeRowMap, show); +} + +bool dMeter2Draw_c::lightDropRowUsed() const { + return s_fade_rows[kFadeRowLightDrop].used; +} + +void dMeter2Draw_c::resetRows() { + mExecuting = false; + for (int i = 0; i < kFadeRowCount; i++) { + s_fade_rows[i] = FadeRow{}; + } + s_follow_a = FollowA{}; + s_follow_b = FollowB{}; + s_follow_xy[0] = FollowXY{}; + s_follow_xy[1] = FollowXY{}; + s_midona = MidonaChase{}; + s_kantera_fill = GaugeFill{}; + s_oxygen_fill = GaugeFill{}; + s_light_drop_get = false; +} diff --git a/src/d/d_meter2_info.cpp b/src/d/d_meter2_info.cpp index 15569a72d8..3bb9ffdcd3 100644 --- a/src/d/d_meter2_info.cpp +++ b/src/d/d_meter2_info.cpp @@ -15,8 +15,10 @@ #include +#if TARGET_PC #include "dusk/version.hpp" #include "helpers/string.hpp" +#endif enum ITEMICON_RES_FILE_ID { ITEMICON_BTI_ARI_MESU_00=0x3, diff --git a/src/d/d_meter2_vdt.inc b/src/d/d_meter2_vdt.inc new file mode 100644 index 0000000000..17f6452335 --- /dev/null +++ b/src/d/d_meter2_vdt.inc @@ -0,0 +1,40 @@ +#include "dusk/interp/user_interface.h" + +#include "d/d_meter_map.h" + +void dMeter2_c::presentAnims() { + if (mpMeterDraw != NULL) { + mpMeterDraw->present(); + + if (!mPresentationTargets.ready) { + return; + } + + if (mpMeterDraw->lightDropRowUsed()) { + dusk::vdt::present_addCalc2(&mVesselPosX, mPresentationTargets.vesselX, 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&mVesselPosY, mPresentationTargets.vesselY, 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&mVesselScale, mPresentationTargets.vesselScale, 0.2f, 1.0f, 0.1f); + dusk::vdt::present_addCalc2(&mVesselAlpha, mPresentationTargets.vesselAlpha, 0.2f, 1.0f, 0.1f); + mpMeterDraw->field_0x6fc = mVesselAlpha; + mpMeterDraw->presentVessel(mVesselPosX, mVesselPosY, mVesselScale); + } + + for (int i = 0; i < 2; i++) { + dusk::vdt::present_addCalc2(&mButtonATalkPosX[i], mPresentationTargets.buttonAX[i], 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&mButtonATalkPosY[i], mPresentationTargets.buttonAY[i], 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&field_0x148[i], mPresentationTargets.buttonBX[i], 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&field_0x150[i], mPresentationTargets.buttonBY[i], 1.0f, 10.0f, 0.1f); + } + dusk::vdt::present_addCalc2(&field_0x15c, mPresentationTargets.crossY, 0.5f, 50.0f, 0.5f); + mpMeterDraw->presentButtonA(mButtonATalkPosX[0], mButtonATalkPosY[0], mButtonATalkPosX[1], mButtonATalkPosY[1], 1.0f); + mpMeterDraw->presentButtonB(field_0x148[0], field_0x150[0], field_0x148[1], field_0x150[1], 1.0f); + mpMeterDraw->drawButtonCross(mPresentationTargets.crossX, field_0x15c); + } +} + +void dMeter2_c::presentMap() { + if (mpMeterDraw == NULL || mpMap == NULL) { + return; + } + mpMeterDraw->presentMapAlpha(mpMap); +} diff --git a/src/d/d_meter_button.cpp b/src/d/d_meter_button.cpp index 56d756ef44..068fcf3b53 100644 --- a/src/d/d_meter_button.cpp +++ b/src/d/d_meter_button.cpp @@ -16,18 +16,35 @@ #include "d/d_msg_out_font.h" #include "d/d_msg_string.h" #include "d/d_pane_class.h" -#include "dusk/frame_interpolation.h" #include -#include "dusk/version.hpp" - #if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" +#include "dusk/version.hpp" #include "helpers/string.hpp" + +namespace { +bool advance_repeat_hit(f32& frame, u8& cycle, f32 period, f32& elapsedFrames) { + const f32 frames = dusk::game_clock::original_frames(); + elapsedFrames = frames; + if (period <= 0.0f || frames <= 0.0f) { + return false; + } + f32 phase = frame + cycle * period; + const bool pulse = phase == 0.0f || phase + frames > 2.0f * period; + dusk::vdt::advance_looping_frame(phase, 1.0f, 2.0f * period); + if (pulse) { + elapsedFrames = phase == 0.0f ? 2.0f * period : phase; + } + cycle = phase >= period; + frame = phase - cycle * period; + return pulse; +} +} // namespace #endif -#if TARGET_PC -#define STR_BUF_LEN 528 -#elif VERSION == VERSION_GCN_JPN +#if TARGET_PC || VERSION == VERSION_GCN_JPN #define STR_BUF_LEN 528 #else #define STR_BUF_LEN 512 @@ -54,6 +71,10 @@ int dMeterButton_c::_execute(u32 i_flags, bool i_drawA, bool i_drawB, bool i_dra bool i_draw3DB, bool i_drawNURE, bool i_drawReel, bool i_drawReel2, bool i_drawAB, bool i_drawTate, bool i_drawNunZ, bool i_drawNunC, bool i_drawBin) { +#if TARGET_PC + mBlinkButton = g_meter2_info.mBlinkButton; + dMeter2Info_resetBlinkButton(); +#endif updateButton(); updateText(i_flags); field_0x4b0 = 0; @@ -231,6 +252,7 @@ int dMeterButton_c::_execute(u32 i_flags, bool i_drawA, bool i_drawB, bool i_dra } } +#if !TARGET_PC for (int i = 0; i < 2; i++) { if (field_0x2f4[i] != 0.0f) { field_0x2fc[i] = field_0x2f4[i]; @@ -244,6 +266,7 @@ int dMeterButton_c::_execute(u32 i_flags, bool i_drawA, bool i_drawB, bool i_dra trans_button(i, field_0x2fc[i]); } +#endif return 1; } @@ -251,6 +274,23 @@ void dMeterButton_c::draw() { J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort(); graf_ctx->setup2D(); +#if TARGET_PC + if (isButtonShowBit(BUTTON_Z_e) && mpButtonZ->getAlphaTimer() < 5.0f) { + mpButtonZ->setAlphaRate(1.0f); + dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonZ, 5); + } + + for (int i = 0; i < 2; i++) { + if (field_0x2f4[i] != 0.0f) { + field_0x2fc[i] = field_0x2f4[i]; + } else { + dusk::vdt::present_chase(&field_0x2fc[i], field_0x2f4[i], 1.0f, 10.0f, 0.1f); + } + + trans_button(i, field_0x2fc[i]); + } +#endif + mpButtonScreen->draw(0.0f, 0.0f, graf_ctx); if (field_0x00c != NULL) { field_0x00c->draw(0.0f, 0.0f, graf_ctx); @@ -284,35 +324,40 @@ void dMeterButton_c::draw() { SAFE_STRCPY(static_cast(mpTm_c[0]->getPanePtr())->getStringPtr(), tmp_buf); } +#if TARGET_PC + const bool blinkA = (mBlinkButton & 1) != 0; + const bool blinkB = (mBlinkButton & 2) != 0; +#endif + for (int i = 0; i < 2; i++) { bool var_r22 = 0; bool var_r23 = 0; + IF_DUSK(f32 repeatFrames = 0.0f); if (field_0x4be[i] == BUTTON_A_e && dComIfGp_isDoSetFlag(4)) { var_r23 = 1; - if (field_0x4b8[i] == 0 && field_0x4bc[i] == 0) { + if (DUSK_IF_ELSE(advance_repeat_hit(field_0x4b8[i], field_0x4bc[i], g_drawHIO.mEmpButton.mRepeatHitFrameNum, repeatFrames), + field_0x4b8[i] == 0 && field_0x4bc[i] == 0)) + { field_0x2e8[i] = 18.0f - g_drawHIO.mEmpButton.mPikariRepeatHitAnimSpeed; } s16 temp_r6 = g_drawHIO.mEmpButton.mRepeatHitFrameNum; s16 temp_r6_2 = g_drawHIO.mEmpButton.mRepeatHitFrameNum / 2; -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - field_0x4b8[i]++; +#if !TARGET_PC + field_0x4b8[i]++; - if (field_0x4b8[i] >= temp_r6) { - field_0x4b8[i] = 0; + if (field_0x4b8[i] >= temp_r6) { + field_0x4b8[i] = 0; - if (field_0x4bc[i] == 0) { - field_0x4bc[i] = 1; - } else { - field_0x4bc[i] = 0; - } + if (field_0x4bc[i] == 0) { + field_0x4bc[i] = 1; + } else { + field_0x4bc[i] = 0; } } +#endif f32 var_f2; if (temp_r6_2 < field_0x4b8[i]) { @@ -326,9 +371,9 @@ void dMeterButton_c::draw() { } else if ((field_0x4be[i] == BUTTON_A_e && (dComIfGp_getDoStatus() == 0x3B || dComIfGp_getDoStatus() == 0x3F || dComIfGp_getDoStatus() == 0x40) && - dMeter2Info_isBlinkButton(1)) || + DUSK_IF_ELSE(blinkA, dMeter2Info_isBlinkButton(1))) || (field_0x4be[i] == BUTTON_B_e && dComIfGp_getAStatus() == 0x3A && - dMeter2Info_isBlinkButton(2))) + DUSK_IF_ELSE(blinkB, dMeter2Info_isBlinkButton(2)))) { var_r22 = 1; if (field_0x2e8[i] <= 0.0f) { @@ -384,22 +429,8 @@ void dMeterButton_c::draw() { } if (var_r3) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) { - mWasListen[i] = var_r22; - mWasRepeat[i] = var_r23; - } else { - var_r22 = mWasListen[i]; - var_r23 = mWasRepeat[i]; - } -#endif if (var_r22) { -#ifdef TARGET_PC - if (field_0x2e8[i] == 18.0f && dusk::frame_interp::get_ui_tick_pending()) -#else - if (field_0x2e8[i] == 18.0f) -#endif - { + if (field_0x2e8[i] == 18.0f) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 0); } @@ -429,7 +460,7 @@ void dMeterButton_c::draw() { g_drawHIO.mEmpButton.mPikariRepeatHitFrontInner, g_drawHIO.mEmpButton.mPikariRepeatHitBackOuter, g_drawHIO.mEmpButton.mPikariRepeatHitBackInner, - g_drawHIO.mEmpButton.mPikariRepeatHitAnimSpeed, 0); + g_drawHIO.mEmpButton.mPikariRepeatHitAnimSpeed, 0 IF_DUSK_ARG(repeatFrames)); } else if (isFastSet(i)) { dMeter2Info_getMeterClass()->getMeterDrawPtr()->drawPikari( vtx.x, vtx.y, &field_0x2e8[i], g_drawHIO.mEmpButton.mPikariFastScale, @@ -451,7 +482,7 @@ void dMeterButton_c::draw() { } } - dMeter2Info_resetBlinkButton(); + IF_NOT_DUSK(dMeter2Info_resetBlinkButton()); } int dMeterButton_c::_delete() { @@ -2085,7 +2116,7 @@ void dMeterButton_c::setAlphaButtonAAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonA, 5); } - if (mpButtonA->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_A_e]) { + if (DUSK_IF_ELSE(true, mpButtonA->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_A_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_A_e] = true; } @@ -2121,7 +2152,7 @@ void dMeterButton_c::setAlphaButtonBAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonB, 5); } - if (mpButtonB->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_B_e]) { + if (DUSK_IF_ELSE(true, mpButtonB->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_B_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_B_e] = true; } @@ -2157,7 +2188,7 @@ void dMeterButton_c::setAlphaButtonRAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonR, 5); } - if (mpButtonR->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_R_e]) { + if (DUSK_IF_ELSE(true, mpButtonR->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_R_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_R_e] = true; } @@ -2176,7 +2207,7 @@ void dMeterButton_c::setAlphaButtonZAnimeMin() { mpButtonZ->setAlphaRate(0.0f); mpButtonZ->alphaAnimeStart(0); - if (mpButtonZ->getAlphaRate() == 0.0f) { + if (DUSK_IF_ELSE(true, mpButtonZ->getAlphaRate() == 0.0f)) { hide_button(BUTTON_Z_e); mPlayedButtonSound[BUTTON_Z_e] = false; } @@ -2188,12 +2219,14 @@ void dMeterButton_c::setAlphaButtonZAnimeMax() { if (dComIfGp_isZSetFlag(1)) { mpButtonZ->setAlphaRate(1.0f); mpButtonZ->alphaAnimeStart(5); +#if !TARGET_PC } else { mpButtonZ->setAlphaRate(1.0f); dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonZ, 5); +#endif } - if (mpButtonZ->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_Z_e]) { + if (DUSK_IF_ELSE(true, mpButtonZ->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_Z_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_Z_e] = true; } @@ -2230,7 +2263,7 @@ void dMeterButton_c::setAlphaButton3DAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButton3D, 5); } - if (mpButton3D->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_3D_e]) { + if (DUSK_IF_ELSE(true, mpButton3D->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_3D_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_3D_e] = true; } @@ -2274,7 +2307,7 @@ void dMeterButton_c::setAlphaButtonCAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonC, 5); } - if (mpButtonC->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_C_e]) { + if (DUSK_IF_ELSE(true, mpButtonC->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_C_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_C_e] = true; } @@ -2339,7 +2372,7 @@ void dMeterButton_c::setAlphaButtonSAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonS, 5); } - if (mpButtonS->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_S_e]) { + if (DUSK_IF_ELSE(true, mpButtonS->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_S_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_S_e] = true; } @@ -2375,7 +2408,7 @@ void dMeterButton_c::setAlphaButtonXAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonX, 5); } - if (mpButtonX->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_X_e]) { + if (DUSK_IF_ELSE(true, mpButtonX->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_X_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_X_e] = true; } @@ -2411,7 +2444,7 @@ void dMeterButton_c::setAlphaButtonYAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonY, 5); } - if (mpButtonY->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_Y_e]) { + if (DUSK_IF_ELSE(true, mpButtonY->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_Y_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_Y_e] = true; } @@ -2450,7 +2483,7 @@ void dMeterButton_c::setAlphaButtonNunAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonNun, 5); } - if (mpButtonNun->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_NUN_e]) { + if (DUSK_IF_ELSE(true, mpButtonNun->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_NUN_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_NUN_e] = true; } @@ -2490,7 +2523,7 @@ void dMeterButton_c::setAlphaButtonRemoAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonRemo, 5); } - if (mpButtonRemo->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_REMO_e]) { + if (DUSK_IF_ELSE(true, mpButtonRemo->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_REMO_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_REMO_e] = true; } @@ -2530,7 +2563,7 @@ void dMeterButton_c::setAlphaButtonRemo2AnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonRemo2, 5); } - if (mpButtonRemo2->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_REMO2_e]) { + if (DUSK_IF_ELSE(true, mpButtonRemo2->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_REMO2_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_REMO2_e] = true; } @@ -2572,7 +2605,7 @@ void dMeterButton_c::setAlphaButtonARAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonAR, 5); } - if (mpButtonAR->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_AR_e]) { + if (DUSK_IF_ELSE(true, mpButtonAR->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_AR_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_AR_e] = true; } @@ -2614,7 +2647,7 @@ void dMeterButton_c::setAlphaButton3DBAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButton3DB, 5); } - if (mpButton3DB->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_3DB_e]) { + if (DUSK_IF_ELSE(true, mpButton3DB->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_3DB_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_3DB_e] = true; } @@ -2656,7 +2689,7 @@ void dMeterButton_c::setAlphaButtonNUREAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonNURE, 5); } - if (mpButtonNURE->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_NURE_e]) { + if (DUSK_IF_ELSE(true, mpButtonNURE->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_NURE_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_NURE_e] = true; } @@ -2698,7 +2731,7 @@ void dMeterButton_c::setAlphaButtonReelAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonReel, 5); } - if (mpButtonReel->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_REEL_e]) { + if (DUSK_IF_ELSE(true, mpButtonReel->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_REEL_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_REEL_e] = true; } @@ -2730,7 +2763,7 @@ void dMeterButton_c::setAlphaButtonReel2AnimeMax() { mpButtonReel2->setAlphaRate(1.0f); mpButtonReel2->alphaAnimeStart(5); - if (mpButtonReel2->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_REEL2_e]) { + if (DUSK_IF_ELSE(true, mpButtonReel2->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_REEL2_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_REEL2_e] = true; } @@ -2762,7 +2795,7 @@ void dMeterButton_c::setAlphaButtonABAnimeMax() { mpButtonAB->setAlphaRate(1.0f); mpButtonAB->alphaAnimeStart(5); - if (mpButtonAB->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_AB_e]) { + if (DUSK_IF_ELSE(true, mpButtonAB->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_AB_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_AB_e] = true; } @@ -2794,7 +2827,7 @@ void dMeterButton_c::setAlphaButtonTateAnimeMax() { mpButtonTate->setAlphaRate(1.0f); mpButtonTate->alphaAnimeStart(5); - if (mpButtonTate->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_TATE_e]) { + if (DUSK_IF_ELSE(true, mpButtonTate->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_TATE_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_TATE_e] = true; } @@ -2836,7 +2869,7 @@ void dMeterButton_c::setAlphaButtonNunZAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonNunZ, 5); } - if (mpButtonNunZ->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_NUNZ_e]) { + if (DUSK_IF_ELSE(true, mpButtonNunZ->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_NUNZ_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_NUNZ_e] = true; } @@ -2878,7 +2911,7 @@ void dMeterButton_c::setAlphaButtonNunCAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonNunC, 5); } - if (mpButtonNunC->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_NUNC_e]) { + if (DUSK_IF_ELSE(true, mpButtonNunC->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_NUNC_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_NUNC_e] = true; } @@ -2920,7 +2953,7 @@ void dMeterButton_c::setAlphaButtonBinAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonBin, 5); } - if (mpButtonBin->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_BIN_e]) { + if (DUSK_IF_ELSE(true, mpButtonBin->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_BIN_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_BIN_e] = true; } diff --git a/src/d/d_meter_haihai.cpp b/src/d/d_meter_haihai.cpp index 5ea5dad5b7..6eadfca9d9 100644 --- a/src/d/d_meter_haihai.cpp +++ b/src/d/d_meter_haihai.cpp @@ -11,7 +11,10 @@ #include "d/d_com_inf_game.h" #include "d/d_meter_HIO.h" #include "d/d_pane_class.h" -#include "dusk/frame_interpolation.h" + +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif dMeterHaihai_c::dMeterHaihai_c(u8 i_type) { mType = i_type; @@ -56,12 +59,13 @@ int dMeterHaihai_c::_create() { int dMeterHaihai_c::_execute(u32 i_flags) { mFlags = i_flags; - updateHaihai(); + DUSK_IF_ELSE(setScale(1.0f), updateHaihai()); alphaAnimeHaihai(i_flags); return 1; } void dMeterHaihai_c::draw() { + IF_DUSK(updateHaihai()); if ((mFlags & 8) || (mFlags & 0x10) || (mFlags & 0x1000000) || (mFlags & 0x800000) || (mFlags & 0x20) || (mFlags & 0x4000000)) { @@ -129,6 +133,7 @@ void dMeterHaihai_c::draw() { } void dMeterHaihai_c::drawHaihai(u8 i_direction) { + IF_DUSK(updateHaihai()); f32 center_x = FB_WIDTH_BASE / 2; f32 center_y = FB_HEIGHT_BASE / 2; @@ -184,6 +189,7 @@ void dMeterHaihai_c::drawHaihai(u8 i_direction) { } void dMeterHaihai_c::drawHaihai(u8 i_direction, f32 i_posX, f32 i_posY, f32 param_3, f32 param_4) { + IF_DUSK(updateHaihai()); if (mType == 1) { i_posX += (3.0f + g_drawHIO.mScrollArrowCenterPosX); i_posY += (g_drawHIO.mScrollArrowCenterPosY - 5.0f); @@ -281,31 +287,36 @@ void dMeterHaihai_c::updateHaihai() { playBtkAnime(mpCursorBtk); playBpkAnime(mpCursorBpk); mpHaihaiScreen->animation(); - setScale(1.0f); + IF_NOT_DUSK(setScale(1.0f)); } void dMeterHaihai_c::playBckAnime(J2DAnmTransformKey* i_bck) { if (checkPlayAnime(1)) { if (i_bck != NULL) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (mType == 4) { - mBckFrame += g_drawHIO.mWiiLockArrowBCKAnimSpeed; - } else { - mBckFrame += g_drawHIO.mScrollArrowBCKAnimSpeed; - } - - if (mBckFrame >= i_bck->getFrameMax()) { - mBckFrame -= i_bck->getFrameMax(); - } +#if TARGET_PC + f32 speed; + if (mType == 4) { + speed = g_drawHIO.mWiiLockArrowBCKAnimSpeed; + } else { + speed = g_drawHIO.mScrollArrowBCKAnimSpeed; } + dusk::vdt::present_looping(mBckFrame, i_bck, speed); +#else + if (mType == 4) { + mBckFrame += g_drawHIO.mWiiLockArrowBCKAnimSpeed; + } else { + mBckFrame += g_drawHIO.mScrollArrowBCKAnimSpeed; + } + + if (mBckFrame >= i_bck->getFrameMax()) { + mBckFrame -= i_bck->getFrameMax(); + } +#endif } else { mBtkFrame = 1.0f; } - i_bck->setFrame(mBckFrame); + IF_NOT_DUSK(i_bck->setFrame(mBckFrame)); mpHaihaiScreen->search(MULTI_CHAR('n_anim'))->setAnimation(i_bck); mpHaihaiScreen->search(MULTI_CHAR('n_anim'))->animationTransform(); mpHaihaiScreen->search(MULTI_CHAR('n_anim'))->setAnimation((J2DAnmTransform*)NULL); @@ -315,25 +326,30 @@ void dMeterHaihai_c::playBckAnime(J2DAnmTransformKey* i_bck) { void dMeterHaihai_c::playBtkAnime(J2DAnmTextureSRTKey* i_btk) { if (checkPlayAnime(2)) { if (i_btk != NULL) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (mType == 4) { - mBtkFrame += g_drawHIO.mWiiLockArrowBTKAnimSpeed; - } else { - mBtkFrame += g_drawHIO.mScrollArrowBTKAnimSpeed; - } - - if (mBtkFrame >= i_btk->getFrameMax()) { - mBtkFrame -= i_btk->getFrameMax(); - } +#if TARGET_PC + f32 speed; + if (mType == 4) { + speed = g_drawHIO.mWiiLockArrowBTKAnimSpeed; + } else { + speed = g_drawHIO.mScrollArrowBTKAnimSpeed; } + dusk::vdt::present_looping(mBtkFrame, i_btk, speed); +#else + if (mType == 4) { + mBtkFrame += g_drawHIO.mWiiLockArrowBTKAnimSpeed; + } else { + mBtkFrame += g_drawHIO.mScrollArrowBTKAnimSpeed; + } + + if (mBtkFrame >= i_btk->getFrameMax()) { + mBtkFrame -= i_btk->getFrameMax(); + } +#endif } else { mBtkFrame = 1.0f; } - i_btk->setFrame(mBtkFrame); + IF_NOT_DUSK(i_btk->setFrame(mBtkFrame)); mpHaihaiScreen->search(MULTI_CHAR('yaji00'))->setAnimation(i_btk); mpHaihaiScreen->search(MULTI_CHAR('yaji01'))->setAnimation(i_btk); } @@ -342,25 +358,30 @@ void dMeterHaihai_c::playBtkAnime(J2DAnmTextureSRTKey* i_btk) { void dMeterHaihai_c::playBpkAnime(J2DAnmColor* i_bpk) { if (checkPlayAnime(0)) { if (i_bpk != NULL) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (mType == 4) { - mBpkFrame += g_drawHIO.mWiiLockArrowBPKAnimSpeed; - } else { - mBpkFrame += g_drawHIO.mScrollArrowBPKAnimSpeed; - } - - if (mBpkFrame >= i_bpk->getFrameMax()) { - mBpkFrame -= i_bpk->getFrameMax(); - } +#if TARGET_PC + f32 speed; + if (mType == 4) { + speed = g_drawHIO.mWiiLockArrowBPKAnimSpeed; + } else { + speed = g_drawHIO.mScrollArrowBPKAnimSpeed; } + dusk::vdt::present_looping(mBpkFrame, i_bpk, speed); +#else + if (mType == 4) { + mBpkFrame += g_drawHIO.mWiiLockArrowBPKAnimSpeed; + } else { + mBpkFrame += g_drawHIO.mScrollArrowBPKAnimSpeed; + } + + if (mBpkFrame >= i_bpk->getFrameMax()) { + mBpkFrame -= i_bpk->getFrameMax(); + } +#endif } else { mBpkFrame = 1.0f; } - i_bpk->setFrame(mBpkFrame); + IF_NOT_DUSK(i_bpk->setFrame(mBpkFrame)); mpHaihaiScreen->search(MULTI_CHAR('npc_l1'))->setAnimation(i_bpk); mpHaihaiScreen->search(MULTI_CHAR('yaji_l'))->setAnimation(i_bpk); } diff --git a/src/d/d_meter_hakusha.cpp b/src/d/d_meter_hakusha.cpp index aa03e1755a..a9fc1421ad 100644 --- a/src/d/d_meter_hakusha.cpp +++ b/src/d/d_meter_hakusha.cpp @@ -15,6 +15,10 @@ #include "d/d_pane_class.h" #include +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif + dMeterHakusha_c::dMeterHakusha_c(void* i_screen) { field_0x004 = (J2DScreen*)i_screen; _create(); @@ -115,6 +119,20 @@ void dMeterHakusha_c::draw() { mpButtonScreen->draw(0.0f, 0.0f, graf_ctx); for (int i = 0; i < getHakushaNum(); i++) { +#if TARGET_PC + if (mHakushaAnimFrame[i] > 0.0f) { + if (mHakushaStatus[i] == 0) { + dusk::vdt::advance_toward_frame(mHakushaAnimFrame[i], 28.0f, g_drawHIO.mSpurIconPikariAnimSpeed); + } else { + dusk::vdt::advance_toward_frame(mHakushaAnimFrame[i], 28.0f, g_drawHIO.mSpurIconRevivePikariAnimSpeed); + } + + if (mHakushaAnimFrame[i] >= 28.0f) { + mHakushaAnimFrame[i] = 0.0f; + } + } +#endif + if (mHakushaData[i].flags & 1) { mpHakushaOn->show(); } else { @@ -226,6 +244,7 @@ void dMeterHakusha_c::updateHakusha() { } for (int i = 0; i < getHakushaNum(); i++) { +#if !TARGET_PC if (mHakushaAnimFrame[i] > 0.0f) { if (mHakushaStatus[i] == 0) { mHakushaAnimFrame[i] += g_drawHIO.mSpurIconPikariAnimSpeed; @@ -237,6 +256,7 @@ void dMeterHakusha_c::updateHakusha() { mHakushaAnimFrame[i] = 0.0f; } } +#endif mHakushaData[i].pos_x = abtn_x_offset; mHakushaData[i].pos_y = abtn_y_offset; diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index d526d7877f..e5fef0783e 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -16,14 +16,19 @@ #include "f_op/f_op_overlap_mng.h" #include "m_Do/m_Do_controller_pad.h" #include "d/d_camera.h" -#if TARGET_PC -#include "dusk/settings.h" -#include -#endif #include #if TARGET_PC #include "dusk/action_bindings.h" +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" +#include "dusk/settings.h" + +#include "d/d_meter2.h" + +#include "JSystem/JUtility/JUTPalette.h" + +#include namespace { @@ -489,15 +494,18 @@ void dMeterMap_c::_create(J2DScreen* unused) { if (mMapIsInside != 0) { mMapIsInside = 1; mSlidePositionOffset = getDispPosInside_OffsetX(); + IF_DUSK(mSlidePositionOffsetTarget = mSlidePositionOffset); dMeter2Info_setMapStatus(1); } else { mMapIsInside = 0; mSlidePositionOffset = getDispPosOutSide_OffsetX(); + IF_DUSK(mSlidePositionOffsetTarget = mSlidePositionOffset); dMeter2Info_setMapStatus(0); } field_0x2e = 0; mSlidePositionOffset = 0; + IF_DUSK(mSlidePositionOffsetTarget = 0); field_0x30 = 0; /* dSv_event_flag_c::M_085 - Twilight Hyrule Field - Midna dialogue right before Boss Bug's Tear of Light appears */ field_0x2b = dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[118]); @@ -506,11 +514,13 @@ void dMeterMap_c::_create(J2DScreen* unused) { void dMeterMap_c::setDispPosOutSide() { mMapIsInside = 0; mSlidePositionOffset = getDispPosOutSide_OffsetX(); + IF_DUSK(mSlidePositionOffsetTarget = mSlidePositionOffset); } void dMeterMap_c::setDispPosInSide() { mMapIsInside = 1; mSlidePositionOffset = getDispPosInside_OffsetX(); + IF_DUSK(mSlidePositionOffsetTarget = mSlidePositionOffset); } void dMeterMap_c::_delete() { @@ -572,6 +582,8 @@ void dMeterMap_c::_move(u32 param_0) { } if (mMapIsInside != 0) { + IF_DUSK(mSlidePositionOffsetTarget = getDispPosInside_OffsetX()); +#if !TARGET_PC if (mSlidePositionOffset != getDispPosInside_OffsetX()) { if (!cLib_addCalcAngleS(&mSlidePositionOffset, getDispPosInside_OffsetX(), 2, 60, 10)) { #if DEBUG @@ -579,8 +591,10 @@ void dMeterMap_c::_move(u32 param_0) { #endif } } +#endif } else { - cLib_addCalcAngleS(&mSlidePositionOffset, getDispPosOutSide_OffsetX(), 2, 60, 10); + IF_DUSK(mSlidePositionOffsetTarget = getDispPosOutSide_OffsetX()); + IF_NOT_DUSK(cLib_addCalcAngleS(&mSlidePositionOffset, getDispPosOutSide_OffsetX(), 2, 60, 10)); } Vec map_pos = dMapInfo_n::getMapPlayerPos(); @@ -622,6 +636,13 @@ void dMeterMap_c::_draw() { } void dMeterMap_c::draw() { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + if (dMeter2_c* meter = dMeter2Info_getMeterClass()) { + meter->presentMap(); + } + } +#endif if ( #if DEBUG !g_meter_mapHIO.mMapDisplayProhibited && @@ -635,6 +656,12 @@ void dMeterMap_c::draw() { f32 drawPosX = mDrawPosX; f32 drawPosY = mDrawPosY; +#if TARGET_PC + dusk::vdt::present_addCalc(&mSlidePositionOffset, mSlidePositionOffsetTarget, 0.5f, 60.0f, 10.0f); + mDrawPosX = mSlidePositionOffset + getMapDispEdgeLeftX_Layout(); + drawPosX = mDrawPosX; +#endif + u8 alpha = mMapAlpha; #if DEBUG if (g_meter_mapHIO.mMapAlphaTest) { @@ -644,6 +671,11 @@ void dMeterMap_c::draw() { mMapJ2DPicture->setAlpha(alpha); #if TARGET_PC + // Ensure minimap palette gets reuploaded since it is modified for the pulsating border + // effect. + JUTPalette* pPalette = mMapJ2DPicture->getTexture(0)->getPalette(); + pPalette->dataUploaded(); + // Scale the minimap with the user HUD scale and shift down so its bottom-left // corner stays anchored to the same screen position as at scale 1.0. const f32 userHudScale = dGetUserHudScale(); diff --git a/src/d/d_meter_string.cpp b/src/d/d_meter_string.cpp index c99e7c564e..0e19fa1c04 100644 --- a/src/d/d_meter_string.cpp +++ b/src/d/d_meter_string.cpp @@ -16,9 +16,12 @@ #include "d/d_meter2_info.h" #include "d/d_meter_HIO.h" #include "d/d_pane_class.h" -#include "dusk/frame_interpolation.h" #include +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif + dMeterString_c::dMeterString_c(int i_stringID) { mpMapArchive = dComIfGp_getAllMapArchive(); field_0x28 = 0; @@ -107,26 +110,19 @@ void dMeterString_c::draw() { if (mAnimFrame < 60.0f) { #if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - mAnimFrame += g_drawHIO.mMiniGame.mReadyFightTextAnimSpeed; - if (mAnimFrame > 60.0f) { - mAnimFrame = 60.0f; - } + dusk::vdt::advance_toward_frame(mAnimFrame, 60.0f, g_drawHIO.mMiniGame.mReadyFightTextAnimSpeed); +#else + mAnimFrame += g_drawHIO.mMiniGame.mReadyFightTextAnimSpeed; + if (mAnimFrame > 60.0f) { + mAnimFrame = 60.0f; } +#endif playBckAnimation(mAnimFrame); } else if (mAnimFrame < (f32)g_drawHIO.mMiniGame.mReadyFightTextWaitFrames + 60.0f) { -#if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - mAnimFrame += var_f30; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(mAnimFrame, (f32)g_drawHIO.mMiniGame.mReadyFightTextWaitFrames + 60.0f, 1.0f), mAnimFrame += var_f30); } else if (mAnimFrame < var_f31) { -#if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - mAnimFrame += var_f30; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(mAnimFrame, var_f31, 1.0f), mAnimFrame += var_f30); var_f30 = acc(g_drawHIO.mMiniGame.field_0x172, var_f31 - mAnimFrame, 0); } @@ -140,21 +136,13 @@ void dMeterString_c::draw() { if (mPikariAnimFrame > 0.0f) { drawPikari(); } else if (mPikariAnimFrame == -1.0f && -#if TARGET_PC - dusk::frame_interp::get_ui_tick_pending() && -#endif mAnimFrame > g_drawHIO.mMiniGame.mReadyFightPikariAppearFrames) { mPikariAnimFrame = 18.0f - g_drawHIO.mMiniGame.mReadyFightPikariAnimSpeed; } -#if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (mAnimFrame >= var_f31) { - dMeter2Info_resetMeterString(); - } + if (mAnimFrame >= var_f31) { + dMeter2Info_resetMeterString(); } } } diff --git a/src/d/d_model.cpp b/src/d/d_model.cpp index bdff472367..c236b5f2db 100644 --- a/src/d/d_model.cpp +++ b/src/d/d_model.cpp @@ -6,7 +6,7 @@ #include "d/d_com_inf_game.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/interp/frame_interpolation.h" #endif void dMdl_c::draw() { @@ -52,7 +52,7 @@ void dMdl_c::entryObj(dMdl_obj_c* i_obj) { #ifdef TARGET_PC // if field_0x1a is false, this dMdl_c is not in the drawlist // if true, we need to make sure with interp enabled - if (dusk::frame_interp::is_enabled() && field_0x1a) { + if (dusk::interp::is_enabled() && field_0x1a) { auto pkt = dComIfGd_getListPacket()->mpBuffer[0]; while (pkt && pkt != this) { pkt = pkt->getNextPacket(); diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp index 4358d6d5d2..ee559f16d2 100644 --- a/src/d/d_msg_class.cpp +++ b/src/d/d_msg_class.cpp @@ -1,21 +1,22 @@ #include "d/dolzel.h" // IWYU pragma: keep +#include "d/d_msg_class.h" #include #include -#include "JSystem/J2DGraph/J2DTextBox.h" -#include "JSystem/JUtility/JUTFont.h" -#include "d/d_lib.h" #include "d/d_meter2_info.h" -#include "d/d_msg_class.h" #include "d/d_msg_object.h" -#include "d/d_msg_out_font.h" #include "d/d_msg_unit.h" -#include "dusk/version.hpp" +#include "JSystem/J2DGraph/J2DTextBox.h" +#include "d/d_msg_out_font.h" #include "m_Do/m_Do_graphic.h" +#include "d/d_lib.h" +#include "JSystem/JUtility/JUTFont.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/scope_guard.hpp" +#include "dusk/version.hpp" #include "helpers/bits.hpp" static bool read_full_color(const void* data, u32 size, u32& ccColor, u32& gcColor) { @@ -31,9 +32,7 @@ static bool read_full_color(const void* data, u32 size, u32& ccColor, u32& gcCol } return false; } -#endif -#if TARGET_PC #define CHAR_CODE_MALE_ICON (dusk::version::isRegionJpn() ? 0x8189 : 0xB2) #define CHAR_CODE_FEMALE_ICON (dusk::version::isRegionJpn() ? 0x818A : 0xB3) #define CHAR_CODE_STAR_ICON (dusk::version::isRegionJpn() ? 0x819A : 0xB1) @@ -909,6 +908,16 @@ bool jmessage_tReference::isLightEnd() { return false; } +#if TARGET_PC +void jmessage_tReference::addCharAlpha() { + dusk::vdt::advance_toward_frame(mCharAlpha, 255.0f, mAddCharAlpha); +} + +void jmessage_tReference::addCharAllAlphaRate() { + mCharAllAlphaRate = std::min(mCharAllAlphaRate + mAddCharAllAlphaRate, 1.0f); +} +#endif + void jmessage_tReference::decideOutFontRupeeColor(int i_inputVal) { int price_diff = getObjectPtr()->getNowTotalPrice() - getObjectPtr()->getNowTotalPayment(); diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 072bd5039b..afed0a8862 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -24,15 +24,12 @@ #include "f_op/f_op_msg_mng.h" #include #include - -#include "JSystem/JKernel/JKRExpHeap.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_lib.h" +#include "JSystem/JKernel/JKRExpHeap.h" #if TARGET_PC -#include -#include -#include +#include "dusk/interp/user_interface.h" #include "dusk/language.hpp" #include "dusk/logging.h" #include "dusk/menu_pointer.h" @@ -40,6 +37,21 @@ #include "dusk/mods/svc/flow.hpp" #include "dusk/settings.h" #include "dusk/version.hpp" + +#include + +#include +#include +#include + +namespace { +struct MessageAnimation { + int phase = -1; + dusk::vdt::FrameAnimation frame, alpha; +}; + +absl::flat_hash_map sMessageAnimations; +} // namespace #endif static void dMsgObject_addFundRaising(s16 param_0); @@ -319,6 +331,7 @@ dMsgObject_HIO_c::dMsgObject_HIO_c() { int dMsgObject_c::_create(msg_class* param_1) { #if TARGET_PC + sMessageAnimations.erase(this); if (dusk::version::isRegionJpn()) g_MsgObject_HIO_c.mBoxTalkScaleX = 1.1f; else @@ -569,7 +582,100 @@ int dMsgObject_c::_draw() { return 1; } +#if TARGET_PC +static void startAnimation(dMsgObject_c& message, int phase, f32 from, f32 target) { + auto& animations = sMessageAnimations[&message]; + auto& animation = animations.frame; + if (animations.phase != phase) { + animation.start(from, target); + } else { + animation.approach(from, target); + } + animations.phase = phase; +} + +void dMsgObject_c::presentAnims() { + if (mpScrnDraw == NULL || mpOutFont == NULL || !dusk::game_clock::g_frameTiming.interpolating) { + return; + } + const auto found = sMessageAnimations.find(this); + if (found == sMessageAnimations.end()) { + return; + } + auto& animations = found->second; + const u16 status = getStatusLocal(); + if (animations.phase != status) { + return; + } + + auto& animation = animations.frame; + const f32 frame = animation.advance(field_0x16a); + f32 target = 0.0f; + if (status == 2) { + if (isPlaceMessage() || isStaffMessage()) { + target = g_MsgObject_HIO_c.mStageTitleFadeIn; + } else if (isBossMessage()) { + target = g_MsgObject_HIO_c.mBossNameFadeIn; + } else if (isBookMessage()) { + target = g_MsgObject_HIO_c.mBoxAppearFrame + g_MsgObject_HIO_c.mWaitFrame + + g_MsgObject_HIO_c.mLightAppearFrame; + } else { + target = g_MsgObject_HIO_c.mBoxAppearFrame; + } + const f32 ratio = dusk::vdt::clamped_fraction(frame, target); + mpScrnDraw->fukiAlpha(ratio); + mpOutFont->setAlphaRatio(ratio); + if (isKanbanMessage() || isPlaceMessage() || isStaffMessage() || isBossMessage()) { + mpScrnDraw->fukiScale(ratio); + } else if (isBookMessage()) { + s16 waitEnd = g_MsgObject_HIO_c.mBoxAppearFrame + g_MsgObject_HIO_c.mWaitFrame; + if (frame >= waitEnd && frame <= waitEnd + g_MsgObject_HIO_c.mLightAppearFrame) { + f32 scale = dusk::vdt::clamped_fraction(frame - waitEnd, g_MsgObject_HIO_c.mLightAppearFrame); + mpScrnDraw->fukiScale(scale); + } + } else { + mpScrnDraw->fukiScale(1.0f); + } + } else if (status == 17) { + if (isKanbanMessage() || isBookMessage()) { + target = g_MsgObject_HIO_c.field_0x304; + } else if (isPlaceMessage() || isStaffMessage()) { + target = g_MsgObject_HIO_c.mStageTitleFadeOut; + } else if (isBossMessage()) { + target = g_MsgObject_HIO_c.mBossNameFadeOut; + } else { + target = 5.0f; + } + f32 ratio = dusk::vdt::clamped_fraction(frame, target); + mpScrnDraw->fukiAlpha(1.0f - ratio); + if (isBookMessage()) { + mpScrnDraw->fukiScale(1.0f - ratio); + } + mpOutFont->setAlphaRatio(1.0f - ratio); + } else if (status == 6) { + if (isBookMessage() && frame > 0) { + f32 alpha = (10 - frame) / 10.0f; + mpScrnDraw->fontAlpha(alpha); + mpOutFont->setAlphaRatio(alpha); + } + jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference(); + if (pRef->getCharAllAlphaRate() < 1.0f) { + f32 alpha = animations.alpha.advance(pRef->getCharAllAlphaRate()); + mpScrnDraw->setCharAlphaRate(alpha); + mpOutFont->setAlphaRatio(alpha); + } + } else if (status == 5 && isBookMessage() && (field_0x199 || frame > 0)) { + if (frame <= 10) { + f32 alpha = (10 - frame) / 10.0f; + mpScrnDraw->fontAlpha(alpha); + mpOutFont->setAlphaRatio(alpha); + } + } +} +#endif + int dMsgObject_c::_delete() { + IF_DUSK(sMessageAnimations.erase(this)); mpResCont->destroyResource_all(); if (mpScrnDraw != NULL) { JKR_DELETE(mpScrnDraw); @@ -959,6 +1065,13 @@ void dMsgObject_c::waitProc() { } void dMsgObject_c::openProc() { +#if TARGET_PC + auto& animations = sMessageAnimations[this]; + if (field_0x16a == 0) { + animations.phase = -1; + animations.alpha = {}; + } +#endif if (isMidonaMessage()) { bool uVar12 = 0; if (field_0x16a == 0) { @@ -985,7 +1098,7 @@ void dMsgObject_c::openProc() { field_0x16a = 9; } if (mpRefer->getMsgID() == 0x7fa) { - mpScrnDraw->selectAnimeMove(2, getSelectCursorPosLocal(), uVar12); + mpScrnDraw->selectAnimeMove(DUSK_IF_ELSE(3, 2), getSelectCursorPosLocal(), uVar12); } else { if (getSelectCursorPosLocal() != 0xff) { mpScrnDraw->selectAnimeMove(2, getSelectCursorPosLocal() + 1, uVar12); @@ -1041,7 +1154,9 @@ void dMsgObject_c::openProc() { } mpRenProc->setTextInitPos(mpScrnDraw->getTextBoxPosX(), mpScrnDraw->getTextBoxPosY()); mpRenProc->setTextScale(mpScrnDraw->getTextBoxScaleX(), mpScrnDraw->getTextBoxScaleY()); + IF_DUSK(startAnimation(*this, 2, field_0x16a - 1.0f, sVar7)); if (field_0x16a >= sVar7) { + IF_DUSK(animations.frame.finish(field_0x16a)); mpScrnDraw->fukiTrans(0.0f, 0.0f); for (int i = 0; i < 3; i++) { mpRenProc->setSelTextInitPos(i, mpScrnDraw->getSelTextBoxPosX(i), @@ -1061,25 +1176,44 @@ void dMsgObject_c::openProc() { } void dMsgObject_c::outnowProc() { + IF_DUSK(auto& animations = sMessageAnimations[this]); mpRefer->shiftCharCountBuffer(); - if (isBookMessage() && field_0x16a > 0) { + if (isBookMessage() && field_0x16a DUSK_IF_ELSE(>, !=) 0) { field_0x16a--; +#if TARGET_PC + startAnimation(*this, 6, field_0x16a + 1.0f, 0.0f); + if (field_0x16a == 0) { + animations.frame.finish(0.0f); + } +#endif f32 alpha = (10 - field_0x16a) / 10.0f; mpScrnDraw->fontAlpha(alpha); mpOutFont->setAlphaRatio(alpha); - if (field_0x16a > 0) { + if (field_0x16a DUSK_IF_ELSE(>, !=) 0) { return; } } jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference(); if (pRef->getCharAllAlphaRate() < 1.0f) { + IF_DUSK(const f32 before = pRef->getCharAllAlphaRate()); if (mDoCPd_c::getTrigA(0)) { pRef->setCharAllAlphaRate(1.0f); } else { pRef->addCharAllAlphaRate(); } f32 alpha = pRef->getCharAllAlphaRate(); +#if TARGET_PC + if (animations.phase != 6) { + animations.frame.finish(field_0x16a); + } + animations.phase = 6; + if (alpha >= 1.0f) { + animations.alpha.finish(alpha); + } else { + animations.alpha.approach(before, 1.0f, alpha - before); + } +#endif mpScrnDraw->setCharAlphaRate(alpha); mpOutFont->setAlphaRatio(alpha); } else if (mpRefer->isLightEnd()) { @@ -1127,17 +1261,20 @@ void dMsgObject_c::outnowProc() { } void dMsgObject_c::outwaitProc() { + IF_DUSK(auto& animations = sMessageAnimations[this]); jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference(); mpScrnDraw->arwAnimeMove(); if (isBookMessage()) { - if (isSend() || field_0x16a > 0) { + if (isSend() || field_0x16a DUSK_IF_ELSE(>, !=) 0) { field_0x16a++; + IF_DUSK(startAnimation(*this, 5, field_0x16a - 1.0f, 10.0f)); if (field_0x16a <= 10) { f32 alpha = (10 - field_0x16a) / 10.0f; mpScrnDraw->fontAlpha(alpha); mpOutFont->setAlphaRatio(alpha); if (field_0x16a >= 10) { + IF_DUSK(animations.frame.finish(10.0f)); field_0x172++; mpRefer->setPageNum(field_0x172); mpCtrl->render_synchronize(); @@ -1187,6 +1324,10 @@ void dMsgObject_c::continueProc() { updateEquipBombInfoLocal(); offAutoMessageFlagLocal(); setMessageIndex(field_0x100->msg_idx, field_0x100->select_msg_idx, true); +#if TARGET_PC + sMessageAnimations.erase(this); + mpOutFont->setAlphaRatio(1.0f); +#endif mpScrnDraw->fukiPosCalc(pRef->getFukiPosType()); SAFE_STRCPY(pRef->getTextPtr(), ""); SAFE_STRCPY(pRef->getTextSPtr(), ""); @@ -1230,11 +1371,7 @@ void dMsgObject_c::selectProc() { pRef->setSelectPos(pointerChoice); } #endif - if (mDoCPd_c::getTrigA(0) -#if TARGET_PC - || pointerConfirm -#endif - ) { + if (mDoCPd_c::getTrigA(0) IF_DUSK(|| pointerConfirm)) { if (getSelectCursorPosLocal() != 0xff) { field_0x1a3 = 1; } @@ -1256,15 +1393,13 @@ void dMsgObject_c::selectProc() { } field_0x1a3 = 2; } -#ifndef TARGET_PC - jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference(); -#endif + IF_NOT_DUSK(jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference()); if (getStatusLocal() == 8) { if (isMidonaMessage() && field_0x1a3 != 0) { if (field_0x1a3 == 2 && getSelectCancelPos() == 3) { iVar8 = true; } else { - while (!iVar8) { + IF_NOT_DUSK(while (!iVar8)) { if (getSelectCursorPosLocal() != 0xff) { iVar8 = mpScrnDraw->selectAnimeMove(2, getSelectCursorPosLocal() + 1, uVar7); @@ -1288,7 +1423,7 @@ void dMsgObject_c::selectProc() { if (field_0x1a3 == 2 && getSelectCancelPos() == 4) { iVar8 = true; } else { - while (!iVar8) { + IF_NOT_DUSK(while (!iVar8)) { iVar8 = mpScrnDraw->selectAnimeMove(3, getSelectCursorPosLocal(), uVar7); } } @@ -1414,6 +1549,7 @@ void dMsgObject_c::finishProc() { void dMsgObject_c::endProc() { field_0x16a++; + IF_DUSK(auto& animations = sMessageAnimations[this]); s16 sVar4 = 5; if (isKanbanMessage() || isBookMessage()) { sVar4 = g_MsgObject_HIO_c.field_0x304; @@ -1422,6 +1558,7 @@ void dMsgObject_c::endProc() { } else if (isBossMessage()) { sVar4 = g_MsgObject_HIO_c.mBossNameFadeOut; } + IF_DUSK(startAnimation(*this, 17, field_0x16a - 1.0f, sVar4)); f32 dVar6 = (f32)field_0x16a / sVar4; mpScrnDraw->fukiAlpha(1.0f - dVar6); if (isBookMessage()) { @@ -1429,6 +1566,7 @@ void dMsgObject_c::endProc() { } mpOutFont->setAlphaRatio(1.0f - dVar6); if (field_0x16a >= sVar4) { + IF_DUSK(animations.frame.finish(field_0x16a)); mpScrnDraw->arwAnimeInit(); mpScrnDraw->dotAnimeInit(); if (mNoDemoFlag && !field_0x4d4) { @@ -1720,6 +1858,7 @@ u16 dMsgObject_c::getStatusLocal() { } void dMsgObject_c::delete_screen(bool param_1) { + IF_DUSK(sMessageAnimations.erase(this)); if (mpOutFont != NULL) { JKR_DELETE(mpOutFont); mpOutFont = NULL; diff --git a/src/d/d_msg_out_font.cpp b/src/d/d_msg_out_font.cpp index 79bbad5ebb..9cab28a328 100644 --- a/src/d/d_msg_out_font.cpp +++ b/src/d/d_msg_out_font.cpp @@ -5,10 +5,13 @@ #include "d/d_meter2_info.h" #include "d/d_msg_object.h" #include "d/d_msg_out_font.h" -#include "dusk/frame_interpolation.h" -#include "dusk/version.hpp" #include "f_op/f_op_msg_mng.h" +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#include "dusk/version.hpp" +#endif + COutFontSet_c::COutFontSet_c() { initialize(); } @@ -309,21 +312,12 @@ void COutFont_c::setAlphaRatio(f32 i_ratio) { } void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param_3) { - s16 sp256[70]; + DUSK_IF_ELSE(f32, s16) sp256[70]; for (int i = 0; i < 70; i++) { sp256[i] = field_0x1b4[i]; } -#ifdef TARGET_PC - bool uiTickPending = dusk::frame_interp::get_ui_tick_pending(); - if (!uiTickPending) { - for (int i = 0; i < 70; i++) { - sp256[i] = -1; - } - } -#endif - for (int i = 0; i < 35; i++) { u8 type = mpOfs[i]->getType(); J2DTextBox* tbox = mpOfs[i]->getTextBoxPtr(); @@ -411,11 +405,14 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param } case 9: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 80.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 80) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } @@ -431,55 +428,70 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param break; case 14: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 20.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 20) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, false, false, true); break; case 15: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 20.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 20) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, true, true, true); break; case 16: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 20.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 20) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, false, false, false); break; case 17: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 20.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 20) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, true, true, false); break; case 18: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 40.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 40) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } @@ -491,11 +503,14 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param break; case 19: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 40.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 40) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } @@ -526,27 +541,28 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param case 20: case 21: case 22: -#ifdef TARGET_PC - if (uiTickPending) -#endif - { - field_0x1b4[type]++; - if (field_0x1b4[type] >= 28) { - field_0x1b4[type] = 0; - } +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 28.0f); +#else + field_0x1b4[type]++; + if (field_0x1b4[type] >= 28) { + field_0x1b4[type] = 0; } - +#endif mpPane[type]->rotate(0.5f * sizeX, 0.5f * sizeY, ROTATE_Z, (360.0f * (f32)field_0x1b4[type]) / 28.0f); mpPane[type]->draw(posX, posY, sizeX, sizeY, false, false, false); break; case 25: // some issues in here, 2020 if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 18.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 18) { field_0x1b4[type] -= 18; } +#endif } f32 alpha; @@ -623,11 +639,14 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param case 67: case 68: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 40.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 40) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } @@ -643,23 +662,28 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param case 59: case 60: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 40.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 40) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, false, false, false); break; case 62: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 10.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 10) { field_0x1b4[type] = 0; } - +#endif if (field_0x1b4[type] < 5) { mpPane[type]->setBlendRatio(1.0f, 0.0f); } else { @@ -698,14 +722,22 @@ void COutFont_c::reset(J2DTextBox* i_textbox) { } } -void COutFont_c::setBlendAnime(J2DPicture* i_pic, s16 param_1) { +void COutFont_c::setBlendAnime(J2DPicture* i_pic, DUSK_IF_ELSE(f32, s16) param_1) { +#if TARGET_PC + f32 i = fmodf(param_1, 20.0f); + if (i < 0.0f) { + i += 20.0f; + } +#else int i = param_1 % 20; +#endif if (i < 10) { - f32 dVar6 = fopMsgM_valueIncrease(10, i, 0); + f32 dVar6 = DUSK_IF_ELSE((i / 10.0f) * (i / 10.0f), fopMsgM_valueIncrease(10, i, 0)); i_pic->setBlendRatio(1.0f - dVar6, dVar6); } else { - f32 dVar6 = fopMsgM_valueIncrease(10, i - 10, 0); + f32 dVar6 = DUSK_IF_ELSE(((i - 10.0f) / 10.0f) * ((i - 10.0f) / 10.0f), + fopMsgM_valueIncrease(10, i - 10, 0)); i_pic->setBlendRatio(dVar6, 1.0f - dVar6); } } diff --git a/src/d/d_msg_scrn_3select.cpp b/src/d/d_msg_scrn_3select.cpp index b3a41a2988..8840797dff 100644 --- a/src/d/d_msg_scrn_3select.cpp +++ b/src/d/d_msg_scrn_3select.cpp @@ -5,22 +5,40 @@ #include "d/dolzel.h" // IWYU pragma: keep -#include +#include "d/d_msg_scrn_3select.h" #include "JSystem/J2DGraph/J2DAnmLoader.h" #include "JSystem/J2DGraph/J2DGrafContext.h" #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J2DGraph/J2DTextBox.h" +#include #include "d/d_com_inf_game.h" -#include "d/d_msg_object.h" -#include "d/d_msg_scrn_3select.h" -#include "d/d_pane_class.h" #include "d/d_select_cursor.h" -#include "dusk/version.hpp" +#include "d/d_msg_object.h" +#include "d/d_pane_class.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" +#include "dusk/version.hpp" + +#include "d/d_msg_scrn_talk.h" + +#include "JSystem/J3DGraphBase/J3DTransform.h" + +#include namespace { +struct SelectionAnimation { + dusk::vdt::FrameAnimation bck[3], bpk, btk; + f32 paneFrames[3] = {-1.0f, -1.0f, -1.0f}; + f32 scaleFrames[3] = {}; + f32 appliedFrames[3] = {-1.0f, -1.0f, -1.0f}; +}; + +absl::flat_hash_map sSelectionAnimations; + +void selectTransform(dMsgScrn3Select_c& screen, int selection, bool preserveScale, f32 step = 0.0f, f32 target = 0.0f); + bool hit_choice_pane(CPaneMgr* pane, f32 padding) { return pane != NULL && pane->getPanePtr() != NULL && pane->getPanePtr()->isVisible() && dusk::menu_pointer::hit_pane(pane, padding); @@ -28,6 +46,13 @@ bool hit_choice_pane(CPaneMgr* pane, f32 padding) { } // namespace #endif +#define PROC_OPEN2_BCK_START 190.0f +#define PROC_OPEN2_BCK_END 199.0f +#define PROC_CHANGE_BCK_START_A 300.0f +#define PROC_CHANGE_BCK_START_B 400.0f +#define PROC_CHANGE_BCK_END_A 303.0f +#define PROC_CHANGE_BCK_END_B 403.0f + typedef void (dMsgScrn3Select_c::*processFn)(); DUSK_GAME_DATA processFn process[] = { &dMsgScrn3Select_c::open1Proc, &dMsgScrn3Select_c::open2Proc, &dMsgScrn3Select_c::waitProc, @@ -35,6 +60,7 @@ DUSK_GAME_DATA processFn process[] = { }; dMsgScrn3Select_c::dMsgScrn3Select_c() { + IF_DUSK(sSelectionAnimations.erase(this)); static u64 const tag_name[3][12] = { // A 'a_n', 'a_g', 'a_gr', 'a_m', MULTI_CHAR('w_yes_00'), MULTI_CHAR('w_yes_01'), MULTI_CHAR('w_yes_02'), MULTI_CHAR('w_yes_03'), MULTI_CHAR('w_yes_04'), @@ -61,7 +87,7 @@ dMsgScrn3Select_c::dMsgScrn3Select_c() { mpAnmBck = (J2DAnmTransform*)J2DAnmLoaderDataBase::load( JKRGetNameResource("zelda_window_3menu.bck", dComIfGp_getMsgArchive(0))); mBckFrame = 0.0f; - mBckFrame = 190.0f; + mBckFrame = PROC_OPEN2_BCK_START; mOffsetX = 0.0f; mpAnmBpk = (J2DAnmColorKey*)J2DAnmLoaderDataBase::load( @@ -320,6 +346,7 @@ dMsgScrn3Select_c::dMsgScrn3Select_c() { } dMsgScrn3Select_c::~dMsgScrn3Select_c() { + IF_DUSK(sSelectionAnimations.erase(this)); JKR_DELETE(mpScreen); mpScreen = NULL; @@ -441,6 +468,66 @@ void dMsgScrn3Select_c::translate(f32 i_x, f32 i_y) { mpParent->translate(i_x, i_y); } +#if TARGET_PC +void dMsgScrn3Select_c::presentAnims() { + const auto found = sSelectionAnimations.find(this); + if (found == sSelectionAnimations.end()) { + return; + } + auto& animations = found->second; + if (animations.paneFrames[0] < 0.0f || + (mProcess == PROC_MAX_e && animations.appliedFrames[0] == animations.paneFrames[0])) + { + return; + } + const u8 updates = field_0x114; + field_0x114 = 0; + for (int i = 0; i < 3; ++i) { + auto* pane = mpSel_c[i]->getPanePtr(); + const f32 frame = animations.bck[i].advance(animations.paneFrames[i]); + if (frame == animations.appliedFrames[i]) { + continue; + } + animations.appliedFrames[i] = frame; + mpAnmBck->setFrame(frame); + pane->setAnimation(mpAnmBck); + pane->animationTransform(); + pane->setAnimation((J2DAnmTransform*)nullptr); + if (pane->field_0x4 != 0xFFFF) { + J3DTransformInfo layout; + if (animations.scaleFrames[i] >= 0.0f) { + mpAnmBck->setFrame(animations.scaleFrames[i]); + mpAnmBck->getTransform(pane->field_0x4, &layout); + pane->scale(layout.mScale.x, layout.mScale.z); + } + if (mProcess == PROC_OPEN2_e && mBckFrame == PROC_OPEN2_BCK_START) { + mpAnmBck->setFrame(PROC_OPEN2_BCK_START); + mpAnmBck->getTransform(pane->field_0x4, &layout); + pane->translate(layout.mTranslate.x, pane->getTranslateY()); + } + } + onAnimeUpdate(i); + } + mpAnmBpk->setFrame(animations.bpk.advance(mBpkFrame)); + mpAnmBtk->setFrame(animations.btk.advance(mBtkFrame)); + + mpScreen->animation(); + + for (int i = 0; i < 3; i++) { + if (i == mSelNo) { + mpSelCld_c[i]->getPanePtr()->scale(1.0f, 1.0f); + mpSelCldr_c[i]->getPanePtr()->scale(1.0f, 1.0f); + } else { + mpSelCld_c[i]->getPanePtr()->scale(0.0f, 0.0f); + mpSelCldr_c[i]->getPanePtr()->scale(0.0f, 0.0f); + } + } + selectTrans(); + + field_0x114 = updates; +} +#endif + void dMsgScrn3Select_c::draw(f32 i_xPos, f32 i_yPos) { J2DGrafContext* port = dComIfGp_getCurrentGrafPort(); port->setup2D(); @@ -488,6 +575,10 @@ void dMsgScrn3Select_c::draw(f32 i_xPos, f32 i_yPos) { } void dMsgScrn3Select_c::selAnimeInit(u8 i_selNum, u8 i_selNo, u8 param_2, f32 i_width, u8 param_4) { +#if TARGET_PC + auto& animations = sSelectionAnimations[this]; + animations = {}; +#endif mSelNo = i_selNo; field_0x110 = i_selNo; mSelNum = i_selNum; @@ -539,11 +630,13 @@ void dMsgScrn3Select_c::selAnimeInit(u8 i_selNum, u8 i_selNo, u8 param_2, f32 i_ break; } - mBckFrame = 190.0f; + mBckFrame = PROC_OPEN2_BCK_START; + IF_DUSK(mProcess = PROC_OPEN1_e); for (int i = 0; i < 3; i++) { selectAnimeTransform(i); } + IF_DUSK(presentAnims()); selectScale(); selectTrans(); @@ -565,17 +658,44 @@ void dMsgScrn3Select_c::selAnimeInit(u8 i_selNum, u8 i_selNo, u8 param_2, f32 i_ } mProcess = PROC_OPEN1_e; +#if TARGET_PC + animations.bpk.start(mBpkFrame, 0.0f, 1.0f, mpAnmBpk->getFrameMax()); + animations.btk.start(mBtkFrame, 0.0f, 1.0f, mpAnmBtk->getFrameMax()); +#endif } bool dMsgScrn3Select_c::selAnimeMove(u8 i_selNum, u8 param_1, bool param_2) { + IF_DUSK(auto& animations = sSelectionAnimations[this]); field_0x110 = param_1; mSelNum = i_selNum; field_0x114 = 0; field_0x108 = param_2; #if TARGET_PC - pointerMove(); -#endif - + dMsgObject_c* msg = dMsgObject_getMsgObjectClass(); + if (mProcess >= PROC_MAX_e) { + return false; + } + const bool midnaPending = msg != NULL && msg->isMidonaMessage() && msg->field_0x1a3 != 0 && + msg->mpScrnDraw != NULL && + static_cast(msg->mpScrnDraw)->mpSelect_c == this; + if (!midnaPending) { + pointerMove(); + } + const bool finishingMidna = midnaPending && (msg->getStatusLocal() == 8 || msg->getStatusLocal() == 9); + do { + (this->*process[mProcess])(); + mBpkFrame = std::fmod(mBpkFrame + 1.0f, mpAnmBpk->getFrameMax()); + mBtkFrame = std::fmod(mBtkFrame + 1.0f, mpAnmBtk->getFrameMax()); + } while (finishingMidna && mProcess != PROC_SELECT_e && mProcess < PROC_MAX_e); + if (finishingMidna) { + for (int i = 0; i < 3; ++i) { + animations.bck[i].finish(animations.paneFrames[i]); + } + animations.bpk.start(mBpkFrame, 0.0f, 1.0f, mpAnmBpk->getFrameMax()); + animations.btk.start(mBtkFrame, 0.0f, 1.0f, mpAnmBtk->getFrameMax()); + } + presentAnims(); +#else (this->*process[mProcess])(); mBpkFrame++; @@ -603,6 +723,7 @@ bool dMsgScrn3Select_c::selAnimeMove(u8 i_selNum, u8 param_1, bool param_2) { } selectTrans(); +#endif for (int i = 0; i < 3; i++) { if (i == mSelNo) { @@ -673,14 +794,18 @@ bool dMsgScrn3Select_c::selAnimeEnd() { field_0x114 = 0; if (mProcess != PROC_CLOSE_e) { - mBckFrame = 199.0f; + mBckFrame = PROC_OPEN2_BCK_END; for (int i = 0; i < 3; i++) { +#if TARGET_PC + selectTransform(*this, i, true); +#else f32 scale_x = mpSel_c[i]->getPanePtr()->getScaleX(); f32 scale_y = mpSel_c[i]->getPanePtr()->getScaleY(); selectAnimeTransform(i); mpSel_c[i]->getPanePtr()->scale(scale_x, scale_y); +#endif } mProcess = PROC_CLOSE_e; @@ -693,12 +818,15 @@ bool dMsgScrn3Select_c::selAnimeEnd() { if (mBpkFrame >= mpAnmBpk->getFrameMax()) { mBpkFrame -= mpAnmBpk->getFrameMax(); } - mpAnmBpk->setFrame(mBpkFrame); + IF_NOT_DUSK(mpAnmBpk->setFrame(mBpkFrame)); mBtkFrame++; if (mBtkFrame >= mpAnmBtk->getFrameMax()) { mBtkFrame -= mpAnmBtk->getFrameMax(); } +#if TARGET_PC + presentAnims(); +#else mpAnmBtk->setFrame(mBtkFrame); mpScreen->animation(); @@ -714,6 +842,7 @@ bool dMsgScrn3Select_c::selAnimeEnd() { } selectTrans(); +#endif return mProcess == PROC_MAX_e ? TRUE : FALSE; } @@ -764,14 +893,18 @@ void dMsgScrn3Select_c::open1Proc() { mBckFrame = sel_anm_frame[mSelNo]; for (int i = 0; i < 3; i++) { +#if TARGET_PC + selectAnimeTransform(i); +#else f32 x = mpSel_c[i]->getPosX(); selectAnimeTransform(i); mpSel_c[i]->move(x, mpSel_c[i]->getPosY()); +#endif } } - mBckFrame = 190.0f; + mBckFrame = PROC_OPEN2_BCK_START; mProcess = PROC_OPEN2_e; } @@ -781,7 +914,7 @@ void dMsgScrn3Select_c::open2Proc() { mBckFrame++; mpSelectCursor->setAlphaRate(1.0f); - if (mBckFrame >= 199.0f) { + if (mBckFrame >= PROC_OPEN2_BCK_END) { if (mSelNo != 0xFF) { mBckFrame = sel_anm_frame[mSelNo]; } @@ -791,11 +924,15 @@ void dMsgScrn3Select_c::open2Proc() { } for (int i = 0; i < 3; i++) { +#if TARGET_PC + selectTransform(*this, i, true, mProcess == PROC_OPEN2_e ? 1.0f : 0.0f, PROC_OPEN2_BCK_END); +#else f32 scale_x = mpSel_c[i]->getPanePtr()->getScaleX(); f32 scale_y = mpSel_c[i]->getPanePtr()->getScaleY(); selectAnimeTransform(i); mpSel_c[i]->getPanePtr()->scale(scale_x, scale_y); +#endif } } @@ -815,56 +952,56 @@ void dMsgScrn3Select_c::selectProc() { switch (mLastSelNo) { case 0: if (mSelNo == 1) { - mBckFrame = 303.0f; + mBckFrame = PROC_CHANGE_BCK_END_A; selectAnimeTransform(1); - mBckFrame = 300.0f; + mBckFrame = PROC_CHANGE_BCK_START_A; selectAnimeTransform(0); } else if (mSelNo == 2) { - mBckFrame = 403.0f; + mBckFrame = PROC_CHANGE_BCK_END_B; selectAnimeTransform(2); - mBckFrame = 400.0f; + mBckFrame = PROC_CHANGE_BCK_START_B; selectAnimeTransform(0); } break; case 1: if (mSelNo == 0) { - mBckFrame = 300.0f; + mBckFrame = PROC_CHANGE_BCK_START_A; selectAnimeTransform(0); - mBckFrame = 303.0f; + mBckFrame = PROC_CHANGE_BCK_END_A; selectAnimeTransform(1); } else if (mSelNo == 2) { - mBckFrame = 403.0f; + mBckFrame = PROC_CHANGE_BCK_END_B; selectAnimeTransform(2); - mBckFrame = 400.0f; + mBckFrame = PROC_CHANGE_BCK_START_B; selectAnimeTransform(1); } break; case 2: if (mSelNo == 1) { - mBckFrame = 400.0f; + mBckFrame = PROC_CHANGE_BCK_START_B; selectAnimeTransform(1); - mBckFrame = 403.0f; + mBckFrame = PROC_CHANGE_BCK_END_B; selectAnimeTransform(2); } else if (mSelNo == 0) { - mBckFrame = 300.0f; + mBckFrame = PROC_CHANGE_BCK_START_A; selectAnimeTransform(0); - mBckFrame = 303.0f; + mBckFrame = PROC_CHANGE_BCK_END_A; selectAnimeTransform(2); } break; case 0xFF: if (mSelNo == 0) { - mBckFrame = 300.0f; + mBckFrame = PROC_CHANGE_BCK_START_A; } else if (mSelNo == 1) { - mBckFrame = 303.0f; + mBckFrame = PROC_CHANGE_BCK_END_A; } else if (mSelNo == 2) { - mBckFrame = 403.0f; + mBckFrame = PROC_CHANGE_BCK_END_B; } for (int i = 0; i < 3; i++) { @@ -885,13 +1022,13 @@ void dMsgScrn3Select_c::selectProc() { void dMsgScrn3Select_c::changeProc() { if (mSelNo == 0xFF) { if (mLastSelNo == 0) { - mBckFrame = 303.0f; + mBckFrame = PROC_CHANGE_BCK_END_A; selectAnimeTransform(0); } else if (mLastSelNo == 1) { - mBckFrame = 300.0f; + mBckFrame = PROC_CHANGE_BCK_START_A; selectAnimeTransform(1); } else if (mLastSelNo == 2) { - mBckFrame = 400.0f; + mBckFrame = PROC_CHANGE_BCK_START_B; selectAnimeTransform(2); } @@ -901,26 +1038,28 @@ void dMsgScrn3Select_c::changeProc() { case 0: if (mSelNo == 1) { if (field_0x108 != 0) { - mBckFrame = 303.0f; + mBckFrame = PROC_CHANGE_BCK_END_A; } else { mBckFrame++; } - selectAnimeTransform(0); + DUSK_IF_ELSE(selectTransform(*this, 0, false, 1.0f, PROC_CHANGE_BCK_END_A), + selectAnimeTransform(0)); - if (mBckFrame >= 303.0f) { + if (mBckFrame >= PROC_CHANGE_BCK_END_A) { mProcess = PROC_SELECT_e; } } else if (mSelNo == 2) { if (field_0x108 != 0) { - mBckFrame = 403.0f; + mBckFrame = PROC_CHANGE_BCK_END_B; } else { mBckFrame++; } - selectAnimeTransform(0); + DUSK_IF_ELSE(selectTransform(*this, 0, false, 1.0f, PROC_CHANGE_BCK_END_B), + selectAnimeTransform(0)); - if (mBckFrame >= 403.0f) { + if (mBckFrame >= PROC_CHANGE_BCK_END_B) { mProcess = PROC_SELECT_e; } } @@ -928,26 +1067,28 @@ void dMsgScrn3Select_c::changeProc() { case 1: if (mSelNo == 0) { if (field_0x108 != 0) { - mBckFrame = 300.0f; + mBckFrame = PROC_CHANGE_BCK_START_A; } else { mBckFrame--; } - selectAnimeTransform(1); + DUSK_IF_ELSE(selectTransform(*this, 1, false, -1.0f, PROC_CHANGE_BCK_START_A), + selectAnimeTransform(1)); - if (mBckFrame <= 300.0f) { + if (mBckFrame <= PROC_CHANGE_BCK_START_A) { mProcess = PROC_SELECT_e; } } else if (mSelNo == 2) { if (field_0x108 != 0) { - mBckFrame = 403.0f; + mBckFrame = PROC_CHANGE_BCK_END_B; } else { mBckFrame++; } - selectAnimeTransform(1); + DUSK_IF_ELSE(selectTransform(*this, 1, false, 1.0f, PROC_CHANGE_BCK_END_B), + selectAnimeTransform(1)); - if (mBckFrame >= 403.0f) { + if (mBckFrame >= PROC_CHANGE_BCK_END_B) { mProcess = PROC_SELECT_e; } } @@ -955,26 +1096,28 @@ void dMsgScrn3Select_c::changeProc() { case 2: if (mSelNo == 1) { if (field_0x108 != 0) { - mBckFrame = 400.0f; + mBckFrame = PROC_CHANGE_BCK_START_B; } else { mBckFrame--; } - selectAnimeTransform(2); + DUSK_IF_ELSE(selectTransform(*this, 2, false, -1.0f, PROC_CHANGE_BCK_START_B), + selectAnimeTransform(2)); - if (mBckFrame <= 400.0f) { + if (mBckFrame <= PROC_CHANGE_BCK_START_B) { mProcess = PROC_SELECT_e; } } else if (mSelNo == 0) { if (field_0x108 != 0) { - mBckFrame = 300.0f; + mBckFrame = PROC_CHANGE_BCK_START_A; } else { mBckFrame--; } - selectAnimeTransform(2); + DUSK_IF_ELSE(selectTransform(*this, 2, false, -1.0f, PROC_CHANGE_BCK_START_A), + selectAnimeTransform(2)); - if (mBckFrame <= 300.0f) { + if (mBckFrame <= PROC_CHANGE_BCK_START_A) { mProcess = PROC_SELECT_e; } } @@ -988,14 +1131,18 @@ void dMsgScrn3Select_c::closeProc() { mBckFrame--; for (int i = 0; i < 3; i++) { +#if TARGET_PC + selectTransform(*this, i, true, -1.0f, PROC_OPEN2_BCK_START); +#else f32 scale_x = mpSel_c[i]->getPanePtr()->getScaleX(); f32 scale_y = mpSel_c[i]->getPanePtr()->getScaleY(); selectAnimeTransform(i); mpSel_c[i]->getPanePtr()->scale(scale_x, scale_y); +#endif } - if (mBckFrame <= 190.0f) { + if (mBckFrame <= PROC_OPEN2_BCK_START) { mProcess = PROC_MAX_e; } } @@ -1142,11 +1289,36 @@ void dMsgScrn3Select_c::selectTrans() { } } +#if TARGET_PC +namespace { +void selectTransform(dMsgScrn3Select_c& screen, int selection, bool preserveScale, f32 step, f32 target) { + auto& animations = sSelectionAnimations[&screen]; + animations.appliedFrames[selection] = -1.0f; + auto& visual = animations.bck[selection]; + if (step != 0.0f && screen.mBckFrame != target) { + visual.approach(screen.mBckFrame - step, target); + } else { + visual.finish(screen.mBckFrame); + } + if (!preserveScale) { + animations.scaleFrames[selection] = -1.0f; + } else if (animations.scaleFrames[selection] < 0.0f) { + animations.scaleFrames[selection] = animations.paneFrames[selection]; + } + animations.paneFrames[selection] = screen.mBckFrame; +} +} // namespace +#endif + void dMsgScrn3Select_c::selectAnimeTransform(int i_sel) { +#if TARGET_PC + selectTransform(*this, i_sel, false); +#else mpAnmBck->setFrame(mBckFrame); mpSel_c[i_sel]->getPanePtr()->setAnimation(mpAnmBck); mpSel_c[i_sel]->getPanePtr()->animationTransform(); mpSel_c[i_sel]->getPanePtr()->setAnimation((J2DAnmTransform*)NULL); onAnimeUpdate(i_sel); +#endif } diff --git a/src/d/d_msg_scrn_arrow.cpp b/src/d/d_msg_scrn_arrow.cpp index cea0dae145..587eb6a21a 100644 --- a/src/d/d_msg_scrn_arrow.cpp +++ b/src/d/d_msg_scrn_arrow.cpp @@ -7,6 +7,7 @@ #include "d/d_pane_class.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" #endif @@ -73,6 +74,14 @@ void dMsgScrnArrow_c::draw() { if (dusk::getSettings().game.recordingMode) { return; } + if (mpArw_c->isVisible()) { + dusk::vdt::present_looping(mBckFrame, mpBck, 1.0f); + dusk::vdt::present_looping(mBpkFrame, mpBpk, 1.0f); + mpScreen->animation(); + } else if (mpDot_c->isVisible()) { + dusk::vdt::present_looping(mBpkFrame, mpBpk, 1.0f); + mpScreen->animation(); + } #endif J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort(); mpScreen->draw(0.0f, 0.0f, graf_ctx); @@ -103,6 +112,7 @@ void dMsgScrnArrow_c::arwAnimeMove() { mpDot_c->hide(); } +#if !TARGET_PC mBckFrame += 1.0f; if (mBckFrame >= (f32)mpBck->getFrameMax()) { mBckFrame -= (f32)mpBck->getFrameMax(); @@ -114,6 +124,7 @@ void dMsgScrnArrow_c::arwAnimeMove() { mBpkFrame -= (f32)mpBpk->getFrameMax(); } mpBpk->setFrame(mBpkFrame); +#endif mpScreen->animation(); } @@ -136,11 +147,14 @@ void dMsgScrnArrow_c::dotAnimeMove() { mpDot_c->show(); } +#if !TARGET_PC mBpkFrame += 1.0f; if (mBpkFrame >= (f32)mpBpk->getFrameMax()) { mBpkFrame -= (f32)mpBpk->getFrameMax(); } mpBpk->setFrame(mBpkFrame); +#endif + mpScreen->animation(); } diff --git a/src/d/d_msg_scrn_base.cpp b/src/d/d_msg_scrn_base.cpp index 5488b76648..638bf1fc8b 100644 --- a/src/d/d_msg_scrn_base.cpp +++ b/src/d/d_msg_scrn_base.cpp @@ -71,8 +71,19 @@ void dMsgScrnBase_c::multiDraw() { } } +#if TARGET_PC +void dMsgScrnBase_c::presentAnims() { + if (dMsgObject_c* msg = dMsgObject_getMsgObjectClass()) { + if (msg->getScrnDrawPtrLocal() == this) { + msg->presentAnims(); + } + } +} +#endif + void dMsgScrnBase_c::draw() { #if TARGET_PC + presentAnims(); if (dusk::getSettings().game.recordingMode) { return; } diff --git a/src/d/d_msg_scrn_explain.cpp b/src/d/d_msg_scrn_explain.cpp index 9460ce8921..1d90898017 100644 --- a/src/d/d_msg_scrn_explain.cpp +++ b/src/d/d_msg_scrn_explain.cpp @@ -14,7 +14,10 @@ #include #include +#if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" +#endif #if TARGET_PC || VERSION == VERSION_GCN_JPN #define STR_BUF_LEN 528 @@ -324,6 +327,7 @@ dMsgScrnExplain_c::~dMsgScrnExplain_c() { void dMsgScrnExplain_c::move() { u8 currentStatus = mStatus; +#if !TARGET_PC f32 y_offset = 0.0f; if (field_0x66 == 2) { y_offset = -35.0f; @@ -333,6 +337,7 @@ void dMsgScrnExplain_c::move() { mpRoot_c[i]->paneTrans(field_0x48 + g_MsgObject_HIO_c.mChoicePos[0][6], field_0x4c + g_MsgObject_HIO_c.mBoxPos[0][6] + y_offset); } +#endif (this->*move_process[mStatus])(); @@ -344,7 +349,29 @@ void dMsgScrnExplain_c::move() { g_MsgObject_HIO_c.mBoxTalkScaleY); } +#if TARGET_PC +void dMsgScrnExplain_c::presentAnims() { + if (mStatus == STATUS_OPEN_e || mStatus == STATUS_CLOSE_e) { + dusk::vdt::advance_toward_frame(field_0x5a, mStatus == STATUS_OPEN_e ? 5.0f : 0.0f, 1.0f); + field_0x48 = FB_WIDTH_BASE * getAlphaRatio(); + for (int i = 0; i < 2; i++) { + mpRoot_c[i]->setAlphaRate(1.0f - getAlphaRatio()); + } + if (mpBackTex != NULL) { + mpBackTex->setAlpha((1.0f - getAlphaRatio()) * 150.0f); + } + } + + const f32 y_offset = field_0x66 == 2 ? -35.0f : 0.0f; + for (int i = 0; i < 2; i++) { + mpRoot_c[i]->paneTrans(field_0x48 + g_MsgObject_HIO_c.mChoicePos[0][6], + field_0x4c + g_MsgObject_HIO_c.mBoxPos[0][6] + y_offset); + } +} +#endif + void dMsgScrnExplain_c::draw(J2DOrthoGraph* i_graf) { + IF_DUSK(presentAnims()); if (mStatus == STATUS_WAIT_e || mStatus == STATUS_OPEN_REQ_e) { return; } @@ -387,6 +414,7 @@ void dMsgScrnExplain_c::draw(J2DOrthoGraph* i_graf) { mpSelect_c->translate(486.0f + g_MsgObject_HIO_c.mChoicePos[0][7], 209.0f + g_MsgObject_HIO_c.mBoxPos[0][7] + y_offset); + IF_DUSK(mpSelect_c->presentAnims()); mpSelect_c->draw(0.0f, 0.0f); } @@ -443,7 +471,7 @@ void dMsgScrnExplain_c::open_init() { } void dMsgScrnExplain_c::open_proc() { - field_0x5a++; + IF_NOT_DUSK(field_0x5a++); if (field_0x5a >= 5) { field_0x5a = 5; if (field_0x64 == 1 || field_0x64 == 2) { @@ -625,7 +653,7 @@ void dMsgScrnExplain_c::close_proc() { iVar1 = true; } - field_0x5a--; + IF_NOT_DUSK(field_0x5a--); if (field_0x5a <= 0) { field_0x5a = 0; if (iVar1) { diff --git a/src/d/d_msg_scrn_howl.cpp b/src/d/d_msg_scrn_howl.cpp index 4e5c59546c..a661526b9c 100644 --- a/src/d/d_msg_scrn_howl.cpp +++ b/src/d/d_msg_scrn_howl.cpp @@ -21,8 +21,8 @@ #include "m_Do/m_Do_graphic.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" -#include "dusk/settings.h" +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #endif // POSIX already defines a macro with this name, but we know that this specific name is @@ -148,6 +148,10 @@ dMsgScrnHowl_c::dMsgScrnHowl_c() { field_0x2124 = 0; field_0x2128 = 0; mPlotTime = 0; +#if TARGET_PC + mSampleAcc = 0.0f; + mPulseFrame = 0.0f; +#endif field_0x212c = 0; field_0x212e = 0; field_0x2126 = 0; @@ -289,18 +293,52 @@ dMsgScrnHowl_c::~dMsgScrnHowl_c() { dComIfGp_getMsgArchive(5)->removeResourceAll();; } +#if TARGET_PC +void dMsgScrnHowl_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + for (int i = 0; i < 0x300; i++) { + if (field_0x1b14[i] > 0) { + dusk::vdt::advance_toward_frame(field_0x1b14[i], 0.0f, 1.0f); + } + } + f32 alphaRate = mpPmP_c->getAlphaRate(); + f32 fVar1; + f32 fVar2; + if (field_0x2798 == 3) { + fVar1 = 0.5f; + fVar2 = 0.0f; + } else { + fVar1 = 1.0f; + fVar2 = 1.0f; + } + if (field_0x1994 != fVar1) { + dusk::vdt::present_addCalc2(&field_0x1994, fVar1, 0.2f, 1.0f, 0.1f); + } + if (field_0x1998 != fVar2) { + dusk::vdt::present_addCalc2(&field_0x1998, fVar2, 0.2f, 1.0f, 0.1f); + } + mpButtonIcon[0]->setAlphaRate(field_0x1994 * alphaRate); + mpButtonIcon[1]->setAlphaRate(field_0x1994 * alphaRate); + mpButtonText[0]->setAlphaRate(field_0x1998 * alphaRate); + mpButtonText[1]->setAlphaRate(field_0x1998 * alphaRate); +} +#endif + void dMsgScrnHowl_c::exec() { field_0x2799 = field_0x2798; (this->*process[field_0x2798])(); +#if !TARGET_PC for (int i = 0; i < 0x300; i++) { if (field_0x1b14[i] > 0) { field_0x1b14[i]--; } } +#endif if (field_0x2799 != field_0x2798) { (this->*init_proc[field_0x2798])(); } - + +#if !TARGET_PC f32 alphaRate = mpPmP_c->getAlphaRate(); f32 fVar1; f32 fVar2; @@ -327,6 +365,7 @@ void dMsgScrnHowl_c::exec() { mpButtonIcon[1]->setAlphaRate(field_0x1994 * alphaRate); mpButtonText[0]->setAlphaRate(field_0x1998 * alphaRate); mpButtonText[1]->setAlphaRate(field_0x1998 * alphaRate); +#endif #if TARGET_PC showCursor = true; @@ -467,6 +506,7 @@ void dMsgScrnHowl_c::resetLine() { field_0x2195 = 0; field_0x2128 = 0; mPlotTime = 0; + IF_DUSK(mSampleAcc = 0.0f); field_0x212c = 0; field_0x212e = 0; field_0x2124 = 0; @@ -503,7 +543,7 @@ void dMsgScrnHowl_c::drawWave() { if (local_80 < 0) { local_80 = 0; } - s32 local_94 = 0; + DUSK_IF_ELSE(f32, s32) local_94 = 0; Vec fVar12 = field_0x128; Vec this_02 = field_0x140; #if TARGET_PC // TODO: make this actually use the scissor @@ -541,7 +581,7 @@ void dMsgScrnHowl_c::drawWave() { for (int iVar10 = 0; iVar10 < field_0x2128 - 1; iVar10++) { f32 local_54 = local_e0; f32 local_c8 = field_0x180[sVar14]; - s32 sVar3 = field_0x1b14[sVar14]; + DUSK_IF_ELSE(f32, s32) sVar3 = field_0x1b14[sVar14]; local_e0 += field_0x1980; local_68 += field_0x1984; if (local_68 > 255.0f) { @@ -549,7 +589,7 @@ void dMsgScrnHowl_c::drawWave() { } sVar14 = addCount(sVar14); f32 fVar2 = field_0x180[sVar14]; - s16 temp_r4 = field_0x1b14[sVar14]; + DUSK_IF_ELSE(f32, s16) temp_r4 = field_0x1b14[sVar14]; if (iVar10 == field_0x2128 - 2) { local_60 = local_e0; local_64 = fVar2; @@ -597,19 +637,23 @@ void dMsgScrnHowl_c::drawWave() { f18 = local_64; } else { #if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - field_0x2134++; - if (field_0x2134 > 30) { - field_0x2134 = 0; - } + dusk::vdt::advance_looping_frame(mPulseFrame, 1.0f, 31.0f); + if (mPulseFrame < 15.0f) { + local_dc = mPulseFrame / 15.0f; + } else { + local_dc = (30.0f - mPulseFrame) / 15.0f; + } +#else + field_0x2134++; + if (field_0x2134 > 30) { + field_0x2134 = 0; } if (field_0x2134 < 15) { local_dc = field_0x2134 / 15.0f; } else { local_dc = (30.0f - field_0x2134) / 15.0f; } +#endif f17 = f26; f18 = local_e4; } @@ -882,6 +926,19 @@ void dMsgScrnHowl_c::drawEffect() { } void dMsgScrnHowl_c::calcMain() { +#if TARGET_PC + mSampleAcc += dusk::game_clock::original_frames(); + while (mSampleAcc >= 1.0f) { + mSampleAcc -= 1.0f; + if (mPlotTime < field_0x2138 + 380) { + mPlotTime++; + } else { + field_0x212c--; + } + calcWave(); + calcGuide(); + } +#else if (mPlotTime < field_0x2138 + 380) { mPlotTime++; } else { @@ -889,6 +946,7 @@ void dMsgScrnHowl_c::calcMain() { } calcWave(); calcGuide(); +#endif } void dMsgScrnHowl_c::calcWave() { diff --git a/src/d/d_msg_scrn_item.cpp b/src/d/d_msg_scrn_item.cpp index ab91739acc..33a975b6e9 100644 --- a/src/d/d_msg_scrn_item.cpp +++ b/src/d/d_msg_scrn_item.cpp @@ -24,7 +24,10 @@ #include "JSystem/JUtility/JUTTexture.h" #include +#if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" +#endif dMsgScrnItem_c::dMsgScrnItem_c(u8 param_1, u8 param_2, JKRExpHeap* param_3) { if (param_3 != NULL) { @@ -394,6 +397,25 @@ dMsgScrnItem_c::~dMsgScrnItem_c() { dComIfGp_getMsgCommonArchive()->removeResourceAll();; } +#if TARGET_PC +void dMsgScrnItem_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + mpSelect_c->presentAnims(); + for (int i = 0; i < 1; i++) { + dusk::vdt::present_looping(field_0x140[i], field_0x118[i], g_MsgObject_HIO_c.mBoxItemAnmSpeed); + } + for (int i = 0; i < 2; i++) { + dusk::vdt::present_looping(field_0x154[i], field_0x12c[i], g_MsgObject_HIO_c.mBoxItemAnmSpeed); + } + mpScreen->animation(); + if (isTalkNow()) { + fukiAlpha(1.0f); + } else { + fukiAlpha(field_0x13c); + } +} +#endif + void dMsgScrnItem_c::exec() { f32 dVar12 = field_0x13c; if (!field_0x19d) { @@ -402,6 +424,7 @@ void dMsgScrnItem_c::exec() { setBpk1Animation(field_0x12c[1]); field_0x19d = true; } +#if !TARGET_PC for (int i = 0; i < 1; i++) { field_0x140[i] += g_MsgObject_HIO_c.mBoxItemAnmSpeed; if (field_0x140[i] >= field_0x118[i]->getFrameMax()) { @@ -423,7 +446,8 @@ void dMsgScrnItem_c::exec() { } else { fukiAlpha(dVar12); } - +#endif + f32 yOffset; switch(field_0x19c) { case 1: @@ -576,6 +600,12 @@ bool dMsgScrnItem_c::selectAnimeEnd() { return mpSelect_c->selAnimeEnd(); } +#if TARGET_PC +bool dMsgScrnItem_c::isSelectAnimeActive() { + return mpSelect_c != NULL && mpSelect_c->isAnimeActive(); +} +#endif + void dMsgScrnItem_c::fukiScale(f32 param_0) { } diff --git a/src/d/d_msg_scrn_kanban.cpp b/src/d/d_msg_scrn_kanban.cpp index 8e2346feab..41efff8824 100644 --- a/src/d/d_msg_scrn_kanban.cpp +++ b/src/d/d_msg_scrn_kanban.cpp @@ -12,10 +12,11 @@ #include "d/d_msg_out_font.h" #include "d/d_msg_scrn_kanban.h" #include "d/d_pane_class.h" -#include "dusk/version.hpp" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" +#include "dusk/version.hpp" #endif dMsgScrnKanban_c::dMsgScrnKanban_c(JKRExpHeap* param_0) { @@ -204,7 +205,16 @@ dMsgScrnKanban_c::~dMsgScrnKanban_c() { dComIfGp_getMsgArchive(2)->removeResourceAll(); } +#if TARGET_PC +void dMsgScrnKanban_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + dusk::vdt::present_looping(field_0xd8, field_0xd0, 1.0f); + mpScreen->animation(); +} +#endif + void dMsgScrnKanban_c::exec() { +#if !TARGET_PC field_0xd8 += 1.0f; if (field_0xd8 >= field_0xd0->getFrameMax()) { field_0xd8 -= field_0xd0->getFrameMax(); @@ -212,6 +222,7 @@ void dMsgScrnKanban_c::exec() { field_0xd0->setFrame(field_0xd8); mpScreen->animation(); +#endif if (isTalkNow()) { fukiAlpha(1.0f); @@ -222,6 +233,7 @@ void dMsgScrnKanban_c::exec() { void dMsgScrnKanban_c::draw() { #if TARGET_PC + presentAnims(); if (dusk::getSettings().game.recordingMode) { return; } diff --git a/src/d/d_msg_scrn_light.cpp b/src/d/d_msg_scrn_light.cpp index 19671b5d57..57b0ad5871 100644 --- a/src/d/d_msg_scrn_light.cpp +++ b/src/d/d_msg_scrn_light.cpp @@ -6,7 +6,10 @@ #include "JSystem/J2DGraph/J2DScreen.h" #include "d/d_com_inf_game.h" #include "d/d_pane_class.h" -#include "dusk/frame_interpolation.h" + +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif class dMsgScrnLight_HIO_c { public: @@ -203,14 +206,9 @@ void dMsgScrnLight_c::draw(f32* i_anmFrame, f32 i_posX, f32 i_posY, f32 i_scaleX } if (mPlayAnim) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - *i_anmFrame += 1.0f; - if (*i_anmFrame >= mpBck->getFrameMax()) { - *i_anmFrame = 0.0f; - } + *i_anmFrame += 1.0f IF_DUSK(* dusk::game_clock::original_frames()); + if (*i_anmFrame >= mpBck->getFrameMax()) { + *i_anmFrame = 0.0f; } mBckFrame = *i_anmFrame; @@ -226,15 +224,9 @@ void dMsgScrnLight_c::draw(f32* i_anmFrame, f32 i_posX, f32 i_posY, f32 i_scaleX mpParent_c->setBlackWhite(i_black, i_white); if (mPlayAnim) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - *i_anmFrame += i_anmRate; - - if (*i_anmFrame >= mpBck->getFrameMax()) { - *i_anmFrame = 0.0f; - } + *i_anmFrame += i_anmRate IF_DUSK(* dusk::game_clock::original_frames()); + if (*i_anmFrame >= mpBck->getFrameMax()) { + *i_anmFrame = 0.0f; } mBckFrame = *i_anmFrame; diff --git a/src/d/d_msg_scrn_place.cpp b/src/d/d_msg_scrn_place.cpp index 31718f6d49..33ef29ab30 100644 --- a/src/d/d_msg_scrn_place.cpp +++ b/src/d/d_msg_scrn_place.cpp @@ -24,7 +24,7 @@ dMsgScrnPlace_c::dMsgScrnPlace_c() { init(); dCamera_c* camera = dCam_getBody(); - if (camera->mTrimSize && dCam_getBody()->mTrimSize != 4) { + if ((camera->mTrimSize && dCam_getBody()->mTrimSize != 4) IF_DUSK(&& !dusk::isLetterboxingDisabled(dComIfGp_event_runCheck()))) { mScaleX = g_MsgObject_HIO_c.mStageTitleCharPosY; mScaleY = g_MsgObject_HIO_c.mStageTitleBasePosY; } else { diff --git a/src/d/d_msg_scrn_talk.cpp b/src/d/d_msg_scrn_talk.cpp index 6a30d08c47..d4b5d4e24f 100644 --- a/src/d/d_msg_scrn_talk.cpp +++ b/src/d/d_msg_scrn_talk.cpp @@ -20,10 +20,10 @@ #include "JSystem/J2DGraph/J2DScreen.h" #include -#include "dusk/version.hpp" - #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" +#include "dusk/version.hpp" #endif #if TARGET_PC @@ -374,6 +374,15 @@ void dMsgScrnTalk_c::exec() { } } +#if TARGET_PC +void dMsgScrnTalk_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + if (mpSelect_c != NULL) { + mpSelect_c->presentAnims(); + } +} +#endif + void dMsgScrnTalk_c::drawSelf() { #if TARGET_PC if (dusk::getSettings().game.recordingMode) { @@ -405,7 +414,7 @@ void dMsgScrnTalk_c::drawSelf() { } } if (field_0x35c[i] > 0) { - field_0x35c[i]--; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0x35c[i], 0.0f, 1.0f), field_0x35c[i]--); } else { mpLight_c->draw( &mCharInfoPtr[i].field_0x14, mCharInfoPtr[i].field_0x00, mCharInfoPtr[i].field_0x04, @@ -490,6 +499,12 @@ bool dMsgScrnTalk_c::selectAnimeEnd() { return false; } +#if TARGET_PC +bool dMsgScrnTalk_c::isSelectAnimeActive() { + return mpSelect_c != NULL && mpSelect_c->isAnimeActive(); +} +#endif + void dMsgScrnTalk_c::fukiScale(f32 param_0) { } diff --git a/src/d/d_msg_scrn_tree.cpp b/src/d/d_msg_scrn_tree.cpp index 440b2702d3..36d4b9494f 100644 --- a/src/d/d_msg_scrn_tree.cpp +++ b/src/d/d_msg_scrn_tree.cpp @@ -8,10 +8,11 @@ #include "d/d_msg_out_font.h" #include "d/d_msg_scrn_tree.h" #include "d/d_pane_class.h" -#include "dusk/version.hpp" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" +#include "dusk/version.hpp" #endif dMsgScrnTree_c::dMsgScrnTree_c(JUTFont* param_0, JKRExpHeap* param_1) { @@ -216,7 +217,17 @@ dMsgScrnTree_c::~dMsgScrnTree_c() { dComIfGp_getMsgArchive(2)->removeResourceAll(); } +#if TARGET_PC +void dMsgScrnTree_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + dusk::vdt::present_looping(field_0xdc, field_0xd0, 1.0f); + dusk::vdt::present_looping(field_0xe0, field_0xd4, 1.0f); + mpScreen->animation(); +} +#endif + void dMsgScrnTree_c::exec() { +#if !TARGET_PC field_0xdc += 1.0f; if (field_0xdc >= field_0xd0->getFrameMax()) { field_0xdc -= field_0xd0->getFrameMax(); @@ -229,6 +240,7 @@ void dMsgScrnTree_c::exec() { } field_0xd4->setFrame(field_0xe0); mpScreen->animation(); +#endif if (isTalkNow()) { fukiAlpha(1.0f); @@ -246,6 +258,7 @@ void dMsgScrnTree_c::exec() { void dMsgScrnTree_c::draw() { #if TARGET_PC + presentAnims(); if (dusk::getSettings().game.recordingMode) { return; } diff --git a/src/d/d_name.cpp b/src/d/d_name.cpp index aa01a39de4..c41d90ed37 100644 --- a/src/d/d_name.cpp +++ b/src/d/d_name.cpp @@ -8,23 +8,21 @@ #include "m_Do/m_Do_controller_pad.h" #include #include - #include "JSystem/J2DGraph/J2DAnmLoader.h" +#include "f_op/f_op_msg_mng.h" + +#if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/utilities.hpp" #include "dusk/version.hpp" -#include "f_op/f_op_msg_mng.h" static bool isPalOrJpn() { return dusk::version::isRegionPal() || dusk::version::isRegionJpn(); } -#if TARGET_PC #define SJIS_MOJI(wmoji) ((static_cast(static_cast((wmoji)[0])) << 8) | static_cast((wmoji)[1])) -#else -#define SJIS_MOJI(wmoji) *(u16*)wmoji -#endif -#if TARGET_PC static const char* l_mojiHira[65] = { "\x82\xA0", "\x82\xA2", "\x82\xA4", "\x82\xA6", "\x82\xA8", "\x82\xA9", "\x82\xAB", "\x82\xAD", "\x82\xAF", "\x82\xB1", "\x82\xB3", "\x82\xB5", "\x82\xB7", "\x82\xB9", "\x82\xBB", "\x82\xBD", "\x82\xBF", "\x82\xC2", "\x82\xC4", "\x82\xC6", "\x82\xC8", "\x82\xC9", "\x82\xCA", "\x82\xCB", "\x82\xCC", "\x82\xCD", @@ -80,6 +78,8 @@ static const char* l_mojiEisu[65] = { "X", "k", "x", ",", "L", "Y", "l", "y", ".", "M", "Z", "m", "z", " ", }; #else +#define SJIS_MOJI(wmoji) *(u16*)wmoji + static const char* l_mojiHira[65] = { "あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ", "た", "ち", "つ", "て", "と", "な", "に", "ぬ", "ね", "の", "は", @@ -433,7 +433,7 @@ void dName_c::_move() { } #endif - cursorAnm(); + IF_NOT_DUSK(cursorAnm()); } int dName_c::nameCheck() { @@ -478,6 +478,10 @@ void dName_c::playNameSet(int nameLength) { } void dName_c::cursorAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mCurColAnmF, mCursorColorKey, 2.0f); + dusk::vdt::present_looping(mCurTexAnmF, mCursorTexKey, 2.0f); +#else mCurColAnmF += 2; if (mCurColAnmF >= mCursorColorKey->getFrameMax()) { mCurColAnmF -= mCursorColorKey->getFrameMax(); @@ -489,6 +493,7 @@ void dName_c::cursorAnm() { mCurTexAnmF -= mCursorTexKey->getFrameMax(); } mCursorTexKey->setFrame(mCurTexAnmF); +#endif nameIn.NameInScr->animation(); } @@ -1090,9 +1095,7 @@ void dName_c::selectCursorMove() { ((J2DTextBox*)mMojiIcon[mCharRow + mCharColumn * 5]->getPanePtr()) ->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); - #if TARGET_PC - nameWide(); - #endif + IF_DUSK(nameWide()); Vec pos = mMojiIcon[mCharRow + mCharColumn * 5]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(pos.x, pos.y, mMojiIcon[mCharRow + mCharColumn * 5]->getPanePtr(), true); @@ -1619,9 +1622,20 @@ void dName_c::nameWide() { #endif void dName_c::_draw() { - #if TARGET_PC - nameWide(); - #endif +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + for (int i = 0; i < 65; ++i) { + mMojiIcon[i]->presentAnime(); + } + for (int i = 0; i < 4; ++i) { + if (mMenuIcon[i] != NULL) { + mMenuIcon[i]->presentAnime(); + } + } + cursorAnm(); + nameWide(); + } +#endif dComIfGd_set2DOpa(&nameIn); dComIfGd_set2DOpa(mSelIcon); diff --git a/src/d/d_ovlp_fade3.cpp b/src/d/d_ovlp_fade3.cpp index 60bea1337f..3dc931ca74 100644 --- a/src/d/d_ovlp_fade3.cpp +++ b/src/d/d_ovlp_fade3.cpp @@ -137,7 +137,8 @@ dOvlpFd3_c::dOvlpFd3_c() { } dCam_getBody()->Stop(); - mDoGph_gInf_c::startFadeOut(XREG_S(3) + (field_0x11f >> 1) + 90); + IF_DUSK(const int time = dusk::getSettings().game.fastTransitions.getValue() ? 0 : 90); + mDoGph_gInf_c::startFadeOut(XREG_S(3) + (field_0x11f >> 1) + DUSK_IF_ELSE(time, 90)); } void dOvlpFd3_c::execFirstSnap() { @@ -166,8 +167,10 @@ void dOvlpFd3_c::execFadeOut() { if (mTimer < 0) { if (++mTimer == 0) { + IF_DUSK_BLOCK(!dusk::getSettings().game.fastTransitions) mDoGph_gInf_c::startFadeOut(XREG_S(1) + 75); mTimer = XREG_S(2) + 90; + IF_DUSK_BLOCK_END mDoAud_setFadeOutStart(0); } } else { diff --git a/src/d/d_pane_class.cpp b/src/d/d_pane_class.cpp index 4d5104ffcf..f833857873 100644 --- a/src/d/d_pane_class.cpp +++ b/src/d/d_pane_class.cpp @@ -13,7 +13,16 @@ #include "m_Do/m_Do_ext.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include "dusk/interp/lerp.h" +#include "dusk/interp/user_interface.h" +#endif + CPaneMgr::CPaneMgr() { +#if TARGET_PC + mScaleAnimation.active = false; + mColorAnimation.active = false; +#endif mpFirstStackAlpha = NULL; mpFirstStackSize = NULL; } @@ -45,6 +54,12 @@ void CPaneMgr::setAlpha(u8 alpha) { } void CPaneMgr::reinit() { +#if TARGET_PC + mAlphaAnimation.active = false; + mScaleAnimation.active = false; + mColorAnimation.active = false; +#endif + mInitPos.x = getPanePtr()->getBounds().i.x; mInitPos.y = getPanePtr()->getBounds().i.y; @@ -66,6 +81,12 @@ void CPaneMgr::reinit() { } void CPaneMgr::initiate(J2DPane* p_pane, JKRExpHeap* p_heap) { +#if TARGET_PC + mAlphaAnimation.active = false; + mScaleAnimation.active = false; + mColorAnimation.active = false; +#endif + mPane = p_pane; if (p_heap) { @@ -269,7 +290,29 @@ void CPaneMgr::paneScale(f32 x, f32 y) { } } +#if TARGET_PC +void CPaneMgr::presentAnime() { + presentAlphaAnime(); + dusk::vdt::present_animation(mScaleAnimation, mScaleAnime, *this, [&](f32 rate) { + const f32 scale = dusk::interp::lerp(mScaleAnimation.start, mScaleAnimation.end, rate); + getPanePtr()->scale(getInitScaleX() * scale, getInitScaleY() * scale); + }); + dusk::vdt::present_animation(mColorAnimation, mColorAnime, *this, [&](f32 rate) { + setBlackWhite( + dusk::interp::lerp(mColorAnimation.start.first, mColorAnimation.end.first, rate), + dusk::interp::lerp(mColorAnimation.start.second, mColorAnimation.end.second, rate)); + }); +} +#endif + bool CPaneMgr::scaleAnime(s16 param_0, f32 param_1, f32 param_2, u8 param_3) { +#if TARGET_PC + if (!dusk::vdt::request_animation(mScaleAnimation, mScaleAnime, param_0, param_1, param_2, param_3)) { + return false; + } + getPanePtr()->scale(mInitScale.x * param_2, mInitScale.y * param_2); + return true; +#else if (mScaleAnime < param_0 - 1) { mScaleAnime++; f32 rate = rateCalc(param_0, mScaleAnime, param_3); @@ -281,10 +324,20 @@ bool CPaneMgr::scaleAnime(s16 param_0, f32 param_1, f32 param_2, u8 param_3) { return true; } return false; +#endif } bool CPaneMgr::colorAnime(s16 anmTimer, JUtility::TColor startBlack, JUtility::TColor endBlack, - JUtility::TColor startWhite, JUtility::TColor endWhite, u8 calcType) { + JUtility::TColor startWhite, JUtility::TColor endWhite, u8 calcType) { +#if TARGET_PC + if (!dusk::vdt::request_animation(mColorAnimation, mColorAnime, anmTimer, + {startBlack, startWhite}, {endBlack, endWhite}, calcType)) + { + return false; + } + setBlackWhite(endBlack, endWhite); + return true; +#else if (mColorAnime < anmTimer - 1) { mColorAnime++; f32 rate = rateCalc(anmTimer, mColorAnime, calcType); @@ -349,6 +402,7 @@ bool CPaneMgr::colorAnime(s16 anmTimer, JUtility::TColor startBlack, JUtility::T } return false; +#endif } Vec CPaneMgr::getGlobalVtx(J2DPane* p_pane, Mtx* param_1, u8 param_2, bool param_3, diff --git a/src/d/d_pane_class_alpha.cpp b/src/d/d_pane_class_alpha.cpp index 31bc8bc9b4..d82b77d220 100644 --- a/src/d/d_pane_class_alpha.cpp +++ b/src/d/d_pane_class_alpha.cpp @@ -5,7 +5,13 @@ #include "JSystem/JKernel/JKRExpHeap.h" #include "m_Do/m_Do_ext.h" -CPaneMgrAlpha::CPaneMgrAlpha() {} +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif + +CPaneMgrAlpha::CPaneMgrAlpha() { + IF_DUSK(mAlphaAnimation.active = false); +} CPaneMgrAlpha::CPaneMgrAlpha(J2DScreen* p_screen, u64 tag, u8 flags, JKRExpHeap* p_heap) { J2DPane* pane = p_screen->search(tag); @@ -21,6 +27,8 @@ CPaneMgrAlpha::~CPaneMgrAlpha() { } void CPaneMgrAlpha::initiateAlpha(J2DPane* p_pane, JKRExpHeap* p_heap) { + IF_DUSK(mAlphaAnimation.active = false); + mPane = p_pane; if (p_heap != NULL) { @@ -68,7 +76,7 @@ bool CPaneMgrAlpha::isVisible() { return mPane->isVisible(); } -f32 CPaneMgrAlpha::rateCalc(s16 maxTimer, s16 curTimer, u8 calcType) { +f32 CPaneMgrAlpha::rateCalc(DUSK_IF_ELSE(f32, s16) maxTimer, DUSK_IF_ELSE(f32, s16) curTimer, u8 calcType) { if (maxTimer <= curTimer) { return 1.0f; } @@ -105,7 +113,22 @@ f32 CPaneMgrAlpha::getAlphaRate() { return rate; } +#if TARGET_PC +void CPaneMgrAlpha::presentAlphaAnime() { + dusk::vdt::present_animation(mAlphaAnimation, mAlphaTimer, *this, [&](f32 rate) { + setAlpha(mAlphaAnimation.start + rate * (f32)(mAlphaAnimation.end - mAlphaAnimation.start)); + }); +} +#endif + bool CPaneMgrAlpha::alphaAnime(s16 timer, u8 startAlpha, u8 endAlpha, u8 calcType) { +#if TARGET_PC + if (!dusk::vdt::request_animation(mAlphaAnimation, mAlphaTimer, timer, startAlpha, endAlpha, calcType)) { + return false; + } + setAlpha(endAlpha); + return true; +#else if (mAlphaTimer < timer - 1) { mAlphaTimer++; f32 rate = rateCalc(timer, mAlphaTimer, calcType); @@ -117,20 +140,41 @@ bool CPaneMgrAlpha::alphaAnime(s16 timer, u8 startAlpha, u8 endAlpha, u8 calcTyp } return false; +#endif } bool CPaneMgrAlpha::alphaAnimeLoop(s16 param_0, u8 param_1, u8 param_2, u8 param_3) { bool ret = false; s16 temp_r4 = param_0 / 2; +#if TARGET_PC + mAlphaAnimation.active = false; + if (param_0 <= 0) { + mAlphaTimer = 0.0f; + setAlpha(param_1); + return true; + } + const f32 frames = dusk::game_clock::original_frames(); + if (frames <= 0.0f) { + return false; + } + if (mAlphaTimer >= param_0) { + mAlphaTimer = 0.0f; + ret = true; + } else { + ret = mAlphaTimer + frames >= param_0; + dusk::vdt::advance_looping_frame(mAlphaTimer, 1.0f, param_0); + } +#else mAlphaTimer++; if (mAlphaTimer >= param_0) { mAlphaTimer = 0; ret = true; } +#endif - s16 tmp; + DUSK_IF_ELSE(f32, s16) tmp; if (mAlphaTimer < temp_r4) { tmp = mAlphaTimer; } else { diff --git a/src/d/d_particle.cpp b/src/d/d_particle.cpp index e33de068db..d1e175dfc1 100644 --- a/src/d/d_particle.cpp +++ b/src/d/d_particle.cpp @@ -8,30 +8,27 @@ // weak data from it (unlike here). #include "d/d_particle.h" -#include -#include "JSystem/J3DGraphAnimator/J3DMaterialAnm.h" -#include "JSystem/J3DGraphBase/J3DMaterial.h" +#include "d/d_jnt_col.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "JSystem/JKernel/JKRSolidHeap.h" -#include "JSystem/JMath/JMATrigonometric.h" +#include "JSystem/J3DGraphBase/J3DMaterial.h" +#include "JSystem/J3DGraphAnimator/J3DMaterialAnm.h" #include "JSystem/JParticle/JPAEmitterManager.h" #include "JSystem/JParticle/JPAResourceManager.h" -#include "SSystem/SComponent/c_math.h" -#include "d/actor/d_a_player.h" -#include "d/d_com_inf_game.h" -#include "d/d_jnt_col.h" +#include "JSystem/JMath/JMATrigonometric.h" #include "d/d_s_play.h" -#include "f_op/f_op_actor_mng.h" -#include "m_Do/m_Do_graphic.h" +#include +#include "d/d_com_inf_game.h" #include "m_Do/m_Do_lib.h" -#include "tracy/Tracy.hpp" +#include "m_Do/m_Do_graphic.h" +#include "f_op/f_op_actor_mng.h" +#include "d/actor/d_a_player.h" +#include "SSystem/SComponent/c_math.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" -#endif +#include "dusk/game_clock.h" -#ifndef __MWERKS__ -#include "helpers/math.h" +#include #endif #if DEBUG @@ -1979,7 +1976,7 @@ void dPa_light8PcallBack::draw(JPABaseEmitter* param_1, JPABaseParticle* param_2 JGeometry::TVec3 local_160; JGeometry::TVec3 local_16c; #if TARGET_PC - if (dusk::frame_interp::is_sim_frame()) + if (dusk::game_clock::is_sim_frame()) #endif { dPa_setWindPower(param_2); diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp index 97a230b2e0..26295158f4 100644 --- a/src/d/d_resorce.cpp +++ b/src/d/d_resorce.cpp @@ -95,13 +95,28 @@ static void setIndirectTex(J3DModelData* i_modelData) { for (u16 i = 0; i < texture->getNum(); i++) { textureName = nameTab->getName(i); +#if TARGET_PC + if (textureName == NULL) { + continue; + } + if (strcmp(textureName, "fbtex_dummy") == 0) { +#else if (memcmp(textureName, "fbtex_dummy", 0xc) == 0) { +#endif texture->setResTIMG(i, *mDoGph_gInf_c::getFrameBufferTimg()); } +#if TARGET_PC + if (strcmp(textureName, "dummy") == 0) { +#else if (memcmp(textureName, "dummy", 6) == 0) { +#endif texture->setResTIMG(i, *mDoGph_gInf_c::getFrameBufferTimg()); } +#if TARGET_PC + if (strcmp(textureName, "Zbuffer") == 0) { +#else if (memcmp(textureName, "Zbuffer", 8) == 0) { +#endif texture->setResTIMG(i, *mDoGph_gInf_c::getZbufferTimg()); } } diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp index 48c7519ded..ab9f7984bc 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -807,8 +807,7 @@ void dScnLogo_c::nextSceneChange() { dComIfGs_setNoFile(dusk::SaveRequested); dComIfGs_setDataNum(saveSlot); if (status == 1) { - dusk::mods::svc::save_slot_loaded( - saveSlot, buf + saveSlot * SAVEDATA_SIZE); + dusk::mods::svc::save_slot_loaded(saveSlot); const dusk::gamemode::GameMode* gameMode = dusk::gamemode::getGameModeManager().getCurrentGameMode(); if (gameMode) { diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index de66b0317d..12caa544c1 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -5,29 +5,26 @@ #include "d/dolzel.h" // IWYU pragma: keep +#include "d/d_s_name.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "d/d_com_inf_game.h" #include "d/d_meter2_info.h" -#include "d/d_s_name.h" -#include "f_op/f_op_overlap_mng.h" #include "f_op/f_op_scene_mng.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_machine.h" -#include "m_Do/m_Do_main.h" #include "m_Do/m_Do_mtx.h" - -#ifdef TARGET_PC -#include "dusk/autosave.h" -#include "dusk/game_mode.hpp" -#include "dusk/imgui/ImGuiConsole.hpp" -#include "dusk/livesplit.h" -#include "dusk/memory.h" -#include "dusk/settings.h" -#include "dusk/speedrun.h" -#endif +#include "m_Do/m_Do_main.h" +#include "f_op/f_op_overlap_mng.h" #if TARGET_PC +#include "dusk/autosave.h" +#include "dusk/game_clock.h" +#include "dusk/game_mode.hpp" +#include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/memory.h" +#include "dusk/settings.h" + #define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen) #else #define SHOW_TV_SETTINGS_SCREEN (1) @@ -151,6 +148,8 @@ s32 dScnName_c::create() { #endif dComIfGp_getVibration().Init(); + + IF_DUSK(base.draw_interp_frame = true); } return phase_state; } @@ -247,7 +246,9 @@ s32 dScnName_c::execute() { } s32 dScnName_c::draw() { + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame()) dComIfGp_getVibration().Run(); + IF_DUSK_BLOCK_END switch (mDrawProc) { case 0: diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index 7caba5c9c8..6a4d4c3c05 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -16,8 +16,6 @@ #include #include -#include "dusk/version.hpp" - #if PLATFORM_WII || PLATFORM_SHIELD #include #include @@ -28,11 +26,11 @@ #endif #if TARGET_PC -#include #include "dusk/game_mode.hpp" #include "dusk/settings.h" - +#include "dusk/version.hpp" #include "helpers/string.hpp" + #define strcpy SafeStringCopy #endif diff --git a/src/d/d_select_cursor.cpp b/src/d/d_select_cursor.cpp index 640f8072cd..b651c617cd 100644 --- a/src/d/d_select_cursor.cpp +++ b/src/d/d_select_cursor.cpp @@ -5,9 +5,22 @@ #include "d/d_com_inf_game.h" #include "JSystem/J2DGraph/J2DAnimation.h" #include "JSystem/J2DGraph/J2DAnmLoader.h" -#include "dusk/frame_interpolation.h" #include +#if TARGET_PC +#include "dusk/interp/samples.h" +#include "dusk/interp/user_interface.h" + +#include "m_Do/m_Do_lib.h" + +namespace { +struct CursorSamples { + dusk::interp::Samples positions; + dusk::interp::Samples alpha; +}; +} // namespace +#endif + dSelect_cursorHIO_c::dSelect_cursorHIO_c() { field_0x8 = 1.0f; mXAxisExpansion = 1.0f; @@ -202,6 +215,7 @@ dSelect_cursor_c::dSelect_cursor_c(u8 param_0, f32 param_1, JKRArchive* param_2) dSelect_cursor_c::~dSelect_cursor_c() { + IF_DUSK(dusk::interp::erase_owned_samples(this);) JKR_DELETE(mpScreen); mpScreen = NULL; if (mpPaneMgr) { @@ -247,6 +261,15 @@ dSelect_cursor_c::~dSelect_cursor_c() { } void dSelect_cursor_c::draw() { +#if TARGET_PC + const f32 alpha = mpPaneMgr->getAlphaRate(); + const bool visible = mpPaneMgr->isVisible(); + const f32 presentedAlpha = dusk::interp::get(this).alpha.read(0, alpha); + mpPaneMgr->setAlphaRate(presentedAlpha); + if (alpha == 0.0f && presentedAlpha > 0.0f) { + mpPaneMgr->show(); + } +#endif mpPaneMgr->getAlphaRate(); update(); J2DGrafContext* gphCtx = dComIfGp_getCurrentGrafPort(); @@ -255,6 +278,12 @@ void dSelect_cursor_c::draw() { if (mpSelectIcon) { mpSelectIcon->drawSelf(); // inline here, but not sure how to properly define it } +#if TARGET_PC + mpPaneMgr->setAlphaRate(alpha); + if (!visible) { + mpPaneMgr->hide(); + } +#endif } void dSelect_cursor_c::update() { @@ -262,14 +291,31 @@ void dSelect_cursor_c::update() { if (field_0xb6 == 3) { fVar1 = 0.5f; } -#ifdef TARGET_PC + +#if TARGET_PC if (mpPane) { Vec pos = mpPaneMgr->getGlobalVtxCenter(mpPane, false, 0); mPositionX = pos.x; mPositionY = pos.y; } + + auto& samples = dusk::interp::get(this); + cXyz position(mPositionX, mPositionY, 0.0f); + if (mpPane == nullptr) { + if (mInterpolatePosition) { + position = samples.positions.read(0, position); + } + if (mWorldCursor) { + cXyz world = samples.positions.read(1, mWorldPosition); + Vec projected; + mDoLib_project(&world, &projected); + position.x += projected.x; + position.y += projected.y; + } + } #endif - mpPaneMgr->translate(mPositionX, mPositionY); + + mpPaneMgr->translate(DUSK_IF_ELSE(position.x, mPositionX), DUSK_IF_ELSE(position.y, mPositionY)); if (mpCursorHIO->mDebugON) { mParam1 = mpCursorHIO->mXAxisExpansion; mParam2 = mpCursorHIO->mYAxisExpansion; @@ -281,21 +327,22 @@ void dSelect_cursor_c::update() { if (mUpdateFlag) { if (field_0x30) { if (chkPlayAnime(0)) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (mNameIdx == 1) { - field_0x44 += mpCursorHIO->field_0x8 * fVar1; - } else { - field_0x44 += fVar1; - } - - if (field_0x44 >= field_0x30->getFrameMax()) { - field_0x44 -= field_0x30->getFrameMax(); - } +#if TARGET_PC + dusk::vdt::advance_looping_frame( + field_0x44, mNameIdx == 1 ? mpCursorHIO->field_0x8 * fVar1 : fVar1, + field_0x30->getFrameMax()); +#else + if (mNameIdx == 1) { + field_0x44 += mpCursorHIO->field_0x8 * fVar1; + } else { + field_0x44 += fVar1; } + if (field_0x44 >= field_0x30->getFrameMax()) { + field_0x44 -= field_0x30->getFrameMax(); + } +#endif + field_0x30->setFrame(field_0x44); setBpkAnimation(field_0x30); } else { @@ -310,19 +357,20 @@ void dSelect_cursor_c::update() { for (int i = 0; i < 2; i++) { if (field_0x34[i]) { if ((i == 0 && chkPlayAnime(2)) || (i == 1 && chkPlayAnime(3))) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (mNameIdx == 1) { - field_0x48[i] += mpCursorHIO->field_0x8 * fVar1; - } else { - field_0x48[i] += fVar1; - } - if (field_0x48[i] >= field_0x34[i]->getFrameMax()) { - field_0x48[i] -= field_0x34[i]->getFrameMax(); - } +#if TARGET_PC + dusk::vdt::advance_looping_frame( + field_0x48[i], mNameIdx == 1 ? mpCursorHIO->field_0x8 * fVar1 : fVar1, + field_0x34[i]->getFrameMax()); +#else + if (mNameIdx == 1) { + field_0x48[i] += mpCursorHIO->field_0x8 * fVar1; + } else { + field_0x48[i] += fVar1; } + if (field_0x48[i] >= field_0x34[i]->getFrameMax()) { + field_0x48[i] -= field_0x34[i]->getFrameMax(); + } +#endif field_0x34[i]->setFrame(field_0x48[i]); } @@ -331,19 +379,20 @@ void dSelect_cursor_c::update() { } if (field_0x2C && chkPlayAnime(1)) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (mNameIdx == 1) { - field_0x40 += mpCursorHIO->field_0x8 * fVar1; - } else { - field_0x40 += fVar1; - } - if (field_0x40 >= field_0x2C->getFrameMax()) { - field_0x40 -= field_0x2C->getFrameMax(); - } +#if TARGET_PC + dusk::vdt::advance_looping_frame( + field_0x40, mNameIdx == 1 ? mpCursorHIO->field_0x8 * fVar1 : fVar1, + field_0x2C->getFrameMax()); +#else + if (mNameIdx == 1) { + field_0x40 += mpCursorHIO->field_0x8 * fVar1; + } else { + field_0x40 += fVar1; } + if (field_0x40 >= field_0x2C->getFrameMax()) { + field_0x40 -= field_0x2C->getFrameMax(); + } +#endif field_0x2C->setFrame(field_0x40); setBckAnimation(field_0x2C); @@ -351,12 +400,7 @@ void dSelect_cursor_c::update() { } if (chkPlayAnime(1) && mNameIdx == 0) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - setCursorAnimation(); - } + setCursorAnimation(); } mpScreen->animation(); @@ -454,6 +498,7 @@ void dSelect_cursor_c::setAlphaRate(f32 i_alphaRate) { } mpPaneMgr->setAlphaRate(i_alphaRate); + IF_DUSK(dusk::interp::get(this).alpha.capture(&i_alphaRate, 1)); } int dSelect_cursor_c::addAlpha() { @@ -468,7 +513,7 @@ int dSelect_cursor_c::addAlpha() { } else { alpha_timer++; mpPaneMgr->alphaAnimeStart(alpha_timer); - mpPaneMgr->setAlphaRate(alpha_timer/5.0f); + DUSK_IF_ELSE(setAlphaRate, mpPaneMgr->setAlphaRate)(alpha_timer/5.0f); } return 0; @@ -485,7 +530,7 @@ int dSelect_cursor_c::decAlpha() { } else { alpha_timer--; mpPaneMgr->alphaAnimeStart(alpha_timer); - mpPaneMgr->setAlphaRate(alpha_timer/5.0f); + DUSK_IF_ELSE(setAlphaRate, mpPaneMgr->setAlphaRate)(alpha_timer/5.0f); } return 0; @@ -543,10 +588,14 @@ void dSelect_cursor_c::setCursorAnimation() { fVar1 = 0.5f; } +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x40, fVar1, 20.0f); +#else field_0x40 += fVar1; if (field_0x40 >= 20.0f) { field_0x40 -= 20.0f; } +#endif f32 fVar2; f32 param3 = mParam3; fVar2 = field_0x40; @@ -576,7 +625,32 @@ void dSelect_cursor_c::moveCenter(J2DPane* i_pane, f32 i_x, f32 i_y) { i_pane->translate(i_x,i_y); } -#ifdef TARGET_PC +#if TARGET_PC +void dSelect_cursor_c::setInterp(bool enabled) { + if (mInterpolatePosition != enabled) { + mInterpolatePosition = enabled; + dusk::interp::get(this).positions.reset(); + } +} + +void dSelect_cursor_c::setPos(f32 x, f32 y, const cXyz* world) { + auto& positions = dusk::interp::get(this).positions; + if (mWorldCursor != (world != nullptr)) { + positions.reset(); + } + mWorldCursor = world != nullptr; + mPositionX = x; + mPositionY = y; + if (world) { + mpPane = nullptr; + mWorldPosition = *world; + } + if (mInterpolatePosition) { + const cXyz values[] = {cXyz(x, y, 0.0f), mWorldPosition}; + positions.capture(values, mWorldCursor ? 2 : 1); + } +} + void dSelect_cursor_c::refreshAspectScale(f32 param_0) { mParam1 = mBaseParam1 * param_0; } diff --git a/src/d/d_select_icon.cpp b/src/d/d_select_icon.cpp index 32be9c15e2..895146c986 100644 --- a/src/d/d_select_icon.cpp +++ b/src/d/d_select_icon.cpp @@ -2,33 +2,31 @@ #include "d/d_select_icon.h" #include "JSystem/J2DGraph/J2DAnimation.h" -#include "dusk/frame_interpolation.h" + +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif dSi_HIO_c::dSi_HIO_c() {} void dSelect_icon_c::animation() { if (field_0x10->getAlpha() != 0) { -#ifdef TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - field_0x20 += field_0x2c; - if (field_0x20 >= field_0x1c->getFrameMax()) { - field_0x20 = 0.0f; - } - field_0x1c->setFrame(field_0x20); - - field_0x28 += field_0x2c; - if (field_0x28 >= field_0x24->getFrameMax()) { - field_0x28 = 0.0f; - } +#if TARGET_PC + dusk::vdt::present_looping(field_0x20, field_0x1c, field_0x2c); + dusk::vdt::present_looping(field_0x28, field_0x24, field_0x2c); +#else + field_0x20 += field_0x2c; + if (field_0x20 >= field_0x1c->getFrameMax()) { + field_0x20 = 0.0f; } -#ifdef TARGET_PC - // FRAME INTERP NOTE: Set even if not advancing field_0x1c->setFrame(field_0x20); -#endif + field_0x28 += field_0x2c; + if (field_0x28 >= field_0x24->getFrameMax()) { + field_0x28 = 0.0f; + } field_0x24->setFrame(field_0x28); +#endif field_0x8->animation(); field_0x10->paneScale(field_0x14, field_0x14); } diff --git a/src/d/d_shop_system.cpp b/src/d/d_shop_system.cpp index 1d7fed2406..2b26ca2a4a 100644 --- a/src/d/d_shop_system.cpp +++ b/src/d/d_shop_system.cpp @@ -19,6 +19,9 @@ #include "d/actor/d_a_tag_shop_item.h" #include +#if TARGET_PC +#include "dusk/interp/lerp.h" +#endif static daTag_ShopItem_c* dShopSystem_itemActor[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -718,13 +721,21 @@ int dShopSystem_c::itemRotate() { return 1; } +#if TARGET_PC +#define D_SHOP_SELECTED_ITEM_NO \ + ((mCursorPos > 0 && mCursorPos <= ITEM_MAX_e) ? dShopSystem_itemNo[mCursorPos - 1] : \ + dItemNo_NONE_e) +#else +#define D_SHOP_SELECTED_ITEM_NO dShopSystem_itemNo[mCursorPos - 1] +#endif + int dShopSystem_c::itemZoom(cXyz* param_0) { cXyz local_1c; if (field_0xf60 >= 0) { local_1c.set(*param_0); - if (dShopSystem_itemNo[mCursorPos - 1] == dItemNo_OIL_BOTTLE_e) { + if (D_SHOP_SELECTED_ITEM_NO == dItemNo_OIL_BOTTLE_e) { mItemCtrl.setZoomAnime(mCursorPos, &local_1c, g_cursorHIO.mSeraShopObjZoomAngleX + -5000, isFlag(8) ? true : false); @@ -775,9 +786,9 @@ int dShopSystem_c::itemZoom(cXyz* param_0) { g_cursorHIO.mObjZoom.z + 150.0f); } else { if (mMasterType == 5) { - if (dShopSystem_itemNo[mCursorPos - 1] == dItemNo_ARROW_10_e || - dShopSystem_itemNo[mCursorPos - 1] == dItemNo_ARROW_20_e || - dShopSystem_itemNo[mCursorPos - 1] == dItemNo_ARROW_30_e) + if (D_SHOP_SELECTED_ITEM_NO == dItemNo_ARROW_10_e || + D_SHOP_SELECTED_ITEM_NO == dItemNo_ARROW_20_e || + D_SHOP_SELECTED_ITEM_NO == dItemNo_ARROW_30_e) { local_34.set(g_cursorHIO.mObjZoom.x, -50.0f + g_cursorHIO.mObjZoom.y + 5.0f, (g_cursorHIO.mObjZoom.z + 250.0f) - 60.0f); @@ -808,7 +819,7 @@ int dShopSystem_c::itemZoom(cXyz* param_0) { u8 dvar1 = mMasterType; if (dvar1 == 1) { - if (dShopSystem_itemNo[mCursorPos - 1] == dItemNo_OIL_BOTTLE_e) { + if (D_SHOP_SELECTED_ITEM_NO == dItemNo_OIL_BOTTLE_e) { mItemCtrl.setZoomAnime(mCursorPos, &local_1c, g_cursorHIO.mShopObjZoomAngleX + -7000, isFlag(8) ? true : false); @@ -817,7 +828,7 @@ int dShopSystem_c::itemZoom(cXyz* param_0) { isFlag(8) ? true : false); } } else if (dvar1 == 2) { - if (dShopSystem_itemNo[mCursorPos - 1] == dItemNo_RED_BOTTLE_e) { + if (D_SHOP_SELECTED_ITEM_NO == dItemNo_RED_BOTTLE_e) { mItemCtrl.setZoomAnime(mCursorPos, &local_1c, g_cursorHIO.mShopObjZoomAngleX - 3000, isFlag(8) ? true : false); } else { @@ -825,9 +836,9 @@ int dShopSystem_c::itemZoom(cXyz* param_0) { isFlag(8) ? true : false); } } else if (dvar1 == 5) { - if (dShopSystem_itemNo[mCursorPos - 1] == dItemNo_ARROW_10_e || - dShopSystem_itemNo[mCursorPos - 1] == dItemNo_ARROW_20_e || - dShopSystem_itemNo[mCursorPos - 1] == dItemNo_ARROW_30_e) + if (D_SHOP_SELECTED_ITEM_NO == dItemNo_ARROW_10_e || + D_SHOP_SELECTED_ITEM_NO == dItemNo_ARROW_20_e || + D_SHOP_SELECTED_ITEM_NO == dItemNo_ARROW_30_e) { mItemCtrl.setZoomAnime(mCursorPos, &local_1c, g_cursorHIO.mShopObjZoomAngleX - 4000, isFlag(8) ? true : false); @@ -850,6 +861,8 @@ int dShopSystem_c::itemZoom(cXyz* param_0) { return 1; } +#undef D_SHOP_SELECTED_ITEM_NO + int dShopSystem_c::seq_wait(fopAc_ac_c* param_0, dMsgFlow_c* param_1) { return 0; } @@ -866,10 +879,14 @@ int dShopSystem_c::seq_start(fopAc_ac_c* actor, dMsgFlow_c* i_flow) { } cXyz pos3d; - cXyz pos2d; + cXyz pos2d IF_DUSK((0.0f, 0.0f, 0.0f)); pos3d.set(mItemCtrl.getCurrentPos(0)); +#if TARGET_PC + mpDrawCursor->setWorldPos(pos3d, 0.0f, g_cursorHIO.mShopCursorOffsetY); +#else pos3Dto2D(&pos3d, &pos2d); mpDrawCursor->setPos(pos2d.x, pos2d.y + g_cursorHIO.mShopCursorOffsetY); +#endif if (chkSpMode() && !beforeStartSeqAction(i_flow, field_0xf5c)) { return 0; @@ -1083,9 +1100,9 @@ int dShopSystem_c::seq_select(fopAc_ac_c* actor, dMsgFlow_c* i_flow) { if (old_cursor != 0) { cXyz pos3d; - cXyz pos2d; + cXyz pos2d IF_DUSK((0.0f, 0.0f, 0.0f)); pos3d.set(mItemCtrl.getCurrentPos(old_cursor - 1)); - pos3Dto2D(&pos3d, &pos2d); + IF_NOT_DUSK(pos3Dto2D(&pos3d, &pos2d)); if (old_cursor == 7) { pos2d.x += g_cursorHIO.mMagicArmorCursorOffsetX; @@ -1094,7 +1111,11 @@ int dShopSystem_c::seq_select(fopAc_ac_c* actor, dMsgFlow_c* i_flow) { pos2d.y += g_cursorHIO.mShopCursorOffsetY; } +#if TARGET_PC + mpDrawCursor->setWorldPos(pos3d, pos2d.x, pos2d.y); +#else mpDrawCursor->setPos(pos2d.x, pos2d.y); +#endif } return 0; @@ -1105,11 +1126,11 @@ int dShopSystem_c::seq_moving(fopAc_ac_c*, dMsgFlow_c*) { cXyz last_pos3d; cXyz pos3d; - cXyz last_pos2d; - cXyz pos2d; + cXyz last_pos2d IF_DUSK((0.0f, 0.0f, 0.0f)); + cXyz pos2d IF_DUSK((0.0f, 0.0f, 0.0f)); pos3d.set(mItemCtrl.getCurrentPos(mCursorPos - 1)); - pos3Dto2D(&pos3d, &pos2d); + IF_NOT_DUSK(pos3Dto2D(&pos3d, &pos2d)); if (mCursorPos == 7) { pos2d.x += g_cursorHIO.mMagicArmorCursorOffsetX; @@ -1120,7 +1141,7 @@ int dShopSystem_c::seq_moving(fopAc_ac_c*, dMsgFlow_c*) { if (mLastCursorPos != 0) { last_pos3d.set(mItemCtrl.getCurrentPos(mLastCursorPos - 1)); - pos3Dto2D(&last_pos3d, &last_pos2d); + IF_NOT_DUSK(pos3Dto2D(&last_pos3d, &last_pos2d)); if (mLastCursorPos == 7) { last_pos2d.x += g_cursorHIO.mMagicArmorCursorOffsetX; @@ -1130,11 +1151,22 @@ int dShopSystem_c::seq_moving(fopAc_ac_c*, dMsgFlow_c*) { } f32 tmp = (f32)(field_0xf68 * field_0xf68) / 9.0f; +#if TARGET_PC + cXyz position; + dusk::interp::lerp(position, last_pos3d, pos3d, tmp); + dusk::interp::lerp(pos2d, last_pos2d, pos2d, tmp); + mpDrawCursor->setWorldPos(position, pos2d.x, pos2d.y); +#else mpDrawCursor->setPos(last_pos2d.x + tmp * (pos2d.x - last_pos2d.x), last_pos2d.y + tmp * (pos2d.y - last_pos2d.y)); +#endif } else { +#if TARGET_PC + mpDrawCursor->setWorldPos(pos3d, pos2d.x, pos2d.y); +#else mpDrawCursor->setPos(pos2d.x, pos2d.y); +#endif } if (field_0xf68 >= 3) { diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index 0b933bdb34..14167723d9 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -22,11 +22,12 @@ #include #include -#include "dusk/logging.h" -#include "helpers/string.hpp" #if TARGET_PC +#include "dusk/logging.h" +#include "dusk/mods/svc/actor.hpp" #include "dusk/mods/svc/stage.hpp" -#include +#include "helpers/string.hpp" + #include #endif @@ -1524,6 +1525,14 @@ static void dummy0() { } dStage_objectNameInf* dStage_searchName(char const* objName) { +#if TARGET_PC + dStage_objectNameInf* info = + dusk::mods::svc::actor_impl::get_stageinfo_from_full_name(objName); + if (info != nullptr) { + return info; + } +#endif + dStage_objectNameInf* obj = l_objectName; for (u32 i = 0; i < ARRAY_SIZEU(l_objectName); i++) { @@ -1561,6 +1570,15 @@ dStage_objectNameInf* dStage_searchNameCI(char const* objName) { const char* dStage_getName(s16 procName, s8 argument) { static char tmp_name[dStage_NAME_LENGTH]; +#if TARGET_PC + const char* name = dusk::mods::svc::actor_impl::get_full_name_from_proc_name(procName); + if (name[0] != '\0') { + strncpy(tmp_name, name, sizeof(tmp_name) - 1); + tmp_name[sizeof(tmp_name) - 1] = '\0'; + return tmp_name; + } +#endif + dStage_objectNameInf* obj = l_objectName; char* tmp = NULL; @@ -1614,17 +1632,15 @@ DUSK_GAME_DATA dStage_roomControl_c::roomDzs_c dStage_roomControl_c::m_roomDzs; u8 dStage_roomControl_c::mNoArcBank; #endif +static void dStage_actorCreate(stage_actor_data_class* i_actorData, fopAcM_prm_class* i_actorPrm + IF_DUSK_ARG(size_t recordSize = sizeof(stage_actor_data_class))) { #if TARGET_PC -static void dStage_actorCreate(stage_actor_data_class* i_actorData, fopAcM_prm_class* i_actorPrm, - size_t recordSize = sizeof(stage_actor_data_class)) { if (!dusk::mods::svc::stage_apply_actor_edits(i_actorData, i_actorPrm, recordSize, i_actorPrm->room_no)) { JKRFree(i_actorPrm); return; } -#else -static void dStage_actorCreate(stage_actor_data_class* i_actorData, fopAcM_prm_class* i_actorPrm) { #endif dStage_objectNameInf* actorInf = dStage_searchName(i_actorData->name); @@ -1687,6 +1703,8 @@ static int dStage_playerInit(dStage_dt_c* i_stage, void* i_data, int num, void* fopAcM_prm_class* appen = fopAcM_CreateAppend(); JUT_ASSERT(1586, appen != NULL); + IF_DUSK(stage_actor_data_class newPoint{};) // data stored on the stack for any newly registered spawn points + int point = dComIfGp_getStartStagePoint(); u32 roomParam = dComIfGs_getRestartRoomParam(); if (point == -2 || point == -3) { @@ -1707,6 +1725,13 @@ static int dStage_playerInit(dStage_dt_c* i_stage, void* i_data, int num, void* int i; for (i = 0; i < num; i++) { +#ifdef TARGET_PC + // If, for whatever reason, we want to patch the point ID, we need to do it here. + if (!dusk::mods::svc::stage_apply_actor_edits(player_data, nullptr, sizeof(*player_data), i_stage->getRoomNo())) { + player_data++; + continue; // If the spawn is deleted, check the next one + } +#endif if ((u8)player_data->base.angle.z == unk) { break; } @@ -1714,16 +1739,43 @@ static int dStage_playerInit(dStage_dt_c* i_stage, void* i_data, int num, void* } if (i == num) { #if TARGET_PC - std::vector valid_points; - valid_points.reserve(num); - player_data = player->m_entries; - for (i = 0; i < num; i++) { - valid_points.push_back(player_data->base.angle.z); - player_data++; + // Search through all new actor edits and return the point data via userdata + struct newActors_userData { + stage_actor_data_class* out_point; + bool out_pointSet; + int16_t pointNo; + }; + newActors_userData params = { + &newPoint, false, (int16_t)unk + }; + dusk::mods::svc::stage_create_new_actors(i_stage->getRoomNo(), + [](void* user, const void* record, size_t size) { + const stage_actor_data_class* i_record = static_cast(record); + auto* params = static_cast(user); + + // We don't cast i_record->base.angle.z to a u8 here so we can register any spawn point within the s16 range + if (i_record->base.angle.z == params->pointNo && size == sizeof(stage_actor_data_class) && strncmp(i_record->name,"Link",7) == 0) { + std::memcpy(params->out_point, i_record, size); + params->out_pointSet = true; + } + }, + ¶ms); + + if (params.out_pointSet) { + player_data = &newPoint; + } else { + // If the requested spawn point isn't found, print all valid points within the log + std::vector valid_points; + valid_points.reserve(num); + player_data = player->m_entries; + for (i = 0; i < num; i++) { + valid_points.push_back(player_data->base.angle.z); + player_data++; + } + std::ranges::sort(valid_points); + DuskLog.fatal("Failed to find player start point for next stage! Requested point: {}, Valid points: [{}]", + point, fmt::join(valid_points, ", ")); } - std::ranges::sort(valid_points); - DuskLog.fatal("Failed to find player start point for next stage! Requested point: {}, Valid points: [{}]", - point, fmt::join(valid_points, ", ")); #else OS_REPORT_ERROR("プレイヤーが発見できません。[No.%d]\n切り替えの情報や処理の確認をお願いします。\n", point); #endif @@ -2129,12 +2181,8 @@ static int dStage_doorInfoInit(dStage_dt_c* i_stage, void* i_data, int entryNum, appen->base = actor_data->base; appen->room_no = (int)i_stage->getRoomNo(); appen->scale = tgsc_data->scale; -#if TARGET_PC - dStage_actorCreate(actor_data, appen, - sizeof(stage_actor_data_class) + sizeof(fopAcM_prmScale_class)); -#else - dStage_actorCreate(actor_data, appen); -#endif + dStage_actorCreate(actor_data, appen IF_DUSK_ARG( + sizeof(stage_actor_data_class) + sizeof(fopAcM_prmScale_class))); } tgsc_data++; } @@ -2567,6 +2615,11 @@ static void dusk_stage_svc_new_actor_create(dStage_dt_c* i_stage) { stage_tgsc_data_class object{}; std::memcpy(&object, record, size); + if (strncmp(object.name,"Link",7) == 0) { + // We create new spawns for Link during dStage_playerInit. Ignore them here so we don't get duplicates. + return; + } + fopAcM_prm_class* appen = fopAcM_CreateAppend(); if (appen != nullptr) { appen->base = object.base; @@ -2778,9 +2831,7 @@ void dStage_dt_c_roomReLoader(void* i_data, dStage_dt_c* i_stage, int param_2) { }; dStage_dt_c_decode(i_data, i_stage, l_funcTable, ARRAY_SIZEU(l_funcTable)); -#if TARGET_PC - dusk_stage_svc_new_actor_create(i_stage); -#endif + IF_DUSK(dusk_stage_svc_new_actor_create(i_stage)); layerActorLoader(i_data, i_stage, param_2); } diff --git a/src/d/d_timer.cpp b/src/d/d_timer.cpp index e07cc3af80..7e05830e67 100644 --- a/src/d/d_timer.cpp +++ b/src/d/d_timer.cpp @@ -23,9 +23,26 @@ #include "m_Do/m_Do_lib.h" #include -#include "dusk/frame_interpolation.h" +#if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" +#include + +namespace { +struct TimerAnimation { + dusk::vdt::FrameAnimation close; + bool presenting = false; + bool opening = false; + bool closing = false; + bool slide = false; +}; + +absl::flat_hash_map sTimerAnimations; + +} // namespace +#endif + static int dTimer_createStart2D(s32 param_0, u16 param_1); int dTimer_c::_create() { @@ -213,6 +230,9 @@ int dTimer_c::_execute() { } if (m_timer_mode == 3 || m_timer_mode == 4 || m_timer_mode == 6 || m_timer_mode == 5) { +#if TARGET_PC + sTimerAnimations[mp_tm_scrn].slide = true; +#else f32 pos_y = mp_tm_scrn->getTimerTransY(); if (pos_y < 145.0f) { pos_y += 25.0f; @@ -222,6 +242,7 @@ int dTimer_c::_execute() { mp_tm_scrn->setTimerTrans(0.0f, pos_y); } +#endif } break; default: @@ -290,6 +311,11 @@ static f32 dummyLiteralOrder2() { int dTimer_c::_draw() { if (dComIfGp_isPauseFlag() || dMsgObject_isTalkNowCheck()) { +#if TARGET_PC + if (m_mode == 8 && dusk::game_clock::is_presentation_frame()) { + mp_tm_scrn->presentAnims(); + } +#endif return 1; } @@ -477,6 +503,7 @@ bool dTimer_c::isStart() { } dDlst_TimerScrnDraw_c::dDlst_TimerScrnDraw_c() { + IF_DUSK(sTimerAnimations.erase(this)); field_0x3e2 = 0; mHIOType = 0; field_0x3D8 = 0; @@ -881,6 +908,7 @@ void dDlst_TimerScrnDraw_c::setTimer(int i_time) { } } +#if !TARGET_PC if (mpTimeParent != NULL) { if ((dComIfGp_event_getMode() == 1 && field_0x3DF != 0) || !isVisible()) { s16 alpha_time = mpTimeParent->getAlphaTimer(); @@ -900,6 +928,7 @@ void dDlst_TimerScrnDraw_c::setTimer(int i_time) { } } } +#endif } void dDlst_TimerScrnDraw_c::setCounter(u8 i_count, u8 i_max) { @@ -1004,6 +1033,7 @@ void dDlst_TimerScrnDraw_c::setCounter(u8 i_count, u8 i_max) { } } +#if !TARGET_PC if ((dComIfGp_event_getMode() == 1 && field_0x3E0 != 0) || !isVisible()) { if (mpCowParent != NULL) { s16 alpha_time = mpCowParent->getAlphaTimer(); @@ -1053,6 +1083,7 @@ void dDlst_TimerScrnDraw_c::setCounter(u8 i_count, u8 i_max) { } } } +#endif } void dDlst_TimerScrnDraw_c::setParentPos(f32 i_posX, f32 i_posY) { @@ -1110,17 +1141,25 @@ void dDlst_TimerScrnDraw_c::setShowType(u8 i_type) { } void dDlst_TimerScrnDraw_c::anime() { +#if TARGET_PC + auto& animations = sTimerAnimations[this]; + if (!animations.presenting) { + animations.opening = true; + return; + } + const f32 previousFrame = field_0x3D8; +#endif static const s16 animeFrame[] = {7, 15, 22}; if (field_0x3DE == 0) { if (field_0x3D8 <= animeFrame[2]) { - field_0x3D8++; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0x3D8, 23.0f, 1.0f), field_0x3D8++); } else { field_0x3DE = 1; } - if (field_0x3D8 <= animeFrame[1]) { - f32 temp_f31 = acc(animeFrame[1], field_0x3D8, 0); + if (DUSK_IF_ELSE(previousFrame, field_0x3D8) <= animeFrame[1]) { + f32 temp_f31 = acc(animeFrame[1], DUSK_IF_ELSE(std::min(field_0x3D8, (f32)animeFrame[1]), field_0x3D8), 0); f32 temp_f1 = (1.0f - temp_f31) * -50.0f; if (mpTimeParent != NULL) { @@ -1144,13 +1183,13 @@ void dDlst_TimerScrnDraw_c::anime() { } } - if (field_0x3D8 == animeFrame[1]) { + if (field_0x3D8 DUSK_IF_ELSE(>=, ==) animeFrame[1]) { field_0x3DF = 1; } } - if (field_0x3D8 > animeFrame[0] && field_0x3D8 <= animeFrame[2]) { - f32 temp_f31 = acc(animeFrame[1], field_0x3D8 - animeFrame[0], 0); + if (field_0x3D8 > animeFrame[0] && DUSK_IF_ELSE(previousFrame, field_0x3D8) <= animeFrame[2]) { + f32 temp_f31 = acc(animeFrame[1], DUSK_IF_ELSE(std::min(field_0x3D8, (f32)animeFrame[2]), field_0x3D8) - animeFrame[0], 0); f32 temp_f1 = (1.0f - temp_f31) * -50.0f; if (mpCowParent != NULL) { @@ -1171,7 +1210,7 @@ void dDlst_TimerScrnDraw_c::anime() { g_drawHIO.mMiniGame.mIconSizeY[mHIOType]); } - if (field_0x3D8 == animeFrame[2]) { + if (field_0x3D8 DUSK_IF_ELSE(>=, ==) animeFrame[2]) { field_0x3E0 = 1; } } @@ -1180,11 +1219,21 @@ void dDlst_TimerScrnDraw_c::anime() { BOOL dDlst_TimerScrnDraw_c::closeAnime() { BOOL var_r31 = false; +#if TARGET_PC + auto& animations = sTimerAnimations[this]; + if (!animations.presenting) { + animations.closing = true; + field_0x3D8 = std::min(field_0x3D8 + 1.0f, 7.0f); + animations.close.approach(field_0x3D8 - 1.0f, 7.0f); + } + const f32 frame = animations.presenting ? animations.close.advance(field_0x3D8) : field_0x3D8; +#else field_0x3D8++; +#endif - if (field_0x3D8 <= 7) { - f32 temp_f31 = acc(7, field_0x3D8, 0); - f32 temp_f30 = acc(7, 7 - field_0x3D8, 0); + if (DUSK_IF_ELSE(frame, field_0x3D8) <= 7) { + f32 temp_f31 = acc(7, DUSK_IF_ELSE(frame, field_0x3D8), 0); + f32 temp_f30 = acc(7, 7 - DUSK_IF_ELSE(frame, field_0x3D8), 0); f32 temp_f1 = temp_f31 * -50.0f; if (mpTimeParent != NULL) { @@ -1330,7 +1379,55 @@ s32 dDlst_TimerScrnDraw_c::createStart(u16 i_messageID) { return 0; } +#if TARGET_PC +void dDlst_TimerScrnDraw_c::presentAnims() { + auto& animations = sTimerAnimations[this]; + animations.presenting = true; + if (animations.slide) { + dusk::vdt::advance_toward_frame(mTimerTransY, 145.0f, 25.0f); + if (field_0x3e2 != 0 && mHIOType == 0) { + setTimerPos(g_drawHIO.mMiniGame.mTimerPosX_4x3, + g_drawHIO.mMiniGame.mTimerPosY_4x3); + } else { + setTimerPos(g_drawHIO.mMiniGame.mTimerPosX[mHIOType], + g_drawHIO.mMiniGame.mTimerPosY[mHIOType]); + } + } + if (animations.closing) { + closeAnime(); + } else { + if (animations.opening) { + anime(); + } + const bool event = dComIfGp_event_getMode() == 1; + auto fade = [&](CPaneMgr* pane, bool hidden, bool ready, f32 alpha, bool timer) { + if (pane == NULL) { + return; + } + f32 frame = pane->getAlphaTimer(); + const f32 previous = frame; + dusk::vdt::advance_toward_frame(frame, hidden ? 5.0f : 0.0f, 1.0f); + if (ready || frame != previous) { + const f32 rate = timer ? acc(5.0f, 5.0f - frame, 0.0f) + : 1.0f - acc(5.0f, frame, 0.0f); + pane->setAlphaRate(mParentAlpha * alpha * rate); + } + pane->alphaAnimeStart(frame); + }; + fade(mpTimeParent, (event && field_0x3DF != 0) || !isVisible(), field_0x3DF != 0, + g_drawHIO.mMiniGame.mTimerAlpha[mHIOType], true); + const bool hideCounter = (event && field_0x3E0 != 0) || !isVisible(); + fade(mpCowParent, hideCounter, field_0x3E0 != 0, + g_drawHIO.mMiniGame.mCounterAlpha[mHIOType], false); + fade(mpImageParent, hideCounter, field_0x3E0 != 0, + g_drawHIO.mMiniGame.mIconAlpha[mHIOType], false); + } + animations.presenting = false; +} +#endif + void dDlst_TimerScrnDraw_c::draw() { + IF_DUSK(presentAnims()); J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort(); graf_ctx->setup2D(); @@ -1340,21 +1437,20 @@ void dDlst_TimerScrnDraw_c::draw() { ((f32)g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f); for (int i = 0; i < 51; i++) { + if (m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp) { + if (m_getin_info[i].bck_frame < 60.0f) { #if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp) { - if (m_getin_info[i].bck_frame < 60.0f) { - m_getin_info[i].bck_frame += g_drawHIO.mMiniGame.mGetInTextAnimSpeed; - if (m_getin_info[i].bck_frame > 60.0f) { - m_getin_info[i].bck_frame = 60.0f; - } - } else if (m_getin_info[i].bck_frame < g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f) { - m_getin_info[i].bck_frame++; - } else if (m_getin_info[i].bck_frame < temp) { - m_getin_info[i].bck_frame++; + dusk::vdt::advance_toward_frame(m_getin_info[i].bck_frame, 60.0f, g_drawHIO.mMiniGame.mGetInTextAnimSpeed); +#else + m_getin_info[i].bck_frame += g_drawHIO.mMiniGame.mGetInTextAnimSpeed; + if (m_getin_info[i].bck_frame > 60.0f) { + m_getin_info[i].bck_frame = 60.0f; } +#endif + } else if (m_getin_info[i].bck_frame < g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f) { + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(m_getin_info[i].bck_frame, (f32)g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f, 1.0f), m_getin_info[i].bck_frame++); + } else if (m_getin_info[i].bck_frame < temp) { + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(m_getin_info[i].bck_frame, temp, 1.0f), m_getin_info[i].bck_frame++); } } @@ -1396,19 +1492,14 @@ void dDlst_TimerScrnDraw_c::draw() { if (m_getin_info[i].pikari_frame > 0.0f) { drawPikari(i); } else if (m_getin_info[i].pikari_frame == -1.0f) { -#if TARGET_PC - if (dusk::frame_interp::get_ui_tick_pending()) -#endif - { - if (m_getin_info[i].field_0xc == 0) { - if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mGetInPikariAppearFrames) { - m_getin_info[i].pikari_frame = - 18.0f - g_drawHIO.mMiniGame.mGetInPikariAnimSpeed; - } - } else if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mStartPikariAppearFrames) { + if (m_getin_info[i].field_0xc == 0) { + if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mGetInPikariAppearFrames) { m_getin_info[i].pikari_frame = - 18.0f - g_drawHIO.mMiniGame.mStartPikariAnimSpeed; + 18.0f - g_drawHIO.mMiniGame.mGetInPikariAnimSpeed; } + } else if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mStartPikariAppearFrames) { + m_getin_info[i].pikari_frame = + 18.0f - g_drawHIO.mMiniGame.mStartPikariAnimSpeed; } } } @@ -1661,7 +1752,9 @@ u8 dTimer_isReadyFlag() { return 0; } -dDlst_TimerScrnDraw_c::~dDlst_TimerScrnDraw_c() {} +dDlst_TimerScrnDraw_c::~dDlst_TimerScrnDraw_c() { + IF_DUSK(sTimerAnimations.erase(this)); +} int dTimer_c::createGetIn(cXyz i_pos) { return mp_tm_scrn->createGetIn(i_pos); diff --git a/src/dusk/achievements.h b/src/dusk/achievements.h index c93ea5d626..eec7fc6d5a 100644 --- a/src/dusk/achievements.h +++ b/src/dusk/achievements.h @@ -1,13 +1,12 @@ #pragma once +#include + #include #include -#include -#include #include #include #include -#include "nlohmann/json.hpp" namespace dusk { diff --git a/src/dusk/action_bindings.cpp b/src/dusk/action_bindings.cpp index c919c9d4fe..2c97c2c894 100644 --- a/src/dusk/action_bindings.cpp +++ b/src/dusk/action_bindings.cpp @@ -1,9 +1,10 @@ #include "dusk/action_bindings.h" -#include "aurora/lib/input.hpp" #include "dusk/settings.h" #include "dusk/ui/ui.hpp" +#include + namespace dusk { static std::array(ActionBinds::COUNT)>, PAD_CHANMAX> actionPressData{}; diff --git a/src/dusk/archive.cpp b/src/dusk/archive.cpp new file mode 100644 index 0000000000..c9ba131bc8 --- /dev/null +++ b/src/dusk/archive.cpp @@ -0,0 +1,175 @@ +#include "archive.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace dusk::archive { +namespace { + +constexpr std::array ZipMagic{'P', 'K', '\x03', '\x04'}; + +PackageFormat detect_package_format(mz_zip_archive& zip) { + size_t modManifests = 0; + for (mz_uint index = 0, count = mz_zip_reader_get_num_files(&zip); index < count; ++index) { + mz_zip_archive_file_stat stat{}; + if (!mz_zip_reader_file_stat(&zip, index, &stat) || + mz_zip_reader_is_file_a_directory(&zip, index)) + { + continue; + } + const std::string_view name{stat.m_filename}; + modManifests += name == "mod.json"; + } + if (modManifests == 1) { + return PackageFormat::Mod; + } + return PackageFormat::Unknown; +} + +} // namespace + +struct ZipArchive::Impl { + ~Impl() { + if (open) { + mz_zip_reader_end(&zip); + } + } + + static size_t read_zip(void* opaque, mz_uint64 offset, void* buffer, const size_t size) { + auto& archive = *static_cast(opaque); + std::error_code error; + return archive.file.read_at(offset, {static_cast(buffer), size}, error); + } + + borealis::io::RandomAccessFile file; + mz_zip_archive zip{}; + PackageFormat format = PackageFormat::Unknown; + bool open = false; + std::mutex mutex; +}; + +ZipArchive::ZipArchive(const std::filesystem::path& path) : m_impl{std::make_unique()} { + auto opened = borealis::io::RandomAccessFile::open(path); + if (opened.status != borealis::io::Status::Ok) { + throw std::runtime_error(opened.message); + } + m_impl->file = std::move(opened.file); + + std::array header{}; + std::error_code error; + const auto read = m_impl->file.read_at( + 0, {reinterpret_cast(header.data()), header.size()}, error); + if (error) { + throw std::runtime_error(fmt::format("Reading ZIP magic failed: {}", error.message())); + } + if (read != header.size() || header != ZipMagic) { + throw std::runtime_error("File does not have ZIP magic"); + } + + m_impl->zip.m_pRead = Impl::read_zip; + m_impl->zip.m_pIO_opaque = m_impl.get(); + if (!mz_zip_reader_init(&m_impl->zip, m_impl->file.size(), 0)) { + const auto zipError = mz_zip_get_last_error(&m_impl->zip); + throw std::runtime_error( + fmt::format("Opening ZIP failed: {}", mz_zip_get_error_string(zipError))); + } + m_impl->open = true; + m_impl->format = detect_package_format(m_impl->zip); +} + +ZipArchive::~ZipArchive() = default; + +ZipArchive::ZipArchive(ZipArchive&&) noexcept = default; +ZipArchive& ZipArchive::operator=(ZipArchive&&) noexcept = default; + +PackageFormat ZipArchive::package_format() const noexcept { + return m_impl->format; +} + +std::vector ZipArchive::read_file(const std::string_view name) { + std::lock_guard lock{m_impl->mutex}; + const std::string fileName{name}; + size_t size = 0; + void* extracted = mz_zip_reader_extract_file_to_heap(&m_impl->zip, fileName.c_str(), &size, 0); + if (extracted == nullptr) { + throw std::runtime_error(fmt::format("File does not exist: {}", name)); + } + + const std::unique_ptr owner{extracted, &mz_free}; + const std::span data{static_cast(owner.get()), size}; + std::vector result; + result.assign(data.begin(), data.end()); + return result; +} + +std::vector ZipArchive::file_names() { + std::lock_guard lock{m_impl->mutex}; + std::vector results; + for (mz_uint index = 0, count = mz_zip_reader_get_num_files(&m_impl->zip); index < count; + ++index) + { + mz_zip_archive_file_stat stat{}; + if (!mz_zip_reader_file_stat(&m_impl->zip, index, &stat) || + mz_zip_reader_is_file_a_directory(&m_impl->zip, index)) + { + continue; + } + results.emplace_back(stat.m_filename); + } + return results; +} + +std::optional stat_zip_entry( + mz_zip_archive* pZip, std::string_view name) { + const std::string fileName{name}; + uint32_t fileIdx; + if (!mz_zip_reader_locate_file_v2(pZip, fileName.c_str(), nullptr, 0, &fileIdx)) { + return std::nullopt; + } + + mz_zip_archive_file_stat stat; + if (!mz_zip_reader_file_stat(pZip, fileIdx, &stat)) { + return std::nullopt; + } + + return stat; +} + +bool ZipArchive::file_exists(const std::string& name) const { + std::lock_guard lock{m_impl->mutex}; + auto result = stat_zip_entry(&m_impl->zip, name); + if (!result.has_value()) { + return false; + } + + return !result->m_is_directory; +} + +bool ZipArchive::directory_exists(const std::string& name) const { + std::lock_guard lock{m_impl->mutex}; + auto result = stat_zip_entry(&m_impl->zip, name); + if (!result.has_value()) { + return false; + } + + return result->m_is_directory; +} + +size_t ZipArchive::file_size(const std::string& name) { + std::lock_guard lock{m_impl->mutex}; + auto stat = stat_zip_entry(&m_impl->zip, name); + if (!stat.has_value()) { + throw std::runtime_error(fmt::format("Unable to locate file in ZIP: {}", name)); + } + + return static_cast(stat->m_uncomp_size); +} + +} // namespace dusk::archive diff --git a/src/dusk/archive.hpp b/src/dusk/archive.hpp new file mode 100644 index 0000000000..5005620073 --- /dev/null +++ b/src/dusk/archive.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace dusk::archive { + +enum class PackageFormat { + Unknown, + Mod, + // Save, +}; + +class ZipArchive { +public: + explicit ZipArchive(const std::filesystem::path& path); + ~ZipArchive(); + + ZipArchive(ZipArchive&&) noexcept; + ZipArchive& operator=(ZipArchive&&) noexcept; + ZipArchive(const ZipArchive&) = delete; + ZipArchive& operator=(const ZipArchive&) = delete; + + PackageFormat package_format() const noexcept; + std::vector read_file(std::string_view name); + std::vector file_names(); + size_t file_size(const std::string& name); + bool file_exists(const std::string& name) const; + bool directory_exists(const std::string& name) const; + +private: + struct Impl; + std::unique_ptr m_impl; +}; + +} // namespace dusk::archive diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 3fda98a436..bdb2acaf65 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -1,25 +1,26 @@ -#include "dusk/audio/DuskAudioSystem.h" +#include "DuskAudioSystem.h" + +#include "DuskDsp.hpp" + +#include "JSystem/JAudio2/JASAiCtrl.h" +#include "JSystem/JAudio2/JASAudioThread.h" +#include "JSystem/JAudio2/JASChannel.h" +#include "JSystem/JAudio2/JASCriticalSection.h" +#include "JSystem/JAudio2/JASDriverIF.h" +#include "JSystem/JAudio2/JASDSPChannel.h" +#include "JSystem/JAudio2/JASHeapCtrl.h" #include +#include + #include #include #include -#include "JSystem/JAudio2/JASAiCtrl.h" -#include "JSystem/JAudio2/JASChannel.h" -#include "JSystem/JAudio2/JASCriticalSection.h" -#include "JSystem/JAudio2/JASDSPChannel.h" -#include "JSystem/JAudio2/JASHeapCtrl.h" - -#include "DuskDsp.hpp" -#include "JSystem/JAudio2/JASAudioThread.h" -#include "JSystem/JAudio2/JASDriverIF.h" -#include "tracy/Tracy.hpp" - using namespace dusk::audio; static OutputSubframe OutBuffer; -static std::array OutInterleaveBuffer; +static std::array OutInterleaveBufferFull; static SDL_AudioStream* PlaybackStream; @@ -42,24 +43,62 @@ static int RenderNewAudioFrame(); /** * Render an audio subframe and output it to SDL3. */ -static void RenderAudioSubframe(); +static int RenderAudioSubframe(); -static void InitSDL3Output() { - SDL_Init(SDL_INIT_AUDIO); +static size_t GetChannelCountForOutputMode(dusk::AudioOutputMode config) { + switch (config) { + default: + case dusk::AudioOutputMode::StereoSpeakers: + case dusk::AudioOutputMode::StereoHeadphones: + return 2; + case dusk::AudioOutputMode::Surround6ch: + return 6; + case dusk::AudioOutputMode::Surround8ch: + return 8; + } +} - constexpr SDL_AudioSpec spec = { +static bool InitSDL3Output() { + const auto speakerConfig = dusk::getSettings().audio.outputMode.getValue(); + const auto desiredChannelCount = GetChannelCountForOutputMode(speakerConfig); + const bool hrtf = speakerConfig == dusk::AudioOutputMode::StereoHeadphones; + + if (PlaybackStream && desiredChannelCount == OutChannelCount) { + JASCriticalSection section; + EnableHrtf = hrtf; + return false; + } + + if (PlaybackStream) { + SDL_PauseAudioStreamDevice(PlaybackStream); + SDL_DestroyAudioStream(PlaybackStream); + } else { + SDL_Init(SDL_INIT_AUDIO); + } + + const SDL_AudioSpec spec = { SDL_AUDIO_F32, - 2, + static_cast(desiredChannelCount), SampleRate, }; - PlaybackStream = SDL_OpenAudioDeviceStream( - SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, - &spec, - &GetNewAudio, - nullptr); + SDL_AudioStream* newStream = + SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, &GetNewAudio, nullptr); + + { + JASCriticalSection section; + EnableHrtf = hrtf; + OutChannelCount = desiredChannelCount; + PlaybackStream = newStream; + } + + return true; } void dusk::audio::Initialize() { + // enable 48 kHz mode + // this will scale voice pitch and track tempo accordingly + JASDriver::setOutputRate(static_cast(-1)); + InitSDL3Output(); DspInit(); @@ -71,6 +110,22 @@ void dusk::audio::Initialize() { SDL_ResumeAudioStreamDevice(PlaybackStream); } +void dusk::audio::Reinitialize() { + // don't re-init unless we've initialized first (using PlaybackStream being set as proxy) + if (PlaybackStream && InitSDL3Output()) { + SDL_ResumeAudioStreamDevice(PlaybackStream); + } +} + +void dusk::audio::Shutdown() { + if (PlaybackStream) { + SDL_DestroyAudioStream(PlaybackStream); + PlaybackStream = nullptr; + } + + SDL_QuitSubSystem(SDL_INIT_AUDIO); +} + void dusk::audio::SetMasterVolume(const f32 value) { JASCriticalSection section; @@ -112,53 +167,122 @@ int RenderNewAudioFrame() { ZoneScoped; JASCriticalSection section; const u32 countSubframes = JASDriver::getSubFrames(); + int bytesWritten = 0; JASAudioThread::setDSPSyncCount(countSubframes); for (u32 i = 0; i < countSubframes; i++) { - RenderAudioSubframe(); + bytesWritten += RenderAudioSubframe(); JASAudioThread::snIntCount -= 1; } - return static_cast(countSubframes) * sizeof(OutputSubframe); + return bytesWritten; } static void InterleaveOutputData(const OutputSubframe& data, std::span target) { - assert(target.size() >= data.channels[0].size() * OutputSubframe::NUM_CHANNELS); + assert(target.size() >= data.channels[0].size() * OutChannelCount); size_t outPos = 0; for (size_t inPos = 0; inPos < data.channels[0].size(); inPos++) { - for (size_t channelIdx = 0; channelIdx < OutputSubframe::NUM_CHANNELS; channelIdx++) { + for (size_t channelIdx = 0; channelIdx < OutChannelCount; channelIdx++) { target[outPos++] = data.channels[channelIdx][inPos]; } } } -void RenderAudioSubframe() { +constexpr auto kExtChannels = 2; +constexpr auto kExtSrcInSampleCount = DSP_SUBFRAME_SIZE * 2; +constexpr auto kExtSrcOutSampleCount = DSP_SUBFRAME_SIZE * 3; + +// simple 3:2 resampler +struct LinearResampler { + f32 prev = 0.0f; + int phase = 0; + + void resample(std::span in, std::span out, int skip = 1, int offset = 0) { + const auto inSize = in.size() / skip; + const auto outSize = out.size() / skip; + assert(inSize * 3 == outSize * 2); + + int idx = -1; + int phi = phase; + int n = 0; + + while (n < outSize) { + f32 s0 = (idx < 0) ? prev : in[idx * skip + offset] / 32768.0f; + f32 s1 = in[idx * skip + skip + offset] / 32768.0f; + f32 frac = phi * (1.0f / 3.0f); + + out[n++ * skip + offset] = s0 + frac * (s1 - s0); + + phi += 2; + if (phi >= 3) { + phi -= 3; + idx++; + } + } + + prev = in[(inSize - 1) * skip + offset] / 32768.0f; + phase = phi; + } +}; + +static struct { + std::array buf; + int available = 0; + LinearResampler leftSrc; + LinearResampler rightSrc; + + bool hungry() const { return available <= 0; } + + void feed(std::span in) { + const auto out = std::span{buf}; + leftSrc.resample(in, out, kExtChannels, 0); + rightSrc.resample(in, out, kExtChannels, 1); + available = buf.size(); + } + + std::span drain_subframe() { + constexpr auto kDrainSize = DSP_SUBFRAME_SIZE * kExtChannels; + assert(available >= kDrainSize); + const auto rv = std::span{&buf[buf.size() - available], kDrainSize}; + available -= kDrainSize; + return rv; + } +} extResampler; + +int RenderAudioSubframe() { ZoneScoped; OutBuffer = {}; JASDriver::updateDSP(); DspRender(OutBuffer); + std::span OutInterleaveBuffer{OutInterleaveBufferFull.data(), static_cast(DSP_SUBFRAME_SIZE * OutChannelCount)}; InterleaveOutputData(OutBuffer, OutInterleaveBuffer); if (JASDriver::extMixCallback != nullptr && JASDriver::sMixMode == MIX_MODE_INTERLEAVE) { - static_assert(OutputSubframe::NUM_CHANNELS == 2); // This code only works with Stereo so far. - // NOTE: In the real game, this gets called on the entire audio frame, rather than the subframe. - // That's probably more efficient, but I didn't wanna change the code to calculate the - // entire audio buffers at once. - // This is only used for the movie player, and it seems to work fine with the smaller calls. - const auto mixData = JASDriver::extMixCallback(DSP_SUBFRAME_SIZE); - if (mixData) { - for (int i = 0; i < OutInterleaveBuffer.size(); i++) { - OutInterleaveBuffer[i] += static_cast(mixData[i]) / static_cast(0x7FFF); + if (extResampler.hungry()) { + const auto mixData = JASDriver::extMixCallback(kExtSrcInSampleCount); + if (mixData) { + extResampler.feed(std::span{mixData, kExtSrcInSampleCount * kExtChannels}); + } + } + + if (!extResampler.hungry()) { + const auto inBuf = extResampler.drain_subframe(); + for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { + const auto oi = i * OutChannelCount; + OutInterleaveBuffer[oi] += inBuf[i * kExtChannels]; + OutInterleaveBuffer[oi + 1] += inBuf[i * kExtChannels + 1]; } } } - SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer)); + auto bytesToWrite = OutInterleaveBuffer.size_bytes(); + SDL_PutAudioStreamData(PlaybackStream, OutInterleaveBuffer.data(), bytesToWrite); + return bytesToWrite; } u32 dusk::audio::GetResetCount(int channelIdx) { diff --git a/src/dusk/audio/DuskAudioSystem.h b/src/dusk/audio/DuskAudioSystem.h index 780187f9d2..8a9f9634b7 100644 --- a/src/dusk/audio/DuskAudioSystem.h +++ b/src/dusk/audio/DuskAudioSystem.h @@ -19,6 +19,10 @@ namespace dusk::audio { */ void Initialize(); + void Reinitialize(); + + void Shutdown(); + void SetEnableReverb(bool value); void SetMasterVolume(f32 value); diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 4009b37e39..92bfb74cb3 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -1,21 +1,23 @@ +#include "DuskDsp.hpp" + +#include "Adpcm.hpp" +#include "DuskAudioSystem.h" +#include "global.h" + +#include "dusk/logging.h" +#include "helpers/endian.h" + #include #include - -#include "DuskDsp.hpp" +#include +#include #include #include #include -#include +#include #include - -#include "Adpcm.hpp" -#include "dusk/audio/DuskAudioSystem.h" -#include "dusk/logging.h" -#include "freeverb/revmodel.hpp" -#include "global.h" -#include "helpers/endian.h" -#include "tracy/Tracy.hpp" +#include using namespace dusk::audio; @@ -50,11 +52,11 @@ bool dusk::audio::EnableReverb = true; bool dusk::audio::DumpAudio = false; bool dusk::audio::EnableHrtf = false; f32 dusk::audio::HrtfGain = 0.5f; - +u8 dusk::audio::OutChannelCount = 0; // 3dB at 5kHz. -static constexpr f32 HRTF_LP_K = 0.75f; -static constexpr f32 HRTF_ALLPASS_G = 0.3f; +static constexpr f32 HRTF_LP_K = (SampleRate == 48000) ? 0.6267f : 0.75f; +static constexpr f32 HRTF_ALLPASS_G = (SampleRate == 48000) ? 0.448f : 0.3f; // Front never drops below (1 - HRTF_EXTRACT_MAX). static constexpr f32 HRTF_EXTRACT_MAX = 0.6f; @@ -101,28 +103,6 @@ static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 sampl return (samples / channel.mSamplesPerBlock) * BlockBytes(channel); } -/** - * Render the audio data contributed by a single DSP channel. Reads & decodes new input samples. - */ -static void RenderChannel( - JASDsp::TChannel& channel, - ChannelAuxData& channelAux, - OutputSubframe& subframe); - -static void RenderOutputChannel( - const JASDsp::TChannel& sourceChannel, - ChannelAuxData& aux, - OutputChannel outputChannel, - const std::span inputSamples, - OutputSubframe& fullOutputSubframe); - -/** - * Converts a pitch value on a DSP channel to a sample rate. - */ -constexpr static int PitchToSampleRate(u16 value) { - return static_cast(static_cast(SampleRate) * value / 4096); -} - /** * Reset state for a DSP channel between independent playbacks. */ @@ -164,6 +144,12 @@ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { } } +static void MixOutputSubframe(OutputSubframe& dst, const OutputSubframe& src) { + for (int i = 0; i < OutChannelCount; i++) { + MixSubframe(dst.channels[i], src.channels[i]); + } +} + enum class OscType : u16 { SQUARE_WAVE_PW_50 = 0, SAW_WAVE = 1, @@ -203,16 +189,14 @@ static void GenerateEvolvingHarmonic() { } } - static void RenderOscChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, - OutputSubframe& subframe) { + DspSubframe& buf) { if (channel.mResetFlag) ResetChannel(channel, channelAux); const u32 pitch = channel.mPitch; - DspSubframe buf = {}; const auto oscType = static_cast(channel.mBytesPerBlock); switch (oscType) { @@ -270,142 +254,6 @@ static void RenderOscChannel( DuskLog.error("RenderOscChannel: unimplemented oscillator type {}", channel.mBytesPerBlock); break; } - - auto samples = std::span(buf).subspan(0, DSP_SUBFRAME_SIZE); - RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, samples, subframe); - RenderOutputChannel(channel, channelAux, OutputChannel::RIGHT, samples, subframe); -} - - -void dusk::audio::DspRender(OutputSubframe& subframe) { - ZoneScoped; - if (DumpAudio != sDumpWasActive) { - sDumpWasActive = DumpAudio; - if (DumpAudio) { - OpenChannelDumpFiles(); - } else { - CloseChannelDumpFiles(); - } - } - - GenerateEvolvingHarmonic(); - - std::span channels(JASDsp::CH_BUF, DSP_CHANNELS); - - DspSubframe reverbInputL = {}; - DspSubframe reverbInputR = {}; - bool anyReverbInput = false; - - DspSubframe surroundBus = {}; - bool anySurroundInput = false; - - for (int i = 0; i < channels.size(); i++) { - auto& channel = channels[i]; - auto& channelAux = ChannelAux[i]; - - if (!channel.mIsActive) { - continue; - } - else if (channel.mPauseFlag) { - // Not really sure what the practical difference between pause and - // deactivation is. Either avoids clearing state or allows the DSP to avoid popping? - continue; - } - else if (channel.mForcedStop) { - channel.mIsFinished = true; - continue; - } - - OutputSubframe channelSubframe = {}; - if (channel.mWaveAramAddress == 0) { - RenderOscChannel(channel, channelAux, channelSubframe); - } else { - ValidateChannel(channel); - RenderChannel(channel, channelAux, channelSubframe); - } - - if (EnableReverb) { - // scale the input to the reverb rather than using wet/dry on the output. - // this way the reverb's internal buffers accumulate energy proportional to mAutoMixerFxMix, - // so any tail always decays at the correct level regardless of mAutoMixerFxMix changes - // prevents transients when the next sound starts playing with a different reverb level - // 600.0f was pulled out of my ass and just sounds good enough for console - f32 inputGain = (channel.mAutoMixerFxMix >> 8) / 600.0f; - if (inputGain > 0) { - anyReverbInput = true; - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - reverbInputL[j] += channelSubframe.channels[0][j] * inputGain; - reverbInputR[j] += channelSubframe.channels[1][j] * inputGain; - } - } - } - - if (EnableHrtf && channel.mAutoMixerBeenSet) { - f32 dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.0f; - if (dolby > 0.0f) { - anySurroundInput = true; - f32 extract = dolby * HRTF_EXTRACT_MAX; - f32 frontScale = 1.0f - extract; - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - f32 mono = (channelSubframe.channels[0][j] + channelSubframe.channels[1][j]) * 0.5f; - surroundBus[j] += mono * extract; - channelSubframe.channels[0][j] *= frontScale; - channelSubframe.channels[1][j] *= frontScale; - } - } - } - - if (DumpAudio && sChannelDumpFiles[i]) { - f32 interleaved[DSP_SUBFRAME_SIZE * 2]; - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - interleaved[j * 2 + 0] = channelSubframe.channels[0][j]; - interleaved[j * 2 + 1] = channelSubframe.channels[1][j]; - } - fwrite(interleaved, sizeof(f32), DSP_SUBFRAME_SIZE * 2, sChannelDumpFiles[i]); - } - - for (int o = 0; o < subframe.channels.size(); o++) { - MixSubframe(subframe.channels[o], channelSubframe.channels[o]); - } - } - - if (EnableReverb && (anyReverbInput || ReverbHasTail)) { - // Equivalent to -80 dBFS: rms = 1e-4, rms^2 = 1e-8, sumSq = 2 * N * 1e-8 - constexpr f32 REVERB_ENERGY_EPSILON = 2.0f * DSP_SUBFRAME_SIZE * 1e-8f; - f32 wetEnergy = SharedReverb.processmix( - reverbInputL.data(), reverbInputR.data(), - subframe.channels[0].data(), subframe.channels[1].data(), - DSP_SUBFRAME_SIZE, 1, 1.0f - ); - ReverbHasTail = wetEnergy >= REVERB_ENERGY_EPSILON; - } - - if (EnableHrtf && anySurroundInput) { - // Two-pole LPF: -12 dB/oct above 3 kHz - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - sHrtfLp1 = (1.0f - HRTF_LP_K) * sHrtfLp1 + HRTF_LP_K * surroundBus[j]; - sHrtfLp2 = (1.0f - HRTF_LP_K) * sHrtfLp2 + HRTF_LP_K * sHrtfLp1; - surroundBus[j] = sHrtfLp2; - } - - // Mix into L and R - // L gets the filtered signal directly; R gets it allpass for mild decorrelation - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - f32 s = surroundBus[j]; - - subframe.channels[0][j] += s * HrtfGain; - - f32 r = -HRTF_ALLPASS_G * s + sHrtfApIn1 + HRTF_ALLPASS_G * sHrtfApOut1; - sHrtfApIn1 = s; - sHrtfApOut1 = r; - subframe.channels[1][j] += r * HrtfGain; - } - } - - for (auto& channel : subframe.channels) { - ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); - } - PrevMasterVolume = MasterVolume; } /** @@ -455,7 +303,8 @@ static int ReadChannelSamplesChunk( assert(desiredSamples >= 0); - auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; + auto aramBase = static_cast(channel.mAramBaseAddress ? channel.mAramBaseAddress : ARGetStorageAddress()); + aramBase += channel.mWaveAramAddress; auto curSamplePosition = channel.mSamplePosition; u32 skipSamples = curSamplePosition % channel.mSamplesPerBlock; @@ -539,37 +388,154 @@ static void FillDecodeBuf(JASDsp::TChannel& channel, ChannelAuxData& aux, int ne } /** - * Get the expected BusConnect value needed to define the given output channel in a DSP channel. + * Adapts IIR biquad filter coeffs for fs = 32kHz -> 48kHz, Hz-preserving. */ -constexpr u16 GetBusConnect(const OutputChannel channel) { - switch (channel) { - // TODO: This is a guess for now. - case OutputChannel::LEFT: - return 0x0D00; - case OutputChannel::RIGHT: - return 0x0D60; - default: - CRASH("Invalid output channel!"); +static BiquadCoeffs RemapBiquad32to48(float b1, float b2, float a1, float a2) { + using cplx = std::complex; + + constexpr auto kOldRate = 32000.0; + constexpr auto kNewRate = f64(SampleRate); + constexpr auto kGamma = kOldRate / kNewRate; + + if (b1 == 0 && b2 == 0 && a1 == 0 && a2 == 0) { + return {0, 0, 0, 0}; } + + auto mapPoint = [](cplx z) { + if (std::abs(z.imag()) < 1e-9 * std::max(std::abs(z.real()), 1.0)) { + f64 r = z.real(); + f64 sign = (r >= 0.0) ? 1.0 : -1.0; + return cplx(sign * std::pow(std::abs(r), kGamma), 0.0); + } + f64 r = std::abs(z); + f64 theta = std::arg(z); + return std::polar(std::pow(r, kGamma), theta * kGamma); + }; + + f64 disc = f64(a1) * a1 + 4.0 * a2; + cplx p1, p2; + if (disc >= 0.0) { + f64 sq = std::sqrt(disc); + p1 = {(a1 + sq) * 0.5, 0.0}; + p2 = {(a1 - sq) * 0.5, 0.0}; + } else { + f64 sq = std::sqrt(-disc); + p1 = {a1 * 0.5, sq * 0.5}; + p2 = {a1 * 0.5, -sq * 0.5}; + } + cplx p1n = mapPoint(p1); + cplx p2n = mapPoint(p2); + f64 a1n = (p1n + p2n).real(); + f64 a2n = -(p1n * p2n).real(); + + f64 z0n = (b1 == 0.0f) ? 0.0 : mapPoint(cplx(-f64(b2) / b1, 0.0)).real(); + + f64 denOldDc = 1.0 - a1 - a2; + f64 fRef = (std::abs(denOldDc) > 1e-6) ? 0.0 : 1.0; + + auto H = [](cplx z, cplx p1_, cplx p2_, f64 bb1, f64 bb2) { + return (bb1 * z + bb2) / (z * (z - p1_) * (z - p2_)); + }; + + cplx zOldRef = std::polar(1.0, 2.0 * M_PI * fRef / kOldRate); + cplx zNewRef = std::polar(1.0, 2.0 * M_PI * fRef / kNewRate); + + cplx hOldRef = H(zOldRef, p1, p2, b1, b2) * zOldRef; + cplx shapeNewRef = H(zNewRef, p1n, p2n, 1.0, -z0n) * zNewRef; + + f64 K = (hOldRef / shapeNewRef).real(); + + return {float(K), float(-K * z0n), float(a1n), float(a2n)}; } /** - * For a DSP channel the JASDsp::OutputChannelConfig value targeting the given output channel. - * Returns null if the DSP channel does not output to this output channel. + * Render the audio data contributed by a single DSP channel. Reads & decodes new input samples. */ -static const JASDsp::OutputChannelConfig* GetOutputConfig( - const JASDsp::TChannel& sourceChannel, - OutputChannel channel) { +static void RenderChannel( + JASDsp::TChannel& channel, + ChannelAuxData& aux, + DspSubframe& buf) { - auto busConnect = GetBusConnect(channel); - for (const auto& mOutputChannel : sourceChannel.mOutputChannels) { - auto config = &mOutputChannel; - if (config->mBusConnect == busConnect) { - return config; + if (channel.mResetFlag) { + ResetChannel(channel, aux); + } + + // how many input samples we step per output sample, aka the resampling ratio + auto step = static_cast(channel.mPitch) / 4096.0f; + + // how many input samples to resample to DSP_SUBFRAME_SIZE output samples + int needed = static_cast(aux.resamplePos + DSP_SUBFRAME_SIZE * step) + 2; + + FillDecodeBuf(channel, aux, needed); + + // source ran dry, channel is finished + if(aux.decodeBufCount < needed) { + channel.mIsFinished = true; + } + + f32 pos = aux.resamplePos; + s16 prev = aux.resamplePrev; + s16 next = aux.decodeBufCount > 0 ? aux.decodeBuf[0] : prev; + int srcIdx = 0; + + // linear resampling and f32 conversion + for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { + buf[i] = (prev + pos * (next - prev)) / 32768.0f; + pos += step; + while (pos >= 1.0f) { + pos -= 1.0f; + prev = next; + srcIdx++; + next = srcIdx < aux.decodeBufCount ? aux.decodeBuf[srcIdx] : prev; } } - return nullptr; + // save resampler state for the next subframe, prevents popping on pitch change + aux.resamplePos = pos; + aux.resamplePrev = prev; + + // IIR FILTER + + // IIR part 1, low-pass: out[n] = (in[n] - in[n-1]) * (coeff/128) + out[n-1] + // removed: seems to be dead code that doesn't work as intended anyway + + // IIR part 2, biquad: out[n] = (b1*in[n-1] + b2*in[n-2] + a1*out[n-1] + a2*out[n-2]) / 32768 + if ((channel.mFilterMode & 0x20) != 0) { + std::span newCoefs{channel.iir_filter_params, 4}; + if (!std::ranges::equal(newCoefs, aux.curBiquadCoefs)) { + aux.remappedBiquadCoefs = RemapBiquad32to48( + newCoefs[0] / 32768.0f, + newCoefs[1] / 32768.0f, + newCoefs[2] / 32768.0f, + newCoefs[3] / 32768.0f + ); + std::ranges::copy(newCoefs, aux.curBiquadCoefs.begin()); + } + + const auto coefs = aux.remappedBiquadCoefs; + for (f32& sample : buf) { + f32 out = std::clamp(( + coefs.b1 * aux.biq_in1 + + coefs.b2 * aux.biq_in2 + + coefs.a1 * aux.biq_out1 + + coefs.a2 * aux.biq_out2 + ), -1.0f, 1.0f); + + // shift history, then store new input and output + aux.biq_in2 = aux.biq_in1; // in[n-2] = in[n-1] + aux.biq_in1 = sample; // in[n-1] = in[n] + aux.biq_out2 = aux.biq_out1; // out[n-2] = out[n-1] + sample = aux.biq_out1 = out; // out[n-1] = out[n] + } + } + + // move any remaining samples in the decode buf to the beginning + int remainingDecodeBuf = aux.decodeBufCount - srcIdx; + if (remainingDecodeBuf > 0) { + memmove(aux.decodeBuf, aux.decodeBuf + srcIdx, remainingDecodeBuf * sizeof(s16)); + } + + aux.decodeBufCount = std::max(0, remainingDecodeBuf); } struct VolumeValue { @@ -577,191 +543,11 @@ struct VolumeValue { f32 Init; }; -/** - * Get the volume that the given DSP channel should render to the given output channel at. - */ -static VolumeValue GetVolumeForOutputChannel( - const JASDsp::TChannel& sourceChannel, - OutputChannel outputChannel) { +using VolumeArray = std::array; - u16 volume; - u16 initVolume; - f32 panValue = 1; - if (sourceChannel.mAutoMixerBeenSet) { - volume = sourceChannel.mAutoMixerVolume; - initVolume = sourceChannel.mAutoMixerInitVolume; - - auto autoMixerPan = static_cast(sourceChannel.mAutoMixerPanDolby >> 8) / 127; - - switch (outputChannel) { - case OutputChannel::LEFT: - panValue = 1 - autoMixerPan; - break; - case OutputChannel::RIGHT: - panValue = autoMixerPan; - break; - default: - CRASH("Unhandled output channel: OutputChannel"); - } - - } else { - auto config = GetOutputConfig(sourceChannel, outputChannel); - if (config == nullptr) { - return {0, 0}; - } - - volume = config->mTargetVolume; - initVolume = config->mCurrentVolume; - } - - // TODO: interpolate to avoid popping. - f32 targetRatio = VolumeFromU16(volume); - targetRatio *= panValue; - - f32 initRatio = VolumeFromU16(initVolume); - initRatio *= panValue; - - return {targetRatio, initRatio}; -} - -/** - * Given decoded & resampled input samples, render a DSP channel to a given output channel. - */ -static void RenderOutputChannel( - const JASDsp::TChannel& sourceChannel, - ChannelAuxData& aux, - OutputChannel outputChannel, - const std::span inputSamples, - OutputSubframe& fullOutputSubframe) { - - auto& outputSubframe = fullOutputSubframe[outputChannel]; - assert(inputSamples.size() <= outputSubframe.size()); - - auto volume = GetVolumeForOutputChannel(sourceChannel, outputChannel); - - f32 targetVolume = volume.Target; - auto& prevVolume = aux.PrevVolume(outputChannel); - if (std::isnan(prevVolume)) { - // Initialize previous volume to new volume on first render. - prevVolume = volume.Init; - } - - if (prevVolume == 0 && targetVolume == 0) { - return; - } - - ApplyVolume(outputSubframe, inputSamples, prevVolume, targetVolume); - prevVolume = targetVolume; -} - -/** - * Fetch, decode, resample, output - */ -static void RenderChannel( - JASDsp::TChannel& channel, - ChannelAuxData& channelAux, - OutputSubframe& subframe) { - - if (channel.mResetFlag) { - ResetChannel(channel, channelAux); - } - - // how many input samples we step per output sample, aka the resampling ratio - f32 step = (f32)PitchToSampleRate(channel.mPitch) / SampleRate; - - // how many input samples to resample to DSP_SUBFRAME_SIZE output samples - int needed = static_cast(channelAux.resamplePos + DSP_SUBFRAME_SIZE * step) + 2; - - FillDecodeBuf(channel, channelAux, needed); - - // source ran dry, channel is finished - if(channelAux.decodeBufCount < needed) { - channel.mIsFinished = true; - } - - DspSubframe audioLoadBuffer = {}; - f32 pos = channelAux.resamplePos; - s16 prev = channelAux.resamplePrev; - s16 next = channelAux.decodeBufCount > 0 ? channelAux.decodeBuf[0] : prev; - int srcIdx = 0; - - // linear resampling and f32 conversion - for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { - audioLoadBuffer[i] = (prev + pos * (next - prev)) / 32768.0f; - pos += step; - while (pos >= 1.0f) { - pos -= 1.0f; - prev = next; - srcIdx++; - next = srcIdx < channelAux.decodeBufCount ? channelAux.decodeBuf[srcIdx] : prev; - } - } - - // save resampler state for the next subframe, prevents popping on pitch change - channelAux.resamplePos = pos; - channelAux.resamplePrev = prev; - - // IIR FILTER - - // IIR part 1, low-pass: out[n] = (in[n] - in[n-1]) * (coeff/128) + out[n-1] - if (s16 coeff = channel.iir_filter_params[4]; coeff != 0) { - for (f32& sample : audioLoadBuffer) { - f32 out = std::clamp( - (sample - channelAux.prev_lp_in) * ((f32)coeff / 128.0f) + channelAux.prev_lp_out, -1.0f, 1.0f - ); - - channelAux.prev_lp_in = sample; // in[n-1] = in[n] - sample = channelAux.prev_lp_out = out; // out[n-1] = out[n] - } - } - - // IIR part 2, biquad: out[n] = (b1*in[n-1] + b2*in[n-2] + a1*out[n-1] + a2*out[n-2]) / 32768 - if ((channel.mFilterMode & 0x20) != 0) { - for (f32& sample : audioLoadBuffer) { - f32 out = std::clamp(( - channel.iir_filter_params[0] * channelAux.biq_in1 + // b1 - channel.iir_filter_params[1] * channelAux.biq_in2 + // b2 - channel.iir_filter_params[2] * channelAux.biq_out1 + // a1 - channel.iir_filter_params[3] * channelAux.biq_out2 // a2 - ) / 32768.0f, -1.0f, 1.0f); - - // shift history, then store new input and output - channelAux.biq_in2 = channelAux.biq_in1; // in[n-2] = in[n-1] - channelAux.biq_in1 = sample; // in[n-1] = in[n] - channelAux.biq_out2 = channelAux.biq_out1; // out[n-2] = out[n-1] - sample = channelAux.biq_out1 = out; // out[n-1] = out[n] - } - } - - // move any remaining samples in the decode buf to the beginning - int remainingDecodeBuf = channelAux.decodeBufCount - srcIdx; - if (remainingDecodeBuf > 0) { - memmove(channelAux.decodeBuf, channelAux.decodeBuf + srcIdx, remainingDecodeBuf * sizeof(s16)); - } - - channelAux.decodeBufCount = std::max(0, remainingDecodeBuf); - - auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, DSP_SUBFRAME_SIZE); - - static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!"); - - RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, hasReadSamples, subframe); - RenderOutputChannel(channel, channelAux, OutputChannel::RIGHT, hasReadSamples, subframe); -} - -void dusk::audio::DspInit() { - SharedReverb.setwet(1.0f); - SharedReverb.setdry(0.0f); - SharedReverb.setroomsize(0.5f); - SharedReverb.setdamp(0.7f); - SharedReverb.setwidth(1.0f); - SharedReverb.setmode(0.0f); - SharedReverb.mute(); -} - -void dusk::audio::ApplyVolume( +static void ApplyVolume( std::span dst, - const std::span src, + std::span src, const f32 startVolume, const f32 endVolume) { assert(dst.size() >= src.size()); @@ -777,3 +563,391 @@ void dusk::audio::ApplyVolume( } } } + +struct SpeakerPlacement { + OutputChannel channel; + f32 angle; +}; + +struct SpeakerPair { + OutputChannel first; + OutputChannel second; + f32 lo; + f32 span; +}; + +template +constexpr auto BuildSpeakerPairs(const std::array& config) { + std::array pairs = {}; + constexpr f32 kDegToRad = std::numbers::pi_v / 180.0f; + + for (std::size_t i = 0; i < N; i++) { + std::size_t j = (i + 1) % N; + float lo = config[i].angle; + float hi = config[j].angle; + float span = hi - lo; + if (span < 0) span += 360.0f; + + pairs[i] = {config[i].channel, config[j].channel, lo * kDegToRad, span * kDegToRad}; + } + + return pairs; +} + +constexpr auto Placement6ch = std::to_array({ + // the "rear" channels are actually surround left/right, + // changed to match SDL order + {OutputChannel::FRONT_RIGHT, -30}, + {OutputChannel::FRONT_CENTER, 0}, + {OutputChannel::FRONT_LEFT, 30}, + {OutputChannel::REAR_LEFT, 120}, + {OutputChannel::REAR_RIGHT, -120}, +}); + +constexpr auto Placement8ch = std::to_array({ + {OutputChannel::SURROUND_RIGHT, -90}, + {OutputChannel::FRONT_RIGHT, -30}, + {OutputChannel::FRONT_CENTER, 0}, + {OutputChannel::FRONT_LEFT, 30}, + {OutputChannel::SURROUND_LEFT, 90}, + {OutputChannel::REAR_LEFT, 150}, + {OutputChannel::REAR_RIGHT, -150}, +}); + +constexpr auto Pairs6ch = BuildSpeakerPairs(Placement6ch); +constexpr auto Pairs8ch = BuildSpeakerPairs(Placement8ch); + +static void CalcStereoChannelVolumes( + const JASDsp::TChannel& voice, + VolumeArray& volumes) +{ + const auto volume = VolumeFromU16(voice.mAutoMixerVolume); + const auto initVolume = VolumeFromU16(voice.mAutoMixerInitVolume); + + const auto right = static_cast(voice.mAutoMixerPanDolby >> 8) / 127.0f; + const auto left = 1.0f - right; + + volumes[0] = {left * volume, left * initVolume}; + volumes[1] = {right * volume, right * initVolume}; +} + +static void CalcSurroundChannelVolumes( + const JASDsp::TChannel& voice, + VolumeArray& volumes) +{ + constexpr f32 kTurn = 2.0f * std::numbers::pi_v; + + const auto omniGain = 1.0f / static_cast(OutChannelCount - 1); + const auto pan = static_cast(voice.mAutoMixerPanDolby >> 8) / 63.5f - 1.0f; + const auto dolby = static_cast(voice.mAutoMixerPanDolby & 0xFF) / 63.5f - 1.0f; + const auto focus = std::min(std::sqrt(pan * pan + dolby * dolby), 1.0f); + + f32 angle = std::atan2(-pan, -dolby); + angle = std::fmod(angle, kTurn); + if (angle < 0) angle += kTurn; + + std::array gains = {}; + gains.fill(omniGain * (1.0f - focus)); + + using Pairs = std::span; + const auto pairs = OutChannelCount == 6 ? Pairs{Pairs6ch} : Pairs{Pairs8ch}; + + for (const auto& pair : pairs) { + const auto offset = std::fmod(angle - pair.lo + kTurn, kTurn); + + if (offset <= pair.span) { + const auto first = static_cast(pair.first); + const auto second = static_cast(pair.second); + const auto t = std::clamp(offset / pair.span, 0.0f, 1.0f); + + const auto firstGain = std::cos(t * std::numbers::pi_v / 2.0f); + const auto secondGain = std::sin(t * std::numbers::pi_v / 2.0f); + + gains[first] += focus * firstGain; + gains[second] += focus * secondGain; + + break; + } + } + + const auto volume = VolumeFromU16(voice.mAutoMixerVolume); + const auto initVolume = VolumeFromU16(voice.mAutoMixerInitVolume); + + for (size_t i = 0; i < OutChannelCount; i++) { + volumes[i].Target = volume * gains[i]; + volumes[i].Init = initVolume * gains[i]; + } +} + +static void ApplyPanning( + const JASDsp::TChannel& voice, + ChannelAuxData& aux, + const DspSubframe& input, + OutputSubframe& output) +{ + VolumeArray volumes = {}; + + if (voice.mAutoMixerBeenSet) { + if (OutChannelCount > 2) { + CalcSurroundChannelVolumes(voice, volumes); + } else { + CalcStereoChannelVolumes(voice, volumes); + } + } else { + for (const auto& outChannel : voice.mOutputChannels) { + std::optional ch; + + switch (outChannel.mBusConnect) { + case 0x0D00: + ch = OutputChannel::FRONT_LEFT; + break; + case 0x0D60: + ch = OutputChannel::FRONT_RIGHT; + break; + default: + break; + } + + if (ch) { + auto& v = volumes[static_cast(*ch)]; + v.Target = VolumeFromU16(outChannel.mTargetVolume); + v.Init = VolumeFromU16(outChannel.mCurrentVolume); + } + } + } + + for (size_t i = 0; i < OutChannelCount; i++) { + const auto ch = static_cast(i); + if (ch == OutputChannel::LFE) { + continue; + } + + const auto& volume = volumes[i]; + + const f32 targetVolume = volume.Target; + auto& prevVolume = aux.PrevVolume(ch); + + if (std::isnan(prevVolume)) { + // Initialize previous volume to new volume on first render. + prevVolume = volume.Init; + } + + if (prevVolume == 0 && targetVolume == 0) { + continue; + } + + ApplyVolume(output[ch], input, prevVolume, targetVolume); + prevVolume = targetVolume; + } +} + +static void DownmixSurroundToStereo( + const OutputSubframe& input, + OutputSubframe& output) +{ + auto& left = output.channels[0]; + auto& right = output.channels[1]; + for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { + const auto fc = input.channels[2][i] * 0.5f; + left[i] = input.channels[0][i] + fc + input.channels[4][i]; + right[i] = input.channels[1][i] + fc + input.channels[5][i]; + if (OutChannelCount > 6) { + left[i] += input.channels[6][i]; + right[i] += input.channels[7][i]; + left[i] /= 3.5f; + right[i] /= 3.5f; + } else { + left[i] /= 2.5f; + right[i] /= 2.5f; + } + } +} + +static void UpmixStereoToSurroundInplace(OutputSubframe& buf) { + // pseudoinverse of downmix matrix + const auto w = OutChannelCount > 6 ? 1.0f / 12.0f : 1.0f / 8.0f; + for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { + const auto le = buf.channels[0][i] * (1.0f + w) + buf.channels[1][i] * -w; + const auto re = buf.channels[1][i] * (1.0f + w) + buf.channels[0][i] * -w; + const auto c = buf.channels[0][i] * 0.5f + buf.channels[1][i] * 0.5f; + /* FL */ buf.channels[0][i] = le; + /* FR */ buf.channels[1][i] = re; + /* FC */ buf.channels[2][i] = c; + /* LFE */ buf.channels[3][i] = 0.0f; + /* BL */ buf.channels[4][i] = le; + /* BR */ buf.channels[5][i] = re; + if (OutChannelCount > 6) { + /* SL */ buf.channels[6][i] = le; + /* SR */ buf.channels[7][i] = re; + } + } +} + +static void AccumulateReverbInput( + DspSubframe& dstL, DspSubframe& dstR, + const DspSubframe& srcL, const DspSubframe& srcR, + f32 gain) +{ + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + dstL[j] += srcL[j] * gain; + dstR[j] += srcR[j] * gain; + } +} + +void dusk::audio::DspInit() { + SharedReverb.setwet(1.0f); + SharedReverb.setdry(0.0f); + SharedReverb.setroomsize(0.5f); + SharedReverb.setdamp(0.7f); + SharedReverb.setwidth(1.0f); + SharedReverb.setmode(0.0f); + SharedReverb.mute(); +} + +void dusk::audio::DspRender(OutputSubframe& subframe) { + ZoneScoped; + if (DumpAudio != sDumpWasActive) { + sDumpWasActive = DumpAudio; + if (DumpAudio) { + OpenChannelDumpFiles(); + } else { + CloseChannelDumpFiles(); + } + } + + GenerateEvolvingHarmonic(); + + std::span voices(JASDsp::CH_BUF, DSP_CHANNELS); + + DspSubframe reverbInputL = {}; + DspSubframe reverbInputR = {}; + bool anyReverbInput = false; + + DspSubframe surroundBus = {}; + bool anySurroundInput = false; + + for (int i = 0; i < voices.size(); i++) { + auto& voice = voices[i]; + auto& aux = ChannelAux[i]; + + if (!voice.mIsActive) { + continue; + } + else if (voice.mPauseFlag) { + // Not really sure what the practical difference between pause and + // deactivation is. Either avoids clearing state or allows the DSP to avoid popping? + continue; + } + else if (voice.mForcedStop) { + voice.mIsFinished = true; + continue; + } + + DspSubframe monoBuf = {}; + if (voice.mWaveAramAddress == 0 && !voice.mAramBaseAddress) { + RenderOscChannel(voice, aux, monoBuf); + } else { + ValidateChannel(voice); + RenderChannel(voice, aux, monoBuf); + } + + OutputSubframe buf = {}; + ApplyPanning(voice, aux, monoBuf, buf); + + if (EnableReverb) { + // scale the input to the reverb rather than using wet/dry on the output. + // this way the reverb's internal buffers accumulate energy proportional to mAutoMixerFxMix, + // so any tail always decays at the correct level regardless of mAutoMixerFxMix changes + // prevents transients when the next sound starts playing with a different reverb level + // 600.0f was pulled out of my ass and just sounds good enough for console + f32 inputGain = (voice.mAutoMixerFxMix >> 8) / 600.0f; + if (inputGain > 0) { + anyReverbInput = true; + if (OutChannelCount > 2) { + OutputSubframe downmix; + DownmixSurroundToStereo(buf, downmix); + AccumulateReverbInput(reverbInputL, reverbInputR, downmix.channels[0], downmix.channels[1], inputGain); + } else { + AccumulateReverbInput(reverbInputL, reverbInputR, buf.channels[0], buf.channels[1], inputGain); + } + } + } + + if (EnableHrtf && voice.mAutoMixerBeenSet) { + f32 dolby = (voice.mAutoMixerPanDolby & 0xFF) / 127.0f; + if (dolby > 0.0f) { + anySurroundInput = true; + f32 extract = dolby * HRTF_EXTRACT_MAX; + f32 frontScale = 1.0f - extract; + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + f32 mono = (buf.channels[0][j] + buf.channels[1][j]) * 0.5f; + surroundBus[j] += mono * extract; + buf.channels[0][j] *= frontScale; + buf.channels[1][j] *= frontScale; + } + } + } + + if (DumpAudio && sChannelDumpFiles[i]) { + f32 interleaved[DSP_SUBFRAME_SIZE * 2]; + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + interleaved[j * 2 + 0] = buf.channels[0][j]; + interleaved[j * 2 + 1] = buf.channels[1][j]; + } + fwrite(interleaved, sizeof(f32), DSP_SUBFRAME_SIZE * 2, sChannelDumpFiles[i]); + } + + MixOutputSubframe(subframe, buf); + } + + if (EnableReverb && (anyReverbInput || ReverbHasTail)) { + // Equivalent to -80 dBFS: rms = 1e-4, rms^2 = 1e-8, sumSq = 2 * N * 1e-8 + constexpr f32 REVERB_ENERGY_EPSILON = 2.0f * DSP_SUBFRAME_SIZE * 1e-8f; + f32 wetEnergy = 0.0f; + if (OutChannelCount > 2) { + OutputSubframe reverbOut; + wetEnergy = SharedReverb.processreplace( + reverbInputL.data(), reverbInputR.data(), + reverbOut.channels[0].data(), reverbOut.channels[1].data(), + DSP_SUBFRAME_SIZE, 1, 1.0f + ); + UpmixStereoToSurroundInplace(reverbOut); + MixOutputSubframe(subframe, reverbOut); + } else { + wetEnergy = SharedReverb.processmix( + reverbInputL.data(), reverbInputR.data(), + subframe.channels[0].data(), subframe.channels[1].data(), + DSP_SUBFRAME_SIZE, 1, 1.0f + ); + } + ReverbHasTail = wetEnergy >= REVERB_ENERGY_EPSILON; + } + + if (EnableHrtf && anySurroundInput) { + // Two-pole LPF: -12 dB/oct above 3 kHz + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + sHrtfLp1 = (1.0f - HRTF_LP_K) * sHrtfLp1 + HRTF_LP_K * surroundBus[j]; + sHrtfLp2 = (1.0f - HRTF_LP_K) * sHrtfLp2 + HRTF_LP_K * sHrtfLp1; + surroundBus[j] = sHrtfLp2; + } + + // Mix into L and R + // L gets the filtered signal directly; R gets it allpass for mild decorrelation + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + f32 s = surroundBus[j]; + + subframe.channels[0][j] += s * HrtfGain; + + f32 r = -HRTF_ALLPASS_G * s + sHrtfApIn1 + HRTF_ALLPASS_G * sHrtfApOut1; + sHrtfApIn1 = s; + sHrtfApOut1 = r; + subframe.channels[1][j] += r * HrtfGain; + } + } + + for (int i = 0; i < OutChannelCount; i++) { + auto& channel = subframe.channels[i]; + ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); + } + PrevMasterVolume = MasterVolume; +} diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index cfcbfe3f46..ef1eb98a6e 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -1,25 +1,32 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "global.h" #include "JSystem/JAudio2/JASDSPInterface.h" +#include + #include #include -#include "SDL3/SDL_audio.h" -#include - -// ReSharper disable once CppUnusedIncludeDirective -#include "global.h" - namespace dusk::audio { - constexpr int SampleRate = 32000; + constexpr int SampleRate = 48000; enum class OutputChannel : u8 { - LEFT, - RIGHT, + // same as SDL channel layout for 7.1 + FRONT_LEFT, + FRONT_RIGHT, + FRONT_CENTER, + LFE, + REAR_LEFT, + REAR_RIGHT, + SURROUND_LEFT, + SURROUND_RIGHT, OutputChannel_MAX }; + struct BiquadCoeffs { float b1, b2, a1, a2; }; + /** * Data stored by DSP implementation for each DSP channel. */ @@ -60,6 +67,9 @@ namespace dusk::audio { f32 prev_lp_out; // out[n-1] f32 prev_lp_in; // in[n-1] + std::array curBiquadCoefs; + BiquadCoeffs remappedBiquadCoefs; + // biquad state f32 biq_in1; // in[n-1] f32 biq_in2; // in[n-2] @@ -123,16 +133,11 @@ namespace dusk::audio { return channel.mBytesPerBlock; } - /** - * Apply a volume level to audio data. - * Interpolates across the two provided volume levels to avoid clicking. - */ - void ApplyVolume(std::span dst, std::span src, f32 startVolume, f32 endVolume); - extern f32 MasterVolume; extern f32 PrevMasterVolume; extern bool EnableReverb; extern bool DumpAudio; extern bool EnableHrtf; extern f32 HrtfGain; + extern u8 OutChannelCount; } diff --git a/src/dusk/audio/JASCriticalSection.cpp b/src/dusk/audio/JASCriticalSection.cpp index 9baf2d9bac..73a2671630 100644 --- a/src/dusk/audio/JASCriticalSection.cpp +++ b/src/dusk/audio/JASCriticalSection.cpp @@ -1,8 +1,8 @@ #include "JSystem/JAudio2/JASCriticalSection.h" -#include +#include -#include "tracy/Tracy.hpp" +#include static TracyLockable(std::recursive_mutex, gAudioThreadMutex); diff --git a/src/dusk/autosave.cpp b/src/dusk/autosave.cpp index bae2812a1f..01f5e169bf 100644 --- a/src/dusk/autosave.cpp +++ b/src/dusk/autosave.cpp @@ -1,8 +1,13 @@ #include "dusk/autosave.h" + #include "dusk/ui/ui.hpp" #include "imgui/ImGuiConsole.hpp" #include "mods/svc/save.hpp" +#include "d/actor/d_a_alink.h" +#include "m_Do/m_Do_MemCard.h" +#include "m_Do/m_Do_MemCardRWmng.h" + bool shouldAutoSave = false; u8 mSaveBuffer[QUEST_LOG_SIZE * 3]; u8 mAutoSaveProc = 0; @@ -107,7 +112,7 @@ void waitingForWrite() { void endAutoSave() { const int slot = dComIfGs_getDataNum(); - dusk::mods::svc::save_slot_written(slot, mSaveBuffer + slot * QUEST_LOG_SIZE); + dusk::mods::svc::save_slot_written(slot); dusk::ui::push_toast({ .type = "autosave", diff --git a/src/dusk/autosave.h b/src/dusk/autosave.h index 2ab0887295..d9e83b442a 100644 --- a/src/dusk/autosave.h +++ b/src/dusk/autosave.h @@ -1,9 +1,5 @@ #pragma once -#include -#include -#include - void noAutoSave(); void triggerAutoSave(); void updateAutoSave(); diff --git a/src/dusk/commands.cpp b/src/dusk/commands.cpp index b953095936..b4267700da 100644 --- a/src/dusk/commands.cpp +++ b/src/dusk/commands.cpp @@ -1,31 +1,33 @@ #include "commands.hpp" -#include "JSystem/JUtility/JUTGamePad.h" -#include "SSystem/SComponent/c_math.h" -#include "SSystem/SComponent/c_sxyz.h" -#include "SSystem/SComponent/c_xyz.h" +#include "game_clock.h" + #include "c/c_damagereaction.h" #include "d/actor/d_a_alink.h" +#include "d/d_camera.h" #include "d/d_com_inf_actor.h" #include "d/d_com_inf_game.h" -#include "d/d_camera.h" #include "d/d_kankyo.h" #include "d/d_stage.h" -#include "dusk/game_clock.h" #include "f_op/f_op_actor_mng.h" #include "f_pc/f_pc_layer.h" #include "f_pc/f_pc_layer_iter.h" #include "f_pc/f_pc_manager.h" #include "f_pc/f_pc_node.h" +#include "JSystem/JUtility/JUTGamePad.h" +#include "SSystem/SComponent/c_math.h" +#include "SSystem/SComponent/c_sxyz.h" +#include "SSystem/SComponent/c_xyz.h" + +#include + #include #include #include #include #include -#include "fmt/format.h" - namespace dusk { namespace { diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index 5a66380ced..9afdf58421 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -1,12 +1,16 @@ #include "dusk/config.hpp" -#include "absl/container/flat_hash_map.h" -#include "fmt/format.h" -#include "nlohmann/json.hpp" +#include "dusk/action_bindings.h" #include "dusk/io.hpp" -#include +#include "dusk/logging.h" +#include "dusk/main.h" #include "dusk/settings.h" +#include +#include +#include +#include + #include #include #include @@ -18,10 +22,6 @@ #include #include -#include "dusk/action_bindings.h" -#include "dusk/logging.h" -#include "dusk/main.h" - namespace dusk::config { namespace { constexpr auto ConfigFileName = "config.json"; @@ -316,6 +316,8 @@ template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; +template class ConfigImpl; +template class ConfigImpl; template <> void ConfigImpl::loadFromJson( @@ -394,6 +396,7 @@ template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; +template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; @@ -477,6 +480,11 @@ static void LoadFromPath(const char* path) { return; } + // Configure mod update checks from the existing Dusklight updates cvar + if (!j.contains("backend.checkForModUpdates") && j.contains("backend.checkForUpdates")) { + j["backend.checkForModUpdates"] = j["backend.checkForUpdates"]; + } + UnregisteredConfigVars.clear(); for (const auto& el : j.items()) { @@ -639,4 +647,4 @@ void shutdown() { s_activeChangeNotifications.clear(); } -} // namespace dusk::config \ No newline at end of file +} // namespace dusk::config diff --git a/src/dusk/data.cpp b/src/dusk/data.cpp index 0477d096a1..0ddd957e6e 100644 --- a/src/dusk/data.cpp +++ b/src/dusk/data.cpp @@ -98,6 +98,12 @@ borealis::data::Manager& manager() { "USA", "EUR", "JAP", + "MemoryCardA.USA.raw.mods", + "MemoryCardA.EUR.raw.mods", + "MemoryCardA.JAP.raw.mods", + "MemoryCardB.USA.raw.mods", + "MemoryCardB.EUR.raw.mods", + "MemoryCardB.JAP.raw.mods", }, .files = { diff --git a/src/dusk/discord_presence.cpp b/src/dusk/discord_presence.cpp index 6eca5d2186..29e4f27617 100644 --- a/src/dusk/discord_presence.cpp +++ b/src/dusk/discord_presence.cpp @@ -1,14 +1,15 @@ #if BOREALIS_HAS_DISCORD +#include "discord_presence.hpp" + +#include "app_info.hpp" +#include "logging.h" +#include "main.h" +#include "map_loader_definitions.h" -#include "dusk/discord_presence.hpp" #include "d/d_com_inf_game.h" -#include "dusk/app_info.hpp" -#include "dusk/logging.h" -#include "dusk/main.h" -#include "dusk/map_loader_definitions.h" -#include "fmt/format.h" #include +#include #include #include diff --git a/src/dusk/dusk.h b/src/dusk/dusk.h index b95b33515e..e360a24d26 100644 --- a/src/dusk/dusk.h +++ b/src/dusk/dusk.h @@ -1,8 +1,7 @@ #pragma once #include - -#include "aurora/gfx.h" +#include extern AuroraInfo auroraInfo; diff --git a/src/dusk/dvd_asset.cpp b/src/dusk/dvd_asset.cpp index f5c8e73d4d..335b1c5572 100644 --- a/src/dusk/dvd_asset.cpp +++ b/src/dusk/dvd_asset.cpp @@ -1,12 +1,13 @@ #include "dusk/dvd_asset.hpp" + #include "dusk/logging.h" #include "helpers/endian.h" -#include "dolphin/dvd.h" + #include "DynamicLink.h" #include "JSystem/JKernel/JKRArchive.h" #include "JSystem/JKernel/JKRDvdRipper.h" -#include +#include namespace dusk { diff --git a/src/dusk/extras.c b/src/dusk/extras.c index a1fb26e998..ec81d45f9d 100644 --- a/src/dusk/extras.c +++ b/src/dusk/extras.c @@ -68,11 +68,3 @@ int strnicmp(const char* str1, const char* str2, int n) { return 0; } #endif - -void DCZeroRange(void* addr, uint32_t nBytes) { -#if defined(_MSC_VER) || TARGET_ANDROID - memset(addr, 0, nBytes); -#else - bzero(addr, nBytes); -#endif -} diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp deleted file mode 100644 index ca30ec0db5..0000000000 --- a/src/dusk/frame_interpolation.cpp +++ /dev/null @@ -1,463 +0,0 @@ -#include "dusk/frame_interpolation.h" - -#include "f_op/f_op_camera_mng.h" -#include "m_Do/m_Do_graphic.h" -#include "mtx.h" - -#include - -namespace { - -struct Recording { - absl::flat_hash_map matrix_values; -}; - -bool s_initialized = false; - -bool g_enabled = false; -bool g_recording = false; -bool g_interpolating = false; -bool g_sync_presentation = false; - -float g_step = 0.0f; -bool g_is_sim_frame = false; -bool g_ui_tick_pending = false; -uint64_t g_sim_tick_seq = 0; - -Recording g_current_recording; -Recording g_previous_recording; - -absl::flat_hash_map g_replacements; - -struct CameraSnapshot { - cXyz eye{}; - cXyz center{}; - cXyz up{}; - s16 bank{}; - f32 fovy{}; - f32 aspect{}; - f32 near_{}; - f32 far_{}; - bool wideZoom{}; - bool valid{}; -}; - -CameraSnapshot s_cam_prev{}; -CameraSnapshot s_cam_curr{}; - -view_class s_presentation_view_backup{}; -int s_presentation_depth = 0; - -struct InterpolationCallBackWork { - dusk::frame_interp::InterpolationCallBack pCallBack; - void* pUserWork; -}; - -std::vector s_interpolationCallBackWork; - -void copy_view_to_snap(CameraSnapshot* dst, const view_class& v) { - dst->eye = v.lookat.eye; - dst->center = v.lookat.center; - dst->up = v.lookat.up; - dst->bank = v.bank; - dst->fovy = v.fovy; - dst->aspect = v.aspect; - dst->near_ = v.near_; - dst->far_ = v.far_; - dst->valid = true; -} - -inline void lerp_matrix(Mtx out, const Mtx lhs, const Mtx rhs, float step) { - for (size_t row = 0; row < 3; ++row) { - for (size_t col = 0; col < 4; ++col) { - const float l = lhs[row][col]; - out[row][col] = l + (rhs[row][col] - l) * step; - } - } -} - -inline void lerp_xyz(cXyz* out, const cXyz& lhs, const cXyz& rhs, float step) { - out->x = lhs.x + (rhs.x - lhs.x) * step; - out->y = lhs.y + (rhs.y - lhs.y) * step; - out->z = lhs.z + (rhs.z - lhs.z) * step; -} - -static s16 lerp_bank(s16 a, s16 b, f32 t) { - const f32 ra = S2RAD(a); - const f32 d = remainderf(S2RAD(b) - ra, 2.0f * static_cast(M_PI)); - return cAngle::Radian_to_SAngle(ra + d * t); -} - -inline bool matrix_differs(const Mtx lhs, const Mtx rhs, float epsilon = 0.0001f) { - for (size_t row = 0; row < 3; ++row) { - for (size_t col = 0; col < 4; ++col) { - if (std::abs(lhs[row][col] - rhs[row][col]) > epsilon) { - return true; - } - } - } - return false; -} - -const Mtx* resolve_replacement(const Mtx* source, Mtx* scratch) { - if (!g_interpolating || source == nullptr || dusk::frame_interp::presentation_sync_active()) { - return source; - } - - auto it = g_replacements.find(reinterpret_cast(source)); - if (it == g_replacements.end()) { - return source; - } - - MTXCopy(it->second, *scratch); - return scratch; -} - -bool has_recording_data(const Recording& recording) { - return !recording.matrix_values.empty(); -} - -void clear_replacements() { - g_replacements.clear(); -} - -} // namespace - -namespace dusk::frame_interp { -void ensure_initialized() { - s_initialized = true; -} - -void begin_sim_tick() { - ensure_initialized(); - if (!g_enabled) { - return; - } - - s_interpolationCallBackWork.clear(); - s_cam_prev = std::move(s_cam_curr); - ++g_sim_tick_seq; -} - -uint64_t sim_tick_seq() { - return g_sim_tick_seq; -} - -void begin_frame(FrameInterpMode mode, bool is_sim_frame, float step) { - g_enabled = mode != FrameInterpMode::Off; - g_is_sim_frame = is_sim_frame; - g_step = std::clamp(step, 0.0f, 1.0f); - if (!g_enabled) { - g_interpolating = false; - clear_replacements(); - } -} - -bool is_enabled() { - return g_enabled; -} - -bool is_sim_frame() { - return g_is_sim_frame; -} - -void begin_record() { - ensure_initialized(); - - if (!g_enabled) { - g_interpolating = false; - g_sync_presentation = false; - g_previous_recording = {}; - g_current_recording = {}; - clear_replacements(); - s_cam_prev.valid = false; - s_cam_curr.valid = false; - return; - } - - g_sync_presentation = false; - g_previous_recording = std::move(g_current_recording); - g_current_recording = {}; - g_recording = true; - g_interpolating = false; - clear_replacements(); - - ::camera_process_class* cam = dComIfGp_getCamera(0); - if (cam == nullptr) { - s_cam_prev.valid = false; - s_cam_curr.valid = false; - return; - } -} - -void end_record() { - g_recording = false; -} - -void interpolate() { - ensure_initialized(); - clear_replacements(); - g_interpolating = g_enabled && !g_recording && !g_sync_presentation && has_recording_data(g_current_recording); - if (!g_interpolating) { - return; - } - for (auto const& old : g_previous_recording.matrix_values) { - if (auto it = g_current_recording.matrix_values.find(old.first); - it != g_current_recording.matrix_values.end()) - { - lerp_matrix(g_replacements[old.first], old.second, it->second, g_step); - } - } -} - -void request_presentation_sync() { - ensure_initialized(); - if (!g_enabled) { - return; - } - g_sync_presentation = true; -} - -bool presentation_sync_active() { - if (!s_initialized || !g_enabled) { - return false; - } - return g_sync_presentation; -} - -float get_interpolation_step() { - ensure_initialized(); - return presentation_sync_active() ? 1.0f : g_step; -} - -void set_ui_tick_pending(bool value) { - if (g_ui_tick_pending == value) { return; } - g_ui_tick_pending = value; -} - -bool get_ui_tick_pending() { - ensure_initialized(); - return g_enabled ? g_ui_tick_pending : true; -} - -void record_final_mtx(Mtx m, const void* key) { - if (!s_initialized || !g_recording || m == nullptr) { - return; - } - - auto& it = g_current_recording.matrix_values[reinterpret_cast(key)]; - MTXCopy(m, it); -} - -void record_final_mtx(Mtx m) { - record_final_mtx(m, m); -} - -bool lookup_replacement(const void* key, Mtx out) { - if (presentation_sync_active() || !g_interpolating || key == nullptr) { - return false; - } - - auto it = g_replacements.find(reinterpret_cast(key)); - if (it == g_replacements.end()) { - return false; - } - - MTXCopy(it->second, out); - return true; -} - -bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { - if (presentation_sync_active() || !g_interpolating || lhs == nullptr || rhs == nullptr) { - return false; - } - - Mtx lhs_scratch; - Mtx rhs_scratch; - const Mtx* resolved_lhs = resolve_replacement(reinterpret_cast(lhs), &lhs_scratch); - const Mtx* resolved_rhs = resolve_replacement(reinterpret_cast(rhs), &rhs_scratch); - if (resolved_lhs == reinterpret_cast(lhs) && resolved_rhs == reinterpret_cast(rhs)) { - return false; - } - - MTXConcat(*resolved_lhs, *resolved_rhs, out); - return true; -} - -void record_camera(::camera_process_class* cam, int camera_id) { - if (!g_enabled || camera_id != 0 || cam == nullptr) { - return; - } - copy_view_to_snap(&s_cam_curr, cam->view); -#if WIDESCREEN_SUPPORT - s_cam_curr.wideZoom = mDoGph_gInf_c::isWideZoom(); -#endif -} - -void interp_view(::view_class* view) { - if (!g_enabled) - return; - - if (!s_cam_prev.valid || !s_cam_curr.valid) - return; - - const f32 step = get_interpolation_step(); - const bool is_cam_curr_authoritative = g_is_sim_frame && step <= 0.0f; - - cXyz eye; - cXyz center; - cXyz up; - if (is_cam_curr_authoritative) { - eye = s_cam_curr.eye; - center = s_cam_curr.center; - up = s_cam_curr.up; - } else { - lerp_xyz(&eye, s_cam_prev.eye, s_cam_curr.eye, step); - lerp_xyz(¢er, s_cam_prev.center, s_cam_curr.center, step); - lerp_xyz(&up, s_cam_prev.up, s_cam_curr.up, step); - } - if (!up.normalizeRS()) { - up = s_cam_curr.up; - up.normalizeRS(); - } - - view->lookat.eye = eye; - view->lookat.center = center; - view->lookat.up = up; - if (is_cam_curr_authoritative) { - view->bank = s_cam_curr.bank; - view->fovy = s_cam_curr.fovy; - view->aspect = s_cam_curr.aspect; - view->near_ = s_cam_curr.near_; - view->far_ = s_cam_curr.far_; - } else { - view->bank = lerp_bank(s_cam_prev.bank, s_cam_curr.bank, step); - view->fovy = s_cam_prev.fovy + (s_cam_curr.fovy - s_cam_prev.fovy) * step; - view->aspect = s_cam_prev.aspect + (s_cam_curr.aspect - s_cam_prev.aspect) * step; - view->near_ = s_cam_prev.near_ + (s_cam_curr.near_ - s_cam_prev.near_) * step; - view->far_ = s_cam_prev.far_ + (s_cam_curr.far_ - s_cam_prev.far_) * step; - } - - // FRAME INTERP TODO: It might be better if I rewired the game to not clear this flag until the - // next sim frame, but I don't care enough to right now -#if WIDESCREEN_SUPPORT - const f32 wide_step = is_cam_curr_authoritative ? 1.0f : step; - if (mDoGph_gInf_c::isWide() && !mDoGph_gInf_c::isWideZoom() && wide_step >= 0.5f ? s_cam_curr.wideZoom : s_cam_prev.wideZoom) { - mDoGph_gInf_c::onWideZoom(); - } -#endif -} - -static void run_interpolation_callbacks() { - for (size_t i = 0; i < s_interpolationCallBackWork.size(); i++) { - auto const& work = s_interpolationCallBackWork[i]; - work.pCallBack(g_is_sim_frame, work.pUserWork); - } -} - -void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork) { - if (!is_enabled() || s_presentation_depth > 0 || !g_is_sim_frame) - return; - - s_interpolationCallBackWork.emplace_back(pCallBack, pUserWork); -} - -void begin_presentation_camera() { - ensure_initialized(); - if (!g_enabled) { - return; - } - if (s_presentation_depth > 0) { - s_presentation_depth++; - return; - } - if (!s_cam_prev.valid || !s_cam_curr.valid) { - return; - } - - view_class* const view = dComIfGd_getView(); - if (view == nullptr) { - return; - } - - std::memcpy(&s_presentation_view_backup, view, sizeof(view_class)); - interp_view(view); - - // FRAME INTERP TODO: Largely copied from d_camera's camera_draw function from this point, got any better ideas? - C_MTXPerspective(view->projMtx, view->fovy, view->aspect, view->near_, view->far_); - mDoMtx_lookAt(view->viewMtx, &view->lookat.eye, &view->lookat.center, &view->lookat.up, view->bank); -#if WIDESCREEN_SUPPORT - mDoGph_gInf_c::setWideZoomProjection(view->projMtx); -#endif - j3dSys.setViewMtx(view->viewMtx); - cMtx_inverse(view->viewMtx, view->invViewMtx); - - bool camera_attention_status = dComIfGp_getCameraAttentionStatus(0) & 0x80; - Z2GetAudience()->setAudioCamera(view->viewMtx, view->lookat.eye, view->lookat.center, view->fovy, view->aspect, camera_attention_status, 0, false); - - dBgS_GndChk gndchk; - gndchk.OnWaterGrp(); - gndchk.SetPos(&view->lookat.eye); - f32 cross = dComIfG_Bgsp().GroundCross(&gndchk); - if (cross != -G_CM3D_F_INF) { - if (dComIfG_Bgsp().ChkGrpInf(gndchk, 0x100)) { - mDoAud_getCameraMapInfo(6); - } else { - mDoAud_getCameraMapInfo(dComIfG_Bgsp().GetMtrlSndId(gndchk)); - } - mDoAud_setCameraGroupInfo(dComIfG_Bgsp().GetGrpSoundId(gndchk)); - Vec spDC; - spDC.x = view->lookat.eye.x; - spDC.y = cross; - spDC.z = view->lookat.eye.z; - Z2AudioMgr::getInterface()->setCameraPolygonPos(&spDC); - } else { - Z2AudioMgr::getInterface()->setCameraPolygonPos(nullptr); - } - - MTXCopy(view->viewMtx, view->viewMtxNoTrans); - view->viewMtxNoTrans[0][3] = 0.0f; - view->viewMtxNoTrans[1][3] = 0.0f; - view->viewMtxNoTrans[2][3] = 0.0f; - cMtx_concatProjView(view->projMtx, view->viewMtx, view->projViewMtx); - - f32 far_; - f32 var_f30; - if (dComIfGp_getCameraAttentionStatus(0) & 8) { - far_ = view->far_; - } else { -#if DEBUG - if (g_envHIO.mOther.mAdjustCullFar != 0) { - var_f30 = g_envHIO.mOther.mCullFarValue; - } else -#endif - { - var_f30 = dStage_stagInfo_GetCullPoint(dComIfGp_getStageStagInfo()); - } - far_ = var_f30; - } - - mDoLib_clipper::setup(view->fovy, view->aspect, view->near_, far_); - - // FRAME INTERP NOTE: Removed the call to offWideZoom that was here, it causes problems with presentation during cutscenes. - - s_presentation_depth = 1; - - run_interpolation_callbacks(); -} - -void end_presentation_camera() { - if (s_presentation_depth == 0) { - return; - } - s_presentation_depth--; - if (s_presentation_depth > 0) { - return; - } - - view_class* const view = dComIfGd_getView(); - if (view != nullptr) { - std::memcpy(view, &s_presentation_view_backup, sizeof(view_class)); - } -} -} // namespace dusk::frame_interp diff --git a/src/dusk/frame_interpolation.h b/src/dusk/frame_interpolation.h deleted file mode 100644 index 5f2f3d134b..0000000000 --- a/src/dusk/frame_interpolation.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "settings.h" - -class camera_process_class; -class view_class; - -#ifdef __cplusplus -namespace dusk { -namespace frame_interp { - -void ensure_initialized(); - -void begin_record(); -void end_record(); -void begin_sim_tick(); -uint64_t sim_tick_seq(); -void begin_frame(FrameInterpMode mode, bool is_sim_frame, float step); -void interpolate(); -float get_interpolation_step(); - -void request_presentation_sync(); -bool presentation_sync_active(); - -bool is_enabled(); - -// TODO: These should be phased out as UI is progressively updated to use game_clock -void set_ui_tick_pending(bool value); -bool get_ui_tick_pending(); - -bool is_sim_frame(); - -void record_camera(::camera_process_class* cam, int camera_id); -void interp_view(::view_class* view); -void record_final_mtx(Mtx m, const void *key); -void record_final_mtx(Mtx m); - -bool lookup_replacement(const void* key, Mtx out); -bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); - -typedef void (*InterpolationCallBack)(bool isSimFrame, void* pUserWork); -// call on a sim tick, will get called during presentation -void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork); - -void begin_presentation_camera(); -void end_presentation_camera(); - -} // namespace frame_interp -} // namespace dusk -#endif diff --git a/src/dusk/game_clock.cpp b/src/dusk/game_clock.cpp index a38a08c7d2..cbaa49ec02 100644 --- a/src/dusk/game_clock.cpp +++ b/src/dusk/game_clock.cpp @@ -1,34 +1,43 @@ #include "dusk/game_clock.h" -#include #include + +#include #include -#include -#include -#include namespace dusk::game_clock { using native_clock = aurora::time::native_clock; using game_clock = aurora::time::game_clock; -FrameTiming g_frameTiming; +FrameTiming g_frameTiming{.dt = kUiInitialDt}; namespace { bool s_initialized = false; -bool s_fixedStepActive = false; bool s_simTickActive = false; native_clock::time_point s_previousNativeSample{}; game_clock::time_point s_latestGameSample{}; game_clock::time_point s_currentSnapshotTime{}; game_clock::time_point s_pendingSimTime{}; - -std::unordered_map s_intervalLastSample; +uint64_t s_presentationEpoch = 1; +bool s_timingModeInitialized = false; +bool s_previousSeparatePresentation = false; +bool s_previousInterpolating = false; +bool s_previousTimeStopped = false; constexpr game_clock::duration kSimPeriodDuration = std::chrono::duration_cast(std::chrono::duration(kSimPeriod)); constexpr native_clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250); constexpr int kMaxSimTicksPerFrame = static_cast(aurora::time::kMaximumTimeScale) * 4; + +float ui_dt() { + if (s_simTickActive) { + return kSimPeriod; + } + + const float maximumDt = kUiMaximumDt * aurora::time::scale(); + return std::clamp(g_frameTiming.dt, 0.0f, maximumDt); +} } // namespace void initialize() { @@ -48,6 +57,7 @@ void reset() { s_currentSnapshotTime = s_latestGameSample - kSimPeriodDuration; s_pendingSimTime = s_currentSnapshotTime; s_simTickActive = false; + ++s_presentationEpoch; } void set_sim_rate(float hz) { @@ -64,11 +74,15 @@ const FrameTiming& advance() { const auto nativeNow = native_clock::now(); const auto gameNow = game_clock::now(); const auto nativeFrameGap = nativeNow - s_previousNativeSample; + const auto gameFrameGap = gameNow - s_latestGameSample; s_previousNativeSample = nativeNow; s_latestGameSample = gameNow; auto& out = g_frameTiming; - out = {.dt = std::chrono::duration().count()}; + out = { + .dt = std::chrono::duration(gameFrameGap).count(), + .presentationEpoch = s_presentationEpoch, + }; const float timeScale = aurora::time::scale(); const bool interpolating = @@ -76,7 +90,21 @@ const FrameTiming& advance() { const bool separatePresentation = interpolating || timeScale != 1.0f; out.interpolating = interpolating; out.separatePresentation = separatePresentation; - s_fixedStepActive = separatePresentation; + + const bool timeStopped = timeScale == 0.0f; + const bool timingModeChanged = + s_timingModeInitialized && + (separatePresentation != s_previousSeparatePresentation || + interpolating != s_previousInterpolating || timeStopped != s_previousTimeStopped); + const bool abnormalGap = nativeFrameGap > kAbnormalGapResetThreshold; + if (timingModeChanged || abnormalGap) { + ++s_presentationEpoch; + out.presentationEpoch = s_presentationEpoch; + } + s_timingModeInitialized = true; + s_previousSeparatePresentation = separatePresentation; + s_previousInterpolating = interpolating; + s_previousTimeStopped = timeStopped; if (!separatePresentation) { s_currentSnapshotTime = gameNow; @@ -85,7 +113,7 @@ const FrameTiming& advance() { } const auto simulationTarget = interpolating ? gameNow - kSimPeriodDuration : gameNow; - if (timeScale == 0.f || nativeFrameGap > kAbnormalGapResetThreshold) { + if (timeStopped || abnormalGap) { s_currentSnapshotTime = simulationTarget; out.numSimTicks = 0; return out; @@ -108,8 +136,8 @@ const FrameTiming& advance() { } void begin_sim_tick() { - s_pendingSimTime = - s_fixedStepActive ? s_currentSnapshotTime + kSimPeriodDuration : s_latestGameSample; + s_pendingSimTime = g_frameTiming.separatePresentation ? s_currentSnapshotTime + kSimPeriodDuration : + s_latestGameSample; s_simTickActive = true; } @@ -122,6 +150,14 @@ void commit_sim_tick() { } } +bool is_sim_frame() { + return !g_frameTiming.separatePresentation || s_simTickActive; +} + +bool is_presentation_frame() { + return !g_frameTiming.separatePresentation || !s_simTickActive; +} + float sample_interpolation_step() { const float step = std::chrono::duration(game_clock::now() - s_currentSnapshotTime).count() / @@ -129,17 +165,19 @@ float sample_interpolation_step() { return std::clamp(step, 0.0f, 1.0f); } -float consume_interval(const void* consumer) { - const auto key = reinterpret_cast(consumer); +double sample_time() { const auto now = s_simTickActive ? s_pendingSimTime : game_clock::now(); - const float timeScale = aurora::time::scale(); - float dt = kUiInitialDt * timeScale; - if (const auto it = s_intervalLastSample.find(key); it != s_intervalLastSample.end()) { - dt = std::chrono::duration(now - it->second).count(); - const float maximumDt = std::max(kUiMaximumDt * timeScale, kSimPeriod); - dt = std::min(dt, maximumDt); - } - s_intervalLastSample[key] = now; + return std::chrono::duration(now.time_since_epoch()).count(); +} + +float original_frames() { + return ui_dt() / kSimPeriod; +} + +float consume_interval(double& lastSample) { + const double now = sample_time(); + const float dt = std::max(0.0, now - lastSample); + lastSample = now; return dt; } diff --git a/src/dusk/game_clock.h b/src/dusk/game_clock.h index 5e71ea4c0f..5731e8cbd3 100644 --- a/src/dusk/game_clock.h +++ b/src/dusk/game_clock.h @@ -7,6 +7,8 @@ constexpr float kSimPeriod = 1.0f / 30.0f; constexpr float kUiMaximumDt = 0.05f; constexpr float kUiInitialDt = 1.0f / 60.0f; +float original_frames(); + struct FrameTiming { // Amount of time elapsed in seconds since the last advance float dt; @@ -16,6 +18,8 @@ struct FrameTiming { bool separatePresentation; // Number of simulation ticks to run int numSimTicks; + // Changes whenever presentation history must be discarded and re-anchored. + uint64_t presentationEpoch; }; extern FrameTiming g_frameTiming; @@ -26,10 +30,14 @@ void begin_sim_tick(); void commit_sim_tick(); float sample_interpolation_step(); -float consume_interval(const void* consumer); +bool is_sim_frame(); +bool is_presentation_frame(); + +double sample_time(); +float consume_interval(double& lastSample); // Sets the effective simulation rate through the game clock time scale. void set_sim_rate(float hz); float get_sim_rate(); -} // namespace dusk::game_clock +} // namespace dusk::game_clock diff --git a/src/dusk/game_mode.cpp b/src/dusk/game_mode.cpp index 8fdb3106f3..ffecd66df3 100644 --- a/src/dusk/game_mode.cpp +++ b/src/dusk/game_mode.cpp @@ -1,10 +1,14 @@ #include "dusk/game_mode.hpp" -#include "JSystem/JUtility/JUTGamePad.h" -#include "aurora/lib/logging.hpp" + #include "dusk/config.hpp" #include "dusk/ui/prelaunch.hpp" + #include "m_Do/m_Do_MemCard.h" +#include "JSystem/JUtility/JUTGamePad.h" + +#include + namespace dusk::gamemode { namespace { aurora::Module Log("dusk::gamemode"); diff --git a/src/dusk/game_mode.hpp b/src/dusk/game_mode.hpp index 3c2b63a484..2376eb062b 100644 --- a/src/dusk/game_mode.hpp +++ b/src/dusk/game_mode.hpp @@ -1,8 +1,9 @@ #pragma once -#include "d/d_file_select.h" #include "mods/svc/game_mode.h" +#include "d/d_file_select.h" + #include #include #include diff --git a/src/dusk/gamepad_color.cpp b/src/dusk/gamepad_color.cpp index 9fea8fd769..36e738c29e 100644 --- a/src/dusk/gamepad_color.cpp +++ b/src/dusk/gamepad_color.cpp @@ -1,14 +1,17 @@ -#include -#include -#include -#include -#include -#include +#include "dusk/gamepad_color.h" #include "ui/controller_config.hpp" +#include "d/d_com_inf_game.h" +#include "d/actor/d_a_player.h" +#include "d/actor/d_a_alink.h" + +#include + #include +#include + namespace dusk::input { namespace { diff --git a/src/dusk/globals.cpp b/src/dusk/globals.cpp index 591ece679f..34649470c9 100644 --- a/src/dusk/globals.cpp +++ b/src/dusk/globals.cpp @@ -34,5 +34,5 @@ DSPTaskInfo* __DSP_first_task; DSPTaskInfo* __DSP_curr_task; // mDo_dvd -#include +#include "m_Do/m_Do_dvd_thread.h" u8 mDoDvdThd::DVDLogoMode; diff --git a/src/dusk/hash.hpp b/src/dusk/hash.hpp new file mode 100644 index 0000000000..da88aa5547 --- /dev/null +++ b/src/dusk/hash.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include +#include +#include + +namespace dusk::hash { + +class Sha256 { +public: + void update(std::span bytes) { + mHasher.process(bytes.begin(), bytes.end()); + } + + std::string finish() { + mHasher.finish(); + return picosha2::get_hash_hex_string(mHasher); + } + +private: + picosha2::hash256_one_by_one mHasher; +}; + +} // namespace dusk::hash diff --git a/src/dusk/hq_minimap.cpp b/src/dusk/hq_minimap.cpp index a4d73cbcf1..790597d579 100644 --- a/src/dusk/hq_minimap.cpp +++ b/src/dusk/hq_minimap.cpp @@ -1,6 +1,7 @@ -#include "aurora/texture.hpp" #include "dusk/texture_replacements.hpp" -#include "fmt/format.h" + +#include +#include #include #include diff --git a/src/dusk/imgui/ImGuiActorSpawner.cpp b/src/dusk/imgui/ImGuiActorSpawner.cpp index f3aefd4c41..ba2a41dcc4 100644 --- a/src/dusk/imgui/ImGuiActorSpawner.cpp +++ b/src/dusk/imgui/ImGuiActorSpawner.cpp @@ -1,12 +1,13 @@ -#include "imgui.h" - #include "ImGuiMenuTools.hpp" + #include "d/actor/d_a_alink.h" -#include "d/d_com_inf_game.h" #include "f_op/f_op_actor_mng.h" + #include "SSystem/SComponent/c_sxyz.h" #include "SSystem/SComponent/c_xyz.h" +#include + namespace dusk { namespace { diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index ce9290efa5..8cdc462c2a 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -1,14 +1,15 @@ +#include #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" -#include -#include "JSystem/JAudio2/JAISeq.h" #include "JSystem/JAudio2/JAISeMgr.h" +#include "JSystem/JAudio2/JAISeq.h" #include "JSystem/JAudio2/JAISeqMgr.h" #include "JSystem/JAudio2/JAIStreamMgr.h" #include "JSystem/JAudio2/JASCriticalSection.h" #include "JSystem/JAudio2/JASDSPChannel.h" #include "JSystem/JAudio2/JASDSPInterface.h" #include "JSystem/JAudio2/JASTrack.h" +#include "JSystem/JAudio2/JAUSectionHeap.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/audio/DuskDsp.hpp" @@ -281,6 +282,37 @@ static void ShowAllJAISeqs() { } } +static void ShowSeqDataBlock(JAUSeqDataBlock const& block) { + char buf[64]; + SAFE_SPRINTF(buf, "%p", &block); + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("Sound: %08X", block.field_0x10.id_.composite_.host()); + ImGui::Text("Region: %p %08X", block.region.addr, block.region.size); + } + + ImGui::EndChild(); +} + +static void ShowSeqDataBlocks() { + auto const& heap = *JAUSectionHeap::getInstance(); + auto const& blocks = heap.sectionHeapData_.seqDataBlocks; + + ImGui::Text("Loaded:"); + for (auto link = blocks.mLoadedBlocks.getFirst(); link != nullptr; link = link->getNext()) { + ShowSeqDataBlock(*link->getObject()); + } + + ImGui::Text("Loading:"); + for (auto link = blocks.field_0xc.getFirst(); link != nullptr; link = link->getNext()) { + ShowSeqDataBlock(*link->getObject()); + } + + ImGui::Text("Free:"); + for (auto link = blocks.mFreeBlocks.getFirst(); link != nullptr; link = link->getNext()) { + ShowSeqDataBlock(*link->getObject()); + } +} + void dusk::ImGuiMenuTools::ShowAudioDebug() { if (!getSettings().backend.enableAdvancedSettings || !ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F10, m_showAudioDebug)) @@ -322,6 +354,11 @@ void dusk::ImGuiMenuTools::ShowAudioDebug() { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("SeqDataBlocks")) { + ShowSeqDataBlocks(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } } diff --git a/src/dusk/imgui/ImGuiBloomWindow.cpp b/src/dusk/imgui/ImGuiBloomWindow.cpp index 1741f82cd8..e22b86ee1e 100644 --- a/src/dusk/imgui/ImGuiBloomWindow.cpp +++ b/src/dusk/imgui/ImGuiBloomWindow.cpp @@ -1,11 +1,13 @@ -#include - -#include "imgui.h" - #include "ImGuiBloomWindow.hpp" + #include "ImGuiMenuTools.hpp" + #include "m_Do/m_Do_graphic.h" +#include + +#include + namespace dusk { namespace { struct BloomOverride { diff --git a/src/dusk/imgui/ImGuiCameraOverlay.cpp b/src/dusk/imgui/ImGuiCameraOverlay.cpp index 3b859769b9..914f13f1af 100644 --- a/src/dusk/imgui/ImGuiCameraOverlay.cpp +++ b/src/dusk/imgui/ImGuiCameraOverlay.cpp @@ -1,13 +1,16 @@ -#include "f_op/f_op_camera_mng.h" -#include "SSystem/SComponent/c_xyz.h" -#include "d/d_com_inf_game.h" - -#include "imgui.h" #include "ImGuiConfig.hpp" #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" + #include "dusk/settings.h" +#include "d/d_com_inf_game.h" +#include "f_op/f_op_camera_mng.h" + +#include "SSystem/SComponent/c_xyz.h" + +#include + namespace dusk { void ImGuiMenuTools::ShowCameraOverlay() { if (!getSettings().backend.enableAdvancedSettings || diff --git a/src/dusk/imgui/ImGuiConfig.hpp b/src/dusk/imgui/ImGuiConfig.hpp index 4bceb36a38..6e6cbdea9d 100644 --- a/src/dusk/imgui/ImGuiConfig.hpp +++ b/src/dusk/imgui/ImGuiConfig.hpp @@ -2,7 +2,8 @@ #define DUSK_IMGUICONFIG_HPP #include "dusk/config.hpp" -#include "imgui.h" + +#include namespace dusk::config { inline bool ImGuiCheckbox(const char* title, ConfigVar& var) { diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index d15e1c1b8e..badf7297c2 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -1,35 +1,32 @@ -#include -#include -#include -#include -#include -#include - #define IMGUI_DEFINE_MATH_OPERATORS -#include "imgui.h" -#include - #include "ImGuiConsole.hpp" + #include "ImGuiEngine.hpp" -#include "JSystem/JUtility/JUTGamePad.h" + #include "dusk/action_bindings.h" -#include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" #include "dusk/data.hpp" -#include "dusk/dusk.h" -#include "dusk/frame_interpolation.h" #include "dusk/game_mode.hpp" #include "dusk/livesplit.h" #include "dusk/main.h" #include "dusk/presentation.hpp" #include "dusk/settings.h" #include "dusk/ui/ui.hpp" + #include "f_pc/f_pc_manager.h" #include "f_pc/f_pc_name.h" -#include "fmt/format.h" -#include "m_Do/m_Do_controller_pad.h" -#include "m_Do/m_Do_main.h" -#include "tracy/Tracy.hpp" + +#include "JSystem/JUtility/JUTGamePad.h" + +#include +#include +#include +#include +#include + +#include +#include +#include #if _WIN32 #define NOMINMAX diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index be344afa3f..87f0b1795a 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -1,15 +1,14 @@ #ifndef DUSK_IMGUI_HPP #define DUSK_IMGUI_HPP +#include "ImGuiMenuTools.hpp" -#include -#include -#include +#include "dusk/main.h" #include +#include -#include "ImGuiMenuTools.hpp" -#include "dusk/main.h" -#include "imgui.h" +#include +#include union SDL_Event; struct ImGuiWindow; diff --git a/src/dusk/imgui/ImGuiControllerOverlay.cpp b/src/dusk/imgui/ImGuiControllerOverlay.cpp index ad07978a8b..7432c1f49f 100644 --- a/src/dusk/imgui/ImGuiControllerOverlay.cpp +++ b/src/dusk/imgui/ImGuiControllerOverlay.cpp @@ -1,11 +1,12 @@ -#include "m_Do/m_Do_controller_pad.h" - -#include "imgui.h" -#include #include "ImGuiConsole.hpp" + #include "dusk/settings.h" +#include "m_Do/m_Do_controller_pad.h" + #include +#include +#include namespace dusk { void ImGuiMenuTools::ShowInputViewer() { diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp index b2ea1f8e1a..8c4fd16a43 100644 --- a/src/dusk/imgui/ImGuiEngine.cpp +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -1,17 +1,17 @@ #include "ImGuiEngine.hpp" +#include "dusk/logging.h" + +#include +#include +#include #include #include #include #include -#include -#include -#include -#include -#include -#include "aurora/lib/window.hpp" -#include "dusk/logging.h" +#include +#include #ifdef IMGUI_ENABLE_FREETYPE #include "misc/freetype/imgui_freetype.h" diff --git a/src/dusk/imgui/ImGuiEngine.hpp b/src/dusk/imgui/ImGuiEngine.hpp index 44077c9625..2c40b02c89 100644 --- a/src/dusk/imgui/ImGuiEngine.hpp +++ b/src/dusk/imgui/ImGuiEngine.hpp @@ -1,8 +1,8 @@ #pragma once +#include + #include -#include "imgui.h" -#include "misc/cpp/imgui_stdlib.h" namespace dusk { class ImGuiEngine { diff --git a/src/dusk/imgui/ImGuiHeapOverlay.cpp b/src/dusk/imgui/ImGuiHeapOverlay.cpp index 33209ab0d4..0d6da0b210 100644 --- a/src/dusk/imgui/ImGuiHeapOverlay.cpp +++ b/src/dusk/imgui/ImGuiHeapOverlay.cpp @@ -1,13 +1,15 @@ -#include -#include - #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" + #include "JSystem/JFramework/JFWSystem.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "JSystem/JKernel/JKRHeap.h" -#include "absl/container/flat_hash_map.h" -#include "imgui.h" + +#include +#include + +#include +#include struct OpenHeapData { bool Safe; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 7657c48815..0ecfdce149 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -1,25 +1,26 @@ -#include "fmt/format.h" -#include "imgui.h" -#include "aurora/gfx.h" - -#include "ImGuiConfig.hpp" -#include "dusk/hotkeys.h" -#include "dusk/settings.h" -#include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" +#include "ImGuiConfig.hpp" +#include "ImGuiConsole.hpp" #include "ImGuiEngine.hpp" + +#include "dusk/data.hpp" +#include "dusk/dusk.h" +#include "dusk/hotkeys.h" +#include "dusk/main.h" +#include "dusk/os.h" +#include "dusk/settings.h" +#include "dusk/speedrun.h" + #include "d/actor/d_a_alink.h" #include "d/actor/d_a_horse.h" #include "d/d_com_inf_game.h" -#include "dusk/data.hpp" -#include "dusk/dusk.h" -#include "dusk/speedrun.h" -#include "dusk/main.h" -#include "dusk/os.h" #include "m_Do/m_Do_main.h" +#include #include +#include +#include #include #if defined(__APPLE__) diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 61fab81f79..68b150b6d4 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -1,14 +1,10 @@ #ifndef DUSK_IMGUI_MENUTOOLS_HPP #define DUSK_IMGUI_MENUTOOLS_HPP - -#include -#include -#include - -#include "imgui.h" #include "ImGuiSaveEditor.hpp" #include "ImGuiStateShare.hpp" +#include + namespace dusk { class ImGuiMenuTools { public: diff --git a/src/dusk/imgui/ImGuiProcessOverlay.cpp b/src/dusk/imgui/ImGuiProcessOverlay.cpp index 5b4fff28db..03ad595ac1 100644 --- a/src/dusk/imgui/ImGuiProcessOverlay.cpp +++ b/src/dusk/imgui/ImGuiProcessOverlay.cpp @@ -1,19 +1,17 @@ #define PROCS_DUMP_NAMES 1 -#include +#include "ImGuiConsole.hpp" +#include "ImGuiMenuTools.hpp" -#include "f_pc/f_pc_name.h" #include "f_pc/f_pc_create_iter.h" #include "f_pc/f_pc_create_req.h" #include "f_pc/f_pc_layer.h" #include "f_pc/f_pc_layer_iter.h" #include "f_pc/f_pc_leaf.h" +#include "f_pc/f_pc_name.h" #include "f_pc/f_pc_node.h" -#include "d/d_debug_viewer.h" -#include "imgui.h" -#include "ImGuiConsole.hpp" -#include "ImGuiMenuTools.hpp" -#include "imgui_internal.h" + +#include namespace dusk { static bool BeginProcTable() { diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index 244b26e200..866b0fe369 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -1,16 +1,16 @@ -#include "fmt/format.h" -#include "imgui.h" -#include "aurora/gfx.h" +#include "ImGuiSaveEditor.hpp" #include "ImGuiConsole.hpp" -#include "ImGuiSaveEditor.hpp" #include "ImGuiEventFlags.hpp" +#include "d/actor/d_a_player.h" #include "d/d_com_inf_game.h" #include "d/d_item_data.h" #include "d/d_meter2_info.h" #include "d/d_save.h" -#include "d/actor/d_a_player.h" + +#include +#include #include diff --git a/src/dusk/imgui/ImGuiSaveEditor.hpp b/src/dusk/imgui/ImGuiSaveEditor.hpp index a18262862f..50ac7530a5 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.hpp +++ b/src/dusk/imgui/ImGuiSaveEditor.hpp @@ -1,11 +1,6 @@ #ifndef DUSK_IMGUI_SAVEEDITOR_HPP #define DUSK_IMGUI_SAVEEDITOR_HPP -#include -#include - -#include "imgui.h" - namespace dusk { class ImGuiSaveEditor { public: diff --git a/src/dusk/imgui/ImGuiStateShare.cpp b/src/dusk/imgui/ImGuiStateShare.cpp index be45d5394b..9f7ee09282 100644 --- a/src/dusk/imgui/ImGuiStateShare.cpp +++ b/src/dusk/imgui/ImGuiStateShare.cpp @@ -1,24 +1,26 @@ #include "ImGuiStateShare.hpp" -#include "ImGuiMenuTools.hpp" + #include "ImGuiConsole.hpp" +#include "ImGuiMenuTools.hpp" -#include "imgui.h" -#include "fmt/format.h" -#include "absl/strings/escaping.h" -#include "nlohmann/json.hpp" - -#include "d/d_com_inf_game.h" -#include "dusk/main.h" +#include "dusk/autosave.h" #include "dusk/io.hpp" #include "dusk/logging.h" +#include "dusk/main.h" #include "dusk/settings.h" + +#include "d/d_com_inf_game.h" #include "f_op/f_op_overlap_mng.h" + +#include +#include #include -#include "aurora/lib/window.hpp" +#include +#include +#include +#include #include -#include -#include namespace dusk { diff --git a/src/dusk/imgui/ImGuiStubLog.cpp b/src/dusk/imgui/ImGuiStubLog.cpp index 9a6f084eef..cc28bbc53d 100644 --- a/src/dusk/imgui/ImGuiStubLog.cpp +++ b/src/dusk/imgui/ImGuiStubLog.cpp @@ -1,9 +1,11 @@ -#include -#include +#include "ImGuiMenuTools.hpp" #include "dusk/logging.h" -#include "imgui.h" -#include "ImGuiMenuTools.hpp" + +#include + +#include +#include namespace dusk { static ImGuiTextBuffer StubLogBuffer; diff --git a/src/dusk/interp/camera.cpp b/src/dusk/interp/camera.cpp new file mode 100644 index 0000000000..19ab197214 --- /dev/null +++ b/src/dusk/interp/camera.cpp @@ -0,0 +1,218 @@ +#include "dusk/interp/camera.h" + +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/lerp.h" + +#include "d/d_com_inf_game.h" +#include "f_op/f_op_camera_mng.h" +#include "m_Do/m_Do_graphic.h" + +#include +#include + +namespace { + +struct CameraSnapshot { + cXyz eye{}; + cXyz center{}; + cXyz up{}; + s16 bank{}; + f32 fovy{}; + f32 aspect{}; + f32 near_{}; + f32 far_{}; + bool wideZoom{}; + bool valid{}; +}; + +CameraSnapshot s_camPrev{}; +CameraSnapshot s_camCurr{}; + +view_class s_presentationViewBackup{}; + +void copy_view_to_snap(CameraSnapshot* dst, const view_class& v) { + dst->eye = v.lookat.eye; + dst->center = v.lookat.center; + dst->up = v.lookat.up; + dst->bank = v.bank; + dst->fovy = v.fovy; + dst->aspect = v.aspect; + dst->near_ = v.near_; + dst->far_ = v.far_; + dst->valid = true; +} + +void apply_presented_view(view_class* view) { + // FRAME INTERP TODO: Largely copied from d_camera's camera_draw function from this point, got any + // better ideas? + C_MTXPerspective(view->projMtx, view->fovy, view->aspect, view->near_, view->far_); + mDoMtx_lookAt(view->viewMtx, &view->lookat.eye, &view->lookat.center, &view->lookat.up, + view->bank); +#if WIDESCREEN_SUPPORT + mDoGph_gInf_c::setWideZoomProjection(view->projMtx); +#endif + j3dSys.setViewMtx(view->viewMtx); + cMtx_inverse(view->viewMtx, view->invViewMtx); + + bool camera_attention_status = dComIfGp_getCameraAttentionStatus(0) & 0x80; + Z2GetAudience()->setAudioCamera(view->viewMtx, view->lookat.eye, view->lookat.center, view->fovy, + view->aspect, camera_attention_status, 0, false); + + dBgS_GndChk gndchk; + gndchk.OnWaterGrp(); + gndchk.SetPos(&view->lookat.eye); + f32 cross = dComIfG_Bgsp().GroundCross(&gndchk); + if (cross != -G_CM3D_F_INF) { + if (dComIfG_Bgsp().ChkGrpInf(gndchk, 0x100)) { + mDoAud_getCameraMapInfo(6); + } else { + mDoAud_getCameraMapInfo(dComIfG_Bgsp().GetMtrlSndId(gndchk)); + } + mDoAud_setCameraGroupInfo(dComIfG_Bgsp().GetGrpSoundId(gndchk)); + Vec spDC; + spDC.x = view->lookat.eye.x; + spDC.y = cross; + spDC.z = view->lookat.eye.z; + Z2AudioMgr::getInterface()->setCameraPolygonPos(&spDC); + } else { + Z2AudioMgr::getInterface()->setCameraPolygonPos(nullptr); + } + + MTXCopy(view->viewMtx, view->viewMtxNoTrans); + view->viewMtxNoTrans[0][3] = 0.0f; + view->viewMtxNoTrans[1][3] = 0.0f; + view->viewMtxNoTrans[2][3] = 0.0f; + cMtx_concatProjView(view->projMtx, view->viewMtx, view->projViewMtx); + + f32 far_; + f32 var_f30; + if (dComIfGp_getCameraAttentionStatus(0) & 8) { + far_ = view->far_; + } else { +#if DEBUG + if (g_envHIO.mOther.mAdjustCullFar != 0) { + var_f30 = g_envHIO.mOther.mCullFarValue; + } else +#endif + { + var_f30 = dStage_stagInfo_GetCullPoint(dComIfGp_getStageStagInfo()); + } + far_ = var_f30; + } + + mDoLib_clipper::setup(view->fovy, view->aspect, view->near_, far_); + + // FRAME INTERP NOTE: Removed the call to offWideZoom that was here, it causes problems with + // presentation during cutscenes. +} + +} // namespace + +namespace dusk::interp { + +void record_camera(::camera_process_class* cam, int camera_id) { + if (!is_enabled() || camera_id != 0 || cam == nullptr) { + return; + } + copy_view_to_snap(&s_camCurr, cam->view); +#if WIDESCREEN_SUPPORT + s_camCurr.wideZoom = mDoGph_gInf_c::isWideZoom(); +#endif +} + +void interp_view(::view_class* view) { + if (!is_enabled()) + return; + + if (!s_camPrev.valid || !s_camCurr.valid) + return; + + const f32 step = get_interpolation_step(); + const bool is_cam_curr_authoritative = game_clock::is_sim_frame() && step <= 0.0f; + + cXyz eye; + cXyz center; + cXyz up; + if (is_cam_curr_authoritative) { + eye = s_camCurr.eye; + center = s_camCurr.center; + up = s_camCurr.up; + } else { + lerp(eye, s_camPrev.eye, s_camCurr.eye, step); + lerp(center, s_camPrev.center, s_camCurr.center, step); + lerp(up, s_camPrev.up, s_camCurr.up, step); + } + if (!up.normalizeRS()) { + up = s_camCurr.up; + up.normalizeRS(); + } + + view->lookat.eye = eye; + view->lookat.center = center; + view->lookat.up = up; + if (is_cam_curr_authoritative) { + view->bank = s_camCurr.bank; + view->fovy = s_camCurr.fovy; + view->aspect = s_camCurr.aspect; + view->near_ = s_camCurr.near_; + view->far_ = s_camCurr.far_; + } else { + view->bank = lerp(s_camPrev.bank, s_camCurr.bank, step); + view->fovy = s_camPrev.fovy + (s_camCurr.fovy - s_camPrev.fovy) * step; + view->aspect = s_camPrev.aspect + (s_camCurr.aspect - s_camPrev.aspect) * step; + view->near_ = s_camPrev.near_ + (s_camCurr.near_ - s_camPrev.near_) * step; + view->far_ = s_camPrev.far_ + (s_camCurr.far_ - s_camPrev.far_) * step; + } + + // FRAME INTERP TODO: It might be better if I rewired the game to not clear this flag until the + // next sim frame, but I don't care enough to right now +#if WIDESCREEN_SUPPORT + const f32 wide_step = is_cam_curr_authoritative ? 1.0f : step; + if (mDoGph_gInf_c::isWide() && !mDoGph_gInf_c::isWideZoom() && + wide_step >= 0.5f ? s_camCurr.wideZoom : s_camPrev.wideZoom) + { + mDoGph_gInf_c::onWideZoom(); + } +#endif +} + +void camera_on_sim_tick() { + s_camPrev = std::move(s_camCurr); +} + +void camera_invalidate_snapshots() { + s_camPrev.valid = false; + s_camCurr.valid = false; +} + +void camera_on_begin_record() { + if (dComIfGp_getCamera(0) == nullptr) { + camera_invalidate_snapshots(); + } +} + +bool camera_apply_presentation() { + if (!s_camPrev.valid || !s_camCurr.valid) { + return false; + } + + view_class* const view = dComIfGd_getView(); + if (view == nullptr) { + return false; + } + + std::memcpy(&s_presentationViewBackup, view, sizeof(view_class)); + interp_view(view); + apply_presented_view(view); + return true; +} + +void camera_restore_presentation() { + view_class* const view = dComIfGd_getView(); + if (view != nullptr) { + std::memcpy(view, &s_presentationViewBackup, sizeof(view_class)); + } +} + +} // namespace dusk::interp diff --git a/src/dusk/interp/camera.h b/src/dusk/interp/camera.h new file mode 100644 index 0000000000..e4a1824979 --- /dev/null +++ b/src/dusk/interp/camera.h @@ -0,0 +1,13 @@ +#pragma once + +class camera_process_class; +class view_class; + +#ifdef __cplusplus +namespace dusk::interp { + +void record_camera(::camera_process_class* cam, int camera_id); +void interp_view(::view_class* view); + +} // namespace dusk::interp +#endif diff --git a/src/dusk/interp/frame_interpolation.cpp b/src/dusk/interp/frame_interpolation.cpp new file mode 100644 index 0000000000..598ddfb272 --- /dev/null +++ b/src/dusk/interp/frame_interpolation.cpp @@ -0,0 +1,312 @@ +#include "dusk/interp/frame_interpolation.h" + +#include "dusk/game_clock.h" +#include "dusk/interp/lerp.h" +#include "dusk/interp/material.h" +#include "dusk/interp/particle.h" +#include "dusk/interp/samples.h" +#include "dusk/interp/vertex.h" + +#include "mtx.h" + +#include +#include +#include + +namespace dusk::interp { +void camera_on_sim_tick(); +void camera_on_begin_record(); +bool camera_apply_presentation(); +void camera_restore_presentation(); +void camera_invalidate_snapshots(); +void clear_weather_samples(); +} // namespace dusk::interp + +namespace { + +struct Recording { + absl::flat_hash_map matrix_values; +}; + +bool s_recording = false; +bool s_replacementsActive = false; +bool s_syncPresentation = false; + +float s_step = 0.0f; +uint64_t s_simTickSeq = 0; +uint64_t s_observedPresentationEpoch = 0; + +Recording s_currentRecording; +Recording s_previousRecording; + +absl::flat_hash_map g_replacements; + +int s_presentationDepth = 0; +bool s_cameraPresentationActive = false; + +const Mtx* resolve_replacement(const Mtx* source, Mtx* scratch) { + if (!s_replacementsActive || source == nullptr || dusk::interp::presentation_sync_active()) { + return source; + } + + auto it = g_replacements.find(reinterpret_cast(source)); + if (it == g_replacements.end()) { + return source; + } + + MTXCopy(it->second, *scratch); + return scratch; +} + +bool has_recording_data(const Recording& recording) { + return !recording.matrix_values.empty(); +} + +void clear_replacements() { + g_replacements.clear(); +} + +void interpolate_replacements() { + clear_replacements(); + s_replacementsActive = dusk::interp::is_enabled() && !s_recording && !s_syncPresentation && + has_recording_data(s_currentRecording); + if (!s_replacementsActive) { + return; + } + for (auto const& old : s_previousRecording.matrix_values) { + if (auto it = s_currentRecording.matrix_values.find(old.first); + it != s_currentRecording.matrix_values.end()) + { + dusk::interp::lerp(g_replacements[old.first], old.second, it->second, s_step); + } + } +} + +struct InterpolationCallBackWork { + dusk::interp::InterpolationCallBack pCallBack; + void* pUserWork; + std::shared_ptr owner; +}; + +std::vector s_interpolationCallBackWork; + +void clear_callbacks() { + s_interpolationCallBackWork.clear(); +} + +void callbacks_run() { + for (const auto& work : s_interpolationCallBackWork) { + if (work.pCallBack != nullptr) { + work.pCallBack(work.pUserWork); + } + } +} + +void clear_interpolation_history() { + s_recording = false; + s_replacementsActive = false; + s_syncPresentation = false; + s_previousRecording = {}; + s_currentRecording = {}; + clear_replacements(); + clear_callbacks(); + dusk::interp::camera_invalidate_snapshots(); + dusk::interp::clear_owned_samples(); + dusk::interp::clear_weather_samples(); + dusk::interp::material::clear(); + dusk::interp::particle::clear(); + dusk::interp::vertex::clear(); + s_presentationDepth = 0; + s_cameraPresentationActive = false; +} + +} // namespace + +namespace dusk::interp { + +void begin_sim_tick() { + if (!is_enabled()) { + return; + } + + clear_callbacks(); + camera_on_sim_tick(); + ++s_simTickSeq; + material::prune(); + particle::prune(); + vertex::prune(); +} + +uint64_t sim_tick_seq() { + return s_simTickSeq; +} + +void begin_frame(float step) { + const game_clock::FrameTiming& timing = game_clock::g_frameTiming; + if (s_observedPresentationEpoch != timing.presentationEpoch) { + s_observedPresentationEpoch = timing.presentationEpoch; + clear_interpolation_history(); + } + + s_step = std::clamp(step, 0.0f, 1.0f); + if (!is_enabled()) { + clear_interpolation_history(); + } +} + +bool is_enabled() { + return game_clock::g_frameTiming.interpolating; +} + +bool should_capture() { + return is_enabled() && game_clock::is_sim_frame(); +} + +void begin_record() { + if (!is_enabled()) { + clear_interpolation_history(); + return; + } + + s_syncPresentation = false; + s_previousRecording = std::move(s_currentRecording); + s_currentRecording = {}; + s_recording = true; + s_replacementsActive = false; + clear_replacements(); + camera_on_begin_record(); +} + +void end_record() { + s_recording = false; +} + +void request_presentation_sync() { + if (!is_enabled()) { + return; + } + s_syncPresentation = true; +} + +bool presentation_sync_active() { + if (!is_enabled()) { + return false; + } + return s_syncPresentation; +} + +float get_interpolation_step() { + return presentation_sync_active() ? 1.0f : s_step; +} + +void record_final_mtx(Mtx m, const void* key) { + if (!s_recording || m == nullptr) { + return; + } + + auto& it = s_currentRecording.matrix_values[reinterpret_cast(key)]; + MTXCopy(m, it); +} + +void record_final_mtx(Mtx m) { + record_final_mtx(m, m); +} + +void forget_mtx(const void* key) { + const auto address = reinterpret_cast(key); + s_previousRecording.matrix_values.erase(address); + s_currentRecording.matrix_values.erase(address); + g_replacements.erase(address); +} + +bool lookup_replacement(const void* key, Mtx out) { + if (presentation_sync_active() || !s_replacementsActive || key == nullptr) { + return false; + } + + auto it = g_replacements.find(reinterpret_cast(key)); + if (it == g_replacements.end()) { + return false; + } + + MTXCopy(it->second, out); + return true; +} + +bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { + if (presentation_sync_active() || !s_replacementsActive || lhs == nullptr || rhs == nullptr) { + return false; + } + + Mtx lhs_scratch; + Mtx rhs_scratch; + const Mtx* resolved_lhs = resolve_replacement(reinterpret_cast(lhs), &lhs_scratch); + const Mtx* resolved_rhs = resolve_replacement(reinterpret_cast(rhs), &rhs_scratch); + if (resolved_lhs == reinterpret_cast(lhs) && + resolved_rhs == reinterpret_cast(rhs)) + { + return false; + } + + MTXConcat(*resolved_lhs, *resolved_rhs, out); + return true; +} + +void begin_presentation(float step) { + begin_frame(step); + if (!is_enabled()) { + return; + } + + interpolate_replacements(); + + if (s_presentationDepth > 0) { + s_presentationDepth++; + return; + } + s_presentationDepth = 1; + s_cameraPresentationActive = camera_apply_presentation(); + if (s_cameraPresentationActive) { + callbacks_run(); + } +} + +void end_presentation() { + if (s_presentationDepth == 0) { + return; + } + s_presentationDepth--; + if (s_presentationDepth > 0) { + return; + } + + if (s_cameraPresentationActive) { + camera_restore_presentation(); + s_cameraPresentationActive = false; + } +} + +bool is_presentation_active() { + return s_presentationDepth > 0; +} + +void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork) { + if (!is_enabled() || is_presentation_active() || !game_clock::is_sim_frame()) { + return; + } + if (pCallBack == nullptr) { + return; + } + + s_interpolationCallBackWork.push_back({pCallBack, pUserWork}); +} + +void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork, + std::shared_ptr owner) { + if (!should_capture() || is_presentation_active() || pCallBack == nullptr) { + return; + } + s_interpolationCallBackWork.push_back({pCallBack, pUserWork, std::move(owner)}); +} + +} // namespace dusk::interp diff --git a/src/dusk/interp/frame_interpolation.h b/src/dusk/interp/frame_interpolation.h new file mode 100644 index 0000000000..028a111c3e --- /dev/null +++ b/src/dusk/interp/frame_interpolation.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +namespace dusk::interp { + +void begin_record(); +void end_record(); +void begin_sim_tick(); +uint64_t sim_tick_seq(); +void begin_frame(float step); +float get_interpolation_step(); + +void request_presentation_sync(); +bool presentation_sync_active(); + +bool is_enabled(); + +bool should_capture(); + +void record_final_mtx(Mtx m, const void* key); +void record_final_mtx(Mtx m); + +void forget_mtx(const void* key); + +bool lookup_replacement(const void* key, Mtx out); +bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); + +void begin_presentation(float step); +void end_presentation(); +bool is_presentation_active(); + +typedef void (*InterpolationCallBack)(void* pUserWork); +void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork); +void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork, std::shared_ptr owner); + +} // namespace dusk::interp +#endif diff --git a/src/dusk/interp/lerp.h b/src/dusk/interp/lerp.h new file mode 100644 index 0000000000..3083b6b463 --- /dev/null +++ b/src/dusk/interp/lerp.h @@ -0,0 +1,70 @@ +#pragma once + +#include "JSystem/JUtility/TColor.h" +#include "SSystem/SComponent/c_angle.h" +#include "SSystem/SComponent/c_sxyz.h" +#include "SSystem/SComponent/c_xyz.h" + +#include +#include + +#ifdef __cplusplus +namespace dusk::interp { + +inline s16 lerp(s16 lhs, s16 rhs, float step) { + const f32 ra = S2RAD(lhs); + const f32 d = remainderf(S2RAD(rhs) - ra, 2.0f * M_PI); + return cAngle::Radian_to_SAngle(ra + d * step); +} + +inline u16 lerp(u16 lhs, u16 rhs, float step) { + return static_cast(lerp(static_cast(lhs), static_cast(rhs), step)); +} + +inline f32 lerp(f32 lhs, f32 rhs, float step) { + return lhs + (rhs - lhs) * step; +} + +inline void lerp(f32& out, const f32& lhs, const f32& rhs, float step) { + out = lerp(lhs, rhs, step); +} + +inline u8 lerp(u8 lhs, u8 rhs, float step) { + return static_cast(std::lround(lerp(static_cast(lhs), static_cast(rhs), step))); +} + +inline void lerp(cXyz& out, const cXyz& lhs, const cXyz& rhs, float step) { + out.x = lerp(lhs.x, rhs.x, step); + out.y = lerp(lhs.y, rhs.y, step); + out.z = lerp(lhs.z, rhs.z, step); +} + +inline void lerp(csXyz& out, const csXyz& lhs, const csXyz& rhs, float step) { + out.x = lerp(lhs.x, rhs.x, step); + out.y = lerp(lhs.y, rhs.y, step); + out.z = lerp(lhs.z, rhs.z, step); +} + +inline void lerp(Mtx& out, const Mtx& lhs, const Mtx& rhs, float step) { + for (size_t row = 0; row < 3; ++row) { + for (size_t col = 0; col < 4; ++col) { + const float l = lhs[row][col]; + out[row][col] = l + (rhs[row][col] - l) * step; + } + } +} + +inline Vec lerp(const Vec& lhs, const Vec& rhs, float step) { + return {lerp(lhs.x, rhs.x, step), lerp(lhs.y, rhs.y, step), lerp(lhs.z, rhs.z, step)}; +} + +inline JUtility::TColor lerp(const JUtility::TColor& lhs, const JUtility::TColor& rhs, float step) { + return JUtility::TColor( + lerp(lhs.r, rhs.r, step), + lerp(lhs.g, rhs.g, step), + lerp(lhs.b, rhs.b, step), + lerp(lhs.a, rhs.a, step)); +} + +} // namespace dusk::interp +#endif diff --git a/src/dusk/interp/line.cpp b/src/dusk/interp/line.cpp new file mode 100644 index 0000000000..04e54bd603 --- /dev/null +++ b/src/dusk/interp/line.cpp @@ -0,0 +1,159 @@ +#include "dusk/interp/line.h" + +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/lerp.h" +#include "dusk/interp/samples.h" + +#include "m_Do/m_Do_ext.h" + +#include +#include +#include +#include + +namespace { + +struct Record { + Record() + : store(nullptr), previous(nullptr), current(nullptr), sample_capacity(0), tick(0), + strand_count(0), point_count(0), previous_valid(false), current_valid(false) {} + + ~Record() { delete[] store; } + + Record(const Record&) = delete; + Record& operator=(const Record&) = delete; + + void invalidate() { + previous_valid = false; + current_valid = false; + } + + bool ensure(size_t required) { + if (required <= sample_capacity) { + return store != nullptr; + } + if (required > std::numeric_limits::max() / (2 * sizeof(cXyz))) { + return false; + } + + cXyz* next = new (std::nothrow) cXyz[required * 2]; + if (next == nullptr) { + return false; + } + + delete[] store; + store = next; + previous = store; + current = store + required; + sample_capacity = required; + invalidate(); + return true; + } + + cXyz* store; + cXyz* previous; + cXyz* current; + size_t sample_capacity; + uint64_t tick; + u16 strand_count; + u16 point_count; + bool previous_valid; + bool current_valid; +}; + +bool valid(const dusk::interp::line::Points& points) { + if (points.strands == nullptr || points.strand_count == 0 || points.point_count == 0) { + return false; + } + for (u16 strand = 0; strand < points.strand_count; ++strand) { + if (points.strands[strand].field_0x0 == nullptr) { + return false; + } + } + return true; +} + +bool same_layout(const Record& record, const dusk::interp::line::Points& points) { + return record.current_valid && record.strand_count == points.strand_count && + record.point_count == points.point_count; +} + +void copy_points(cXyz* destination, const dusk::interp::line::Points& points) { + const size_t points_size = static_cast(points.point_count) * sizeof(cXyz); + for (u16 strand = 0; strand < points.strand_count; ++strand) { + std::memcpy(destination + static_cast(strand) * points.point_count, + points.strands[strand].field_0x0, points_size); + } +} + +} // namespace + +namespace dusk::interp::line { + +void reset(const void* owner) { + erase_owned_samples(owner); +} + +void capture(const void* owner, Points points) { + if (owner == nullptr || !should_capture()) { + return; + } + + Record& record = get(owner); + if (!valid(points)) { + record.invalidate(); + return; + } + + const size_t required = static_cast(points.strand_count) * points.point_count; + if (!record.ensure(required)) { + record.invalidate(); + return; + } + + const uint64_t tick = sim_tick_seq(); + const bool layout_matches = same_layout(record, points); + + if (layout_matches && tick == record.tick) { + copy_points(record.current, points); + return; + } + + if (layout_matches && tick == record.tick + 1) { + std::swap(record.previous, record.current); + record.previous_valid = true; + } else { + record.previous_valid = false; + } + + copy_points(record.current, points); + record.tick = tick; + record.strand_count = points.strand_count; + record.point_count = points.point_count; + record.current_valid = true; +} + +void write(const void* owner, Points points) { + if (owner == nullptr || !is_enabled() || !valid(points)) { + return; + } + + Record& record = get(owner); + if (!record.previous_valid || !same_layout(record, points) || + record.tick != sim_tick_seq()) + { + return; + } + + const f32 step = get_interpolation_step(); + for (u16 strand = 0; strand < points.strand_count; ++strand) { + cXyz* destination = points.strands[strand].field_0x0; + const size_t offset = static_cast(strand) * points.point_count; + for (u16 point = 0; point < points.point_count; ++point) { + lerp(destination[point], record.previous[offset + point], + record.current[offset + point], step); + } + } +} + +} // namespace dusk::interp::line diff --git a/src/dusk/interp/line.h b/src/dusk/interp/line.h new file mode 100644 index 0000000000..7b4f3b56bc --- /dev/null +++ b/src/dusk/interp/line.h @@ -0,0 +1,19 @@ +#pragma once + +#include "dolphin/types.h" + +class mDoExt_3Dline_c; + +namespace dusk::interp::line { + +struct Points { + mDoExt_3Dline_c* strands; + u16 strand_count; + u16 point_count; +}; + +void reset(const void* owner); +void capture(const void* owner, Points points); +void write(const void* owner, Points points); + +} // namespace dusk::interp::line diff --git a/src/dusk/interp/material.cpp b/src/dusk/interp/material.cpp new file mode 100644 index 0000000000..f8a16162a3 --- /dev/null +++ b/src/dusk/interp/material.cpp @@ -0,0 +1,577 @@ +#include "material.h" + +#include "frame_interpolation.h" + +#include "dusk/game_clock.h" + +#include "d/d_com_inf_game.h" +#include "m_Do/m_Do_graphic.h" + +#include "JSystem/J3DGraphAnimator/J3DAnimation.h" +#include "JSystem/J3DGraphAnimator/J3DModel.h" +#include "JSystem/J3DGraphAnimator/J3DModelData.h" +#include "JSystem/J3DGraphBase/J3DMaterial.h" + +#include +#include +#include +#include +#include +#include + +namespace dusk::interp::material { + +namespace { +struct ViewProjection { + f32 scaleS, scaleT, transS, transT; + + void apply(J3DTexMtxInfo* info) const { + Mtx projection; + const auto* view = dComIfGd_getView(); + C_MTXLightPerspective(projection, view->fovy, view->aspect, scaleS, scaleT, transS, transT); +#if WIDESCREEN_SUPPORT + mDoGph_gInf_c::setWideZoomLightProjection(projection); +#endif + info->setEffectMtx(projection); + } +}; + +struct LightView { + Mtx inverse; + + void apply(J3DLightInfo* light, bool rotateDirection) const { + Mtx changeOfView; + MTXConcat(j3dSys.getViewMtx(), inverse, changeOfView); + MTXMultVec(changeOfView, &light->mLightPosition, &light->mLightPosition); + if (rotateDirection) { + MTXMultVecSR(changeOfView, &light->mLightDirection, &light->mLightDirection); + } + } +}; + +struct Tables { + std::unordered_map controllers; + std::unordered_map resources; + std::unordered_map projections; + std::unordered_map lightViews; +}; + +Tables& tables() { + static Tables stored; + return stored; +} + +template +Frames lookup(const Map& map, Key key) { + auto it = map.find(key); + return it == map.end() ? Frames{} : it->second; +} + +f32 sample_texture_translation(const J3DAnmKeyTableBase& table, const BE(f32)* values, f32 frame, f32 evaluated) { + if (table.mMaxFrame < 2 || table.mType != 1) { + return evaluated; + } + + const auto* keys = values + table.mOffset; + for (u16 i = 1; i < table.mMaxFrame; ++i) { + const auto* left = keys + (i - 1) * 4; + const auto* right = left + 4; + if ((f32)left[0] >= frame) { + break; + } + if ((f32)right[0] <= frame || (f32)right[0] - (f32)left[0] != 1.0f) { + continue; + } + const f32 slope = left[2]; + const f32 jump = (f32)right[1] - (f32)left[1] - slope; + const f32 tiles = std::round(jump); + if (std::abs((f32)right[3] - slope) < 0.0001f && std::abs(tiles) >= 1.0f && + std::abs(jump - tiles) < 0.0001f) + { + return (f32)left[1] + slope * (frame - (f32)left[0]); + } + } + + return evaluated; +} +} // namespace + +Frames get_frames(const J3DFrameCtrl* controller) { + return lookup(tables().controllers, controller); +} + +Frames get_frames(const J3DAnmBase* resource) { + return lookup(tables().resources, resource); +} + +void reset(const J3DFrameCtrl* controller) { + tables().controllers.erase(controller); +} + +void reset(const J3DAnmBase* resource) { + tables().resources.erase(resource); +} + +void bind(J3DAnmBase* resource, const J3DFrameCtrl* controller) { + Frames frames = get_frames(controller); + if (frames.valid && frames.tick == sim_tick_seq() && + frames.epoch == game_clock::g_frameTiming.presentationEpoch) + { + tables().resources[resource] = frames; + } else { + reset(resource); + } +} + +void swap_frames(J3DAnmBase* resource, Frames& frames) { + Frames saved = get_frames(resource); + if (frames.valid) { + tables().resources[resource] = frames; + } else { + reset(resource); + } + frames = saved; +} + +void clear() { + tables().controllers.clear(); + tables().resources.clear(); + tables().projections.clear(); + tables().lightViews.clear(); +} + +void prune() { + tables().projections.clear(); + tables().lightViews.clear(); + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + auto stale = [=](const auto& entry) { + return entry.second.epoch != epoch || entry.second.tick < tick - (tick != 0); + }; + std::erase_if(tables().controllers, stale); + std::erase_if(tables().resources, stale); +} + +f32 Frames::read(f32 requested) const { + if (!valid || !smooth || !is_enabled() || !is_presentation_active() || tick != sim_tick_seq() || + epoch != game_clock::g_frameTiming.presentationEpoch || requested != current) + { + return requested; + } + + const f32 step = get_interpolation_step(); + if (step <= 0.0f) { + return previous; + } + if (step >= 1.0f) { + return current; + } + + f32 frame = previous + (loop ? travel : current - previous) * step; + if (loop && (frame < loopStart || frame >= loopEnd)) { + if ((travel > 0.0f && frame >= loopEnd) || (travel < 0.0f && frame < loopStart)) { + const f32 length = loopEnd - loopStart; + frame = loopStart + std::fmod(frame - loopStart, length); + if (frame < loopStart) { + frame += length; + } + } + } + + return frame; +} + +Update::Update(J3DFrameCtrl& controller) : m_controller(controller), m_before(controller.getFrame()), m_continuous(true) { + m_travel = controller.getRate(); + const f32 next = m_before + m_travel; + const f32 start = controller.getStart(); + const f32 end = controller.getEnd(); + m_continuous = std::isfinite(m_before) && std::isfinite(next) && end > start; + switch (controller.getAttribute()) { + case J3DFrameCtrl::EMode_NONE: + break; + case J3DFrameCtrl::EMode_RESET: + m_continuous &= next < end; + break; + case J3DFrameCtrl::EMode_LOOP: + m_continuous &= end - start > 1.0f; + m_loopStart = m_travel < 0.0f ? start : controller.getLoop(); + m_loopEnd = m_travel < 0.0f ? controller.getLoop() : end; + if (m_loopEnd > m_loopStart && controller.getLoop() >= start && controller.getLoop() <= end) { + m_continuous &= m_before >= start && m_before < end; + } else { + m_loopStart = m_loopEnd = 0.0f; + m_continuous &= next >= start && next < end; + } + break; + case J3DFrameCtrl::EMode_REVERSE: + m_continuous &= next >= start && next < end; + break; + case J3DFrameCtrl::EMode_LOOP_REVERSE: + m_continuous &= next >= start && next < end - 1.0f; + break; + default: + m_continuous = false; + break; + } +} + +Update::~Update() { + auto& stored = tables().controllers; + Frames frames = get_frames(&m_controller); + if (!should_capture() || is_presentation_active()) { + reset(&m_controller); + return; + } + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + const bool same_tick = frames.tick == tick && frames.epoch == epoch && frames.valid; + const f32 current = m_controller.getFrame(); + const bool loop = m_loopEnd > m_loopStart; + const bool compatible = !same_tick || + (frames.loop == loop && (!loop || (frames.loopStart == m_loopStart && + frames.loopEnd == m_loopEnd && frames.travel * m_travel >= 0.0f))); + const bool continuous = compatible && m_continuous && std::isfinite(current) && + (!same_tick || (frames.smooth && frames.current == m_before)); + const f32 previous = same_tick ? frames.previous : m_before; + Frames captured{continuous ? previous : current, current, tick, epoch, true, continuous}; + captured.loop = continuous && loop; + captured.travel = same_tick ? frames.travel + m_travel : m_travel; + captured.loopStart = m_loopStart; + captured.loopEnd = m_loopEnd; + stored[&m_controller] = captured; +} + +Sample::Sample(J3DAnmBase* animation) : m_animation(animation), m_frame(animation->getFrame()) { + animation->mFrame = get_frames(animation).read(m_frame); +} + +Sample::~Sample() { + m_animation->mFrame = m_frame; +} + +void sample_texture(const J3DAnmTextureSRTKey* animation, u16 track, J3DTextureSRTInfo* result) { + const Frames frames = get_frames(animation); + const f32 current = animation->getFrame(); + const f32 sampled = frames.read(current); + animation->calcTransform(sampled, track, result); + + if (!is_enabled() || !is_presentation_active() || sampled == current) { + return; + } + + const auto* table = animation->mAnmTable + track * 3; + result->mTranslationX = sample_texture_translation(table[0].mTranslateInfo, animation->mTransData, + sampled, result->mTranslationX); + result->mTranslationY = sample_texture_translation(table[1].mTranslateInfo, animation->mTransData, + sampled, result->mTranslationY); +} + +namespace { +template +struct Values { + struct Entry { + T* target; + T captured; + T saved; + }; + std::vector entries; + + bool capture(T* target) { + if (target == nullptr || std::any_of(entries.begin(), entries.end(), + [=](const Entry& entry) { + return entry.target == target; + })) + { + return false; + } + entries.push_back({target, *target, *target}); + return true; + } + + void apply() { + for (auto& entry : entries) { + entry.saved = *entry.target; + *entry.target = entry.captured; + } + } + + void restore() { + for (auto& entry : entries) { + *entry.target = entry.saved; + } + } +}; + +template +bool same_color(const Color& a, const Color& b) { + return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; +} +} // namespace + +struct ModelBindings::State { + std::tuple, + Values, Values, Values, + Values, Values, Values, + Values, Values, Values, + Values, Values, Values, + Values, Values, Values, + Values, Values, Values> values; + + struct Animation { + J3DMaterial* material; + J3DMaterialAnm tracks; + }; + + struct Resource { + J3DAnmBase* target; + f32 frame; + Frames frames; + f32 savedFrame; + Frames savedFrames; + }; + + struct Projection { + J3DTexMtxInfo* target; + ViewProjection recipe; + }; + + struct Light { + J3DLightInfo* target; + LightView view; + bool rotateDirection; + }; + + std::vector animations; + std::vector resources; + std::vector projections; + std::vector lights; + + template + bool capture(T* target) { + return std::get>(values).capture(target); + } +}; + +ModelBindings::ModelBindings() : m_state(std::make_unique()) {} +ModelBindings::~ModelBindings() = default; + +void ModelBindings::capture(J3DMaterial* material) { + auto& state = *m_state; + if (!state.capture(material)) { + return; + } + + auto* color = material->getColorBlock(); + switch (color->getType()) { + case 'CLOF': state.capture(static_cast(color)); break; + case 'CLAB': state.capture(static_cast(color)); break; + case 'CLON': state.capture(static_cast(color)); break; + } + + auto* texgen = material->getTexGenBlock(); + switch (texgen->getType()) { + case 'TGPT': state.capture(static_cast(texgen)); break; + case 'TGB4': state.capture(static_cast(texgen)); break; + case 'TGBC': state.capture(static_cast(texgen)); break; + } + + auto* tev = material->getTevBlock(); + switch (tev->getType()) { + case 'TVPT': state.capture(static_cast(tev)); break; + case 'TVB1': state.capture(static_cast(tev)); break; + case 'TVB2': state.capture(static_cast(tev)); break; + case 'TVB4': state.capture(static_cast(tev)); break; + case 'TV16': state.capture(static_cast(tev)); break; + } + + auto* indirect = material->getIndBlock(); + if (indirect->getType() == 'IBLF') { + state.capture(static_cast(indirect)); + } + + auto* pe = material->getPEBlock(); + switch (pe->getType()) { + case 'PEFG': state.capture(static_cast(pe)); break; + case 'PEFL': state.capture(static_cast(pe)); break; + } + + for (int i = 0; i < 8; ++i) { + if (auto* light = color->getLight(i)) { + if (state.capture(light->getLightInfo())) { + if (auto it = tables().lightViews.find(material); i != 1 && it != tables().lightViews.end()) { + state.lights.push_back({light->getLightInfo(), it->second, i >= 2}); + } + } + state.capture(&light->mLightObj); + } + if (auto* coord = texgen->getTexCoord(i)) { + state.capture(&coord->mTexMtxReg); + } + if (auto* matrix = texgen->getTexMtx(i); state.capture(matrix)) { + auto* info = &matrix->getTexMtxInfo(); + if (auto it = tables().projections.find(info); it != tables().projections.end()) { + state.projections.push_back({info, it->second}); + } + } + } + + auto* animation = material->getMaterialAnm(); + if (animation == nullptr) { + return; + } + + auto& tracks = state.animations.emplace_back(State::Animation{material, *animation}).tracks; + for (int i = 0; i < 2; ++i) { + const auto& track = tracks.getMatColorAnm(i); + if (!track.getAnmFlag()) { + continue; + } + capture(track.getAnimation()); + GXColor evaluated; + track.calc(&evaluated); + if (!same_color(*material->getMatColor(i), evaluated)) { + tracks.setMatColorAnm(i, nullptr); + } + } + for (int i = 0; i < 8; ++i) { + const auto& track = tracks.getTexNoAnm(i); + if (!track.getAnmFlag()) { + continue; + } + capture(track.getAnimation()); + u16 evaluated; + track.calc(&evaluated); + if (material->getTexNo(i) != evaluated) { + tracks.setTexNoAnm(i, nullptr); + } + } + for (int i = 0; i < 8; ++i) { + const auto& track = tracks.getTexMtxAnm(i); + if (!track.getAnmFlag()) { + continue; + } + capture(track.getAnimation()); + J3DTextureSRTInfo evaluated; + track.calc(&evaluated); + if (!material->getTexMtx(i)->getTexMtxInfo().mSRT.operator==(evaluated)) { + tracks.setTexMtxAnm(i, nullptr); + } + } + for (int i = 0; i < 3; ++i) { + const auto& track = tracks.getTevColorAnm(i); + if (!track.getAnmFlag()) { + continue; + } + capture(track.getAnimation()); + GXColorS10 evaluated; + track.calc(&evaluated); + if (!same_color(*material->getTevColor(i), evaluated)) { + tracks.setTevColorAnm(i, nullptr); + } + } + for (int i = 0; i < 4; ++i) { + const auto& track = tracks.getTevKColorAnm(i); + if (!track.getAnmFlag()) { + continue; + } + capture(track.getAnimation()); + GXColor evaluated; + track.calc(&evaluated); + if (!same_color(*material->getTevKColor(i), evaluated)) { + tracks.setTevKColorAnm(i, nullptr); + } + } +} + +void ModelBindings::capture(J3DAnmBase* resource) { + if (resource == nullptr) { + return; + } + + for (const auto& entry : m_state->resources) { + if (entry.target == resource) { + return; + } + } + + m_state->resources.push_back({resource, resource->getFrame(), get_frames(resource)}); +} + +void ModelBindings::apply() { + std::apply([](auto&... values) { (values.apply(), ...); }, m_state->values); + + for (auto& entry : m_state->animations) { + entry.material->setMaterialAnm(&entry.tracks); + } + + for (auto& entry : m_state->resources) { + entry.savedFrame = entry.target->mFrame; + entry.target->mFrame = entry.frame; + entry.savedFrames = entry.frames; + swap_frames(entry.target, entry.savedFrames); + } + + for (const auto& entry : m_state->projections) { + entry.recipe.apply(entry.target); + } + + for (const auto& entry : m_state->lights) { + entry.view.apply(entry.target, entry.rotateDirection); + } +} + +void ModelBindings::restore() { + for (auto& entry : m_state->resources) { + entry.target->mFrame = entry.savedFrame; + swap_frames(entry.target, entry.savedFrames); + } + + std::apply([](auto&... values) { (values.restore(), ...); }, m_state->values); +} + +void record_model(J3DModel* model) { + if (!should_capture() || is_presentation_active()) { + return; + } + + struct Recording { + J3DModel* model; + ModelBindings bindings; + }; + + auto recording = std::make_shared(); + recording->model = model; + J3DModelData* data = model->getModelData(); + for (u16 i = 0; i < data->getMaterialNum(); ++i) { + recording->bindings.capture(data->getMaterialNodePointer(i)); + } + + add_interpolation_callback([](void* work) { + auto& recording = *static_cast(work); + ModelScope scope(recording.bindings); + recording.model->calcMaterial(); + recording.model->diff(); + }, recording.get(), recording); +} + +void set_view_projection(J3DTexMtxInfo* info, f32 scaleS, f32 scaleT, f32 transS, f32 transT) { + ViewProjection recipe{scaleS, scaleT, transS, transT}; + recipe.apply(info); + if (should_capture() && !is_presentation_active()) { + tables().projections.insert_or_assign(info, recipe); + } +} + +void record_light_view(J3DMaterial* material) { + if (!should_capture() || is_presentation_active()) { + return; + } + + LightView view; + if (MTXInverse(j3dSys.getViewMtx(), view.inverse)) { + tables().lightViews.insert_or_assign(material, view); + } +} + +} // namespace dusk::interp::material diff --git a/src/dusk/interp/material.h b/src/dusk/interp/material.h new file mode 100644 index 0000000000..cfd7f5abc7 --- /dev/null +++ b/src/dusk/interp/material.h @@ -0,0 +1,104 @@ +#pragma once + +#include "JSystem/J3DGraphAnimator/J3DMaterialAnm.h" +#include "JSystem/J3DGraphBase/J3DMatBlock.h" + +#include +#include +#include + +class J3DAnmBase; +class J3DFrameCtrl; +class J3DModel; + +namespace dusk::interp::material { + +class ModelBindings; + +struct Frames { + f32 previous = 0.0f; + f32 current = 0.0f; + uint64_t tick = 0; + uint64_t epoch = 0; + bool valid = false; + bool smooth = false; + + bool loop = false; + f32 travel = 0.0f; + f32 loopStart = 0.0f; + f32 loopEnd = 0.0f; + + f32 read(f32 requested) const; +}; + +class Update { +public: + explicit Update(J3DFrameCtrl& controller); + ~Update(); + Update(const Update&) = delete; + Update& operator=(const Update&) = delete; + +private: + J3DFrameCtrl& m_controller; + f32 m_before; + bool m_continuous; + f32 m_travel = 0.0f; + f32 m_loopStart = 0.0f; + f32 m_loopEnd = 0.0f; +}; + +class Sample { +public: + explicit Sample(J3DAnmBase* animation); + ~Sample(); + Sample(const Sample&) = delete; + Sample& operator=(const Sample&) = delete; + +private: + J3DAnmBase* m_animation; + f32 m_frame; +}; + +class ModelBindings { +public: + ModelBindings(); + ~ModelBindings(); + ModelBindings(const ModelBindings&) = delete; + ModelBindings& operator=(const ModelBindings&) = delete; + void capture(J3DMaterial* material); + void apply(); + void restore(); + +private: + void capture(J3DAnmBase* resource); + struct State; + std::unique_ptr m_state; +}; + +class ModelScope { +public: + explicit ModelScope(ModelBindings& bindings) : m_bindings(bindings) { m_bindings.apply(); } + ~ModelScope() { m_bindings.restore(); } + ModelScope(const ModelScope&) = delete; + ModelScope& operator=(const ModelScope&) = delete; +private: + ModelBindings& m_bindings; +}; + +void record_model(J3DModel* model); + +void set_view_projection(J3DTexMtxInfo* info, f32 scaleS, f32 scaleT, f32 transS, f32 transT); +void record_light_view(J3DMaterial* material); + +void sample_texture(const J3DAnmTextureSRTKey* animation, u16 track, J3DTextureSRTInfo* result); + +Frames get_frames(const J3DFrameCtrl* controller); +Frames get_frames(const J3DAnmBase* resource); +void reset(const J3DFrameCtrl* controller); +void reset(const J3DAnmBase* resource); +void bind(J3DAnmBase* resource, const J3DFrameCtrl* controller); +void swap_frames(J3DAnmBase* resource, Frames& frames); +void prune(); +void clear(); + +} // namespace dusk::interp::material diff --git a/src/dusk/interp/menus.h b/src/dusk/interp/menus.h new file mode 100644 index 0000000000..185ee195e7 --- /dev/null +++ b/src/dusk/interp/menus.h @@ -0,0 +1,31 @@ +#pragma once + +#include "samples.h" + +namespace dusk::interp { + +struct MenuPose { + f32 x; + f32 y; + f32 alpha; +}; + +inline void lerp(MenuPose& out, const MenuPose& lhs, const MenuPose& rhs, float step) { + out = {lerp(lhs.x, rhs.x, step), lerp(lhs.y, rhs.y, step), lerp(lhs.alpha, rhs.alpha, step)}; +} + +struct MenuTransition { + Samples poses; +}; + +inline void capture_menu_pose(const void* owner, MenuPose pose) { + if (should_capture()) { + get(owner).poses.capture(&pose, 1); + } +} + +inline MenuPose read_menu_pose(const void* owner, MenuPose current) { + return get(owner).poses.read(0, current); +} + +} // namespace dusk::interp diff --git a/src/dusk/interp/particle.cpp b/src/dusk/interp/particle.cpp new file mode 100644 index 0000000000..8ec079e9e4 --- /dev/null +++ b/src/dusk/interp/particle.cpp @@ -0,0 +1,340 @@ +#include "particle.h" + +#include "frame_interpolation.h" +#include "lerp.h" + +#include "dusk/game_clock.h" + +#include "JSystem/JParticle/JPAChildShape.h" + +#include +#include +#include + +namespace dusk::interp::particle { +namespace { +struct ParticleState { + JGeometry::TVec3 position, localPosition, offsetPosition, velocity, baseAxis; + float scaleX, scaleY, time, age; + u16 angle; + u8 alpha; + GXColor prm, env; + + explicit ParticleState(const JPABaseParticle* p) : ParticleState(p, p->mPosition) {} + + ParticleState(const JPABaseParticle* p, const JGeometry::TVec3& pos) + : position(pos), localPosition(p->mLocalPosition), + offsetPosition(p->mOffsetPosition), velocity(p->mVelocity), baseAxis(p->mBaseAxis), + scaleX(p->mParticleScaleX), scaleY(p->mParticleScaleY), time(p->mTime), age(p->mAge), + angle(p->mRotateAngle), alpha(p->mPrmColorAlphaAnm), prm(p->mPrmClr), + env{p->mEnvClr.r, p->mEnvClr.g, p->mEnvClr.b, 0} {} +}; + +struct EmitterState { + EmitterVisualState visual; + JGeometry::TVec3 position, direction; + float age; + + explicit EmitterState(JPABaseEmitter* e) : visual(e), age(e->mTick) { + e->calcEmitterGlobalPosition(&position); + JPAEmitterWorkData drawWork; + drawWork.mpEmtr = e; + e->pRes->calcWorkData_d(&drawWork); + direction = drawWork.mGlobalEmtrDir; + } +}; + +template +struct History { + T previous, current; + uint64_t tick, epoch; +}; + +std::unordered_map> s_particles; +std::unordered_map> s_emitters; + +// Infuriatingly, some particles are recreated every frame rather than having a real +// lifespan, so it's necessary to track their relationship to be able to do interpolation +struct ReplacementTrack { + uint64_t generation = 0, tick = 0, epoch = 0; + std::optional> frames; +}; + +void capture_replacement(ReplacementTrack& track, History& h) { + if (track.frames && track.frames->epoch == h.epoch) { + const auto& last = *track.frames; + if (last.tick == h.tick) { + h.previous = last.previous; + } else if (last.tick + 1 == h.tick) { + h.previous = last.current; + } + } + track.frames = h; + track.tick = h.tick; + track.epoch = h.epoch; +} + +struct ChildBirth { + const JPABaseParticle* parent; + uint64_t generation, tick, epoch; +}; + +std::unordered_map s_childTracks; +std::unordered_map s_childBirths; +std::unordered_map s_singleFrameParticles; +uint64_t s_childGeneration = 0; + +template +void capture_state(Map& map, Key key, const State& current) { + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + auto [it, added] = map.try_emplace(key, History{current, current, tick, epoch}); + auto& h = it->second; + if (!added) { + if (h.epoch != epoch || (h.tick != tick && h.tick + 1 != tick) || current.age < h.current.age) { + h.previous = current; + } else if (h.tick != tick) { + h.previous = h.current; + } + h.current = current; + h.tick = tick; + h.epoch = epoch; + } +} + +template +const Map::mapped_type* history(const Map& map, Key key) { + if (!is_enabled() || !is_presentation_active()) { + return nullptr; + } + auto it = map.find(key); + if (it == map.end() || it->second.tick != sim_tick_seq() || + it->second.epoch != game_clock::g_frameTiming.presentationEpoch) + { + return nullptr; + } + return &it->second; +} + +u16 blend_angle(u16 a, u16 b, float t) { + int delta = (static_cast(b) - a + 32768) & 65535; + delta -= 32768; + return static_cast(static_cast(a) + std::lround(delta * t)); +} + +template +void prune_map(Map& map) { + for (auto it = map.begin(); it != map.end();) { + if (it->second.epoch != game_clock::g_frameTiming.presentationEpoch || + it->second.tick + 1 < sim_tick_seq()) + { + it = map.erase(it); + } else { + ++it; + } + } +} +} // namespace + +void clear() { + s_particles.clear(); + s_emitters.clear(); + s_childTracks.clear(); + s_childBirths.clear(); + s_singleFrameParticles.clear(); +} + +void prune() { + prune_map(s_particles); + prune_map(s_emitters); + prune_map(s_childTracks); + prune_map(s_childBirths); + prune_map(s_singleFrameParticles); +} + +void reset(const JPABaseParticle* p) { + s_particles.erase(p); + s_childTracks.erase(p); + s_childBirths.erase(p); +} + +void reset_child(const JPABaseParticle* p, const JPABaseParticle* parent, + const JPAEmitterWorkData* work) { + reset(p); + const auto* shape = work->mpRes->getCsp(); + if (!should_capture() || shape->getLife() != 1 || shape->getRate() != 1 || shape->getStep() != 0) { + return; + } + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + auto [it, added] = s_childTracks.try_emplace(parent, ReplacementTrack{0, tick, epoch, std::nullopt}); + if (added) { + it->second.generation = ++s_childGeneration; + } + s_childBirths.insert_or_assign(p, ChildBirth{parent, it->second.generation, tick, epoch}); +} + +void reset(const JPABaseEmitter* e) { + s_emitters.erase(e); + s_singleFrameParticles.erase(e); +} + +void capture_birth(const JPABaseParticle* p, const JPAEmitterWorkData* work) { + if (!should_capture() || p->mAge != 0 || s_particles.contains(p)) { + return; + } + const JGeometry::TVec3 position(p->mOffsetPosition.x + p->mLocalPosition.x * work->mPublicScale.x, + p->mOffsetPosition.y + p->mLocalPosition.y * work->mPublicScale.y, + p->mOffsetPosition.z + p->mLocalPosition.z * work->mPublicScale.z); + capture_state(s_particles, p, ParticleState(p, position)); +} + +void capture(const JPABaseParticle* p) { + if (should_capture() && p->mAge >= 0) { + capture_state(s_particles, p, ParticleState(p)); + if (auto birth = s_childBirths.find(p); birth != s_childBirths.end()) { + auto track = s_childTracks.find(birth->second.parent); + auto& h = s_particles.at(p); + if (p->mAge == 0 && birth->second.tick == h.tick && birth->second.epoch == h.epoch && + track != s_childTracks.end() && track->second.generation == birth->second.generation) + { + capture_replacement(track->second, h); + } + s_childBirths.erase(birth); + } + } +} + +void capture(JPABaseEmitter* e) { + if (should_capture()) { + capture_state(s_emitters, e, EmitterState(e)); + + const auto* dynamics = e->pRes->getDyn(); + if (e->mLifeTime == 1 && e->mRate == 1.0f && e->mRateStep == 0 && + dynamics->getRateRndm() == 0.0f && dynamics->getLifetimeRndm() == 0.0f && + e->mAlivePtclBase.getNum() == 1) + { + const auto* particle = e->mAlivePtclBase.getFirst()->getObject(); + if (particle->mAge == 0 && particle->mLifeTime == 1) { + auto& track = s_singleFrameParticles[e]; + capture_state(s_particles, particle, ParticleState(particle)); + auto& h = s_particles.at(particle); + capture_replacement(track, h); + return; + } + } + s_singleFrameParticles.erase(e); + } +} + +JPABaseParticle* present(JPABaseParticle* p, JPABaseParticle& scratch, float& age) { + age = p->mAge; + if (!game_clock::is_presentation_frame()) { + return p; + } + scratch = *p; + const auto* h = history(s_particles, p); + const float t = get_interpolation_step(); + if (h == nullptr || t >= 1.0f) { + return &scratch; + } + const auto& a = h->previous; + const auto& b = h->current; + scratch.mPosition = lerp(a.position, b.position, t); + scratch.mLocalPosition = lerp(a.localPosition, b.localPosition, t); + scratch.mOffsetPosition = lerp(a.offsetPosition, b.offsetPosition, t); + scratch.mVelocity = lerp(a.velocity, b.velocity, t); + scratch.mBaseAxis = lerp(a.baseAxis, b.baseAxis, t); + if (scratch.mBaseAxis.isZero()) { + scratch.mBaseAxis = b.baseAxis; + } + scratch.mParticleScaleX = lerp(a.scaleX, b.scaleX, t); + scratch.mParticleScaleY = lerp(a.scaleY, b.scaleY, t); + scratch.mRotateAngle = blend_angle(a.angle, b.angle, t); + scratch.mPrmColorAlphaAnm = lerp(a.alpha, b.alpha, t); + scratch.mPrmClr = lerp(a.prm, b.prm, t); + const GXColor env = lerp(a.env, b.env, t); + scratch.mEnvClr.r = env.r; + scratch.mEnvClr.g = env.g; + scratch.mEnvClr.b = env.b; + scratch.mTime = lerp(a.time, b.time, t); + age = lerp(a.age, b.age, t); + return &scratch; +} + +JGeometry::TVec3 position(const JPABaseParticle* p) { + const auto* h = history(s_particles, p); + const float t = get_interpolation_step(); + if (h != nullptr && t < 1.0f) { + return lerp(h->previous.position, h->current.position, t); + } + return p->mPosition; +} + +JGeometry::TVec3 emitter_position(const JPABaseEmitter* e, const JGeometry::TVec3& current) { + const auto* h = history(s_emitters, e); + const float t = get_interpolation_step(); + if (h != nullptr && t < 1.0f) { + return lerp(h->previous.position, h->current.position, t); + } + return current; +} + +JGeometry::TVec3 emitter_direction(const JPABaseEmitter* e, const JGeometry::TVec3& current) { + const auto* h = history(s_emitters, e); + const float t = get_interpolation_step(); + if (h != nullptr && t < 1.0f) { + return lerp(h->previous.direction, h->current.direction, t); + } + return current; +} + +float emitter_age(const JPABaseEmitter* e) { + const auto* h = history(s_emitters, e); + const float t = get_interpolation_step(); + if (h != nullptr && t < 1.0f) { + return lerp(h->previous.age, h->current.age, t); + } + return e->mTick; +} + +EmitterVisualState::EmitterVisualState(const JPABaseEmitter* e) + : scale(e->mGlobalPScl), prm(e->mPrmClr), env(e->mEnvClr), globalPrm(e->mGlobalPrmClr), + globalEnv(e->mGlobalEnvClr) {} + +void EmitterVisualState::apply(JPABaseEmitter* e) const { + e->mGlobalPScl = scale; + e->mPrmClr = prm; + e->mEnvClr = env; + e->mGlobalPrmClr = globalPrm; + e->mGlobalEnvClr = globalEnv; +} + +EmitterDraw::EmitterDraw(JPABaseEmitter* e) : m_emitter(nullptr), m_saved(e) { + if (!game_clock::is_presentation_frame()) { + return; + } + m_emitter = e; + const auto* h = history(s_emitters, e); + const float t = get_interpolation_step(); + if (h == nullptr || t >= 1.0f) { + return; + } + const auto& a = h->previous.visual; + const auto& b = h->current.visual; + EmitterVisualState visual(e); + visual.scale.set(lerp(a.scale.x, b.scale.x, t), lerp(a.scale.y, b.scale.y, t)); + visual.prm = lerp(a.prm, b.prm, t); + visual.env = lerp(a.env, b.env, t); + visual.globalPrm = lerp(a.globalPrm, b.globalPrm, t); + visual.globalEnv = lerp(a.globalEnv, b.globalEnv, t); + visual.apply(e); +} + +EmitterDraw::~EmitterDraw() { + if (m_emitter != nullptr) { + m_saved.apply(m_emitter); + } +} + +} // namespace dusk::interp::particle diff --git a/src/dusk/interp/particle.h b/src/dusk/interp/particle.h new file mode 100644 index 0000000000..acadf55619 --- /dev/null +++ b/src/dusk/interp/particle.h @@ -0,0 +1,42 @@ +#pragma once + +#include "JSystem/JParticle/JPAEmitter.h" +#include "JSystem/JParticle/JPAParticle.h" + +namespace dusk::interp::particle { + +void clear(); +void prune(); +void reset(const JPABaseParticle*); +void reset(const JPABaseEmitter*); +void reset_child(const JPABaseParticle*, const JPABaseParticle* parent, const JPAEmitterWorkData*); +void capture_birth(const JPABaseParticle*, const JPAEmitterWorkData*); +void capture(const JPABaseParticle*); +void capture(JPABaseEmitter*); + +JPABaseParticle* present(JPABaseParticle*, JPABaseParticle& scratch, float& age); +JGeometry::TVec3 position(const JPABaseParticle*); +JGeometry::TVec3 emitter_position(const JPABaseEmitter*, const JGeometry::TVec3&); +JGeometry::TVec3 emitter_direction(const JPABaseEmitter*, const JGeometry::TVec3&); +float emitter_age(const JPABaseEmitter*); + +struct EmitterVisualState { + JGeometry::TVec2 scale; + GXColor prm, env, globalPrm, globalEnv; + explicit EmitterVisualState(const JPABaseEmitter*); + void apply(JPABaseEmitter*) const; +}; + +class EmitterDraw { +public: + explicit EmitterDraw(JPABaseEmitter*); + ~EmitterDraw(); + EmitterDraw(const EmitterDraw&) = delete; + EmitterDraw& operator=(const EmitterDraw&) = delete; + +private: + JPABaseEmitter* m_emitter; + EmitterVisualState m_saved; +}; + +} // namespace dusk::interp::particle diff --git a/src/dusk/interp/samples.cpp b/src/dusk/interp/samples.cpp new file mode 100644 index 0000000000..4221814f9c --- /dev/null +++ b/src/dusk/interp/samples.cpp @@ -0,0 +1,65 @@ +#include "dusk/interp/samples.h" + +#include +#include + +namespace dusk::interp { +namespace { + +struct Slot { + const void* type; + void* ptr; + void (*destroy)(void*); +}; + +using OwnerMap = absl::flat_hash_map>; + +OwnerMap& owner_map() { + static OwnerMap s_map; + return s_map; +} + +} // namespace + +void* detail::acquire(const void* key, const void* type, void* (*make)(), void (*destroy)(void*)) { + const uintptr_t id = reinterpret_cast(key); + auto& slots = owner_map()[id]; + for (Slot& slot : slots) { + if (slot.type == type) { + return slot.ptr; + } + } + + void* ptr = make(); + slots.push_back({type, ptr, destroy}); + return ptr; +} + +void erase_owned_samples(const void* key) { + if (key == nullptr) { + return; + } + + OwnerMap& stored = owner_map(); + auto it = stored.find(reinterpret_cast(key)); + if (it == stored.end()) { + return; + } + + for (Slot& slot : it->second) { + slot.destroy(slot.ptr); + } + stored.erase(it); +} + +void clear_owned_samples() { + OwnerMap& stored = owner_map(); + for (auto& entry : stored) { + for (Slot& slot : entry.second) { + slot.destroy(slot.ptr); + } + } + stored.clear(); +} + +} // namespace dusk::interp diff --git a/src/dusk/interp/samples.h b/src/dusk/interp/samples.h new file mode 100644 index 0000000000..e1ccc661ad --- /dev/null +++ b/src/dusk/interp/samples.h @@ -0,0 +1,106 @@ +#pragma once + +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/lerp.h" + +#include + +namespace dusk::interp { + +template +class Samples { +public: + void reset() { m_samples.clear(); } + + template + void capture(int count, Sample sample) { + if (!should_capture()) { + return; + } + if (count <= 0) { + reset(); + return; + } + + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + const bool continuous = epoch == m_epoch && (tick == m_tick || tick == m_tick + 1); + if (!continuous) { + reset(); + } + if (count < m_samples.size()) { + m_samples.erase(m_samples.begin() + count, m_samples.end()); + } + m_samples.reserve(count); + for (int i = 0; i < count; ++i) { + T current = sample(i); + if (i == m_samples.size()) { + m_samples.push_back({current, current}); + continue; + } + auto& entry = m_samples[i]; + if (tick != m_tick) { + entry.previous = entry.current; + } + entry.current = current; + } + m_tick = tick; + m_epoch = epoch; + } + + void capture(const T* source, int count) { + capture(source != nullptr ? count : 0, [&](int i) { return source[i]; }); + } + + T read(int index, const T& current) const { + if (!valid(index)) { + return current; + } + T result = current; + const auto& entry = m_samples[index]; + lerp(result, entry.previous, entry.current, get_interpolation_step()); + return result; + } + + T read(int index, const T& current, f32 snap_distance) const { + if (!valid(index) || + m_samples[index].previous.abs(m_samples[index].current) > snap_distance) + { + return current; + } + return read(index, current); + } + +private: + bool valid(int index) const { + return is_enabled() && is_presentation_active() && m_tick == sim_tick_seq() && + m_epoch == game_clock::g_frameTiming.presentationEpoch && + index >= 0 && static_cast(index) < m_samples.size(); + } + + struct Entry { + T previous; + T current; + }; + std::vector m_samples; + uint64_t m_tick = 0; + uint64_t m_epoch = 0; +}; + +namespace detail { +void* acquire(const void* key, const void* type, void* (*make)(), void (*destroy)(void*)); +} + +template +Record& get(const void* key) { + static const char token{}; + return *static_cast(detail::acquire( + key, &token, []() -> void* { return new Record; }, + [](void* p) { delete static_cast(p); })); +} + +void erase_owned_samples(const void* key); +void clear_owned_samples(); + +} // namespace dusk::interp diff --git a/src/dusk/interp/sight.h b/src/dusk/interp/sight.h new file mode 100644 index 0000000000..c56c186cae --- /dev/null +++ b/src/dusk/interp/sight.h @@ -0,0 +1,23 @@ +#pragma once + +#include "dusk/interp/samples.h" + +namespace dusk::interp { + +struct SightAnimation { + f32 bck, brk, alpha, brkEnd; +}; + +inline void lerp(SightAnimation& out, const SightAnimation& a, const SightAnimation& b, f32 step) { + out.bck = lerp(a.bck, b.bck < a.bck ? b.bck + 29.0f : b.bck, step); + if (out.bck >= 50.0f) { + out.bck -= 29.0f; + } + out.brk = lerp(a.brk, b.brk < a.brk ? b.brk + b.brkEnd : b.brk, step); + if (out.brk >= b.brkEnd) { + out.brk -= b.brkEnd; + } + out.alpha = lerp(a.alpha, b.alpha, step); +} + +} // namespace dusk::interp diff --git a/src/dusk/interp/user_interface.h b/src/dusk/interp/user_interface.h new file mode 100644 index 0000000000..a6f34bbe8d --- /dev/null +++ b/src/dusk/interp/user_interface.h @@ -0,0 +1,207 @@ +#pragma once + +#include "dusk/game_clock.h" + +#include "JSystem/J2DGraph/J2DAnimation.h" +#include "JSystem/J2DGraph/J2DPane.h" +#include "SSystem/SComponent/c_lib.h" +#include "SSystem/SComponent/c_math.h" + +#include + +#include +#include +#include + +namespace dusk::vdt { + +class FrameAnimation { +public: + void start(f32 from, f32 target, f32 speed = 1.0f, f32 period = 0.0f) { + mFrame = from; + mTarget = target; + mSpeed = speed; + mPeriod = period; + mTime = game_clock::sample_time(); + mEpoch = game_clock::g_frameTiming.presentationEpoch; + } + + void approach(f32 from, f32 target, f32 speed = 1.0f) { + if (mTarget != target || std::abs(mSpeed - speed) > 0.000001f || mPeriod != 0.0f || + mEpoch != game_clock::g_frameTiming.presentationEpoch) + { + start(from, target, speed); + } + } + + void finish(f32 frame) { start(frame, frame, 0.0f); } + + f32 advance(f32 logicalFrame) { + const f32 frames = game_clock::consume_interval(mTime) / game_clock::kSimPeriod; + if (!game_clock::g_frameTiming.interpolating || + mEpoch != game_clock::g_frameTiming.presentationEpoch) + { + mFrame = logicalFrame; + mEpoch = game_clock::g_frameTiming.presentationEpoch; + } else if (mPeriod > 0.0f) { + mFrame = std::fmod(mFrame + mSpeed * frames, mPeriod); + } else { + const f32 step = mSpeed * frames; + mFrame = mFrame < mTarget ? std::min(mFrame + step, mTarget) + : std::max(mFrame - step, mTarget); + } + return mFrame; + } + + f32 value() const { return mFrame; } + +private: + f32 mFrame = 0.0f; + f32 mTarget = 0.0f; + f32 mSpeed = 0.0f; + f32 mPeriod = 0.0f; + double mTime = 0.0; + u64 mEpoch = 0; +}; + +inline f32 clamped_fraction(f32 timer, f32 duration) { + if (duration <= 0.0f) { + return 1.0f; + } + return std::clamp(timer / duration, 0.0f, 1.0f); +} + +inline void advance_looping_frame(f32& frame, f32 speed, f32 max) { + if (max <= 0.0f) { + return; + } + frame += speed * game_clock::original_frames(); + if (frame >= max) { + frame = fmodf(frame, max); + } +} + +inline void advance_toward_frame(f32& frame, f32 target, f32 speed) { + if (frame == target) { + return; + } + const f32 step = speed * game_clock::original_frames(); + frame = frame < target ? std::min(frame + step, target) : std::max(frame - step, target); +} + +template +bool request_animation(Animation& animation, f32& frame, s16 time, + decltype(animation.start) from, decltype(animation.end) to, u8 curve) { + if (frame < time) { + animation = {true, time, curve, from, to}; + return false; + } + animation.active = false; + frame = time; + return true; +} + +template +void present_animation(Animation& animation, f32& frame, Pane& pane, Apply apply) { + if (!animation.active) { + return; + } + advance_toward_frame(frame, animation.duration, 1.0f); + apply(pane.rateCalc(animation.duration, frame, animation.curve)); + if (frame >= animation.duration) { + animation.active = false; + } +} + +inline void present_looping(f32& frame, J2DAnmBase* anm, f32 speed) { + if (anm == nullptr) { + return; + } + advance_looping_frame(frame, speed, anm->getFrameMax()); + anm->setFrame(frame); +} + +inline void present_toward(f32& frame, f32 target, J2DAnmTransform* anm, J2DPane* pane = nullptr) { + if (anm == nullptr) { + return; + } + if (pane != nullptr && pane->mTransform != anm) { + return; + } + advance_toward_frame(frame, target, 2.0f); + anm->setFrame(frame); + if (pane != nullptr) { + pane->animationTransform(); + } +} + +inline void present_selected(f32& frame, J2DPane* pane, J2DAnmTransform* first, f32 firstTarget, + J2DAnmTransform* second, f32 secondTarget) +{ + if (pane != nullptr) { + if (pane->mTransform == first) { + present_toward(frame, firstTarget, first, pane); + } else if (pane->mTransform == second) { + present_toward(frame, secondTarget, second, pane); + } + } +} + +inline void present_shared(f32& frame, f32 target, J2DAnmTransform* anm, + std::initializer_list panes) +{ + if (frame == target) { + return; + } + bool advanced = false; + for (J2DPane* pane : panes) { + if (pane != nullptr && pane->mTransform == anm) { + if (!advanced) { + present_toward(frame, target, anm); + advanced = true; + } + pane->animationTransform(); + } + } +} + +inline void present_addCalc(f32* value, f32 target, f32 scale, f32 maxStep, f32 minStep) { + const f32 frames = game_clock::original_frames(); + if (*value == target || frames <= 0.0f) { + return; + } + const f32 timedScale = frames == 1.0f ? scale : 1.0f - std::pow(1.0f - scale, frames); + cLib_addCalc(value, target, timedScale, maxStep * frames, minStep * frames); +} + +inline void present_addCalc2(f32* value, f32 target, f32 scale, f32 maxStep, f32 snap) { + const f32 frames = game_clock::original_frames(); + if (*value == target || frames <= 0.0f) { + return; + } + const f32 timedScale = frames == 1.0f ? scale : 1.0f - std::pow(1.0f - scale, frames); + cLib_addCalc2(value, target, timedScale, maxStep * frames); + if (fabsf(*value - target) < snap) { + *value = target; + } +} + +inline f32 present_sine_ease(f32 max, f32 value) { + if (max <= 0.0f) { + return 1.0f; + } + value = std::clamp(value, 0.0f, max); + f32 rate = value / max; + rate = cM_ssin((int)(0.5f * (32768.0f * rate))); + return rate * rate; +} + +inline bool present_chase(f32* value, f32 target, f32 scale, f32 maxStep, f32 snap) { + if (*value == target) { + return false; + } + present_addCalc2(value, target, scale, maxStep, snap); + return true; +} + +} // namespace dusk::vdt diff --git a/src/dusk/interp/vertex.cpp b/src/dusk/interp/vertex.cpp new file mode 100644 index 0000000000..790f508484 --- /dev/null +++ b/src/dusk/interp/vertex.cpp @@ -0,0 +1,131 @@ +#include "vertex.h" + +#include "frame_interpolation.h" +#include "dusk/game_clock.h" +#include "JSystem/J3DGraphBase/J3DVertex.h" + +#include +#include +#include + +namespace dusk::interp::vertex { +namespace { + +struct Array { + std::vector previous; + std::vector current; + std::vector presentation; + const void* source = nullptr; + float cached_step = -1.0f; + + void capture(const void* data, u32 count, bool continuous, bool same_tick) { + source = data; + cached_step = -1.0f; + if (data == nullptr || count == 0) { + *this = {}; + return; + } + continuous &= current.size() == count; + if (continuous && !same_tick) previous.swap(current); + const auto* values = static_cast(data); + current.assign(values, values + count); + if (!continuous) previous = current; + } + + void* read(void* requested, float step) { + if (requested != source || current.empty() || step >= 1.0f) return requested; + if (step <= 0.0f) return previous.data(); + if (step != cached_step) { + presentation.resize(current.size()); + for (size_t i = 0; i < current.size(); ++i) { + const auto& a = previous[i]; + const auto& b = current[i]; + presentation[i] = {a.x + (b.x - a.x) * step, + a.y + (b.y - a.y) * step, + a.z + (b.z - a.z) * step}; + } + cached_step = step; + } + return presentation.data(); + } +}; + +struct Record { + const J3DDeformData* deformation = nullptr; + const J3DVertexData* layout = nullptr; + uint64_t tick = 0; + uint64_t epoch = 0; + Array positions; + Array normals; +}; + +auto& records() { + static std::unordered_map stored; + return stored; +} + +void* read(const J3DVertexBuffer* buffer, void* current, Array Record::* array) { + if (!is_enabled() || !is_presentation_active()) return current; + auto it = records().find(buffer); + if (it == records().end() || it->second.tick != sim_tick_seq() || + it->second.epoch != game_clock::g_frameTiming.presentationEpoch) + { + return current; + } + return (it->second.*array).read(current, get_interpolation_step()); +} + +} // namespace + +void capture(J3DVertexBuffer* buffer, const J3DDeformData* deformation) { + if (!should_capture() || is_presentation_active()) return; + if (deformation == nullptr) { + reset(buffer); + return; + } + auto* layout = buffer->getVertexData(); + void* positions = buffer->getCurrentVtxPos(); + void* normals = buffer->getCurrentVtxNrm(); + if (layout->getVtxNum() == 0 || layout->getVtxPosType() != GX_F32 || + positions == layout->getVtxPosArray()) positions = nullptr; + if (layout->getNrmNum() == 0 || layout->getVtxNrmType() != GX_F32 || + normals == layout->getVtxNrmArray()) normals = nullptr; + if (positions == nullptr && normals == nullptr) { + reset(buffer); + return; + } + + auto& record = records()[buffer]; + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + const bool same_tick = record.tick == tick; + const bool continuous = record.deformation == deformation && record.layout == layout && + record.epoch == epoch && (same_tick || record.tick + 1 == tick); + + record.positions.capture(positions, layout->getVtxNum(), continuous, same_tick); + record.normals.capture(normals, layout->getNrmNum(), continuous, same_tick); + record.deformation = deformation; + record.layout = layout; + record.tick = tick; + record.epoch = epoch; +} + +void* positions(const J3DVertexBuffer* buffer, void* current) { return read(buffer, current, &Record::positions); } +void* normals(const J3DVertexBuffer* buffer, void* current) { return read(buffer, current, &Record::normals); } +void reset(const J3DVertexBuffer* buffer) { records().erase(buffer); } + +void invalidate(const J3DDeformData* deformation) { + std::erase_if(records(), [=](const auto& entry) { return entry.second.deformation == deformation; }); +} + +void prune() { + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + std::erase_if(records(), [=](const auto& entry) { + return entry.second.epoch != epoch || entry.second.tick < tick - (tick != 0); + }); +} + +void clear() { records().clear(); } + +} // namespace dusk::interp::vertex diff --git a/src/dusk/interp/vertex.h b/src/dusk/interp/vertex.h new file mode 100644 index 0000000000..ce00a84c1c --- /dev/null +++ b/src/dusk/interp/vertex.h @@ -0,0 +1,16 @@ +#pragma once + +class J3DVertexBuffer; +class J3DDeformData; + +namespace dusk::interp::vertex { + +void capture(J3DVertexBuffer* buffer, const J3DDeformData* deformation); +void* positions(const J3DVertexBuffer* buffer, void* current); +void* normals(const J3DVertexBuffer* buffer, void* current); +void reset(const J3DVertexBuffer* buffer); +void invalidate(const J3DDeformData* deformation); +void prune(); +void clear(); + +} // namespace dusk::interp::vertex diff --git a/src/dusk/iso_validate.cpp b/src/dusk/iso_validate.cpp index 26fdef11e9..bd8e293805 100644 --- a/src/dusk/iso_validate.cpp +++ b/src/dusk/iso_validate.cpp @@ -41,11 +41,13 @@ constexpr auto AcceptedDiscs = std::to_array({ .expectedHash = borealis::disc::parse_xxh3_128("9ef597588b0035ca9e91b333fa9a8a7e"), }, { - .gameId = "RZDE01", .revision = 0, + .gameId = "RZDE01", + .revision = 0, .expectedHash = borealis::disc::parse_xxh3_128("b3d91fbea59e5c66934d04c01566728e"), }, { - .gameId = "RZDE01", .revision = 2, + .gameId = "RZDE01", + .revision = 2, .expectedHash = borealis::disc::parse_xxh3_128("c3ec420921a1b36d6ae43f576491d25c"), }, { @@ -103,11 +105,12 @@ Region region_from_game_id(std::string_view gameId) noexcept { return Region::NorthAmerica; } -void update_info(const borealis::disc::Result& result, DiscInfo& info) noexcept { +void update_info(const borealis::disc::Result& result, DiscInfo& info) { if (!result.metadata.gameId.empty()) { info.platform = result.metadata.platform; info.region = region_from_game_id(result.metadata.gameId); info.revision = result.metadata.revision; + info.gameId = result.metadata.gameId; } } diff --git a/src/dusk/iso_validate.hpp b/src/dusk/iso_validate.hpp index e5a8eac1ef..1975d30c4c 100644 --- a/src/dusk/iso_validate.hpp +++ b/src/dusk/iso_validate.hpp @@ -1,10 +1,11 @@ #ifndef DUSK_ISO_VALIDATE_HPP #define DUSK_ISO_VALIDATE_HPP -#include "dusk/settings.h" #include +#include "dusk/settings.h" #include +#include #include namespace dusk { @@ -38,7 +39,8 @@ using VerificationStatus = borealis::disc::Progress; struct DiscInfo { Platform platform = Platform::Unknown; Region region = Region::NorthAmerica; - std::uint8_t revision = 0; + uint8_t revision = 0; + std::string gameId; }; ValidationError inspect(const char* path, DiscInfo& info); diff --git a/src/dusk/livesplit.cpp b/src/dusk/livesplit.cpp index d583ef75fa..2bf01dada1 100644 --- a/src/dusk/livesplit.cpp +++ b/src/dusk/livesplit.cpp @@ -1,107 +1,80 @@ -#if _WIN32 -#include -#include -using socket_t = SOCKET; -static void closeSocket(socket_t s) { - LINGER li{1, 0}; - setsockopt(s, SOL_SOCKET, SO_LINGER, reinterpret_cast(&li), sizeof(li)); - closesocket(s); -} -static int socketError(socket_t s) { - int err = 0; - int len = sizeof(err); - getsockopt(s, SOL_SOCKET, SO_ERROR, reinterpret_cast(&err), &len); - return err; -} -static constexpr int kSendFlags = 0; -#else -#include -#include -#include -#include -#include -#include -#include -using socket_t = int; -static void closeSocket(socket_t s) { - struct linger li{1, 0}; - setsockopt(s, SOL_SOCKET, SO_LINGER, &li, sizeof(li)); - close(s); -} -static int socketError(socket_t s) { - int err = 0; - socklen_t len = sizeof(err); - getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len); - return err; -} -#ifndef INVALID_SOCKET -#define INVALID_SOCKET -1 -#endif - -#if defined(__APPLE__) -static constexpr int kSendFlags = 0; -#else -static constexpr int kSendFlags = MSG_NOSIGNAL; -#endif -#endif - -#include #include "dusk/livesplit.h" + +#include "borealis/net.hpp" + #include "f_op/f_op_overlap_mng.h" +#include +#include +#include +#include + namespace dusk::speedrun { +namespace { -static bool running = false; -static bool startPending = false; -static uint64_t frameCount = 0; -static socket_t sock = INVALID_SOCKET; -static bool wasLoading = false; -static bool connected = false; -static bool connectPending = false; -static bool disconnectPending = false; -static uint32_t idleProbeCounter = 0; -static uint32_t reconnectCounter = 0; -static char storedHost[64] = "127.0.0.1"; -static int storedPort = 16834; +bool running = false; +bool startPending = false; +uint64_t frameCount = 0; +bool wasLoading = false; +bool connected = false; +bool connectPending = false; +bool disconnectPending = false; +uint32_t reconnectCounter = 0; +std::string storedEndpoint = "tcp://127.0.0.1:16834"; +std::unique_ptr netContext; +borealis::net::SocketId socketId = 0; -static void sendCmd(const char* cmd) { - if (sock == INVALID_SOCKET) { +void send_cmd(const char* command) { + if (!netContext || !connected || socketId == 0) { return; } - char msg[64]; - const int len = snprintf(msg, sizeof(msg), "%s\r\n", cmd); - if (len <= 0 || len >= static_cast(sizeof(msg))) { + char message[64]; + const int length = snprintf(message, sizeof(message), "%s\r\n", command); + if (length <= 0 || length >= static_cast(sizeof(message))) { return; } - if (send(sock, msg, len, kSendFlags) >= 0) { - if (!connected) { - connected = connectPending = true; - } - return; - } - -#if _WIN32 - const int err = WSAGetLastError(); - if (err == WSAEWOULDBLOCK || err == WSAENOTCONN) { - return; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOTCONN) { - return; - } -#endif - - if (connected) { - disconnectPending = true; - } - closeSocket(sock); - sock = INVALID_SOCKET; - connected = connectPending = false; - reconnectCounter = 0; + const auto chars = std::span{message, static_cast(length)}; + netContext->send(socketId, std::as_bytes(chars)); } +void reconnect() { + netContext.reset(); + netContext = std::make_unique(); + connected = false; + connectPending = false; + socketId = netContext->connect(storedEndpoint); +} + +void poll_network() { + if (!netContext) { + return; + } + + borealis::net::Event event; + while (netContext->poll(event)) { + if (event.id != socketId) { + continue; + } + if (event.kind == borealis::net::Event::Kind::Connected) { + connected = true; + connectPending = true; + send_cmd("initgametime"); + } else if (event.kind == borealis::net::Event::Kind::Closed) { + if (connected) { + disconnectPending = true; + } + connected = false; + connectPending = false; + socketId = 0; + reconnectCounter = 0; + } + } +} + +} // namespace + uint64_t getFrameCount() { return frameCount; } @@ -111,10 +84,9 @@ void onGameFrame() { return; } - bool loading = fopOvlpM_IsDoingReq() != 0; - + const bool loading = fopOvlpM_IsDoingReq() != 0; if (loading != wasLoading) { - sendCmd(loading ? "pausegametime" : "unpausegametime"); + send_cmd(loading ? "pausegametime" : "unpausegametime"); wasLoading = loading; } @@ -141,172 +113,74 @@ void reset() { startPending = false; frameCount = 0; wasLoading = false; - sendCmd("reset"); -} - -static void reconnect() { - if (sock != INVALID_SOCKET) { - closeSocket(sock); - sock = INVALID_SOCKET; - } - connected = connectPending = false; - - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sock == INVALID_SOCKET) { - return; - } - -#if _WIN32 - u_long nb = 1; - if (ioctlsocket(sock, FIONBIO, &nb) != 0) { - closeSocket(sock); - sock = INVALID_SOCKET; - return; - } -#else - const int fl = fcntl(sock, F_GETFL, 0); - if (fl < 0 || fcntl(sock, F_SETFL, fl | O_NONBLOCK) < 0) { - closeSocket(sock); - sock = INVALID_SOCKET; - return; - } -#endif - -#if defined(__APPLE__) - { - int opt = 1; - setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt)); - } -#endif - - sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_port = htons(static_cast(storedPort)); - if (inet_pton(AF_INET, storedHost, &addr.sin_addr) != 1) { - closeSocket(sock); - sock = INVALID_SOCKET; - return; - } - - const int cr = connect(sock, reinterpret_cast(&addr), sizeof(addr)); -#if _WIN32 - const bool connectPending_ = cr < 0 && WSAGetLastError() == WSAEWOULDBLOCK; -#else - const bool connectPending_ = cr < 0 && errno == EINPROGRESS; -#endif - if (cr != 0 && !connectPending_) { - closeSocket(sock); - sock = INVALID_SOCKET; - } + send_cmd("reset"); } void connectLiveSplit(const char* host, int port) { -#if _WIN32 - WSADATA wd{}; - WSAStartup(MAKEWORD(2, 2), &wd); -#endif - snprintf(storedHost, sizeof(storedHost), "%s", host); - storedPort = port; + std::string endpointHost = host; + if (endpointHost.find(':') != std::string::npos && + !(endpointHost.starts_with('[') && endpointHost.ends_with(']'))) + { + endpointHost = '[' + endpointHost + ']'; + } + storedEndpoint = "tcp://" + endpointHost + ':' + std::to_string(port); reconnect(); } void disconnectLiveSplit() { - if (sock != INVALID_SOCKET) { - closeSocket(sock); - sock = INVALID_SOCKET; - } - connected = connectPending = disconnectPending = false; + netContext.reset(); + socketId = 0; + connected = false; + connectPending = false; + disconnectPending = false; } bool consumeConnectedEvent() { - bool v = connectPending; + const bool value = connectPending; connectPending = false; - return v; + return value; } + bool consumeDisconnectedEvent() { - bool v = disconnectPending; + const bool value = disconnectPending; disconnectPending = false; - return v; + return value; } void updateLiveSplit() { - if (sock == INVALID_SOCKET) { + poll_network(); + if (socketId == 0) { if ((reconnectCounter++ % 30) == 0) { reconnect(); } return; } - if (!connected) { - fd_set writefds, errorfds; - FD_ZERO(&writefds); - FD_ZERO(&errorfds); - FD_SET(sock, &writefds); - FD_SET(sock, &errorfds); - timeval tv{0, 0}; -#if _WIN32 - const int r = select(0, nullptr, &writefds, &errorfds, &tv); -#else - const int r = select(sock + 1, nullptr, &writefds, &errorfds, &tv); -#endif - if (r < 0 || FD_ISSET(sock, &errorfds) || socketError(sock) != 0) { - closeSocket(sock); - sock = INVALID_SOCKET; - reconnectCounter = 0; - return; - } - if (!FD_ISSET(sock, &writefds)) { - return; - } - sendCmd("initgametime"); return; } if (startPending) { startPending = false; - sendCmd("initgametime"); - sendCmd("reset"); - sendCmd("starttimer"); + send_cmd("initgametime"); + send_cmd("reset"); + send_cmd("starttimer"); } if (!running) { - if ((idleProbeCounter++ % 60) == 0) { - char buf; - const int r = recv(sock, &buf, 1, 0); - if (r == 0 -#if _WIN32 - || (r < 0 && WSAGetLastError() != WSAEWOULDBLOCK) -#else - || (r < 0 && errno != EAGAIN && errno != EWOULDBLOCK) -#endif - ) - { - if (connected) { - disconnectPending = true; - } - closeSocket(sock); - sock = INVALID_SOCKET; - connected = connectPending = false; - reconnectCounter = 0; - } - } return; } const uint64_t totalMs = frameCount * 1000 / 30; const uint64_t totalSec = totalMs / 1000; - char cmd[32]; - snprintf(cmd, sizeof(cmd), "setgametime %u:%02u:%02u.%03u", + char command[32]; + snprintf(command, sizeof(command), "setgametime %u:%02u:%02u.%03u", static_cast(totalSec / 3600), static_cast((totalSec / 60) % 60), static_cast(totalSec % 60), static_cast(totalMs % 1000)); - sendCmd(cmd); + send_cmd(command); } void shutdown() { disconnectLiveSplit(); -#if _WIN32 - WSACleanup(); -#endif } } // namespace dusk::speedrun diff --git a/src/dusk/logging.cpp b/src/dusk/logging.cpp index 1f3a0451b4..b85635f1a5 100644 --- a/src/dusk/logging.cpp +++ b/src/dusk/logging.cpp @@ -1,8 +1,8 @@ #include "dusk/logging.h" -#include +#include -#include "tracy/Tracy.hpp" +#include bool StubLogEnabled = true; diff --git a/src/dusk/mod_loader.hpp b/src/dusk/mod_loader.hpp index bf3e93aa53..3d5094ca70 100644 --- a/src/dusk/mod_loader.hpp +++ b/src/dusk/mod_loader.hpp @@ -1,15 +1,19 @@ #pragma once -#include -#include -#include -#include -#include -#include - #include "dusk/config.hpp" #include "dusk/config_var.hpp" +#include "dusk/mods/updates.hpp" #include "mods/api.h" +#include "mods/runtime.h" + +#include +#include +#include +#include +#include +#include +#include +#include namespace dusk::mods { struct LoadedMod; @@ -35,6 +39,7 @@ struct ModManifestInfo { struct Import { std::string id; uint16_t major = 0; + uint16_t minMinor = 0; bool required = false; bool operator==(const Import&) const = default; }; @@ -48,6 +53,19 @@ struct ModManifestInfo { bool operator==(const ModManifestInfo&) const = default; }; +struct DelegatedModRuntime { + std::string id; + uint16_t major = 0; + uint16_t minMinor = 0; + + const ModRuntimeService* service = nullptr; + ModContext* providerContext = nullptr; + + bool operator==(const DelegatedModRuntime& other) const { + return id == other.id && major == other.major && minMinor == other.minMinor; + } +}; + struct ModMetadata { std::string id; std::string name; @@ -67,6 +85,19 @@ struct ModSearchDir { std::filesystem::path nativeLibDir; }; +struct ModOperation { + enum class State : u8 { + Pending, + Succeeded, + Failed, + }; + + State state = State::Pending; + std::string message; +}; + +using ModOperationHandle = std::shared_ptr; + struct ModMetaParsed { uint32_t abiVersion = 0; std::vector imports; @@ -161,6 +192,14 @@ enum class NativeModStatus : u8 { }; struct LoadedMod { + struct FileIdentity { + std::uintmax_t size = 0; + std::filesystem::file_time_type modified{}; + bool valid = false; + + bool operator==(const FileIdentity&) const = default; + }; + ModMetadata metadata; std::filesystem::path modPath; std::filesystem::path dir; @@ -170,8 +209,12 @@ struct LoadedMod { std::string dataDirUtf8; uint32_t searchDirIndex = 0; - // Native lib is dlopen'd in place and stays resident for the session. Reload is unsupported. - bool inPlace = false; + bool fromDirectory = false; + // Native lib is dlopen'd in place. + bool nativeInPlace = false; + bool hasUserPackage = false; + bool hasBundledCopy = false; + FileIdentity fileIdentity; std::unique_ptr> cvarIsEnabled; config::Subscription enabledSubscription = 0; @@ -180,7 +223,7 @@ struct LoadedMod { bool loadFailed = false; std::string failureReason; - // mod_initialize succeeded; a mod_shutdown is owed on deactivation. + // Initialization succeeded; shutdown is owed on deactivation. bool initialized = false; // Static service exports are currently present in the registry. bool servicesRegistered = false; @@ -202,6 +245,7 @@ struct LoadedMod { NativeModStatus nativeStatus = NativeModStatus::None; std::unique_ptr native; + std::optional runtime; std::unique_ptr context; // Shared with overlay file registrations so in-flight DVD reads survive disable/reload. @@ -212,8 +256,15 @@ struct LoadedMod { // Mods this mod imports services from, and mods importing services from this mod. std::vector dependencies; std::vector dependents; + + [[nodiscard]] bool is_enabled() const { + return cvarIsEnabled != nullptr && cvarIsEnabled->getValue(); + } + [[nodiscard]] bool activation_failed() const { return loadFailed || (is_enabled() && !active); } }; +struct PackageCandidate; + class ModLoader { public: static ModLoader& instance(); @@ -226,9 +277,22 @@ public: void request_enable(std::string_view id); void request_disable(std::string_view id); - void request_reload(std::string_view id); + ModOperationHandle request_reload(std::string_view id); + ModOperationHandle request_install( + std::filesystem::path path, std::optional update = {}); + ModOperationHandle request_uninstall(std::string_view id); + ModOperationHandle request_reactivate(std::string_view id); void notify_mod_failure(LoadedMod& mod, bool firstFailure); + [[nodiscard]] std::filesystem::path user_mods_dir() const; + [[nodiscard]] bool can_uninstall(const LoadedMod& mod) const; + [[nodiscard]] bool can_update(const LoadedMod& mod) const; + [[nodiscard]] LoadedMod* find_mod(std::string_view id); + [[nodiscard]] const LoadedMod* find_mod(std::string_view id) const; + + [[nodiscard]] bool initialized() const noexcept { return m_startupComplete; } + [[nodiscard]] uint64_t generation() const noexcept { return m_generation; } + [[nodiscard]] auto mods() const { return m_mods | std::views::transform([](const auto& m) -> LoadedMod& { return *m; }); } @@ -238,10 +302,31 @@ public: } private: - enum class RequestKind : u8 { Enable, Disable, Reload }; - struct Request { + enum class LifecycleAction : u8 { Enable, Disable, Reactivate }; + struct LifecycleRequest { std::string modId; - RequestKind kind; + LifecycleAction action; + std::shared_ptr operation; + }; + struct InstallRequest { + std::filesystem::path stagedPath; + std::optional update; + std::shared_ptr operation; + }; + struct ReloadRequest { + std::string modId; + std::shared_ptr operation; + }; + struct UninstallRequest { + std::string modId; + std::shared_ptr operation; + }; + using Request = std::variant; + + struct OperationResult { + bool success = true; + std::string message; + LoadedMod* mod = nullptr; }; // 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 @@ -257,10 +342,12 @@ private: std::vector m_pendingRequests; std::vector m_pendingFailures; std::vector m_retiredNatives; + uint64_t m_generation = 0; bool m_initialized = false; bool m_startupComplete = false; - void try_load_mod(const std::filesystem::path& modPath, bool fromDir, uint32_t searchDirIndex); + LoadedMod* try_load_mod(const std::filesystem::path& modPath, bool fromDir, + uint32_t searchDirIndex, std::unique_ptr bundle = {}); void load_native(LoadedMod& mod, const std::string& dllEntry, const std::vector& runtimeEntries); bool load_native_if_present(LoadedMod& mod); @@ -279,20 +366,32 @@ private: [[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(); + [[nodiscard]] OperationResult install_staged( + const std::filesystem::path& path, const std::optional& update); + [[nodiscard]] OperationResult load_runtime_mod(const std::filesystem::path& path); + [[nodiscard]] OperationResult reload_runtime_mod( + LoadedMod& mod, const PackageCandidate* replacement = nullptr); + [[nodiscard]] OperationResult uninstall_runtime_mod(LoadedMod& mod); + [[nodiscard]] OperationResult runtime_result(LoadedMod& mod); + void forget_mod(LoadedMod& mod); 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); + void apply_lifecycle_change( + LoadedMod& target, bool reload, const PackageCandidate* replacement = nullptr); // `target` plus transitive active/suspended dependents, in m_mods (init) order. - std::vector collect_lifecycle_set(LoadedMod& target); + std::vector collect_lifecycle_set(LoadedMod& target) const; + void resume_lifecycle_set(const std::vector& mods); bool reload_bundle(LoadedMod& mod); bool ensure_native_loaded(LoadedMod& mod); }; +bool inspect_mod_bundle(const std::filesystem::path& path, ModMetadata& metadata, + std::string& error, bool* hasNative = nullptr) noexcept; + using ModIndex = std::ranges::range_difference_t().mods())>; } // namespace dusk::mods diff --git a/src/dusk/mods/catalog.cpp b/src/dusk/mods/catalog.cpp new file mode 100644 index 0000000000..c060cfdb61 --- /dev/null +++ b/src/dusk/mods/catalog.cpp @@ -0,0 +1,611 @@ +#include "catalog.hpp" + +#include "dusk/app_info.hpp" +#include "fmt/format.h" +#include "nlohmann/json.hpp" + +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +#include +#endif + +namespace dusk::mods::catalog { +namespace { + +using json = nlohmann::json; +using namespace std::chrono_literals; + +constexpr std::string_view apiUrl = + "https://staging.twilitrealm.workers.dev/api/v1/games/dusklight"; + +std::string_view sort_value(Sort sort) noexcept { + switch (sort) { + case Sort::Featured: + return "featured"; + case Sort::Endorsements: + return "endorsements"; + case Sort::Updated: + return "updated"; + case Sort::Newest: + return "newest"; + case Sort::Name: + return "name"; + case Sort::Downloads: + default: + return "downloads"; + } +} + +std::string_view catalog_platform() noexcept { +#if defined(_WIN32) && defined(_M_ARM64) + return "windows-arm64"; +#elif defined(_WIN32) && defined(_M_X64) + return "windows-amd64"; +#elif defined(__ANDROID__) && defined(__aarch64__) + return "android-aarch64"; +#elif defined(__APPLE__) && TARGET_OS_IOS + return "ios-arm64"; +#elif defined(__APPLE__) && !TARGET_OS_TV && defined(__aarch64__) + return "macos-arm64"; +#elif defined(__APPLE__) && !TARGET_OS_TV && defined(__x86_64__) + return "macos-x86_64"; +#elif defined(__linux__) && defined(__aarch64__) + return "linux-aarch64"; +#elif defined(__linux__) && defined(__x86_64__) + return "linux-x86_64"; +#else + // The catalog rejects platforms outside its published package matrix. + return {}; +#endif +} + +std::string url_encode(std::string_view value) { + constexpr char hex[] = "0123456789ABCDEF"; + std::string encoded; + encoded.reserve(value.size()); + for (const unsigned char c : value) { + const bool unreserved = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || + c == '~'; + if (unreserved) { + encoded.push_back(static_cast(c)); + } else { + encoded.push_back('%'); + encoded.push_back(hex[c >> 4]); + encoded.push_back(hex[c & 0x0f]); + } + } + return encoded; +} + +void append_query(std::string& url, std::string_view name, std::string_view value) { + fmt::format_to(std::back_inserter(url), "{}{}={}", + url.find('?') == std::string::npos ? '?' : '&', name, url_encode(value)); +} + +std::string make_url(const Query& query) { + std::string url{fmt::format("{}/mods", apiUrl)}; + if (!query.search.empty()) { + append_query(url, "q", query.search); + } + if (!query.category.empty()) { + append_query(url, "category", query.category); + } + append_query(url, "sort", sort_value(query.sort)); + append_query(url, "page", fmt::format("{}", std::max(query.page, 1))); + if (query.thisDevice) { + const auto platform = catalog_platform(); + if (!platform.empty()) { + append_query(url, "platform", platform); + } + } + return url; +} + +std::string make_detail_url(std::string_view id) { + return fmt::format("{}/{}", fmt::format("{}/mods", apiUrl), url_encode(id)); +} + +const json& required_field(const json& object, const char* name) { + if (!object.is_object()) { + throw std::runtime_error{"expected an object"}; + } + const auto iter = object.find(name); + if (iter == object.end()) { + throw std::runtime_error{fmt::format("missing field '{}'", name)}; + } + return *iter; +} + +const json& required_array(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (!value.is_array()) { + throw std::runtime_error{fmt::format("field '{}' is not an array", name)}; + } + return value; +} + +std::string required_string(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (!value.is_string()) { + throw std::runtime_error{fmt::format("field '{}' is not a string", name)}; + } + return value.get(); +} + +bool required_bool(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (!value.is_boolean()) { + throw std::runtime_error{fmt::format("field '{}' is not a boolean", name)}; + } + return value.get(); +} + +uint64_t required_count(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (value.is_number_unsigned()) { + return value.get(); + } + if (value.is_number_integer()) { + const auto count = value.get(); + if (count >= 0) { + return static_cast(count); + } + } + throw std::runtime_error{fmt::format("field '{}' is not a non-negative integer", name)}; +} + +int required_int(const json& object, const char* name) { + const uint64_t value = required_count(object, name); + if (value > static_cast(std::numeric_limits::max())) { + throw std::runtime_error{fmt::format("field '{}' is too large", name)}; + } + return static_cast(value); +} + +std::optional optional_string(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (value.is_null()) { + return std::nullopt; + } + if (!value.is_string()) { + throw std::runtime_error{fmt::format("field '{}' is not a string or null", name)}; + } + return value.get(); +} + +uint16_t required_u16(const json& object, const char* name) { + const auto value = required_count(object, name); + if (value > std::numeric_limits::max()) { + throw std::runtime_error{fmt::format("field '{}' is too large", name)}; + } + return static_cast(value); +} + +std::optional optional_u32(const json& object, const char* name) { + if (required_field(object, name).is_null()) { + return std::nullopt; + } + const auto value = required_count(object, name); + if (value > std::numeric_limits::max()) { + throw std::runtime_error{fmt::format("field '{}' is too large", name)}; + } + return static_cast(value); +} + +Download parse_download(const json& value) { + return { + .url = required_string(value, "url"), + .sha256 = required_string(value, "sha256"), + .size = required_count(value, "size"), + }; +} + +std::vector parse_service_imports(const json& object) { + const auto& imports = required_array(object, "service_imports"); + std::vector result; + result.reserve(imports.size()); + for (const auto& service : imports) { + result.push_back({ + .id = required_string(service, "id"), + .major = required_u16(service, "major"), + .minMinor = required_u16(service, "min_minor"), + .optional = required_bool(service, "optional"), + }); + } + return result; +} + +Image parse_image(const json& value) { + const auto width = required_count(value, "width"); + const auto height = required_count(value, "height"); + if (width > std::numeric_limits::max() || + height > std::numeric_limits::max()) + { + throw std::runtime_error{"image dimensions are too large"}; + } + Image image{ + .width = static_cast(width), + .height = static_cast(height), + }; + const auto& sources = required_array(value, "sources"); + image.sources.reserve(sources.size()); + for (const auto& source : sources) { + const auto sourceWidth = required_count(source, "width"); + if (sourceWidth > std::numeric_limits::max()) { + throw std::runtime_error{"image source width is too large"}; + } + image.sources.push_back({ + .width = static_cast(sourceWidth), + .pngUrl = required_string(source, "png_url"), + }); + } + if (image.sources.empty()) { + throw std::runtime_error{"image has no sources"}; + } + return image; +} + +Category parse_category(const json& value) { + return { + .slug = required_string(value, "slug"), + .name = required_string(value, "name"), + .modCount = required_count(value, "mod_count"), + }; +} + +Category parse_mod_category(const json& value) { + return { + .slug = required_string(value, "slug"), + .name = required_string(value, "name"), + }; +} + +Tag parse_tag(const json& value) { + return { + .slug = required_string(value, "slug"), + .name = required_string(value, "name"), + }; +} + +Author parse_author(const json& value) { + return { + .name = required_string(value, "name"), + .handle = required_string(value, "handle"), + .official = required_bool(value, "official"), + }; +} + +Mod parse_mod(const json& value) { + Mod mod{ + .id = required_string(value, "id"), + .name = required_string(value, "name"), + .version = required_string(value, "version"), + .author = parse_author(required_field(value, "author")), + .summary = required_string(value, "summary"), + .downloads = required_count(value, "downloads"), + .endorsements = required_count(value, "endorsements"), + .publishedAt = required_string(value, "published_at"), + .updatedAt = required_string(value, "updated_at"), + .packageSize = required_count(value, "package_size"), + .containsNativeCode = required_bool(value, "contains_native_code"), + }; + + const auto& category = required_field(value, "category"); + if (!category.is_null()) { + mod.category = parse_mod_category(category); + } + + const auto& tags = required_array(value, "tags"); + mod.tags.reserve(tags.size()); + for (const auto& tag : tags) { + mod.tags.push_back(parse_tag(tag)); + } + + const auto& platforms = required_array(value, "supported_platforms"); + mod.supportedPlatforms.reserve(platforms.size()); + for (const auto& platform : platforms) { + if (!platform.is_string()) { + throw std::runtime_error{"supported platform is not a string"}; + } + mod.supportedPlatforms.push_back(platform.get()); + } + + const auto& icon = required_field(value, "icon"); + if (!icon.is_null()) { + mod.icon = parse_image(icon); + } + const auto& banner = required_field(value, "banner"); + if (!banner.is_null()) { + mod.banner = parse_image(banner); + } + return mod; +} + +Detail parse_detail(std::string_view body) { + const json root = json::parse(body); + Detail detail{ + .mod = parse_mod(root), + .siteUrl = required_string(root, "site_url"), + .sourceUrl = optional_string(root, "source_url"), + .license = optional_string(root, "license"), + .descriptionHtml = required_string(root, "description_html"), + .changelogHtml = required_string(root, "changelog_html"), + .download = parse_download(required_field(root, "download")), + .modAbi = optional_u32(root, "mod_abi"), + }; + + const auto& screenshots = required_array(root, "screenshots"); + detail.screenshots.reserve(screenshots.size()); + for (const auto& screenshot : screenshots) { + detail.screenshots.push_back({ + .altText = required_string(screenshot, "alt_text"), + .image = parse_image(required_field(screenshot, "image")), + }); + } + + detail.serviceImports = parse_service_imports(root); + return detail; +} + +Page parse_page(std::string_view body) { + const json root = json::parse(body); + const auto& game = required_field(root, "game"); + if (required_string(game, "id") != "dusklight") { + throw std::runtime_error{"catalog response is for a different game"}; + } + + Page page; + const auto& categories = required_array(root, "categories"); + page.categories.reserve(categories.size()); + for (const auto& category : categories) { + page.categories.push_back(parse_category(category)); + } + + const auto& mods = required_array(root, "mods"); + page.mods.reserve(mods.size()); + for (const auto& mod : mods) { + page.mods.push_back(parse_mod(mod)); + } + + const auto& pagination = required_field(root, "pagination"); + page.pagination = { + .page = required_int(pagination, "page"), + .pageSize = required_int(pagination, "page_size"), + .pageCount = required_int(pagination, "page_count"), + .total = required_count(pagination, "total"), + }; + return page; +} + +std::string api_error(const borealis::http::Response& response) { + try { + const auto body = json::parse(response.body); + const auto& error = required_field(body, "error"); + return required_string(error, "message"); + } catch (...) { + return fmt::format("The catalog returned HTTP {}.", response.statusCode); + } +} + +FetchResult finish_request(borealis::http::Result result) { + if (result.error != borealis::http::Error::None) { + return {.error = result.message.empty() ? "The catalog request failed." : + std::move(result.message)}; + } + if (result.response.statusCode != 200) { + return {.error = api_error(result.response)}; + } + try { + return {.page = parse_page(result.response.body)}; + } catch (const std::exception& exception) { + return {.error = fmt::format("The catalog response was invalid: {}", exception.what())}; + } catch (...) { + return {.error = "The catalog response was invalid."}; + } +} + +DetailFetchResult finish_detail_request(borealis::http::Result result) { + if (result.error != borealis::http::Error::None) { + return {.error = + result.message.empty() ? "The mod request failed." : std::move(result.message)}; + } + if (result.response.statusCode != 200) { + return {.error = api_error(result.response)}; + } + try { + return {.detail = parse_detail(result.response.body)}; + } catch (const std::exception& exception) { + return {.error = fmt::format("The mod response was invalid: {}", exception.what())}; + } catch (...) { + return {.error = "The mod response was invalid."}; + } +} + +borealis::http::Request make_request(std::string url) { + return { + .url = std::move(url), + .headers = + { + {.name = "User-Agent", .value = borealis::user_agent(dusk::AppInfo)}, + {.name = "Accept", .value = "application/json"}, + }, + .connectTimeout = 10s, + .idleTimeout = 10s, + .totalTimeout = 20s, + }; +} + +} // namespace + +borealis::Task fetch_page(Query query) { + return borealis::http::start(make_request(make_url(query))).map(finish_request); +} + +borealis::Task fetch_detail(std::string id) { + return borealis::http::start(make_request(make_detail_url(id))).map(finish_detail_request); +} + +std::string_view platform() noexcept { + return catalog_platform(); +} + +borealis::Task fetch_updates( + UpdateEnvironment environment, std::vector targets) { + json platformValue = environment.platform; + if (environment.platform.empty()) { + platformValue = nullptr; + } + json body{ + {"app_version", BOREALIS_APP_VERSION}, + {"platform", platformValue}, + {"mod_abi", environment.abi}, + {"targets", targets}, + {"services", json::array()}, + {"mods", json::array()}, + }; + for (const auto& service : environment.services) { + json provider = service.providerId; + if (service.providerId.empty()) { + provider = nullptr; + } + body["services"].push_back({ + {"id", service.id}, + {"major", service.major}, + {"minor", service.minor}, + {"provider_mod_id", provider}, + }); + } + for (const auto& mod : environment.mods) { + json imports = json::array(); + for (const auto& service : mod.imports) { + imports.push_back({ + {"id", service.id}, + {"major", service.major}, + {"min_minor", service.minMinor}, + }); + } + body["mods"].push_back({ + {"id", mod.id}, + {"version", mod.version}, + {"enabled", mod.enabled}, + {"required_imports", std::move(imports)}, + }); + } + auto request = make_request(fmt::format("{}/update-check", apiUrl)); + request.method = borealis::http::Method::Post; + request.headers.push_back({"Content-Type", "application/json"}); + request.body = body.dump(); + request.maxBodyBytes = 8 * 1024 * 1024; + return borealis::http::start(std::move(request)) + .map([environment = std::move(environment), targets = std::move(targets)]( + borealis::http::Result result) -> UpdateFetchResult { + if (result.error != borealis::http::Error::None) { + return { + .error = + result.message.empty() ? "Could not check mod updates." : result.message, + .retryable = result.error == borealis::http::Error::Network || + result.error == borealis::http::Error::Timeout, + }; + } + if (result.response.statusCode != 200) { + int retryAfter = 0; + for (const auto& header : result.response.headers) { + if (header.name == "Retry-After" || header.name == "retry-after") { + try { + retryAfter = std::clamp(std::stoi(header.value), 0, 3600); + } catch (...) { + } + } + } + return { + .error = api_error(result.response), + .retryable = + result.response.statusCode == 429 || result.response.statusCode >= 500, + .retryAfter = retryAfter, + }; + } + try { + const auto root = json::parse(result.response.body); + const auto& rows = required_field(root, "mods"); + if (!rows.is_array() || rows.size() != targets.size()) { + throw std::runtime_error{"Incomplete update response"}; + } + std::vector updates; + for (const auto& row : rows) { + ModUpdate update{ + .id = required_string(row, "id"), + .installedVersion = required_string(row, "installed_version"), + .published = required_bool(row, "published"), + .yankedInstalled = required_bool(row, "yanked_installed"), + }; + const auto installed = + std::ranges::find(environment.mods, update.id, &InstalledPackage::id); + if (std::ranges::find(targets, update.id) == targets.end() || + std::ranges::find(updates, update.id, &ModUpdate::id) != updates.end() || + installed == environment.mods.end() || + installed->version != update.installedVersion) + { + throw std::runtime_error{ + "Update response does not match the installed inventory"}; + } + const auto& latest = required_field(row, "latest"); + if (!latest.is_null()) { + update.latestVersion = required_string(latest, "version"); + const auto& blockers = required_field(latest, "blockers"); + if (!blockers.is_array()) { + throw std::runtime_error{"Invalid blockers"}; + } + for (const auto& blocker : blockers) { + update.blockers.push_back(required_string(blocker, "message")); + } + } + const auto& target = required_field(row, "latest_compatible"); + if (!target.is_null()) { + UpdateTarget parsed; + parsed.version = required_string(target, "version"); + parsed.changelogHtml = required_string(target, "changelog_html"); + parsed.download = parse_download(required_field(target, "download")); + auto& compatibility = parsed.compatibility; + compatibility.containsNativeCode = + required_bool(target, "contains_native_code"); + compatibility.platforms = required_field(target, "supported_platforms") + .get>(); + compatibility.abi = optional_u32(target, "mod_abi"); + compatibility.imports = parse_service_imports(target); + const auto& exports = required_array(target, "service_exports"); + for (const auto& service : exports) { + compatibility.exports.push_back({ + required_string(service, "id"), + required_u16(service, "major"), + required_u16(service, "minor"), + {}, + }); + } + if (!parsed.download.url.starts_with("https://") || + parsed.download.size == 0 || parsed.download.sha256.size() != 64 || + !std::ranges::all_of(parsed.download.sha256, + [](char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); + })) + { + throw std::runtime_error{"Invalid update download"}; + } + update.target = std::move(parsed); + } + updates.push_back(std::move(update)); + } + return {.updates = std::move(updates)}; + } catch (const std::exception& error) { + return {.error = fmt::format("Invalid mod update response: {}", error.what())}; + } + }); +} + +} // namespace dusk::mods::catalog diff --git a/src/dusk/mods/catalog.hpp b/src/dusk/mods/catalog.hpp new file mode 100644 index 0000000000..4b90cb58aa --- /dev/null +++ b/src/dusk/mods/catalog.hpp @@ -0,0 +1,136 @@ +#pragma once + +#include "updates.hpp" + +#include + +#include +#include +#include +#include + +namespace dusk::mods::catalog { + +enum class Sort { + Featured, + Downloads, + Endorsements, + Updated, + Newest, + Name, +}; + +struct Query { + std::string search; + std::string category; + Sort sort = Sort::Featured; + int page = 1; + bool thisDevice = true; +}; + +struct Category { + std::string slug; + std::string name; + uint64_t modCount = 0; +}; + +struct Tag { + std::string slug; + std::string name; +}; + +struct Author { + std::string name; + std::string handle; + bool official = false; +}; + +struct ImageSource { + uint32_t width = 0; + std::string pngUrl; +}; + +struct Image { + uint32_t width = 0; + uint32_t height = 0; + std::vector sources; +}; + +struct Mod { + std::string id; + std::string name; + std::string version; + Author author; + std::string summary; + std::optional category; + std::vector tags; + uint64_t downloads = 0; + uint64_t endorsements = 0; + std::string publishedAt; + std::string updatedAt; + uint64_t packageSize = 0; + bool containsNativeCode = false; + std::vector supportedPlatforms; + std::optional icon; + std::optional banner; +}; + +struct Screenshot { + std::string altText; + Image image; +}; + +struct Detail { + Mod mod; + std::string siteUrl; + std::optional sourceUrl; + std::optional license; + std::string descriptionHtml; + std::string changelogHtml; + Download download; + std::optional modAbi; + std::vector screenshots; + std::vector serviceImports; +}; + +struct Pagination { + int page = 1; + int pageSize = 0; + int pageCount = 0; + uint64_t total = 0; +}; + +struct Page { + std::vector categories; + std::vector mods; + Pagination pagination; +}; + +struct FetchResult { + std::optional page; + std::string error; +}; + +struct DetailFetchResult { + std::optional detail; + std::string error; +}; + +struct UpdateFetchResult { + std::optional> updates; + std::string error; + bool retryable = false; + int retryAfter = 0; +}; + +std::string_view platform() noexcept; +borealis::Task fetch_updates( + UpdateEnvironment environment, std::vector targets); + +/** Fetches one filtered page from the Dusklight catalog. */ +borealis::Task fetch_page(Query query); + +/** Fetches the full catalog record for one mod. */ +borealis::Task fetch_detail(std::string id); + +} // namespace dusk::mods::catalog diff --git a/src/dusk/mods/item_checks.cpp b/src/dusk/mods/item_checks.cpp index dc0a66d209..221654ea07 100644 --- a/src/dusk/mods/item_checks.cpp +++ b/src/dusk/mods/item_checks.cpp @@ -3,17 +3,16 @@ #include "dusk/mod_loader.hpp" #include "dusk/mods/loader/loader.hpp" #include "dusk/mods/svc/item.hpp" - -#include "aurora/lib/logging.hpp" -#include "d/d_com_inf_game.h" -#include "d/d_item_data.h" #include "mods/items.h" +#include "d/d_com_inf_game.h" +#include "d/d_item_data.h" + +#include #include #include #include -#include #include #include #include diff --git a/src/dusk/mods/item_gives.cpp b/src/dusk/mods/item_gives.cpp index 8feb0a7847..7a0ed3f6b1 100644 --- a/src/dusk/mods/item_gives.cpp +++ b/src/dusk/mods/item_gives.cpp @@ -4,13 +4,13 @@ #include "dusk/mods/loader/loader.hpp" #include "dusk/mods/svc/item.hpp" -#include "aurora/lib/logging.hpp" #include "d/actor/d_a_alink.h" #include "d/d_com_inf_game.h" #include "d/d_item.h" #include "d/d_item_data.h" #include "f_op/f_op_actor_mng.h" +#include #include #include diff --git a/src/dusk/mods/loader/bundle_disk.cpp b/src/dusk/mods/loader/bundle_disk.cpp index c9903b9b38..a94044e596 100644 --- a/src/dusk/mods/loader/bundle_disk.cpp +++ b/src/dusk/mods/loader/bundle_disk.cpp @@ -58,4 +58,12 @@ std::filesystem::path ModBundleDisk::toRealPath(const std::string& fileName) con return root_path / filePath; } +bool ModBundleDisk::file_exists(const std::string& fileName) { + return std::filesystem::is_regular_file(toRealPath(fileName)); +} + +bool ModBundleDisk::directory_exists(const std::string& fileName) { + return std::filesystem::is_directory(toRealPath(fileName)); +} + } // namespace dusk::mods diff --git a/src/dusk/mods/loader/bundle_zip.cpp b/src/dusk/mods/loader/bundle_zip.cpp index 517c5943c2..6c3e798952 100644 --- a/src/dusk/mods/loader/bundle_zip.cpp +++ b/src/dusk/mods/loader/bundle_zip.cpp @@ -1,68 +1,33 @@ -#include "fmt/format.h" #include "loader.hpp" -#include +#include namespace dusk::mods { -ModBundleZip::ModBundleZip(std::vector&& data) : zip_data(std::move(data)) { - if (!mz_zip_reader_init_mem(&res_zip, zip_data.data(), zip_data.size(), 0)) { - const auto error = mz_zip_get_last_error(&res_zip); - throw std::runtime_error( - fmt::format("Opening zip failed: {}", mz_zip_get_error_string(error))); +ModBundleZip::ModBundleZip(const std::filesystem::path& path) : m_archive{path} { + if (m_archive.package_format() != archive::PackageFormat::Mod) { + throw std::runtime_error("Archive is not a valid mod package"); } } -ModBundleZip::~ModBundleZip() { - mz_zip_reader_end(&res_zip); -} - std::vector ModBundleZip::readFile(const std::string& fileName) { - std::lock_guard lock{m_mutex}; - size_t size; - const auto ptr = mz_zip_reader_extract_file_to_heap(&res_zip, fileName.c_str(), &size, 0); - - if (!ptr) { - throw std::runtime_error(fmt::format("File does not exist: {}", fileName)); - } - - std::span data(static_cast(ptr), size); - std::vector vec(data.begin(), data.end()); - - mz_free(ptr); - - return vec; + return m_archive.read_file(fileName); } std::vector ModBundleZip::getFileNames() { - std::lock_guard lock{m_mutex}; - std::vector results; - - for (mz_uint i = 0, n = mz_zip_reader_get_num_files(&res_zip); i < n; ++i) { - mz_zip_archive_file_stat stat{}; - if (!mz_zip_reader_file_stat(&res_zip, i, &stat)) { - continue; - } - if (mz_zip_reader_is_file_a_directory(&res_zip, i)) { - continue; - } - - results.emplace_back(stat.m_filename); - } - - return results; + return m_archive.file_names(); } size_t ModBundleZip::getFileSize(const std::string& fileName) { - std::lock_guard lock{m_mutex}; - const auto idx = mz_zip_reader_locate_file(&res_zip, fileName.c_str(), nullptr, 0); - if (idx < 0) { - throw std::runtime_error(fmt::format("Unable to locate file in zip: {}", fileName)); - } + return m_archive.file_size(fileName); +} - mz_zip_archive_file_stat stat{}; - mz_zip_reader_file_stat(&res_zip, idx, &stat); - return stat.m_uncomp_size; +bool ModBundleZip::file_exists(const std::string& fileName) { + return m_archive.file_exists(fileName); +} + +bool ModBundleZip::directory_exists(const std::string& fileName) { + return m_archive.directory_exists(fileName); } } // namespace dusk::mods diff --git a/src/dusk/mods/loader/code_patch_macos.cpp b/src/dusk/mods/loader/code_patch_macos.cpp new file mode 100644 index 0000000000..64a7b4a606 --- /dev/null +++ b/src/dusk/mods/loader/code_patch_macos.cpp @@ -0,0 +1,223 @@ +#include "code_patch_macos.hpp" + +#include +#include +#include +#include +#include +#include + +#define PATCH_CODE \ + __attribute__((section("__TEXT,__code_patch,regular,pure_instructions"), noinline)) + +extern const char kPatchBegin[] asm("section$start$__TEXT$__code_patch"); +extern const char kPatchEnd[] asm("section$end$__TEXT$__code_patch"); + +namespace { + +constexpr unsigned kMaxThreads = 1024; +constexpr unsigned kMaxAttempts = 16; +constexpr size_t kMaxPatchSize = 16; +pthread_mutex_t sPatchMutex = PTHREAD_MUTEX_INITIALIZER; + +struct ThreadList { + thread_act_array_t threads = nullptr; + mach_msg_type_number_t count = 0; +}; + +PATCH_CODE void release_threads(ThreadList& list) { + for (unsigned i = 0; i < list.count; ++i) { + mach_port_deallocate(mach_task_self(), list.threads[i]); + } + if (list.threads != nullptr) { + vm_deallocate(mach_task_self(), reinterpret_cast(list.threads), + list.count * sizeof(thread_t)); + } +} + +PATCH_CODE kern_return_t read_pc(thread_t thread, uintptr_t& pc) { +#if defined(__aarch64__) + arm_thread_state64_t state{}; + mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; + const auto result = thread_get_state( + thread, ARM_THREAD_STATE64, reinterpret_cast(&state), &count); + pc = arm_thread_state64_get_pc(state); +#elif defined(__x86_64__) + x86_thread_state64_t state{}; + mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; + const auto result = thread_get_state( + thread, x86_THREAD_STATE64, reinterpret_cast(&state), &count); + pc = state.__rip; +#else +#error Unsupported macOS architecture +#endif + return result; +} + +PATCH_CODE __attribute__((aligned(16384))) kern_return_t commit_patch(uintptr_t target, + const unsigned char* expected, const unsigned char* replacement, size_t size, uintptr_t page, + size_t pageSize, thread_t currentThread) { + ThreadList initial; + auto result = task_threads(mach_task_self(), &initial.threads, &initial.count); + if (result != KERN_SUCCESS) { + return result; + } + thread_t suspended[kMaxThreads]; + unsigned suspendedCount = 0; + if (initial.count > kMaxThreads) { + release_threads(initial); + return KERN_RESOURCE_SHORTAGE; + } + + for (unsigned i = 0; i < initial.count; ++i) { + const auto thread = initial.threads[i]; + if (thread == currentThread) { + continue; + } + result = thread_suspend(thread); + if (result != KERN_SUCCESS) { + break; + } + suspended[suspendedCount++] = thread; + uintptr_t pc = 0; + result = read_pc(thread, pc); + if (result != KERN_SUCCESS) { + break; + } + if (pc >= target && pc < target + size) { + result = KERN_ABORTED; + break; + } + } + + if (result == KERN_SUCCESS) { + ThreadList current; + result = task_threads(mach_task_self(), ¤t.threads, ¤t.count); + if (result == KERN_SUCCESS) { + for (unsigned i = 0; i < current.count; ++i) { + bool known = false; + for (unsigned j = 0; j < initial.count; ++j) { + known |= current.threads[i] == initial.threads[j]; + } + if (!known) { + result = KERN_ABORTED; + break; + } + } + } + release_threads(current); + } + + if (result == KERN_SUCCESS) { + const auto* bytes = reinterpret_cast(target); + for (size_t i = 0; i < size; ++i) { + if (bytes[i] != expected[i]) { + result = KERN_INVALID_VALUE; + break; + } + } + } + + if (result == KERN_SUCCESS) { + result = mach_vm_protect( + mach_task_self(), page, pageSize, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); + if (result == KERN_SUCCESS) { + auto* bytes = reinterpret_cast(target); + for (size_t i = 0; i < size; ++i) { + bytes[i] = replacement[i]; + } + sys_icache_invalidate(reinterpret_cast(target), size); + result = mach_vm_protect( + mach_task_self(), page, pageSize, false, VM_PROT_READ | VM_PROT_EXECUTE); + if (result != KERN_SUCCESS) { + for (size_t i = 0; i < size; ++i) { + bytes[i] = expected[i]; + } + sys_icache_invalidate(reinterpret_cast(target), size); + if (mach_vm_protect(mach_task_self(), page, pageSize, false, + VM_PROT_READ | VM_PROT_EXECUTE) != KERN_SUCCESS) + { + __builtin_trap(); // Can't resume into non-executable code + } + } + } + } + + for (unsigned i = 0; i < suspendedCount; ++i) { + if (thread_resume(suspended[i]) != KERN_SUCCESS) { + __builtin_trap(); + } + } + release_threads(initial); + return result; +} + +} // namespace + +extern "C" int commit_code_patch( + void* targetPointer, const void* expected, const void* replacement, size_t size) { + if (targetPointer == nullptr || expected == nullptr || replacement == nullptr || size == 0 || + size > kMaxPatchSize) + { + return KERN_INVALID_ARGUMENT; + } + const auto target = reinterpret_cast(targetPointer); + const size_t pageSize = vm_page_size; + if (target > UINTPTR_MAX - size - pageSize) { + return KERN_INVALID_ADDRESS; + } + const auto page = target & ~(pageSize - 1); + const size_t length = ((target + size + pageSize - 1) & ~(pageSize - 1)) - page; + if (page < reinterpret_cast(kPatchEnd) && + page + length > reinterpret_cast(kPatchBegin)) + { + return KERN_PROTECTION_FAILURE; + } + + unsigned char oldCode[kMaxPatchSize]; + unsigned char newCode[kMaxPatchSize]; + for (size_t i = 0; i < size; ++i) { + oldCode[i] = static_cast(expected)[i]; + newCode[i] = static_cast(replacement)[i]; + } + + pthread_mutex_lock(&sPatchMutex); + mach_vm_address_t region = page; + mach_vm_size_t regionSize = 0; + vm_region_basic_info_data_64_t info{}; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object = MACH_PORT_NULL; + auto result = mach_vm_region(mach_task_self(), ®ion, ®ionSize, VM_REGION_BASIC_INFO_64, + reinterpret_cast(&info), &count, &object); + if (object != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), object); + } + if (result == KERN_SUCCESS && (region > page || regionSize < page + length - region || + info.protection != (VM_PROT_READ | VM_PROT_EXECUTE))) + { + result = KERN_PROTECTION_FAILURE; + } + if (result == KERN_SUCCESS) { + const auto currentThread = mach_thread_self(); + uintptr_t pc = 0; + read_pc(currentThread, pc); + thread_suspend(MACH_PORT_NULL); + thread_resume(MACH_PORT_NULL); + vm_deallocate(mach_task_self(), 0, 0); + mach_port_deallocate(mach_task_self(), MACH_PORT_NULL); + sys_icache_invalidate(targetPointer, size); + result = mach_vm_protect(mach_task_self(), page, length, false, info.protection); + if (result == KERN_SUCCESS) { + for (unsigned attempt = 0; attempt < kMaxAttempts; ++attempt) { + result = commit_patch(target, oldCode, newCode, size, page, length, currentThread); + if (result != KERN_ABORTED || attempt + 1 == kMaxAttempts) { + break; + } + usleep(1000); + } + } + mach_port_deallocate(mach_task_self(), currentThread); + } + pthread_mutex_unlock(&sPatchMutex); + return result; +} diff --git a/src/dusk/mods/loader/code_patch_macos.hpp b/src/dusk/mods/loader/code_patch_macos.hpp new file mode 100644 index 0000000000..540e806404 --- /dev/null +++ b/src/dusk/mods/loader/code_patch_macos.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int commit_code_patch(void* target, const void* expected, const void* replacement, size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/src/dusk/mods/loader/context.cpp b/src/dusk/mods/loader/context.cpp index 548f0bb06f..c3d4fe8e23 100644 --- a/src/dusk/mods/loader/context.cpp +++ b/src/dusk/mods/loader/context.cpp @@ -18,28 +18,6 @@ const char* mod_id_from_context(ModContext* context) { return mod != nullptr ? mod->metadata.id.c_str() : "mod"; } -bool is_safe_resource_path(std::string_view path) { - if (path.empty() || path.starts_with('/') || path.starts_with('\\') || - path.find(':') != std::string_view::npos) - { - return false; - } - - while (!path.empty()) { - const auto slash = path.find_first_of("/\\"); - const auto segment = path.substr(0, slash); - if (segment.empty() || segment == "." || segment == "..") { - return false; - } - if (slash == std::string_view::npos) { - break; - } - path.remove_prefix(slash + 1); - } - - return true; -} - void fail_mod(LoadedMod& mod, ModResult code, std::string_view message) { const bool firstFailure = !mod.loadFailed; mod.active = false; diff --git a/src/dusk/mods/loader/loader.cpp b/src/dusk/mods/loader/loader.cpp index 83c7660410..16e52f12da 100644 --- a/src/dusk/mods/loader/loader.cpp +++ b/src/dusk/mods/loader/loader.cpp @@ -1,481 +1,81 @@ #include "loader.hpp" + +#include "depgraph.hpp" +#include "manifest.hpp" +#include "native_module.hpp" +#include "natives.hpp" +#include "packages.hpp" +#if DUSK_HAS_PREPATCH +#include "prepatch.hpp" +#endif + +#include "dusk/config.hpp" +#include "dusk/data.hpp" #include "dusk/logging.h" #include "dusk/mod_loader.hpp" +#include "dusk/mods/log_buffer.hpp" +#include "dusk/mods/manifest.hpp" +#include "dusk/mods/queue.hpp" +#include "dusk/mods/svc/config.hpp" +#include "dusk/mods/svc/hook.hpp" +#include "dusk/mods/svc/registry.hpp" +#include "dusk/mods/updates.hpp" +#include "dusk/ui/mod_texture_provider.hpp" +#include "dusk/ui/mods_window.hpp" +#include "dusk/ui/ui.hpp" +#include "dusk/utilities.hpp" #include +#include +#include #include #include -#include #include -#include #include #include #include #include -#include "../manifest.hpp" -#include "depgraph.hpp" -#include "dusk/config.hpp" -#include "dusk/data.hpp" -#include "dusk/io.hpp" -#include "dusk/mods/log_buffer.hpp" -#include "dusk/mods/svc/config.hpp" -#include "dusk/mods/svc/hook.hpp" -#include "dusk/mods/svc/registry.hpp" -#include "dusk/ui/mods_window.hpp" -#include "dusk/ui/ui.hpp" -#include "miniz.h" -#include "native_module.hpp" -#include "nlohmann/json.hpp" -#if DUSK_HAS_PREPATCH -#include "prepatch.hpp" -#endif - -using namespace std::string_literals; -using namespace std::string_view_literals; - -#if defined(_WIN32) -#if defined(_M_ARM64) -static constexpr std::string_view k_nativePlatform = "windows-arm64"sv; -#elif defined(_M_X64) -static constexpr std::string_view k_nativePlatform = "windows-amd64"sv; -#elif defined(_M_IX86) -static constexpr std::string_view k_nativePlatform = "windows-x86"sv; -#else -static constexpr std::string_view k_nativePlatform = ""sv; -#endif -static constexpr std::string_view k_nativeLibName = "mod.dll"sv; -#elif defined(__ANDROID__) -#if defined(__aarch64__) -static constexpr std::string_view k_nativePlatform = "android-aarch64"sv; -#elif defined(__x86_64__) -static constexpr std::string_view k_nativePlatform = "android-x86_64"sv; -#else -static constexpr std::string_view k_nativePlatform = ""sv; -#endif -static constexpr std::string_view k_nativeLibName = "mod.so"sv; -#elif defined(__APPLE__) -#include -#if TARGET_OS_IOS -static constexpr std::string_view k_nativePlatform = "ios-arm64"sv; -#elif TARGET_OS_TV -static constexpr std::string_view k_nativePlatform = "tvos-arm64"sv; -#elif defined(__aarch64__) -static constexpr std::string_view k_nativePlatform = "macos-arm64"sv; -#elif defined(__x86_64__) -static constexpr std::string_view k_nativePlatform = "macos-x86_64"sv; -#else -static constexpr std::string_view k_nativePlatform = ""sv; -#endif -static constexpr std::string_view k_nativeLibName = "mod.so"sv; -#elif defined(__linux__) -#if defined(__aarch64__) -static constexpr std::string_view k_nativePlatform = "linux-aarch64"sv; -#elif defined(__x86_64__) -static constexpr std::string_view k_nativePlatform = "linux-x86_64"sv; -#elif defined(__i386__) -static constexpr std::string_view k_nativePlatform = "linux-x86"sv; -#else -static constexpr std::string_view k_nativePlatform = ""sv; -#endif -static constexpr std::string_view k_nativeLibName = "mod.so"sv; -#else -static constexpr std::string_view k_nativePlatform = ""sv; -static constexpr std::string_view k_nativeLibName = ""sv; -#endif +namespace fs = std::filesystem; namespace dusk::mods { namespace { constexpr borealis::Log Log{"dusk::mods::loader"}; ModLoader g_modLoader; -constexpr std::string_view k_nativeLibDir = "lib/"sv; -class DirectoryRollback { -public: - ~DirectoryRollback() { - if (!mPath.empty()) { - std::error_code ec; - std::filesystem::remove_all(mPath, ec); - } - } - - void set_path(std::filesystem::path path) { mPath = std::move(path); } - void release() { mPath.clear(); } - -private: - std::filesystem::path mPath; -}; - -std::unique_ptr load_bundle(const std::filesystem::path& modPath, bool fromDir) { - if (fromDir) { - return std::make_unique(modPath); - } else { - std::vector data = io::FileStream::ReadAllBytes(modPath); - return std::make_unique(std::move(data)); +void complete_operation(const std::shared_ptr& operation, const bool success = true, + std::string message = {}) { + if (operation == nullptr) { + return; } + operation->state = success ? ModOperation::State::Succeeded : ModOperation::State::Failed; + operation->message = std::move(message); } -struct NativeRuntimeLocation { - std::string entry; - std::vector runtimeEntries; - bool anyLibs = false; -}; - -struct NativeLocateFailure { - NativeModStatus status; - std::string logMessage; -}; - -using NativeLocateResult = std::variant; - -bool has_native_library_extension(std::string_view name) { - const auto endsWith = [name](std::string_view extension) { - if (name.size() < extension.size()) { - return false; - } - const auto suffix = name.substr(name.size() - extension.size()); - return std::ranges::equal(suffix, extension, [](char lhs, char rhs) { - const auto lower = [](char value) { - return value >= 'A' && value <= 'Z' ? static_cast(value + ('a' - 'A')) : - value; - }; - return lower(lhs) == lower(rhs); - }); - }; - return endsWith(".dll"sv) || endsWith(".so"sv) || endsWith(".dylib"sv); -} - -NativeLocateResult locate_native_runtime(ModBundle& bundle) { - NativeRuntimeLocation result; - const std::string platformPrefix = fmt::format("{}{}/", k_nativeLibDir, k_nativePlatform); - const std::string nativeEntry = platformPrefix + std::string{k_nativeLibName}; - for (const auto& name : bundle.getFileNames()) { - if (name.find('/') == std::string::npos && has_native_library_extension(name)) { - return NativeLocateFailure{ - NativeModStatus::InvalidBundle, - fmt::format( - "native library '{}' found at the root (natives go in /lib/{{platform}})", - name), - }; - } - if (!name.starts_with(k_nativeLibDir)) { - continue; - } - - const std::string_view libPath{ - name.data() + k_nativeLibDir.size(), name.size() - k_nativeLibDir.size()}; - const auto platformEnd = libPath.find('/'); - if (platformEnd != std::string_view::npos) { - const auto entryName = libPath.substr(platformEnd + 1); - if (entryName.find('/') == std::string_view::npos && - (entryName == "mod.dll"sv || entryName == "mod.so"sv)) - { - result.anyLibs = true; - } - } - - if (!k_nativePlatform.empty() && name.starts_with(platformPrefix)) { - const std::string_view relativeName{ - name.data() + platformPrefix.size(), name.size() - platformPrefix.size()}; - if (!is_safe_resource_path(relativeName)) { - continue; - } - result.runtimeEntries.push_back(name); - } - if (name == nativeEntry) { - result.entry = name; - } +LoadedMod::FileIdentity file_identity(const fs::path& path) { + std::error_code error; + const bool isDirectory = fs::is_directory(path, error); + if (error) { + return {}; } - std::ranges::sort(result.runtimeEntries); - result.runtimeEntries.erase( - std::unique(result.runtimeEntries.begin(), result.runtimeEntries.end()), - result.runtimeEntries.end()); - return result; + const auto size = isDirectory ? 0 : fs::file_size(path, error); + if (error) { + return {}; + } + const auto modified = fs::last_write_time(path, error); + if (error) { + return {}; + } + return {.size = size, .modified = modified, .valid = true}; } + } // namespace ModLoader& ModLoader::instance() { return g_modLoader; } -class InvalidModDataException : public std::runtime_error { -public: - explicit InvalidModDataException(const std::string& msg) : runtime_error(msg) {} - explicit InvalidModDataException(const char* msg) : runtime_error(msg) {} -}; - -static void validate_mod_id(std::string_view const str) { - if (str.empty()) { - throw InvalidModDataException("Missing ID value in mod metadata!"); - } - - bool lastWasPeriod = false; - for (auto const chr : str) { - if (chr == '.') { - if (lastWasPeriod) { - throw InvalidModDataException("Cannot have two consecutive periods in mod ID!"); - } - lastWasPeriod = true; - continue; - } - - lastWasPeriod = false; - - if (chr == '_') - continue; - - if (chr >= '0' && chr <= '9') - continue; - - if (chr >= 'a' && chr <= 'z') - continue; - - if (chr >= 'A' && chr <= 'Z') - continue; - - throw InvalidModDataException( - fmt::format("Invalid character '{}' in mod ID. Valid characters are period, " - "underscore, and alphanumerics.", - chr)); - } -} - -static bool bundle_has_file(ModBundle& bundle, const std::string& path) { - try { - bundle.getFileSize(path); - return true; - } catch (const std::runtime_error&) { - return false; - } -} - -static std::string resolve_image_path(ModBundle& bundle, const std::string& modId, - std::string_view key, const std::string& manifestPath, const std::string& defaultPath) { - if (!manifestPath.empty()) { - if (!is_safe_resource_path(manifestPath)) { - log::write( - modId, LOG_LEVEL_WARN, "invalid {} path '{}' in mod.json", key, manifestPath); - } else if (!bundle_has_file(bundle, manifestPath)) { - log::write( - modId, LOG_LEVEL_WARN, "{} path '{}' not found in bundle", key, manifestPath); - } else { - return manifestPath; - } - } - if (bundle_has_file(bundle, defaultPath)) { - return defaultPath; - } - return {}; -} - -static ModMetadata load_metadata(const std::filesystem::path& modPath, ModBundle& bundle) { - const auto metaJson = bundle.readFile("mod.json"); - auto j = nlohmann::json::parse(metaJson); - - std::string metaId = j.value("id", ""); - std::string metaName = j.value("name", ""); - std::string metaVersion = j.value("version", ""); - std::string metaAuthor = j.value("author", ""); - std::string metaDescription = j.value("description", ""); - std::string metaIcon = j.value("icon", ""); - std::string metaBanner = j.value("banner", ""); - - validate_mod_id(metaId); - - if (metaName.empty()) { - metaName = borealis::io::fs_path_to_string(modPath.stem()); - } - if (metaVersion.empty()) { - metaVersion = "?"s; - } - if (metaAuthor.empty()) { - metaAuthor = "unknown"s; - } - - std::string iconPath = resolve_image_path(bundle, metaId, "icon", metaIcon, "res/icon.png"s); - std::string bannerPath = - resolve_image_path(bundle, metaId, "banner", metaBanner, "res/banner.png"s); - - return ModMetadata{ - std::move(metaId), - std::move(metaName), - std::move(metaVersion), - std::move(metaAuthor), - std::move(metaDescription), - std::move(iconPath), - std::move(bannerPath), - }; -} - -// True if the first `capacity` bytes of `str` contain a NUL. -static bool terminated_within(const char* str, size_t capacity) { - return std::memchr(str, '\0', capacity) != nullptr; -} - -static bool parse_meta(NativeMod& native, LoadedMod& mod) { - const ModMeta* meta = native.meta; - if (meta->struct_size < sizeof(ModMeta)) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "mod_meta descriptor has invalid size {}", - meta->struct_size); - mod.nativeStatus = NativeModStatus::InvalidMetadata; - return false; - } - const auto* cursor = static_cast(meta->records_begin); - const auto* end = static_cast(meta->records_end); - if (cursor == nullptr || end == nullptr || cursor > end || - (reinterpret_cast(cursor) & 7) != 0) - { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "mod_meta section bounds are invalid"); - mod.nativeStatus = NativeModStatus::InvalidMetadata; - return false; - } - - ModMetaParsed parsed; - size_t headerCount = 0; - const auto invalid = [&](std::string_view why) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "invalid metadata record at offset {}: {}", - cursor - static_cast(meta->records_begin), why); - mod.nativeStatus = NativeModStatus::InvalidMetadata; - return false; - }; - - while (cursor < end) { - if (end - cursor < 8) { - return invalid("trailing bytes"); - } - uint64_t first = 0; - std::memcpy(&first, cursor, sizeof(first)); - if (first == 0) { // linker padding / bounds sentinel - cursor += 8; - continue; - } - - const auto* rec = reinterpret_cast(cursor); - const size_t size = rec->size; - if (size < 8 || size % 8 != 0 || size > static_cast(end - cursor)) { - return invalid("bad record size"); - } - - switch (rec->kind) { - case MOD_META_PAD: - break; - case MOD_META_HEADER: { - if (size < sizeof(ModMetaHeader)) { - return invalid("truncated header record"); - } - const auto* header = reinterpret_cast(rec); - ++headerCount; - parsed.abiVersion = header->abi_version; - break; - } - case MOD_META_IMPORT: { - if (size < sizeof(ModMetaImport)) { - return invalid("truncated import record"); - } - auto* record = reinterpret_cast(const_cast(cursor)); - if (!terminated_within(record->service_id.chars, sizeof(record->service_id.chars))) { - return invalid("unterminated import service id"); - } - parsed.imports.push_back(record); - break; - } - case MOD_META_EXPORT: { - if (size < sizeof(ModMetaExport)) { - return invalid("truncated export record"); - } - auto* record = reinterpret_cast(const_cast(cursor)); - if (!terminated_within(record->service_id.chars, sizeof(record->service_id.chars))) { - return invalid("unterminated export service id"); - } - parsed.exports.push_back(record); - break; - } - case MOD_META_HOOK_FN: { - if (size < sizeof(ModMetaHookFn)) { - return invalid("truncated hook record"); - } - parsed.hookFns.push_back( - reinterpret_cast(const_cast(cursor))); - break; - } - case MOD_META_HOOK_MEM: { - if (size <= sizeof(ModMetaHookMem)) { - return invalid("truncated hook record"); - } - auto* record = reinterpret_cast(const_cast(cursor)); - const char* strings = reinterpret_cast(cursor) + sizeof(ModMetaHookMem); - const size_t capacity = size - sizeof(ModMetaHookMem); - if (!terminated_within(strings, capacity)) { - return invalid("unterminated hook vtable symbol"); - } - const size_t vtableLen = std::char_traits::length(strings); - if (!terminated_within(strings + vtableLen + 1, capacity - vtableLen - 1)) { - return invalid("unterminated hook display name"); - } - parsed.hookMems.push_back(record); - break; - } - case MOD_META_HOOK_MEM_EXT: { - if (size <= sizeof(ModMetaHookMemExt)) { - return invalid("truncated extended hook record"); - } - auto* record = reinterpret_cast(const_cast(cursor)); - if (record->pmf_size <= MOD_META_HOOK_MEM_CAPACITY || - record->pmf_size > MOD_META_HOOK_MEM_EXT_CAPACITY || record->materialize == nullptr) - { - return invalid("bad extended hook member-pointer size"); - } - const char* strings = reinterpret_cast(cursor) + sizeof(ModMetaHookMemExt); - const size_t capacity = size - sizeof(ModMetaHookMemExt); - if (!terminated_within(strings, capacity)) { - return invalid("unterminated extended hook vtable symbol"); - } - const size_t vtableLen = std::char_traits::length(strings); - if (!terminated_within(strings + vtableLen + 1, capacity - vtableLen - 1)) { - return invalid("unterminated extended hook display name"); - } - parsed.hookMemExts.push_back(record); - break; - } - case MOD_META_HOOK_NAME: { - if (size <= sizeof(ModMetaHookName)) { - return invalid("truncated hook record"); - } - auto* record = reinterpret_cast(const_cast(cursor)); - const char* name = reinterpret_cast(cursor) + sizeof(ModMetaHookName); - if (!terminated_within(name, size - sizeof(ModMetaHookName))) { - return invalid("unterminated hook symbol name"); - } - parsed.hookNames.push_back(record); - break; - } - default: - // Additive record kinds may appear within a format version; skip them. - log::write(mod.metadata.id, LOG_LEVEL_DEBUG, "skipping unknown metadata record kind {}", - rec->kind); - break; - } - cursor += size; - } - - if (headerCount != 1) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "expected 1 metadata header record, found {}", - headerCount); - mod.nativeStatus = NativeModStatus::InvalidMetadata; - return false; - } - if (parsed.abiVersion != MOD_ABI_VERSION) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "expects ABI v{} but engine is v{}, skipping", - parsed.abiVersion, MOD_ABI_VERSION); - mod.nativeStatus = NativeModStatus::ApiVersionMismatch; - return false; - } - - native.parsed = std::move(parsed); - return true; -} - static std::string lifecycle_error_message( const char* fnName, const ModResult result, const ModError& error) { if (error.message[0] != '\0') { @@ -484,262 +84,6 @@ static std::string lifecycle_error_message( return fmt::format("{} failed with result {}", fnName, static_cast(result)); } -static std::string native_status_message(const NativeModStatus status) { - switch (status) { - case NativeModStatus::BuildDisabled: - return "Code mods are disabled on this Dusklight build"; - case NativeModStatus::ModMissingPlatform: - return fmt::format("Mod not supported on this platform ({})", k_nativePlatform); - case NativeModStatus::ApiVersionMismatch: - // TODO: differentiate whether mod or Dusklight is out of date - return "Mod ABI version mismatch"; - case NativeModStatus::MissingExport: - return "Missing required mod API exports"; - case NativeModStatus::InvalidMetadata: - return "Invalid mod metadata records"; - case NativeModStatus::InvalidBundle: - return "Invalid mod bundle layout (old mod?)"; - case NativeModStatus::Unknown: - return "Unknown mod load failure"; - case NativeModStatus::None: - case NativeModStatus::Loaded: - break; - } - return "native mod failed to load"; -} - -std::filesystem::path ModLoader::external_native_lib_path(const LoadedMod& mod) const { - namespace fs = std::filesystem; - if (k_nativeLibName.empty()) { - return {}; - } - const auto& libDir = m_searchDirs[mod.searchDirIndex].nativeLibDir; - if (libDir.empty()) { - return {}; - } - fs::path path = libDir / fs::path(mod.metadata.id + - borealis::io::fs_path_to_string(fs::path(k_nativeLibName).extension())); - std::error_code ec; - if (!fs::is_regular_file(path, ec)) { - return {}; - } - return path; -} - -void ModLoader::load_native( - LoadedMod& mod, const std::string& dllEntry, const std::vector& runtimeEntries) { - if (!EnableCodeMods) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "Code mods are not available in this build"); - mod.nativeStatus = NativeModStatus::BuildDisabled; - return; - } - - namespace fs = std::filesystem; - - const fs::path cacheDir = m_cacheDir / mod.metadata.id; - const fs::path scratchDir = cacheDir / "data"; - std::error_code ec; - fs::create_directories(scratchDir, ec); - if (ec) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to create mod directory {}: {}", - data::abbreviated_path_string(scratchDir), ec.message()); - return; - } - mod.dir = fs::absolute(scratchDir); - mod.dirUtf8 = borealis::io::fs_path_to_string(mod.dir); - - fs::path libPath; - fs::path runtimeDir; - DirectoryRollback runtimeDirRollback; - if (mod.inPlace) { - if (!dllEntry.empty()) { - libPath = mod.modPath / dllEntry; - } else if (auto external = external_native_lib_path(mod); !external.empty()) { - libPath = std::move(external); - } else { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, - "no native library named {} found; skipping", k_nativeLibName); - mod.nativeStatus = NativeModStatus::ModMissingPlatform; - return; - } - runtimeDir = libPath.parent_path(); - } else { - if (dllEntry.empty()) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, - "no native library named {} found; skipping", k_nativeLibName); - mod.nativeStatus = NativeModStatus::ModMissingPlatform; - return; - } - - // Every generation gets a new directory. The main module and all of its runtime - // libraries therefore have fresh paths and can coexist with a previous generation - // that is still unwinding after a reload. - runtimeDir = cacheDir / fmt::format("g{}", ++mod.cacheGeneration); - runtimeDirRollback.set_path(runtimeDir); - fs::create_directories(runtimeDir, ec); - if (ec) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, - "failed to create native runtime directory {}: {}", - data::abbreviated_path_string(runtimeDir), ec.message()); - return; - } - - const std::string platformPrefix = fmt::format("{}{}/", k_nativeLibDir, k_nativePlatform); - for (const auto& entry : runtimeEntries) { - if (!entry.starts_with(platformPrefix)) { - continue; - } - const std::string_view relativeName{ - entry.data() + platformPrefix.size(), entry.size() - platformPrefix.size()}; - if (!is_safe_resource_path(relativeName)) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, - "unsafe native runtime path '{}'; skipping", entry); - return; - } - - const fs::path outputPath = runtimeDir / fs::path{relativeName}; - fs::create_directories(outputPath.parent_path(), ec); - if (ec) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, - "failed to create directory for {}: {}", entry, ec.message()); - return; - } - - std::vector data; - try { - data = mod.bundle->readFile(entry); - } catch (const std::exception& e) { - log::write( - mod.metadata.id, LOG_LEVEL_ERROR, "failed to extract {}: {}", entry, e.what()); - return; - } - - std::ofstream out(outputPath, std::ios::binary | std::ios::out); - if (!out) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to write {}", entry); - return; - } - out.write(reinterpret_cast(data.data()), - static_cast(data.size())); - if (!out) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to write {}", entry); - return; - } - } - - libPath = runtimeDir / fs::path{dllEntry}.filename(); - } - - auto nativeMod = std::make_unique(); - try { - nativeMod->handle = std::make_unique(libPath); - } catch (const std::runtime_error& e) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to open {}: {}", - data::abbreviated_path_string(libPath), e.what()); - return; - } - - nativeMod->meta = nativeMod->handle->LookupSymbol("mod_meta"); - nativeMod->contextSymbol = nativeMod->handle->LookupSymbol("mod_ctx"); - nativeMod->fn_initialize = nativeMod->handle->LookupSymbol("mod_initialize"); - nativeMod->fn_update = nativeMod->handle->LookupSymbol("mod_update"); - nativeMod->fn_shutdown = nativeMod->handle->LookupSymbol("mod_shutdown"); - - if (!nativeMod->meta || !nativeMod->contextSymbol || !nativeMod->fn_initialize || - !nativeMod->fn_update || !nativeMod->fn_shutdown) - { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, - "{} missing required mod API exports; skipping", - data::abbreviated_path_string(libPath)); - mod.nativeStatus = NativeModStatus::MissingExport; - return; - } - - if (!parse_meta(*nativeMod, mod)) { - return; - } - - if (nativeMod->contextSymbol == nullptr) { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "missing required mod_ctx export"); - mod.nativeStatus = NativeModStatus::MissingExport; - return; - } - *nativeMod->contextSymbol = mod.context.get(); - - mod.nativePath = fs::absolute(libPath); - mod.nativeDir = fs::absolute(runtimeDir); - mod.nativeDirUtf8 = borealis::io::fs_path_to_string(mod.nativeDir); - mod.native = std::move(nativeMod); - mod.nativeStatus = NativeModStatus::Loaded; - runtimeDirRollback.release(); -} - -bool ModLoader::load_native_if_present(LoadedMod& mod) { - const auto result = locate_native_runtime(*mod.bundle); - if (const auto* failure = std::get_if(&result)) { - mod.nativeStatus = failure->status; - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "{}", failure->logMessage); - fail_mod(mod, MOD_ERROR, native_status_message(failure->status)); - return false; - } - - const auto& native = std::get(result); - if (!native.anyLibs && !(mod.inPlace && !external_native_lib_path(mod).empty())) { - mod.nativeStatus = NativeModStatus::None; - return true; - } - - mod.nativeStatus = NativeModStatus::Unknown; - load_native(mod, native.entry, native.runtimeEntries); - if (mod.nativeStatus != NativeModStatus::Loaded) { - fail_mod(mod, MOD_ERROR, native_status_message(mod.nativeStatus)); - return false; - } - return true; -} - -void ModLoader::unload_native(LoadedMod& mod) { - if (!mod.native || mod.inPlace) { - return; - } - // Deferred dlclose: this mod's code may still be on the stack below the current tick - m_retiredNatives.push_back({std::move(mod.native), std::move(mod.nativeDir)}); - mod.nativePath.clear(); - mod.nativeDir.clear(); - mod.nativeDirUtf8.clear(); -} - -void ModLoader::drain_retired_natives() { - for (auto& retired : m_retiredNatives) { - retired.native.reset(); - if (!retired.directory.empty()) { - std::error_code ec; - std::filesystem::remove_all(retired.directory, ec); - } - } - m_retiredNatives.clear(); -} - -static ModManifestInfo build_manifest_info(const ModMetaParsed& parsed) { - ModManifestInfo info; - info.imports.reserve(parsed.imports.size()); - for (const auto* record : parsed.imports) { - if (!svc::valid_service_id(record->service_id.chars)) { - continue; - } - info.imports.push_back({record->service_id.chars, record->major_version, - (record->rec.flags & SERVICE_IMPORT_OPTIONAL) == 0}); - } - info.exports.reserve(parsed.exports.size()); - for (const auto* record : parsed.exports) { - if (!svc::valid_service_id(record->service_id.chars)) { - continue; - } - info.exports.push_back({record->service_id.chars, record->major_version}); - } - return info; -} - std::string escape_mod_id_for_config(std::string_view const id) { std::string buf; @@ -789,36 +133,37 @@ static void warn_unpublished_deferred_exports(const LoadedMod& mod) { } } -void ModLoader::try_load_mod( - const std::filesystem::path& modPath, bool fromDir, uint32_t searchDirIndex) { - namespace fs = std::filesystem; - - std::unique_ptr bundle; - try { - bundle = load_bundle(modPath, fromDir); - } catch (const std::exception& e) { - Log.error("Failed to open {} bundle: {}", data::abbreviated_path_string(modPath), e.what()); - return; +LoadedMod* ModLoader::try_load_mod(const fs::path& modPath, bool fromDir, uint32_t searchDirIndex, + std::unique_ptr bundle) { + if (bundle == nullptr) { + try { + bundle = load_bundle(modPath, fromDir); + } catch (const std::exception& e) { + Log.error( + "Failed to open {} bundle: {}", data::abbreviated_path_string(modPath), e.what()); + return nullptr; + } } - ModMetadata metadata; + LoadedManifest manifest; try { - metadata = load_metadata(modPath, *bundle); + manifest = load_manifest(modPath, *bundle); } catch (const std::exception& e) { Log.error("bad mod.json in {}: {}", data::abbreviated_path_string(modPath), e.what()); - return; + return nullptr; } - if (const auto* existing = find_mod(metadata.id)) { + if (const auto* existing = find_mod(manifest.metadata.id)) { if (existing->searchDirIndex < searchDirIndex) { - log::write(metadata.id, LOG_LEVEL_INFO, "{} shadowed by higher-priority duplicate {}", + log::write(manifest.metadata.id, LOG_LEVEL_INFO, + "{} shadowed by higher-priority duplicate {}", data::abbreviated_path_string(modPath), data::abbreviated_path_string(existing->modPath)); } else { - log::write(metadata.id, LOG_LEVEL_ERROR, "duplicate mod id, not loading {}", + log::write(manifest.metadata.id, LOG_LEVEL_ERROR, "duplicate mod id, not loading {}", data::abbreviated_path_string(modPath)); } - return; + return nullptr; } const auto& inserted = m_mods.emplace_back(std::make_unique()); @@ -826,19 +171,39 @@ void ModLoader::try_load_mod( mod.active = true; mod.modPath = fs::absolute(modPath); mod.searchDirIndex = searchDirIndex; - mod.inPlace = m_searchDirs[searchDirIndex].inPlaceNative && fromDir; - mod.metadata = std::move(metadata); + mod.fromDirectory = fromDir; + mod.nativeInPlace = m_searchDirs[searchDirIndex].inPlaceNative && fromDir; + mod.fileIdentity = file_identity(modPath); + mod.metadata = std::move(manifest.metadata); + mod.runtime = std::move(manifest.runtime); mod.bundle = std::move(bundle); mod.context = std::make_unique(); mod.context->mod = &mod; mod.cvarIsEnabled = std::make_unique>(mod_enabled_cvar_name(mod.metadata.id), true); + if (mod.runtime.has_value()) { + const auto& runtime = *mod.runtime; + mod.manifestInfo.imports.push_back({runtime.id, runtime.major, runtime.minMinor, true}); + + std::error_code ec; + mod.dir = fs::absolute(m_cacheDir / mod.metadata.id / "data", ec); + if (!ec) { + fs::create_directories(mod.dir, ec); + } + if (ec) { + fail_mod(mod, MOD_ERROR, + fmt::format("Failed to create script scratch directory: {}", ec.message())); + } else { + mod.dirUtf8 = borealis::io::fs_path_to_string(mod.dir); + } + } if (load_native_if_present(mod) && mod.native) { mod.manifestInfo = build_manifest_info(mod.native->parsed); } log::write(mod.metadata.id, LOG_LEVEL_INFO, "found '{}' v{} by {} ({})", mod.metadata.name, mod.metadata.version, mod.metadata.author, data::abbreviated_path_string(modPath)); + return &mod; } bool ModLoader::activate_mod(LoadedMod& mod) { @@ -846,12 +211,12 @@ bool ModLoader::activate_mod(LoadedMod& mod) { mod.active = true; // Asset-only mods have no lifecycle beyond their overlay files. - if (!mod.native) { + if (!mod.native && !mod.runtime.has_value()) { mod.enabledApplied = true; return true; } - if (!mod.servicesRegistered) { + if (mod.native && !mod.servicesRegistered) { if (!register_static_service_exports(mod)) { log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to register service exports"); deactivate_mod(mod); @@ -860,30 +225,60 @@ bool ModLoader::activate_mod(LoadedMod& mod) { mod.servicesRegistered = true; } - if (!resolve_service_imports(mod)) { + if (mod.native && !resolve_service_imports(mod)) { log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to resolve service imports"); deactivate_mod(mod); return false; } - svc::hook_resolve_mod_records(mod); + if (mod.native) { + svc::hook_resolve_mod_records(mod); + *mod.native->contextSymbol = mod.context.get(); + } else { + auto& runtime = *mod.runtime; + const auto* record = svc::find_service(runtime.id.c_str(), runtime.major, runtime.minMinor); + if (record == nullptr) { + fail_mod(mod, MOD_UNAVAILABLE, + describe_missing_import(runtime.id.c_str(), runtime.major, runtime.minMinor)); + deactivate_mod(mod); + return false; + } - *mod.native->contextSymbol = mod.context.get(); + const auto* service = static_cast(record->service); + constexpr size_t kRequiredSize = offsetof(ModRuntimeService, deactivate) + + sizeof(((ModRuntimeService*)nullptr)->deactivate); + if (record->provider == nullptr || service == nullptr || + service->header.struct_size < kRequiredSize || service->activate == nullptr || + service->update == nullptr || service->deactivate == nullptr) + { + fail_mod(mod, MOD_UNAVAILABLE, + fmt::format("Runtime service {}@{} has an invalid lifecycle contract", runtime.id, + runtime.major)); + deactivate_mod(mod); + return false; + } + runtime.service = service; + runtime.providerContext = record->provider->context.get(); + } - log::write(mod.metadata.id, LOG_LEVEL_TRACE, "calling mod_initialize"); + const char* initializeName = mod.native ? "mod_initialize" : "runtime activate"; + log::write(mod.metadata.id, LOG_LEVEL_TRACE, "calling {}", initializeName); try { ModError error = MOD_ERROR_INIT; - const auto result = mod.native->fn_initialize(&error); + const auto result = mod.native ? + mod.native->fn_initialize(&error) : + mod.runtime->service->activate( + mod.runtime->providerContext, mod.context.get(), &error); if (result == MOD_OK && !mod.loadFailed) { mod.initialized = true; - log::write(mod.metadata.id, LOG_LEVEL_TRACE, "mod_initialize succeeded"); + log::write(mod.metadata.id, LOG_LEVEL_TRACE, "{} succeeded", initializeName); } else if (result != MOD_OK && !mod.loadFailed) { - fail_mod(mod, result, lifecycle_error_message("mod_initialize", result, error)); + fail_mod(mod, result, lifecycle_error_message(initializeName, result, error)); } } catch (const std::exception& e) { - fail_mod(mod, MOD_ERROR, fmt::format("Exception in mod_initialize: {}", e.what())); + fail_mod(mod, MOD_ERROR, fmt::format("Exception in {}: {}", initializeName, e.what())); } catch (...) { - fail_mod(mod, MOD_ERROR, "Unknown exception in mod_initialize"); + fail_mod(mod, MOD_ERROR, fmt::format("Unknown exception in {}", initializeName)); } warn_unpublished_deferred_exports(mod); @@ -900,21 +295,36 @@ bool ModLoader::activate_mod(LoadedMod& mod) { void ModLoader::deactivate_mod(LoadedMod& mod) { svc::modules_mod_deactivating(mod); - if (mod.initialized && mod.native && mod.native->fn_shutdown) { - log::write(mod.metadata.id, LOG_LEVEL_TRACE, "calling mod_shutdown"); + if (mod.initialized && ((mod.native && mod.native->fn_shutdown) || + (mod.runtime.has_value() && mod.runtime->service != nullptr))) + { + const char* shutdownName = mod.native ? "mod_shutdown" : "runtime deactivate"; + log::write(mod.metadata.id, LOG_LEVEL_TRACE, "calling {}", shutdownName); try { ModError error = MOD_ERROR_INIT; - const auto result = mod.native->fn_shutdown(&error); + const auto result = mod.native ? + mod.native->fn_shutdown(&error) : + mod.runtime->service->deactivate( + mod.runtime->providerContext, mod.context.get(), &error); if (result == MOD_OK) { - log::write(mod.metadata.id, LOG_LEVEL_TRACE, "mod_shutdown succeeded"); + log::write(mod.metadata.id, LOG_LEVEL_TRACE, "{} succeeded", shutdownName); } else { - log::write(mod.metadata.id, LOG_LEVEL_ERROR, "mod_shutdown failed: {}", - lifecycle_error_message("mod_shutdown", result, error)); + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "{} failed: {}", shutdownName, + lifecycle_error_message(shutdownName, result, error)); } + } catch (const std::exception& exception) { + log::write( + mod.metadata.id, LOG_LEVEL_ERROR, "{} threw: {}", shutdownName, exception.what()); } catch (...) { + log::write( + mod.metadata.id, LOG_LEVEL_ERROR, "{} threw an unknown exception", shutdownName); } } mod.initialized = false; + if (mod.runtime.has_value()) { + mod.runtime->service = nullptr; + mod.runtime->providerContext = nullptr; + } if (mod.servicesRegistered) { svc::remove_services_for_provider(mod); @@ -947,55 +357,30 @@ void ModLoader::init() { m_cacheDir = m_searchDirs.front().path / ".cache"; } - namespace fs = std::filesystem; std::error_code ec; // Stale libs from previous sessions (see load_native). fs::remove_all(m_cacheDir, ec); - for (size_t dirIndex = 0; dirIndex < m_searchDirs.size(); ++dirIndex) { - const auto& searchDir = m_searchDirs[dirIndex]; - - // --mods can point the user dir at the bundled dir; don't scan the same dir twice. - bool alreadyScanned = false; - for (size_t earlier = 0; earlier < dirIndex && !alreadyScanned; ++earlier) { - alreadyScanned = fs::equivalent(m_searchDirs[earlier].path, searchDir.path, ec); - } - if (alreadyScanned) { + const auto packages = scan_packages(m_searchDirs); + for (const auto& package : packages) { + const auto* selected = select_package(packages, package.metadata.id); + if (selected != &package) { + log::write(package.metadata.id, LOG_LEVEL_INFO, "{} v{} shadowed by {} v{}", + data::abbreviated_path_string(package.path), package.metadata.version, + data::abbreviated_path_string(selected->path), selected->metadata.version); continue; } - - if (!fs::is_directory(searchDir.path)) { - if (dirIndex == 0) { - Log.info( - "mods directory '{}' not found", data::abbreviated_path_string(searchDir.path)); - } else { - Log.debug( - "mods directory '{}' not found", data::abbreviated_path_string(searchDir.path)); - } - continue; - } - - std::vector entries; - for (auto& e : fs::directory_iterator(searchDir.path, ec)) { - if (e.is_directory() && std::filesystem::exists(e.path() / "mod.json")) { - entries.push_back(e); - } else if (e.is_regular_file() && e.path().extension() == ".dusk") { - entries.push_back(e); - } - } - std::sort(entries.begin(), entries.end(), - [](const fs::directory_entry& a, const fs::directory_entry& b) { - return a.path().filename() < b.path().filename(); - }); - - for (auto& entry : entries) { - try_load_mod(entry.path(), entry.is_directory(), static_cast(dirIndex)); + if (auto* mod = try_load_mod(package.path, package.fromDirectory, package.searchDirIndex)) { + record_package_sources(*mod, packages); } } if (m_mods.empty()) { + init_services(); Log.info("no mods found"); + svc::modules_lifecycle_applied(); + m_startupComplete = true; return; } @@ -1060,10 +445,19 @@ void ModLoader::init() { m_startupComplete = true; } -LoadedMod* ModLoader::find_mod(std::string_view id) const { - for (auto& mod : mods()) { - if (mod.metadata.id == id) { - return &mod; +LoadedMod* ModLoader::find_mod(std::string_view id) { + for (auto& mod : m_mods) { + if (mod->metadata.id == id) { + return mod.get(); + } + } + return nullptr; +} + +const LoadedMod* ModLoader::find_mod(std::string_view id) const { + for (const auto& mod : m_mods) { + if (mod->metadata.id == id) { + return mod.get(); } } return nullptr; @@ -1081,8 +475,63 @@ void ModLoader::request_disable(std::string_view id) { } } -void ModLoader::request_reload(std::string_view id) { - m_pendingRequests.push_back({std::string{id}, RequestKind::Reload}); +ModOperationHandle ModLoader::request_reload(std::string_view id) { + auto operation = std::make_shared(); + if (find_mod(id) != nullptr) { + m_pendingRequests.push_back(ReloadRequest{ + .modId = std::string{id}, + .operation = operation, + }); + } else { + complete_operation(operation, false, "The mod is no longer installed"); + } + return operation; +} + +ModOperationHandle ModLoader::request_install( + fs::path path, std::optional update) { + auto operation = std::make_shared(); + m_pendingRequests.push_back(InstallRequest{ + .stagedPath = std::move(path), + .update = std::move(update), + .operation = operation, + }); + return operation; +} + +ModOperationHandle ModLoader::request_uninstall(std::string_view id) { + auto operation = std::make_shared(); + if (find_mod(id) != nullptr) { + m_pendingRequests.push_back(UninstallRequest{ + .modId = std::string{id}, + .operation = operation, + }); + } else { + complete_operation(operation); + } + return operation; +} + +ModOperationHandle ModLoader::request_reactivate(std::string_view id) { + auto operation = std::make_shared(); + m_pendingRequests.push_back(LifecycleRequest{ + .modId = std::string{id}, + .action = LifecycleAction::Reactivate, + .operation = operation, + }); + return operation; +} + +fs::path ModLoader::user_mods_dir() const { + return m_searchDirs.empty() ? fs::path{} : m_searchDirs.front().path; +} + +bool ModLoader::can_uninstall(const LoadedMod& mod) const { + return mod.hasUserPackage; +} + +bool ModLoader::can_update(const LoadedMod& mod) const { + return mod.searchDirIndex != 0 || (!mod.fromDirectory && can_uninstall(mod)); } void ModLoader::notify_mod_failure(LoadedMod& mod, bool firstFailure) { @@ -1093,7 +542,10 @@ void ModLoader::notify_mod_failure(LoadedMod& mod, bool firstFailure) { if (!m_startupComplete) { return; } - m_pendingRequests.push_back({mod.metadata.id, RequestKind::Disable}); + m_pendingRequests.emplace_back(LifecycleRequest{ + .modId = mod.metadata.id, + .action = LifecycleAction::Disable, + }); } void ModLoader::flush_toasts() { @@ -1126,7 +578,7 @@ void ModLoader::flush_toasts() { ui::push_toast(std::move(toast)); } -std::vector ModLoader::collect_lifecycle_set(LoadedMod& target) { +std::vector ModLoader::collect_lifecycle_set(LoadedMod& target) const { std::vector included{&target}; std::vector pending{&target}; while (!pending.empty()) { @@ -1155,42 +607,41 @@ std::vector ModLoader::collect_lifecycle_set(LoadedMod& target) { return ordered; } -bool ModLoader::ensure_native_loaded(LoadedMod& mod) { - if (mod.native || mod.nativeStatus == NativeModStatus::None) { - return true; - } - return load_native_if_present(mod); -} - bool ModLoader::reload_bundle(LoadedMod& mod) { - namespace fs = std::filesystem; log::write(mod.metadata.id, LOG_LEVEL_INFO, "reloading from {}", data::abbreviated_path_string(mod.modPath)); std::shared_ptr newBundle; - ModMetadata newMetadata; + LoadedManifest newManifest; try { std::error_code ec; newBundle = load_bundle(mod.modPath, fs::is_directory(mod.modPath, ec)); - newMetadata = load_metadata(mod.modPath, *newBundle); + newManifest = load_manifest(mod.modPath, *newBundle); } catch (const std::exception& e) { fail_mod(mod, MOD_ERROR, fmt::format("Reload failed: {}", e.what())); return false; } - if (newMetadata.id != mod.metadata.id) { + if (newManifest.metadata.id != mod.metadata.id) { fail_mod(mod, MOD_CONFLICT, - fmt::format("Mod ID changed on reload ('{}'); restart required", newMetadata.id)); + fmt::format( + "Mod ID changed on reload ('{}'); restart required", newManifest.metadata.id)); return false; } - mod.metadata = std::move(newMetadata); + mod.metadata = std::move(newManifest.metadata); + mod.runtime = std::move(newManifest.runtime); // In-flight readers of the old bundle keep it alive through their shared_ptr. mod.bundle = std::move(newBundle); + mod.fileIdentity = file_identity(mod.modPath); mod.loadFailed = false; mod.failureReason.clear(); ModManifestInfo newInfo; + if (mod.runtime.has_value()) { + const auto& runtime = *mod.runtime; + newInfo.imports.push_back({runtime.id, runtime.major, runtime.minMinor, true}); + } if (!load_native_if_present(mod)) { return false; } @@ -1212,7 +663,48 @@ bool ModLoader::reload_bundle(LoadedMod& mod) { return true; } -void ModLoader::apply_lifecycle_change(LoadedMod& target, const bool reload) { +void ModLoader::resume_lifecycle_set(const std::vector& affected) { + for (auto* mod : affected) { + if (mod->active || mod->loadFailed || !mod->cvarIsEnabled->getValue()) { + continue; + } + if (!ensure_native_loaded(*mod)) { + continue; + } + if (mod->native && !mod->servicesRegistered) { + if (register_static_service_exports(*mod)) { + mod->servicesRegistered = true; + } else { + log::write(mod->metadata.id, LOG_LEVEL_ERROR, "failed to register service exports"); + deactivate_mod(*mod); + } + } + } + + for (auto* mod : affected) { + if (mod->active || mod->loadFailed || !mod->cvarIsEnabled->getValue()) { + continue; + } + if (!required_deps_active(*mod)) { + mod->suspendedByProvider = true; + log::write( + mod->metadata.id, LOG_LEVEL_INFO, "suspended: a required provider is disabled"); + continue; + } + mod->suspendedByProvider = false; + activate_mod(*mod); + } + + for (auto* mod : affected) { + if (!mod->active && mod->servicesRegistered) { + svc::remove_services_for_provider(*mod); + mod->servicesRegistered = false; + } + } +} + +void ModLoader::apply_lifecycle_change( + LoadedMod& target, const bool reload, const PackageCandidate* replacement) { auto affected = collect_lifecycle_set(target); // Dependents first (reverse init order), like shutdown. @@ -1232,6 +724,17 @@ void ModLoader::apply_lifecycle_change(LoadedMod& target, const bool reload) { } } + if (replacement != nullptr) { + target.modPath = replacement->path; + target.searchDirIndex = replacement->searchDirIndex; + target.fromDirectory = replacement->fromDirectory; + target.nativeInPlace = + replacement->fromDirectory && m_searchDirs[replacement->searchDirIndex].inPlaceNative; + std::stable_sort(m_mods.begin(), m_mods.end(), + [](const auto& a, const auto& b) { return a->searchDirIndex > b->searchDirIndex; }); + loader::sort_mods(m_mods); + } + if (reload) { // On failure the target is failed and stays down; dependents get resume attempts below // and suspend against the failed provider where required. @@ -1249,48 +752,7 @@ void ModLoader::apply_lifecycle_change(LoadedMod& target, const bool reload) { affected = std::move(reordered); } - // Mirror startup: publish every candidate's static exports before any of them initialize, - // so importers within the set resolve providers regardless of initialization order - // (optional cycles rely on this). - for (auto* mod : affected) { - if (mod->active || mod->loadFailed || !mod->cvarIsEnabled->getValue()) { - continue; - } - if (!ensure_native_loaded(*mod)) { - continue; - } - if (mod->native && !mod->servicesRegistered) { - if (register_static_service_exports(*mod)) { - mod->servicesRegistered = true; - } else { - log::write(mod->metadata.id, LOG_LEVEL_ERROR, "failed to register service exports"); - deactivate_mod(*mod); - } - } - } - - // Providers first (init order). The target is naturally first among the affected mods. - for (auto* mod : affected) { - if (mod->active || mod->loadFailed || !mod->cvarIsEnabled->getValue()) { - continue; - } - if (!required_deps_active(*mod)) { - mod->suspendedByProvider = true; - log::write( - mod->metadata.id, LOG_LEVEL_INFO, "suspended: a required provider is disabled"); - continue; - } - mod->suspendedByProvider = false; - activate_mod(*mod); - } - - // Mods that stayed down must not leave their exports resolvable. - for (auto* mod : affected) { - if (!mod->active && mod->servicesRegistered) { - svc::remove_services_for_provider(*mod); - mod->servicesRegistered = false; - } - } + resume_lifecycle_set(affected); } void ModLoader::on_enabled_changed(LoadedMod& mod) { @@ -1304,13 +766,335 @@ void ModLoader::on_enabled_changed(LoadedMod& mod) { } if (mod.suspendedByProvider) { if (!mod.cvarIsEnabled->getValue()) { - // The user disabled a suspended mod; stop waiting for its providers. mod.suspendedByProvider = false; } return; } - m_pendingRequests.push_back({mod.metadata.id, - mod.cvarIsEnabled->getValue() ? RequestKind::Enable : RequestKind::Disable}); + m_pendingRequests.push_back(LifecycleRequest{ + .modId = mod.metadata.id, + .action = + mod.cvarIsEnabled->getValue() ? LifecycleAction::Enable : LifecycleAction::Disable, + }); +} + +void ModLoader::forget_mod(LoadedMod& mod) { + auto affected = collect_lifecycle_set(mod); + std::vector blocked; + std::vector pending{&mod}; + while (!pending.empty()) { + auto* provider = pending.back(); + pending.pop_back(); + for (const auto& edge : provider->dependents) { + if (!edge.required || edge.mod == nullptr || + std::ranges::find(blocked, edge.mod) != blocked.end()) + { + continue; + } + blocked.push_back(edge.mod); + pending.push_back(edge.mod); + } + } + + for (auto* affectedMod : affected | std::views::reverse) { + const bool wasActive = affectedMod->active; + if (affectedMod->active || affectedMod->initialized || affectedMod->native != nullptr) { + log::write(affectedMod->metadata.id, LOG_LEVEL_INFO, "deactivating mod"); + deactivate_mod(*affectedMod); + } + if (affectedMod != &mod && wasActive) { + affectedMod->suspendedByProvider = true; + } + } + + const std::string modId = mod.metadata.id; + auto* removedMod = &mod; + if (mod.enabledSubscription != 0) { + config::unsubscribe(mod.enabledSubscription); + mod.enabledSubscription = 0; + } + unregister(*mod.cvarIsEnabled); + + std::vector remaining; + remaining.reserve(affected.size()); + for (auto* affectedMod : affected) { + if (affectedMod != &mod) { + remaining.push_back(affectedMod); + } + } + std::erase_if( + m_mods, [removedMod](const auto& candidate) { return candidate.get() == removedMod; }); + loader::sort_mods(m_mods); + + std::vector resumable; + for (auto* affectedMod : remaining) { + const bool needsRemovedProvider = std::ranges::find(blocked, affectedMod) != blocked.end(); + affectedMod->suspendedByProvider = needsRemovedProvider; + if (needsRemovedProvider) { + log::write(affectedMod->metadata.id, LOG_LEVEL_INFO, + "suspended: required provider '{}' was removed", modId); + } else { + resumable.push_back(affectedMod); + } + } + resume_lifecycle_set(resumable); + + ++m_generation; + log::write(modId, LOG_LEVEL_INFO, "forgot removed package"); +} + +ModLoader::OperationResult ModLoader::load_runtime_mod(const fs::path& requestedPath) { + const auto path = fs::absolute(requestedPath).lexically_normal(); + std::error_code error; + const auto status = fs::status(path, error); + if (error || !fs::exists(status)) { + return { + .success = false, + .message = error ? fmt::format("Could not inspect the package: {}", error.message()) : + "The package was not found", + }; + } + + const bool fromDir = fs::is_directory(status); + std::unique_ptr bundle; + std::optional metadata; + try { + bundle = load_bundle(path, fromDir); + metadata = load_manifest(path, *bundle).metadata; + } catch (const std::exception& exception) { + return { + .success = false, + .message = fmt::format("Invalid mod package: {}", exception.what()), + }; + } + if (const auto* duplicate = find_mod(metadata->id)) { + return { + .success = false, + .message = fmt::format("A mod with this ID is already loaded from {}", + data::abbreviated_path_string(duplicate->modPath)), + }; + } + auto* mod = try_load_mod(path, fromDir, 0, std::move(bundle)); + if (mod == nullptr) { + return { + .success = false, + .message = "The mod could not be loaded", + }; + } + + mod->enabledSubscription = Register( + *mod->cvarIsEnabled, [this, mod](const bool&, const bool&) { on_enabled_changed(*mod); }); + loader::sort_mods(m_mods); + if (!mod->cvarIsEnabled->getValue()) { + mod->active = false; + mod->suspendedByProvider = false; + log::write(mod->metadata.id, LOG_LEVEL_INFO, "installed disabled by config"); + } else if (!mod->loadFailed) { + mod->active = false; + apply_lifecycle_change(*mod, false); + } + ++m_generation; + log::write(mod->metadata.id, LOG_LEVEL_INFO, "installed at runtime"); + return runtime_result(*mod); +} + +ModLoader::OperationResult ModLoader::reload_runtime_mod( + LoadedMod& mod, const PackageCandidate* replacement) { + if (mod.nativeInPlace && replacement == nullptr) { + return { + .success = false, + .message = "An in-place native library cannot be reloaded", + .mod = &mod, + }; + } + apply_lifecycle_change(mod, true, replacement); + ++m_generation; + return runtime_result(mod); +} + +ModLoader::OperationResult ModLoader::uninstall_runtime_mod(LoadedMod& mod) { + std::vector packages; + try { + packages = scan_packages(m_searchDirs); + } catch (const std::exception& exception) { + return {.success = false, .message = exception.what()}; + } + record_package_sources(mod, packages); + if (!can_uninstall(mod)) { + return {.success = false, .message = "No installed package to remove"}; + } + + std::string removalError; + std::erase_if(packages, [&](const auto& package) { + if (package.metadata.id != mod.metadata.id || package.searchDirIndex != 0 || + package.fromDirectory || package.symlink) + { + return false; + } + std::error_code error; + fs::remove(package.path, error); + if (error) { + removalError = fmt::format("Could not remove {}: {}", + data::abbreviated_path_string(package.path), error.message()); + } + return !error; + }); + + OperationResult result; + if (const auto* selected = select_package(packages, mod.metadata.id)) { + if (selected->path != mod.modPath) { + result = reload_runtime_mod(mod, selected); + } else { + result.mod = &mod; + ++m_generation; + } + record_package_sources(mod, packages); + } else { + forget_mod(mod); + } + if (!removalError.empty()) { + result.success = false; + result.message = std::move(removalError); + } + return result; +} + +ModLoader::OperationResult ModLoader::runtime_result(LoadedMod& mod) { + if (mod.loadFailed) { + return { + .success = false, + .message = mod.failureReason.empty() ? "Mod failed to activate" : mod.failureReason, + .mod = &mod, + }; + } + if (mod.cvarIsEnabled->getValue() && !mod.active) { + return { + .success = false, + .message = "A required provider is unavailable", + .mod = &mod, + }; + } + if (!mod.cvarIsEnabled->getValue()) { + return { + .message = "Installed, disabled by config", + .mod = &mod, + }; + } + return {.mod = &mod}; +} + +ModLoader::OperationResult ModLoader::install_staged( + const fs::path& requestedPath, const std::optional& update) { + if (m_searchDirs.empty()) { + return { + .success = false, + .message = "No writable mods directory is configured", + }; + } + std::error_code error; + const auto userDir = fs::weakly_canonical(m_searchDirs.front().path, error); + if (error) { + return { + .success = false, + .message = + fmt::format("Could not resolve the user mods directory: {}", error.message()), + }; + } + const auto stagingDir = userDir / ".staging"; + const auto path = fs::weakly_canonical(requestedPath, error); + const bool stagedName = path.extension() == ".part" && path.stem().extension() == ".dusk"; + if (error || path.parent_path() != stagingDir || !stagedName || + !fs::is_regular_file(path, error)) + { + Log.error("refusing staged install from {}", data::abbreviated_path_string(requestedPath)); + return { + .success = false, + .message = "The package is not in the mod staging directory", + }; + } + + ModMetadata metadata; + std::string validationError; + if (!inspect_mod_bundle(path, metadata, validationError)) { + return { + .success = false, + .message = fmt::format("Invalid mod package: {}", validationError), + }; + } + + if (update) { + if (metadata.id != update->modId || metadata.version != update->targetVersion) { + return {.success = false, .message = "The update package identity changed"}; + } + if (auto reason = updates::validate(*update); !reason.empty()) { + return {.success = false, .message = std::move(reason)}; + } + } + + const auto destination = userDir / fmt::format("{}.dusk", utils::safe_filename(metadata.id)); + auto* installed = find_mod(metadata.id); + if (installed != nullptr && !can_update(*installed)) { + return { + .success = false, + .message = "Cannot install mod over a development directory", + }; + } + + for (const auto& mod : mods()) { + if (mod.metadata.id != metadata.id && fs::equivalent(mod.modPath, destination, error)) { + return { + .success = false, + .message = "The destination filename belongs to a different mod", + }; + } + } + error.clear(); + + if (!borealis::update::parse_version(metadata.version)) { + return {.success = false, .message = "The package version is invalid"}; + } + std::vector packages; + try { + packages = scan_packages(m_searchDirs); + } catch (const std::exception& exception) { + return {.success = false, .message = exception.what()}; + } + if (const auto* selected = select_package(packages, metadata.id); + selected && compare_package_versions(metadata.version, selected->metadata.version) < 0) + { + return { + .success = false, + .message = fmt::format( + "A newer version ({}) is already installed", selected->metadata.version), + }; + } + + const auto packageResult = install_package(path, destination, metadata.id); + if (!packageResult.replaced) { + return {.success = false, .message = packageResult.error}; + } + std::erase_if(packages, [&](const auto& package) { + if (package.metadata.id != metadata.id || package.searchDirIndex != 0 || + package.fromDirectory) + { + return false; + } + std::error_code statusError; + return fs::equivalent(package.path, destination, statusError) || + !fs::exists(package.path, statusError); + }); + packages.push_back({.path = destination, .metadata = metadata}); + const auto* selected = select_package(packages, metadata.id); + auto result = installed != nullptr ? reload_runtime_mod(*installed, selected) : + load_runtime_mod(selected->path); + if (result.mod != nullptr) { + record_package_sources(*result.mod, packages); + } + + if (result.success && !packageResult.error.empty()) { + result.success = false; + result.message = packageResult.error; + } + return result; } void ModLoader::apply_pending_requests() { @@ -1321,16 +1105,90 @@ void ModLoader::apply_pending_requests() { return; } - // Coalesce per mod, last request wins. Failures during apply re-enqueue for next tick. const auto requests = std::exchange(m_pendingRequests, {}); - std::vector coalesced; + std::vector coalesced; for (const auto& request : requests) { - const auto existing = std::ranges::find_if( - coalesced, [&](const Request& r) { return r.modId == request.modId; }); + if (const auto* install = std::get_if(&request)) { + auto result = install_staged(install->stagedPath, install->update); + if (result.success && result.mod != nullptr) { + const auto& metadata = result.mod->metadata; + const std::string iconRml = + metadata.iconPath.empty() ? + std::string{} : + fmt::format(R"()", + ui::escape(ui::mod_image_source(*result.mod, metadata.iconPath))); + ui::push_toast({ + .type = "mod-installed", + .title = "Mod installed", + .content = fmt::format( + R"({}{}v{}{})", + iconRml, ui::escape(metadata.name), ui::escape(metadata.version), + ui::escape(metadata.author)), + .duration = std::chrono::seconds{4}, + }); + } else if (!result.success && result.mod == nullptr) { + ui::push_toast({ + .type = "warning", + .title = "Mod install failed", + .content = + result.message.empty() ? "The loader rejected the package" : result.message, + .duration = std::chrono::seconds{6}, + }); + } + if (!result.success && !m_searchDirs.empty()) { + // request_install owns only files under the configured staging directory. + std::error_code error; + const auto userDir = fs::weakly_canonical(m_searchDirs.front().path, error); + if (!error) { + const auto stagedPath = fs::weakly_canonical(install->stagedPath, error); + if (!error && stagedPath.parent_path() == userDir / ".staging") { + fs::remove(stagedPath, error); + } + } + } + complete_operation(install->operation, result.success, std::move(result.message)); + continue; + } + if (const auto* reload = std::get_if(&request)) { + auto* mod = find_mod(reload->modId); + if (mod == nullptr) { + complete_operation(reload->operation, false, "The mod is no longer installed"); + continue; + } + auto result = reload_runtime_mod(*mod); + complete_operation(reload->operation, result.success, std::move(result.message)); + continue; + } + if (const auto* uninstall = std::get_if(&request)) { + auto* mod = find_mod(uninstall->modId); + if (mod == nullptr) { + complete_operation(uninstall->operation); + continue; + } + const auto removedName = mod->metadata.name; + const auto removedId = mod->metadata.id; + auto result = uninstall_runtime_mod(*mod); + if (result.success) { + queue::remove_by_mod_id(removedId); + ui::push_toast({ + .title = result.mod != nullptr ? "User update removed" : "Mod uninstalled", + .content = removedName, + .duration = std::chrono::seconds{2}, + }); + } + complete_operation(uninstall->operation, result.success, std::move(result.message)); + continue; + } + + const auto& lifecycle = std::get(request); + const auto existing = + std::ranges::find(coalesced, lifecycle.modId, &LifecycleRequest::modId); if (existing != coalesced.end()) { - existing->kind = request.kind; + complete_operation( + existing->operation, false, "Superseded by a newer lifecycle request"); + *existing = lifecycle; } else { - coalesced.push_back(request); + coalesced.push_back(lifecycle); } } @@ -1338,19 +1196,36 @@ void ModLoader::apply_pending_requests() { auto* mod = find_mod(request.modId); if (mod == nullptr) { Log.warn("lifecycle request for unknown mod '{}'", request.modId); + complete_operation(request.operation, false, "The mod is no longer installed"); continue; } - if (request.kind == RequestKind::Reload && mod->inPlace) { - log::write(mod->metadata.id, LOG_LEVEL_WARN, "is a built-in mod and can't be reloaded"); + if (request.action == LifecycleAction::Enable && mod->enabledApplied) { continue; } - if (request.kind == RequestKind::Enable && mod->enabledApplied) { + if (request.action == LifecycleAction::Disable && !mod->enabledApplied && !mod->active) { continue; } - if (request.kind == RequestKind::Disable && !mod->enabledApplied && !mod->active) { - continue; + + if (request.action == LifecycleAction::Reactivate) { + mod->loadFailed = false; + mod->failureReason.clear(); + mod->suspendedByProvider = false; + if (!mod->cvarIsEnabled->getValue()) { + mod->cvarIsEnabled->setValue(true); + } + } + apply_lifecycle_change(*mod, false); + + if (request.action == LifecycleAction::Reactivate) { + std::string error; + if (mod->loadFailed) { + error = + mod->failureReason.empty() ? "The mod failed to activate" : mod->failureReason; + } else if (mod->cvarIsEnabled->getValue() && !mod->active) { + error = "A required provider is unavailable"; + } + complete_operation(request.operation, error.empty(), std::move(error)); } - apply_lifecycle_change(*mod, request.kind == RequestKind::Reload); } svc::modules_lifecycle_applied(); @@ -1364,17 +1239,23 @@ void ModLoader::tick() { apply_pending_requests(); for (auto& mod : mods()) { - if (!mod.active || !mod.native) { + if (!mod.active || (!mod.native && !mod.runtime.has_value())) { continue; } try { ModError error = MOD_ERROR_INIT; - const auto result = mod.native->fn_update(&error); + const bool delegated = !mod.native; + const auto result = delegated ? + mod.runtime->service->update( + mod.runtime->providerContext, mod.context.get(), &error) : + mod.native->fn_update(&error); if (result != MOD_OK) { - fail_mod(mod, result, lifecycle_error_message("mod_update", result, error)); + fail_mod(mod, result, + lifecycle_error_message( + delegated ? "runtime update" : "mod_update", result, error)); } } catch (const std::exception& e) { - fail_mod(mod, MOD_ERROR, fmt::format("Exception in mod_update: {}", e.what())); + fail_mod(mod, MOD_ERROR, fmt::format("Exception in mod update: {}", e.what())); } catch (...) { fail_mod(mod, MOD_ERROR, "Unknown exception in mod_update"); } diff --git a/src/dusk/mods/loader/loader.hpp b/src/dusk/mods/loader/loader.hpp index 6b9fd39232..6fb1f13b6f 100644 --- a/src/dusk/mods/loader/loader.hpp +++ b/src/dusk/mods/loader/loader.hpp @@ -1,10 +1,9 @@ #pragma once #include -#include #include -#include "miniz.h" +#include "dusk/archive.hpp" #include "dusk/mod_loader.hpp" namespace dusk::mods { @@ -24,21 +23,22 @@ public: virtual std::vector readFile(const std::string& fileName) = 0; virtual std::vector getFileNames() = 0; virtual size_t getFileSize(const std::string& fileName) = 0; + virtual bool file_exists(std::string const& fileName) = 0; + virtual bool directory_exists(std::string const& fileName) = 0; }; class ModBundleZip final : public ModBundle { public: - explicit ModBundleZip(std::vector&& data); - ~ModBundleZip() override; + explicit ModBundleZip(const std::filesystem::path& path); + ~ModBundleZip() override = default; std::vector readFile(const std::string& fileName) override; std::vector getFileNames() override; size_t getFileSize(const std::string& fileName) override; + bool file_exists(const std::string& fileName) override; + bool directory_exists(const std::string& fileName) override; private: - std::vector zip_data; - mz_zip_archive res_zip{}; - bool res_zip_open = false; - std::mutex m_mutex; + archive::ZipArchive m_archive; }; class ModBundleDisk final : public ModBundle { @@ -48,6 +48,8 @@ public: std::vector readFile(const std::string& fileName) override; std::vector getFileNames() override; size_t getFileSize(const std::string& fileName) override; + bool file_exists(const std::string& fileName) override; + bool directory_exists(const std::string& fileName) override; private: [[nodiscard]] std::filesystem::path toRealPath(const std::string& fileName) const; @@ -58,7 +60,6 @@ LoadedMod* mod_from_context(ModContext* context); const LoadedMod* mod_from_context(const ModContext* context); const char* mod_id_from_context(ModContext* context); void fail_mod(LoadedMod& mod, ModResult code, std::string_view message); -bool is_safe_resource_path(std::string_view path); std::string escape_mod_id_for_config(std::string_view id); } // namespace dusk::mods diff --git a/src/dusk/mods/loader/manifest.cpp b/src/dusk/mods/loader/manifest.cpp new file mode 100644 index 0000000000..9f6bea0737 --- /dev/null +++ b/src/dusk/mods/loader/manifest.cpp @@ -0,0 +1,174 @@ +#include "manifest.hpp" + +#include "loader.hpp" +#include "natives.hpp" +#include "packages.hpp" + +#include "dusk/mods/log_buffer.hpp" +#include "dusk/utilities.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +using namespace std::string_literals; +namespace fs = std::filesystem; + +namespace dusk::mods { +namespace { + +class InvalidModDataException : public std::runtime_error { +public: + explicit InvalidModDataException(const std::string& msg) : runtime_error(msg) {} + + explicit InvalidModDataException(const char* msg) : runtime_error(msg) {} +}; + +bool bundle_has_file(ModBundle& bundle, const std::string& path) { + try { + bundle.getFileSize(path); + return true; + } catch (const std::runtime_error&) { + return false; + } +} + +std::string resolve_image_path(ModBundle& bundle, const std::string& modId, std::string_view key, + const std::string& manifestPath, const std::string& defaultPath) { + if (!manifestPath.empty()) { + if (!utils::is_safe_resource_path(manifestPath)) { + log::write( + modId, LOG_LEVEL_WARN, "invalid {} path '{}' in mod.json", key, manifestPath); + } else if (!bundle_has_file(bundle, manifestPath)) { + log::write( + modId, LOG_LEVEL_WARN, "{} path '{}' not found in bundle", key, manifestPath); + } else { + return manifestPath; + } + } + if (bundle_has_file(bundle, defaultPath)) { + return defaultPath; + } + return {}; +} + +uint16_t parse_runtime_version_component(std::string_view text, std::string_view fieldName) { + uint32_t value = 0; + const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value); + if (text.empty() || error != std::errc{} || end != text.data() + text.size() || + value > UINT16_MAX) + { + throw InvalidModDataException(fmt::format("Invalid {} in runtime version pin", fieldName)); + } + return static_cast(value); +} + +std::optional parse_runtime(const nlohmann::json& manifest) { + const auto field = manifest.find("runtime"); + if (field == manifest.end()) { + return std::nullopt; + } + if (!field->is_string()) { + throw InvalidModDataException("runtime must be a string"); + } + + const std::string pin = field->get(); + const auto at = pin.rfind('@'); + if (at == std::string::npos || at == 0 || at + 1 == pin.size() || pin.find('@') != at || + at >= MOD_META_SERVICE_ID_SIZE) + { + throw InvalidModDataException( + "runtime must be a service id followed by @major or @major.minor"); + } + + const std::string_view version{pin.data() + at + 1, pin.size() - at - 1}; + const auto dot = version.find('.'); + if (dot != std::string_view::npos && version.find('.', dot + 1) != std::string_view::npos) { + throw InvalidModDataException("runtime version pin has too many components"); + } + + DelegatedModRuntime result; + result.id = pin.substr(0, at); + result.major = parse_runtime_version_component( + dot == std::string_view::npos ? version : version.substr(0, dot), "major version"); + if (dot != std::string_view::npos) { + result.minMinor = parse_runtime_version_component(version.substr(dot + 1), "minor version"); + } + return result; +} + +} // namespace + +LoadedManifest load_manifest(const std::filesystem::path& modPath, ModBundle& bundle) { + const auto metaJson = bundle.readFile("mod.json"); + auto j = nlohmann::json::parse(metaJson); + + std::string metaId = j.value("id", ""); + std::string metaName = j.value("name", ""); + std::string metaVersion = j.value("version", ""); + std::string metaAuthor = j.value("author", ""); + std::string metaDescription = j.value("description", ""); + std::string metaIcon = j.value("icon", ""); + std::string metaBanner = j.value("banner", ""); + + if (!utils::is_valid_mod_id(metaId)) { + throw InvalidModDataException{fmt::format( + "Invalid mod ID '{}'; expected lowercase letters, digits, or underscores separated " + "by single periods, with no leading or trailing period.", + metaId)}; + } + + if (metaName.empty()) { + metaName = borealis::io::fs_path_to_string(modPath.stem()); + } + if (metaVersion.empty()) { + metaVersion = "?"s; + } + if (metaAuthor.empty()) { + metaAuthor = "unknown"s; + } + + std::string iconPath = resolve_image_path(bundle, metaId, "icon", metaIcon, "res/icon.png"s); + std::string bannerPath = + resolve_image_path(bundle, metaId, "banner", metaBanner, "res/banner.png"s); + + return LoadedManifest{ + .metadata = + { + std::move(metaId), + std::move(metaName), + std::move(metaVersion), + std::move(metaAuthor), + std::move(metaDescription), + std::move(iconPath), + std::move(bannerPath), + }, + .runtime = parse_runtime(j), + }; +} + +bool inspect_mod_bundle( + const fs::path& path, ModMetadata& metadata, std::string& error, bool* hasNative) noexcept { + try { + auto bundle = load_bundle(path, false); + metadata = load_manifest(path, *bundle).metadata; + if (hasNative != nullptr) { + *hasNative = std::ranges::any_of(bundle->getFileNames(), + [](const auto& name) { return has_native_library_extension(name); }); + } + error.clear(); + return true; + } catch (const std::exception& exception) { + error = exception.what(); + } catch (...) { + error = "Unknown bundle validation error"; + } + return false; +} + +} // namespace dusk::mods diff --git a/src/dusk/mods/loader/manifest.hpp b/src/dusk/mods/loader/manifest.hpp new file mode 100644 index 0000000000..e3eea70298 --- /dev/null +++ b/src/dusk/mods/loader/manifest.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "dusk/mod_loader.hpp" + +namespace dusk::mods { + +struct LoadedManifest { + ModMetadata metadata; + std::optional runtime; +}; + +LoadedManifest load_manifest(const std::filesystem::path& modPath, ModBundle& bundle); + +} // namespace dusk::mods diff --git a/src/dusk/mods/loader/natives.cpp b/src/dusk/mods/loader/natives.cpp new file mode 100644 index 0000000000..1db66e508b --- /dev/null +++ b/src/dusk/mods/loader/natives.cpp @@ -0,0 +1,620 @@ +#include "natives.hpp" + +#include "loader.hpp" +#include "native_module.hpp" + +#include "dusk/data.hpp" +#include "dusk/mods/log_buffer.hpp" +#include "dusk/mods/svc/registry.hpp" +#include "dusk/utilities.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; +namespace fs = std::filesystem; + +#if defined(_WIN32) +#if defined(_M_ARM64) +static constexpr std::string_view k_nativePlatform = "windows-arm64"sv; +#elif defined(_M_X64) +static constexpr std::string_view k_nativePlatform = "windows-amd64"sv; +#elif defined(_M_IX86) +static constexpr std::string_view k_nativePlatform = "windows-x86"sv; +#else +static constexpr std::string_view k_nativePlatform = ""sv; +#endif +static constexpr std::string_view k_nativeLibName = "mod.dll"sv; +#elif defined(__ANDROID__) +#if defined(__aarch64__) +static constexpr std::string_view k_nativePlatform = "android-aarch64"sv; +#elif defined(__x86_64__) +static constexpr std::string_view k_nativePlatform = "android-x86_64"sv; +#else +static constexpr std::string_view k_nativePlatform = ""sv; +#endif +static constexpr std::string_view k_nativeLibName = "mod.so"sv; +#elif defined(__APPLE__) +#include +#if TARGET_OS_IOS +static constexpr std::string_view k_nativePlatform = "ios-arm64"sv; +#elif TARGET_OS_TV +static constexpr std::string_view k_nativePlatform = "tvos-arm64"sv; +#elif defined(__aarch64__) +static constexpr std::string_view k_nativePlatform = "macos-arm64"sv; +#elif defined(__x86_64__) +static constexpr std::string_view k_nativePlatform = "macos-x86_64"sv; +#else +static constexpr std::string_view k_nativePlatform = ""sv; +#endif +static constexpr std::string_view k_nativeLibName = "mod.so"sv; +#elif defined(__linux__) +#if defined(__aarch64__) +static constexpr std::string_view k_nativePlatform = "linux-aarch64"sv; +#elif defined(__x86_64__) +static constexpr std::string_view k_nativePlatform = "linux-x86_64"sv; +#elif defined(__i386__) +static constexpr std::string_view k_nativePlatform = "linux-x86"sv; +#else +static constexpr std::string_view k_nativePlatform = ""sv; +#endif +static constexpr std::string_view k_nativeLibName = "mod.so"sv; +#else +static constexpr std::string_view k_nativePlatform = ""sv; +static constexpr std::string_view k_nativeLibName = ""sv; +#endif + +namespace dusk::mods { + +bool has_native_library_extension(std::string_view name) { + const auto endsWith = [name](std::string_view extension) { + if (name.size() < extension.size()) { + return false; + } + const auto suffix = name.substr(name.size() - extension.size()); + return std::ranges::equal(suffix, extension, [](char lhs, char rhs) { + const auto lower = [](char value) { + return value >= 'A' && value <= 'Z' ? static_cast(value + ('a' - 'A')) : + value; + }; + return lower(lhs) == lower(rhs); + }); + }; + return endsWith(".dll"sv) || endsWith(".so"sv) || endsWith(".dylib"sv); +} + +namespace { + +constexpr std::string_view k_nativeLibDir = "lib/"sv; + +class DirectoryRollback { +public: + ~DirectoryRollback() { + if (!mPath.empty()) { + std::error_code ec; + fs::remove_all(mPath, ec); + } + } + + void set_path(fs::path path) { mPath = std::move(path); } + void release() { mPath.clear(); } + +private: + fs::path mPath; +}; + +struct NativeRuntimeLocation { + std::string entry; + std::vector runtimeEntries; + bool anyLibs = false; +}; + +struct NativeLocateFailure { + NativeModStatus status; + std::string logMessage; +}; + +using NativeLocateResult = std::variant; + +NativeLocateResult locate_native_runtime(ModBundle& bundle) { + NativeRuntimeLocation result; + const std::string platformPrefix = fmt::format("{}{}/", k_nativeLibDir, k_nativePlatform); + const std::string nativeEntry = platformPrefix + std::string{k_nativeLibName}; + for (const auto& name : bundle.getFileNames()) { + if (name.find('/') == std::string::npos && has_native_library_extension(name)) { + return NativeLocateFailure{ + NativeModStatus::InvalidBundle, + fmt::format( + "native library '{}' found at the root (natives go in /lib/{{platform}})", + name), + }; + } + if (!name.starts_with(k_nativeLibDir)) { + continue; + } + + const std::string_view libPath{ + name.data() + k_nativeLibDir.size(), name.size() - k_nativeLibDir.size()}; + const auto platformEnd = libPath.find('/'); + if (platformEnd != std::string_view::npos) { + const auto entryName = libPath.substr(platformEnd + 1); + if (entryName.find('/') == std::string_view::npos && + (entryName == "mod.dll"sv || entryName == "mod.so"sv)) + { + result.anyLibs = true; + } + } + + if (!k_nativePlatform.empty() && name.starts_with(platformPrefix)) { + const std::string_view relativeName{ + name.data() + platformPrefix.size(), name.size() - platformPrefix.size()}; + if (!utils::is_safe_resource_path(relativeName)) { + continue; + } + result.runtimeEntries.push_back(name); + } + if (name == nativeEntry) { + result.entry = name; + } + } + std::ranges::sort(result.runtimeEntries); + result.runtimeEntries.erase( + std::unique(result.runtimeEntries.begin(), result.runtimeEntries.end()), + result.runtimeEntries.end()); + return result; +} + +bool parse_meta(NativeMod& native, LoadedMod& mod) { + const ModMeta* meta = native.meta; + if (meta->struct_size < sizeof(ModMeta)) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "mod_meta descriptor has invalid size {}", + meta->struct_size); + mod.nativeStatus = NativeModStatus::InvalidMetadata; + return false; + } + const auto* cursor = static_cast(meta->records_begin); + const auto* end = static_cast(meta->records_end); + if (cursor == nullptr || end == nullptr || cursor > end || + (reinterpret_cast(cursor) & 7) != 0) + { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "mod_meta section bounds are invalid"); + mod.nativeStatus = NativeModStatus::InvalidMetadata; + return false; + } + + ModMetaParsed parsed; + size_t headerCount = 0; + const auto invalid = [&](std::string_view why) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "invalid metadata record at offset {}: {}", + cursor - static_cast(meta->records_begin), why); + mod.nativeStatus = NativeModStatus::InvalidMetadata; + return false; + }; + + while (cursor < end) { + if (end - cursor < 8) { + return invalid("trailing bytes"); + } + uint64_t first = 0; + std::memcpy(&first, cursor, sizeof(first)); + if (first == 0) { // linker padding / bounds sentinel + cursor += 8; + continue; + } + + const auto* rec = reinterpret_cast(cursor); + const size_t size = rec->size; + if (size < 8 || size % 8 != 0 || size > static_cast(end - cursor)) { + return invalid("bad record size"); + } + + switch (rec->kind) { + case MOD_META_PAD: + break; + case MOD_META_HEADER: { + if (size < sizeof(ModMetaHeader)) { + return invalid("truncated header record"); + } + const auto* header = reinterpret_cast(rec); + ++headerCount; + parsed.abiVersion = header->abi_version; + break; + } + case MOD_META_IMPORT: { + if (size < sizeof(ModMetaImport)) { + return invalid("truncated import record"); + } + auto* record = reinterpret_cast(const_cast(cursor)); + if (!utils::bounded_string(record->service_id.chars, sizeof(record->service_id.chars))) + { + return invalid("unterminated import service id"); + } + parsed.imports.push_back(record); + break; + } + case MOD_META_EXPORT: { + if (size < sizeof(ModMetaExport)) { + return invalid("truncated export record"); + } + auto* record = reinterpret_cast(const_cast(cursor)); + if (!utils::bounded_string(record->service_id.chars, sizeof(record->service_id.chars))) + { + return invalid("unterminated export service id"); + } + parsed.exports.push_back(record); + break; + } + case MOD_META_HOOK_FN: { + if (size < sizeof(ModMetaHookFn)) { + return invalid("truncated hook record"); + } + parsed.hookFns.push_back( + reinterpret_cast(const_cast(cursor))); + break; + } + case MOD_META_HOOK_MEM: { + if (size <= sizeof(ModMetaHookMem)) { + return invalid("truncated hook record"); + } + auto* record = reinterpret_cast(const_cast(cursor)); + const char* strings = reinterpret_cast(cursor) + sizeof(ModMetaHookMem); + const size_t capacity = size - sizeof(ModMetaHookMem); + const auto vtableName = utils::bounded_string(strings, capacity); + if (!vtableName) { + return invalid("unterminated hook vtable symbol"); + } + const size_t vtableLen = vtableName->size(); + if (!utils::bounded_string(strings + vtableLen + 1, capacity - vtableLen - 1)) { + return invalid("unterminated hook display name"); + } + parsed.hookMems.push_back(record); + break; + } + case MOD_META_HOOK_MEM_EXT: { + if (size <= sizeof(ModMetaHookMemExt)) { + return invalid("truncated extended hook record"); + } + auto* record = reinterpret_cast(const_cast(cursor)); + if (record->pmf_size <= MOD_META_HOOK_MEM_CAPACITY || + record->pmf_size > MOD_META_HOOK_MEM_EXT_CAPACITY || record->materialize == nullptr) + { + return invalid("bad extended hook member-pointer size"); + } + const char* strings = reinterpret_cast(cursor) + sizeof(ModMetaHookMemExt); + const size_t capacity = size - sizeof(ModMetaHookMemExt); + const auto vtableName = utils::bounded_string(strings, capacity); + if (!vtableName) { + return invalid("unterminated extended hook vtable symbol"); + } + const size_t vtableLen = vtableName->size(); + if (!utils::bounded_string(strings + vtableLen + 1, capacity - vtableLen - 1)) { + return invalid("unterminated extended hook display name"); + } + parsed.hookMemExts.push_back(record); + break; + } + case MOD_META_HOOK_NAME: { + if (size <= sizeof(ModMetaHookName)) { + return invalid("truncated hook record"); + } + auto* record = reinterpret_cast(const_cast(cursor)); + const char* name = reinterpret_cast(cursor) + sizeof(ModMetaHookName); + if (!utils::bounded_string(name, size - sizeof(ModMetaHookName))) { + return invalid("unterminated hook symbol name"); + } + parsed.hookNames.push_back(record); + break; + } + default: + // Additive record kinds may appear within a format version; skip them. + log::write(mod.metadata.id, LOG_LEVEL_DEBUG, "skipping unknown metadata record kind {}", + rec->kind); + break; + } + cursor += size; + } + + if (headerCount != 1) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "expected 1 metadata header record, found {}", + headerCount); + mod.nativeStatus = NativeModStatus::InvalidMetadata; + return false; + } + if (parsed.abiVersion != MOD_ABI_VERSION) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "expects ABI v{} but engine is v{}, skipping", + parsed.abiVersion, MOD_ABI_VERSION); + mod.nativeStatus = NativeModStatus::ApiVersionMismatch; + return false; + } + + native.parsed = std::move(parsed); + return true; +} + +std::string native_status_message(const NativeModStatus status) { + switch (status) { + case NativeModStatus::BuildDisabled: + return "Code mods are disabled on this Dusklight build"; + case NativeModStatus::ModMissingPlatform: + return fmt::format("Mod not supported on this platform ({})", k_nativePlatform); + case NativeModStatus::ApiVersionMismatch: + // TODO: differentiate whether mod or Dusklight is out of date + return "Mod ABI version mismatch"; + case NativeModStatus::MissingExport: + return "Missing required mod API exports"; + case NativeModStatus::InvalidMetadata: + return "Invalid mod metadata records"; + case NativeModStatus::InvalidBundle: + return "Invalid mod bundle layout (old mod?)"; + case NativeModStatus::Unknown: + return "Unknown mod load failure"; + case NativeModStatus::None: + case NativeModStatus::Loaded: + break; + } + return "native mod failed to load"; +} + +} // namespace + +fs::path ModLoader::external_native_lib_path(const LoadedMod& mod) const { + if (k_nativeLibName.empty()) { + return {}; + } + const auto& libDir = m_searchDirs[mod.searchDirIndex].nativeLibDir; + if (libDir.empty()) { + return {}; + } + const auto filename = fmt::format("{}{}", mod.metadata.id, + borealis::io::fs_path_to_string(fs::path{k_nativeLibName}.extension())); + fs::path path = libDir / fs::path{filename}; + std::error_code ec; + if (!fs::is_regular_file(path, ec)) { + return {}; + } + return path; +} + +void ModLoader::load_native( + LoadedMod& mod, const std::string& dllEntry, const std::vector& runtimeEntries) { + if (!EnableCodeMods) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "Code mods are not available in this build"); + mod.nativeStatus = NativeModStatus::BuildDisabled; + return; + } + + const fs::path cacheDir = m_cacheDir / mod.metadata.id; + const fs::path scratchDir = cacheDir / "data"; + std::error_code ec; + fs::create_directories(scratchDir, ec); + if (ec) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to create mod directory {}: {}", + data::abbreviated_path_string(scratchDir), ec.message()); + return; + } + mod.dir = fs::absolute(scratchDir); + mod.dirUtf8 = borealis::io::fs_path_to_string(mod.dir); + + fs::path libPath; + fs::path runtimeDir; + DirectoryRollback runtimeDirRollback; + if (mod.nativeInPlace) { + if (!dllEntry.empty()) { + libPath = mod.modPath / dllEntry; + } else if (auto external = external_native_lib_path(mod); !external.empty()) { + libPath = std::move(external); + } else { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, + "no native library named {} found; skipping", k_nativeLibName); + mod.nativeStatus = NativeModStatus::ModMissingPlatform; + return; + } + runtimeDir = libPath.parent_path(); + } else { + if (dllEntry.empty()) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, + "no native library named {} found; skipping", k_nativeLibName); + mod.nativeStatus = NativeModStatus::ModMissingPlatform; + return; + } + + // Every generation gets a new directory. The main module and all of its runtime + // libraries therefore have fresh paths and can coexist with a previous generation + // that is still unwinding after a reload. + runtimeDir = cacheDir / fmt::format("g{}", ++mod.cacheGeneration); + runtimeDirRollback.set_path(runtimeDir); + fs::create_directories(runtimeDir, ec); + if (ec) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, + "failed to create native runtime directory {}: {}", + data::abbreviated_path_string(runtimeDir), ec.message()); + return; + } + + const std::string platformPrefix = fmt::format("{}{}/", k_nativeLibDir, k_nativePlatform); + for (const auto& entry : runtimeEntries) { + if (!entry.starts_with(platformPrefix)) { + continue; + } + const std::string_view relativeName{ + entry.data() + platformPrefix.size(), entry.size() - platformPrefix.size()}; + if (!utils::is_safe_resource_path(relativeName)) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, + "unsafe native runtime path '{}'; skipping", entry); + return; + } + + const fs::path outputPath = runtimeDir / fs::path{relativeName}; + fs::create_directories(outputPath.parent_path(), ec); + if (ec) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, + "failed to create directory for {}: {}", entry, ec.message()); + return; + } + + std::vector data; + try { + data = mod.bundle->readFile(entry); + } catch (const std::exception& e) { + log::write( + mod.metadata.id, LOG_LEVEL_ERROR, "failed to extract {}: {}", entry, e.what()); + return; + } + + std::ofstream out(outputPath, std::ios::binary | std::ios::out); + if (!out) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to write {}", entry); + return; + } + out.write(reinterpret_cast(data.data()), + static_cast(data.size())); + if (!out) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to write {}", entry); + return; + } + } + + libPath = runtimeDir / fs::path{dllEntry}.filename(); + } + + auto nativeMod = std::make_unique(); + try { + nativeMod->handle = std::make_unique(libPath); + } catch (const std::runtime_error& e) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to open {}: {}", + data::abbreviated_path_string(libPath), e.what()); + return; + } + + nativeMod->meta = nativeMod->handle->LookupSymbol("mod_meta"); + nativeMod->contextSymbol = nativeMod->handle->LookupSymbol("mod_ctx"); + nativeMod->fn_initialize = nativeMod->handle->LookupSymbol("mod_initialize"); + nativeMod->fn_update = nativeMod->handle->LookupSymbol("mod_update"); + nativeMod->fn_shutdown = nativeMod->handle->LookupSymbol("mod_shutdown"); + + if (!nativeMod->meta || !nativeMod->contextSymbol || !nativeMod->fn_initialize || + !nativeMod->fn_update || !nativeMod->fn_shutdown) + { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, + "{} missing required mod API exports; skipping", + data::abbreviated_path_string(libPath)); + mod.nativeStatus = NativeModStatus::MissingExport; + return; + } + + if (!parse_meta(*nativeMod, mod)) { + return; + } + + if (nativeMod->contextSymbol == nullptr) { + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "missing required mod_ctx export"); + mod.nativeStatus = NativeModStatus::MissingExport; + return; + } + *nativeMod->contextSymbol = mod.context.get(); + + mod.nativePath = fs::absolute(libPath); + mod.nativeDir = fs::absolute(runtimeDir); + mod.nativeDirUtf8 = borealis::io::fs_path_to_string(mod.nativeDir); + mod.native = std::move(nativeMod); + mod.nativeStatus = NativeModStatus::Loaded; + runtimeDirRollback.release(); +} + +bool ModLoader::load_native_if_present(LoadedMod& mod) { + const auto result = locate_native_runtime(*mod.bundle); + if (const auto* failure = std::get_if(&result)) { + mod.nativeStatus = failure->status; + log::write(mod.metadata.id, LOG_LEVEL_ERROR, "{}", failure->logMessage); + fail_mod(mod, MOD_ERROR, native_status_message(failure->status)); + return false; + } + + const auto& native = std::get(result); + if (mod.runtime.has_value() && + (native.anyLibs || (mod.nativeInPlace && !external_native_lib_path(mod).empty()))) + { + mod.nativeStatus = NativeModStatus::InvalidBundle; + fail_mod(mod, MOD_CONFLICT, "A mod cannot declare both runtime and native code"); + return false; + } + if (!native.anyLibs && !(mod.nativeInPlace && !external_native_lib_path(mod).empty())) { + mod.nativeStatus = NativeModStatus::None; + return true; + } + + mod.nativeStatus = NativeModStatus::Unknown; + load_native(mod, native.entry, native.runtimeEntries); + if (mod.nativeStatus != NativeModStatus::Loaded) { + fail_mod(mod, MOD_ERROR, native_status_message(mod.nativeStatus)); + return false; + } + return true; +} + +void ModLoader::unload_native(LoadedMod& mod) { + if (!mod.native) { + return; + } + // Deferred dlclose: this mod's code may still be on the stack below the current tick + m_retiredNatives.push_back( + {std::move(mod.native), mod.nativeInPlace ? fs::path{} : std::move(mod.nativeDir)}); + mod.nativePath.clear(); + mod.nativeDir.clear(); + mod.nativeDirUtf8.clear(); +} + +void ModLoader::drain_retired_natives() { + for (auto& retired : m_retiredNatives) { + retired.native.reset(); + if (!retired.directory.empty()) { + std::error_code ec; + fs::remove_all(retired.directory, ec); + } + } + m_retiredNatives.clear(); +} + +bool ModLoader::ensure_native_loaded(LoadedMod& mod) { + if (mod.native || mod.nativeStatus == NativeModStatus::None) { + return true; + } + return load_native_if_present(mod); +} + +ModManifestInfo build_manifest_info(const ModMetaParsed& parsed) { + ModManifestInfo info; + info.imports.reserve(parsed.imports.size()); + for (const auto* record : parsed.imports) { + if (!utils::is_valid_name(record->service_id.chars)) { + continue; + } + info.imports.push_back({ + .id = record->service_id.chars, + .major = record->major_version, + .minMinor = record->min_minor_version, + .required = (record->rec.flags & SERVICE_IMPORT_OPTIONAL) == 0, + }); + } + info.exports.reserve(parsed.exports.size()); + for (const auto* record : parsed.exports) { + if (!utils::is_valid_name(record->service_id.chars)) { + continue; + } + info.exports.push_back({ + .id = record->service_id.chars, + .major = record->major_version, + }); + } + return info; +} + +} // namespace dusk::mods diff --git a/src/dusk/mods/loader/natives.hpp b/src/dusk/mods/loader/natives.hpp new file mode 100644 index 0000000000..657f3d011a --- /dev/null +++ b/src/dusk/mods/loader/natives.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace dusk::mods { + +struct ModManifestInfo; +struct ModMetaParsed; + +bool has_native_library_extension(std::string_view name); +ModManifestInfo build_manifest_info(const ModMetaParsed& parsed); + +} // namespace dusk::mods diff --git a/src/dusk/mods/loader/packages.cpp b/src/dusk/mods/loader/packages.cpp new file mode 100644 index 0000000000..071f5b276e --- /dev/null +++ b/src/dusk/mods/loader/packages.cpp @@ -0,0 +1,201 @@ +#include "packages.hpp" + +#include "loader.hpp" +#include "manifest.hpp" + +#include "dusk/data.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace fs = std::filesystem; + +namespace dusk::mods { +namespace { + +constexpr borealis::Log Log{"dusk::mods::loader"}; + +} // namespace + +std::unique_ptr load_bundle(const fs::path& modPath, bool fromDir) { + if (fromDir) { + return std::make_unique(modPath); + } else { + return std::make_unique(modPath); + } +} + +int compare_package_versions(std::string_view lhs, std::string_view rhs) { + const auto left = borealis::update::parse_version(lhs); + const auto right = borealis::update::parse_version(rhs); + if (left && right) { + return borealis::update::compare_version(*left, *right); + } + return static_cast(left.has_value()) - static_cast(right.has_value()); +} + +std::vector scan_packages(std::span searchDirs) { + std::vector packages; + for (size_t dirIndex = 0; dirIndex < searchDirs.size(); ++dirIndex) { + const auto& searchDir = searchDirs[dirIndex]; + std::error_code error; + bool alreadyScanned = false; + for (size_t earlier = 0; earlier < dirIndex && !alreadyScanned; ++earlier) { + alreadyScanned = fs::equivalent(searchDirs[earlier].path, searchDir.path, error); + } + if (alreadyScanned || !fs::is_directory(searchDir.path)) { + continue; + } + std::vector entries; + for (const auto& entry : fs::directory_iterator{searchDir.path}) { + if ((entry.is_directory() && fs::exists(entry.path() / "mod.json")) || + (entry.is_regular_file() && entry.path().extension() == ".dusk")) + { + entries.push_back(entry); + } + } + std::ranges::sort(entries, {}, &fs::directory_entry::path); + for (const auto& entry : entries) { + try { + const bool fromDirectory = entry.is_directory(); + auto bundle = load_bundle(entry.path(), fromDirectory); + packages.push_back({ + .path = fs::absolute(entry.path()), + .metadata = load_manifest(entry.path(), *bundle).metadata, + .searchDirIndex = static_cast(dirIndex), + .fromDirectory = fromDirectory, + .symlink = entry.is_symlink(), + }); + } catch (const std::exception& exception) { + Log.error("bad mod package {}: {}", + data::abbreviated_path_string(entry.path()), exception.what()); + } + } + } + return packages; +} + +const PackageCandidate* select_package(std::span packages, + std::string_view modId) { + const PackageCandidate* selected = nullptr; + for (const auto& package : packages) { + if (package.metadata.id != modId) { + continue; + } + const int order = selected ? + compare_package_versions(package.metadata.version, selected->metadata.version) : 1; + if (order > 0 || (order == 0 && + (package.searchDirIndex < selected->searchDirIndex || + (package.searchDirIndex == selected->searchDirIndex && package.path < selected->path)))) + { + selected = &package; + } + } + return selected; +} + +void record_package_sources(LoadedMod& mod, std::span packages) { + mod.hasUserPackage = false; + mod.hasBundledCopy = false; + for (const auto& package : packages) { + if (package.metadata.id != mod.metadata.id) { + continue; + } + if (package.searchDirIndex != 0) { + mod.hasBundledCopy = true; + } else if (!package.fromDirectory && !package.symlink) { + mod.hasUserPackage = true; + } + } +} + +PackageInstallResult install_package(const fs::path& path, const fs::path& destination, + std::string_view modId) { + const auto userDir = destination.parent_path(); + std::error_code error; + std::string validationError; + const auto destinationStatus = fs::symlink_status(destination, error); + if (error == std::errc::no_such_file_or_directory) { + error.clear(); + } + if (error) { + return {.replaced = false, + .error = fmt::format("Could not inspect the destination: {}", error.message())}; + } + if (fs::exists(destinationStatus)) { + ModMetadata existingMetadata; + if (!fs::is_regular_file(destinationStatus) || + !inspect_mod_bundle(destination, existingMetadata, validationError) || + existingMetadata.id != modId) + { + return { + .replaced = false, + .error = "The destination filename is already used by another package", + }; + } + } + std::vector duplicates; + for (fs::directory_iterator entry{userDir, error}, end; !error && entry != end; + entry.increment(error)) + { + const auto& candidate = entry->path(); + if (candidate.extension() != ".dusk") { + continue; + } + const bool regularFile = entry->is_regular_file(error); + if (error) { + break; + } + if (!regularFile) { + continue; + } + ModMetadata candidateMetadata; + if (inspect_mod_bundle(candidate, candidateMetadata, validationError) && + candidateMetadata.id == modId) + { + duplicates.push_back(candidate); + } + } + if (error) { + return {.replaced = false, + .error = fmt::format("Could not scan the mods directory: {}", error.message())}; + } + + std::string replaceError; + if (!borealis::io::atomic_replace(path, destination, replaceError)) { + return { + .replaced = false, + .error = std::move(replaceError), + }; + } + + std::string cleanupError; + for (const auto& duplicate : duplicates) { + // Replacing a file on a case-insensitive filesystem can preserve its old spelling. + const bool destinationAlias = + fs::equivalent(duplicate, destination, error) && !fs::is_symlink(duplicate, error); + error.clear(); + if (destinationAlias) { + if (duplicate.filename() == destination.filename()) { + continue; + } + fs::rename(duplicate, destination, error); + } else { + fs::remove(duplicate, error); + } + if (error) { + cleanupError = fmt::format("Installed, but could not consolidate '{}': {}", + data::abbreviated_path_string(duplicate), error.message()); + Log.warn("{}", cleanupError); + } + } + return {.replaced = true, .error = std::move(cleanupError)}; +} + +} // namespace dusk::mods diff --git a/src/dusk/mods/loader/packages.hpp b/src/dusk/mods/loader/packages.hpp new file mode 100644 index 0000000000..0271dca5fe --- /dev/null +++ b/src/dusk/mods/loader/packages.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "dusk/mod_loader.hpp" + +#include +#include +#include +#include +#include +#include + +namespace dusk::mods { + +struct PackageCandidate { + std::filesystem::path path; + ModMetadata metadata; + uint32_t searchDirIndex = 0; + bool fromDirectory = false; + bool symlink = false; +}; + +std::vector scan_packages(std::span searchDirs); +int compare_package_versions(std::string_view lhs, std::string_view rhs); +const PackageCandidate* select_package( + std::span packages, std::string_view modId); +void record_package_sources(LoadedMod& mod, std::span packages); + +std::unique_ptr load_bundle(const std::filesystem::path& modPath, bool fromDir); + +struct PackageInstallResult { + bool replaced = false; + std::string error; +}; + +PackageInstallResult install_package(const std::filesystem::path& path, + const std::filesystem::path& destination, std::string_view modId); + +} // namespace dusk::mods diff --git a/src/dusk/mods/log_buffer.hpp b/src/dusk/mods/log_buffer.hpp index 33a143705d..2787ccb07f 100644 --- a/src/dusk/mods/log_buffer.hpp +++ b/src/dusk/mods/log_buffer.hpp @@ -1,13 +1,12 @@ #pragma once -#include -#include -#include -#include +#include "mods/svc/log.h" #include -#include "mods/svc/log.h" +#include +#include +#include namespace dusk::mods::log { diff --git a/src/dusk/mods/manifest.hpp b/src/dusk/mods/manifest.hpp index c6bc03f038..586759a8b1 100644 --- a/src/dusk/mods/manifest.hpp +++ b/src/dusk/mods/manifest.hpp @@ -1,10 +1,10 @@ #pragma once +#include "mods/svc/hook.h" + #include #include -#include "mods/svc/hook.h" - namespace dusk::mods::manifest { // Symbol flags mirrored from symgen. diff --git a/src/dusk/mods/queue.cpp b/src/dusk/mods/queue.cpp new file mode 100644 index 0000000000..85b131fa66 --- /dev/null +++ b/src/dusk/mods/queue.cpp @@ -0,0 +1,783 @@ +#include "queue.hpp" +#include "updates.hpp" + +#include "dusk/hash.hpp" +#include "dusk/mod_loader.hpp" +#include "dusk/ui/ui.hpp" +#include "dusk/utilities.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dusk::mods::queue { +namespace { + +using clock = std::chrono::steady_clock; + +struct VerifyResult { + std::string error; + ModMetadata metadata; + std::filesystem::path stagedPath; + bool canceled = false; +}; + +enum class PendingIntent { None, Pause, Cancel }; + +struct QueueItem { + std::string key; + Request request; + std::string previousVersion; + State state = State::Queued; + std::filesystem::path partialPath; + uint64_t completed = 0; + uint64_t total = 0; + std::string message; + int retryCount = 0; + clock::time_point retryAt{}; + borealis::Task task; + borealis::Task verification; + ModOperationHandle operation; + PendingIntent pendingIntent = PendingIntent::None; +}; + +std::vector queueItems; +uint64_t nextQueueKey = 1; + +QueueItem* find_queue_item(std::string_view key) { + const auto item = std::ranges::find(queueItems, key, + [](const QueueItem& candidate) { return std::string_view{candidate.key}; }); + return item == queueItems.end() ? nullptr : &*item; +} + +QueueItem* find_queue_item_by_mod_id(std::string_view id) { + const auto item = std::ranges::find(queueItems, id, + [](const QueueItem& candidate) { return std::string_view{candidate.request.id}; }); + return item == queueItems.end() ? nullptr : &*item; +} + +const Download* url_source(const QueueItem& item) { + return std::get_if(&item.request.source); +} + +const LocalFile* local_source(const QueueItem& item) { + return std::get_if(&item.request.source); +} + +std::string lowercase(std::string value) { + std::ranges::transform(value, value.begin(), [](char character) { + return character >= 'A' && character <= 'Z' ? static_cast(character + ('a' - 'A')) : + character; + }); + return value; +} + +bool valid_sha256(std::string_view value) { + return value.size() == 64 && std::ranges::all_of(value, [](char character) { + return (character >= '0' && character <= '9') || (character >= 'a' && character <= 'f') || + (character >= 'A' && character <= 'F'); + }); +} + +std::string sha256_file( + const std::filesystem::path& path, borealis::TaskContext& context, std::string& error) { + std::ifstream input{path, std::ios::binary}; + if (!input) { + error = "Could not open the downloaded package"; + return {}; + } + + hash::Sha256 hash; + std::array buffer{}; + uint64_t completed = 0; + while (input) { + if (context.cancel_requested()) { + error = "Canceled"; + return {}; + } + input.read(reinterpret_cast(buffer.data()), buffer.size()); + const auto count = input.gcount(); + if (count > 0) { + hash.update(std::span{buffer.data(), static_cast(count)}); + completed += static_cast(count); + context.report_progress(completed); + } + } + if (!input.eof()) { + error = "Could not read the downloaded package"; + return {}; + } + + return hash.finish(); +} + +std::filesystem::path staging_path( + const std::filesystem::path& stagingDir, std::string_view modId, std::string_view key) { + return stagingDir / + fmt::format("{}-{}.dusk.part", utils::safe_filename(modId), utils::safe_filename(key)); +} + +bool copy_to_staging(const std::filesystem::path& source, const std::filesystem::path& destination, + uint64_t total, borealis::TaskContext& context, std::string& error) { + std::error_code filesystemError; + std::filesystem::create_directories(destination.parent_path(), filesystemError); + if (filesystemError) { + error = + fmt::format("Could not create the staging directory: {}", filesystemError.message()); + return false; + } + std::ifstream input{source, std::ios::binary}; + std::ofstream output{destination, std::ios::binary | std::ios::trunc}; + if (!input || !output) { + error = "Could not stage the local package"; + return false; + } + std::array buffer{}; + uint64_t completed = 0; + while (input) { + if (context.cancel_requested()) { + error = "Canceled"; + output.close(); + std::filesystem::remove(destination, filesystemError); + return false; + } + input.read(buffer.data(), buffer.size()); + const auto count = input.gcount(); + if (count > 0) { + output.write(buffer.data(), count); + completed += static_cast(count); + context.report_progress(completed, total); + } + } + output.close(); + if (!input.eof() || !output) { + error = "Could not copy the local package"; + std::filesystem::remove(destination, filesystemError); + return false; + } + return true; +} + +VerifyResult verify_url_package(const std::filesystem::path& path, const Request& request, + const Download& source, const std::filesystem::path& stagingDir, std::string key, + borealis::TaskContext& context) { + std::error_code ec; + const auto actualSize = std::filesystem::file_size(path, ec); + if (ec) { + return {.error = fmt::format("Could not read the downloaded package: {}", ec.message())}; + } + if (actualSize != source.size) { + return {.error = "Package size mismatch"}; + } + + std::string error; + const auto actualHash = sha256_file(path, context, error); + if (!error.empty()) { + return {.error = std::move(error)}; + } + if (context.cancel_requested()) { + return {.canceled = true}; + } + if (actualHash != lowercase(source.sha256)) { + return {.error = "Package checksum mismatch"}; + } + + ModMetadata metadata; + if (!inspect_mod_bundle(path, metadata, error)) { + return {.error = fmt::format("Invalid mod package: {}", error)}; + } + if (metadata.id != request.id) { + return {.error = "Package ID does not match the catalog entry"}; + } + if (metadata.version != request.version) { + return {.error = "Package version does not match the catalog entry"}; + } + if (context.cancel_requested()) { + return {.canceled = true}; + } + const auto stagedPath = staging_path(stagingDir, metadata.id, key); + std::filesystem::create_directories(stagedPath.parent_path(), ec); + if (ec) { + return {.error = fmt::format("Could not create the staging directory: {}", ec.message())}; + } + std::string replaceError; + if (!borealis::io::atomic_replace(path, stagedPath, replaceError)) { + return {.error = std::move(replaceError)}; + } + return {.metadata = std::move(metadata), .stagedPath = stagedPath}; +} + +VerifyResult verify_local_package(const LocalFile& source, const std::filesystem::path& stagingDir, + std::string key, borealis::TaskContext& context) { + std::error_code ec; + const auto size = std::filesystem::file_size(source.path, ec); + if (ec) { + return {.error = fmt::format("Could not read the local package: {}", ec.message())}; + } + context.report_progress(0, size); + const auto stagedPath = stagingDir / fmt::format("{}.dusk.part", key); + std::string error; + if (!copy_to_staging(source.path, stagedPath, size, context, error)) { + return {.error = std::move(error), .canceled = context.cancel_requested()}; + } + // Validate the bytes handed to the loader; the source may change during copying. + ModMetadata metadata; + if (!inspect_mod_bundle(stagedPath, metadata, error)) { + std::filesystem::remove(stagedPath, ec); + return {.error = fmt::format("Invalid mod package: {}", error)}; + } + return {.metadata = std::move(metadata), .stagedPath = stagedPath}; +} + +void remove_partial(const QueueItem& item) { + std::error_code ec; + if (!item.partialPath.empty()) { + std::filesystem::remove(item.partialPath, ec); + auto metadataPath = item.partialPath; + metadataPath += ".borealis-resume.json"; + std::filesystem::remove(metadataPath, ec); + } +} + +void fail(QueueItem& item, std::string message, bool discardPartial) { + item.task = {}; + item.verification = {}; + item.state = State::Failed; + item.message = std::move(message); + item.pendingIntent = PendingIntent::None; + if (discardPartial) { + remove_partial(item); + item.completed = 0; + } + const char* title = + local_source(item) != nullptr ? "Mod package failed" : "Mod download failed"; + ui::push_toast({ + .type = "warning", + .title = title, + .content = fmt::format("{}: {}", item.request.name, item.message), + .duration = std::chrono::seconds{6}, + }); +} + +bool retryable(const borealis::http::Result& result) { + if (result.error == borealis::http::Error::Network || + result.error == borealis::http::Error::Timeout) + { + return true; + } + const int status = result.response.statusCode; + return result.error == borealis::http::Error::None && + (status == 408 || status == 425 || status == 429 || status >= 500); +} + +void schedule_retry(QueueItem& item, std::string message) { + ++item.retryCount; + const int delaySeconds = std::min(30, 1 << std::min(item.retryCount, 4)); + item.retryAt = clock::now() + std::chrono::seconds{delaySeconds}; + item.state = State::Retrying; + item.message = std::move(message); + item.task = {}; +} + +void start_download(QueueItem& item) { + if (item.request.update) { + if (auto error = updates::validate(*item.request.update); !error.empty()) { + fail(item, std::move(error), false); + return; + } + } + const auto* source = url_source(item); + if (source == nullptr) { + fail(item, "The install source is not a URL", false); + return; + } + const auto userDir = ModLoader::instance().user_mods_dir(); + if (userDir.empty()) { + fail(item, "No writable mods directory is configured", false); + return; + } + + item.partialPath = + userDir / ".downloads" / fmt::format("{}.dusk.part", utils::safe_filename(item.request.id)); + std::error_code ec; + std::filesystem::create_directories(item.partialPath.parent_path(), ec); + if (ec) { + fail(item, fmt::format("Could not create the download directory: {}", ec.message()), false); + return; + } + + item.pendingIntent = PendingIntent::None; + item.message.clear(); + item.total = source->size; + item.state = State::Downloading; + item.task = borealis::http::start({ + .url = source->url, + .downloadTo = item.partialPath, + .connectTimeout = std::chrono::seconds{10}, + .idleTimeout = std::chrono::seconds{15}, + .totalTimeout = std::nullopt, + }); +} + +void start_local_verification(QueueItem& item) { + const auto* source = local_source(item); + if (source == nullptr) { + fail(item, "The install source is not a local file", false); + return; + } + const auto userDir = ModLoader::instance().user_mods_dir(); + if (userDir.empty()) { + fail(item, "No writable mods directory is configured", false); + return; + } + item.state = State::Verifying; + item.message.clear(); + item.completed = 0; + std::error_code error; + item.total = std::filesystem::file_size(source->path, error); + const auto stagingDir = userDir / ".staging"; + const auto local = *source; + item.verification = + borealis::spawn([local, stagingDir, key = item.key](borealis::TaskContext& context) { + return verify_local_package(local, stagingDir, key, context); + }); +} + +void finish_download(QueueItem& item) { + const auto progress = item.task.progress(); + item.completed = std::max(item.completed, progress.completed); + + std::optional completed; + std::string taskError; + bool taskFailed = false; + try { + completed = item.task.try_take(); + } catch (const std::exception& exception) { + taskError = exception.what(); + taskFailed = true; + } catch (...) { + taskError = "The download failed"; + taskFailed = true; + } + if (!completed && !taskFailed) { + return; + } + item.task = {}; + + switch (std::exchange(item.pendingIntent, PendingIntent::None)) { + case PendingIntent::Cancel: + remove_partial(item); + item.completed = 0; + item.state = State::Canceled; + return; + case PendingIntent::Pause: + return; + case PendingIntent::None: + break; + } + + if (taskFailed) { + schedule_retry(item, std::move(taskError)); + return; + } + + if (completed->error != borealis::http::Error::None || completed->response.statusCode < 200 || + completed->response.statusCode >= 300) + { + const auto message = !completed->message.empty() ? completed->message : + completed->response.statusCode != 0 ? + fmt::format("Server returned HTTP {}", + completed->response.statusCode) : + "The download failed"; + if (retryable(*completed)) { + schedule_retry(item, message); + } else { + fail(item, message, true); + } + return; + } + + const auto* source = url_source(item); + if (source == nullptr) { + fail(item, "The install source changed", true); + return; + } + item.completed = source->size; + item.state = State::Verifying; + item.message.clear(); + const auto stagingDir = ModLoader::instance().user_mods_dir() / ".staging"; + item.verification = + borealis::spawn([path = item.partialPath, request = item.request, source = *source, + stagingDir, key = item.key](borealis::TaskContext& context) { + return verify_url_package(path, request, source, stagingDir, key, context); + }); +} + +void finish_verification(QueueItem& item) { + VerifyResult result; + try { + auto completed = item.verification.try_take(); + if (!completed) { + return; + } + result = std::move(*completed); + } catch (const std::exception& exception) { + result.error = exception.what(); + } catch (...) { + result.error = "Package verification failed"; + } + item.verification = {}; + if (result.canceled || item.pendingIntent == PendingIntent::Cancel) { + item.pendingIntent = PendingIntent::None; + if (!result.stagedPath.empty()) { + std::error_code error; + std::filesystem::remove(result.stagedPath, error); + } + remove_partial(item); + item.state = State::Canceled; + item.completed = 0; + return; + } + if (!result.error.empty()) { + fail(item, std::move(result.error), true); + return; + } + remove_partial(item); + item.partialPath = result.stagedPath; + if (const auto duplicate = find_queue_item_by_mod_id(result.metadata.id); + duplicate != nullptr && duplicate != &item && !is_terminal(duplicate->state)) + { + fail(item, "This mod already has an active install", true); + return; + } + if (local_source(item) != nullptr && !item.request.id.empty() && + (item.request.id != result.metadata.id || item.request.version != result.metadata.version)) + { + fail(item, "The local package changed after confirmation", true); + return; + } + if (item.request.update) { + if (auto error = updates::validate(*item.request.update); !error.empty()) { + fail(item, std::move(error), true); + return; + } + } + item.request.id = result.metadata.id; + item.request.name = result.metadata.name; + item.request.version = result.metadata.version; + item.completed = item.total; + item.state = State::Handoff; + item.operation = + ModLoader::instance().request_install(std::move(result.stagedPath), item.request.update); +} + +Item snapshot(const QueueItem& item) { + Item result{ + .id = item.key, + .modId = item.request.id, + .name = item.request.name, + .version = item.request.version, + .previousVersion = item.previousVersion, + .state = item.state, + .completed = item.completed, + .total = item.total, + .message = item.message, + .local = local_source(item) != nullptr, + .icon = item.request.icon, + }; + if (item.task) { + result.completed = std::max(result.completed, item.task.progress().completed); + } + if (item.verification) { + const auto progress = item.verification.progress(); + result.completed = progress.completed; + if (progress.total) { + result.total = *progress.total; + } + } + if (item.state == State::Retrying) { + const auto remaining = item.retryAt - clock::now(); + result.retrySeconds = std::max( + 0, static_cast(std::chrono::ceil(remaining).count())); + } + return result; +} + +} // namespace + +bool enqueue(Request request, std::string* keyOut) { + const auto* source = std::get_if(&request.source); + const auto* local = std::get_if(&request.source); + if (source != nullptr) { + if (request.id.empty() || request.version.empty() || source->size == 0 || + !source->url.starts_with("https://") || !valid_sha256(source->sha256)) + { + return false; + } + } else if (local == nullptr || request.id.empty() || request.version.empty()) { + return false; + } + const auto total = source == nullptr ? 0 : source->size; + if (request.name.empty()) { + request.name = request.id; + } + + std::string previousVersion; + if (const auto* installed = ModLoader::instance().find_mod(request.id); + installed && installed->metadata.version != request.version) + { + previousVersion = installed->metadata.version; + } + if (auto* existing = find_queue_item_by_mod_id(request.id)) { + if (!is_terminal(existing->state)) { + return false; + } + existing->request = std::move(request); + existing->previousVersion = std::move(previousVersion); + existing->state = State::Queued; + existing->completed = 0; + existing->total = total; + existing->partialPath.clear(); + existing->message.clear(); + existing->retryCount = 0; + existing->operation.reset(); + existing->pendingIntent = PendingIntent::None; + if (keyOut != nullptr) { + *keyOut = existing->key; + } + return true; + } + + const auto key = fmt::format("queue-{}", nextQueueKey++); + if (keyOut != nullptr) { + *keyOut = key; + } + queueItems.push_back({ + .key = key, + .request = std::move(request), + .previousVersion = std::move(previousVersion), + .total = total, + }); + return true; +} + +void update() { + for (auto item = queueItems.begin(); item != queueItems.end();) { + if (item->task && item->task.ready()) { + finish_download(*item); + } + if (item->state == State::Verifying && item->verification && item->verification.ready()) { + finish_verification(*item); + } + if (item->state == State::Handoff && item->operation && + item->operation->state != ModOperation::State::Pending) + { + item->message = item->operation->message; + item->state = item->operation->state == ModOperation::State::Succeeded ? + State::Installed : + State::InstallFailed; + item->operation.reset(); + } + ++item; + } + + for (auto& item : queueItems) { + if (item.task || item.verification || item.operation) { + return; + } + if (is_terminal(item.state) || item.state == State::Paused) { + continue; + } + if (item.state == State::Downloading || item.state == State::Verifying) { + return; + } + if (item.state == State::Retrying && clock::now() < item.retryAt) { + return; + } + if (item.state == State::Queued || item.state == State::Retrying) { + if (local_source(item) != nullptr) { + start_local_verification(item); + } else { + start_download(item); + } + } + return; + } +} + +void shutdown() noexcept { + for (auto& item : queueItems) { + if (item.task) { + item.task.cancel(); + } + if (item.verification) { + item.verification.cancel(); + } + } + queueItems.clear(); +} + +std::vector items() { + std::vector result; + result.reserve(queueItems.size()); + for (const auto& item : queueItems) { + result.push_back(snapshot(item)); + } + return result; +} + +std::optional find(std::string_view id) { + const auto* item = find_queue_item(id); + return item == nullptr ? std::nullopt : std::optional{snapshot(*item)}; +} + +std::optional find_by_mod_id(std::string_view id) { + const auto* item = find_queue_item_by_mod_id(id); + return item == nullptr ? std::nullopt : std::optional{snapshot(*item)}; +} + +bool has_active_items() { + return std::ranges::any_of( + queueItems, [](const QueueItem& item) { return !is_terminal(item.state); }); +} + +size_t item_count() noexcept { + return queueItems.size(); +} + +size_t active_items_ahead(std::string_view id) noexcept { + size_t result = 0; + for (const auto& item : queueItems) { + if (item.request.id == id) { + break; + } + if (!is_terminal(item.state)) { + ++result; + } + } + return result; +} + +void pause(std::string_view id) { + auto* item = find_queue_item(id); + if (item == nullptr || local_source(*item) != nullptr || + item->pendingIntent == PendingIntent::Cancel) + { + return; + } + if (item->state == State::Queued || item->state == State::Retrying) { + item->state = State::Paused; + return; + } + if (item->state == State::Downloading && item->task) { + item->completed = std::max(item->completed, item->task.progress().completed); + item->pendingIntent = PendingIntent::Pause; + item->state = State::Paused; + item->task.cancel(); + } +} + +void resume(std::string_view id) { + auto* item = find_queue_item(id); + if (item == nullptr || item->state != State::Paused || + item->pendingIntent == PendingIntent::Cancel) + { + return; + } + item->state = State::Queued; +} + +void retry(std::string_view id) { + auto* item = find_queue_item(id); + if (item == nullptr) { + return; + } + if (item->state == State::InstallFailed) { + auto* mod = ModLoader::instance().find_mod(item->request.id); + if (mod != nullptr) { + if (mod->activation_failed()) { + item->message.clear(); + item->state = State::Handoff; + item->operation = ModLoader::instance().request_reactivate(item->request.id); + } else { + item->message.clear(); + item->state = State::Installed; + } + return; + } + } else if (item->state != State::Failed) { + return; + } + remove_partial(*item); + item->completed = 0; + item->retryCount = 0; + item->message.clear(); + item->state = State::Queued; +} + +void cancel(std::string_view id) { + auto* item = find_queue_item(id); + if (item == nullptr || item->state == State::Handoff || is_terminal(item->state)) { + return; + } + if (item->task) { + item->pendingIntent = PendingIntent::Cancel; + item->message = "Canceling..."; + item->task.cancel(); + return; + } + if (item->verification) { + item->pendingIntent = PendingIntent::Cancel; + item->message = "Canceling..."; + item->verification.cancel(); + return; + } + remove_partial(*item); + item->pendingIntent = PendingIntent::None; + item->completed = 0; + item->state = State::Canceled; +} + +void clear(std::string_view id) { + const auto item = std::ranges::find( + queueItems, id, [](const QueueItem& candidate) { return std::string_view{candidate.key}; }); + if (item != queueItems.end() && is_terminal(item->state)) { + queueItems.erase(item); + } +} + +void remove_by_mod_id(std::string_view id) { + std::erase_if(queueItems, + [id](const QueueItem& item) { return std::string_view{item.request.id} == id; }); +} + +void pause_all() { + std::vector ids; + for (const auto& item : queueItems) { + if (item.state == State::Queued || item.state == State::Retrying || + item.state == State::Downloading) + { + ids.push_back(item.key); + } + } + for (const auto& id : ids) { + pause(id); + } +} + +} // namespace dusk::mods::queue diff --git a/src/dusk/mods/queue.hpp b/src/dusk/mods/queue.hpp new file mode 100644 index 0000000000..20c047d8cd --- /dev/null +++ b/src/dusk/mods/queue.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include "updates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dusk::mods::queue { + +enum class State { + Queued, + Downloading, + Paused, + Retrying, + Verifying, + Handoff, + Installed, + InstallFailed, + Failed, + Canceled, +}; + +[[nodiscard]] constexpr bool is_failed(State state) noexcept { + return state == State::InstallFailed || state == State::Failed; +} + +[[nodiscard]] constexpr bool is_completed(State state) noexcept { + return state == State::Installed || state == State::Canceled; +} + +[[nodiscard]] constexpr bool is_terminal(State state) noexcept { + return is_completed(state) || is_failed(state); +} + +[[nodiscard]] constexpr bool is_install_result(State state) noexcept { + return state == State::Installed || state == State::InstallFailed; +} + +struct LocalFile { + std::filesystem::path path; +}; + +using Source = std::variant; + +struct Icon { + std::string url; + uint32_t width = 0; + uint32_t height = 0; +}; + +struct Request { + std::string id; + std::string name; + std::string version; + Source source; + std::optional icon; + std::optional update; +}; + +struct Item { + // Queue key, independent of the mod ID. + std::string id; + std::string modId; + std::string name; + std::string version; + std::string previousVersion; + State state = State::Queued; + uint64_t completed = 0; + uint64_t total = 0; + std::string message; + int retrySeconds = 0; + bool local = false; + std::optional icon; +}; + +/** Adds an install, replacing failed or canceled work for the same package ID. */ +bool enqueue(Request request, std::string* key = nullptr); + +void update(); +void shutdown() noexcept; + +[[nodiscard]] std::vector items(); +[[nodiscard]] std::optional find(std::string_view key); +[[nodiscard]] std::optional find_by_mod_id(std::string_view id); +[[nodiscard]] bool has_active_items(); +[[nodiscard]] size_t item_count() noexcept; +[[nodiscard]] size_t active_items_ahead(std::string_view id) noexcept; + +void pause(std::string_view id); +void resume(std::string_view id); +void retry(std::string_view id); +void cancel(std::string_view id); +void clear(std::string_view id); +void remove_by_mod_id(std::string_view id); +void pause_all(); + +} // namespace dusk::mods::queue diff --git a/src/dusk/mods/services.hpp b/src/dusk/mods/services.hpp new file mode 100644 index 0000000000..57a9157d8e --- /dev/null +++ b/src/dusk/mods/services.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +namespace dusk::mods { + +struct ServiceImport { + std::string id; + uint16_t major = 0; + uint16_t minMinor = 0; + bool optional = false; + bool operator==(const ServiceImport&) const = default; +}; + +struct ServiceExport { + std::string id; + uint16_t major = 0; + uint16_t minor = 0; + std::string providerId; + bool operator==(const ServiceExport&) const = default; +}; + +inline std::string service_key(std::string_view id, uint16_t major) { + return std::string{id} + '\x1f' + std::to_string(major); +} + +} // namespace dusk::mods diff --git a/src/dusk/mods/svc/actor.cpp b/src/dusk/mods/svc/actor.cpp new file mode 100644 index 0000000000..dbcf547d55 --- /dev/null +++ b/src/dusk/mods/svc/actor.cpp @@ -0,0 +1,403 @@ +#include "mods/svc/actor.h" + +#include "actor.hpp" +#include "internal.hpp" +#include "registry.hpp" + +#include "dusk/mod_loader.hpp" +#include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" + +#include "d/d_stage.h" +#include "f_op/f_op_actor_tag.h" +#include "f_pc/f_pc_deletor.h" + +#include + +#include + +namespace dusk::mods::svc::actor_impl { +namespace { + +aurora::Module Log("dusk::mods::actor"); + +SlotMap> s_slots; +std::unordered_map procNameToHandle; +std::unordered_map fullNameToHandle; + +ActorSlot* get_actor_slot(void* actorPtr) { + auto* actor = static_cast(actorPtr); + const auto handle = procNameToHandle.find(actor->name); + if (handle == procNameToHandle.end()) { + return nullptr; + } + auto* slot = s_slots.find(handle->second); + return slot != nullptr ? slot->value.get() : nullptr; +} + +int actor_is_delete(void* actorPtr) { + auto* slot = get_actor_slot(actorPtr); + if (slot == nullptr || slot->forceDelete) { + return 1; + } + return slot->isDeleteFunction(actorPtr); +} + +int actor_delete(void* actorPtr) { + auto* slot = get_actor_slot(actorPtr); + if (slot == nullptr) { + return 1; + } + const bool forceDelete = slot->forceDelete; + const int result = slot->deleteFunction(actorPtr); + return forceDelete ? 1 : result; +} + +ModResult register_actor(ModContext* ctx, const ActorProfileDesc* desc, ProfileName* outProfileName, + ActorHandle* outActorHandle) { + auto* owner = mod_from_context(ctx); + if (owner == nullptr || desc == nullptr || outProfileName == nullptr || + outActorHandle == nullptr) + { + return MOD_INVALID_ARGUMENT; + } + const auto name = utils::bounded_string(desc->name, sizeof(desc->name)); + if (!name || name->empty() || desc->process_size < sizeof(fopAc_ac_c) || + desc->create_function == nullptr || desc->delete_function == nullptr || + desc->execute_function == nullptr || desc->is_delete_function == nullptr || + desc->draw_function == nullptr) + { + return MOD_INVALID_ARGUMENT; + } + + // If another mod has already registered an actor with this name, note a conflict + if (get_stageinfo_from_full_name(desc->name)) { + return MOD_CONFLICT; + } + + const auto handle = s_slots.emplace(*owner, + std::make_unique( + ActorSlot{{desc->create_function, actor_delete, desc->execute_function, actor_is_delete, + desc->draw_function}, + desc->delete_function, desc->is_delete_function, false, + { + {}, // Set after registered + 0, // Set after registered to a slot + -1 // Use the default argument + }, + { + /* Layer ID */ fpcLy_CURRENT_e, + /* List ID */ desc->priority_group, + /* List Prio */ fpcPi_CURRENT_e, // Always fpcPi_CURRENT_e + /* Proc Name */ 0, + /* Proc SubMtd */ &g_fpcLf_Method.base, // usually: &g_fpcLf_Method.base + /* Size */ (u32)desc->process_size, + /* Size Other */ 0, // Always 0 + /* Parameters */ 0, // Always 0 + /* Leaf SubMtd */ &g_fopAc_Method.base, // usually &g_fopAc_Method.base + /* Draw Prio */ desc->draw_priority, + /* Actor SubMtd */ nullptr, // set this later + /* Status */ desc->status, + /* Group */ desc->group, + /* Cull Type */ desc->cull_type, + }})); + + s32 procNameFull = fpcNm_MAX_NUM + s_slots.index_of(handle); + if (procNameFull >= 0x7FFF) { + s_slots.erase(handle); + Log.error("Hit registered actor limit (0x7FFF) while registering actor {}", desc->name); + return MOD_ERROR; + } + + ActorSlot& slot = *s_slots.find(handle)->value.get(); + strncpy(slot.objNameInf.name, desc->name, sizeof(slot.objNameInf.name) - 1); + slot.profile.sub_method = &slot.methodTable; + s16 procName = (s16)procNameFull; + procNameToHandle[procName] = handle; + fullNameToHandle[std::string(slot.objNameInf.name)] = handle; + slot.objNameInf.procname = (s16)procName; + slot.profile.base.base.name = procName; + *outProfileName = procName; + *outActorHandle = handle; + + return MOD_OK; +} + +// Must be called before the slot associated with the handle is erased +void remove_handle_from_maps(ActorHandle handle) { + auto entry = s_slots.find(handle); + if (entry == nullptr) { + return; + } + + std::string fullName = entry->value->objNameInf.name; + + const auto it = fullNameToHandle.find(fullName); + if (it != fullNameToHandle.end()) { + fullNameToHandle.erase(it); + } + + const auto it2 = procNameToHandle.find(entry->value->profile.base.base.name); + if (it2 != procNameToHandle.end()) { + procNameToHandle.erase(it2); + } +} + +void request_delete_all_actors_of_name(s16 procName) { + node_class* node = g_fopAcTg_Queue.mpHead; + while (node != nullptr) { + node_class* next = NODE_GET_NEXT(node); + auto* actor = + static_cast(reinterpret_cast(node)->mpTagData); + if (actor->name == procName) { + fopAcM_delete(actor); + } + node = next; + } +} + +bool finish_delete_all_actors_of_name(s16 procName) { + bool complete = true; + + node_class* node = g_fpcDtTg_Queue.mpHead; + while (node != nullptr) { + node_class* next = NODE_GET_NEXT(node); + auto* tag = reinterpret_cast(node); + auto* proc = static_cast(tag->base.mpTagData); + if (proc->name == procName) { + tag->timer = 0; + const int deleteStatus = fpcDtTg_Do( + tag, [](void* proc) { return fpcDt_deleteMethod((base_process_class*)proc); }); + if (deleteStatus == 0) { + complete = false; + } + } + node = next; + } + return complete; +} + +bool has_actor_of_name(s16 procName) { + node_class* node = g_fopAcTg_Queue.mpHead; + while (node != nullptr) { + auto* actor = + static_cast(reinterpret_cast(node)->mpTagData); + if (actor->name == procName) { + return true; + } + node = NODE_GET_NEXT(node); + } + return false; +} + +ModResult unregister_actor(ModContext* ctx, ActorHandle handle) { + auto* owner = mod_from_context(ctx); + if (owner == nullptr) { + return MOD_INVALID_ARGUMENT; + } + auto* slot = s_slots.find_owned(handle, *owner); + if (slot == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + const s16 actorProcName = slot->value->profile.base.base.name; + slot->value->forceDelete = true; + request_delete_all_actors_of_name(actorProcName); + const bool deletesFinished = finish_delete_all_actors_of_name(actorProcName); + if (!deletesFinished || has_actor_of_name(actorProcName)) { + slot->value->forceDelete = false; + Log.warn("Actor profile '{}' could not be unregistered synchronously", + slot->value->objNameInf.name); + return MOD_UNAVAILABLE; + } + + remove_handle_from_maps(handle); + s_slots.erase_owned(handle, *owner); + + return MOD_OK; +} + +ModResult create_actor( + ModContext* ctx, ProfileName name, const ActorSpawnParams* params, ActorId* outId) { + cXyz pos = {params->position.x, params->position.y, params->position.z}; + csXyz angle = {params->angle.x, params->angle.y, params->angle.z}; + cXyz scale = {params->scale.x, params->scale.y, params->scale.z}; + fpc_ProcID id = fopAcM_create(name, 0xFFFF, params->parameters, &pos, params->room_num, &angle, + &scale, params->argument, params->create_function); + + if (id == fpcM_ERROR_PROCESS_ID_e) { + Log.error("Error Creating Actor with profile name {}", name); + return MOD_ERROR; + } + + if (outId) { + *outId = id; + } + return MOD_OK; +} + +ModResult create_actor_from_name( + ModContext* ctx, const char* name, const ActorSpawnParams* params, ActorId* outId) { + dStage_objectNameInf* objectName = dStage_searchName(name); + if (objectName == nullptr) { + Log.error("Attempted to create actor ({}) but it can't be found!", name); + return MOD_ERROR; + } + + ActorSpawnParams copy = *params; + copy.argument = objectName->argument; + + return create_actor(ctx, objectName->procname, ©, outId); +} + +ModResult create_child_actor(ModContext* ctx, ProfileName name, ActorId parentID, + const ActorSpawnParams* params, ActorId* outId) { + cXyz pos = {params->position.x, params->position.y, params->position.z}; + csXyz angle = {params->angle.x, params->angle.y, params->angle.z}; + cXyz scale = {params->scale.x, params->scale.y, params->scale.z}; + fpc_ProcID id = fopAcM_createChild(name, parentID, params->parameters, &pos, params->room_num, + &angle, &scale, params->argument, params->create_function); + + if (id == fpcM_ERROR_PROCESS_ID_e) { + Log.error("Error Creating Actor with profile name {} as child of actor with id {}", name, + parentID); + return MOD_ERROR; + } + + if (outId) { + *outId = id; + } + return MOD_OK; +} + +ModResult create_child_actor_from_name(ModContext* ctx, const char* name, ActorId parentID, + const ActorSpawnParams* params, ActorId* outId) { + dStage_objectNameInf* objectName = dStage_searchName(name); + if (objectName == nullptr) { + Log.error("Attempted to create actor ({}) but it can't be found!", name); + return MOD_ERROR; + } + + ActorSpawnParams copy = *params; + copy.argument = objectName->argument; + + return create_child_actor(ctx, objectName->procname, parentID, ©, outId); +} + +ModResult get_actor_id(ModContext* ctx, ProfileName name, ActorId* outId) { + fopAc_ac_c* actor = fopAcM_SearchByName(name); + if (actor == nullptr) { + Log.error("Attempting to get actor by profile name ({}) but it doesn't exist!", name); + return MOD_ERROR; + } + + *outId = fopAcM_GetID(actor); + return MOD_OK; +} + +ModResult get_actor_room_num(ModContext* ctx, ActorId actorId, int8_t* outRoomNum) { + fopAc_ac_c* actor = fopAcM_SearchByID(actorId); + if (actor == nullptr) { + Log.error("Attempted to get room number of actor Id ({}) but it doesn't exist!", actorId); + return MOD_ERROR; + } + + *outRoomNum = fopAcM_GetRoomNo(actor); + return MOD_OK; +} + +ModResult delete_actor(ModContext* ctx, ActorId actorId) { + if (mod_from_context(ctx) == nullptr) { + return MOD_INVALID_ARGUMENT; + } + fopAc_ac_c* actor = fopAcM_SearchByID(actorId); + if (actor == nullptr) { + Log.warn("Attempted to delete actor with ID ({}) but it doesn't exist!", actorId); + return MOD_OK; + } + return fopAcM_delete(actor) != 0 ? MOD_OK : MOD_UNAVAILABLE; +} + +} // namespace + +process_profile_definition* get_profile_from_proc_name(s16 name) { + const auto& it = procNameToHandle.find(name); + if (it == procNameToHandle.end()) { + return nullptr; + } + auto entry = s_slots.find(it->second); + if (entry) { + return &entry->value->profile.base.base; + } + return nullptr; +} + +const char* get_full_name_from_proc_name(s16 name) { + const auto& it = procNameToHandle.find(name); + if (it == procNameToHandle.end()) { + return ""; + } + auto entry = s_slots.find(it->second); + if (entry) { + return entry->value->objNameInf.name; + } + return ""; +} + +dStage_objectNameInf* get_stageinfo_from_full_name(const std::string& name) { + const auto& it = fullNameToHandle.find(name); + if (it == fullNameToHandle.end()) { + return nullptr; + } + auto entry = s_slots.find(it->second); + if (entry) { + return &entry->value->objNameInf; + } + return nullptr; +} + +void actor_remove_mod(LoadedMod& mod) { + std::vector handles; + s_slots.for_each([&](const ActorHandle handle, const auto& slot) { + if (slot.owner == &mod) { + handles.push_back(handle); + } + }); + + for (const auto handle : handles) { + const auto result = unregister_actor(mod.context.get(), handle); + if (result != MOD_OK) { + Log.error("Actor profile could not be removed during mod deactivation"); + } + } +} + +} // namespace dusk::mods::svc::actor_impl + +namespace dusk::mods::svc { +namespace { + +constexpr ActorService s_actorService{ + .header = SERVICE_HEADER(ActorService, ACTOR_SERVICE_MAJOR, ACTOR_SERVICE_MINOR), + .register_actor = actor_impl::register_actor, + .unregister_actor = actor_impl::unregister_actor, + .create_actor_from_name = actor_impl::create_actor_from_name, + .create_actor = actor_impl::create_actor, + .create_child_actor_from_name = actor_impl::create_child_actor_from_name, + .create_child_actor = actor_impl::create_child_actor, + .get_actor_id = actor_impl::get_actor_id, + .get_actor_room_num = actor_impl::get_actor_room_num, + .delete_actor = actor_impl::delete_actor, +}; + +} + +constinit const ServiceModule g_actorModule{ + .id = ACTOR_SERVICE_ID, + .majorVersion = ACTOR_SERVICE_MAJOR, + .minorVersion = ACTOR_SERVICE_MINOR, + .service = &s_actorService, + .modDeactivating = actor_impl::actor_remove_mod, +}; + +} // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/actor.hpp b/src/dusk/mods/svc/actor.hpp new file mode 100644 index 0000000000..7e68760503 --- /dev/null +++ b/src/dusk/mods/svc/actor.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +#include "f_op/f_op_actor_mng.h" + +namespace dusk::mods::svc::actor_impl { + +struct ActorSlot { + actor_method_class methodTable; + int (*deleteFunction)(void*); + int (*isDeleteFunction)(void*); + bool forceDelete; + dStage_objectNameInf objNameInf; + actor_process_profile_definition profile; +}; + +process_profile_definition* get_profile_from_proc_name(s16 name); + +dStage_objectNameInf* get_stageinfo_from_full_name(const std::string& name); + +const char* get_full_name_from_proc_name(s16 name); // Returns "" when not found + +}; // namespace dusk::mods::svc::actor_impl diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp new file mode 100644 index 0000000000..3735408106 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -0,0 +1,56 @@ +#include "audio_res.hpp" + +#include "helpers/cast.hpp" +#include "mods/svc/audio_res.h" + +#include "../registry.hpp" + +namespace dusk::mods::svc { + +namespace audio_res { +namespace { + +using namespace dusk::helpers::cast; + +constexpr AudioResService s_audioResService{ + .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), + .default_wave_info = &wsys::default_wave_info, + .replace_wave = &wsys::insert_replace_wave, + .add_wave = &wsys::insert_add_wave, + .remove_wave = &wsys::remove_wave, + .default_effect_info = &bst::default_effect_info, + .replace_sound_table_effect = &bst::replace_sound_table_effect, + .add_sound_table_effect = &bst::add_sound_table_effect, + .default_stream_info = &bst::default_stream_info, + .replace_sound_table_stream = &bst::replace_sound_table_stream, + .add_sound_table_stream = &bst::add_sound_table_stream, + .remove_sound_table = &bst::remove_sound_table, +}; + +void frame_end() { + wsys::frame_end(); + bst::frame_end(); +} + +void mod_detached(LoadedMod& mod) { + wsys::remove_mod(mod); + bst::remove_mod(mod); +} + +void sync_audio_replacements() { + wsys::sync_audio_replacements(); + bst::sync_audio_replacements(); +} + +} // namespace + +} // namespace audio_res + +constinit const ServiceModule g_audioResModule{.id = AUDIO_RES_SERVICE_ID, + .majorVersion = AUDIO_RES_SERVICE_MAJOR, + .minorVersion = AUDIO_RES_SERVICE_MINOR, + .service = &audio_res::s_audioResService, + .modDetached = audio_res::mod_detached, + .lifecycleApplied = audio_res::sync_audio_replacements, + .frameEnd = audio_res::frame_end}; +}; // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp new file mode 100644 index 0000000000..6e7b04e47b --- /dev/null +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -0,0 +1,56 @@ +#pragma once +#include "mods/svc/audio_res.h" + +namespace dusk::mods { +struct LoadedMod; +} + +namespace dusk::mods::svc::audio_res { + +namespace wsys { + +void frame_end(); +void remove_mod(LoadedMod& mod); +void sync_audio_replacements(); + +extern AudioWaveInfo const default_wave_info; + +ModResult insert_replace_wave(ModContext* ctx, AudioWaveBank bank, u16 wave_id, + char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); + +ModResult insert_add_wave(ModContext* ctx, AudioWaveBank bank, char const* file_name, + AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle, u16* out_wave_id); + +ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle); + +} // namespace wsys + +namespace bst { + +void frame_end(); +void remove_mod(LoadedMod const& mod); +void sync_audio_replacements(); + +extern AudioSoundTableEffectInfo const default_effect_info; + +ModResult replace_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + uint16_t effect_id, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle); + +ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id); + +extern AudioSoundTableStreamInfo const default_stream_info; + +ModResult replace_sound_table_stream(ModContext* ctx, uint16_t stream_id, char const* file_path, + AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle); + +ModResult add_sound_table_stream(ModContext* ctx, char const* file_path, + AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, + uint16_t* out_stream_id); + +ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle); + +} // namespace bst + +} // namespace dusk::mods::svc::audio_res diff --git a/src/dusk/mods/svc/audio_res/bst.cpp b/src/dusk/mods/svc/audio_res/bst.cpp new file mode 100644 index 0000000000..917697bed7 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/bst.cpp @@ -0,0 +1,368 @@ +#include "bst.hpp" + +#include "audio_res.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mods/loader/loader.hpp" +#include "dusk/mods/svc/id_allocator.hpp" +#include "dusk/mods/svc/internal.hpp" + +namespace dusk::mods::svc::audio_res::bst { + +namespace { + +bool sound_replacements_dirty = false; + +aurora::Module Log("dusk::mods::svc::audio_res"); + +SlotMap> sound_replacements; + +std::array sound_effect_id_allocator = { + PlainIdAllocator(0x1000), // SYSTEM_SE + PlainIdAllocator(0x1000), // PLAYER_VOICE + PlainIdAllocator(0x1000), // PLAYER_SE + PlainIdAllocator(0x1000), // FOOTNOTE_SE + PlainIdAllocator(0x1000), // COLLISION_SE + PlainIdAllocator(0x1000), // CHARA_VOICE + PlainIdAllocator(0x1000), // CHARA_SE + PlainIdAllocator(0x1000), // ENEMY_SE + PlainIdAllocator(0x1000), // OBJECT_SE + PlainIdAllocator(0x1000), // ENV_SE +}; + +PlainIdAllocator stream_id_allocator(0x1000); + +bool validate_category(SoundEffectCategory const category) { + return category <= SE_CATEGORY_ENV_SE; +} + +uint8_t volume_to_item(float volume) { + volume = std::clamp(volume, 0.0f, 2.0f); + return static_cast(volume * 127); +} + +absl::flat_hash_map> + active_se_replacements; +absl::flat_hash_map> active_stream_replacements; +std::mutex active_replacements_mutex; + +} // namespace + +SoundTableReplacementSlot::SoundTableReplacementSlot(bool const mod_defined, u16 const id) + : mod_defined(mod_defined), id(id), item() {} + +SoundTableReplacementSlot::~SoundTableReplacementSlot() = default; + +SoundEffectReplacementSlot::SoundEffectReplacementSlot( + bool mod_defined, u16 id, SoundEffectCategory category, const AudioSoundTableEffectInfo& info) + : SoundTableReplacementSlot(mod_defined, id), category(category) { + item.mPriority = info.priority; + item.mVolume = volume_to_item(info.volume); + item.mPitch = info.pitch; + + u32 sw_bit = 0; + if (info.always_max_priority) + sw_bit |= SOUND_SW_ALWAYS_MAX_PRIORITY; + if (info.ignore_distance_volume) + sw_bit |= SOUND_SW_IGNORE_DISTANCE_VOL; + if (info.ignore_distance_fx_mix) + sw_bit |= SOUND_SW_IGNORE_FX_MIX; + if (info.ignore_pan) + sw_bit |= SOUND_SW_IGNORE_PAN; + if (info.ignore_dolby) + sw_bit |= SOUND_SW_IGNORE_DOLBY; + sw_bit |= (info.random_volume << SOUND_SW_RANDOM_VOLUME_OFFSET) & SOUND_SW_RANDOM_VOLUME_MASK; + sw_bit |= (info.random_pitch << SOUND_SW_RANDOM_PITCH_OFFSET) & SOUND_SW_RANDOM_PITCH_MASK; + sw_bit |= (info.doppler_power << SOUND_SW_DOPPLER_POWER_OFFSET) & SOUND_SW_DOPPLER_POWER_MASK; + + if (info.volume_dist_class < 8) { + sw_bit |= + (info.volume_dist_class << SOUND_SW_VOL_DIST_BIT_OFFSET) & SOUND_SW_VOL_DIST_BIT_MASK; + } else { + sw_bit |= ((info.volume_dist_class - 8) << SOUND_SW_VOL_DIST_BIT_2_OFFSET) & + SOUND_SW_VOL_DIST_BIT_2_MASK; + } + + if (info.clamp_min_volume) + sw_bit |= SOUND_SW_CLAMP_MIN_VOLUME; + if (info.cull_at_max_distance) + sw_bit |= SOUND_SW_CULL_AT_MAX_DISTANCE; + + item.mSwBit = sw_bit; +} + +u8 SoundEffectReplacementSlot::get_type_id() const { + return SOUND_TYPEID_SOUND_EFFECT; +} + +StreamReplacementSlot::StreamReplacementSlot( + bool mod_defined, u16 id, char const* file_path, const AudioSoundTableStreamInfo& info) + : SoundTableReplacementSlot(mod_defined, id), file_path(file_path) { + stop_on_scene_change = info.stop_on_scene_change; + item.mPriority = info.priority; + item.mVolume = volume_to_item(info.volume); + + u16 panParam = 0; + for (int i = STREAM_MAX_CHILDREN - 1; i >= 0; i--) { + panParam <<= STRM_CH_SHIFT_; + + switch (info.pan_parameters[i]) { + case STREAM_PAN_CENTER: + panParam |= STRM_CH_CENTER; + break; + case STREAM_PAN_LEFT: + panParam |= STRM_CH_LEFT; + break; + case STREAM_PAN_RIGHT: + panParam |= STRM_CH_RIGHT; + break; + } + } + item.mStreamPanParameters = panParam; +} + +u8 StreamReplacementSlot::get_type_id() const { + return stop_on_scene_change ? SOUND_TYPEID_STREAM_ALT : SOUND_TYPEID_STREAM; +} + +std::shared_ptr get_override_for(JAISoundID id) { + switch (id.id_.info.type.parts.sectionID) { + case 0: + return get_override_for_se(id); + case 2: + return get_override_for_stream(id); + default: + return nullptr; + } +} + +std::shared_ptr get_override_for_se(JAISoundID id) { + if (id.id_.info.type.parts.sectionID != 0) { + return nullptr; + } + + std::lock_guard lock(active_replacements_mutex); + + SoundEffectKey const key( + static_cast(id.id_.info.type.parts.groupID), id.id_.info.waveID); + + auto const entry = active_se_replacements.find(key); + if (entry == active_se_replacements.end()) { + return nullptr; + } + + return entry->second; +} + +std::shared_ptr get_override_for_stream(JAISoundID id) { + if (id.id_.info.type.parts.sectionID != 2) { + return nullptr; + } + + std::lock_guard lock(active_replacements_mutex); + + auto const entry = active_stream_replacements.find(id.id_.info.waveID); + if (entry == active_stream_replacements.end()) { + return nullptr; + } + + return entry->second; +} + +void remove_mod(LoadedMod const& mod) { + sound_replacements.erase_all(mod); + + sound_replacements_dirty = true; +} + +void frame_end() { + if (sound_replacements_dirty) { + wsys::sync_audio_replacements(); + } +} + +void sync_audio_replacements() { + sound_replacements_dirty = false; + + absl::flat_hash_map> + new_se_replacements; + absl::flat_hash_map> new_stream_replacements; + + for (auto const& mod : ModLoader::instance().active_mods()) { + sound_replacements.for_each([&](auto, auto const& entry) { + if (entry.owner != &mod) { + return; + } + + std::shared_ptr const& value = entry.value; + auto se = std::dynamic_pointer_cast(value); + if (se) { + new_se_replacements.emplace(SoundEffectKey(se->category, se->id), std::move(se)); + return; + } + + auto stream = std::dynamic_pointer_cast(value); + if (stream) { + new_stream_replacements.emplace(stream->id, std::move(stream)); + return; + } + }); + } + + std::lock_guard lock(active_replacements_mutex); + + std::exchange(active_se_replacements, std::move(new_se_replacements)); + std::exchange(active_stream_replacements, std::move(new_stream_replacements)); +} + +static ModResult insert_sound_table_effect_core(ModContext* ctx, SoundEffectCategory category_id, + uint16_t effect_id, bool mod_defined, AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || !validate_category(category_id)) { + return MOD_INVALID_ARGUMENT; + } + + if (info == nullptr) { + info = &default_effect_info; + } + + auto slot = + std::make_shared(mod_defined, effect_id, category_id, *info); + sound_replacements_dirty = true; + + auto const handle = sound_replacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +ModResult replace_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + uint16_t effect_id, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle) { + return insert_sound_table_effect_core(ctx, category_id, effect_id, false, info, out_handle); +} + +AudioSoundTableEffectInfo const default_effect_info(128, 1, 1); + +ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id) { + if (out_effect_id == nullptr || !validate_category(category_id)) { + return MOD_INVALID_ARGUMENT; + } + + assert(category_id < sound_effect_id_allocator.size()); + + auto& allocator = sound_effect_id_allocator[category_id]; + auto const effect_id = allocator.alloc(); + + auto const result = + insert_sound_table_effect_core(ctx, category_id, effect_id, true, info, out_handle); + if (result != MOD_OK) { + allocator.free(effect_id); + } + + *out_effect_id = effect_id; + + return result; +} + +static ModResult insert_sound_table_stream_core(ModContext* ctx, uint16_t stream_id, + bool mod_defined, char const* file_path, AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || file_path == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + if (info == nullptr) { + info = &default_stream_info; + } + + auto slot = std::make_shared(mod_defined, stream_id, file_path, *info); + sound_replacements_dirty = true; + + auto const handle = sound_replacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +AudioSoundTableStreamInfo const default_stream_info(128, 1, {STREAM_PAN_LEFT, STREAM_PAN_RIGHT}); + +ModResult replace_sound_table_stream(ModContext* ctx, uint16_t stream_id, char const* file_path, + AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle) { + return insert_sound_table_stream_core(ctx, stream_id, false, file_path, info, out_handle); +} + +ModResult add_sound_table_stream(ModContext* ctx, char const* file_path, + AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, + uint16_t* out_stream_id) { + if (out_stream_id == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + auto const stream_id = stream_id_allocator.alloc(); + + auto const result = + insert_sound_table_stream_core(ctx, stream_id, true, file_path, info, out_handle); + if (result != MOD_OK) { + stream_id_allocator.free(stream_id); + } + + *out_stream_id = stream_id; + + return result; +} + +static PlainIdAllocator& id_allocator_for(SoundTableReplacementSlot const* slot) { + if (dynamic_cast(slot)) + return stream_id_allocator; + + if (auto const se = dynamic_cast(slot)) + return sound_effect_id_allocator[se->id]; + + CRASH("Unknown type???"); +} + +static bool sound_table_remove(LoadedMod const& mod, AudioSoundTableHandle const handle) { + auto const found = sound_replacements.find_owned(handle, mod); + if (found == nullptr) { + return false; + } + + if (found->value->mod_defined) { + auto& allocator = id_allocator_for(found->value.get()); + allocator.free(found->value->id); + } + + sound_replacements.erase_owned(handle, mod); + return true; +} + +ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + + if (!sound_table_remove(*mod, handle)) { + Log.error("[{}] remove sound table failed: unknown handle {}", mod->metadata.id, handle); + return MOD_INVALID_ARGUMENT; + } + + return MOD_OK; +} + +} // namespace dusk::mods::svc::audio_res::bst diff --git a/src/dusk/mods/svc/audio_res/bst.hpp b/src/dusk/mods/svc/audio_res/bst.hpp new file mode 100644 index 0000000000..0db6bd7c1f --- /dev/null +++ b/src/dusk/mods/svc/audio_res/bst.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +#include "JSystem/JAudio2/JAUSoundTable.h" +#include "mods/svc/audio_res.h" + +namespace dusk::mods::svc::audio_res::bst { + +struct SoundTableReplacementSlot { + bool mod_defined; + u16 id; + JAUSoundTableItem item; + + SoundTableReplacementSlot(bool mod_defined, u16 id); + + virtual ~SoundTableReplacementSlot() = 0; + + [[nodiscard]] virtual u8 get_type_id() const = 0; +}; + +struct SoundEffectReplacementSlot final : SoundTableReplacementSlot { + SoundEffectCategory category; + + SoundEffectReplacementSlot(bool mod_defined, u16 id, SoundEffectCategory category, + const AudioSoundTableEffectInfo& info); + + ~SoundEffectReplacementSlot() override = default; + + [[nodiscard]] u8 get_type_id() const override; +}; + +struct StreamReplacementSlot final : SoundTableReplacementSlot { + std::string file_path; + bool stop_on_scene_change; + + StreamReplacementSlot( + bool mod_defined, u16 id, char const* file_path, const AudioSoundTableStreamInfo& info); + + ~StreamReplacementSlot() override = default; + + [[nodiscard]] u8 get_type_id() const override; +}; + +struct SoundEffectKey { + SoundEffectCategory category; + u16 id; + + template + friend H AbslHashValue(H h, const SoundEffectKey& k) { + return H::combine(std::move(h), k.category, k.id); + } + + [[nodiscard]] bool operator==(const SoundEffectKey& other) const { + return other.category == category && other.id == id; + } +}; + +std::shared_ptr get_override_for(JAISoundID id); +std::shared_ptr get_override_for_se(JAISoundID id); +std::shared_ptr get_override_for_stream(JAISoundID id); + +} // namespace dusk::mods::svc::audio_res::bst diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp new file mode 100644 index 0000000000..afa346cfba --- /dev/null +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -0,0 +1,93 @@ +#if DUSK_OPUS + +#include "aurora/lib/logging.hpp" +#include "dusk/mod_loader.hpp" +#include "helpers/cast.hpp" +#include "opusfile.h" +#include "wsys.hpp" + +namespace { + +aurora::Module Log("dusk::mods::svc::audio_res::wsys::opus"); + +struct OpusHandle { + OggOpusFile* file; + + ~OpusHandle() { op_free(file); } + + operator OggOpusFile*() const { return file; } +}; + +} // namespace + +namespace dusk::mods::svc::audio_res::wsys { +ModResult load_opus( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { + OpusHead head; + const int result = op_test(&head, fileData.data(), fileData.size()); + if (result != 0) { + return MOD_UNSUPPORTED; + } + + if (head.channel_count != 1) { + Log.error("[{}] replace_wave: file '{}' must be mono but actually has {} channels", + mod.metadata.id, slot.bundle_path, head.channel_count); + return MOD_INVALID_ARGUMENT; + } + + if (head.stream_count != 1) { + Log.error("[{}] replace_wave: file '{}' has multiple Opus streams!", mod.metadata.id, + slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + int error; + auto const handle_raw = op_open_memory(fileData.data(), fileData.size(), &error); + if (error != 0) { + Log.error("[{}] replace_wave: file '{}': unable to open Opus file: {}", mod.metadata.id, + slot.bundle_path, error); + return MOD_ERROR; + } + + const OpusHandle handle(handle_raw); + auto const length = op_pcm_total(handle, -1); + if (length < 0) { + Log.error("[{}] replace_wave: file '{}': failed to check stream length?", mod.metadata.id, + slot.bundle_path); + return MOD_ERROR; + } + + SampleDataPcm16 pcm_buffer; + pcm_buffer.data.resize(length); + + std::span span_pcm(pcm_buffer.data); + + while (!span_pcm.empty()) { + auto read_result = + op_read(handle, span_pcm.data(), helpers::cast::bounded_cast(span_pcm.size()), nullptr); + + if (read_result <= 0) { + Log.error("[{}] replace_wave: file '{}': failed to read Opus: {}", mod.metadata.id, + slot.bundle_path, read_result); + return MOD_ERROR; + } + + if (read_result > span_pcm.size()) { + Log.fatal("What teh fuck?"); + } + + span_pcm = span_pcm.subspan(read_result); + } + + pcm_buffer.be_swap(); + + slot.data = std::make_shared(std::move(pcm_buffer)); + slot.sample_rate = 48000; // Opus always decodes at 48 kHz. + slot.sample_count = length; + slot.format = AUDIO_WAVE_FORMAT_PCM16; + + return MOD_OK; +} +} // namespace dusk::mods::svc::audio_res::wsys + +#endif diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp new file mode 100644 index 0000000000..7ab8ac8773 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -0,0 +1,153 @@ +#include "aurora/lib/logging.hpp" +#include "dusk/mod_loader.hpp" +#include "helpers/alignment.hpp" +#include "wsys.hpp" + +namespace { + +aurora::Module Log("dusk::mods::svc::audio_res::wsys::wav"); + +struct ChunkHeader { + char magic[4]; + LE(u32) size; +}; + +struct RiffChunk { + ChunkHeader header; // magic "RIFF" + char id[4]; // "WAVE" + // Data after chunk. +}; + +constexpr u16 WAVE_FORMAT_PCM = 0x0001; + +struct FmtPcmChunkData { + ChunkHeader header; + + u16 wFormatTag; + u16 nChannels; + u32 nSamplesPerSec; + u32 nAvgBytesPerSec; + u16 nBlockAlign; + u16 wBitsPerSample; +}; + +bool check_four_cc(char const (&field)[4], char const (&expected)[5]) { + return memcmp(field, expected, 4) == 0; +} + +} // namespace + +namespace dusk::mods::svc::audio_res::wsys { + +ModResult load_wav( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { + using namespace helpers; + + if (fileData.size() < sizeof(RiffChunk)) { + return MOD_UNSUPPORTED; + } + + auto const riffChunk = read_unaligned(&fileData[0]); + if (!check_four_cc(riffChunk.header.magic, "RIFF")) { + return MOD_UNSUPPORTED; + } + + if (!check_four_cc(riffChunk.id, "WAVE")) { + return MOD_UNSUPPORTED; + } + + if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size() || riffChunk.header.size < 4) + { + Log.error("[{}] wav file {}: invalid size!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + bool has_read_fmt = false; + SampleDataPcm16 pcmData; + + auto waveData = fileData.subspan(sizeof(RiffChunk), riffChunk.header.size - 4); + while (waveData.size() > sizeof(ChunkHeader)) { + auto const chunkHeader = read_unaligned(&waveData[0]); + + if (check_four_cc(chunkHeader.magic, "fmt ")) { + if (has_read_fmt) { + Log.error( + "[{}] wav file {}: multiple fmt chunks!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (waveData.size() < sizeof(FmtPcmChunkData)) { + Log.error( + "[{}] wav file {}: fmt chunk too small!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + auto const fmtChunk = read_unaligned(&waveData[0]); + + if (fmtChunk.nChannels != 1) { + Log.error("[{}] wav file {}: only mono audio is supported", mod.metadata.id, + slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (fmtChunk.wFormatTag != WAVE_FORMAT_PCM) { + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, + slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (fmtChunk.wBitsPerSample != 16) { + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, + slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + has_read_fmt = true; + slot.sample_rate = static_cast(fmtChunk.nSamplesPerSec); + + } else if (check_four_cc(chunkHeader.magic, "data")) { + if (sizeof(chunkHeader) + chunkHeader.size > fileData.size()) { + Log.error("[{}] wav file {}: unexpected EOF reading sample data", mod.metadata.id, + slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + auto const sampleData = waveData.subspan(sizeof(chunkHeader), chunkHeader.size); + for (size_t i = 0; i + 1 < sampleData.size(); i += 2) { + pcmData.data.push_back(read_unaligned(&sampleData[i])); + } + } + + auto skip = sizeof(chunkHeader) + chunkHeader.size; + if (skip % 2 != 0) { + skip += 1; + } + + if (waveData.size() < skip) { + Log.error("[{}] wav file {}: unexpected EOF", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + waveData = waveData.subspan(skip); + } + + if (!has_read_fmt) { + Log.error("[{}] wav file {}: missing fmt chunk", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (pcmData.data.empty()) { + Log.error("[{}] wav file {}: missing data chunk", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + pcmData.be_swap(); + + slot.format = AUDIO_WAVE_FORMAT_PCM16; + slot.sample_count = pcmData.size() / sizeof(u16); + slot.data = std::make_unique(std::move(pcmData)); + + return MOD_OK; +} + +} // namespace dusk::mods::svc::audio_res::wsys diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp new file mode 100644 index 0000000000..dc57150541 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -0,0 +1,320 @@ +#include "wsys.hpp" +#include "../id_allocator.hpp" +#include "../internal.hpp" +#include "audio_res.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/audio/DuskAudioSystem.h" +#include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" +#include "helpers/cast.hpp" + +namespace dusk::mods::svc::audio_res::wsys { + +namespace { + +using namespace dusk::helpers::cast; + +bool wave_replacements_dirty = false; + +aurora::Module Log("dusk::mods::svc::audio_res"); + +SlotMap s_waveReplacements; + +constexpr ContainerLoadFunction s_containerLoadFunctions[] = { + load_wav, +#if DUSK_OPUS + load_opus, +#endif +}; + +PlainIdAllocator sound_effect_id_allocator(5'000); +PlainIdAllocator music_sample_id_allocator(1'000); + +PlainIdAllocator& id_allocator_for_bank(AudioWaveBank const bank) { + return bank == AUDIO_WAVE_BANK_SOUND_EFFECTS ? sound_effect_id_allocator : + music_sample_id_allocator; +} + +bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, + AudioRawWave const& raw, u32& sample_count) { + u32 samples_per_block; + u32 bytes_per_block; + switch (raw.format) { + case AUDIO_WAVE_FORMAT_ADPCM4: + samples_per_block = 16; + bytes_per_block = 9; + break; + case AUDIO_WAVE_FORMAT_PCM16: + samples_per_block = 1; + bytes_per_block = 2; + break; + default: + Log.error("[{}] unimplemented wave format (ADPCM2/PCM8): '{}'", mod.metadata.id, path); + return false; + } + + if (actual_size % bytes_per_block != 0) { + Log.error( + "[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path); + return false; + } + + sample_count = (actual_size / bytes_per_block) * samples_per_block; + return true; +} + +ModResult load_raw( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, AudioRawWave const& raw) { + auto file_contents = mod.bundle->readFile(slot.bundle_path); + u32 sample_count = 0; + + if (!validate_raw_size(mod, slot.bundle_path, file_contents.size(), raw, sample_count)) { + Log.error( + "[{}] replace_wave: file '{}' is a raw .abf file, but raw_wave data was not specified", + mod.metadata.id, slot.bundle_path); + + return MOD_INVALID_ARGUMENT; + } + + if (slot.format == AUDIO_WAVE_FORMAT_PCM16) { + SampleDataPcm16 pcm16; + pcm16.data.resize(sample_count); + memcpy(pcm16.data.data(), file_contents.data(), file_contents.size()); + slot.data = std::make_shared(std::move(pcm16)); + } else { + slot.data = std::make_shared(std::move(file_contents)); + } + + slot.sample_count = sample_count; + slot.sample_rate = raw.sample_rate; + slot.format = raw.format; + slot.sample_value_penult = raw.sample_value_penult; + slot.sample_value_last = raw.sample_value_last; + return MOD_OK; +} + +/** + * Load an audio file stored in some sort of container format. + * File type is determined by looking at contents, not extension. + */ +ModResult load_container(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot) { + auto file_contents = mod.bundle->readFile(slot.bundle_path); + + for (auto const loader : s_containerLoadFunctions) { + auto const result = loader(mod, slot, file_contents); + if (result == MOD_OK) { + return MOD_OK; + } + + if (result != MOD_UNSUPPORTED) { + return result; + } + } + + return MOD_UNSUPPORTED; +} + +JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) { + JASWaveInfo info; + info.mWaveFormat = static_cast(slot.format); + info.mBaseKey = slot.base_key; + info.mLoopFlag = slot.loop ? 0xFF : 0; + info.mSampleRate = slot.sample_rate; + info.mOffsetStart = 0; // Unused but just init it ig. + info.mOffsetLength = bounded_cast(slot.data->size()); + info.mLoopStartSample = slot.loop_start_sample; + info.mLoopEndSample = bounded_cast(slot.loop_end_sample); + info.mSampleCount = bounded_cast(slot.sample_count); + info.mpLast = slot.sample_value_last; + info.mpPenult = slot.sample_value_penult; + // mpLoaded is initialized to point to a 1, so we're good there. + return info; +} + +bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) { + auto const found = s_waveReplacements.find_owned(handle, mod); + if (found == nullptr) { + return false; + } + + if (found->value.mod_defined) { + auto& allocator = id_allocator_for_bank(found->value.bank); + allocator.free(found->value.wave_id); + } + + s_waveReplacements.erase_owned(handle, mod); + return true; +} + +ModResult insert_replace_wave_core(ModContext* ctx, AudioWaveBank bank, u16 wave_id, + bool mod_defined, char const* file_name, AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || file_name == nullptr || !utils::is_safe_resource_path(file_name)) { + return MOD_INVALID_ARGUMENT; + } + + RuntimeWaveReplacementSlot slot{}; + slot.bundle_path = file_name; + slot.bank = bank; + slot.wave_id = wave_id; + slot.mod_defined = mod_defined; + + ModResult result; + if (wave_info && wave_info->raw_wave) { + result = load_raw(*mod, slot, *wave_info->raw_wave); + } else { + result = load_container(*mod, slot); + } + if (result != MOD_OK) { + return result; + } + + if (wave_info != nullptr) { + slot.base_key = wave_info->base_key; + slot.loop = wave_info->loop; + slot.loop_start_sample = wave_info->loop_start_sample; + slot.loop_end_sample = wave_info->loop_end_sample; + + if (slot.loop_end_sample > slot.sample_count) { + slot.loop_end_sample = slot.sample_count; + } + + if (slot.loop_start_sample >= slot.loop_end_sample) { + Log.error("[{}] wave has start >= end", mod->metadata.id); + return MOD_INVALID_ARGUMENT; + } + } else { + slot.base_key = AUDIO_RES_DEFAULT_KEY; + slot.loop = false; + slot.loop_start_sample = 0; + slot.loop_end_sample = slot.sample_count; + } + + wave_replacements_dirty = true; + + const auto handle = s_waveReplacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +} // namespace + +absl::flat_hash_map s_replacements; +std::mutex s_replacements_mutex; + +AudioWaveInfo const default_wave_info( + AUDIO_RES_DEFAULT_KEY, false, 0, std::numeric_limits::max(), nullptr); + +ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + if (!wave_remove(*mod, handle)) { + Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle); + return MOD_INVALID_ARGUMENT; + } + return MOD_OK; +} + +ModResult insert_replace_wave(ModContext* ctx, AudioWaveBank bank, u16 wave_id, + char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle) { + return insert_replace_wave_core(ctx, bank, wave_id, false, file_name, wave_info, out_handle); +} + +ModResult insert_add_wave(ModContext* ctx, AudioWaveBank bank, char const* file_name, + AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle, u16* out_wave_id) { + if (out_wave_id == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + *out_wave_id = 0; + + if (bank != AUDIO_WAVE_BANK_SOUND_EFFECTS && bank != AUDIO_WAVE_BANK_MUSIC_SAMPLES) { + return MOD_INVALID_ARGUMENT; + } + + auto& allocator = id_allocator_for_bank(bank); + auto const new_wave_id = allocator.alloc(); + + auto const result = + insert_replace_wave_core(ctx, bank, new_wave_id, true, file_name, wave_info, out_handle); + if (result != MOD_OK) { + allocator.free(new_wave_id); + } + + *out_wave_id = new_wave_id; + return result; +} + +void remove_mod(LoadedMod& mod) { + s_waveReplacements.erase_all(mod); + + wave_replacements_dirty = true; +} + +void sync_audio_replacements() { + wave_replacements_dirty = false; + + absl::flat_hash_map new_map; + + for (auto const& mod : ModLoader::instance().active_mods()) { + s_waveReplacements.for_each([&](auto, auto entry) { + if (entry.owner != &mod) { + return; + } + + auto const wave_info = wave_info_from_slot(entry.value); + new_map.emplace(AudioWaveKey(entry.value.bank, entry.value.wave_id), + AudioWaveReplacementValue(wave_info, entry.value.data)); + }); + } + + // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); + + std::lock_guard lock(s_replacements_mutex); + // Note: new_map will contain the old contents, and is dropped *outside* the lock. + // As to avoid holding the lock any longer than necessary. + std::exchange(s_replacements, std::move(new_map)); + + // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); +} + +void frame_end() { + if (wave_replacements_dirty) { + sync_audio_replacements(); + } +} + +AudioWaveReplacementValue::~AudioWaveReplacementValue() = default; +const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const { + return &wave_info; +} + +void const* AudioWaveReplacementValue::getAramBaseAddress() const { + return data->get_data().data(); +} + +intptr_t AudioWaveReplacementValue::getWavePtr() const { + return 0; +} + +std::unique_ptr AudioWaveReplacementValue::getSampleReference() const { + return std::make_unique(data); +} + +void SampleDataPcm16::be_swap() { + for (auto& sample : data) { + ::be_swap(sample); + } +} + +} // namespace dusk::mods::svc::audio_res::wsys diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp new file mode 100644 index 0000000000..d609d04206 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include "JSystem/JAudio2/JASWaveInfo.h" +#include "absl/container/flat_hash_map.h" +#include "mods/svc/audio_res.h" + +namespace dusk::mods { +struct LoadedMod; +} + +namespace dusk::mods::svc::audio_res::wsys { + +struct SampleDataBuf { + virtual ~SampleDataBuf() = default; + [[nodiscard]] virtual std::span get_data() const = 0; + [[nodiscard]] size_t size() const { return get_data().size(); } +}; + +// Making a separate type for this rather than just using a vector +// because I don't want aliasing or alignment to eat my face. +struct SampleDataPcm16 : SampleDataBuf { + std::vector data; + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } + + void be_swap(); +}; + +struct SampleDataU8 : SampleDataBuf { + std::vector data; + + explicit SampleDataU8(std::vector data) : data(std::move(data)) {} + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } +}; + +struct SampleReference : JASSampleDataReference { + explicit SampleReference(std::shared_ptr data) : data(std::move(data)) {} + + std::shared_ptr data; +}; + +struct AudioWaveKey { + AudioWaveBank bank; + u32 wave_id; + + AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {}; + + template + friend H AbslHashValue(H h, const AudioWaveKey& k) { + return H::combine(std::move(h), k.bank, k.wave_id); + } + + [[nodiscard]] bool operator==(const AudioWaveKey& other) const { + return other.bank == bank && other.wave_id == wave_id; + } +}; + +struct AudioWaveReplacementValue : JASWaveHandle { + AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr data) + : wave_info(wave_info), data(std::move(data)) {}; + ~AudioWaveReplacementValue() override; + [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; + [[nodiscard]] intptr_t getWavePtr() const override; + [[nodiscard]] void const* getAramBaseAddress() const override; + [[nodiscard]] std::unique_ptr getSampleReference() const override; + + JASWaveInfo wave_info; + std::shared_ptr data; +}; + +extern absl::flat_hash_map s_replacements; +extern std::mutex s_replacements_mutex; + +struct RuntimeWaveReplacementSlot { + std::string bundle_path; + AudioWaveBank bank; + bool mod_defined; + u16 wave_id; + + u8 base_key; + bool loop; + u32 loop_start_sample; + u32 loop_end_sample; + + AudioWaveFormat format; + f32 sample_rate; + u32 sample_count; + s16 sample_value_last; + s16 sample_value_penult; + + std::shared_ptr data; +}; + +using ContainerLoadFunction = ModResult (*)( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +ModResult load_wav( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +ModResult load_opus( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +} // namespace dusk::mods::svc::audio_res::wsys diff --git a/src/dusk/mods/svc/camera.cpp b/src/dusk/mods/svc/camera.cpp index 60fbf42499..b93ea00e62 100644 --- a/src/dusk/mods/svc/camera.cpp +++ b/src/dusk/mods/svc/camera.cpp @@ -1,5 +1,5 @@ +#include "internal.hpp" #include "registry.hpp" -#include "slot_map.hpp" #include "dusk/camera_operators.hpp" #include "dusk/mods/loader/loader.hpp" diff --git a/src/dusk/mods/svc/config.cpp b/src/dusk/mods/svc/config.cpp index b1bbf63705..665005f8f8 100644 --- a/src/dusk/mods/svc/config.cpp +++ b/src/dusk/mods/svc/config.cpp @@ -1,11 +1,12 @@ #include "config.hpp" +#include "internal.hpp" #include "registry.hpp" -#include "slot_map.hpp" #include #include "dusk/config.hpp" #include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" #include "mods/svc/config.h" #include @@ -112,20 +113,6 @@ ConfigVarValue translate_current( return value; } -bool valid_var_fragment(const char* name) { - if (name == nullptr) { - return false; - } - const std::string_view fragment{name}; - if (fragment.empty() || fragment.size() > 64) { - return false; - } - return std::ranges::all_of(fragment, [](char ch) { - return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || - ch == '_' || ch == '-'; - }); -} - config::ConfigVarBase* find_var(LoadedMod& mod, const uint64_t handle, uint32_t expectedType) { const auto* entry = s_slots.find_owned(handle, mod); if (entry == nullptr || entry->value.kind != ConfigSlotKind::Var || @@ -167,7 +154,7 @@ ModResult config_register_var( } auto* mod = mod_from_context(context); if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(ConfigVarDesc) || - !valid_var_fragment(desc->name)) + desc->name == nullptr || !utils::is_valid_config_name(desc->name)) { return MOD_INVALID_ARGUMENT; } diff --git a/src/dusk/mods/svc/file.cpp b/src/dusk/mods/svc/file.cpp index f1ede94a3c..9225772803 100644 --- a/src/dusk/mods/svc/file.cpp +++ b/src/dusk/mods/svc/file.cpp @@ -1,6 +1,6 @@ #include "registry.hpp" -#include "slot_map.hpp" +#include "internal.hpp" #include #include @@ -8,6 +8,7 @@ #include #include "dusk/config.hpp" #include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" #include "mods/svc/file.h" #include @@ -184,8 +185,8 @@ ModResult begin_export(ModContext* context, const char* sourceLocation, const ch FilePickFn callback, void* userData) { auto* mod = mod_from_context(context); const std::string_view name = suggestedName != nullptr ? suggestedName : ""; - if (mod == nullptr || sourceLocation == nullptr || callback == nullptr || name.empty() || - name == "." || name == ".." || name.find_first_of("/\\") != std::string_view::npos) + if (mod == nullptr || sourceLocation == nullptr || callback == nullptr || + !utils::is_safe_path_component(name)) { return MOD_INVALID_ARGUMENT; } diff --git a/src/dusk/mods/svc/flow.cpp b/src/dusk/mods/svc/flow.cpp index 9c5af25e4c..c53636d1fa 100644 --- a/src/dusk/mods/svc/flow.cpp +++ b/src/dusk/mods/svc/flow.cpp @@ -5,6 +5,7 @@ #include "dusk/logging.h" #include "dusk/mods/loader/loader.hpp" #include "dusk/settings.h" +#include "dusk/utilities.hpp" #include "helpers/bits.hpp" @@ -164,14 +165,6 @@ bool valid_group(uint16_t group) { return group <= kGroupMax; } -bool valid_debug_name(const char* name) { - if (name == nullptr) { - return false; - } - const std::string_view value{name}; - return !value.empty() && value.size() <= kDebugNameMax; -} - int32_t mod_priority(const mods::LoadedMod& mod) { int32_t priority = 0; for (const auto& candidate : mods::ModLoader::instance().mods()) { @@ -1230,7 +1223,9 @@ ModResult register_query(ModContext* context, const char* debugName, FlowQueryFn *outId = 0; } auto* mod = mod_from_context(context); - if (mod == nullptr || !flow::valid_debug_name(debugName) || fn == nullptr || outId == nullptr) { + if (mod == nullptr || !utils::is_valid_name(debugName, flow::kDebugNameMax) || fn == nullptr || + outId == nullptr) + { return MOD_INVALID_ARGUMENT; } uint16_t id = 0; @@ -1254,7 +1249,9 @@ ModResult register_event(ModContext* context, const char* debugName, FlowEventFn *outId = 0; } auto* mod = mod_from_context(context); - if (mod == nullptr || !flow::valid_debug_name(debugName) || fn == nullptr || outId == nullptr) { + if (mod == nullptr || !utils::is_valid_name(debugName, flow::kDebugNameMax) || fn == nullptr || + outId == nullptr) + { return MOD_INVALID_ARGUMENT; } uint8_t id = 0; diff --git a/src/dusk/mods/svc/game_mode.cpp b/src/dusk/mods/svc/game_mode.cpp index cfa655baf8..568e71d342 100644 --- a/src/dusk/mods/svc/game_mode.cpp +++ b/src/dusk/mods/svc/game_mode.cpp @@ -1,19 +1,22 @@ #include "mods/svc/game_mode.h" -#include "dusk/game_mode.hpp" -#include "config.hpp" +#include "internal.hpp" #include "registry.hpp" -#include "slot_map.hpp" -#include "aurora/lib/logging.hpp" +#include "dusk/game_mode.hpp" #include "dusk/mod_loader.hpp" #include "dusk/mods/loader/loader.hpp" -#include "fmt/format.h" +#include "dusk/utilities.hpp" + +#include +#include #include #include #include +#include #include +#include #include #include @@ -76,6 +79,23 @@ std::string get_mod_game_mode_id(ModContext* ctx, const std::string& id) { return fullId; } +std::optional parse_save_name(const GameModeDesc& desc, std::string_view id) { + const auto saveName = utils::bounded_string(desc.save_name, sizeof(desc.save_name)); + if (!saveName) { + Log.error("Game mode {} has a save name longer than {} characters", id, + sizeof(desc.save_name) - 1); + return std::nullopt; + } + + if (!saveName->empty() && !utils::is_valid_save_name(*saveName)) { + Log.error("Game mode {} has invalid save name '{}'; expected only letters, digits, '.', " + "'_', and '-'", + id, *saveName); + return std::nullopt; + } + return std::string{*saveName}; +} + void game_mode_remove_mod(LoadedMod& mod) { const auto it = s_gameModesByMod.find(mod.metadata.id); if (it != s_gameModesByMod.end()) { @@ -93,17 +113,11 @@ ModResult register_game_mode(ModContext* ctx, const GameModeDesc* desc) { return MOD_INVALID_ARGUMENT; } - std::string id; - if (!desc->game_mode_id) { - Log.error("Attempted to register a game mode with a null ID"); + if (!utils::is_valid_name(desc->game_mode_id)) { + Log.error("Attempted to register a game mode with no ID"); return MOD_ERROR; } - id = desc->game_mode_id; - if (id.empty()) { - Log.error("Attempted to register a game mode with an empty ID"); - return MOD_ERROR; - } - id = get_mod_game_mode_id(ctx, id); + const auto id = get_mod_game_mode_id(ctx, desc->game_mode_id); std::string fullName; if (!desc->full_name) { @@ -117,7 +131,12 @@ ModResult register_game_mode(ModContext* ctx, const GameModeDesc* desc) { } } - gamemode::GameMode mode{id, fullName, desc->save_name}; + const auto saveName = parse_save_name(*desc, id); + if (!saveName) { + return MOD_INVALID_ARGUMENT; + } + + gamemode::GameMode mode{id, fullName, *saveName}; if (desc->on_activated) { mode.mOnActivatedFunction = wrap_callback( *owner, desc->on_activated, desc->user_data, "game mode activation callback"); diff --git a/src/dusk/mods/svc/gfx.cpp b/src/dusk/mods/svc/gfx.cpp index 098894007a..f398e29410 100644 --- a/src/dusk/mods/svc/gfx.cpp +++ b/src/dusk/mods/svc/gfx.cpp @@ -1,5 +1,5 @@ +#include "internal.hpp" #include "registry.hpp" -#include "slot_map.hpp" #include "window.hpp" #include @@ -27,6 +27,10 @@ namespace dusk::mods { namespace { +// Oops! We forgot the FIFO thread exists. The design of this service isn't thread safe. +// Encounter said he'd fix it, this is the temporary workaround. +#define OH_FUCK_SYNC AuroraGXSync(); + constexpr borealis::Log Log{"dusk::mods::gfx"}; enum class GfxSlotKind : uint8_t { @@ -1082,6 +1086,8 @@ ModResult gfx_get_device_info(ModContext* context, GfxDeviceInfo* outInfo) { } ModResult gfx_get_scene_target_layout(ModContext* context, GfxRenderTargetLayout* outLayout) { + OH_FUCK_SYNC + if (outLayout == nullptr || outLayout->struct_size < sizeof(GfxRenderTargetLayout) || mod_from_context(context) == nullptr) { @@ -1172,6 +1178,8 @@ ModResult gfx_register_window_present_target_impl(ModContext* context, WindowHan ModResult gfx_resize_present_target_impl( ModContext* context, GfxPresentTargetHandle handle, uint32_t width, uint32_t height) { + OH_FUCK_SYNC + auto* mod = mod_from_context(context); if (mod == nullptr || handle == 0 || width == 0 || height == 0) { return MOD_INVALID_ARGUMENT; @@ -1189,6 +1197,8 @@ ModResult gfx_unregister_present_target_impl(ModContext* context, GfxPresentTarg ModResult gfx_push_present_impl( ModContext* context, GfxPresentTargetHandle handle, const void* payload, size_t payloadSize) { + OH_FUCK_SYNC + auto* mod = mod_from_context(context); if (mod == nullptr || handle == 0 || payloadSize > GFX_INLINE_DRAW_PAYLOAD_SIZE || (payloadSize > 0 && payload == nullptr)) @@ -1229,6 +1239,8 @@ ModResult gfx_unregister_draw_type_impl(ModContext* context, GfxDrawTypeHandle h ModResult gfx_push_draw_impl( ModContext* context, GfxDrawTypeHandle handle, const void* payload, size_t payloadSize) { + OH_FUCK_SYNC + auto* mod = mod_from_context(context); if (mod == nullptr || handle == 0 || payloadSize > GFX_INLINE_DRAW_PAYLOAD_SIZE || (payloadSize > 0 && payload == nullptr)) @@ -1252,21 +1264,29 @@ ModResult gfx_push_stream_impl(ModContext* context, GfxStreamBuffer buffer, cons ModResult gfx_push_verts_impl( ModContext* context, const void* data, size_t size, size_t alignment, GfxRange* outRange) { + OH_FUCK_SYNC + return gfx_push_stream_impl(context, GfxStreamBuffer::Verts, data, size, alignment, outRange); } ModResult gfx_push_indices_impl( ModContext* context, const void* data, size_t size, size_t alignment, GfxRange* outRange) { + OH_FUCK_SYNC + return gfx_push_stream_impl(context, GfxStreamBuffer::Indices, data, size, alignment, outRange); } ModResult gfx_push_uniform_impl( ModContext* context, const void* data, size_t size, GfxRange* outRange) { + OH_FUCK_SYNC + return gfx_push_stream_impl(context, GfxStreamBuffer::Uniform, data, size, 0, outRange); } ModResult gfx_push_storage_impl( ModContext* context, const void* data, size_t size, GfxRange* outRange) { + OH_FUCK_SYNC + return gfx_push_stream_impl(context, GfxStreamBuffer::Storage, data, size, 0, outRange); } @@ -1307,6 +1327,8 @@ ModResult gfx_unregister_stage_hook_impl(ModContext* context, GfxStageHookHandle ModResult gfx_resolve_pass_impl( ModContext* context, const GfxResolveDesc* desc, GfxResolvedTargets* outTargets) { + OH_FUCK_SYNC + if (outTargets != nullptr && outTargets->struct_size >= sizeof(GfxResolvedTargets)) { *outTargets = GfxResolvedTargets{.struct_size = sizeof(GfxResolvedTargets)}; } @@ -1321,6 +1343,8 @@ ModResult gfx_resolve_pass_impl( } ModResult gfx_create_pass_impl(ModContext* context, uint32_t width, uint32_t height) { + OH_FUCK_SYNC + auto* mod = mod_from_context(context); if (mod == nullptr || width == 0 || height == 0) { return MOD_INVALID_ARGUMENT; @@ -1359,6 +1383,8 @@ ModResult gfx_unregister_compute_type_impl(ModContext* context, GfxComputeTypeHa ModResult gfx_push_compute_impl( ModContext* context, GfxComputeTypeHandle handle, const void* payload, size_t payloadSize) { + OH_FUCK_SYNC + auto* mod = mod_from_context(context); if (mod == nullptr || handle == 0 || payloadSize > GFX_INLINE_DRAW_PAYLOAD_SIZE || (payloadSize > 0 && payload == nullptr)) diff --git a/src/dusk/mods/svc/hook.cpp b/src/dusk/mods/svc/hook.cpp index d8a9005deb..0cc983c02d 100644 --- a/src/dusk/mods/svc/hook.cpp +++ b/src/dusk/mods/svc/hook.cpp @@ -297,27 +297,32 @@ bool install_backend( #endif } -void deactivate_backend(void* target, InstalledBackend& backend) { +bool deactivate_backend(void* target, InstalledBackend& backend) { #if DUSK_HAS_PREPATCH if (backend.kind == BackendKind::Prepatch) { prepatch::publish(backend.prepatchSite, nullptr); backend = {}; - return; + return true; } #endif #if DUSK_HAS_FUNCHOOK if (backend.kind == BackendKind::Funchook) { const int uninst = funchook_uninstall(backend.handle, 0); + if (uninst != 0) { + DuskLog.warn("HookSystem: funchook uninstall for {:p} failed: {}", target, + funchook_error_message(backend.handle)); + return false; + } const int destr = funchook_destroy(backend.handle); - if (uninst != 0 || destr != 0) { - DuskLog.warn("HookSystem: funchook uninstall/destroy for {:p} returned {}/{}", target, - uninst, destr); + if (destr != 0) { + DuskLog.warn("HookSystem: funchook destroy for {:p} returned {}", target, destr); } } #else (void)target; #endif backend = {}; + return true; } bool handoff_backend( @@ -352,8 +357,8 @@ bool handoff_hook(void* target, InstalledHook& entry) { #else constexpr bool prepatched = false; #endif - if (!prepatched) { - deactivate_backend(target, entry.backend); + if (!prepatched && !deactivate_backend(target, entry.backend)) { + DuskLog.fatal("HookSystem: cannot hand off a hook that remains installed at {:p}", target); } entry.active = nullptr; @@ -535,13 +540,20 @@ ModResult hook_uninstall(ModContext* context, void* fnAddr, void** originalFnSlo return MOD_INVALID_ARGUMENT; } + const bool removedActive = entry.activeStore == originalFnSlot; +#if DUSK_HAS_FUNCHOOK + if (removedActive && entry.backend.kind == BackendKind::Funchook && + !deactivate_backend(fnAddr, entry.backend)) { + return MOD_ERROR; + } +#endif + if (const auto registryIt = s_registry.find(key); registryIt != s_registry.end() && erase_callbacks(registryIt->second, context)) { s_registry.erase(registryIt); } - const bool removedActive = entry.activeStore == originalFnSlot; entry.candidates.erase(candidateIt); *originalFnSlot = nullptr; if (!removedActive) { @@ -745,7 +757,8 @@ bool resolve_symbol_checked(const char* symbol, bool requireCode, void** out, st why = fmt::format("symbol '{}' not found", symbol); return false; case manifest::ResolveStatus::Ambiguous: - why = fmt::format("'{}' maps to more than one address; use the mangled name", symbol); + why = fmt::format( + "'{}' maps to more than one address; use a qualified or mangled name", symbol); return false; } why = "unexpected resolve failure"; @@ -897,7 +910,9 @@ void hook_remove_mod(LoadedMod& mod) { auto* target = reinterpret_cast(it->first); if (entry.candidates.empty()) { - deactivate_backend(target, entry.backend); + if (!deactivate_backend(target, entry.backend)) { + DuskLog.fatal("HookSystem: cannot detach a mod with a live hook at {:p}", target); + } it = s_installed.erase(it); continue; } diff --git a/src/dusk/mods/svc/host.cpp b/src/dusk/mods/svc/host.cpp index a8e975e22f..edc647fc4d 100644 --- a/src/dusk/mods/svc/host.cpp +++ b/src/dusk/mods/svc/host.cpp @@ -1,15 +1,17 @@ +#include "internal.hpp" #include "registry.hpp" -#include "slot_map.hpp" #include "dusk/main.h" #include "dusk/mods/loader/loader.hpp" #include "dusk/mods/log_buffer.hpp" #include "dusk/mods/manifest.hpp" -#include "fmt/format.h" +#include "dusk/utilities.hpp" -#include #include #include +#include + +#include #include #include @@ -33,7 +35,7 @@ ModResult host_get_service(ModContext*, const char* serviceId, const uint16_t ma ModResult host_publish_service( ModContext* context, const char* serviceId, const uint16_t majorVersion, const void* service) { auto* mod = mod_from_context(context); - if (mod == nullptr || !valid_service_id(serviceId) || service == nullptr) { + if (mod == nullptr || !utils::is_valid_name(serviceId) || service == nullptr) { return MOD_INVALID_ARGUMENT; } diff --git a/src/dusk/mods/svc/http.cpp b/src/dusk/mods/svc/http.cpp index 337758d5d4..423bd50b70 100644 --- a/src/dusk/mods/svc/http.cpp +++ b/src/dusk/mods/svc/http.cpp @@ -1,21 +1,20 @@ #include "registry.hpp" -#include "slot_map.hpp" +#include "internal.hpp" +#include "net.hpp" -#include "dusk/app_info.hpp" #include "dusk/main.h" #include "dusk/mods/loader/loader.hpp" #include "mods/svc/http.h" #include #include -#include +#include #include #include #include #include -#include #include #include #include @@ -38,6 +37,15 @@ constexpr size_t MaxRequestBodyBytes = 16 * 1024 * 1024; constexpr size_t DefaultResponseBodyBytes = 1024 * 1024; constexpr size_t MaxResponseBodyBytes = 64 * 1024 * 1024; constexpr std::chrono::milliseconds DefaultTimeout{10000}; +constexpr std::string_view ReservedHeaders[]{ + "User-Agent", + "Host", + "Content-Length", + "Connection", + "Accept-Encoding", + "Range", + "If-Range", +}; struct PendingRequest { HttpCompleteFn callback = nullptr; @@ -52,53 +60,9 @@ static_assert(std::is_nothrow_move_constructible_v); SlotMap s_requests; -bool ascii_iequals(std::string_view left, std::string_view right) { - return left.size() == right.size() && std::ranges::equal(left, right, [](char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - }); -} - -bool is_reserved_header(std::string_view name) { - constexpr std::string_view reserved[]{ - "User-Agent", - "Host", - "Content-Length", - "Connection", - "Accept-Encoding", - "Range", - "If-Range", - }; - return std::ranges::any_of( - reserved, [&](std::string_view value) { return ascii_iequals(name, value); }); -} - -bool valid_header_name(std::string_view name) { - constexpr std::string_view separators{"()<>@,;:\\\"/[]?={} \t"}; - return !name.empty() && std::ranges::all_of(name, [&](unsigned char value) { - return value > 32 && value < 127 && - separators.find(static_cast(value)) == std::string_view::npos; - }); -} - bool valid_url(std::string_view url) { - constexpr std::string_view scheme{"https://"}; - if (!url.starts_with(scheme) || url.size() <= scheme.size() || url.size() > MaxUrlBytes) { - return false; - } - if (std::ranges::any_of(url, [](unsigned char value) { return value <= 32 || value == 127; })) { - return false; - } - const auto authorityEnd = url.find_first_of("/?#", scheme.size()); - const auto authority = url.substr(scheme.size(), authorityEnd - scheme.size()); - return !authority.empty(); -} - -bool declares_http_import(const LoadedMod& mod) { - return std::ranges::any_of( - mod.manifestInfo.imports, [](const ModManifestInfo::Import& serviceImport) { - return serviceImport.id == HTTP_SERVICE_ID; - }); + const auto parsed = url.size() <= MaxUrlBytes ? borealis::url::parse(url) : std::nullopt; + return parsed && parsed->scheme == "https"; } std::filesystem::path normalized_absolute(const std::filesystem::path& path, std::error_code& ec) { @@ -183,7 +147,6 @@ HttpError map_error(borealis::http::Error error) { case borealis::http::Error::Io: return HTTP_ERROR_IO; case borealis::http::Error::NoBackend: - case borealis::http::Error::NotInitialized: case borealis::http::Error::Network: return HTTP_ERROR_NETWORK; default: @@ -216,7 +179,7 @@ borealis::http::Result publish_download(borealis::http::Result result, } std::filesystem::path temporary = destination; - temporary += "." + borealis::io::fs_path_to_string(staging.filename()) + ".part"; + temporary += fmt::format(".{}.part", borealis::io::fs_path_to_string(staging.filename())); std::error_code ec; std::filesystem::copy_file( staging, temporary, std::filesystem::copy_options::overwrite_existing, ec); @@ -225,7 +188,7 @@ borealis::http::Result publish_download(borealis::http::Result result, std::error_code ignored; std::filesystem::remove(temporary, ignored); result.error = borealis::http::Error::Io; - result.message = "Failed to publish download: " + copyError; + result.message = fmt::format("Failed to publish download: {}", copyError); return result; } @@ -233,14 +196,14 @@ borealis::http::Result publish_download(borealis::http::Result result, if (!borealis::io::atomic_replace(temporary, destination, replaceError)) { std::filesystem::remove(temporary, ec); result.error = borealis::http::Error::Io; - result.message = "Failed to publish download: " + replaceError; + result.message = fmt::format("Failed to publish download: {}", replaceError); return result; } std::filesystem::remove(staging, ec); return result; } catch (const std::exception& exception) { result.error = borealis::http::Error::Io; - result.message = std::string{"Failed to publish download: "} + exception.what(); + result.message = fmt::format("Failed to publish download: {}", exception.what()); return result; } catch (...) { result.error = borealis::http::Error::Io; @@ -312,14 +275,8 @@ void http_frame_begin() { .download_path = downloadSucceeded ? publishedPath.c_str() : nullptr, }; - try { - callback(owner->context.get(), handle, &snapshot, userData); - } catch (const std::exception& exception) { - fail_mod(*owner, MOD_ERROR, - std::string{"exception in HTTP completion callback: "} + exception.what()); - } catch (...) { - fail_mod(*owner, MOD_ERROR, "unknown exception in HTTP completion callback"); - } + guarded_callback(*owner, "HTTP completion callback", + [&] { callback(owner->context.get(), handle, &snapshot, userData); }); s_requests.erase(handle); } } @@ -344,17 +301,6 @@ bool staging_path_in_use(const LoadedMod& mod, const std::filesystem::path& path return inUse; } -std::string user_agent_version(std::string_view version) { - std::string result{version}; - for (char& ch : result) { - const auto value = static_cast(ch); - if (value <= 32 || value >= 127) { - ch = '_'; - } - } - return result; -} - ModResult start_request(LoadedMod& mod, const HttpRequestDesc& desc, HttpCompleteFn callback, void* userData, HttpRequestHandle& outHandle) { const std::string_view url{desc.url}; @@ -377,9 +323,7 @@ ModResult start_request(LoadedMod& mod, const HttpRequestDesc& desc, HttpComplet } const std::string_view name{header.name}; const std::string_view value{header.value}; - const bool invalidValue = std::ranges::any_of( - value, [](unsigned char ch) { return (ch < 32 && ch != '\t') || ch == 127; }); - if (!valid_header_name(name) || invalidValue || is_reserved_header(name) || + if (!valid_header(name, value, ReservedHeaders, true) || name.size() > MaxHeaderBytes - headerBytes) { return MOD_INVALID_ARGUMENT; @@ -438,14 +382,12 @@ ModResult start_request(LoadedMod& mod, const HttpRequestDesc& desc, HttpComplet .connectTimeout = desc.connect_timeout_ms != 0 ? std::chrono::milliseconds{desc.connect_timeout_ms} : DefaultTimeout, - .idleTimeout = desc.idle_timeout_ms != 0 ? - std::chrono::milliseconds{desc.idle_timeout_ms} : - DefaultTimeout, + .idleTimeout = desc.idle_timeout_ms != 0 ? std::chrono::milliseconds{desc.idle_timeout_ms} : + DefaultTimeout, .totalTimeout = desc.total_timeout_ms != 0 ? std::optional{std::chrono::milliseconds{desc.total_timeout_ms}} : std::nullopt, - .maxBodyBytes = - desc.max_body_bytes != 0 ? desc.max_body_bytes : DefaultResponseBodyBytes, + .maxBodyBytes = desc.max_body_bytes != 0 ? desc.max_body_bytes : DefaultResponseBodyBytes, }; request.headers.reserve(desc.header_count + 1); for (uint32_t i = 0; i < desc.header_count; ++i) { @@ -453,8 +395,7 @@ ModResult start_request(LoadedMod& mod, const HttpRequestDesc& desc, HttpComplet } request.headers.push_back({ .name = "User-Agent", - .value = fmt::format("{}/{} {}/{}", AppName, BOREALIS_APP_VERSION, mod.metadata.id, - user_agent_version(mod.metadata.version)), + .value = user_agent(mod), }); auto task = borealis::http::start(std::move(request)); @@ -463,9 +404,7 @@ ModResult start_request(LoadedMod& mod, const HttpRequestDesc& desc, HttpComplet if (!immediate.has_value()) { return MOD_UNAVAILABLE; } - if (immediate->error == borealis::http::Error::NoBackend || - immediate->error == borealis::http::Error::NotInitialized) - { + if (immediate->error == borealis::http::Error::NoBackend) { return MOD_UNAVAILABLE; } task = borealis::detail::make_ready_task(std::move(*immediate)); @@ -492,14 +431,10 @@ ModResult http_request(ModContext* context, const HttpRequestDesc* desc, HttpCom { return MOD_INVALID_ARGUMENT; } - if (!declares_http_import(*mod)) { + if (!declares_import(*mod, HTTP_SERVICE_ID)) { return MOD_UNSUPPORTED; } - try { - return start_request(*mod, *desc, callback, userData, *outHandle); - } catch (...) { - return MOD_ERROR; - } + return start_request(*mod, *desc, callback, userData, *outHandle); } ModResult http_progress(ModContext* context, HttpRequestHandle handle, HttpProgress* outProgress) { @@ -550,14 +485,14 @@ void http_shutdown() { } bool http_available() { - return borealis::http::available() && borealis::http::initialize(); + return borealis::http::available(); } constexpr HttpService s_httpService{ .header = SERVICE_HEADER(HttpService, HTTP_SERVICE_MAJOR, HTTP_SERVICE_MINOR), - .request = http_request, - .progress = http_progress, - .cancel = http_cancel, + .request = SERVICE_FUNCTION(http_request), + .progress = SERVICE_FUNCTION(http_progress), + .cancel = SERVICE_FUNCTION(http_cancel), }; } // namespace diff --git a/src/dusk/mods/svc/id_allocator.cpp b/src/dusk/mods/svc/id_allocator.cpp new file mode 100644 index 0000000000..3778bc7b81 --- /dev/null +++ b/src/dusk/mods/svc/id_allocator.cpp @@ -0,0 +1,9 @@ +#include "id_allocator.hpp" + +namespace dusk::mods::svc { + +void id_allocator_exhausted() { + CRASH("ID allocator exhausted!"); +} + +} // namespace dusk::mods::svc \ No newline at end of file diff --git a/src/dusk/mods/svc/id_allocator.hpp b/src/dusk/mods/svc/id_allocator.hpp new file mode 100644 index 0000000000..2ee1118028 --- /dev/null +++ b/src/dusk/mods/svc/id_allocator.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +namespace dusk::mods::svc { + +[[noreturn]] void id_allocator_exhausted(); + +template + requires std::is_integral_v +class PlainIdAllocator { + std::vector reusable; + T alloc_next; + T alloc_max; + +public: + constexpr explicit PlainIdAllocator(T first, T max = std::numeric_limits::max()) + : alloc_next(first), alloc_max(max) {} + + T alloc() { + if (reusable.empty()) { + if (alloc_next == alloc_max) { + id_allocator_exhausted(); + } + + return alloc_next++; + } + + auto val = reusable.back(); + reusable.pop_back(); + return val; + } + + void free(T value) { + assert(value < alloc_max && value < alloc_next); + + reusable.push_back(value); + } +}; + +} // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/slot_map.hpp b/src/dusk/mods/svc/internal.hpp similarity index 66% rename from src/dusk/mods/svc/slot_map.hpp rename to src/dusk/mods/svc/internal.hpp index 3e45635d79..496b2a58a5 100644 --- a/src/dusk/mods/svc/slot_map.hpp +++ b/src/dusk/mods/svc/internal.hpp @@ -1,5 +1,10 @@ #pragma once +#include "../loader/loader.hpp" +#include "../log_buffer.hpp" + +#include + #include #include #include @@ -106,9 +111,7 @@ public: return erase(handle); } - size_t erase_all(const LoadedMod& owner) { - return take_all(owner).size(); - } + size_t erase_all(const LoadedMod& owner) { return take_all(owner).size(); } template void for_each(Fn&& fn) const { @@ -122,6 +125,9 @@ public: } } + // Returns the index of the handle within the slot map. + static constexpr uint32_t index_of(Handle handle) { return handle_index(handle); } + private: struct Slot { uint32_t generation = 1; @@ -184,5 +190,69 @@ private: std::vector m_freeSlots; }; +template +class PerMod { +public: + template + T& get_or_create(LoadedMod& mod, Args&&... args) { + auto& state = m_states[&mod]; + if (!state) { + state = std::make_unique(std::forward(args)...); + } + return *state; + } + + T* find(LoadedMod& mod) { + const auto found = m_states.find(&mod); + return found != m_states.end() ? found->second.get() : nullptr; + } + + const T* find(const LoadedMod& mod) const { + const auto found = m_states.find(&mod); + return found != m_states.end() ? found->second.get() : nullptr; + } + + void erase(LoadedMod& mod) { m_states.erase(&mod); } + bool contains(const LoadedMod& mod) const { return m_states.contains(&mod); } + void clear() { m_states.clear(); } + +private: + std::unordered_map> m_states; +}; + +template +ModResult guarded(ModContext* context, std::string_view operation, Fn&& fn) noexcept { + try { + return fn(); + } catch (const std::exception& exception) { + log::emit(log::Source::Mod, mod_id_from_context(context), LOG_LEVEL_ERROR, + fmt::format("{}: {}", operation, exception.what())); + } catch (...) { + log::emit(log::Source::Mod, mod_id_from_context(context), LOG_LEVEL_ERROR, + fmt::format("{}: unknown exception", operation)); + } + return MOD_ERROR; +} + +// Wraps a mod callback, attempting to catch any escaping exceptions. +// Immediately logs and fails the mod. +template +void guarded_callback(LoadedMod& mod, std::string_view operation, Fn&& fn) noexcept { + try { + fn(); + } catch (const std::exception& exception) { + fail_mod(mod, MOD_ERROR, fmt::format("exception in {}: {}", operation, exception.what())); + } catch (...) { + fail_mod(mod, MOD_ERROR, fmt::format("unknown exception in {}", operation)); + } +} + } // namespace svc } // namespace dusk::mods + +// Wraps service functions, catching C++ exceptions to avoid unwinding into mod code. +#define SERVICE_FUNCTION(function) \ + [](ModContext* context, auto... arguments) -> ModResult { \ + return ::dusk::mods::svc::guarded( \ + context, #function, [&] { return function(context, arguments...); }); \ + } diff --git a/src/dusk/mods/svc/item.cpp b/src/dusk/mods/svc/item.cpp index 44b0eff37e..2fcb018c95 100644 --- a/src/dusk/mods/svc/item.cpp +++ b/src/dusk/mods/svc/item.cpp @@ -4,28 +4,19 @@ #include "dusk/mods/item.hpp" #include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" #include "d/d_item_data.h" -#include - namespace dusk::mods::svc { namespace { constexpr size_t kMaxCheckNameLength = 256; constexpr uint32_t kGiveFlagMask = ITEM_GIVE_SILENT | ITEM_GIVE_RESOLVE; -bool is_valid_check_name(const char* name) { - if (name == nullptr) { - return false; - } - const std::string_view view{name}; - return !view.empty() && view.size() <= kMaxCheckNameLength; -} - ModResult item_set_check_override(ModContext* context, const char* name, uint8_t itemNo) { auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_check_name(name)) { + if (mod == nullptr || !utils::is_valid_name(name, kMaxCheckNameLength)) { return MOD_INVALID_ARGUMENT; } return item_check_set_override(*mod, name, itemNo); @@ -33,7 +24,7 @@ ModResult item_set_check_override(ModContext* context, const char* name, uint8_t ModResult item_clear_check_override(ModContext* context, const char* name) { auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_check_name(name)) { + if (mod == nullptr || !utils::is_valid_name(name, kMaxCheckNameLength)) { return MOD_INVALID_ARGUMENT; } return item_check_clear_override(*mod, name); @@ -46,7 +37,9 @@ ModResult item_set_check_resolver(ModContext* context, const char* name, ItemChe } auto* mod = mod_from_context(context); - if (mod == nullptr || fn == nullptr || (name != nullptr && !is_valid_check_name(name))) { + if (mod == nullptr || fn == nullptr || + (name != nullptr && !utils::is_valid_name(name, kMaxCheckNameLength))) + { return MOD_INVALID_ARGUMENT; } @@ -68,7 +61,9 @@ ModResult item_clear_check_resolver(ModContext* context, ItemCheckHandle handle) ModResult item_resolve_check( ModContext* context, const char* name, uint8_t originalItemNo, uint8_t* outItem) { - if (mod_from_context(context) == nullptr || !is_valid_check_name(name) || outItem == nullptr) { + if (mod_from_context(context) == nullptr || !utils::is_valid_name(name, kMaxCheckNameLength) || + outItem == nullptr) + { return MOD_INVALID_ARGUMENT; } *outItem = item_check(name, originalItemNo, nullptr); @@ -77,7 +72,7 @@ ModResult item_resolve_check( ModResult item_resolve_check_full(ModContext* context, const char* name, uint8_t originalItemNo, ItemCheckResolution* outResolution) { - if (mod_from_context(context) == nullptr || !is_valid_check_name(name) || + if (mod_from_context(context) == nullptr || !utils::is_valid_name(name, kMaxCheckNameLength) || outResolution == nullptr) { return MOD_INVALID_ARGUMENT; @@ -89,7 +84,8 @@ ModResult item_resolve_check_full(ModContext* context, const char* name, uint8_t ModResult item_give_item( ModContext* context, const char* checkName, uint8_t itemNo, uint32_t flags) { auto* mod = mod_from_context(context); - if (mod == nullptr || (checkName != nullptr && !is_valid_check_name(checkName)) || + if (mod == nullptr || + (checkName != nullptr && !utils::is_valid_name(checkName, kMaxCheckNameLength)) || (flags & ~kGiveFlagMask) != 0) { return MOD_INVALID_ARGUMENT; diff --git a/src/dusk/mods/svc/net.cpp b/src/dusk/mods/svc/net.cpp new file mode 100644 index 0000000000..4b929524cf --- /dev/null +++ b/src/dusk/mods/svc/net.cpp @@ -0,0 +1,574 @@ +#include "registry.hpp" + +#include "internal.hpp" +#include "net.hpp" + +#include "dusk/logging.h" +#include "dusk/mods/loader/loader.hpp" +#include "mods/svc/net.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dusk::mods::svc { +namespace { + +constexpr size_t MaxListenersPerMod = 4; +constexpr size_t MaxStreamsPerMod = 32; +constexpr size_t MaxDatagramsPerMod = 4; +constexpr size_t MaxResolversPerMod = 8; +constexpr size_t DefaultSendQueueBytes = 1024 * 1024; +constexpr size_t MaxSendQueueBytes = 8 * 1024 * 1024; +constexpr std::chrono::milliseconds DefaultConnectTimeout{10000}; +constexpr std::chrono::milliseconds DefaultCloseTimeout{5000}; +constexpr std::chrono::milliseconds InboundStallTimeout{30000}; + +enum class HandleKind { + Listener, + Stream, + Datagram, + Resolver, +}; + +struct HandleState { + borealis::net::SocketId socket = 0; + HandleKind kind = HandleKind::Stream; + // Stream sends remain unavailable until the mod observes CONNECTED. + bool openPublished = false; +}; + +struct ModState { + ModState() + : context{borealis::net::ContextOptions{ + .maxQueuedBytes = 16 * 1024 * 1024, + .maxQueuedEvents = 4096, + .maxStreams = MaxStreamsPerMod, + .maxListeners = MaxListenersPerMod, + .maxDatagramSockets = MaxDatagramsPerMod, + .maxResolvers = MaxResolversPerMod, + }} {} + + borealis::net::Context context; + std::unordered_map handles; + borealis::net::Event currentEvent; +}; + +SlotMap s_handles; +PerMod s_modStates; + +NetError map_error(borealis::net::Error error) { + switch (error) { + case borealis::net::Error::None: + return NET_ERROR_NONE; + case borealis::net::Error::InvalidEndpoint: + return NET_ERROR_INVALID_ENDPOINT; + case borealis::net::Error::Resolve: + return NET_ERROR_RESOLVE; + case borealis::net::Error::Timeout: + return NET_ERROR_TIMEOUT; + case borealis::net::Error::Refused: + return NET_ERROR_REFUSED; + case borealis::net::Error::Unreachable: + return NET_ERROR_UNREACHABLE; + case borealis::net::Error::Reset: + return NET_ERROR_RESET; + case borealis::net::Error::AddressInUse: + return NET_ERROR_ADDRESS_IN_USE; + case borealis::net::Error::Permission: + return NET_ERROR_PERMISSION; + case borealis::net::Error::TooLarge: + return NET_ERROR_TOO_LARGE; + case borealis::net::Error::Canceled: + return NET_ERROR_CANCELED; + case borealis::net::Error::Network: + default: + return NET_ERROR_NETWORK; + } +} + +ModResult map_send_result(borealis::net::SendResult result) { + switch (result) { + case borealis::net::SendResult::Ok: + return MOD_OK; + case borealis::net::SendResult::NotOpen: + return MOD_UNAVAILABLE; + case borealis::net::SendResult::QueueFull: + return MOD_CONFLICT; + case borealis::net::SendResult::TooLarge: + case borealis::net::SendResult::InvalidEndpoint: + return MOD_INVALID_ARGUMENT; + } + return MOD_ERROR; +} + +NetEventType map_event(borealis::net::Event::Kind kind) { + switch (kind) { + case borealis::net::Event::Kind::Connected: + return NET_EVENT_CONNECTED; + case borealis::net::Event::Kind::Accepted: + return NET_EVENT_ACCEPTED; + case borealis::net::Event::Kind::StreamData: + return NET_EVENT_STREAM_DATA; + case borealis::net::Event::Kind::Datagram: + return NET_EVENT_DATAGRAM; + case borealis::net::Event::Kind::Dropped: + return NET_EVENT_DROPPED; + case borealis::net::Event::Kind::Resolved: + return NET_EVENT_RESOLVED; + case borealis::net::Event::Kind::Closed: + return NET_EVENT_CLOSED; + default: + return NET_EVENT_NONE; + } +} + +bool valid_endpoint(std::string_view text, std::string_view scheme, bool requireLiteral) { + if (text.empty() || text.size() >= NET_ENDPOINT_MAX) { + return false; + } + const auto endpoint = borealis::net::parse_endpoint(text); + return endpoint && endpoint->scheme == scheme && (!requireLiteral || endpoint->literal); +} + +bool copy_endpoint(NetEndpoint& output, std::string_view endpoint) { + if (endpoint.size() >= sizeof(output.text)) { + output.text[0] = '\0'; + return false; + } + std::memcpy(output.text, endpoint.data(), endpoint.size()); + output.text[endpoint.size()] = '\0'; + return true; +} + +size_t bounded_send_queue(size_t requested) { + return requested != 0 ? requested : DefaultSendQueueBytes; +} + +std::chrono::milliseconds timeout_or_default(uint32_t value, std::chrono::milliseconds fallback) { + return value != 0 ? std::chrono::milliseconds{value} : fallback; +} + +size_t count_handles(const LoadedMod& mod, HandleKind kind) { + size_t count = 0; + s_handles.for_each([&](NetHandle, const auto& entry) { + if (entry.owner == &mod && entry.value.kind == kind) { + ++count; + } + }); + return count; +} + +ModState& state_for(LoadedMod& mod) { + return s_modStates.get_or_create(mod); +} + +ModState* find_state(LoadedMod& mod) { + return s_modStates.find(mod); +} + +NetHandle add_handle(LoadedMod& mod, ModState& state, borealis::net::SocketId socket, + HandleKind kind, bool openPublished) { + const NetHandle handle = s_handles.emplace(mod, HandleState{ + .socket = socket, + .kind = kind, + .openPublished = openPublished, + }); + state.handles.emplace(socket, handle); + return handle; +} + +ModResult net_connect(ModContext* context, const NetConnectDesc* desc, NetHandle* outHandle) { + if (outHandle != nullptr) { + *outHandle = 0; + } + auto* mod = mod_from_context(context); + if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(NetConnectDesc) || + desc->endpoint == nullptr || outHandle == nullptr) + { + return MOD_INVALID_ARGUMENT; + } + if (!declares_import(*mod, NET_SERVICE_ID)) { + return MOD_UNSUPPORTED; + } + const std::string_view endpoint{desc->endpoint}; + if (!valid_endpoint(endpoint, "tcp", false) || desc->max_send_queue_bytes > MaxSendQueueBytes) { + return MOD_INVALID_ARGUMENT; + } + if (!borealis::net::available()) { + return MOD_UNAVAILABLE; + } + if (count_handles(*mod, HandleKind::Stream) >= MaxStreamsPerMod) { + return MOD_CONFLICT; + } + auto& state = state_for(*mod); + const size_t maxSendQueueBytes = bounded_send_queue(desc->max_send_queue_bytes); + const borealis::net::SocketId socket = state.context.connect(endpoint, + borealis::net::StreamOptions{ + .connectTimeout = timeout_or_default(desc->connect_timeout_ms, DefaultConnectTimeout), + .closeTimeout = timeout_or_default(desc->close_timeout_ms, DefaultCloseTimeout), + .inboundStallTimeout = InboundStallTimeout, + .maxSendQueueBytes = maxSendQueueBytes, + .readChunkBytes = 64 * 1024, + .noDelay = desc->no_delay, + }, + desc->user_data); + if (socket == 0) { + return MOD_CONFLICT; + } + *outHandle = add_handle(*mod, state, socket, HandleKind::Stream, false); + return MOD_OK; +} + +ModResult net_listen(ModContext* context, const NetListenDesc* desc, NetHandle* outHandle, + NetEndpoint* outLocal, NetError* outError) { + if (outHandle != nullptr) { + *outHandle = 0; + } + if (outLocal != nullptr) { + *outLocal = {}; + } + if (outError != nullptr) { + *outError = NET_ERROR_NONE; + } + auto* mod = mod_from_context(context); + if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(NetListenDesc) || + desc->bind == nullptr || outHandle == nullptr || outLocal == nullptr) + { + return MOD_INVALID_ARGUMENT; + } + if (!declares_import(*mod, NET_SERVICE_ID)) { + return MOD_UNSUPPORTED; + } + const std::string_view bind{desc->bind}; + if (!valid_endpoint(bind, "tcp", true) || desc->max_send_queue_bytes > MaxSendQueueBytes) { + return MOD_INVALID_ARGUMENT; + } + if (!borealis::net::available()) { + return MOD_UNAVAILABLE; + } + if (count_handles(*mod, HandleKind::Listener) >= MaxListenersPerMod) { + return MOD_CONFLICT; + } + auto& state = state_for(*mod); + const size_t maxSendQueueBytes = bounded_send_queue(desc->max_send_queue_bytes); + const auto result = state.context.listen(bind, + borealis::net::ListenOptions{ + .accepted = + borealis::net::StreamOptions{ + .connectTimeout = DefaultConnectTimeout, + .closeTimeout = timeout_or_default(desc->close_timeout_ms, DefaultCloseTimeout), + .inboundStallTimeout = InboundStallTimeout, + .maxSendQueueBytes = maxSendQueueBytes, + .readChunkBytes = 64 * 1024, + .noDelay = desc->no_delay, + }, + .backlog = 64, + .reuseAddress = true, + }, + desc->user_data); + if (result.id == 0) { + if (outError != nullptr) { + *outError = map_error(result.error); + } + DuskLog.error("[{}] could not listen on '{}': {}", mod->metadata.id, bind, result.message); + return MOD_ERROR; + } + if (!copy_endpoint(*outLocal, result.localEndpoint)) { + state.context.close(result.id); + return MOD_ERROR; + } + *outHandle = add_handle(*mod, state, result.id, HandleKind::Listener, true); + return MOD_OK; +} + +ModResult net_open_datagram(ModContext* context, const NetDatagramDesc* desc, NetHandle* outHandle, + NetEndpoint* outLocal, NetError* outError) { + if (outHandle != nullptr) { + *outHandle = 0; + } + if (outLocal != nullptr) { + *outLocal = {}; + } + if (outError != nullptr) { + *outError = NET_ERROR_NONE; + } + auto* mod = mod_from_context(context); + if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(NetDatagramDesc) || + desc->bind == nullptr || outHandle == nullptr || outLocal == nullptr) + { + return MOD_INVALID_ARGUMENT; + } + if (!declares_import(*mod, NET_SERVICE_ID)) { + return MOD_UNSUPPORTED; + } + const std::string_view bind{desc->bind}; + if (!valid_endpoint(bind, "udp", true) || desc->max_send_queue_bytes > MaxSendQueueBytes) { + return MOD_INVALID_ARGUMENT; + } + if (!borealis::net::available()) { + return MOD_UNAVAILABLE; + } + if (count_handles(*mod, HandleKind::Datagram) >= MaxDatagramsPerMod) { + return MOD_CONFLICT; + } + auto& state = state_for(*mod); + const size_t maxSendQueueBytes = bounded_send_queue(desc->max_send_queue_bytes); + const auto result = state.context.open_datagram(bind, + borealis::net::DatagramOptions{ + .maxSendQueueBytes = maxSendQueueBytes, + .recvBufferBytes = 1024 * 1024, + .sendBufferBytes = 1024 * 1024, + }, + desc->user_data); + if (result.id == 0) { + if (outError != nullptr) { + *outError = map_error(result.error); + } + DuskLog.error("[{}] could not bind datagram socket on '{}': {}", mod->metadata.id, bind, + result.message); + return MOD_ERROR; + } + if (!copy_endpoint(*outLocal, result.localEndpoint)) { + state.context.close(result.id); + return MOD_ERROR; + } + *outHandle = add_handle(*mod, state, result.id, HandleKind::Datagram, true); + return MOD_OK; +} + +ModResult net_resolve( + ModContext* context, const char* endpoint, void* userData, NetHandle* outHandle) { + if (outHandle != nullptr) { + *outHandle = 0; + } + auto* mod = mod_from_context(context); + if (mod == nullptr || endpoint == nullptr || outHandle == nullptr) { + return MOD_INVALID_ARGUMENT; + } + if (!declares_import(*mod, NET_SERVICE_ID)) { + return MOD_UNSUPPORTED; + } + const std::string_view endpointText{endpoint}; + if (endpointText.empty() || endpointText.size() >= NET_ENDPOINT_MAX || + !borealis::net::parse_endpoint(endpointText)) + { + return MOD_INVALID_ARGUMENT; + } + if (!borealis::net::available()) { + return MOD_UNAVAILABLE; + } + if (count_handles(*mod, HandleKind::Resolver) >= MaxResolversPerMod) { + return MOD_CONFLICT; + } + auto& state = state_for(*mod); + const auto socket = state.context.resolve(endpointText, userData); + if (socket == 0) { + return MOD_CONFLICT; + } + *outHandle = add_handle(*mod, state, socket, HandleKind::Resolver, false); + return MOD_OK; +} + +ModResult net_poll_event(ModContext* context, NetEvent* outEvent) { + const uint32_t structSize = outEvent != nullptr ? outEvent->struct_size : 0; + auto* mod = mod_from_context(context); + if (mod == nullptr || outEvent == nullptr || structSize < sizeof(NetEvent)) { + return MOD_INVALID_ARGUMENT; + } + NetEvent output{.struct_size = structSize, .error_message = ""}; + auto* state = find_state(*mod); + if (state == nullptr) { + std::memcpy(outEvent, &output, sizeof(output)); + return MOD_OK; + } + + while (state->context.poll(state->currentEvent)) { + const auto handleFound = state->handles.find(state->currentEvent.id); + if (handleFound == state->handles.end()) { + continue; + } + const NetHandle handle = handleFound->second; + auto* handleState = s_handles.find_owned(handle, *mod); + if (handleState == nullptr) { + continue; + } + output.type = map_event(state->currentEvent.kind); + output.handle = handle; + output.user_data = state->currentEvent.userData; + output.data = state->currentEvent.data.data(); + output.size = state->currentEvent.data.size(); + output.dropped = state->currentEvent.dropped; + output.error = map_error(state->currentEvent.error); + output.error_message = state->currentEvent.message.c_str(); + if (!copy_endpoint(output.endpoint, state->currentEvent.endpoint)) { + output.endpoint = {}; + } + + if (state->currentEvent.kind == borealis::net::Event::Kind::Connected) { + handleState->value.openPublished = true; + } else if (state->currentEvent.kind == borealis::net::Event::Kind::Accepted) { + const NetHandle accepted = + add_handle(*mod, *state, state->currentEvent.accepted, HandleKind::Stream, true); + output.accepted = accepted; + } else if (state->currentEvent.kind == borealis::net::Event::Kind::Closed || + state->currentEvent.kind == borealis::net::Event::Kind::Resolved) + { + state->handles.erase(state->currentEvent.id); + s_handles.erase(handle); + } + + std::memcpy(outEvent, &output, sizeof(output)); + return MOD_OK; + } + + std::memcpy(outEvent, &output, sizeof(output)); + return MOD_OK; +} + +ModResult net_send(ModContext* context, NetHandle handle, const void* data, size_t size) { + auto* mod = mod_from_context(context); + if (mod == nullptr || (size != 0 && data == nullptr)) { + return MOD_INVALID_ARGUMENT; + } + auto* handleState = s_handles.find_owned(handle, *mod); + auto* state = find_state(*mod); + if (handleState == nullptr || state == nullptr || + handleState->value.kind != HandleKind::Stream || !handleState->value.openPublished) + { + return MOD_UNAVAILABLE; + } + const auto* bytes = static_cast(data); + return map_send_result(state->context.send(handleState->value.socket, {bytes, size})); +} + +ModResult net_send_to( + ModContext* context, NetHandle handle, const char* endpoint, const void* data, size_t size) { + auto* mod = mod_from_context(context); + if (mod == nullptr || endpoint == nullptr || (size != 0 && data == nullptr)) { + return MOD_INVALID_ARGUMENT; + } + auto* handleState = s_handles.find_owned(handle, *mod); + auto* state = find_state(*mod); + if (handleState == nullptr || state == nullptr || + handleState->value.kind != HandleKind::Datagram) + { + return MOD_UNAVAILABLE; + } + const auto* bytes = static_cast(data); + return map_send_result( + state->context.send_to(handleState->value.socket, endpoint, {bytes, size})); +} + +ModResult net_set_user_data(ModContext* context, NetHandle handle, void* userData) { + auto* mod = mod_from_context(context); + if (mod == nullptr) { + return MOD_INVALID_ARGUMENT; + } + const auto* handleState = s_handles.find_owned(handle, *mod); + auto* state = find_state(*mod); + if (handleState == nullptr || state == nullptr) { + return MOD_UNAVAILABLE; + } + state->context.set_user_data(handleState->value.socket, userData); + return MOD_OK; +} + +ModResult net_stats(ModContext* context, NetHandle handle, NetStats* outStats) { + const uint32_t structSize = outStats != nullptr ? outStats->struct_size : 0; + auto* mod = mod_from_context(context); + if (mod == nullptr || outStats == nullptr || structSize < sizeof(NetStats)) { + return MOD_INVALID_ARGUMENT; + } + NetStats output{.struct_size = structSize}; + const auto* handleState = s_handles.find_owned(handle, *mod); + auto* state = find_state(*mod); + if (handleState == nullptr || state == nullptr) { + std::memcpy(outStats, &output, sizeof(output)); + return MOD_UNAVAILABLE; + } + const auto stats = state->context.stats(handleState->value.socket); + if (!stats) { + std::memcpy(outStats, &output, sizeof(output)); + return MOD_UNAVAILABLE; + } + output.queued_send_bytes = stats->queuedSendBytes; + output.inbound_dropped = stats->inboundDropped; + output.send_failures = stats->sendFailures; + output.bytes_sent = stats->bytesSent; + output.bytes_received = stats->bytesReceived; + std::memcpy(outStats, &output, sizeof(output)); + return MOD_OK; +} + +ModResult net_close(ModContext* context, NetHandle handle) { + auto* mod = mod_from_context(context); + if (mod == nullptr) { + return MOD_INVALID_ARGUMENT; + } + auto* handleState = s_handles.find_owned(handle, *mod); + auto* state = find_state(*mod); + if (handleState == nullptr || state == nullptr) { + return MOD_UNAVAILABLE; + } + state->context.close(handleState->value.socket); + return MOD_OK; +} + +void net_mod_deactivating(LoadedMod& mod) { + (void)s_handles.take_all(mod); + s_modStates.erase(mod); +} + +void net_mod_detached(LoadedMod& mod) { + assert(find_state(mod) == nullptr); + bool found = false; + s_handles.for_each([&](NetHandle, const auto& entry) { found = found || entry.owner == &mod; }); + assert(!found); +} + +void net_shutdown() { + s_modStates.clear(); + s_handles = {}; +} + +bool net_available() { + return borealis::net::available(); +} + +constexpr NetService s_netService{ + .header = SERVICE_HEADER(NetService, NET_SERVICE_MAJOR, NET_SERVICE_MINOR), + .connect = SERVICE_FUNCTION(net_connect), + .listen = SERVICE_FUNCTION(net_listen), + .open_datagram = SERVICE_FUNCTION(net_open_datagram), + .resolve = SERVICE_FUNCTION(net_resolve), + .poll_event = SERVICE_FUNCTION(net_poll_event), + .send = SERVICE_FUNCTION(net_send), + .send_to = SERVICE_FUNCTION(net_send_to), + .set_user_data = SERVICE_FUNCTION(net_set_user_data), + .stats = SERVICE_FUNCTION(net_stats), + .close = SERVICE_FUNCTION(net_close), +}; + +} // namespace + +constinit const ServiceModule g_netModule{ + .id = NET_SERVICE_ID, + .majorVersion = NET_SERVICE_MAJOR, + .minorVersion = NET_SERVICE_MINOR, + .service = &s_netService, + .available = net_available, + .modDeactivating = net_mod_deactivating, + .modDetached = net_mod_detached, + .shutdown = net_shutdown, +}; + +} // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/net.hpp b/src/dusk/mods/svc/net.hpp new file mode 100644 index 0000000000..34ca17362f --- /dev/null +++ b/src/dusk/mods/svc/net.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "dusk/app_info.hpp" +#include "dusk/mods/loader/loader.hpp" +#include "mods/svc/http.h" +#include "mods/svc/net.h" +#include "mods/svc/websocket.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace dusk::mods::svc { + +inline bool ascii_iequals(std::string_view left, std::string_view right) { + if (left.size() != right.size()) { + return false; + } + const auto ascii_lower = [](char value) { + return value >= 'A' && value <= 'Z' ? static_cast(value - 'A' + 'a') : value; + }; + return std::ranges::equal( + left, right, [&](char a, char b) { return ascii_lower(a) == ascii_lower(b); }); +} + +inline bool valid_header_name(std::string_view name) { + constexpr std::string_view Separators{"()<>@,;:\\\"/[]?={} \t"}; + return !name.empty() && std::ranges::all_of(name, [&](unsigned char value) { + return value > 32 && value < 127 && + Separators.find(static_cast(value)) == std::string_view::npos; + }); +} + +inline bool valid_header_value(std::string_view value, bool allowHorizontalTab) { + return std::ranges::all_of(value, [=](unsigned char character) { + return character >= 32 || (allowHorizontalTab && character == '\t'); + }) && std::ranges::none_of(value, [](unsigned char character) { return character == 127; }); +} + +inline bool is_reserved_header(std::string_view name, std::span reserved) { + return std::ranges::any_of( + reserved, [&](std::string_view value) { return ascii_iequals(name, value); }); +} + +inline bool valid_header(std::string_view name, std::string_view value, + std::span reserved, bool allowHorizontalTab = false) { + return valid_header_name(name) && valid_header_value(value, allowHorizontalTab) && + !is_reserved_header(name, reserved); +} + +inline bool declares_import(const LoadedMod& mod, std::string_view serviceId) { + return std::ranges::any_of( + mod.manifestInfo.imports, [&](const ModManifestInfo::Import& serviceImport) { + return serviceImport.id == serviceId; + }); +} + +inline bool is_network_service(std::string_view serviceId) { + return serviceId == HTTP_SERVICE_ID || serviceId == NET_SERVICE_ID || + serviceId == WEBSOCKET_SERVICE_ID; +} + +inline std::string user_agent(const LoadedMod& mod) { + std::string version{mod.metadata.version}; + for (char& character : version) { + const auto value = static_cast(character); + if (value <= 32 || value >= 127) { + character = '_'; + } + } + return fmt::format("{}/{} {}/{}", AppName, BOREALIS_APP_VERSION, mod.metadata.id, version); +} + +} // namespace dusk::mods::svc::network diff --git a/src/dusk/mods/svc/overlay.cpp b/src/dusk/mods/svc/overlay.cpp index f4ad2ff6a6..a8e8d0434b 100644 --- a/src/dusk/mods/svc/overlay.cpp +++ b/src/dusk/mods/svc/overlay.cpp @@ -1,15 +1,17 @@ +#include "internal.hpp" #include "registry.hpp" -#include "slot_map.hpp" -#include -#include "JSystem/JKernel/JKRArchive.h" -#include "aurora/dvd.h" #include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" #include "mods/svc/overlay.h" +#include "JSystem/JKernel/JKRArchive.h" + +#include +#include + #include #include -#include #include #include #include @@ -254,22 +256,14 @@ bool consume_overlays_dirty() { constexpr size_t kMaxOverlayFileSize = UINT32_MAX; -bool is_valid_disc_path(const char* discPath) { - if (discPath == nullptr) { - return false; - } - const std::string_view path{discPath}; - return path.starts_with('/') && is_safe_resource_path(path.substr(1)); -} - ModResult overlay_add_file( ModContext* context, const char* discPath, const char* bundlePath, OverlayHandle* outHandle) { if (outHandle != nullptr) { *outHandle = 0; } auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_disc_path(discPath) || bundlePath == nullptr || - !is_safe_resource_path(bundlePath)) + if (mod == nullptr || discPath == nullptr || !utils::is_valid_disc_path(discPath) || + bundlePath == nullptr || !utils::is_safe_resource_path(bundlePath)) { return MOD_INVALID_ARGUMENT; } @@ -300,8 +294,8 @@ ModResult overlay_add_buffer(ModContext* context, const char* discPath, const vo *outHandle = 0; } auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_disc_path(discPath) || (data == nullptr && size != 0) || - size > kMaxOverlayFileSize) + if (mod == nullptr || discPath == nullptr || !utils::is_valid_disc_path(discPath) || + (data == nullptr && size != 0) || size > kMaxOverlayFileSize) { return MOD_INVALID_ARGUMENT; } diff --git a/src/dusk/mods/svc/registry.cpp b/src/dusk/mods/svc/registry.cpp index 13c042af3d..69903fcf4f 100644 --- a/src/dusk/mods/svc/registry.cpp +++ b/src/dusk/mods/svc/registry.cpp @@ -3,25 +3,27 @@ #include "dusk/app_info.hpp" #include "dusk/logging.h" #include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" +#include + +#include +#include #include #include +#include #include +#include #include namespace dusk::mods::svc { namespace { +uint64_t s_serviceGeneration = 0; std::unordered_map s_services; +std::unordered_set s_unavailableServices; std::vector s_modules; -std::string service_key(std::string_view id, const uint16_t majorVersion) { - std::string key{id}; - key.push_back('\x1f'); - key += std::to_string(majorVersion); - return key; -} - const char* mod_id(const LoadedMod* mod) { return mod != nullptr ? mod->metadata.id.c_str() : AppName; } @@ -47,19 +49,37 @@ bool validate_service_header(const ServiceHeader* header, const char* serviceId, } void clear_services() { + ++s_serviceGeneration; s_services.clear(); + s_unavailableServices.clear(); s_modules.clear(); } } // namespace -bool valid_service_id(const char* serviceId) { - return serviceId != nullptr && serviceId[0] != '\0'; +uint64_t services_generation() noexcept { + return s_serviceGeneration; +} + +std::vector list_services() { + std::vector services; + for (const auto& [key, record] : s_services) { + if (record.service == nullptr || + (record.provider != nullptr && !record.provider->is_enabled())) + { + continue; + } + services.push_back({record.id, record.majorVersion, record.minorVersion, + record.provider != nullptr ? record.provider->metadata.id : std::string{}}); + } + std::ranges::sort( + services, {}, [](const auto& service) { return std::tie(service.id, service.major); }); + return services; } ModResult register_service(const char* serviceId, const uint16_t majorVersion, const uint16_t minorVersion, const void* service, LoadedMod* provider, const bool deferred) { - if (!valid_service_id(serviceId)) { + if (!utils::is_valid_name(serviceId)) { DuskLog.error("[{}] attempted to register a service with no id", mod_id(provider)); return MOD_INVALID_ARGUMENT; } @@ -76,6 +96,7 @@ ModResult register_service(const char* serviceId, const uint16_t majorVersion, return MOD_CONFLICT; } + ++s_serviceGeneration; s_services.emplace(key, ServiceRecord{ serviceId, majorVersion, @@ -89,7 +110,7 @@ ModResult register_service(const char* serviceId, const uint16_t majorVersion, ModResult publish_deferred_service( LoadedMod& provider, const char* serviceId, const uint16_t majorVersion, const void* service) { - if (!valid_service_id(serviceId) || service == nullptr) { + if (!utils::is_valid_name(serviceId) || service == nullptr) { return MOD_INVALID_ARGUMENT; } @@ -109,12 +130,14 @@ ModResult publish_deferred_service( return MOD_INVALID_ARGUMENT; } + ++s_serviceGeneration; record.service = service; record.minorVersion = header->minor_version; return MOD_OK; } void remove_services_for_provider(const LoadedMod& provider) { + ++s_serviceGeneration; std::erase_if( s_services, [&](const auto& entry) { return entry.second.provider == &provider; }); } @@ -129,7 +152,7 @@ const ServiceRecord* find_service( } const ServiceRecord* find_service_record(const char* serviceId, const uint16_t majorVersion) { - if (!valid_service_id(serviceId)) { + if (!utils::is_valid_name(serviceId)) { return nullptr; } @@ -137,8 +160,38 @@ const ServiceRecord* find_service_record(const char* serviceId, const uint16_t m return it != s_services.end() ? &it->second : nullptr; } +std::string describe_missing_service(const char* serviceId, const uint16_t majorVersion, + const uint16_t minMinorVersion) { + const char* message = "Mod requires a service that is unavailable"; + if (std::string_view{serviceId}.starts_with(DUSKLIGHT_SERVICE_ID_PREFIX) && + !s_unavailableServices.contains(service_key(serviceId, majorVersion))) + { + if (const auto* record = find_service_record(serviceId, majorVersion)) { + if (record->provider == nullptr && record->service != nullptr && + record->minorVersion < minMinorVersion) + { + message = "Mod requires a newer Dusklight version"; + } + } else { + std::optional highestMajor; + for (const auto& [key, record] : s_services) { + if (record.provider == nullptr && record.service != nullptr && record.id == serviceId) { + highestMajor = std::max(highestMajor.value_or(0), record.majorVersion); + } + } + if (highestMajor) { + message = majorVersion > *highestMajor ? + "Mod requires a newer Dusklight version" : + "Mod must be updated for the current Dusklight version"; + } + } + } + return fmt::format("{} (missing: {})", message, serviceId); +} + ModResult register_module(const ServiceModule& module) { if (module.available != nullptr && !module.available()) { + s_unavailableServices.insert(service_key(module.id, module.majorVersion)); return MOD_UNAVAILABLE; } const auto result = register_service( @@ -146,6 +199,7 @@ ModResult register_module(const ServiceModule& module) { if (result != MOD_OK) { return result; } + s_unavailableServices.erase(service_key(module.id, module.majorVersion)); s_modules.push_back(&module); if (module.initialize != nullptr) { module.initialize(); @@ -215,6 +269,8 @@ void ModLoader::init_services() { &svc::g_resourceModule, &svc::g_fileModule, &svc::g_httpModule, + &svc::g_netModule, + &svc::g_websocketModule, &svc::g_hookModule, &svc::g_overlayModule, &svc::g_textureModule, @@ -225,12 +281,14 @@ void ModLoader::init_services() { &svc::g_cameraModule, &svc::g_windowModule, &svc::g_gfxModule, + &svc::g_audioResModule, &svc::g_saveModule, &svc::g_stageModule, &svc::g_itemModule, &svc::g_flowModule, &svc::g_messageModule, &svc::g_gamemodeModule, + &svc::g_actorModule, }) { svc::register_module(*module); @@ -243,7 +301,7 @@ bool ModLoader::register_static_service_exports(LoadedMod& mod) { } for (const auto* serviceExport : mod.native->parsed.exports) { - if (!svc::valid_service_id(serviceExport->service_id.chars)) { + if (!utils::is_valid_name(serviceExport->service_id.chars)) { fail_mod(mod, MOD_INVALID_ARGUMENT, "Invalid service export descriptor"); return false; } @@ -268,6 +326,9 @@ bool ModLoader::register_static_service_exports(LoadedMod& mod) { std::string ModLoader::describe_missing_import( const char* serviceId, const uint16_t majorVersion, const uint16_t minMinorVersion) const { + if (std::string_view{serviceId}.starts_with(DUSKLIGHT_SERVICE_ID_PREFIX)) { + return svc::describe_missing_service(serviceId, majorVersion, minMinorVersion); + } if (const auto* record = svc::find_service_record(serviceId, majorVersion)) { if (record->service == nullptr) { return fmt::format("Required service {}@{} was never published by provider '{}'", @@ -283,7 +344,7 @@ std::string ModLoader::describe_missing_import( continue; } for (const auto* serviceExport : other.native->parsed.exports) { - if (svc::valid_service_id(serviceExport->service_id.chars) && + if (utils::is_valid_name(serviceExport->service_id.chars) && std::string_view{serviceExport->service_id.chars} == serviceId && serviceExport->major_version == majorVersion) { @@ -294,7 +355,7 @@ std::string ModLoader::describe_missing_import( } } - return fmt::format("Required service unavailable: {}@{}", serviceId, majorVersion); + return svc::describe_missing_service(serviceId, majorVersion, minMinorVersion); } bool ModLoader::resolve_service_imports(LoadedMod& mod) { @@ -303,7 +364,7 @@ bool ModLoader::resolve_service_imports(LoadedMod& mod) { } for (const auto* serviceImport : mod.native->parsed.imports) { - if (!svc::valid_service_id(serviceImport->service_id.chars) || + if (!utils::is_valid_name(serviceImport->service_id.chars) || serviceImport->slot == nullptr) { fail_mod(mod, MOD_INVALID_ARGUMENT, "Invalid service import descriptor"); diff --git a/src/dusk/mods/svc/registry.hpp b/src/dusk/mods/svc/registry.hpp index 68ec9353cd..9126f8f55b 100644 --- a/src/dusk/mods/svc/registry.hpp +++ b/src/dusk/mods/svc/registry.hpp @@ -1,6 +1,7 @@ #pragma once #include "dusk/mod_loader.hpp" +#include "dusk/mods/services.hpp" #include "mods/svc/host.h" #include "mods/svc/log.h" @@ -48,7 +49,9 @@ struct ServiceModule { void (*shutdown)() = nullptr; }; -bool valid_service_id(const char* serviceId); +std::vector list_services(); +uint64_t services_generation() noexcept; + ModResult register_service(const char* serviceId, uint16_t majorVersion, uint16_t minorVersion, const void* service, LoadedMod* provider, bool deferred); ModResult publish_deferred_service( @@ -58,6 +61,8 @@ const ServiceRecord* find_service( const char* serviceId, uint16_t majorVersion, uint16_t minMinorVersion); // Unlike find_service, also returns deferred records that have not been published yet. const ServiceRecord* find_service_record(const char* serviceId, uint16_t majorVersion); +std::string describe_missing_service(const char* serviceId, uint16_t majorVersion, + uint16_t minMinorVersion); ModResult register_module(const ServiceModule& module); void modules_mod_deactivating(LoadedMod& mod); @@ -72,6 +77,8 @@ extern const ServiceModule g_logModule; extern const ServiceModule g_resourceModule; extern const ServiceModule g_fileModule; extern const ServiceModule g_httpModule; +extern const ServiceModule g_netModule; +extern const ServiceModule g_websocketModule; extern const ServiceModule g_hookModule; extern const ServiceModule g_overlayModule; extern const ServiceModule g_textureModule; @@ -82,11 +89,13 @@ extern const ServiceModule g_gameModule; extern const ServiceModule g_cameraModule; extern const ServiceModule g_windowModule; extern const ServiceModule g_gfxModule; +extern const ServiceModule g_audioResModule; extern const ServiceModule g_saveModule; extern const ServiceModule g_stageModule; extern const ServiceModule g_itemModule; extern const ServiceModule g_flowModule; extern const ServiceModule g_messageModule; extern const ServiceModule g_gamemodeModule; +extern const ServiceModule g_actorModule; } // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/resource.cpp b/src/dusk/mods/svc/resource.cpp index d92630317e..c373f5a5c9 100644 --- a/src/dusk/mods/svc/resource.cpp +++ b/src/dusk/mods/svc/resource.cpp @@ -2,6 +2,7 @@ #include #include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" #include "mods/svc/resource.h" #include @@ -35,6 +36,10 @@ void resource_remove_mod(LoadedMod& mod) { } } +std::string prefix_path(const char* relativePath) { + return fmt::format("res/{}", relativePath); +} + ModResult resource_load(ModContext* context, const char* relativePath, ResourceBuffer* outBuffer) { if (outBuffer == nullptr || outBuffer->struct_size < sizeof(ResourceBuffer)) { return MOD_INVALID_ARGUMENT; @@ -42,11 +47,11 @@ ModResult resource_load(ModContext* context, const char* relativePath, ResourceB outBuffer->data = nullptr; outBuffer->size = 0; auto* mod = mod_from_context(context); - if (mod == nullptr || relativePath == nullptr || !is_safe_resource_path(relativePath)) { + if (mod == nullptr || relativePath == nullptr || !utils::is_safe_resource_path(relativePath)) { return MOD_INVALID_ARGUMENT; } - const auto entry = fmt::format("res/{}", relativePath); + const auto entry = prefix_path(relativePath); std::vector data; try { data = mod->bundle->readFile(entry); @@ -91,10 +96,32 @@ void resource_free(ModContext* context, ResourceBuffer* buffer) { buffer->size = 0; } +bool file_exists(ModContext* ctx, char const* relative_path) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || relative_path == nullptr || !utils::is_safe_resource_path(relative_path)) + { + return MOD_INVALID_ARGUMENT; + } + + return mod->bundle->file_exists(prefix_path(relative_path)); +} + +bool directory_exists(ModContext* ctx, char const* relative_path) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || relative_path == nullptr || !utils::is_safe_resource_path(relative_path)) + { + return MOD_INVALID_ARGUMENT; + } + + return mod->bundle->directory_exists(prefix_path(relative_path)); +} + constexpr ResourceService s_resourceService{ .header = SERVICE_HEADER(ResourceService, RESOURCE_SERVICE_MAJOR, RESOURCE_SERVICE_MINOR), .load = resource_load, .free = resource_free, + .file_exists = file_exists, + .directory_exists = directory_exists, }; } // namespace diff --git a/src/dusk/mods/svc/save.cpp b/src/dusk/mods/svc/save.cpp index 209c0037fa..d4f0b30e15 100644 --- a/src/dusk/mods/svc/save.cpp +++ b/src/dusk/mods/svc/save.cpp @@ -1,38 +1,56 @@ #include "save.hpp" +#include "internal.hpp" #include "item.hpp" #include "registry.hpp" -#include "aurora/lib/logging.hpp" -#include "d/d_save.h" #include "dusk/main.h" #include "dusk/mods/loader/loader.hpp" +#include "dusk/save_manager.hpp" #include "dusk/utilities.hpp" +#include "dusk/version.hpp" +#include "m_Do/m_Do_MemCard.h" #include "mods/svc/save.h" +#include +#include +#include + +#include +#include #include +#include +#include +#include +#include #include +#include namespace dusk::mods::svc { namespace { -aurora::Module Log("dusk::mods::save"); +borealis::Log Log{"dusk::mods::save"}; constexpr uint32_t kSlotCount = 3; -constexpr size_t kQuestLogSize = 0xA94; -static_assert(kQuestLogSize == QUEST_LOG_SIZE); -constexpr int kSidecarVersion = 1; -constexpr const char* kSidecarName = "mod_saves.json"; +constexpr int kLegacySidecarVersion = 1; +constexpr int kModSidecarVersion = 1; +constexpr const char* kLegacySidecarName = "mod_saves.json"; +constexpr const char* kDefaultSaveName = "gczelda2"; +constexpr s32 kCardChannel = 0; constexpr size_t kMaxBlobNameLength = 256; using BlobMap = std::map>; struct SlotStore { - bool snapshotValid = false; - uint32_t snapshotCrc = 0; std::map mods; }; +struct SaveStore { + std::array slots; + std::set dirtyMods; + bool loaded = false; +}; + struct SaveObserverRecord { uint64_t handle = 0; LoadedMod* mod = nullptr; @@ -42,101 +60,246 @@ struct SaveObserverRecord { void* userData = nullptr; }; -std::array s_slots; +std::map s_saves; int32_t s_currentSlot = -1; -bool s_sidecarLoaded = false; +bool s_legacyMigrationChecked = false; std::vector s_observers; uint64_t s_nextHandle = 1; -std::filesystem::path sidecar_path() { - return dusk::ConfigPath / kSidecarName; +std::filesystem::path legacy_sidecar_path() { + return ConfigPath / kLegacySidecarName; } -void load_sidecar() { - if (s_sidecarLoaded) { - return; +std::optional card_storage() { + const auto cardType = aurora_card_get_type(kCardChannel); + if (cardType == AURORA_CARD_UNAVAILABLE) { + return std::nullopt; } - s_sidecarLoaded = true; - std::ifstream in{sidecar_path()}; - if (!in.is_open()) { - return; + const auto& diskId = version::getDiskID(); + const std::string_view game{diskId.gameName, sizeof(diskId.gameName)}; + const auto kind = cardType == AURORA_CARD_GCI_DIRECTORY ? + save_manager::StorageKind::GciDirectory : + save_manager::StorageKind::RawImage; + auto storage = save_manager::resolve_storage(game, kind, kCardChannel); + if (!storage) { + return std::nullopt; } + return std::move(storage.value); +} + +std::filesystem::path save_sidecar_directory( + const save_manager::Storage& storage, std::string_view saveName) { + const auto& diskId = version::getDiskID(); + const std::string_view maker{diskId.company, sizeof(diskId.company)}; + const std::string_view game{diskId.gameName, sizeof(diskId.gameName)}; + return save_manager::save_sidecar_directory(storage.path, storage.kind, maker, game, saveName); +} + +std::optional mod_sidecar_path( + std::string_view saveName, std::string_view modId) { + const auto storage = card_storage(); + if (!storage) { + return std::nullopt; + } + return save_sidecar_directory(*storage, saveName) / (std::string{modId} + ".json"); +} + +bool write_mod_sidecar( + const std::filesystem::path& path, const std::string& modId, const SaveStore& store) { + nlohmann::json slots = nlohmann::json::array(); + bool hasBlobs = false; + for (const auto& slot : store.slots) { + nlohmann::json blobsJson = nlohmann::json::object(); + const auto modIt = slot.mods.find(modId); + if (modIt != slot.mods.end()) { + for (const auto& [name, bytes] : modIt->second) { + blobsJson[name] = utils::base64_encode(bytes); + hasBlobs = true; + } + } + slots.push_back(nlohmann::json{{"blobs", std::move(blobsJson)}}); + } + + if (!hasBlobs) { + std::error_code ec; + std::filesystem::remove(path, ec); + if (ec) { + Log.error("failed to remove mod save sidecar '{}': {}", + borealis::io::fs_path_to_string(path), ec.message()); + return false; + } + return true; + } + + const nlohmann::json json{ + {"version", kModSidecarVersion}, + {"slots", std::move(slots)}, + }; + std::filesystem::path tempPath{path}; + tempPath += ".tmp"; try { + std::filesystem::create_directories(path.parent_path()); + { + std::ofstream out{tempPath, std::ios::trunc}; + out << json.dump(2); + out.close(); + if (!out.good()) { + throw std::runtime_error{"write failed"}; + } + } + std::string error; + if (!borealis::io::atomic_replace(tempPath, path, error)) { + throw std::runtime_error{error}; + } + return true; + } catch (const std::exception& e) { + Log.error("failed to write mod save sidecar '{}': {}", + borealis::io::fs_path_to_string(path), e.what()); + std::error_code ec; + std::filesystem::remove(tempPath, ec); + return false; + } +} + +bool write_mod_sidecar( + const std::string& saveName, const std::string& modId, const SaveStore& store) { + if (!utils::is_valid_save_name(saveName) || !utils::is_valid_mod_id(modId)) { + Log.error("refusing to write mod save sidecar with invalid path components '{}/{}'", + saveName, modId); + return false; + } + + const auto path = mod_sidecar_path(saveName, modId); + if (!path) { + Log.error("CARD backing storage is unavailable for mod save sidecars"); + return false; + } + return write_mod_sidecar(*path, modId, store); +} + +void load_mod_sidecar(const std::filesystem::path& path, const std::string& saveName, + const std::string& modId, SaveStore& store) { + try { + std::ifstream in{path}; + if (!in.is_open()) { + throw std::runtime_error{"open failed"}; + } const auto json = nlohmann::json::parse(in); - if (json.value("version", 0) != kSidecarVersion) { - Log.warn( - "mod save sidecar has unknown version {}; ignoring it", json.value("version", 0)); + if (json.value("version", 0) != kModSidecarVersion) { + Log.warn("mod save sidecar '{}/{}' has unknown version {}; ignoring it", saveName, + modId, json.value("version", 0)); return; } const auto& slots = json.at("slots"); for (uint32_t slot = 0; slot < kSlotCount && slot < slots.size(); ++slot) { - auto& store = s_slots[slot]; - const auto& slotJson = slots[slot]; - if (slotJson.contains("snapshot_crc32")) { - store.snapshotValid = true; - store.snapshotCrc = slotJson["snapshot_crc32"].get(); - } - const auto modsJson = slotJson.value("mods", nlohmann::json::object()); - for (const auto& [modId, blobs] : modsJson.items()) { - for (const auto& [name, encoded] : blobs.items()) { - std::vector bytes; - if (!utils::base64_decode(encoded.get(), bytes)) { - Log.warn("mod save sidecar: bad blob '{}/{}' in slot {}; dropped", modId, - name, slot); - continue; - } - s_slots[slot].mods[modId][name] = std::move(bytes); + const auto blobsJson = slots[slot].value("blobs", nlohmann::json::object()); + BlobMap blobs; + size_t totalSize = 0; + for (const auto& [name, encoded] : blobsJson.items()) { + if (!utils::is_valid_name(name, kMaxBlobNameLength) || !encoded.is_string()) { + Log.warn("mod save sidecar: invalid blob '{}' in {}/{} slot {}; dropped", name, + saveName, modId, slot); + continue; } + std::vector bytes; + if (!utils::base64_decode(encoded.get(), bytes)) { + Log.warn("mod save sidecar: bad blob '{}' in {}/{} slot {}; dropped", name, + saveName, modId, slot); + continue; + } + if (totalSize + bytes.size() > SAVE_BLOB_BUDGET_BYTES) { + Log.warn("mod save sidecar: blobs exceed the {}-byte budget in {}/{} slot {}; " + "blob '{}' dropped", + SAVE_BLOB_BUDGET_BYTES, saveName, modId, slot, name); + continue; + } + totalSize += bytes.size(); + blobs[name] = std::move(bytes); + } + if (!blobs.empty()) { + store.slots[slot].mods[modId] = std::move(blobs); } } } catch (const std::exception& e) { - Log.error("failed to read mod save sidecar: {}", e.what()); + Log.error("failed to read mod save sidecar '{}': {}", borealis::io::fs_path_to_string(path), + e.what()); } } -void flush_sidecar() { - nlohmann::json slots = nlohmann::json::array(); - for (const auto& store : s_slots) { - nlohmann::json slotJson = nlohmann::json::object(); - if (store.snapshotValid) { - slotJson["snapshot_crc32"] = store.snapshotCrc; +SaveStore& load_save_store(const std::string& saveName) { + auto& store = s_saves[saveName]; + if (store.loaded) { + return store; + } + const auto storage = card_storage(); + if (!storage) { + return store; + } + + const auto& diskId = version::getDiskID(); + if (const auto migrated = migrate_legacy_sidecar(*storage, + {diskId.company, sizeof(diskId.company)}, {diskId.gameName, sizeof(diskId.gameName)}); + !migrated) + { + Log.error("{}", migrated.message); + return store; + } + store.loaded = true; + + const auto directory = save_sidecar_directory(*storage, saveName); + if (std::error_code ec; !std::filesystem::exists(directory, ec)) { + if (ec) { + Log.error("failed to inspect mod save directory '{}': {}", + borealis::io::fs_path_to_string(directory), ec.message()); } - nlohmann::json mods = nlohmann::json::object(); - for (const auto& [modId, blobs] : store.mods) { - if (blobs.empty()) { + return store; + } + + try { + for (const auto& entry : std::filesystem::directory_iterator{directory}) { + if (!entry.is_regular_file() || entry.path().extension() != ".json") { continue; } - nlohmann::json blobsJson = nlohmann::json::object(); - for (const auto& [name, bytes] : blobs) { - blobsJson[name] = utils::base64_encode(bytes); + const auto modId = borealis::io::fs_path_to_string(entry.path().stem()); + if (!utils::is_valid_mod_id(modId)) { + Log.warn("ignoring mod save sidecar with invalid mod ID '{}'", modId); + continue; } - mods[modId] = std::move(blobsJson); + load_mod_sidecar(entry.path(), saveName, modId, store); } - slotJson["mods"] = std::move(mods); - slots.push_back(std::move(slotJson)); - } - const nlohmann::json json{{"version", kSidecarVersion}, {"slots", std::move(slots)}}; - - const auto path = sidecar_path(); - const auto tempPath = path.string() + ".tmp"; - try { - { - std::ofstream out{tempPath, std::ios::trunc}; - out << json.dump(2); - if (!out.good()) { - throw std::runtime_error("write failed"); - } - } - std::filesystem::rename(tempPath, path); } catch (const std::exception& e) { - Log.error("failed to write mod save sidecar: {}", e.what()); - std::error_code ec; - std::filesystem::remove(tempPath, ec); + Log.error("failed to enumerate mod save directory '{}': {}", + borealis::io::fs_path_to_string(directory), e.what()); + } + return store; +} + +std::optional current_save_name() { + const char* fileName = mDoMemCd_GetFileName(); + std::string saveName = fileName != nullptr ? fileName : ""; + if (!utils::is_valid_save_name(saveName)) { + Log.error("CARD save file name '{}' is invalid for mod save storage", saveName); + return std::nullopt; + } + return saveName; +} + +void flush_save_sidecars(const std::string& saveName, SaveStore& store) { + const auto dirtyMods = store.dirtyMods; + for (const auto& modId : dirtyMods) { + if (write_mod_sidecar(saveName, modId, store)) { + store.dirtyMods.erase(modId); + } + } +} + +void mark_slot_dirty(SaveStore& save, const SlotStore& slot) { + for (const auto& entry : slot.mods) { + save.dirtyMods.insert(entry.first); } } void notify(uint32_t slot, SaveEventFn SaveObserverRecord::* which, const char* what) { - // Callbacks may unregister observers. const auto observers = s_observers; for (const auto& observer : observers) { if (!observer.mod->active || observer.*which == nullptr) { @@ -156,72 +319,167 @@ void notify(uint32_t slot, SaveEventFn SaveObserverRecord::* which, const char* } // namespace +save_manager::Result migrate_legacy_sidecar( + const save_manager::Storage& storage, std::string_view maker, std::string_view game) { + if (s_legacyMigrationChecked) { + return {.ok = true}; + } + + const auto path = legacy_sidecar_path(); + try { + if (!std::filesystem::exists(path)) { + s_legacyMigrationChecked = true; + return {.ok = true}; + } + std::ifstream in{path}; + if (!in.is_open()) { + throw std::runtime_error{"Unable to open the legacy mod save file."}; + } + const auto json = nlohmann::json::parse(in); + in.close(); + if (json.value("version", 0) != kLegacySidecarVersion) { + throw std::runtime_error{ + fmt::format("Unsupported legacy mod save version {}.", json.value("version", 0))}; + } + + SaveStore legacyStore; + std::set modIds; + const auto& slots = json.at("slots"); + for (uint32_t slot = 0; slot < kSlotCount && slot < slots.size(); ++slot) { + const auto& slotJson = slots[slot]; + const auto modsJson = slotJson.value("mods", nlohmann::json::object()); + for (const auto& [modId, blobs] : modsJson.items()) { + if (!utils::is_valid_mod_id(modId)) { + Log.warn("legacy mod save sidecar has invalid mod ID '{}'; dropped", modId); + continue; + } + for (const auto& [name, encoded] : blobs.items()) { + if (!utils::is_valid_name(name, kMaxBlobNameLength) || !encoded.is_string()) { + Log.warn( + "legacy mod save sidecar: invalid blob '{}/{}' in slot {}; dropped", + modId, name, slot); + continue; + } + std::vector bytes; + if (!utils::base64_decode(encoded.get(), bytes)) { + Log.warn("legacy mod save sidecar: bad blob '{}/{}' in slot {}; dropped", + modId, name, slot); + continue; + } + legacyStore.slots[slot].mods[modId][name] = std::move(bytes); + modIds.insert(modId); + } + } + } + + for (const auto& modId : modIds) { + const std::string_view saveName = + modId == "dev.twilitrealm.randomizer" ? "randomizer" : kDefaultSaveName; + const auto destination = save_manager::save_sidecar_directory( + storage.path, storage.kind, maker, game, saveName) / + (modId + ".json"); + if (std::filesystem::exists(destination)) { + if (!std::filesystem::is_regular_file(destination)) { + throw std::runtime_error{ + fmt::format("The mod save destination is not a file: {}", + borealis::io::fs_path_to_string(destination))}; + } + continue; + } + if (!write_mod_sidecar(destination, modId, legacyStore)) { + throw std::runtime_error{fmt::format("Unable to write migrated mod data: {}", + borealis::io::fs_path_to_string(destination))}; + } + } + + std::filesystem::path backupPath{path}; + backupPath += ".bak"; + std::string error; + if (!borealis::io::atomic_replace(path, backupPath, error)) { + throw std::runtime_error{ + fmt::format("Unable to preserve the legacy mod save file: {}", error)}; + } + s_legacyMigrationChecked = true; + Log.info( + "migrated legacy mod save data to '{}'", borealis::io::fs_path_to_string(storage.path)); + return {.ok = true}; + } catch (const std::exception& e) { + return {.message = fmt::format("Legacy mod save migration failed: {}", e.what())}; + } +} + void save_slot_new(uint32_t slot) { if (slot >= kSlotCount) { return; } - load_sidecar(); - auto& store = s_slots[slot]; - store.mods.clear(); - store.snapshotValid = false; s_currentSlot = static_cast(slot); item_gives_clear(); - Log.info("new save in slot {}; mod blob store cleared", slot); + if (const auto saveName = current_save_name()) { + auto& save = load_save_store(*saveName); + auto& mods = save.slots[slot].mods; + mark_slot_dirty(save, save.slots[slot]); + mods.clear(); + Log.info("new save in {}/{}; mod blob store cleared", *saveName, slot); + } notify(slot, &SaveObserverRecord::onNewSave, "new-save"); } -void save_slot_loaded(uint32_t slot, const void* slotData) { +void save_slot_loaded(uint32_t slot) { if (slot >= kSlotCount) { return; } - load_sidecar(); - auto& store = s_slots[slot]; - if (store.snapshotValid && slotData != nullptr) { - const auto crc = utils::crc32(slotData, kQuestLogSize); - if (crc != store.snapshotCrc) { - Log.warn("slot {} save data does not match the mod sidecar snapshot; mod save " - "data may be stale (card file changed externally?)", - slot); - } + if (const auto saveName = current_save_name()) { + load_save_store(*saveName); } s_currentSlot = static_cast(slot); item_gives_clear(); notify(slot, &SaveObserverRecord::onLoaded, "save-loaded"); } -void save_slot_written(uint32_t slot, const void* slotData) { +void save_slot_written(uint32_t slot) { if (slot >= kSlotCount) { return; } - load_sidecar(); - auto& store = s_slots[slot]; - if (slotData != nullptr) { - store.snapshotValid = true; - store.snapshotCrc = utils::crc32(slotData, kQuestLogSize); - } - flush_sidecar(); s_currentSlot = static_cast(slot); notify(slot, &SaveObserverRecord::onWritten, "save-written"); + if (const auto saveName = current_save_name()) { + auto& store = load_save_store(*saveName); + flush_save_sidecars(*saveName, store); + } } void save_slot_copied(uint32_t fromSlot, uint32_t toSlot) { if (fromSlot >= kSlotCount || toSlot >= kSlotCount || fromSlot == toSlot) { return; } - load_sidecar(); - s_slots[toSlot] = s_slots[fromSlot]; - flush_sidecar(); - Log.info("mod save data copied with slot {} -> {}", fromSlot, toSlot); + const auto saveName = current_save_name(); + if (!saveName) { + return; + } + auto& save = load_save_store(*saveName); + auto& fromMods = save.slots[fromSlot].mods; + auto& toMods = save.slots[toSlot].mods; + mark_slot_dirty(save, save.slots[fromSlot]); + mark_slot_dirty(save, save.slots[toSlot]); + toMods = fromMods; + flush_save_sidecars(*saveName, save); + Log.info("mod save data copied in {} with slot {} -> {}", *saveName, fromSlot, toSlot); } void save_slot_erased(uint32_t slot) { if (slot >= kSlotCount) { return; } - load_sidecar(); - s_slots[slot] = SlotStore{}; - flush_sidecar(); - Log.info("mod save data erased with slot {}", slot); + const auto saveName = current_save_name(); + if (!saveName) { + return; + } + auto& save = load_save_store(*saveName); + auto& mods = save.slots[slot].mods; + mark_slot_dirty(save, save.slots[slot]); + mods.clear(); + flush_save_sidecars(*saveName, save); + Log.info("mod save data erased in {} with slot {}", *saveName, slot); } void save_no_slot() { @@ -229,25 +487,45 @@ void save_no_slot() { item_gives_clear(); } -namespace { - -BlobMap* current_blobs(const LoadedMod& mod, bool create) { - if (s_currentSlot < 0) { - return nullptr; - } - load_sidecar(); - auto& mods = s_slots[s_currentSlot].mods; - if (!create) { - const auto it = mods.find(mod.metadata.id); - return it != mods.end() ? &it->second : nullptr; - } - return &mods[mod.metadata.id]; +void invalidate_save(std::string_view saveName) { + s_saves.erase(std::string{saveName}); } -} // namespace +namespace { -ModResult save_set_blob(LoadedMod& mod, const char* name, const void* data, size_t size) { - auto* blobs = current_blobs(mod, true); +struct CurrentBlobs { + SaveStore* store = nullptr; + BlobMap* blobs = nullptr; +}; + +CurrentBlobs current_blobs(const LoadedMod& mod, bool create) { + if (s_currentSlot < 0) { + return {}; + } + const auto saveName = current_save_name(); + if (!saveName) { + return {}; + } + auto& store = load_save_store(*saveName); + if (!store.loaded) { + return {}; + } + auto& mods = store.slots[s_currentSlot].mods; + if (!create) { + const auto it = mods.find(mod.metadata.id); + return {&store, it != mods.end() ? &it->second : nullptr}; + } + return {&store, &mods[mod.metadata.id]}; +} + +ModResult save_set_blob(ModContext* context, const char* name, const void* data, size_t size) { + auto* mod = mod_from_context(context); + if (mod == nullptr || !utils::is_valid_name(name, kMaxBlobNameLength) || + (data == nullptr && size != 0) || size > SAVE_BLOB_BUDGET_BYTES) + { + return MOD_INVALID_ARGUMENT; + } + const auto [store, blobs] = current_blobs(*mod, true); if (blobs == nullptr) { return MOD_UNAVAILABLE; } @@ -259,16 +537,25 @@ ModResult save_set_blob(LoadedMod& mod, const char* name, const void* data, size } if (total > SAVE_BLOB_BUDGET_BYTES) { Log.error("[{}] save blob '{}' rejected: {} bytes would exceed the {}-byte budget", - mod.metadata.id, name, total, SAVE_BLOB_BUDGET_BYTES); + mod->metadata.id, name, total, SAVE_BLOB_BUDGET_BYTES); return MOD_UNAVAILABLE; } const auto* bytes = static_cast(data); - (*blobs)[name] = std::vector{bytes, bytes + size}; + std::vector blob; + if (size != 0) { + blob.assign(bytes, bytes + size); + } + (*blobs)[name] = std::move(blob); + store->dirtyMods.insert(mod->metadata.id); return MOD_OK; } -ModResult save_get_blob(LoadedMod& mod, const char* name, void* buf, size_t& inoutSize) { - auto* blobs = current_blobs(mod, false); +ModResult save_get_blob(ModContext* context, const char* name, void* buf, size_t* inoutSize) { + auto* mod = mod_from_context(context); + if (mod == nullptr || !utils::is_valid_name(name, kMaxBlobNameLength) || inoutSize == nullptr) { + return MOD_INVALID_ARGUMENT; + } + const auto [store, blobs] = current_blobs(*mod, false); if (blobs == nullptr) { return MOD_UNAVAILABLE; } @@ -277,112 +564,34 @@ ModResult save_get_blob(LoadedMod& mod, const char* name, void* buf, size_t& ino return MOD_UNAVAILABLE; } if (buf == nullptr) { - inoutSize = it->second.size(); + *inoutSize = it->second.size(); return MOD_OK; } - if (inoutSize < it->second.size()) { + if (*inoutSize < it->second.size()) { return MOD_INVALID_ARGUMENT; } std::memcpy(buf, it->second.data(), it->second.size()); - inoutSize = it->second.size(); + *inoutSize = it->second.size(); return MOD_OK; } -ModResult save_delete_blob(LoadedMod& mod, const char* name) { - auto* blobs = current_blobs(mod, false); +ModResult save_delete_blob(ModContext* context, const char* name) { + auto* mod = mod_from_context(context); + if (mod == nullptr || !utils::is_valid_name(name, kMaxBlobNameLength)) { + return MOD_INVALID_ARGUMENT; + } + const auto [store, blobs] = current_blobs(*mod, false); if (blobs == nullptr) { return MOD_UNAVAILABLE; } - return blobs->erase(name) != 0 ? MOD_OK : MOD_INVALID_ARGUMENT; -} - -ModResult save_observe(LoadedMod& mod, SaveEventFn onNewSave, SaveEventFn onLoaded, - SaveEventFn onWritten, void* userData, uint64_t& outHandle) { - auto& observer = s_observers.emplace_back(); - observer.handle = s_nextHandle++; - observer.mod = &mod; - observer.onNewSave = onNewSave; - observer.onLoaded = onLoaded; - observer.onWritten = onWritten; - observer.userData = userData; - outHandle = observer.handle; + if (blobs->erase(name) == 0) { + return MOD_INVALID_ARGUMENT; + } + store->dirtyMods.insert(mod->metadata.id); return MOD_OK; } -ModResult save_unobserve(LoadedMod& mod, uint64_t handle) { - const auto removed = std::erase_if(s_observers, - [&](const auto& observer) { return observer.handle == handle && observer.mod == &mod; }); - return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT; -} - -ModResult save_peek_blob( - LoadedMod& mod, uint32_t slot, const char* name, void* buf, size_t& inoutSize) { - if (slot >= kSlotCount) { - return MOD_INVALID_ARGUMENT; - } - load_sidecar(); - const auto& mods = s_slots[slot].mods; - const auto modIt = mods.find(mod.metadata.id); - if (modIt == mods.end()) { - return MOD_UNAVAILABLE; - } - const auto it = modIt->second.find(name); - if (it == modIt->second.end()) { - return MOD_UNAVAILABLE; - } - if (buf == nullptr) { - inoutSize = it->second.size(); - return MOD_OK; - } - if (inoutSize < it->second.size()) { - return MOD_INVALID_ARGUMENT; - } - std::memcpy(buf, it->second.data(), it->second.size()); - inoutSize = it->second.size(); - return MOD_OK; -} - -void save_remove_mod(LoadedMod& mod) { - std::erase_if(s_observers, [&](const auto& observer) { return observer.mod == &mod; }); - // Blob data persists across mod reloads. -} - -namespace { -bool is_valid_blob_name(const char* name) { - if (name == nullptr) { - return false; - } - const std::string_view view{name}; - return !view.empty() && view.size() <= kMaxBlobNameLength; -} - -ModResult save_set_blob_(ModContext* context, const char* name, const void* data, size_t size) { - auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_blob_name(name) || (data == nullptr && size != 0) || - size > SAVE_BLOB_BUDGET_BYTES) - { - return MOD_INVALID_ARGUMENT; - } - return save_set_blob(*mod, name, data, size); -} - -ModResult save_get_blob_(ModContext* context, const char* name, void* buf, size_t* inoutSize) { - auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_blob_name(name) || inoutSize == nullptr) { - return MOD_INVALID_ARGUMENT; - } - return save_get_blob(*mod, name, buf, *inoutSize); -} - -ModResult save_delete_blob_(ModContext* context, const char* name) { - auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_blob_name(name)) { - return MOD_INVALID_ARGUMENT; - } - return save_delete_blob(*mod, name); -} - -ModResult save_observe_saves_(ModContext* context, SaveEventFn onNewSave, SaveEventFn onLoaded, +ModResult save_observe(ModContext* context, SaveEventFn onNewSave, SaveEventFn onLoaded, SaveEventFn onWritten, void* userData, SaveObserverHandle* outHandle) { if (outHandle != nullptr) { *outHandle = 0; @@ -391,39 +600,78 @@ ModResult save_observe_saves_(ModContext* context, SaveEventFn onNewSave, SaveEv if (mod == nullptr || (onNewSave == nullptr && onLoaded == nullptr && onWritten == nullptr)) { return MOD_INVALID_ARGUMENT; } - uint64_t handle = 0; - const auto result = save_observe(*mod, onNewSave, onLoaded, onWritten, userData, handle); + auto& observer = s_observers.emplace_back(); + observer.handle = s_nextHandle++; + observer.mod = mod; + observer.onNewSave = onNewSave; + observer.onLoaded = onLoaded; + observer.onWritten = onWritten; + observer.userData = userData; if (outHandle != nullptr) { - *outHandle = handle; + *outHandle = observer.handle; } - return result; + return MOD_OK; } -ModResult save_unobserve_saves_(ModContext* context, SaveObserverHandle handle) { +ModResult save_unobserve(ModContext* context, SaveObserverHandle handle) { auto* mod = mod_from_context(context); if (mod == nullptr || handle == 0) { return MOD_INVALID_ARGUMENT; } - return save_unobserve(*mod, handle); + const auto removed = std::erase_if(s_observers, + [&](const auto& observer) { return observer.handle == handle && observer.mod == mod; }); + return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT; } -ModResult save_peek_blob_( +ModResult save_peek_blob( ModContext* context, uint32_t slot, const char* name, void* buf, size_t* inoutSize) { auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_blob_name(name) || inoutSize == nullptr) { + if (mod == nullptr || !utils::is_valid_name(name, kMaxBlobNameLength) || inoutSize == nullptr || + slot >= kSlotCount) + { return MOD_INVALID_ARGUMENT; } - return save_peek_blob(*mod, slot, name, buf, *inoutSize); + const auto saveName = current_save_name(); + if (!saveName) { + return MOD_UNAVAILABLE; + } + const auto& store = load_save_store(*saveName); + if (!store.loaded) { + return MOD_UNAVAILABLE; + } + const auto& mods = store.slots[slot].mods; + const auto modIt = mods.find(mod->metadata.id); + if (modIt == mods.end()) { + return MOD_UNAVAILABLE; + } + const auto it = modIt->second.find(name); + if (it == modIt->second.end()) { + return MOD_UNAVAILABLE; + } + if (buf == nullptr) { + *inoutSize = it->second.size(); + return MOD_OK; + } + if (*inoutSize < it->second.size()) { + return MOD_INVALID_ARGUMENT; + } + std::memcpy(buf, it->second.data(), it->second.size()); + *inoutSize = it->second.size(); + return MOD_OK; +} + +void save_remove_mod(LoadedMod& mod) { + std::erase_if(s_observers, [&](const auto& observer) { return observer.mod == &mod; }); } constexpr SaveService s_saveService{ .header = SERVICE_HEADER(SaveService, SAVE_SERVICE_MAJOR, SAVE_SERVICE_MINOR), - .set_blob = save_set_blob_, - .get_blob = save_get_blob_, - .delete_blob = save_delete_blob_, - .observe_saves = save_observe_saves_, - .unobserve_saves = save_unobserve_saves_, - .peek_blob = save_peek_blob_, + .set_blob = SERVICE_FUNCTION(save_set_blob), + .get_blob = SERVICE_FUNCTION(save_get_blob), + .delete_blob = SERVICE_FUNCTION(save_delete_blob), + .observe_saves = SERVICE_FUNCTION(save_observe), + .unobserve_saves = SERVICE_FUNCTION(save_unobserve), + .peek_blob = SERVICE_FUNCTION(save_peek_blob), }; } // namespace diff --git a/src/dusk/mods/svc/save.hpp b/src/dusk/mods/svc/save.hpp index 89460af146..0773c74a87 100644 --- a/src/dusk/mods/svc/save.hpp +++ b/src/dusk/mods/svc/save.hpp @@ -1,14 +1,25 @@ #pragma once #include +#include + +namespace dusk::save_manager { +struct Result; +struct Storage; +} // namespace dusk::save_manager namespace dusk::mods::svc { void save_slot_new(uint32_t slot); -void save_slot_loaded(uint32_t slot, const void* slotData); -void save_slot_written(uint32_t slot, const void* slotData); +void save_slot_loaded(uint32_t slot); +void save_slot_written(uint32_t slot); void save_slot_copied(uint32_t fromSlot, uint32_t toSlot); void save_slot_erased(uint32_t slot); void save_no_slot(); +void invalidate_save(std::string_view saveName); + +// Migrates the old mod_saves.json to the new per-file, per-mod JSON. +save_manager::Result migrate_legacy_sidecar( + const save_manager::Storage& storage, std::string_view maker, std::string_view game); } // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/stage.cpp b/src/dusk/mods/svc/stage.cpp index 10af84a238..4dfd265024 100644 --- a/src/dusk/mods/svc/stage.cpp +++ b/src/dusk/mods/svc/stage.cpp @@ -2,15 +2,15 @@ #include "registry.hpp" -#include "aurora/lib/logging.hpp" #include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" #include "mods/svc/stage.h" #include "d/d_com_inf_game.h" -#include "dusk/utilities.hpp" + +#include #include -#include #include #include #include @@ -130,7 +130,9 @@ bool stage_apply_actor_edits(void* actorData, void* actorPrm, size_t recordSize, } std::memcpy(actorData, winner->record.data(), winner->record.size()); - std::memcpy(actorPrm, winner->record.data() + 8, winner->record.size() - 8); + if (actorPrm) { + std::memcpy(actorPrm, winner->record.data() + 8, winner->record.size() - 8); + } return true; } @@ -219,14 +221,6 @@ void stage_remove_mod(LoadedMod& mod) { namespace { constexpr size_t kMaxStageNameLength = 8; -bool is_valid_stage_name(const char* stage) { - if (stage == nullptr) { - return false; - } - const std::string_view view{stage}; - return !view.empty() && view.size() <= kMaxStageNameLength; -} - bool is_valid_record_size(size_t size) { return size == kActrEntrySize || size == kTgscEntrySize; } @@ -238,7 +232,7 @@ ModResult stage_patch_actor_(ModContext* context, const char* stage, uint8_t roo } auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_stage_name(stage) || record == nullptr || + if (mod == nullptr || !utils::is_valid_name(stage, kMaxStageNameLength) || record == nullptr || !is_valid_record_size(recordSize)) { return MOD_INVALID_ARGUMENT; @@ -261,7 +255,7 @@ ModResult stage_delete_actor_(ModContext* context, const char* stage, uint8_t ro } auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_stage_name(stage)) { + if (mod == nullptr || !utils::is_valid_name(stage, kMaxStageNameLength)) { return MOD_INVALID_ARGUMENT; } @@ -280,8 +274,8 @@ ModResult stage_add_actor_(ModContext* context, const char* stage, uint8_t room, } auto* mod = mod_from_context(context); - if (mod == nullptr || !is_valid_stage_name(stage) || room == kRoomDefault || - record == nullptr || !is_valid_record_size(recordSize)) + if (mod == nullptr || !utils::is_valid_name(stage, kMaxStageNameLength) || + room == kRoomDefault || record == nullptr || !is_valid_record_size(recordSize)) { return MOD_INVALID_ARGUMENT; } diff --git a/src/dusk/mods/svc/texture.cpp b/src/dusk/mods/svc/texture.cpp index 90b3bfff94..ac3b2e1664 100644 --- a/src/dusk/mods/svc/texture.cpp +++ b/src/dusk/mods/svc/texture.cpp @@ -2,6 +2,7 @@ #include #include "dusk/mods/loader/loader.hpp" +#include "dusk/utilities.hpp" #include "mods/svc/texture.h" #include @@ -397,7 +398,7 @@ ModResult texture_register_file( *outHandle = 0; } auto* mod = mod_from_context(context); - if (mod == nullptr || bundlePath == nullptr || !is_safe_resource_path(bundlePath)) { + if (mod == nullptr || bundlePath == nullptr || !utils::is_safe_resource_path(bundlePath)) { return MOD_INVALID_ARGUMENT; } diff --git a/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index 75e1d514cf..1bab8b21ce 100644 --- a/src/dusk/mods/svc/ui.cpp +++ b/src/dusk/mods/svc/ui.cpp @@ -1,30 +1,35 @@ #include "ui.hpp" #include "config.hpp" +#include "internal.hpp" #include "registry.hpp" -#include "slot_map.hpp" #include "ui_v1.hpp" -#include #include "dusk/mod_loader.hpp" #include "dusk/mods/loader/loader.hpp" #include "dusk/mods/log_buffer.hpp" +#include "dusk/ui/context_menu.hpp" +#include "dusk/ui/dropdown_button.hpp" +#include "dusk/ui/icon_button.hpp" #include "dusk/ui/list.hpp" #include "dusk/ui/menu_bar.hpp" #include "dusk/ui/mod_window.hpp" #include "dusk/ui/modal.hpp" +#include "dusk/ui/row.hpp" #include "dusk/ui/ui.hpp" +#include "dusk/utilities.hpp" #include "mods/svc/ui.h" #include +#include #include +#include #include #include #include #include #include -#include #include #include #include @@ -37,16 +42,17 @@ #include #include -#include "SDL3/SDL_clipboard.h" - namespace { constexpr size_t kUiControlSelectedSize = offsetof(UiControlDesc, is_selected) + sizeof(UiPredicateFn); constexpr size_t kUiControlStringSetModeSize = offsetof(UiControlDesc, string_set_mode) + sizeof(UiStringSetMode); -constexpr size_t kUiControlFilePickerSize = - offsetof(UiControlDesc, directory_mode) + sizeof(bool); +constexpr size_t kUiControlFilePickerSize = offsetof(UiControlDesc, directory_mode) + sizeof(bool); +constexpr size_t kUiControlIconSize = offsetof(UiControlDesc, icon) + sizeof(const char*); +constexpr size_t kUiControlOptionsSize = + offsetof(UiControlDesc, option_enabled) + sizeof(const bool*); +constexpr size_t kUiControlTooltipSize = offsetof(UiControlDesc, tooltip) + sizeof(const char*); constexpr size_t kUiListItemV21Size = offsetof(UiListItem, label) + sizeof(const char*); constexpr size_t kUiListDescV21Size = offsetof(UiListDesc, user_data) + sizeof(void*); @@ -61,12 +67,14 @@ enum class UiSlotKind : u8 { Window, Dialog, Pane, + Row, Text, Progress, Control, List, Style, MenuTab, + ContextMenu, }; const char* slot_kind_name(UiSlotKind kind) { @@ -75,6 +83,8 @@ const char* slot_kind_name(UiSlotKind kind) { return "window"; case UiSlotKind::Dialog: return "dialog"; + case UiSlotKind::Row: + return "row"; case UiSlotKind::Pane: return "pane"; case UiSlotKind::Text: @@ -89,6 +99,8 @@ const char* slot_kind_name(UiSlotKind kind) { return "style"; case UiSlotKind::MenuTab: return "menu tab"; + case UiSlotKind::ContextMenu: + return "context menu"; default: return "unknown"; } @@ -98,14 +110,20 @@ const char* slot_kind_name(UiSlotKind kind) { // (ui::update), or in the loader's deactivate paths. struct UiSlot { UiSlotKind kind = UiSlotKind::Window; - // Pane/Text/Progress/Control: freed automatically when the element is destroyed + // Pane/Row/Text/Progress/Control: freed automatically when the element is destroyed Rml::Element* element = nullptr; - // Pane payload + // Container payload + ui::Component* component = nullptr; + ui::Component* container = nullptr; + UiControlKind controlKind = UI_CONTROL_BUTTON; + std::unique_ptr displayOverride; + bool hidden = false; + uint64_t anchorHandle = 0; ui::Pane* pane = nullptr; ui::Pane* helpPane = nullptr; // List payload ui::List* list = nullptr; - // Window/Dialog payload (non-owning; the document stack owns the document) + // Window/Dialog payload ui::Document* document = nullptr; UiWindowClosedFn onClosed = nullptr; void* onClosedUserData = nullptr; @@ -114,9 +132,10 @@ struct UiSlot { std::string styleId; // Cached rendered values for element setters. These make the natural "set every update" // style cheap when the displayed value has not changed. - std::string elementRml; + std::string elementValue; float elementFloat = 0.0f; bool hasElementValue = false; + bool elementValueIsRml = false; }; SlotMap s_slots; @@ -126,6 +145,7 @@ struct ModUiPanel { UiPanelUpdateFn update = nullptr; void* userData = nullptr; }; + std::unordered_map s_modPanels; struct ModMenuTab { @@ -134,6 +154,7 @@ struct ModMenuTab { UiPressedFn onSelected = nullptr; void* userData = nullptr; }; + std::unordered_map> s_modMenuTabs; bool s_menuTabsDirty = false; @@ -159,6 +180,35 @@ UiSlot* resolve(LoadedMod& mod, uint64_t handle, UiSlotKind kind, const char* wh return &entry->value; } +UiSlot* resolve_element(LoadedMod& mod, uint64_t handle) { + auto* entry = s_slots.find_owned(handle, mod); + return entry != nullptr && entry->value.element != nullptr ? &entry->value : nullptr; +} + +UiSlot* resolve_container(LoadedMod& mod, uint64_t handle, const char* what) { + auto* entry = s_slots.find_owned(handle, mod); + if (entry == nullptr || + (entry->value.kind != UiSlotKind::Pane && entry->value.kind != UiSlotKind::Row)) + { + Log.error( + "[{}] {}: stale or invalid container handle {:#x}", mod.metadata.id, what, handle); + return nullptr; + } + return &entry->value; +} + +bool element_available(const UiSlot& slot) { + if (!slot.element->IsVisible(true)) { + return false; + } + for (auto* node = slot.element; node != nullptr; node = node->GetParentNode()) { + if (node->IsPseudoClassSet("disabled") || node->HasAttribute("disabled")) { + return false; + } + } + return slot.component == nullptr || !slot.component->disabled(); +} + // Whether the registration a callback was created under is still live. Callbacks captured by // host-owned UI must check this before calling into the mod: `mod->active` alone is true again // once a reload completes, but captured fn pointers still target the unloaded image. Teardown @@ -183,6 +233,16 @@ public: void OnDetach(Rml::Element*) override { s_slots.erase(m_handle); + std::vector menus; + s_slots.for_each([&](uint64_t, const auto& entry) { + if (entry.value.kind == UiSlotKind::ContextMenu && entry.value.anchorHandle == m_handle) + { + menus.push_back(static_cast(entry.value.document)); + } + }); + for (auto* menu : menus) { + menu->dismiss(false); + } delete this; } @@ -239,6 +299,8 @@ void invoke_mod_ui_callback(LoadedMod& mod, const char* what, Fn&& fn) { uint64_t wrap_pane(LoadedMod& mod, ui::Pane& pane, ui::Pane* helpPane) { uint64_t handle = 0; auto& slot = alloc_slot(mod, UiSlotKind::Pane, handle); + slot.component = &pane; + slot.container = &pane; slot.pane = &pane; slot.helpPane = helpPane; track_element(handle, slot, *pane.root()); @@ -296,7 +358,11 @@ void wire_callback_binding( break; case UI_CONTROL_NUMBER: case UI_CONTROL_SELECT: - spec.getInt = [getValue] { return clamp_to_int(getValue().int_value); }; + case UI_CONTROL_DROPDOWN: + spec.getInt = [getValue, dropdown = desc.kind == UI_CONTROL_DROPDOWN] { + const auto value = getValue().int_value; + return dropdown && (value < 0 || value > INT_MAX) ? -1 : clamp_to_int(value); + }; spec.setInt = [setValue](int value) { UiControlValue raw = UI_CONTROL_VALUE_INIT; raw.int_value = value; @@ -356,7 +422,8 @@ bool wire_config_var_binding(LoadedMod& mod, const UiControlDesc& desc, ui::ModC return true; } case UI_CONTROL_NUMBER: - case UI_CONTROL_SELECT: { + case UI_CONTROL_SELECT: + case UI_CONTROL_DROPDOWN: { const auto find = [modPtr, varHandle] { return static_cast*>( config_find_var(*modPtr, varHandle, CONFIG_VAR_INT)); @@ -364,9 +431,10 @@ bool wire_config_var_binding(LoadedMod& mod, const UiControlDesc& desc, ui::ModC if (find() == nullptr) { return false; } - spec.getInt = [find] { + spec.getInt = [find, dropdown = desc.kind == UI_CONTROL_DROPDOWN] { const auto* var = find(); - return var != nullptr ? clamp_to_int(var->getValue()) : 0; + const auto value = var != nullptr ? var->getValue() : (dropdown ? -1 : 0); + return dropdown && (value < 0 || value > INT_MAX) ? -1 : clamp_to_int(value); }; spec.setInt = [find](int value) { auto* var = find(); @@ -533,24 +601,59 @@ void ui_update_mods_panels(LoadedMod& mod) { [&](ModError* error) { return panel.update(mod.context.get(), panel.userData, error); }); } -ModResult ui_pane_add_section(LoadedMod& mod, uint64_t pane, const char* title) { - auto* slot = resolve(mod, pane, UiSlotKind::Pane, "pane_add_section"); +ModResult ui_pane_add_row( + LoadedMod& mod, uint64_t parent, const UiRowDesc& desc, uint64_t* outRow) { + auto* slot = resolve_container(mod, parent, "pane_add_row"); if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } - slot->pane->add_section(title); + auto* pane = slot->pane; + auto* helpPane = slot->helpPane; + auto align = ui::Row::Align::Start; + switch (desc.align) { + case UI_ROW_ALIGN_CENTER: + align = ui::Row::Align::Center; + break; + case UI_ROW_ALIGN_END: + align = ui::Row::Align::End; + break; + case UI_ROW_ALIGN_SPACE_BETWEEN: + align = ui::Row::Align::SpaceBetween; + break; + default: + break; + } + auto& row = slot->container->add_child(ui::Row::Props{ + .align = align, + .wrap = desc.wrap, + }); + auto& rowSlot = alloc_slot(mod, UiSlotKind::Row, *outRow); + rowSlot.component = &row; + rowSlot.container = &row; + rowSlot.pane = pane; + rowSlot.helpPane = helpPane; + track_element(*outRow, rowSlot, *row.root()); + return MOD_OK; +} + +ModResult ui_pane_add_section(LoadedMod& mod, uint64_t pane, const char* title) { + auto* slot = resolve_container(mod, pane, "pane_add_section"); + if (slot == nullptr) { + return MOD_INVALID_ARGUMENT; + } + slot->container->add_section(title); return MOD_OK; } ModResult ui_pane_add_text(LoadedMod& mod, uint64_t pane, const char* text, uint64_t* outElem) { - auto* slot = resolve(mod, pane, UiSlotKind::Pane, "pane_add_text"); + auto* slot = resolve_container(mod, pane, "pane_add_text"); if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } - auto* elem = slot->pane->add_text(text); + auto* elem = slot->container->add_text(text); if (outElem != nullptr) { auto& elemSlot = alloc_slot(mod, UiSlotKind::Text, *outElem); - elemSlot.elementRml = ui::escape(text); + elemSlot.elementValue = text; elemSlot.hasElementValue = true; track_element(*outElem, elemSlot, *elem); } @@ -558,22 +661,23 @@ ModResult ui_pane_add_text(LoadedMod& mod, uint64_t pane, const char* text, uint } ModResult ui_pane_add_rml(LoadedMod& mod, uint64_t pane, const char* rml, uint64_t* outElem) { - auto* slot = resolve(mod, pane, UiSlotKind::Pane, "pane_add_rml"); + auto* slot = resolve_container(mod, pane, "pane_add_rml"); if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } - auto* elem = slot->pane->add_rml(rml); + auto* elem = slot->container->add_rml(rml); if (outElem != nullptr) { auto& elemSlot = alloc_slot(mod, UiSlotKind::Text, *outElem); - elemSlot.elementRml = rml; + elemSlot.elementValue = rml; elemSlot.hasElementValue = true; + elemSlot.elementValueIsRml = true; track_element(*outElem, elemSlot, *elem); } return MOD_OK; } ModResult ui_pane_add_progress(LoadedMod& mod, uint64_t pane, float value, uint64_t* outElem) { - auto* slot = resolve(mod, pane, UiSlotKind::Pane, "pane_add_progress"); + auto* slot = resolve_container(mod, pane, "pane_add_progress"); if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } @@ -590,7 +694,7 @@ ModResult ui_pane_add_progress(LoadedMod& mod, uint64_t pane, float value, uint6 ModResult ui_pane_add_control( LoadedMod& mod, uint64_t pane, const UiControlDesc& desc, uint64_t* outElem) { - auto* slot = resolve(mod, pane, UiSlotKind::Pane, "pane_add_control"); + auto* slot = resolve_container(mod, pane, "pane_add_control"); if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } @@ -598,13 +702,24 @@ ModResult ui_pane_add_control( ui::ModControlSpec spec; spec.label = desc.label; spec.helpRml = desc.help_rml != nullptr ? desc.help_rml : ""; - spec.isDisabled = wrap_predicate(mod, desc.is_disabled, desc.user_data, pane); + spec.isDisabled = [modPtr = &mod, pane, + predicate = wrap_predicate(mod, desc.is_disabled, desc.user_data, pane)] { + return !slot_live(pane) || !modPtr->active || (predicate && predicate()); + }; + if (desc.struct_size >= kUiControlTooltipSize && desc.tooltip != nullptr) { + spec.tooltip = desc.tooltip; + } spec.isModified = wrap_predicate(mod, desc.is_modified, desc.user_data, pane); switch (desc.kind) { + case UI_CONTROL_ICON_BUTTON: case UI_CONTROL_BUTTON: case UI_CONTROL_GROUP: spec.kind = desc.kind == UI_CONTROL_BUTTON ? ui::ModControlSpec::Kind::Button : ui::ModControlSpec::Kind::Group; + if (desc.kind == UI_CONTROL_ICON_BUTTON) { + spec.kind = ui::ModControlSpec::Kind::IconButton; + spec.icon = desc.icon; + } if (desc.struct_size >= kUiControlSelectedSize) { spec.isSelected = wrap_predicate(mod, desc.is_selected, desc.user_data, pane); } @@ -650,8 +765,17 @@ ModResult ui_pane_add_control( spec.kind = ui::ModControlSpec::Kind::FilePicker; spec.directoryMode = desc.directory_mode; for (size_t i = 0; i < desc.file_filter_count; ++i) { - spec.fileFilters.push_back( - {desc.file_filters[i].name, desc.file_filters[i].pattern}); + spec.fileFilters.push_back({desc.file_filters[i].name, desc.file_filters[i].pattern}); + } + break; + case UI_CONTROL_DROPDOWN: + spec.kind = ui::ModControlSpec::Kind::Dropdown; + for (size_t i = 0; i < desc.option_count; ++i) { + spec.dropdownOptions.push_back({ + desc.options[i], + desc.struct_size < kUiControlOptionsSize || desc.option_enabled == nullptr || + desc.option_enabled[i], + }); } break; case UI_CONTROL_SELECT: @@ -669,7 +793,9 @@ ModResult ui_pane_add_control( return MOD_INVALID_ARGUMENT; } - if (desc.kind != UI_CONTROL_BUTTON && desc.kind != UI_CONTROL_GROUP) { + if (desc.kind != UI_CONTROL_BUTTON && desc.kind != UI_CONTROL_GROUP && + desc.kind != UI_CONTROL_ICON_BUTTON) + { if (desc.binding == UI_BINDING_CONFIG_VAR) { if (!wire_config_var_binding(mod, desc, spec)) { Log.error("[{}] pane_add_control: config var handle {:#x} is unknown or its type " @@ -685,12 +811,15 @@ ModResult ui_pane_add_control( // Copy the pane pointers out: allocating the control's slot below may reallocate s_slots auto* paneComponent = slot->pane; auto* helpPane = slot->helpPane; - auto* control = ui::build_mod_control(*paneComponent, helpPane, std::move(spec)); + auto* control = + ui::build_mod_control(*slot->container, *paneComponent, helpPane, std::move(spec)); if (control == nullptr) { return MOD_UNSUPPORTED; } if (outElem != nullptr) { auto& elemSlot = alloc_slot(mod, UiSlotKind::Control, *outElem); + elemSlot.component = control; + elemSlot.controlKind = desc.kind; track_element(*outElem, elemSlot, *control->root()); } return MOD_OK; @@ -699,11 +828,11 @@ ModResult ui_pane_add_control( ModResult ui_pane_add_list(LoadedMod& mod, uint64_t pane, const UiListDesc& desc, std::vector items, uint64_t& outHandle) { outHandle = 0; - auto* paneSlot = resolve(mod, pane, UiSlotKind::Pane, "pane_add_list"); + auto* paneSlot = resolve_container(mod, pane, "pane_add_list"); if (paneSlot == nullptr) { return MOD_INVALID_ARGUMENT; } - auto* paneComponent = paneSlot->pane; + auto* paneComponent = paneSlot->container; uint64_t handle = 0; alloc_slot(mod, UiSlotKind::List, handle); @@ -744,6 +873,7 @@ ModResult ui_pane_add_list(LoadedMod& mod, uint64_t pane, const UiListDesc& desc if (listSlot == nullptr) { return MOD_ERROR; } + listSlot->component = &list; listSlot->list = &list; track_element(handle, *listSlot, *list.root()); outHandle = handle; @@ -787,6 +917,8 @@ ModResult ui_pane_add_group(LoadedMod& mod, uint64_t groupPaneHandle, uint64_t t if (outElem != nullptr) { auto& elemSlot = alloc_slot(mod, UiSlotKind::Control, *outElem); + elemSlot.component = &button; + elemSlot.controlKind = UI_CONTROL_GROUP; track_element(*outElem, elemSlot, *button.root()); } return MOD_OK; @@ -797,13 +929,13 @@ ModResult ui_elem_set_text(LoadedMod& mod, uint64_t elem, const char* text) { if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } - const std::string rml = ui::escape(text); - if (slot->hasElementValue && slot->elementRml == rml) { + if (slot->hasElementValue && !slot->elementValueIsRml && slot->elementValue == text) { return MOD_OK; } - slot->elementRml = rml; + slot->elementValue = text; slot->hasElementValue = true; - slot->element->SetInnerRML(slot->elementRml); + slot->elementValueIsRml = false; + ui::set_text_content(slot->element, slot->elementValue); return MOD_OK; } @@ -812,11 +944,12 @@ ModResult ui_elem_set_rml(LoadedMod& mod, uint64_t elem, const char* rml) { if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } - if (slot->hasElementValue && slot->elementRml == rml) { + if (slot->hasElementValue && slot->elementValueIsRml && slot->elementValue == rml) { return MOD_OK; } - slot->elementRml = rml; + slot->elementValue = rml; slot->hasElementValue = true; + slot->elementValueIsRml = true; slot->element->SetInnerRML(rml); return MOD_OK; } @@ -846,6 +979,201 @@ ModResult ui_elem_set_class(LoadedMod& mod, uint64_t elem, const char* name, boo return MOD_OK; } +ModResult ui_control_set_label(LoadedMod& mod, uint64_t handle, const char* label) { + auto* slot = resolve(mod, handle, UiSlotKind::Control, "control_set_label"); + if (slot == nullptr || slot->component == nullptr) { + return MOD_INVALID_ARGUMENT; + } + if (slot->controlKind == UI_CONTROL_ICON_BUTTON) { + if (label[0] == '\0') { + return MOD_INVALID_ARGUMENT; + } + static_cast(slot->component)->set_label(label); + } else if (slot->controlKind == UI_CONTROL_BUTTON) { + static_cast(slot->component)->set_text(label); + } else { + static_cast(slot->component)->set_key(label); + } + return MOD_OK; +} + +ModResult ui_control_set_icon(LoadedMod& mod, uint64_t handle, const char* icon) { + auto* slot = resolve(mod, handle, UiSlotKind::Control, "control_set_icon"); + if (slot == nullptr || slot->controlKind != UI_CONTROL_ICON_BUTTON || + slot->component == nullptr) + { + return MOD_INVALID_ARGUMENT; + } + static_cast(slot->component)->set_icon(icon); + return MOD_OK; +} + +ModResult ui_control_set_tooltip(LoadedMod& mod, uint64_t handle, const char* text) { + auto* slot = resolve(mod, handle, UiSlotKind::Control, "control_set_tooltip"); + if (slot == nullptr || slot->component == nullptr) { + return MOD_INVALID_ARGUMENT; + } + slot->component->set_tooltip(text != nullptr ? text : ""); + return MOD_OK; +} + +ModResult ui_control_set_options( + LoadedMod& mod, uint64_t handle, std::vector options) { + auto* slot = resolve(mod, handle, UiSlotKind::Control, "control_set_options"); + if (slot == nullptr || slot->controlKind != UI_CONTROL_DROPDOWN || slot->component == nullptr) { + return MOD_INVALID_ARGUMENT; + } + static_cast(slot->component)->set_options(std::move(options)); + return MOD_OK; +} + +ModResult ui_elem_set_visible(LoadedMod& mod, uint64_t handle, bool visible) { + auto* slot = resolve_element(mod, handle); + if (slot == nullptr) { + return MOD_INVALID_ARGUMENT; + } + if (slot->hidden == !visible) { + return MOD_OK; + } + auto element = slot->element->GetObserverPtr(); + auto* context = element->GetContext(); + bool restoreFocus = context != nullptr && element->Contains(context->GetFocusElement()); + slot->hidden = !visible; + if (visible) { + if (slot->displayOverride) { + element->SetProperty(Rml::PropertyId::Display, *slot->displayOverride); + slot->displayOverride.reset(); + } else { + element->RemoveProperty(Rml::PropertyId::Display); + } + } else { + const auto& properties = element->GetLocalStyleProperties(); + if (const auto it = properties.find(Rml::PropertyId::Display); it != properties.end()) { + slot->displayOverride = std::make_unique(it->second); + } + ui::set_display(element.get(), Rml::Style::Display::None); + } + // Focus and menu callbacks may allocate slots. + element->GetOwnerDocument()->UpdateDocument(); + if (!visible && element != nullptr) { + std::vector menus; + s_slots.for_each([&](uint64_t, const auto& entry) { + if (entry.value.kind == UiSlotKind::ContextMenu) { + auto* anchor = slot_from_handle(entry.value.anchorHandle); + if (anchor != nullptr && element->Contains(anchor->element)) { + menus.push_back(static_cast(entry.value.document)); + } + } + }); + for (auto* menu : menus) { + restoreFocus = restoreFocus || menu->has_focus(); + menu->dismiss(false); + } + if (restoreFocus) { + if (auto* top = ui::top_document(); top && top->owns_element(element.get())) { + top->focus(); + } + } + } + return MOD_OK; +} + +ModResult ui_elem_focus(LoadedMod& mod, uint64_t handle) { + auto* slot = resolve_element(mod, handle); + if (slot == nullptr) { + return MOD_INVALID_ARGUMENT; + } + auto* element = slot->element; + auto* top = ui::top_document(); + if (top == nullptr || !top->owns_element(element)) { + return MOD_UNAVAILABLE; + } + element->GetOwnerDocument()->UpdateDocument(); + slot = resolve_element(mod, handle); + if (slot == nullptr) { + return MOD_INVALID_ARGUMENT; + } + if (!element_available(*slot)) { + return MOD_UNAVAILABLE; + } + slot = resolve_element(mod, handle); + if (slot == nullptr) { + return MOD_INVALID_ARGUMENT; + } + auto* component = slot->component; + return (component != nullptr ? component->focus() : element->Focus(true)) ? MOD_OK : + MOD_UNAVAILABLE; +} + +class ModContextMenu final : public ui::ContextMenu { +public: + ModContextMenu(Rml::Element* anchor, std::vector items, uint64_t anchorHandle) + : ContextMenu{anchor, std::move(items)}, mAnchorHandle{anchorHandle} {} + + void update() override { + auto* anchor = slot_from_handle(mAnchorHandle); + if (visible() && (anchor == nullptr || !element_available(*anchor))) { + dismiss(false); + } + ContextMenu::update(); + } + +private: + uint64_t mAnchorHandle; +}; + +ModResult ui_context_menu_push(LoadedMod& mod, uint64_t anchorHandle, + const std::vector& source, uint64_t& outHandle) { + auto* anchor = resolve_element(mod, anchorHandle); + if (anchor == nullptr) { + return MOD_INVALID_ARGUMENT; + } + auto* top = ui::top_document(); + auto* element = anchor->element; + if (top == nullptr || !top->owns_element(anchor->element) || !element_available(*anchor)) { + return MOD_UNAVAILABLE; + } + if (!slot_live(anchorHandle)) { + return MOD_INVALID_ARGUMENT; + } + std::vector items; + items.reserve(source.size()); + for (const auto& item : source) { + items.push_back({ + .text = item.label, + .icon = item.icon != nullptr ? item.icon : "", + .onPressed = + [modPtr = &mod, anchorHandle, fn = item.on_pressed, data = item.user_data] { + if (slot_live(anchorHandle) && fn != nullptr) { + guarded_call(*modPtr, "context menu action", + [&] { fn(modPtr->context.get(), data); }); + } + }, + .enabled = item.enabled && item.on_pressed != nullptr, + .destructive = item.destructive, + .separatorBefore = item.separator_before, + .selected = item.selected, + }); + } + auto menu = std::make_unique(element, std::move(items), anchorHandle); + menu->body()->SetAttribute("mod-id", mod.metadata.id); + auto& slot = alloc_slot(mod, UiSlotKind::ContextMenu, outHandle); + slot.document = menu.get(); + slot.anchorHandle = anchorHandle; + menu->on_close([handle = outHandle] { s_slots.erase(handle); }); + ui::push_document(std::move(menu)); + return MOD_OK; +} + +ModResult ui_context_menu_close(LoadedMod& mod, uint64_t handle) { + auto* slot = resolve(mod, handle, UiSlotKind::ContextMenu, "context_menu_close"); + if (slot == nullptr || slot->document == nullptr) { + return MOD_INVALID_ARGUMENT; + } + static_cast(slot->document)->dismiss(); + return MOD_OK; +} + ModResult ui_window_push(LoadedMod& mod, const UiWindowDesc& desc, uint64_t& outHandle) { outHandle = 0; if (!aurora::rmlui::is_initialized()) { @@ -915,7 +1243,7 @@ ModResult ui_window_close(LoadedMod& mod, uint64_t handle) { if (slot == nullptr || slot->document == nullptr) { return MOD_INVALID_ARGUMENT; } - slot->document->hide(true); + slot->document->pop(); return MOD_OK; } @@ -940,7 +1268,7 @@ ModResult ui_dialog_push(LoadedMod& mod, const UiDialogDesc& desc, uint64_t& out default: break; } - props.title = ui::escape(desc.title); + props.title = desc.title; props.bodyRml = desc.body_rml; props.icon = desc.icon != nullptr ? desc.icon : defaultIcon; props.onDismiss = [modPtr = &mod, handle, fn = desc.on_dismiss, userData = desc.user_data]( @@ -1164,6 +1492,7 @@ void ui_remove_mod(LoadedMod& mod) { if (s_modMenuTabs.erase(&mod) != 0) { s_menuTabsDirty = true; } + bool restoreCoveredDocument = false; auto entries = s_slots.take_all(mod); for (auto& entry : entries) { auto& slot = entry.value; @@ -1171,6 +1500,7 @@ void ui_remove_mod(LoadedMod& mod) { case UiSlotKind::Window: { auto* window = static_cast(slot.document); if (window != nullptr) { + restoreCoveredDocument |= ui::top_document() == window; window->force_hide(true); } break; @@ -1178,10 +1508,16 @@ void ui_remove_mod(LoadedMod& mod) { case UiSlotKind::Dialog: { auto* dialog = static_cast(slot.document); if (dialog != nullptr) { + restoreCoveredDocument |= ui::top_document() == dialog; dialog->force_hide(true); } break; } + case UiSlotKind::ContextMenu: + if (slot.document != nullptr) { + slot.document->force_hide(true); + } + break; case UiSlotKind::Style: ui::unregister_scoped_styles(slot.styleScope, slot.styleId); break; @@ -1189,6 +1525,9 @@ void ui_remove_mod(LoadedMod& mod) { break; } } + if (restoreCoveredDocument) { + ui::uncover_top_document(); + } } ModResult ui_get_clipboard_text( @@ -1256,6 +1595,10 @@ bool valid_control_desc(const UiControlDesc& desc) { return false; } switch (desc.kind) { + case UI_CONTROL_ICON_BUTTON: + return desc.struct_size >= kUiControlIconSize && desc.icon != nullptr && + desc.label[0] != '\0' && ui::material_icon(desc.icon)[0] != '\0' && + desc.on_pressed != nullptr; case UI_CONTROL_BUTTON: case UI_CONTROL_GROUP: return desc.on_pressed != nullptr; @@ -1263,6 +1606,7 @@ bool valid_control_desc(const UiControlDesc& desc) { case UI_CONTROL_NUMBER: case UI_CONTROL_STRING: case UI_CONTROL_SELECT: + case UI_CONTROL_DROPDOWN: break; case UI_CONTROL_COLOR: if (desc.struct_size < kColorDescSize) { @@ -1290,8 +1634,8 @@ bool valid_control_desc(const UiControlDesc& desc) { { return false; } - if (desc.kind == UI_CONTROL_SELECT) { - if (desc.options == nullptr || desc.option_count == 0) { + if (desc.kind == UI_CONTROL_SELECT || desc.kind == UI_CONTROL_DROPDOWN) { + if (desc.options == nullptr || desc.option_count == 0 || desc.option_count > INT_MAX) { return false; } for (size_t i = 0; i < desc.option_count; ++i) { @@ -1352,6 +1696,129 @@ bool copy_list_items( return true; } +template +bool copy_records(const T* records, size_t count, size_t minimumSize, Fn&& copy) { + if (count > INT_MAX || (count != 0 && records == nullptr)) { + return false; + } + auto cursor = reinterpret_cast(records); + for (size_t i = 0; i < count; ++i) { + const auto* record = reinterpret_cast(cursor); + const size_t size = record->struct_size; + if (size < minimumSize || size % alignof(T) != 0 || + cursor > std::numeric_limits::max() - size || !copy(*record)) + { + return false; + } + cursor += size; + } + return true; +} + +ModResult ui_control_set_label(ModContext* context, UiElementHandle handle, const char* label) { + auto* mod = mod_from_context(context); + if (mod == nullptr || handle == 0 || label == nullptr) { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_control_set_label(*mod, handle, label); +} + +ModResult ui_control_set_icon(ModContext* context, UiElementHandle handle, const char* icon) { + auto* mod = mod_from_context(context); + if (mod == nullptr || handle == 0 || icon == nullptr || ui::material_icon(icon)[0] == '\0') { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_control_set_icon(*mod, handle, icon); +} + +ModResult ui_control_set_tooltip(ModContext* context, UiElementHandle handle, const char* text) { + auto* mod = mod_from_context(context); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_control_set_tooltip(*mod, handle, text); +} + +ModResult ui_control_set_options( + ModContext* context, UiElementHandle handle, const UiControlOption* options, size_t count) { + auto* mod = mod_from_context(context); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + std::vector copy; + if (!copy_records(options, count, offsetof(UiControlOption, enabled) + sizeof(bool), + [&](const UiControlOption& option) { + if (option.label == nullptr) { + return false; + } + copy.push_back({option.label, option.enabled}); + return true; + })) + { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_control_set_options(*mod, handle, std::move(copy)); +} + +ModResult ui_elem_set_visible(ModContext* context, UiElementHandle handle, bool visible) { + auto* mod = mod_from_context(context); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_elem_set_visible(*mod, handle, visible); +} + +ModResult ui_elem_focus(ModContext* context, UiElementHandle handle) { + auto* mod = mod_from_context(context); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_elem_focus(*mod, handle); +} + +ModResult ui_context_menu_push(ModContext* context, UiElementHandle anchor, + const UiContextMenuDesc* desc, UiContextMenuHandle* outHandle) { + if (outHandle != nullptr) { + *outHandle = 0; + } + auto* mod = mod_from_context(context); + if (mod == nullptr || anchor == 0 || desc == nullptr || + desc->struct_size < sizeof(UiContextMenuDesc) || desc->item_count == 0) + { + return MOD_INVALID_ARGUMENT; + } + std::vector items; + if (!copy_records(desc->items, desc->item_count, + offsetof(UiContextMenuItem, separator_before) + sizeof(bool), + [&](const UiContextMenuItem& item) { + if (item.label == nullptr || item.label[0] == '\0' || + (item.icon != nullptr && item.icon[0] != '\0' && + ui::material_icon(item.icon)[0] == '\0')) + { + return false; + } + items.push_back(item); + return true; + })) + { + return MOD_INVALID_ARGUMENT; + } + uint64_t handle = 0; + const auto result = ui_impl::ui_context_menu_push(*mod, anchor, items, handle); + if (result == MOD_OK && outHandle != nullptr) { + *outHandle = handle; + } + return result; +} + +ModResult ui_context_menu_close(ModContext* context, UiContextMenuHandle handle) { + auto* mod = mod_from_context(context); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_context_menu_close(*mod, handle); +} + ModResult ui_register_mods_panel(ModContext* context, const UiModsPanelDesc* desc) { auto* mod = mod_from_context(context); if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(UiModsPanelDesc) || @@ -1362,6 +1829,21 @@ ModResult ui_register_mods_panel(ModContext* context, const UiModsPanelDesc* des return ui_impl::ui_register_mods_panel(*mod, *desc); } +ModResult ui_pane_add_row( + ModContext* context, UiElementHandle parent, const UiRowDesc* desc, UiElementHandle* outRow) { + if (outRow != nullptr) { + *outRow = 0; + } + auto* mod = mod_from_context(context); + if (mod == nullptr || parent == 0 || outRow == nullptr || desc == nullptr || + desc->struct_size < offsetof(UiRowDesc, wrap) + sizeof(bool) || + desc->align < UI_ROW_ALIGN_START || desc->align > UI_ROW_ALIGN_SPACE_BETWEEN) + { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_pane_add_row(*mod, parent, *desc, outRow); +} + ModResult ui_pane_add_section(ModContext* context, UiElementHandle pane, const char* title) { auto* mod = mod_from_context(context); if (mod == nullptr || pane == 0 || title == nullptr) { @@ -1605,7 +2087,7 @@ ModResult ui_register_styles_file( *outStyle = 0; } auto* mod = mod_from_context(context); - if (mod == nullptr || path == nullptr || !is_safe_resource_path(path) || + if (mod == nullptr || path == nullptr || !utils::is_safe_resource_path(path) || scope > UI_SCOPE_GRAPHICS_TUNER) { return MOD_INVALID_ARGUMENT; @@ -1816,6 +2298,15 @@ constexpr UiService s_uiService{ .set_clipboard_text = ui_set_clipboard_text, .pane_add_list = ui_pane_add_list, .list_set_items = ui_list_set_items, + .pane_add_row = ui_pane_add_row, + .control_set_label = ui_control_set_label, + .control_set_icon = ui_control_set_icon, + .control_set_options = ui_control_set_options, + .control_set_tooltip = ui_control_set_tooltip, + .elem_set_visible = ui_elem_set_visible, + .elem_focus = ui_elem_focus, + .context_menu_push = ui_context_menu_push, + .context_menu_close = ui_context_menu_close, }; } // namespace diff --git a/src/dusk/mods/svc/websocket.cpp b/src/dusk/mods/svc/websocket.cpp new file mode 100644 index 0000000000..a53c388b30 --- /dev/null +++ b/src/dusk/mods/svc/websocket.cpp @@ -0,0 +1,393 @@ +#include "registry.hpp" + +#include "internal.hpp" +#include "net.hpp" + +#include "dusk/mods/loader/loader.hpp" +#include "mods/svc/websocket.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dusk::mods::svc { +namespace { + +constexpr size_t MaxConnectionsPerMod = 4; +constexpr size_t MaxUrlBytes = 8 * 1024; +constexpr size_t MaxHeaders = 64; +constexpr size_t MaxHeaderBytes = 16 * 1024; +constexpr size_t MaxProtocols = 8; +constexpr size_t DefaultMessageBytes = 1024 * 1024; +constexpr size_t MaxMessageBytes = 16 * 1024 * 1024; +constexpr std::chrono::milliseconds DefaultConnectTimeout{10000}; +constexpr std::chrono::milliseconds DefaultCloseTimeout{5000}; +constexpr std::string_view ReservedHeaders[]{ + "User-Agent", + "Host", + "Connection", + "Upgrade", + "Sec-WebSocket-Key", + "Sec-WebSocket-Version", + "Sec-WebSocket-Extensions", + "Sec-WebSocket-Protocol", + "Content-Length", +}; + +struct ConnectionState { + borealis::ws::Connection connection; + void* userData = nullptr; + bool openPublished = false; +}; + +struct ModPollState { + borealis::ws::Event currentEvent; + std::vector headers; + size_t next = 0; +}; + +SlotMap s_connections; +PerMod s_pollStates; + +bool valid_url(std::string_view url, bool& allowPlaintext) { + allowPlaintext = false; + if (url.size() > MaxUrlBytes) { + return false; + } + const auto parsed = borealis::url::parse(url); + if (!parsed || (parsed->scheme != "wss" && parsed->scheme != "ws")) { + return false; + } + allowPlaintext = parsed->scheme == "ws"; + if (!allowPlaintext) { + return true; + } + return parsed->host == "localhost" || parsed->host == "127.0.0.1" || parsed->host == "::1"; +} + +WebSocketError map_error(borealis::ws::Error error) { + switch (error) { + case borealis::ws::Error::None: + return WEBSOCKET_ERROR_NONE; + case borealis::ws::Error::InvalidUrl: + return WEBSOCKET_ERROR_INVALID_URL; + case borealis::ws::Error::UnsupportedScheme: + return WEBSOCKET_ERROR_UNSUPPORTED_SCHEME; + case borealis::ws::Error::Timeout: + return WEBSOCKET_ERROR_TIMEOUT; + case borealis::ws::Error::TooLarge: + return WEBSOCKET_ERROR_TOO_LARGE; + case borealis::ws::Error::Canceled: + return WEBSOCKET_ERROR_CANCELED; + case borealis::ws::Error::Protocol: + return WEBSOCKET_ERROR_PROTOCOL; + case borealis::ws::Error::Handshake: + return WEBSOCKET_ERROR_HANDSHAKE; + case borealis::ws::Error::NoBackend: + case borealis::ws::Error::Network: + default: + return WEBSOCKET_ERROR_NETWORK; + } +} + +ModResult map_send_result(borealis::ws::SendResult result) { + switch (result) { + case borealis::ws::SendResult::Ok: + return MOD_OK; + case borealis::ws::SendResult::NotOpen: + return MOD_UNAVAILABLE; + case borealis::ws::SendResult::QueueFull: + return MOD_CONFLICT; + case borealis::ws::SendResult::TooLarge: + case borealis::ws::SendResult::InvalidText: + return MOD_INVALID_ARGUMENT; + } + return MOD_ERROR; +} + +ModResult map_close_result(borealis::ws::CloseResult result) { + switch (result) { + case borealis::ws::CloseResult::Ok: + return MOD_OK; + case borealis::ws::CloseResult::NotOpen: + return MOD_UNAVAILABLE; + case borealis::ws::CloseResult::InvalidCode: + case borealis::ws::CloseResult::ReasonTooLarge: + case borealis::ws::CloseResult::InvalidText: + return MOD_INVALID_ARGUMENT; + } + return MOD_ERROR; +} + +size_t connection_count(const LoadedMod& mod) { + size_t count = 0; + s_connections.for_each([&](WebSocketHandle, const auto& entry) { + if (entry.owner == &mod) { + ++count; + } + }); + return count; +} + +ModResult websocket_connect( + ModContext* context, const WebSocketConnectDesc* desc, WebSocketHandle* outHandle) { + if (outHandle != nullptr) { + *outHandle = 0; + } + auto* mod = mod_from_context(context); + if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(WebSocketConnectDesc) || + desc->url == nullptr || outHandle == nullptr || + (desc->header_count != 0 && desc->headers == nullptr) || + (desc->protocol_count != 0 && desc->protocols == nullptr)) + { + return MOD_INVALID_ARGUMENT; + } + if (!declares_import(*mod, WEBSOCKET_SERVICE_ID)) { + return MOD_UNSUPPORTED; + } + bool allowPlaintext = false; + if (!valid_url(desc->url, allowPlaintext) || desc->header_count > MaxHeaders || + desc->protocol_count > MaxProtocols || desc->max_message_bytes > MaxMessageBytes) + { + return MOD_INVALID_ARGUMENT; + } + + size_t headerBytes = 0; + for (uint32_t index = 0; index < desc->header_count; ++index) { + const HttpHeader& header = desc->headers[index]; + if (header.name == nullptr || header.value == nullptr) { + return MOD_INVALID_ARGUMENT; + } + const std::string_view name{header.name}; + const std::string_view value{header.value}; + if (!valid_header(name, value, ReservedHeaders) || + name.size() > MaxHeaderBytes - std::min(headerBytes, MaxHeaderBytes)) + { + return MOD_INVALID_ARGUMENT; + } + headerBytes += name.size(); + if (value.size() > MaxHeaderBytes - std::min(headerBytes, MaxHeaderBytes)) { + return MOD_INVALID_ARGUMENT; + } + headerBytes += value.size(); + } + for (uint32_t index = 0; index < desc->protocol_count; ++index) { + if (desc->protocols[index] == nullptr || !valid_header_name(desc->protocols[index])) { + return MOD_INVALID_ARGUMENT; + } + } + if (!borealis::ws::available()) { + return MOD_UNAVAILABLE; + } + if (connection_count(*mod) >= MaxConnectionsPerMod) { + return MOD_CONFLICT; + } + + const size_t maxMessageBytes = + desc->max_message_bytes != 0 ? desc->max_message_bytes : DefaultMessageBytes; + borealis::ws::Options options{ + .url = desc->url, + .connectTimeout = desc->connect_timeout_ms != 0 ? + std::chrono::milliseconds{desc->connect_timeout_ms} : + DefaultConnectTimeout, + .closeTimeout = desc->close_timeout_ms != 0 ? + std::chrono::milliseconds{desc->close_timeout_ms} : + DefaultCloseTimeout, + .keepaliveInterval = std::chrono::milliseconds{desc->keepalive_interval_ms}, + .maxMessageBytes = maxMessageBytes, + .maxQueuedBytes = 16 * 1024 * 1024, + .maxSendQueueBytes = 4 * 1024 * 1024, + .allowPlaintext = allowPlaintext, + }; + options.headers.reserve(desc->header_count + 1); + for (uint32_t index = 0; index < desc->header_count; ++index) { + options.headers.push_back({desc->headers[index].name, desc->headers[index].value}); + } + options.headers.push_back({ + .name = "User-Agent", + .value = user_agent(*mod), + }); + options.protocols.reserve(desc->protocol_count); + for (uint32_t index = 0; index < desc->protocol_count; ++index) { + options.protocols.emplace_back(desc->protocols[index]); + } + auto connection = borealis::ws::connect(std::move(options)); + if (!connection) { + return MOD_UNAVAILABLE; + } + *outHandle = s_connections.emplace(*mod, ConnectionState{ + .connection = std::move(connection), + .userData = desc->user_data, + }); + return MOD_OK; +} + +ModResult websocket_poll_event(ModContext* context, WebSocketEvent* outEvent) { + const uint32_t structSize = outEvent != nullptr ? outEvent->struct_size : 0; + auto* mod = mod_from_context(context); + if (mod == nullptr || outEvent == nullptr || structSize < sizeof(WebSocketEvent)) { + return MOD_INVALID_ARGUMENT; + } + WebSocketEvent output{ + .struct_size = structSize, + .protocol = "", + .error_message = "", + .close_reason = "", + }; + + std::vector handles; + s_connections.for_each([&](WebSocketHandle handle, const auto& entry) { + if (entry.owner == mod) { + handles.push_back(handle); + } + }); + auto& pollState = s_pollStates.get_or_create(*mod); + if (handles.empty()) { + pollState.next = 0; + std::memcpy(outEvent, &output, sizeof(output)); + return MOD_OK; + } + + const size_t start = pollState.next % handles.size(); + WebSocketHandle selected = 0; + ConnectionState* selectedState = nullptr; + for (size_t offset = 0; offset < handles.size(); ++offset) { + const size_t index = (start + offset) % handles.size(); + auto* entry = s_connections.find_owned(handles[index], *mod); + if (entry != nullptr && entry->value.connection.poll(pollState.currentEvent)) { + selected = handles[index]; + selectedState = &entry->value; + pollState.next = index + 1; + break; + } + } + if (selectedState == nullptr) { + std::memcpy(outEvent, &output, sizeof(output)); + return MOD_OK; + } + + output.ws = selected; + output.user_data = selectedState->userData; + const auto& event = pollState.currentEvent; + pollState.headers.clear(); + pollState.headers.reserve(event.headers.size()); + for (const auto& header : event.headers) { + pollState.headers.push_back({header.name.c_str(), header.value.c_str()}); + } + output.headers = pollState.headers.data(); + output.header_count = static_cast(pollState.headers.size()); + if (event.kind == borealis::ws::Event::Kind::Open) { + output.type = WEBSOCKET_EVENT_OPEN; + output.protocol = event.protocol.c_str(); + selectedState->openPublished = true; + } else if (event.kind == borealis::ws::Event::Kind::Message) { + output.type = WEBSOCKET_EVENT_MESSAGE; + output.message_kind = event.messageKind == borealis::ws::MessageKind::Text ? + WEBSOCKET_MESSAGE_TEXT : + WEBSOCKET_MESSAGE_BINARY; + output.data = event.data.data(); + output.size = event.data.size(); + } else { + output.type = WEBSOCKET_EVENT_CLOSED; + output.error = map_error(event.error); + output.error_message = event.message.c_str(); + output.handshake_status = event.status; + output.close_code = event.code; + output.close_reason = event.reason.c_str(); + } + + std::memcpy(outEvent, &output, sizeof(output)); + if (event.kind == borealis::ws::Event::Kind::Closed) { + s_connections.erase(selected); + } + return MOD_OK; +} + +ModResult websocket_send(ModContext* context, WebSocketHandle handle, WebSocketMessageKind kind, + const void* data, size_t size) { + auto* mod = mod_from_context(context); + if (mod == nullptr || (size != 0 && data == nullptr) || + (kind != WEBSOCKET_MESSAGE_TEXT && kind != WEBSOCKET_MESSAGE_BINARY)) + { + return MOD_INVALID_ARGUMENT; + } + auto* entry = s_connections.find_owned(handle, *mod); + if (entry == nullptr || !entry->value.openPublished) { + return MOD_UNAVAILABLE; + } + const std::string_view bytes = data != nullptr ? + std::string_view{static_cast(data), size} : + std::string_view{}; + return map_send_result(entry->value.connection.send(kind == WEBSOCKET_MESSAGE_TEXT ? + borealis::ws::MessageKind::Text : + borealis::ws::MessageKind::Binary, + bytes)); +} + +ModResult websocket_close( + ModContext* context, WebSocketHandle handle, uint16_t code, const char* reason) { + auto* mod = mod_from_context(context); + const std::string_view reasonText = + reason != nullptr ? std::string_view{reason} : std::string_view{}; + if (mod == nullptr) { + return MOD_INVALID_ARGUMENT; + } + auto* entry = s_connections.find_owned(handle, *mod); + if (entry == nullptr) { + return MOD_UNAVAILABLE; + } + return map_close_result(entry->value.connection.close(code != 0 ? code : 1000, reasonText)); +} + +void websocket_mod_deactivating(LoadedMod& mod) { + (void)s_connections.take_all(mod); + s_pollStates.erase(mod); +} + +void websocket_mod_detached(LoadedMod& mod) { + bool found = false; + s_connections.for_each( + [&](WebSocketHandle, const auto& entry) { found = found || entry.owner == &mod; }); + assert(!found); + assert(!s_pollStates.contains(mod)); +} + +void websocket_shutdown() { + s_connections = {}; + s_pollStates.clear(); +} + +bool websocket_available() { + return borealis::ws::available(); +} + +constexpr WebSocketService s_websocketService{ + .header = SERVICE_HEADER(WebSocketService, WEBSOCKET_SERVICE_MAJOR, WEBSOCKET_SERVICE_MINOR), + .connect = SERVICE_FUNCTION(websocket_connect), + .poll_event = SERVICE_FUNCTION(websocket_poll_event), + .send = SERVICE_FUNCTION(websocket_send), + .close = SERVICE_FUNCTION(websocket_close), +}; + +} // namespace + +constinit const ServiceModule g_websocketModule{ + .id = WEBSOCKET_SERVICE_ID, + .majorVersion = WEBSOCKET_SERVICE_MAJOR, + .minorVersion = WEBSOCKET_SERVICE_MINOR, + .service = &s_websocketService, + .available = websocket_available, + .modDeactivating = websocket_mod_deactivating, + .modDetached = websocket_mod_detached, + .shutdown = websocket_shutdown, +}; + +} // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/window.cpp b/src/dusk/mods/svc/window.cpp index 7c9e938faf..5e61c43033 100644 --- a/src/dusk/mods/svc/window.cpp +++ b/src/dusk/mods/svc/window.cpp @@ -1,7 +1,7 @@ #include "window.hpp" +#include "internal.hpp" #include "registry.hpp" -#include "slot_map.hpp" #include #include "dusk/mods/loader/loader.hpp" diff --git a/src/dusk/mods/updates.cpp b/src/dusk/mods/updates.cpp new file mode 100644 index 0000000000..038b9b2091 --- /dev/null +++ b/src/dusk/mods/updates.cpp @@ -0,0 +1,522 @@ +#include "updates.hpp" + +#include "catalog.hpp" +#include "dusk/mod_loader.hpp" +#include "dusk/mods/queue.hpp" +#include "dusk/mods/svc/registry.hpp" +#include "dusk/settings.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +#include +#endif + +namespace dusk::mods::updates { +namespace { + +using Clock = std::chrono::steady_clock; +constexpr borealis::Log Log{"dusk::mods::updates"}; +constexpr size_t batchSize = 64; + +struct Store { + State state = State::Idle; + bool initialized = false; + bool automatic = false; + bool requested = false; + bool tracking = false; + bool fresh = false; + uint64_t generation = 0; + uint64_t loaderGeneration = 0; + uint64_t serviceGeneration = 0; + UpdateEnvironment environment; + UpdateEnvironment requestEnvironment; + borealis::Task task; + std::vector targets; + size_t nextTarget = 0; + std::vector pending; + std::vector entries; + std::string error; + int retries = 0; + Clock::time_point checkAt{}; +}; + +Store store; + +bool hosted_id(std::string_view id) { + if (id.empty() || id.size() > 128 || id.front() == '.' || id.back() == '.' || + id.find("..") != id.npos || id.find('.') == id.npos) + { + return false; + } + return std::ranges::all_of(id, [](char c) { + return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '.'; + }); +} + +UpdatePrecondition precondition(const Entry& entry) { + return { + .modId = entry.result.id, + .installedVersion = entry.result.installedVersion, + .targetVersion = entry.result.target->version, + .compatibility = entry.result.target->compatibility, + }; +} + +void refresh_entries() { + for (auto& entry : store.entries) { + std::string reason; + std::string queueKey; + if (const auto queued = queue::find_by_mod_id(entry.result.id); + queued && !queue::is_completed(queued->state)) + { + queueKey = queued->id; + } + const auto* local = ModLoader::instance().find_mod(entry.result.id); + if (local == nullptr || local->metadata.version != entry.result.installedVersion) { + reason = "The installed version changed."; + } else if (!store.fresh) { + reason = "Check for updates again."; + } else if (entry.result.target) { + if (!ModLoader::instance().can_update(*local)) { + reason = "Development directories cannot be updated in-game."; + } else { + reason = validate(store.environment, precondition(entry)); + } + } else if (!entry.result.blockers.empty()) { + reason = entry.result.blockers.front(); + } + const bool actionable = entry.result.target && reason.empty() && queueKey.empty(); + if (entry.reason != reason || entry.queueKey != queueKey || entry.actionable != actionable) + { + entry.reason = std::move(reason); + entry.queueKey = std::move(queueKey); + entry.actionable = actionable; + ++store.generation; + } + } +} + +void start_batch() { + const auto end = std::min(store.nextTarget + batchSize, store.targets.size()); + std::vector targets{ + store.targets.begin() + store.nextTarget, store.targets.begin() + end}; + store.nextTarget = end; + store.task = catalog::fetch_updates(store.requestEnvironment, std::move(targets)); +} + +void begin_check() { + store.requested = false; + store.requestEnvironment = store.environment; + store.targets.clear(); + store.pending.clear(); + store.nextTarget = 0; + for (const auto& mod : store.environment.mods) { + if (hosted_id(mod.id) && borealis::update::parse_version(mod.version)) { + store.targets.push_back(mod.id); + } + } + store.error.clear(); + store.state = State::Checking; + ++store.generation; + if (store.environment.mods.size() > 512 || store.environment.services.size() > 4096) { + store.state = State::Failed; + store.error = "The installed mod inventory exceeds the catalog limit."; + } else if (store.targets.empty()) { + store.entries.clear(); + store.state = State::Ready; + store.fresh = true; + } else { + start_batch(); + } +} + +UpdateEnvironment capture_environment() { + UpdateEnvironment result; + result.platform = catalog::platform(); + result.abi = MOD_ABI_VERSION; +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV) + result.nativeUpdates = false; +#endif + result.services = svc::list_services(); + for (const auto& mod : ModLoader::instance().mods()) { + InstalledPackage installed{ + .id = mod.metadata.id, .version = mod.metadata.version, .enabled = mod.is_enabled()}; + for (const auto& service : mod.manifestInfo.imports) { + if (service.required) { + installed.imports.push_back({service.id, service.major, service.minMinor, false}); + } + } + if (mod.runtime) { + installed.imports.push_back( + {mod.runtime->id, mod.runtime->major, mod.runtime->minMinor, false}); + } + std::ranges::sort(installed.imports, {}, [](const auto& service) { + return std::tie(service.id, service.major, service.minMinor); + }); + // Runtime pins can also be present in the native manifest's required imports. + std::vector imports; + for (const auto& service : installed.imports) { + if (!imports.empty() && imports.back().id == service.id && + imports.back().major == service.major) + { + imports.back().minMinor = std::max(imports.back().minMinor, service.minMinor); + } else { + imports.push_back(service); + } + } + installed.imports = std::move(imports); + result.mods.push_back(std::move(installed)); + } + std::ranges::sort(result.mods, {}, &InstalledPackage::id); + return result; +} + +} // namespace + +std::string validate(const UpdateEnvironment& environment, const UpdatePrecondition& update) { + const auto mod = std::ranges::find(environment.mods, update.modId, &InstalledPackage::id); + if (mod == environment.mods.end()) { + return "The mod is no longer installed."; + } + if (mod->version != update.installedVersion) { + return "The installed version changed. Check for updates again."; + } + const auto installedVersion = borealis::update::parse_version(mod->version); + const auto targetVersion = borealis::update::parse_version(update.targetVersion); + if (!installedVersion || !targetVersion) { + return "The mod version cannot be compared."; + } + if (borealis::update::compare_version(*targetVersion, *installedVersion) <= 0) { + return "This version is already installed or older."; + } + const auto& candidate = update.compatibility; + if (candidate.containsNativeCode) { + if (!environment.nativeUpdates) { + return "Native mod updates are not supported on this device."; + } + if (environment.platform.empty() || std::ranges::find(candidate.platforms, + environment.platform) == candidate.platforms.end()) + { + return "This release does not support this device."; + } + if (candidate.abi != environment.abi) { + return "This release requires a different Dusklight ABI."; + } + } + + std::unordered_map services; + for (const auto& service : environment.services) { + if (service.providerId != update.modId) { + services.emplace(service_key(service.id, service.major), service); + } + } + if (mod->enabled) { + for (auto service : candidate.exports) { + service.providerId = update.modId; + if (!services.emplace(service_key(service.id, service.major), service).second) { + return fmt::format( + "Another provider already supplies {}@{}.", service.id, service.major); + } + } + } + const auto available = [&](const std::string& id, uint16_t major, uint16_t minor) { + const auto service = services.find(service_key(id, major)); + return service != services.end() && service->second.minor >= minor; + }; + for (const auto& service : environment.services) { + if (service.providerId == update.modId && + !available(service.id, service.major, service.minor)) + { + return fmt::format("This release removes or lowers {}@{}.{}.", service.id, + service.major, service.minor); + } + } + for (const auto& required : candidate.imports) { + if (!required.optional && !available(required.id, required.major, required.minMinor)) { + return fmt::format( + "Requires {}@{}.{} or newer.", required.id, required.major, required.minMinor); + } + } + + std::unordered_map> graph; + for (const auto& installed : environment.mods) { + if (!installed.enabled) { + continue; + } + const auto& imports = installed.id == update.modId ? candidate.imports : installed.imports; + for (const auto& required : imports) { + const auto service = services.find(service_key(required.id, required.major)); + if (!required.optional && service != services.end() && + !service->second.providerId.empty() && service->second.providerId != installed.id) + { + graph[installed.id].push_back(service->second.providerId); + } + } + } + std::unordered_set visited; + const std::function reaches_target = [&](const std::string& id) { + if (id == update.modId) { + return true; + } + if (!visited.insert(id).second) { + return false; + } + const auto edges = graph.find(id); + return edges != graph.end() && std::ranges::any_of(edges->second, reaches_target); + }; + if (std::ranges::any_of(graph[update.modId], reaches_target)) { + return "This release introduces a required dependency cycle."; + } + return {}; +} + +std::string validate(const UpdatePrecondition& value) { + const auto* mod = ModLoader::instance().find_mod(value.modId); + if (mod != nullptr && !ModLoader::instance().can_update(*mod)) { + return "Development directories cannot be updated in-game."; + } + return validate(capture_environment(), value); +} + +void update() { + auto& loader = ModLoader::instance(); + if (!loader.initialized()) { + return; + } + if (!borealis::http::available()) { + if (store.state != State::Unavailable) { + store.state = State::Unavailable; + ++store.generation; + } + return; + } + const bool automatic = getSettings().backend.checkForModUpdates.getValue(); + if (automatic && !store.automatic) { + store.requested = true; + store.checkAt = Clock::now() + std::chrono::seconds{1}; + } else if (!automatic && !store.tracking) { + store.requested = false; + } + store.automatic = automatic; + bool changed = !store.initialized || store.loaderGeneration != loader.generation() || + store.serviceGeneration != svc::services_generation(); + if (!changed) { + for (const auto& mod : store.environment.mods) { + const auto* current = loader.find_mod(mod.id); + if (current == nullptr || current->is_enabled() != mod.enabled) { + changed = true; + break; + } + } + } + if (changed) { + auto environment = capture_environment(); + if (!store.initialized || environment != store.environment) { + store.environment = std::move(environment); + store.fresh = false; + if (store.automatic || store.tracking) { + store.requested = true; + store.checkAt = Clock::now() + std::chrono::seconds{1}; + } + ++store.generation; + } + store.initialized = true; + store.loaderGeneration = loader.generation(); + store.serviceGeneration = svc::services_generation(); + } + if (store.task && store.task.ready()) { + auto result = store.task.try_take(); + store.task = {}; + if (store.requestEnvironment != store.environment) { + store.requested = store.automatic || store.tracking; + store.checkAt = Clock::now() + std::chrono::seconds{1}; + } else if (result && result->updates) { + store.pending.insert(store.pending.end(), + std::make_move_iterator(result->updates->begin()), + std::make_move_iterator(result->updates->end())); + if (store.nextTarget < store.targets.size()) { + start_batch(); + } else { + store.entries.clear(); + for (auto& entry : store.pending) { + store.entries.push_back({.result = std::move(entry)}); + } + store.pending.clear(); + store.fresh = true; + store.state = State::Ready; + store.retries = 0; + store.requested = false; + ++store.generation; + Log.info("Checked {} installed mods for updates", store.entries.size()); + } + } else { + store.state = State::Failed; + store.error = result ? result->error : "The update check did not return a result."; + if (result && result->retryable && store.retries < 3 && + (store.automatic || store.tracking)) + { + store.requested = true; + store.checkAt = Clock::now() + std::chrono::seconds{std::max( + result->retryAfter, 5 << store.retries++)}; + } + Log.warn("Mod update check failed: {}", store.error); + ++store.generation; + } + } + if (!store.task && store.requested && Clock::now() >= store.checkAt) { + begin_check(); + } + refresh_entries(); +} + +void shutdown() noexcept { + store = {}; +} + +void request_check() { + store.tracking = true; + if (store.task) { + return; + } + store.requested = true; + store.retries = 0; + store.checkAt = Clock::now(); +} + +State state() noexcept { + return store.state; +} + +uint64_t generation() noexcept { + return store.generation; +} + +const std::vector& entries() noexcept { + return store.entries; +} + +const Entry* find(std::string_view id) { + const auto entry = std::ranges::find( + store.entries, id, [](const Entry& entry) { return std::string_view{entry.result.id}; }); + return entry == store.entries.end() ? nullptr : &*entry; +} + +size_t actionable_count() noexcept { + return std::ranges::count_if(store.entries, &Entry::actionable); +} + +uint64_t download_size() noexcept { + uint64_t total = 0; + for (const auto& entry : store.entries) { + if (entry.actionable) { + total += entry.result.target->download.size; + } + } + return total; +} + +std::string status_text() { + if (!store.initialized) { + return "Waiting for mods to initialize."; + } + if (store.state == State::Unavailable) { + return "Online mod updates are unavailable."; + } + if (store.state == State::Checking) { + return "Checking for mod updates…"; + } + if (store.state == State::Idle) { + return store.requested ? "Checking for mod updates…" : "Automatic update checks are off."; + } + if (!store.error.empty()) { + return fmt::format("Could not check: {}", store.error); + } + if (!store.fresh) { + return store.requested || store.task ? "Installed mods changed. Checking again…" : + "Installed mods changed. Check again."; + } + if (const auto count = actionable_count()) { + return fmt::format("{} update{} available", count, count == 1 ? "" : "s"); + } + if (std::ranges::any_of( + store.entries, [](const Entry& entry) { return !entry.queueKey.empty(); })) + { + return "No other updates available."; + } + if (std::ranges::any_of(store.entries, + [](const Entry& entry) { + return entry.result.target || !entry.result.blockers.empty(); + })) + { + return "No updates ready to install."; + } + return "Your mods are up to date."; +} + +EnqueueResult enqueue_update(std::string_view id) { + const auto* entry = find(id); + if (!entry || !entry->result.target) { + return {.skipped = 1, .error = "Check for updates first."}; + } + if (const auto existing = queue::find_by_mod_id(id); + existing && !queue::is_terminal(existing->state)) + { + return {.skipped = 1, .queueKey = existing->id}; + } + if (!store.fresh) { + return {.skipped = 1, .error = "Check for updates again."}; + } + auto expected = precondition(*entry); + if (auto error = validate(expected); !error.empty()) { + return {.skipped = 1, .error = std::move(error)}; + } + const auto* local = ModLoader::instance().find_mod(id); + const auto& target = *entry->result.target; + std::string key; + if (!queue::enqueue( + { + .id = std::string{id}, + .name = local->metadata.name, + .version = target.version, + .source = target.download, + .update = std::move(expected), + }, + &key)) + { + return {.skipped = 1, .error = "Could not enqueue this update."}; + } + refresh_entries(); + return {.accepted = 1, .queueKey = std::move(key)}; +} + +EnqueueResult enqueue_all() { + std::vector ids; + for (const auto& entry : store.entries) { + if (entry.actionable) { + ids.push_back(entry.result.id); + } + } + EnqueueResult result; + for (const auto& id : ids) { + auto item = enqueue_update(id); + result.accepted += item.accepted; + result.skipped += item.skipped; + if (!item.error.empty()) { + result.error = std::move(item.error); + } + } + return result; +} + +} // namespace dusk::mods::updates diff --git a/src/dusk/mods/updates.hpp b/src/dusk/mods/updates.hpp new file mode 100644 index 0000000000..1526616898 --- /dev/null +++ b/src/dusk/mods/updates.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "services.hpp" + +#include +#include +#include +#include +#include +#include + +namespace dusk::mods { + +struct Download { + std::string url; + std::string sha256; + uint64_t size = 0; +}; + +struct InstalledPackage { + std::string id; + std::string version; + bool enabled = false; + std::vector imports; + bool operator==(const InstalledPackage&) const = default; +}; + +struct UpdateEnvironment { + std::string platform; + uint32_t abi = 0; + bool nativeUpdates = true; + std::vector services; + std::vector mods; + bool operator==(const UpdateEnvironment&) const = default; +}; + +struct UpdateCompatibility { + bool containsNativeCode = false; + std::vector platforms; + std::optional abi; + std::vector imports; + std::vector exports; +}; + +struct UpdatePrecondition { + std::string modId; + std::string installedVersion; + std::string targetVersion; + UpdateCompatibility compatibility; +}; + +struct UpdateTarget { + std::string version; + Download download; + UpdateCompatibility compatibility; + std::string changelogHtml; +}; + +struct ModUpdate { + std::string id; + std::string installedVersion; + bool published = false; + bool yankedInstalled = false; + std::string latestVersion; + std::vector blockers; + std::optional target; +}; + +} // namespace dusk::mods + +namespace dusk::mods::updates { + +enum class State { Idle, Checking, Ready, Failed, Unavailable }; + +struct Entry { + ModUpdate result; + std::string reason; + std::string queueKey; + bool actionable = false; +}; + +struct EnqueueResult { + size_t accepted = 0; + size_t skipped = 0; + std::string error; + std::string queueKey; +}; + +void update(); +void shutdown() noexcept; +void request_check(); +State state() noexcept; +std::string status_text(); +uint64_t generation() noexcept; +const std::vector& entries() noexcept; +const Entry* find(std::string_view id); +size_t actionable_count() noexcept; +uint64_t download_size() noexcept; +EnqueueResult enqueue_update(std::string_view id); +EnqueueResult enqueue_all(); + +/** Returns the reason an update cannot be applied, or an empty string. */ +std::string validate(const UpdateEnvironment& environment, const UpdatePrecondition& update); + +/** Rechecks an update against the current loader and callable services. */ +std::string validate(const UpdatePrecondition& precondition); + +} // namespace dusk::mods::updates diff --git a/src/dusk/save_manager.cpp b/src/dusk/save_manager.cpp new file mode 100644 index 0000000000..b9311c66da --- /dev/null +++ b/src/dusk/save_manager.cpp @@ -0,0 +1,1285 @@ +#include "save_manager.hpp" + +#include "aurora/card.h" +#include "borealis/io.hpp" +#include "borealis/version.h" +#include "dusk/main.h" +#include "dusk/mod_loader.hpp" +#include "dusk/mods/svc/save.hpp" +#include "dusk/utilities.hpp" +#include "fmt/format.h" +#include "helpers/bits.hpp" +#include "m_Do/m_Do_MemCard.h" +#include "miniz.h" +#include "nlohmann/json.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dusk::save_manager { +namespace { + +constexpr size_t kGciHeaderSize = 0x40; +constexpr size_t kCardBlockSize = 0x2000; +constexpr size_t kMinRawSize = 512 * 1024; +constexpr size_t kMaxRawSize = 16 * 1024 * 1024; +constexpr size_t kMaxArchiveEntryCount = 130; +constexpr size_t kMaxModFileCount = kMaxArchiveEntryCount - 2; +constexpr size_t kMaxMetadataSize = 1024 * 1024; +constexpr size_t kMaxModFileSize = 1024 * 1024; +constexpr size_t kMaxArtifactSize = 160 * 1024 * 1024; +constexpr int kSaveFormatVersion = 1; + +std::atomic_uint64_t s_tempSequence = 0; + +Result success(std::string message = {}) { + return {.ok = true, .message = std::move(message)}; +} + +Result failure(std::string message) { + return {.ok = false, .message = std::move(message)}; +} + +std::string fixed_string(const uint8_t* value, size_t size) { + const auto* end = static_cast(std::memchr(value, '\0', size)); + return std::string{reinterpret_cast(value), + reinterpret_cast(end != nullptr ? end : value + size)}; +} + +bool valid_raw(const std::vector& bytes) { + if (bytes.size() < kMinRawSize || bytes.size() > kMaxRawSize || + (bytes.size() & (bytes.size() - 1)) != 0 || bytes.size() < 512) + { + return false; + } + + uint16_t checksum = 0; + uint16_t inverse = 0; + for (size_t i = 0; i < 0xfe; ++i) { + const uint16_t word = read_bits(bytes.data() + i * 2); + checksum = static_cast(checksum + word); + inverse = static_cast(inverse + static_cast(word ^ 0xffff)); + } + if (checksum == 0xffff) { + checksum = 0; + } + if (inverse == 0xffff) { + inverse = 0; + } + if (checksum != read_bits(bytes.data() + 0x1fc) || + inverse != read_bits(bytes.data() + 0x1fe)) + { + return false; + } + + const uint16_t sizeMb = read_bits(bytes.data() + 0x22); + return sizeMb >= 4 && sizeMb <= 128 && (sizeMb & (sizeMb - 1)) == 0 && + static_cast(sizeMb) * 16 * kCardBlockSize == bytes.size(); +} + +ValueResult> read_location(std::string_view location, size_t maxSize) { + auto opened = borealis::io::open(location); + if (opened.status != borealis::io::Status::Ok || !opened.file) { + return {failure(opened.message.empty() ? "The selected file could not be opened." : + opened.message), + {}}; + } + const uint64_t size = opened.file.size(); + if (size > maxSize || size > std::numeric_limits::max()) { + return {failure("The selected file is too large."), {}}; + } + std::vector bytes(static_cast(size)); + if (!bytes.empty() && opened.file.read(bytes.data(), bytes.size()) != bytes.size()) { + return {failure(opened.file.error().empty() ? "The selected file could not be read." : + opened.file.error()), + {}}; + } + return {success(), std::move(bytes)}; +} + +bool write_bytes( + const std::filesystem::path& path, std::span bytes, std::string& error) { + try { + if (!path.parent_path().empty()) { + std::filesystem::create_directories(path.parent_path()); + } + std::ofstream out{path, std::ios::binary | std::ios::trunc}; + if (!out.is_open()) { + error = "Unable to open the destination file."; + return false; + } + out.write(reinterpret_cast(bytes.data()), + static_cast(bytes.size())); + out.close(); + if (!out.good()) { + error = "Unable to write the destination file."; + return false; + } + return true; + } catch (const std::exception& exception) { + error = exception.what(); + return false; + } +} + +std::filesystem::path temporary_path(std::string_view extension) { + std::filesystem::path directory = CachePath; + if (directory.empty()) { + std::error_code ec; + directory = std::filesystem::temp_directory_path(ec); + if (ec) { + directory = "."; + } + } + directory /= "save-manager"; + std::error_code ec; + std::filesystem::create_directories(directory, ec); + return directory / fmt::format("artifact-{}{}", s_tempSequence.fetch_add(1), extension); +} + +std::string utc_timestamp(bool filename) { + const std::time_t now = std::time(nullptr); + std::tm value{}; +#if defined(_WIN32) + gmtime_s(&value, &now); +#else + gmtime_r(&now, &value); +#endif + std::array buffer{}; + std::strftime( + buffer.data(), buffer.size(), filename ? "%Y%m%dT%H%M%SZ" : "%Y-%m-%dT%H:%M:%SZ", &value); + return buffer.data(); +} + +std::filesystem::path gci_path(const Storage& storage, const SaveIdentity& identity) { + return storage.path / + (card_file_stem(identity.maker, identity.game, identity.saveName) + ".gci"); +} + +std::filesystem::path backup_directory(const Storage& storage) { + return (storage.kind == StorageKind::GciDirectory ? storage.path : storage.path.parent_path()) / + "backups"; +} + +ValueResult>> read_mod_files( + const Storage& storage, const SaveIdentity& identity) { + std::map> files; + const auto directory = save_sidecar_directory( + storage.path, storage.kind, identity.maker, identity.game, identity.saveName); + std::error_code ec; + if (!std::filesystem::exists(directory, ec)) { + if (ec) { + return {failure(fmt::format("Unable to inspect mod save data: {}", ec.message())), {}}; + } + return {success(), {}}; + } + + try { + for (const auto& entry : std::filesystem::directory_iterator{directory}) { + if (!entry.is_regular_file() || entry.path().extension() != ".json") { + continue; + } + const std::string id = borealis::io::fs_path_to_string(entry.path().stem()); + if (!utils::is_valid_mod_id(id)) { + continue; + } + const auto location = borealis::io::fs_path_to_string(entry.path()); + auto read = read_location(location, kMaxModFileSize); + if (!read) { + return {read.result, {}}; + } + files.emplace(id, std::move(read.value)); + if (files.size() > kMaxModFileCount) { + return {failure("There are too many mod save data files to archive."), {}}; + } + } + } catch (const std::exception& exception) { + return {failure(fmt::format("Unable to read mod save data: {}", exception.what())), {}}; + } + return {success(), std::move(files)}; +} + +std::string mod_version(std::string_view id) { + for (const auto& mod : mods::ModLoader::instance().mods()) { + if (mod.metadata.id == id) { + return mod.metadata.version; + } + } + return {}; +} + +Result pack_dusksave(const std::filesystem::path& destination, const std::vector& gci, + const std::map>& modFiles) { + auto parsed = parse_gci(gci); + if (!parsed) { + return parsed.result; + } + + nlohmann::json mods = nlohmann::json::array(); + for (const auto& entry : modFiles) { + mods.push_back({{"id", entry.first}, {"version", mod_version(entry.first)}}); + } + const nlohmann::json metadata{ + {"version", kSaveFormatVersion}, + {"dusklight", BOREALIS_APP_VERSION}, + {"created", utc_timestamp(false)}, + {"mods", std::move(mods)}, + }; + const std::string metadataText = metadata.dump(2); + + mz_zip_archive zip{}; + if (!mz_zip_writer_init_heap(&zip, 0, 0)) { + return failure("Unable to initialize the save archive."); + } + const std::unique_ptr zipGuard{ + &zip, &mz_zip_writer_end}; + if (!mz_zip_writer_add_mem( + &zip, "save.json", metadataText.data(), metadataText.size(), MZ_BEST_COMPRESSION) || + !mz_zip_writer_add_mem(&zip, "save.gci", gci.data(), gci.size(), MZ_BEST_COMPRESSION)) + { + return failure("Unable to add the save to the archive."); + } + for (const auto& [id, data] : modFiles) { + const std::string name = fmt::format("mods/{}.json", id); + if (!mz_zip_writer_add_mem( + &zip, name.c_str(), data.data(), data.size(), MZ_BEST_COMPRESSION)) + { + return failure("Unable to add mod data to the save archive."); + } + } + + void* archiveData = nullptr; + size_t archiveSize = 0; + if (!mz_zip_writer_finalize_heap_archive(&zip, &archiveData, &archiveSize)) { + return failure("Unable to finish the save archive."); + } + std::string error; + const bool written = write_bytes( + destination, std::span{static_cast(archiveData), archiveSize}, error); + mz_free(archiveData); + return written ? success() : failure(std::move(error)); +} + +ValueResult> extract_zip_entry( + mz_zip_archive& zip, mz_uint index, size_t maxSize) { + mz_zip_archive_file_stat stat{}; + if (!mz_zip_reader_file_stat(&zip, index, &stat) || stat.m_uncomp_size > maxSize) { + return {failure("The save archive contains an invalid or oversized entry."), {}}; + } + std::vector bytes(static_cast(stat.m_uncomp_size)); + if (!mz_zip_reader_extract_to_mem(&zip, index, bytes.data(), bytes.size(), 0)) { + return {failure("The save archive could not be read."), {}}; + } + return {success(), std::move(bytes)}; +} + +ValueResult read_dusksave(std::vector bytes, std::string sourceName) { + mz_zip_archive zip{}; + if (!mz_zip_reader_init_mem(&zip, bytes.data(), bytes.size(), 0)) { + return {failure("The selected ZIP is not a Dusklight save."), {}}; + } + const std::unique_ptr zipGuard{ + &zip, &mz_zip_reader_end}; + const mz_uint entryCount = mz_zip_reader_get_num_files(&zip); + if (entryCount > kMaxArchiveEntryCount) { + return {failure("The save archive contains too many entries."), {}}; + } + + int metadataIndex = -1; + int gciIndex = -1; + size_t metadataCount = 0; + size_t gciCount = 0; + for (mz_uint i = 0; i < entryCount; ++i) { + mz_zip_archive_file_stat stat{}; + if (!mz_zip_reader_file_stat(&zip, i, &stat) || mz_zip_reader_is_file_a_directory(&zip, i)) + { + continue; + } + const std::string_view name{stat.m_filename}; + if (name == "save.json") { + metadataIndex = static_cast(i); + ++metadataCount; + } else if (name == "save.gci") { + gciIndex = static_cast(i); + ++gciCount; + } + } + if (metadataCount != 1 || gciCount != 1) { + return {failure("The selected ZIP is not a Dusklight save."), {}}; + } + + auto metadataBytes = + extract_zip_entry(zip, static_cast(metadataIndex), kMaxMetadataSize); + auto gciBytes = + extract_zip_entry(zip, static_cast(gciIndex), kMaxRawSize + kGciHeaderSize); + if (!metadataBytes || !gciBytes) { + const Result result = !metadataBytes ? metadataBytes.result : gciBytes.result; + return {result, {}}; + } + + Artifact artifact{ + .kind = ArtifactKind::DuskSave, + .gci = std::move(gciBytes.value), + .sourceName = std::move(sourceName), + }; + auto parsedGci = parse_gci(artifact.gci); + if (!parsedGci) { + return {parsedGci.result, {}}; + } + artifact.header = std::move(parsedGci.value); + + try { + const auto metadata = + nlohmann::json::parse(metadataBytes.value.begin(), metadataBytes.value.end()); + if (!metadata.is_object() || metadata.value("version", 0) != kSaveFormatVersion || + !metadata.contains("dusklight") || !metadata["dusklight"].is_string() || + !metadata.contains("created") || !metadata["created"].is_string() || + !metadata.contains("mods") || !metadata["mods"].is_array()) + { + return {failure("The Dusklight save uses an unsupported format version."), {}}; + } + std::set declaredIds; + for (const auto& mod : metadata["mods"]) { + if (!mod.is_object()) { + return {failure("The Dusklight save metadata is invalid."), {}}; + } + const std::string id = mod.value("id", ""); + if (!utils::is_valid_mod_id(id) || !declaredIds.insert(id).second || + !mod.contains("version") || !mod["version"].is_string()) + { + return {failure("The Dusklight save metadata is invalid."), {}}; + } + artifact.declaredMods.push_back( + {.id = id, .version = mod["version"].get()}); + } + } catch (const std::exception&) { + return {failure("The Dusklight save metadata is invalid."), {}}; + } + + for (mz_uint i = 0; i < entryCount; ++i) { + mz_zip_archive_file_stat stat{}; + if (!mz_zip_reader_file_stat(&zip, i, &stat) || mz_zip_reader_is_file_a_directory(&zip, i)) + { + continue; + } + const std::string_view name{stat.m_filename}; + if (!name.starts_with("mods/")) { + continue; + } + const std::string_view child = name.substr(5); + if (!utils::is_safe_path_component(child) || !child.ends_with(".json")) { + return {failure("The save archive contains an unsafe mod data path."), {}}; + } + const std::string id{child.substr(0, child.size() - 5)}; + if (!utils::is_valid_mod_id(id) || artifact.modFiles.contains(id)) { + return {failure("The save archive contains an invalid mod data entry."), {}}; + } + auto data = extract_zip_entry(zip, i, kMaxModFileSize); + if (!data) { + const Result result = data.result; + return {result, {}}; + } + artifact.modFiles.emplace(id, std::move(data.value)); + } + if (artifact.modFiles.size() != artifact.declaredMods.size() || + std::ranges::any_of(artifact.declaredMods, + [&artifact](const ModFileInfo& mod) { return !artifact.modFiles.contains(mod.id); })) + { + return {failure("The save archive metadata does not match its mod data."), {}}; + } + return {success(), std::move(artifact)}; +} + +ValueResult> list_card_saves( + const Storage& storage, std::string_view game, std::string_view maker) { + std::set names; + try { + if (!std::filesystem::exists(storage.path)) { + return {success(), {}}; + } + if (storage.kind == StorageKind::RawImage) { + if (!aurora_card_raw_list( + borealis::io::fs_path_to_string(storage.path).c_str(), + std::string{game}.c_str(), std::string{maker}.c_str(), + [](const char* name, void* userData) { + static_cast*>(userData)->insert(name); + }, + &names)) + { + return {failure("The memory card image could not be read."), {}}; + } + } else { + for (const auto& entry : std::filesystem::directory_iterator{storage.path}) { + if (!entry.is_regular_file() || entry.path().extension() != ".gci") { + continue; + } + auto read = read_location( + borealis::io::fs_path_to_string(entry.path()), kMaxRawSize + kGciHeaderSize); + auto parsed = read ? parse_gci(read.value) : ValueResult{}; + if (parsed && parsed.value.game == game && parsed.value.maker == maker) { + names.insert(parsed.value.saveName); + } + } + } + } catch (const std::exception& exception) { + return {failure(fmt::format("Unable to list saves: {}", exception.what())), {}}; + } + std::vector identities; + for (const auto& name : names) { + if (utils::is_valid_save_name(name)) { + identities.push_back({ + .maker = std::string{maker}, + .game = std::string{game}, + .saveName = name, + }); + } + } + return {success(), std::move(identities)}; +} + +std::string_view backup_save_name( + std::string_view name, std::string_view game, std::string_view maker) { + const std::string prefix = card_file_stem(maker, game, ""); + if (!name.starts_with(prefix) || !name.ends_with(".dusksave")) { + return {}; + } + name.remove_prefix(prefix.size()); + name.remove_suffix(std::string_view{".dusksave"}.size()); + const auto digits = [](std::string_view value) { + return !value.empty() && + std::ranges::all_of(value, [](char ch) { return ch >= '0' && ch <= '9'; }); + }; + if (!name.ends_with('Z')) { + const auto separator = name.rfind('-'); + if (separator == std::string_view::npos || !digits(name.substr(separator + 1))) { + return {}; + } + name = name.substr(0, separator); + } + if (name.size() < 18) { + return {}; + } + const auto timestamp = name.substr(name.size() - 16); + if (name[name.size() - 17] != '-' || timestamp[8] != 'T' || timestamp[15] != 'Z' || + !digits(timestamp.substr(0, 8)) || !digits(timestamp.substr(9, 6))) + { + return {}; + } + const auto saveName = name.substr(0, name.size() - 17); + return utils::is_valid_save_name(saveName) ? saveName : std::string_view{}; +} + +ValueResult> read_current_gci( + const Storage& storage, const SaveIdentity& identity) { + if (storage.kind == StorageKind::RawImage) { + const std::string imagePath = borealis::io::fs_path_to_string(storage.path); + const size_t required = aurora_card_raw_extract(imagePath.c_str(), identity.game.c_str(), + identity.maker.c_str(), identity.saveName.c_str(), nullptr, 0); + if (required == 0) { + return {success(), {}}; + } + std::vector gci(required); + if (aurora_card_raw_extract(imagePath.c_str(), identity.game.c_str(), + identity.maker.c_str(), identity.saveName.c_str(), gci.data(), + gci.size()) != required) + { + return {failure("The save could not be extracted from the card image."), {}}; + } + return {success(), std::move(gci)}; + } + + std::error_code ec; + if (!std::filesystem::exists(storage.path, ec)) { + return ec ? + ValueResult>{ + failure(fmt::format("Unable to inspect the save folder: {}", ec.message())), + {}} : + ValueResult>{success(), {}}; + } + try { + const auto canonical = gci_path(storage, identity); + std::vector candidates; + if (std::filesystem::is_regular_file(canonical)) { + candidates.push_back(canonical); + } + for (const auto& entry : std::filesystem::directory_iterator{storage.path}) { + if (!entry.is_regular_file() || entry.path().extension() != ".gci" || + entry.path() == canonical) + { + continue; + } + candidates.push_back(entry.path()); + } + for (const auto& path : candidates) { + auto read = + read_location(borealis::io::fs_path_to_string(path), kMaxRawSize + kGciHeaderSize); + if (!read) { + continue; + } + auto parsed = parse_gci(read.value); + if (parsed && parsed.value.game == identity.game && + parsed.value.maker == identity.maker && parsed.value.saveName == identity.saveName) + { + return read; + } + } + } catch (const std::exception& exception) { + return { + failure(fmt::format("Unable to inspect the save folder: {}", exception.what())), {}}; + } + return {success(), {}}; +} + +Result replace_sidecars(const Storage& storage, const SaveIdentity& identity, + const std::map>& files) { + const auto destination = save_sidecar_directory( + storage.path, storage.kind, identity.maker, identity.game, identity.saveName); + const auto suffix = fmt::format(".replace-{}", s_tempSequence.fetch_add(1)); + std::filesystem::path staging{destination}; + staging += suffix; + std::filesystem::path previous{destination}; + previous += suffix + ".old"; + std::error_code ec; + std::filesystem::remove_all(staging, ec); + std::filesystem::remove_all(previous, ec); + + try { + if (!files.empty()) { + std::filesystem::create_directories(staging); + for (const auto& [id, data] : files) { + std::string error; + if (!write_bytes(staging / (id + ".json"), data, error)) { + std::filesystem::remove_all(staging, ec); + return failure(fmt::format("Unable to stage mod save data: {}", error)); + } + } + } + if (std::filesystem::exists(destination)) { + std::filesystem::rename(destination, previous); + } + if (!files.empty()) { + try { + std::filesystem::rename(staging, destination); + } catch (...) { + if (std::filesystem::exists(previous)) { + std::filesystem::rename(previous, destination); + } + throw; + } + } + std::filesystem::remove_all(previous, ec); + return success(); + } catch (const std::exception& exception) { + std::filesystem::remove_all(staging, ec); + return failure(fmt::format("Unable to replace mod save data: {}", exception.what())); + } +} + +Result ensure_write_allowed(const Storage& storage) { + if (storage.mounted && !mDoMemCd_isCardCommNone()) { + return failure("The memory card is busy. Try again after returning to the main menu."); + } + return success(); +} + +Result finish_write(const Storage& storage, std::span identities) { + const bool remounted = !storage.mounted || aurora_card_remount(storage.channel); + for (const auto& identity : identities) { + mods::svc::invalidate_save(identity.saveName); + } + if (!remounted) { + return failure("The save changed on disk, but the memory card could not be remounted."); + } + return success(); +} + +Result finish_write(const Storage& storage, const SaveIdentity& identity) { + return finish_write(storage, std::span{&identity, size_t{1}}); +} + +Result write_file_atomic(const std::filesystem::path& destination, std::span bytes) { + auto temporary = destination; + temporary += ".tmp"; + std::string error; + if (!write_bytes(temporary, bytes, error) || + !borealis::io::atomic_replace(temporary, destination, error)) + { + std::error_code ec; + std::filesystem::remove(temporary, ec); + return failure(fmt::format("The save could not be replaced: {}", error)); + } + return success(); +} + +Result write_gci( + const Storage& storage, const SaveIdentity& identity, const std::vector& gci) { + if (storage.kind == StorageKind::RawImage) { + const std::string path = borealis::io::fs_path_to_string(storage.path); + if (!aurora_card_raw_insert(path.c_str(), gci.data(), gci.size(), true)) { + return failure("The save could not be written to the card image."); + } + return success(); + } + + const auto destination = gci_path(storage, identity); + if (const auto written = write_file_atomic(destination, gci); !written) { + return written; + } + // Keep alternate filenames until the replacement is safely on disk. + try { + for (const auto& entry : std::filesystem::directory_iterator{storage.path}) { + if (!entry.is_regular_file() || entry.path().extension() != ".gci" || + entry.path() == destination) + { + continue; + } + auto read = read_location( + borealis::io::fs_path_to_string(entry.path()), kMaxRawSize + kGciHeaderSize); + auto parsed = read ? parse_gci(read.value) : ValueResult{}; + if (parsed && parsed.value.game == identity.game && + parsed.value.maker == identity.maker && parsed.value.saveName == identity.saveName) + { + std::filesystem::remove(entry.path()); + } + } + } catch (const std::exception& exception) { + finish_write(storage, identity); + return failure( + fmt::format("The save was replaced, but duplicate files could not be removed: {}", + exception.what())); + } + return success(); +} + +ValueResult backup_existing_save( + const Storage& storage, const SaveIdentity& identity) { + auto current = read_current_gci(storage, identity); + if (!current) { + return {current.result, {}}; + } + if (current.value.empty()) { + return {success(), {}}; + } + auto modFiles = read_mod_files(storage, identity); + if (!modFiles) { + return {modFiles.result, {}}; + } + + std::filesystem::path destination; + try { + const auto directory = backup_directory(storage); + std::filesystem::create_directories(directory); + const std::string prefix = + card_file_stem(identity.maker, identity.game, identity.saveName) + "-" + + utc_timestamp(true); + destination = directory / (prefix + ".dusksave"); + for (int suffix = 1; std::filesystem::exists(destination); ++suffix) { + destination = directory / fmt::format("{}-{}.dusksave", prefix, suffix); + } + } catch (const std::exception& exception) { + return {failure(fmt::format("Backup failed: {}", exception.what())), {}}; + } + std::filesystem::path temporary{destination}; + temporary += ".tmp"; + const Result packed = pack_dusksave(temporary, current.value, modFiles.value); + if (!packed) { + std::error_code ec; + std::filesystem::remove(temporary, ec); + return {failure(fmt::format("Backup failed: {}", packed.message)), {}}; + } + std::string replaceError; + if (!borealis::io::atomic_replace(temporary, destination, replaceError)) { + std::error_code ec; + std::filesystem::remove(temporary, ec); + return {failure(fmt::format("Backup failed: {}", replaceError)), {}}; + } + + auto backups = list_backups(storage, identity); + if (backups) { + // Always keep this recovery copy, even if older backups have future timestamps. + size_t retained = 1; + for (const auto& backup : backups.value) { + if (backup.path != destination && retained++ >= kDefaultBackupRetention) { + std::error_code ec; + std::filesystem::remove(backup.path, ec); + } + } + } + return {success(), std::move(destination)}; +} + +Result apply_artifact(const Storage& storage, const SaveIdentity& identity, + const Artifact& artifact, ModDataAction modDataAction, bool backupCurrentSave) { + if (const Result allowed = ensure_write_allowed(storage); !allowed) { + return allowed; + } + + if (artifact.kind != ArtifactKind::Gci && artifact.kind != ArtifactKind::DuskSave) { + return failure("The selected artifact is not an individual save."); + } + auto parsed = parse_gci(artifact.gci); + if (!parsed) { + return parsed.result; + } + if (parsed.value.game != identity.game || parsed.value.maker != identity.maker) { + return failure(fmt::format("This save is for {}-{}, but the configured disc uses {}-{}.", + parsed.value.maker, parsed.value.game, identity.maker, identity.game)); + } + if (!utils::is_valid_save_name(parsed.value.saveName) || + parsed.value.saveName != identity.saveName) + { + return failure("The imported save name does not match its destination."); + } + if (backupCurrentSave) { + if (auto backup = backup_existing_save(storage, identity); !backup) { + return backup.result; + } + } + if (const Result written = write_gci(storage, identity, artifact.gci); !written) { + return written; + } + + if (artifact.kind == ArtifactKind::DuskSave) { + modDataAction = ModDataAction::Replace; + } + if (modDataAction != ModDataAction::Keep) { + const std::map> noModFiles; + const auto& files = + modDataAction == ModDataAction::Replace ? artifact.modFiles : noModFiles; + if (const Result sidecars = replace_sidecars(storage, identity, files); !sidecars) { + finish_write(storage, identity); + return sidecars; + } + } + return finish_write(storage, identity); +} + +} // namespace + +std::optional identity_for_disc(const iso::DiscInfo& info, std::string saveName) { + if (info.gameId.size() != 6 || !utils::is_valid_save_name(saveName)) { + return std::nullopt; + } + return SaveIdentity{ + .maker = info.gameId.substr(4), + .game = info.gameId.substr(0, 4), + .saveName = std::move(saveName), + }; +} + +std::string card_file_stem( + std::string_view maker, std::string_view game, std::string_view saveName) { + return fmt::format("{}-{}-{}", maker, game, saveName); +} + +std::filesystem::path save_sidecar_directory(const std::filesystem::path& backingPath, + StorageKind kind, std::string_view maker, std::string_view game, std::string_view saveName) { + const auto stem = card_file_stem(maker, game, saveName); + if (kind == StorageKind::GciDirectory) { + return backingPath / (stem + ".mods"); + } + std::filesystem::path root{backingPath}; + root += ".mods"; + return root / stem; +} + +ValueResult resolve_storage( + std::string_view game, StorageKind preferredKind, int channel) { + if (game.size() != 4 || channel < 0 || channel > 1) { + return {failure("The memory card location could not be resolved."), {}}; + } + Storage storage{.kind = preferredKind, .channel = channel}; + const auto activeType = aurora_card_get_type(channel); + if (activeType != AURORA_CARD_UNAVAILABLE) { + storage.kind = activeType == AURORA_CARD_GCI_DIRECTORY ? StorageKind::GciDirectory : + StorageKind::RawImage; + storage.mounted = true; + } + const std::string gameName{game}; + const auto cardType = storage.kind == StorageKind::GciDirectory ? AURORA_CARD_GCI_DIRECTORY : + AURORA_CARD_RAW_IMAGE; + const size_t required = aurora_card_get_path(gameName.c_str(), cardType, channel, nullptr, 0); + if (required == 0) { + return {failure("The memory card location could not be resolved."), {}}; + } + std::vector path(required); + const size_t copied = + aurora_card_get_path(gameName.c_str(), cardType, channel, path.data(), path.size()); + if (copied != required) { + return {failure("The memory card location could not be resolved."), {}}; + } + storage.path = borealis::io::fs_path_from_utf8(path.data()); + return {success(), std::move(storage)}; +} + +ValueResult> list_saves( + const Storage& storage, std::string_view game, std::string_view maker) { + auto saves = list_card_saves(storage, game, maker); + if (!saves) { + return saves; + } + std::set names; + for (const auto& identity : saves.value) { + names.insert(identity.saveName); + } + const std::string prefix = card_file_stem(maker, game, ""); + const auto add_name = [&](std::string_view name) { + if (utils::is_valid_save_name(name) && names.emplace(name).second) { + saves.value.push_back({.maker = std::string{maker}, + .game = std::string{game}, + .saveName = std::string{name}}); + } + }; + try { + auto modRoot = storage.path; + const bool gciDirectory = storage.kind == StorageKind::GciDirectory; + if (!gciDirectory) { + modRoot += ".mods"; + } + if (std::filesystem::exists(modRoot)) { + for (const auto& entry : std::filesystem::directory_iterator{modRoot}) { + const std::string name = borealis::io::fs_path_to_string(entry.path().filename()); + if (!entry.is_directory() || !name.starts_with(prefix) || + (gciDirectory && !name.ends_with(".mods"))) + { + continue; + } + const auto saveName = std::string_view{name}.substr( + prefix.size(), name.size() - prefix.size() - (gciDirectory ? 5 : 0)); + if (!utils::is_valid_save_name(saveName)) { + continue; + } + for (const auto& mod : std::filesystem::directory_iterator{entry.path()}) { + if (mod.is_regular_file() && mod.path().extension() == ".json" && + utils::is_valid_mod_id(borealis::io::fs_path_to_string(mod.path().stem()))) + { + add_name(saveName); + break; + } + } + } + } + const auto backups = backup_directory(storage); + if (std::filesystem::exists(backups)) { + for (const auto& entry : std::filesystem::directory_iterator{backups}) { + if (entry.is_regular_file()) { + const std::string name = + borealis::io::fs_path_to_string(entry.path().filename()); + add_name(backup_save_name(name, game, maker)); + } + } + } + } catch (const std::exception& exception) { + return { + failure(fmt::format("Unable to list save data and backups: {}", exception.what())), {}}; + } + std::ranges::sort(saves.value, {}, &SaveIdentity::saveName); + return saves; +} + +ValueResult parse_gci(const std::vector& bytes) { + if (bytes.size() < kGciHeaderSize || (bytes.size() - kGciHeaderSize) % kCardBlockSize != 0) { + return {failure("The selected file is not a valid GCI save."), {}}; + } + const uint16_t blockCount = read_bits(bytes.data() + 0x38); + if (blockCount == 0 || blockCount != (bytes.size() - kGciHeaderSize) / kCardBlockSize) { + return {failure("The selected file has an invalid GCI header."), {}}; + } + const std::string saveName = fixed_string(bytes.data() + 8, 32); + if (saveName.empty()) { + return {failure("The selected file has an invalid GCI filename."), {}}; + } + return { + success(), + GciHeader{ + .maker = fixed_string(bytes.data() + 4, 2), + .game = fixed_string(bytes.data(), 4), + .saveName = saveName, + .modifiedTime = read_bits(bytes.data() + 0x28), + .blockCount = blockCount, + }, + }; +} + +ValueResult read_artifact(std::string_view location) { + auto read = read_location(location, kMaxArtifactSize); + if (!read) { + return {read.result, {}}; + } + const std::string sourceName = borealis::io::display_name(location); + if (read.value.size() >= 4 && read.value[0] == 'P' && read.value[1] == 'K' && + read.value[2] == 3 && read.value[3] == 4) + { + return read_dusksave(std::move(read.value), sourceName); + } + if (auto parsed = parse_gci(read.value)) { + return { + success(), + Artifact{ + .kind = ArtifactKind::Gci, + .header = std::move(parsed.value), + .gci = std::move(read.value), + .sourceName = sourceName, + }, + }; + } + if (valid_raw(read.value)) { + return { + success(), + Artifact{ + .kind = ArtifactKind::Raw, + .raw = std::move(read.value), + .sourceName = sourceName, + }, + }; + } + return {failure("The selected file is not a GCI, raw card image, or Dusklight save."), {}}; +} + +ValueResult> extract_raw_saves( + const Artifact& artifact, std::string_view game, std::string_view maker) { + if (artifact.kind != ArtifactKind::Raw) { + return {failure("The selected artifact is not a raw card image."), {}}; + } + const auto staged = temporary_path(".raw"); + const auto removeStaged = [](const std::filesystem::path* path) { + std::error_code ec; + std::filesystem::remove(*path, ec); + }; + const std::unique_ptr stagedGuard{ + &staged, removeStaged}; + if (std::string error; !write_bytes(staged, artifact.raw, error)) { + return {failure(std::move(error)), {}}; + } + const Storage storage{ + .kind = StorageKind::RawImage, + .path = staged, + }; + auto identities = list_card_saves(storage, game, maker); + if (!identities) { + return {identities.result, {}}; + } + std::vector extracted; + for (const auto& identity : identities.value) { + auto gci = read_current_gci(storage, identity); + if (!gci) { + return {gci.result, {}}; + } + auto parsed = parse_gci(gci.value); + if (!parsed || parsed.value.game != identity.game || parsed.value.maker != identity.maker || + parsed.value.saveName != identity.saveName) + { + return {failure("The selected card image contains an invalid save entry."), {}}; + } + extracted.push_back({ + .kind = ArtifactKind::Gci, + .header = std::move(parsed.value), + .gci = std::move(gci.value), + .sourceName = artifact.sourceName, + }); + } + return {success(), std::move(extracted)}; +} + +ValueResult inspect_save(const Storage& storage, const SaveIdentity& identity) { + SaveInfo info; + auto gci = read_current_gci(storage, identity); + if (!gci) { + return {gci.result, {}}; + } + if (!gci.value.empty()) { + auto parsed = parse_gci(gci.value); + if (!parsed) { + return {parsed.result, {}}; + } + info.present = true; + info.size = gci.value.size(); + info.modifiedTime = parsed.value.modifiedTime; + } + auto mods = read_mod_files(storage, identity); + if (!mods) { + return {mods.result, {}}; + } + for (const auto& [id, data] : mods.value) { + info.mods.push_back({ + .id = id, + .version = mod_version(id), + .size = data.size(), + }); + } + return {success(), std::move(info)}; +} + +ValueResult build_export( + const Storage& storage, const SaveIdentity& identity, bool includeModData) { + auto gci = read_current_gci(storage, identity); + if (!gci) { + return {gci.result, {}}; + } + if (gci.value.empty()) { + return {failure("There is no save file to export."), {}}; + } + const std::string extension = includeModData ? ".dusksave" : ".gci"; + ExportArtifact artifact{ + .path = temporary_path(extension), + .suggestedName = + card_file_stem(identity.maker, identity.game, identity.saveName) + extension, + .temporary = true, + }; + Result result; + if (includeModData) { + auto mods = read_mod_files(storage, identity); + if (!mods) { + return {mods.result, {}}; + } + result = pack_dusksave(artifact.path, gci.value, mods.value); + } else { + std::string error; + result = + write_bytes(artifact.path, gci.value, error) ? success() : failure(std::move(error)); + } + if (!result) { + std::error_code ec; + std::filesystem::remove(artifact.path, ec); + return {result, {}}; + } + return {success(), std::move(artifact)}; +} + +ValueResult raw_card_export(const Storage& storage) { + std::error_code ec; + if (storage.kind != StorageKind::RawImage || + !std::filesystem::is_regular_file(storage.path, ec)) + { + return {failure("There is no raw card image to export."), {}}; + } + return { + success(), + ExportArtifact{ + .path = storage.path, + .suggestedName = borealis::io::fs_path_to_string(storage.path.filename()), + }, + }; +} + +Result import_artifact(const Storage& storage, const SaveIdentity& identity, + const Artifact& artifact, ModDataAction modDataAction) { + return apply_artifact(storage, identity, artifact, modDataAction, true); +} + +Result import_raw_image(const Storage& storage, std::string_view game, std::string_view maker, + const Artifact& artifact, ModDataAction modDataAction) { + if (storage.kind != StorageKind::RawImage || artifact.kind != ArtifactKind::Raw) { + return failure("The raw card image import target is invalid."); + } + if (const Result allowed = ensure_write_allowed(storage); !allowed) { + return allowed; + } + if (!valid_raw(artifact.raw)) { + return failure("The selected artifact is not a valid raw card image."); + } + auto identities = list_saves(storage, game, maker); + if (!identities) { + return identities.result; + } + for (const auto& identity : identities.value) { + auto backup = backup_existing_save(storage, identity); + if (!backup) { + return backup.result; + } + } + + if (const Result written = write_file_atomic(storage.path, artifact.raw); !written) { + return written; + } + + Result sidecarResult = success(); + if (modDataAction == ModDataAction::Clear) { + for (const auto& identity : identities.value) { + if (const Result cleared = replace_sidecars(storage, identity, {}); !cleared) { + sidecarResult = cleared; + break; + } + } + } + const Result finished = finish_write(storage, identities.value); + if (!sidecarResult) { + return sidecarResult; + } + return finished; +} + +Result delete_save(const Storage& storage, const SaveIdentity& identity) { + if (const Result allowed = ensure_write_allowed(storage); !allowed) { + return allowed; + } + auto current = read_current_gci(storage, identity); + if (!current) { + return current.result; + } + if (current.value.empty()) { + return failure("There is no save file to delete."); + } + auto backup = backup_existing_save(storage, identity); + if (!backup) { + return backup.result; + } + + if (storage.kind == StorageKind::RawImage) { + const std::string path = borealis::io::fs_path_to_string(storage.path); + if (!aurora_card_raw_delete(path.c_str(), identity.game.c_str(), identity.maker.c_str(), + identity.saveName.c_str())) + { + return failure("The save could not be deleted from the card image."); + } + } else { + try { + for (const auto& entry : std::filesystem::directory_iterator{storage.path}) { + if (!entry.is_regular_file() || entry.path().extension() != ".gci") { + continue; + } + auto read = read_location( + borealis::io::fs_path_to_string(entry.path()), kMaxRawSize + kGciHeaderSize); + auto parsed = read ? parse_gci(read.value) : ValueResult{}; + if (parsed && parsed.value.game == identity.game && + parsed.value.maker == identity.maker && + parsed.value.saveName == identity.saveName) + { + std::filesystem::remove(entry.path()); + } + } + } catch (const std::exception& exception) { + return failure(fmt::format("The save could not be deleted: {}", exception.what())); + } + } + if (const Result sidecars = replace_sidecars(storage, identity, {}); !sidecars) { + finish_write(storage, identity); + return sidecars; + } + return finish_write(storage, identity); +} + +Result delete_mod_data( + const Storage& storage, const SaveIdentity& identity, std::string_view modId) { + if (!utils::is_valid_mod_id(modId)) { + return failure("The mod ID is invalid."); + } + if (const Result allowed = ensure_write_allowed(storage); !allowed) { + return allowed; + } + if (auto backup = backup_existing_save(storage, identity); !backup) { + return backup.result; + } + const auto directory = save_sidecar_directory( + storage.path, storage.kind, identity.maker, identity.game, identity.saveName); + std::error_code ec; + const bool removed = std::filesystem::remove(directory / (std::string{modId} + ".json"), ec); + if (ec) { + return failure(fmt::format("The mod data could not be deleted: {}", ec.message())); + } + if (!removed) { + return failure("The mod data no longer exists."); + } + mods::svc::invalidate_save(identity.saveName); + return success(); +} + +Result create_backup(const Storage& storage, const SaveIdentity& identity) { + if (const Result allowed = ensure_write_allowed(storage); !allowed) { + return allowed; + } + auto backup = backup_existing_save(storage, identity); + if (!backup) { + return backup.result; + } + return backup.value.empty() ? failure("There is no save file to back up.") : success(); +} + +ValueResult> list_backups( + const Storage& storage, const SaveIdentity& identity) { + std::vector backups; + const auto directory = backup_directory(storage); + std::error_code ec; + if (!std::filesystem::exists(directory, ec)) { + return ec ? + ValueResult>{ + failure(fmt::format("Unable to inspect backups: {}", ec.message())), {}} : + ValueResult>{success(), {}}; + } + try { + for (const auto& entry : std::filesystem::directory_iterator{directory}) { + const std::string name = borealis::io::fs_path_to_string(entry.path().filename()); + if (backup_save_name(name, identity.game, identity.maker) != identity.saveName) { + continue; + } + if (entry.is_regular_file()) { + backups.push_back({ + .path = entry.path(), + .name = name, + .modified = entry.last_write_time(), + }); + } + } + } catch (const std::exception& exception) { + return {failure(fmt::format("Unable to inspect backups: {}", exception.what())), {}}; + } + std::ranges::sort(backups, std::greater{}, &BackupInfo::modified); + return {success(), std::move(backups)}; +} + +Result restore_backup( + const Storage& storage, const SaveIdentity& identity, const std::filesystem::path& path) { + auto artifact = read_artifact(borealis::io::fs_path_to_string(path)); + if (!artifact) { + return artifact.result; + } + if (artifact.value.kind != ArtifactKind::DuskSave) { + return failure("The selected backup is not a Dusklight save."); + } + return apply_artifact(storage, identity, artifact.value, ModDataAction::Replace, false); +} + +Result delete_backup(const Storage& storage, const std::filesystem::path& path) { + if (path.parent_path() != backup_directory(storage) || path.extension() != ".dusksave") { + return failure("The backup path is invalid."); + } + std::error_code ec; + if (!std::filesystem::remove(path, ec)) { + return failure(ec ? fmt::format("The backup could not be deleted: {}", ec.message()) : + "The backup no longer exists."); + } + return success(); +} + +std::string format_gc_time(uint32_t value) { + constexpr std::time_t kGcEpoch = 946684800; + const std::time_t time = kGcEpoch + value; + std::tm local{}; +#if defined(_WIN32) + localtime_s(&local, &time); +#else + localtime_r(&time, &local); +#endif + std::array buffer{}; + std::strftime(buffer.data(), buffer.size(), "%Y-%m-%d %H:%M", &local); + return buffer.data(); +} + +void remove_temporary_export(const ExportArtifact& artifact) { + if (!artifact.temporary) { + return; + } + std::error_code ec; + std::filesystem::remove(artifact.path, ec); +} + +} // namespace dusk::save_manager diff --git a/src/dusk/save_manager.hpp b/src/dusk/save_manager.hpp new file mode 100644 index 0000000000..89f39b9815 --- /dev/null +++ b/src/dusk/save_manager.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include "dusk/iso_validate.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace dusk::save_manager { + +constexpr int kDefaultBackupRetention = 10; + +enum class StorageKind { + GciDirectory, + RawImage, +}; + +enum class ArtifactKind { + Unknown, + Gci, + Raw, + DuskSave, +}; + +enum class ModDataAction { + Replace, + Clear, + Keep, +}; + +struct Result { + bool ok = false; + std::string message; + + explicit operator bool() const { return ok; } +}; + +template +struct ValueResult { + Result result; + T value{}; + + explicit operator bool() const { return result.ok; } +}; + +struct SaveIdentity { + std::string maker; + std::string game; + std::string saveName; +}; + +struct GciHeader { + std::string maker; + std::string game; + std::string saveName; + uint32_t modifiedTime = 0; + uint16_t blockCount = 0; +}; + +struct Storage { + StorageKind kind = StorageKind::GciDirectory; + std::filesystem::path path; + int channel = 0; + bool mounted = false; +}; + +struct ModFileInfo { + std::string id; + std::string version; + uintmax_t size = 0; +}; + +struct SaveInfo { + bool present = false; + uintmax_t size = 0; + uint32_t modifiedTime = 0; + std::vector mods; +}; + +struct Artifact { + ArtifactKind kind = ArtifactKind::Unknown; + GciHeader header; + std::vector gci; + std::vector raw; + std::map> modFiles; + std::vector declaredMods; + std::string sourceName; +}; + +struct ExportArtifact { + std::filesystem::path path; + std::string suggestedName; + bool temporary = false; +}; + +struct BackupInfo { + std::filesystem::path path; + std::string name; + std::filesystem::file_time_type modified; +}; + +std::optional identity_for_disc(const iso::DiscInfo& info, std::string saveName); + +std::string card_file_stem( + std::string_view maker, std::string_view game, std::string_view saveName); +std::filesystem::path save_sidecar_directory(const std::filesystem::path& backingPath, + StorageKind kind, std::string_view maker, std::string_view game, std::string_view saveName); + +ValueResult resolve_storage( + std::string_view game, StorageKind preferredKind, int channel = 0); +ValueResult> list_saves( + const Storage& storage, std::string_view game, std::string_view maker); +ValueResult parse_gci(const std::vector& bytes); +ValueResult read_artifact(std::string_view location); +ValueResult> extract_raw_saves( + const Artifact& artifact, std::string_view game, std::string_view maker); + +ValueResult inspect_save(const Storage& storage, const SaveIdentity& identity); +ValueResult build_export( + const Storage& storage, const SaveIdentity& identity, bool includeModData); +ValueResult raw_card_export(const Storage& storage); + +Result import_artifact(const Storage& storage, const SaveIdentity& identity, + const Artifact& artifact, ModDataAction modDataAction); +Result import_raw_image(const Storage& storage, std::string_view game, std::string_view maker, + const Artifact& artifact, ModDataAction modDataAction); +Result delete_save(const Storage& storage, const SaveIdentity& identity); +Result delete_mod_data( + const Storage& storage, const SaveIdentity& identity, std::string_view modId); + +Result create_backup(const Storage& storage, const SaveIdentity& identity); +ValueResult> list_backups( + const Storage& storage, const SaveIdentity& identity); +Result restore_backup( + const Storage& storage, const SaveIdentity& identity, const std::filesystem::path& path); +Result delete_backup(const Storage& storage, const std::filesystem::path& path); + +std::string format_gc_time(uint32_t value); +void remove_temporary_export(const ExportArtifact& artifact); + +} // namespace dusk::save_manager diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 712eb56154..8ce2766b7c 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -25,13 +25,13 @@ UserSettings g_userSettings = { }, .audio = { + .outputMode {"audio.outputMode", AudioOutputMode::StereoSpeakers}, .masterVolume {"audio.masterVolume", 60}, .mainMusicVolume {"audio.mainMusicVolume", 100}, .subMusicVolume {"audio.subMusicVolume", 100}, .soundEffectsVolume {"audio.soundEffectsVolume", 100}, .fanfareVolume {"audio.fanfareVolume", 100}, .enableReverb {"audio.enableReverb", true}, - .enableHrtf {"audio.enableHrtf", false}, .menuSounds {"audio.menuSounds", true}, }, @@ -44,6 +44,7 @@ UserSettings g_userSettings = { .biggerWallets {"game.biggerWallets", false}, .noReturnRupees {"game.noReturnRupees", false}, .disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, + .fastTransitions {"game.fastTransitions", false}, .noSwordRecoil {"game.noSwordRecoil", false}, .damageMultiplier {"game.damageMultiplier", 1}, .noHeartDrops {"game.noHeartDrops", false}, @@ -84,6 +85,7 @@ UserSettings g_userSettings = { .resampler {"game.resampler", Resampler::Bilinear}, .enableMapBackground {"game.enableMapBackground", true}, .disableCutscenePillarboxing {"game.disableCutscenePillarboxing", false}, + .disableLetterboxing {"game.disableLetterboxing", LetterboxMode::Off}, .enableHighQualityMinimapTextures {"game.enableHighQualityMinimapTextures", true}, // Audio @@ -142,13 +144,14 @@ UserSettings g_userSettings = { .enableIndefiniteItemDrops {"game.enableIndefiniteItemDrops", false}, .moonJump {"game.moonJump", false}, .superClawshot {"game.superClawshot", false}, - .alwaysGreatspin {"game.alwaysGreatspin", false}, + .alwaysGreatspin {"game.alwaysGreatspin", AlwaysGreatspinMode::OFF}, .enableFastIronBoots {"game.enableFastIronBoots", false}, .canTransformAnywhere {"game.canTransformAnywhere", false}, .fastRoll {"game.fastRoll", false}, .fastSpinner {"game.fastSpinner", false}, .armorRupeeDrain {"game.armorRupeeDrain", MagicArmorMode::NORMAL}, .invincibleEnemies {"game.invincibleEnemies", false}, + .easyQuickSpin {"game.easyQuickSpin", false}, // Technical .restoreWiiGlitches {"game.restoreWiiGlitches", false}, @@ -177,6 +180,7 @@ UserSettings g_userSettings = { .skipPreLaunchUI {"backend.skipPreLaunchUI", false}, .wasPresetChosen {"backend.wasPresetChosen", false}, .checkForUpdates {"backend.checkForUpdates", true}, + .checkForModUpdates {"backend.checkForModUpdates", true}, .cardFileType {"backend.cardFileType", static_cast(CARD_GCIFOLDER)}, .enableAdvancedSettings {"backend.enableAdvancedSettings", false}, }, @@ -257,13 +261,13 @@ void registerSettings() { [](const int&, const int&) { dusk::ui::apply_scale(); }); // Audio + Register(g_userSettings.audio.outputMode); Register(g_userSettings.audio.masterVolume); Register(g_userSettings.audio.mainMusicVolume); Register(g_userSettings.audio.subMusicVolume); Register(g_userSettings.audio.soundEffectsVolume); Register(g_userSettings.audio.fanfareVolume); Register(g_userSettings.audio.enableReverb); - Register(g_userSettings.audio.enableHrtf); Register(g_userSettings.audio.menuSounds); // Game @@ -273,6 +277,7 @@ void registerSettings() { Register(g_userSettings.game.biggerWallets); Register(g_userSettings.game.noReturnRupees); Register(g_userSettings.game.disableRupeeCutscenes); + Register(g_userSettings.game.fastTransitions); Register(g_userSettings.game.noSwordRecoil); Register(g_userSettings.game.damageMultiplier); Register(g_userSettings.game.noHeartDrops); @@ -317,6 +322,7 @@ void registerSettings() { Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableMapBackground); Register(g_userSettings.game.disableCutscenePillarboxing); + Register(g_userSettings.game.disableLetterboxing); Register(g_userSettings.game.enableHighQualityMinimapTextures); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); @@ -355,6 +361,8 @@ void registerSettings() { Register(g_userSettings.game.superClawshot); Register(g_userSettings.game.alwaysGreatspin); Register(g_userSettings.game.invincibleEnemies); + Register(g_userSettings.game.easyQuickSpin); + Register(g_userSettings.game.enableFrameInterpolation); Register(g_userSettings.game.enableGyroAim); Register(g_userSettings.game.enableGyroRollgoal); @@ -390,6 +398,7 @@ void registerSettings() { Register(g_userSettings.backend.skipPreLaunchUI); Register(g_userSettings.backend.wasPresetChosen); Register(g_userSettings.backend.checkForUpdates); + Register(g_userSettings.backend.checkForModUpdates); Register(g_userSettings.backend.cardFileType); Register(g_userSettings.backend.enableAdvancedSettings); diff --git a/src/dusk/settings.h b/src/dusk/settings.h index 6bf4e349eb..a8a66c82ca 100644 --- a/src/dusk/settings.h +++ b/src/dusk/settings.h @@ -48,6 +48,13 @@ enum class FrameInterpMode : u8 { Unlimited = 2, }; +enum class LetterboxMode : u8 { + Off = 0, + On = 1, + GameplayOnly = 2, + CutsceneOnly = 3, +}; + enum class TouchTargeting : u8 { Hybrid = 0, Hold = 1, @@ -60,6 +67,12 @@ enum class MenuScaling : u8 { Dusklight = 2, }; +enum class AlwaysGreatspinMode : u8 { + OFF = 0, + AFTER_SKILL = 1, + ALWAYS = 2, +}; + enum class MagicArmorMode : u8 { NORMAL = 0, ON_DAMAGE = 1, @@ -68,6 +81,13 @@ enum class MagicArmorMode : u8 { COSMETIC = 4, }; +enum class AudioOutputMode : u8 { + StereoSpeakers = 0, + StereoHeadphones = 1, // spatial audio + Surround6ch = 2, // discrete 5.1 + Surround8ch = 3, // discrete 7.1 +}; + namespace config { template <> struct ConfigEnumRange { @@ -105,6 +125,12 @@ struct ConfigEnumRange { static constexpr auto max = FrameInterpMode::Unlimited; }; +template <> +struct ConfigEnumRange { + static constexpr auto min = LetterboxMode::Off; + static constexpr auto max = LetterboxMode::CutsceneOnly; +}; + template <> struct ConfigEnumRange { static constexpr auto min = TouchTargeting::Hybrid; @@ -117,12 +143,24 @@ struct ConfigEnumRange { static constexpr auto max = MenuScaling::Dusklight; }; +template <> +struct ConfigEnumRange { + static constexpr auto min = AlwaysGreatspinMode::OFF; + static constexpr auto max = AlwaysGreatspinMode::ALWAYS; +}; + template <> struct ConfigEnumRange { static constexpr auto min = MagicArmorMode::NORMAL; static constexpr auto max = MagicArmorMode::COSMETIC; }; +template <> +struct ConfigEnumRange { + static constexpr auto min = AudioOutputMode::StereoSpeakers; + static constexpr auto max = AudioOutputMode::Surround8ch; +}; + template <> struct ConfigValueTraits { static constexpr bool enabled = true; @@ -150,13 +188,13 @@ struct UserSettings { struct { // Audio + ConfigVar outputMode; ConfigVar masterVolume; ConfigVar mainMusicVolume; ConfigVar subMusicVolume; ConfigVar soundEffectsVolume; ConfigVar fanfareVolume; ConfigVar enableReverb; - ConfigVar enableHrtf; ConfigVar menuSounds; } audio; @@ -171,6 +209,7 @@ struct UserSettings { ConfigVar biggerWallets; ConfigVar noReturnRupees; ConfigVar disableRupeeCutscenes; + ConfigVar fastTransitions; ConfigVar noSwordRecoil; ConfigVar damageMultiplier; ConfigVar noHeartDrops; @@ -211,6 +250,7 @@ struct UserSettings { ConfigVar resampler; ConfigVar enableMapBackground; ConfigVar disableCutscenePillarboxing; + ConfigVar disableLetterboxing; ConfigVar enableHighQualityMinimapTextures; // Audio @@ -264,13 +304,14 @@ struct UserSettings { ConfigVar enableIndefiniteItemDrops; ConfigVar moonJump; ConfigVar superClawshot; - ConfigVar alwaysGreatspin; + ConfigVar alwaysGreatspin; ConfigVar enableFastIronBoots; ConfigVar canTransformAnywhere; ConfigVar fastRoll; ConfigVar fastSpinner; ConfigVar armorRupeeDrain; ConfigVar invincibleEnemies; + ConfigVar easyQuickSpin; // Technical ConfigVar restoreWiiGlitches; @@ -300,6 +341,7 @@ struct UserSettings { ConfigVar skipPreLaunchUI; ConfigVar wasPresetChosen; ConfigVar checkForUpdates; + ConfigVar checkForModUpdates; ConfigVar cardFileType; ConfigVar enableAdvancedSettings; } backend; @@ -322,6 +364,13 @@ void registerSettings(); void applyInternalResolutionScale(int scale); void applyResampler(Resampler resampler); +inline bool isLetterboxingDisabled(bool inCutscene) { + const auto mode = getSettings().game.disableLetterboxing.getValue(); + return mode == LetterboxMode::On || + (mode == LetterboxMode::CutsceneOnly && inCutscene) || + (mode == LetterboxMode::GameplayOnly && !inCutscene); +} + // Transient settings struct CollisionViewSettings { diff --git a/src/dusk/speedrun.cpp b/src/dusk/speedrun.cpp index d136cc586c..fa8665f675 100644 --- a/src/dusk/speedrun.cpp +++ b/src/dusk/speedrun.cpp @@ -59,6 +59,7 @@ void resetForSpeedrunMode() { getSettings().game.instantDeath.setSpeedrunValue(false); getSettings().game.noHeartDrops.setSpeedrunValue(false); getSettings().game.holdToMash.setSpeedrunValue(false); + getSettings().game.fastTransitions.setSpeedrunValue(false); getSettings().game.autoSave.setSpeedrunValue(false); getSettings().game.sunsSong.setSpeedrunValue(false); @@ -72,7 +73,7 @@ void resetForSpeedrunMode() { getSettings().game.enableIndefiniteItemDrops.setSpeedrunValue(false); getSettings().game.moonJump.setSpeedrunValue(false); getSettings().game.superClawshot.setSpeedrunValue(false); - getSettings().game.alwaysGreatspin.setSpeedrunValue(false); + getSettings().game.alwaysGreatspin.setSpeedrunValue(AlwaysGreatspinMode::OFF); getSettings().game.enableFastIronBoots.setSpeedrunValue(false); getSettings().game.canTransformAnywhere.setSpeedrunValue(false); getSettings().game.fastRoll.setSpeedrunValue(false); diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index ac72f8377d..fb6ef4cf41 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -1,19 +1,14 @@ +#include "dusk/logging.h" +#include "dusk/main.h" + #include #include -#include -#include -#include -#include -#include +#include + +#include #include #include -#include #include -#include -#include -#include - -#include "tracy/Tracy.hpp" #ifndef _WIN32 #include @@ -244,28 +239,6 @@ void SoundRevID(int a, int b) { STUB_LOG(); } -#pragma mark DC - -void DCFlushRange(void* addr, u32 nBytes) { - // Not needed on PC. -} - -void DCFlushRangeNoSync(void* addr, u32 nBytes) { - // Not needed on PC. -} - -void DCInvalidateRange(void* addr, u32 nBytes) { - // Not needed on PC. -} - -void DCStoreRange(void* addr, u32 nBytes) { - // Not needed on PC. -} - -void DCStoreRangeNoSync(void* addr, u32 nBytes) { - // Not needed on PC. -} - #pragma mark EXI BOOL EXIDeselect(int chan) { @@ -303,12 +276,6 @@ BOOL EXIUnlock(int chan) { return FALSE; } -#pragma mark LC - -void LCEnable() { - STUB_LOG(); -} - // OS-related functions consolidated under "# pragma mark OS" further up #pragma mark VI @@ -1041,11 +1008,6 @@ extern "C" void KPADEnableDPD(s32) { STUB_LOG(); } -// LC (consolidated above) -void LCDisable(void) { - STUB_LOG(); -} - #pragma mark PPC Arch // MSR stuff? void PPCHalt() { diff --git a/src/dusk/time.h b/src/dusk/time.h index fad3186042..6423e084cf 100644 --- a/src/dusk/time.h +++ b/src/dusk/time.h @@ -1,10 +1,10 @@ #pragma once +#include + #include #include -#include "SDL3/SDL_timer.h" - #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN diff --git a/src/dusk/ui/achievements.cpp b/src/dusk/ui/achievements.cpp index 905fa57f59..d746d9ad13 100644 --- a/src/dusk/ui/achievements.cpp +++ b/src/dusk/ui/achievements.cpp @@ -1,12 +1,14 @@ #include "achievements.hpp" -#include "Z2AudioLib/Z2SeMgr.h" -#include "dusk/achievements.h" -#include "fmt/format.h" -#include "m_Do/m_Do_audio.h" #include "nav_types.hpp" #include "pane.hpp" +#include "dusk/achievements.h" + +#include "m_Do/m_Do_audio.h" + +#include + namespace dusk::ui { namespace { @@ -16,50 +18,42 @@ struct CategoryInfo { }; constexpr CategoryInfo kCategories[] = { - {AchievementCategory::Challenge, "Challenge"}, + {AchievementCategory::Challenge, "Challenge"}, {AchievementCategory::Collection, "Collection"}, - {AchievementCategory::Minigame, "Minigame"}, - {AchievementCategory::Misc, "Misc"}, - {AchievementCategory::Glitched, "Glitched"}, + {AchievementCategory::Minigame, "Minigame"}, + {AchievementCategory::Misc, "Misc"}, + {AchievementCategory::Glitched, "Glitched"}, }; -Rml::String build_achievement_info_rml(const Achievement& a) { - Rml::String s = fmt::format( - R"(
)" - R"({})" - R"({})" - R"(
)" - R"(

{}

)", - a.unlocked ? " unlocked" : "", - a.name, - a.unlocked ? " unlocked" : " locked", - a.unlocked ? "Unlocked" : "Locked", - a.description - ); +void append_achievement_info(Rml::Element* parent, const Achievement& a) { + auto* header = append(parent, "achievement-header"); + auto* name = append(header, "achievement-name"); + name->SetClass("unlocked", a.unlocked); + append_text(name, a.name); + auto* badge = append(header, "status-badge"); + badge->SetClass(a.unlocked ? "success" : "error", true); + append_text(badge, a.unlocked ? "Unlocked" : "Locked"); + auto* description = append(parent, "p"); + description->SetClass("achievement-desc", true); + append_text(description, a.description); if (a.isCounter) { - float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f; - s += fmt::format( - R"()" - R"({} / {})", - fraction, - a.unlocked ? "progress-done" : "progress-ongoing", - a.progress, - a.goal - ); + const float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f; + auto* progress = append(parent, "progress"); + progress->SetAttribute("value", fraction); + progress->SetClass(a.unlocked ? "success" : "info", true); + append_text( + append(parent, "achievement-progress"), fmt::format("{} / {}", a.progress, a.goal)); } - - return s; } class AchievementRow : public FluentComponent { public: AchievementRow(Rml::Element* parent, const Achievement& a) - : FluentComponent(createRowRoot(parent)) - { + : FluentComponent(createRowRoot(parent)) { auto& btn = add_child - + @@ -162,22 +167,6 @@ DiscVerificationState verification_to_config(iso::ValidationError validation) { } } -std::string format_bytes(std::size_t bytes) { - constexpr double KiB = 1024.0; - constexpr double MiB = KiB * 1024.0; - constexpr double GiB = MiB * 1024.0; - if (bytes >= static_cast(GiB)) { - return fmt::format("{:.2f} GiB", static_cast(bytes) / GiB); - } - if (bytes >= static_cast(MiB)) { - return fmt::format("{:.0f} MiB", static_cast(bytes) / MiB); - } - if (bytes >= static_cast(KiB)) { - return fmt::format("{:.0f} KiB", static_cast(bytes) / KiB); - } - return fmt::format("{} B", bytes); -} - void begin_disc_verification(std::string path) noexcept { if (path.empty()) { return; @@ -329,7 +318,7 @@ void apply_disc_verification_result(const DiscVerificationResult& result) { state.pendingDiscPath = result.path; state.pendingDiscInfo = result.info; state.pendingDiscValidation = result.validation; - state.errorString = escape(get_error_msg(result.validation)); + state.errorString = get_error_msg(result.validation); return; } @@ -345,41 +334,33 @@ void apply_disc_verification_result(const DiscVerificationResult& result) { state.pendingDiscPath.clear(); state.pendingDiscInfo = {}; state.pendingDiscValidation = iso::ValidationError::Unknown; - state.errorString = escape(get_error_msg(result.validation)); + state.errorString = get_error_msg(result.validation); } class DiscVerificationModal : public WindowSmall { public: - DiscVerificationModal() : WindowSmall("modal", "modal-dialog") { - auto* header = append(mDialog, "div"); - header->SetClass("modal-header", true); + DiscVerificationModal() : WindowSmall("modal") { + auto* header = append(mDialog, "modal-header"); - auto* title = append(header, "div"); - title->SetClass("modal-title", true); - title->SetInnerRML("Verifying disc image"); + auto* title = append(header, "modal-title"); + append_text(title, "Verifying disc image"); auto* icon = append(header, "icon"); icon->SetClass("verifying", true); - auto* body = append(mDialog, "div"); - body->SetClass("modal-body", true); + auto* body = append(mDialog, "modal-body"); - auto* content = append(body, "div"); - content->SetClass("verification-progress", true); + auto* content = append(body, "verification-progress"); - mFileName = append(content, "div"); - mFileName->SetClass("verification-file", true); + mFileName = append(content, "file-path"); mProgress = append(content, "progress"); - mProgress->SetClass("progress-ongoing", true); - mProgress->SetClass("verification-progress-bar", true); + mProgress->SetClass("info", true); mProgress->SetAttribute("value", 0.f); - mDetail = append(content, "div"); - mDetail->SetClass("verification-detail", true); + mDetail = append(content, "small"); - auto* actions = append(mDialog, "div"); - actions->SetClass("modal-actions", true); + auto* actions = append(mDialog, "modal-actions"); mCancelButton = std::make_unique