diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index ab91aca242..467849f761 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -68,13 +68,18 @@ body: required: true - type: dropdown - id: game-region + id: game-disc attributes: - label: Game Region - description: The game region you are playing on + label: Game Disc + description: The game disc you are playing on options: - - NTSC-U (North America) - - PAL (Europe) + - GameCube NTSC-U (North America) + - GameCube PAL (Europe) + - GameCube NTSC-J (Japan) + - Wii NTSC-U (North America) Revision 0 + - Wii NTSC-U (North America) Revision 2 + - Wii PAL (Europe) + - Wii NTSC-J (Japan) validations: required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7abef73e5..41758382cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,7 +76,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v7 with: - name: dusklight-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}} + name: dusklight-${{env.APP_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}} path: | build/install/Dusklight-*.AppImage build/install/debug.tar.* @@ -145,7 +145,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v7 with: - name: dusklight-${{env.DUSK_VERSION}}-${{matrix.artifact_name}} + name: dusklight-${{env.APP_VERSION}}-${{matrix.artifact_name}} path: | build/install/Dusklight.app build/install/debug.tar.* @@ -221,7 +221,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v7 with: - name: dusklight-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}} + name: dusklight-${{env.APP_VERSION}}-android-${{matrix.artifact_arch}} path: upload/ build-windows: @@ -283,7 +283,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v7 with: - name: dusklight-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}} + name: dusklight-${{env.APP_VERSION}}-win32-msvc-${{matrix.artifact_arch}} path: | build/install/*.exe build/install/*.dll diff --git a/.gitmodules b/.gitmodules index b386c1754a..f05842fa7b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "extern/aurora"] path = extern/aurora url = https://github.com/encounter/aurora.git +[submodule "extern/borealis"] + path = extern/borealis + url = https://github.com/encounter/borealis.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 1933ca70f3..6a38dbb96c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,10 +5,11 @@ if (NOT CMAKE_BUILD_TYPE) "Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE) endif () -include(cmake/DetectVersion.cmake) -detect_version() +include(extern/borealis/cmake/DetectVersion.cmake) +borealis_detect_version() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -project(dusklight LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING}) +project(dusklight LANGUAGES C CXX VERSION ${BOREALIS_APP_VERSION}) + if (APPLE) enable_language(OBJC OBJCXX) endif () @@ -32,6 +33,7 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +set(CMAKE_COLOR_DIAGNOSTICS ON) # Folder-based instead of target-based organization # in Visual Studio and Xcode generators @@ -74,6 +76,8 @@ 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) +add_subdirectory(extern/borealis EXCLUDE_FROM_ALL) + add_subdirectory(libs/freeverb) if (CMAKE_BUILD_TYPE STREQUAL "Debug") @@ -85,8 +89,6 @@ 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_ENABLE_UPDATE_CHECKER "Enable update checking support" ON) -option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF) option(DUSK_PACKAGE_INSTALL "Install Dusklight with a Linux-native file structure" OFF) option(DUSK_GFX_DEBUG_GROUPS "Report debug groups to the native graphics API" ${DUSK_GFX_DEBUG_GROUPS_DEFAULT}) option(DUSK_ENABLE_CODE_MODS "Enable code mods" OFF) @@ -151,6 +153,7 @@ if (DUSK_MOVIE_SUPPORT) -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}") @@ -235,18 +238,6 @@ endif () include(FetchContent) # Declare all dependencies first so CMake can download them in parallel -message(STATUS "dusklight: Fetching cxxopts") -FetchContent_Declare(cxxopts - URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz - URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072 - DOWNLOAD_EXTRACT_TIMESTAMP FALSE -) -message(STATUS "dusklight: Fetching nlohmann/json") -FetchContent_Declare(json - URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz - URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa - DOWNLOAD_EXTRACT_TIMESTAMP FALSE -) message(STATUS "dusklight: Fetching miniz") FetchContent_Declare(miniz URL https://github.com/richgel999/miniz/releases/download/3.0.2/miniz-3.0.2.zip @@ -254,7 +245,7 @@ FetchContent_Declare(miniz EXCLUDE_FROM_ALL ) -set(_fetch_content_deps cxxopts json miniz) +set(_fetch_content_deps miniz) if (DUSK_ENABLE_OPUS) message(STATUS "dusklight: Fetching opusfile") @@ -308,40 +299,12 @@ if (DUSK_HAS_FUNCHOOK) endif () FetchContent_MakeAvailable(${_fetch_content_deps}) -if (DUSK_ENABLE_SENTRY_NATIVE) - message(STATUS "dusklight: Fetching sentry-native") - set(SENTRY_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) - set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE) - if (WIN32) - set(SENTRY_TRANSPORT winhttp CACHE STRING "" FORCE) - endif () - set(SENTRY_BUILD_TESTS OFF CACHE BOOL "" FORCE) - set(SENTRY_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(SENTRY_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE) - FetchContent_Declare(sentry_native - GIT_REPOSITORY https://github.com/getsentry/sentry-native.git - GIT_TAG 0.13.6 - GIT_SHALLOW TRUE - GIT_PROGRESS TRUE - GIT_SUBMODULES_RECURSE TRUE - ) - if (NOT sentry_native_POPULATED) - FetchContent_Populate(sentry_native) - set(_skip_install_rules ${CMAKE_SKIP_INSTALL_RULES}) - set(CMAKE_SKIP_INSTALL_RULES ON) - add_subdirectory(${sentry_native_SOURCE_DIR} ${sentry_native_BINARY_DIR} EXCLUDE_FROM_ALL) - set(CMAKE_SKIP_INSTALL_RULES ${_skip_install_rules}) - endif () -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") add_compile_options(-fsigned-char) endif() -configure_version_header() - include(files.cmake) # TODO: version handling for res includes @@ -354,14 +317,14 @@ set(DUSK_PRODUCT_NAME "Dusklight") set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors") source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES}) -source_group("dusklight" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES}) +source_group("dusklight" FILES ${DUSK_FILES}) 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 freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt + aurora::card borealis::cli borealis::crash borealis::data borealis::disc borealis::discord borealis::file_select 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) if (DUSK_HAS_FUNCHOOK) list(APPEND GAME_LIBS funchook-static) @@ -371,54 +334,10 @@ if (DUSK_ENABLE_OPUS) list(APPEND GAME_COMPILE_DEFS DUSK_OPUS=1) endif () -if (DUSK_ENABLE_SENTRY_NATIVE) - list(APPEND GAME_LIBS sentry) - list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_SENTRY_NATIVE=1 SENTRY_BUILD_STATIC=1) -endif () - if (WIN32) list(APPEND GAME_LIBS Ws2_32) - if (CMAKE_BUILD_TYPE STREQUAL Debug) - list(APPEND GAME_LIBS dbghelp) - list(APPEND GAME_COMPILE_DEFS DUSK_CRASH_DBGHELP=1) - endif () endif () -set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/no_backend.cpp) -if (DUSK_ENABLE_UPDATE_CHECKER) - list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_UPDATE_CHECKER=1) - if (WIN32) - set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/winhttp.cpp) - list(APPEND GAME_LIBS winhttp) - list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_WINHTTP=1) - message(STATUS "dusklight: Enabled update checker (WinHTTP)") - elseif (ANDROID) - set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/android.cpp) - list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_ANDROID=1) - message(STATUS "dusklight: Enabled update checker (Android)") - elseif (APPLE) - find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED) - set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/url_session.mm) - set_source_files_properties(src/dusk/http/url_session.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) - list(APPEND GAME_LIBS ${FOUNDATION_FRAMEWORK}) - list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_URLSESSION=1) - message(STATUS "dusklight: Enabled update checker (NSURLSession)") - elseif (CMAKE_SYSTEM_NAME STREQUAL Linux) - find_package(CURL QUIET OPTIONAL_COMPONENTS HTTPS SSL) - if (CURL_FOUND AND CURL_HTTPS_FOUND AND CURL_SSL_FOUND) - set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/curl.cpp) - list(APPEND GAME_LIBS CURL::libcurl) - list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_LIBCURL=1) - message(STATUS "dusklight: Enabled update checker (libcurl)") - else () - message(STATUS "dusklight: Disabled update checker (libcurl + HTTPS/SSL not found)") - endif () - else () - message(STATUS "dusklight: Disabled update checker (unsupported platform)") - endif () -endif () -list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE}) - if (DUSK_MOVIE_SUPPORT) if (TARGET libjpeg-turbo::turbojpeg-static) list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) @@ -428,15 +347,6 @@ if (DUSK_MOVIE_SUPPORT) list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) endif () -set(DUSK_ENABLE_DISCORD_DEFAULT ON) -if (DEFINED DUSK_ENABLE_DISCORD_RPC AND NOT DEFINED DUSK_ENABLE_DISCORD) - set(DUSK_ENABLE_DISCORD_DEFAULT ${DUSK_ENABLE_DISCORD_RPC}) -endif () -option(DUSK_ENABLE_DISCORD "Enable Discord Rich Presence support" ${DUSK_ENABLE_DISCORD_DEFAULT}) -if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS) - list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1) -endif () - if (DUSK_ENABLE_CODE_MODS) list(APPEND GAME_COMPILE_DEFS DUSK_CODE_MODS=1) endif () @@ -501,10 +411,10 @@ endif () set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES} ${miniz_SOURCE_DIR}/miniz.c) if(ANDROID) add_library(dusklight SHARED ${DUSK_FILES}) - set_target_properties(dusklight PROPERTIES OUTPUT_NAME main) else () add_executable(dusklight ${DUSK_FILES}) endif () +borealis_configure_android_application(dusklight) if (ENABLE_ASAN) target_sources(dusklight PRIVATE src/dusk/asan_options.c) endif () @@ -525,7 +435,47 @@ if (DUSK_ENABLE_CODE_MODS) setup_symbol_manifest(dusklight) endif () -# Hook reliability: guaranteed patchable entries on the game ABI surface, and no identical-code folding. +# Hook reliability: prevent auto-inlining, guarantee patchable function entries, +# and disable identical-code folding for game functions. +set(DUSK_GAME_ABI_INLINE_OPTIONS) +if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND DUSK_GAME_ABI_INLINE_OPTIONS + $<$:-finline-hint-functions>) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + list(APPEND DUSK_GAME_ABI_INLINE_OPTIONS + $<$:-flive-patching=inline-clone> + $<$:-fno-inline-functions> + $<$:-fno-inline-small-functions> + $<$:-fno-inline-functions-called-once> + $<$:-fno-early-inlining> + $<$:-fno-ipa-cp> + $<$:-fno-ipa-cp-clone> + $<$:-fno-ipa-sra> + $<$:-fno-partial-inlining>) + endif () +endif () + +if (DUSK_GAME_ABI_INLINE_OPTIONS) + set(_game_abi_files ${GAME_BASE_FILES} ${GAME_DEBUG_FILES}) + list(FILTER _game_abi_files EXCLUDE REGEX "^src/dusk/") + set_property(SOURCE ${_game_abi_files} APPEND PROPERTY + COMPILE_OPTIONS ${DUSK_GAME_ABI_INLINE_OPTIONS}) + + foreach(jsystem_lib IN LISTS JSYSTEM_LIBRARIES) + target_compile_options(${jsystem_lib} PRIVATE ${DUSK_GAME_ABI_INLINE_OPTIONS}) + endforeach() + foreach(_sdk_lib aurora_card aurora_core aurora_dvd aurora_gd aurora_gx aurora_mtx + aurora_os aurora_pad aurora_si aurora_vi) + if (TARGET ${_sdk_lib}) + get_target_property(_sdk_lib_imported ${_sdk_lib} IMPORTED) + if (NOT _sdk_lib_imported) + target_compile_options(${_sdk_lib} PRIVATE ${DUSK_GAME_ABI_INLINE_OPTIONS}) + endif () + endif () + endforeach () +endif () + if (MSVC) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(DUSK_PATCHABLE_ENTRY_FLAG $<$:/hotpatch>) @@ -535,11 +485,11 @@ if (MSVC) else () target_link_options(dusklight PRIVATE /FUNCTIONPADMIN /OPT:NOICF) endif () -elseif (CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang)$") - if (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" OR CMAKE_OSX_ARCHITECTURES MATCHES "arm64") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang|GNU)$") + if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)$" OR CMAKE_OSX_ARCHITECTURES MATCHES "arm64") set(DUSK_PATCHABLE_ENTRY_FLAG $<$:-fpatchable-function-entry=2,1>) else () - set(DUSK_PATCHABLE_ENTRY_FLAG $<$:-fpatchable-function-entry=10,5>) + set(DUSK_PATCHABLE_ENTRY_FLAG $<$:-fpatchable-function-entry=10,4>) endif () endif () @@ -582,17 +532,9 @@ if (TARGET crashpad_handler) ) endif () -if (ANDROID) - # SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static - # archive, force an undefined reference so the linker keeps the SDL_main object. - target_link_options(dusklight PRIVATE "-Wl,-u,SDL_main") -endif () - if (CMAKE_SYSTEM_NAME STREQUAL Linux) target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1") target_link_libraries(dusklight PRIVATE dl) -elseif (ANDROID) - target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1") endif () if (NOT APPLE) @@ -641,6 +583,8 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR 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/audio_mod) endif () @@ -675,8 +619,8 @@ if (APPLE) MACOSX_BUNDLE TRUE MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME} MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER} - MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING} + MACOSX_BUNDLE_BUNDLE_VERSION ${BOREALIS_APP_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${BOREALIS_APP_SHORT_VERSION} MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST} OUTPUT_NAME Dusklight XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" @@ -709,21 +653,6 @@ if (APPLE) endif () endif () -if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - find_library(APPKIT_FRAMEWORK AppKit REQUIRED) - target_sources(dusklight PRIVATE src/dusk/file_select_macos.mm) - set_source_files_properties(src/dusk/file_select_macos.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) - target_link_libraries(dusklight PRIVATE ${APPKIT_FRAMEWORK}) -endif () - -if (IOS) - find_library(UIKIT_FRAMEWORK UIKit REQUIRED) - find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers REQUIRED) - target_sources(dusklight PRIVATE src/dusk/ios/FileSelectDialog.m) - set_source_files_properties(src/dusk/ios/FileSelectDialog.m PROPERTIES COMPILE_FLAGS -fobjc-arc) - target_link_libraries(dusklight PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK}) -endif () - include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) aurora_copy_runtime_dlls(dusklight) diff --git a/CMakePresets.json b/CMakePresets.json index 4faccc4448..45a6522625 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -28,12 +28,12 @@ "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", - "DUSK_ENABLE_SENTRY_NATIVE": { + "BOREALIS_ENABLE_SENTRY": { "type": "BOOL", "value": true }, - "DUSK_SENTRY_DSN": "$env{SENTRY_DSN}", - "DUSK_SENTRY_ENVIRONMENT": "production", + "BOREALIS_SENTRY_DSN": "$env{SENTRY_DSN}", + "BOREALIS_SENTRY_ENVIRONMENT": "production", "Rust_RUSTUP_INSTALL_MISSING_TARGET": { "type": "BOOL", "value": true @@ -448,7 +448,7 @@ "ci" ], "cacheVariables": { - "DUSK_ENABLE_SENTRY_NATIVE": { + "BOREALIS_ENABLE_SENTRY": { "type": "BOOL", "value": false } diff --git a/README.md b/README.md index b5a9ecae72..473efd8b68 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,10 @@ It aims to be as accurate as possible to the original while also providing new o You must dump your own copy of the game. Please see [this article](https://wiki.dolphin-emu.org/index.php?title=Ripping_Games) for instructions. After dumping, you can use a program like [Dolphin](https://dolphin-emu.org/) or [nodtool](https://github.com/encounter/nod/releases) to convert the `.iso` to `.rvz` to save space. -Currently, only the GameCube releases are supported. Support for other versions of the game is planned in the future. +Dusklight currently supports all commercial discs except for Wii's Korean release. + +> [!NOTE] +> Dusklight is based on the [Twilight Princess decompilation](https://github.com/zeldaret/tp), which is currently only matching for GameCube. As a result, even when playing Dusklight with a Wii disc, you will be presented with the GameCube version's HUD and certain other specificities. ### 2. Install Dusklight diff --git a/ci/build-appimage.sh b/ci/build-appimage.sh index e12f1163b9..c4a0e82995 100755 --- a/ci/build-appimage.sh +++ b/ci/build-appimage.sh @@ -23,5 +23,5 @@ cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,102 cp platforms/freedesktop/dev.twilitrealm.dusk.desktop build/appdir/usr/share/applications cd build/install -VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \ +VERSION="$APP_VERSION" NO_STRIP=1 "$linuxdeploy" \ -l "$lib_dir/libusb-1.0.so" --appdir "$build_dir/appdir" --output appimage diff --git a/cmake/DetectVersion.cmake b/cmake/DetectVersion.cmake deleted file mode 100644 index ec72a97a2e..0000000000 --- a/cmake/DetectVersion.cmake +++ /dev/null @@ -1,121 +0,0 @@ -# Version detection shared by the main build and the mod SDK (sdk/CMakeLists.txt) -include_guard(GLOBAL) - -get_filename_component(_DUSK_VERSION_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) - -set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN") -set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment") - -set(DUSK_VERSION_OVERRIDE "" CACHE STRING "Override version string (skips git detection and format validation)") - -macro(detect_version) - if (DUSK_VERSION_OVERRIDE) - set(DUSK_WC_DESCRIBE "${DUSK_VERSION_OVERRIDE}") - set(DUSK_VERSION_STRING "0.0.0.0") - set(DUSK_SHORT_VERSION_STRING "0.0.0") - set(DUSK_VERSION_CODE "1") - set(DUSK_WC_REVISION "") - set(DUSK_WC_BRANCH "") - set(DUSK_WC_DATE "") - message(STATUS "Dusklight version overridden to ${DUSK_WC_DESCRIBE}") - else () - # obtain revision info from git - find_package(Git) - if (GIT_FOUND) - # make sure version information gets re-run when the current Git HEAD changes - execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD - OUTPUT_VARIABLE dusk_git_head_filename - OUTPUT_STRIP_TRAILING_WHITESPACE) - get_filename_component(dusk_git_head_filename "${dusk_git_head_filename}" ABSOLUTE BASE_DIR "${_DUSK_VERSION_ROOT}") - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_filename}") - - execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD - OUTPUT_VARIABLE dusk_git_head_symbolic - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} - COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${dusk_git_head_symbolic} - OUTPUT_VARIABLE dusk_git_head_symbolic_filename - OUTPUT_STRIP_TRAILING_WHITESPACE) - get_filename_component(dusk_git_head_symbolic_filename "${dusk_git_head_symbolic_filename}" ABSOLUTE BASE_DIR "${_DUSK_VERSION_ROOT}") - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_symbolic_filename}") - - # defines DUSK_WC_REVISION - execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD - OUTPUT_VARIABLE DUSK_WC_REVISION - OUTPUT_STRIP_TRAILING_WHITESPACE) - # defines DUSK_WC_DESCRIBE - execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --match "v*" - OUTPUT_VARIABLE DUSK_WC_DESCRIBE - OUTPUT_STRIP_TRAILING_WHITESPACE) - - # remove the git hash, then collapse a clean "-0" suffix only - string(REGEX REPLACE "-[^-]+(-dirty|)$" "\\1" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}") - string(REGEX REPLACE "-0$" "" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}") - - # defines DUSK_WC_BRANCH - execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD - OUTPUT_VARIABLE DUSK_WC_BRANCH - OUTPUT_STRIP_TRAILING_WHITESPACE) - # defines DUSK_WC_DATE - execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad - OUTPUT_VARIABLE DUSK_WC_DATE - OUTPUT_STRIP_TRAILING_WHITESPACE) - else () - message(STATUS "Unable to find git, commit information will not be available") - endif () - - if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)([-+].*)?$") - set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") - set(_ver_major ${CMAKE_MATCH_1}) - set(_ver_minor ${CMAKE_MATCH_2}) - set(_ver_patch ${CMAKE_MATCH_3}) - set(DUSK_VERSION_TWEAK "0") - if (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+)(-dirty)?$") - set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}") - elseif (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-[0-9A-Za-z.-]+-([0-9]+)(-dirty)?$") - set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}") - endif () - set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${DUSK_VERSION_TWEAK}") - if (DUSK_VERSION_TWEAK GREATER 999) - set(_tweak 999) - else () - set(_tweak ${DUSK_VERSION_TWEAK}) - endif () - # encoding: major*1e7 + minor*1e5 + patch*1e3 + tweak; collision-free for major<210, minor<100, patch<100, tweak<=999 - math(EXPR DUSK_VERSION_CODE - "${_ver_major} * 10000000 + ${_ver_minor} * 100000 + ${_ver_patch} * 1000 + ${_tweak}") - else () - set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION") - set(DUSK_VERSION_STRING "0.0.0.0") - set(DUSK_SHORT_VERSION_STRING "0.0.0") - set(DUSK_VERSION_CODE "1") - endif () - - endif () - - # Add version information to CI environment variables - if (DEFINED ENV{GITHUB_ENV}) - file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n") - file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION_CODE=${DUSK_VERSION_CODE}\n") - endif () - message(STATUS "Dusklight version set to ${DUSK_WC_DESCRIBE}") -endmacro() - -# Sets PLATFORM_NAME and configures version.h into the caller's binary dir. -macro(configure_version_header) - if (CMAKE_SYSTEM_NAME STREQUAL Windows) - set(PLATFORM_NAME win32) - elseif (CMAKE_SYSTEM_NAME STREQUAL Darwin) - if (IOS) - set(PLATFORM_NAME ios) - elseif (TVOS) - set(PLATFORM_NAME tvos) - else () - set(PLATFORM_NAME macos) - endif () - else () - string(TOLOWER CMAKE_SYSTEM_NAME PLATFORM_NAME) - endif () - - configure_file(${_DUSK_VERSION_ROOT}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) -endmacro() diff --git a/cmake/SymbolManifest.cmake b/cmake/SymbolManifest.cmake index 7ef57f7ced..48935acdf6 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.2") +set(_SYMGEN_VERSION "1.3.3") 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=0344838d1674df09c17c3eeddcf26eb89c333fdb85dfd78f68adc436070eccbe") + set(_asset_hash "SHA256=e8420df1160242c83bd0e5197efeeb25ceb9fecc69dbc82bdc4927476381e948") elseif (_host_processor MATCHES "^(x86_64|amd64)$") set(_asset "symgen-macos-x86_64") - set(_asset_hash "SHA256=ae0674f4a1e9d0dedfa02d35939ac28cdd229276b331c1f507df5df809cbec7e") + set(_asset_hash "SHA256=e51d13bbf0e982d1519f56850ff3c7b5910ca5a7095dd5a82cd8e59750e2d013") endif () elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (_host_processor MATCHES "^(aarch64|arm64)$") set(_asset "symgen-linux-aarch64") - set(_asset_hash "SHA256=af766de2bfaeb0a06f6d7bc17bb2510b4a9c40f44a56e49bc4a4b798a6223042") + set(_asset_hash "SHA256=331190dd21ecee12a52e857b7f90a8b3e0a114608173813f7df74a31cf783f70") elseif (_host_processor MATCHES "^(x86_64|amd64)$") set(_asset "symgen-linux-x86_64") - set(_asset_hash "SHA256=ebd62fb9623acc942b6295609e2306f85a73043b0a8a117f2072b761dd08e68f") + set(_asset_hash "SHA256=d727e0ba43242f92a92865af1b69c47aa9d81a0242c4570ce53b90e1ecd15e84") elseif (_host_processor MATCHES "^(i[3-6]86|x86)$") set(_asset "symgen-linux-i686") - set(_asset_hash "SHA256=07780a4513fd29726578efc4ff2d88736b22f407ed821b32a68e35ff1e9af5f4") + set(_asset_hash "SHA256=0c9f94d36b7fa46d15a199c9d0f1a633b73eb7d9c02621a6a76a676b83a135c7") endif () elseif (CMAKE_HOST_WIN32) if (_host_processor MATCHES "^(arm64|aarch64)$") set(_asset "symgen-windows-arm64.exe") - set(_asset_hash "SHA256=5bb22b4a4a9b5ad45646af411bfb09b8321a732ff3a077eb4c9de1feaef27d2b") + set(_asset_hash "SHA256=c699192957e2086ed68912ed39fa77858e1ae894476ab8b557711804cf2aa7cb") elseif (_host_processor MATCHES "^(x86_64|amd64)$") set(_asset "symgen-windows-x86_64.exe") - set(_asset_hash "SHA256=1d1ac087f991a96932d108969e15998becfec643e88f3e7d182ca97ebfc6a46f") + set(_asset_hash "SHA256=6d56a45617c75065ab7a3192d09ca52ff735baf5637b0899f446138d46acc310") elseif (_host_processor MATCHES "^(i[3-6]86|x86)$") set(_asset "symgen-windows-x86.exe") - set(_asset_hash "SHA256=c113f4cd05f813efe2b1878dbfcf44d6302aee3974271736e0036430b0cced78") + set(_asset_hash "SHA256=6cccc3a8d29e7525a4d1ade1dc4d89327b7c3002a604ab1c7decb43eea6fd4ab") endif () endif () diff --git a/docs/ios-install-altstore.md b/docs/ios-install-altstore.md index 5c0f071878..0c2e9e36a6 100644 --- a/docs/ios-install-altstore.md +++ b/docs/ios-install-altstore.md @@ -5,7 +5,13 @@ - A Windows, Linux, or macOS device - iOS device connected to computer via USB - Dusklight IPA file (download the latest `Dusklight-vX.X.X-ios-arm64.ipa` from the [releases page](https://github.com/TwilitRealm/dusklight/releases)) -- Legally acquired game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL) +- Legally acquired game disc: + - `GZ2E01` (GameCube USA) + - `GZ2P01` (GameCube PAL) + - `GZ2J01` (GameCube JPN) + - `RZDE01` (Wii USA Rev. 0 *or* Rev. 2) + - `RZDP01` (Wii PAL) + - `RZDJ01` (Wii JPN) ## 1. Install iloader diff --git a/docs/modding.md b/docs/modding.md index 86af7ce34c..317ae32c54 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -209,8 +209,8 @@ if (svc_resource->load(mod_ctx, "config.txt", &buf) == MOD_OK) { } ``` -Missing files return `MOD_UNAVAILABLE`. Always `free` what you `load`. Note that the bundle is read-only; for writable -storage, use the directory from `svc_host->mod_dir(mod_ctx)`. +Missing files return `MOD_UNAVAILABLE`. Always `free` what you `load`. The bundle is read-only; use +`HostService::data_dir` for persistent storage. ### HostService (`mods/svc/host.h`) @@ -219,9 +219,17 @@ Mod metadata and runtime interaction with the loader: ```cpp IMPORT_SERVICE(HostService, svc_host); -const char* id = svc_host->mod_id(mod_ctx); -const char* dir = svc_host->mod_dir(mod_ctx); // writable per-mod directory -svc_host->fail(mod_ctx, MOD_ERROR, "something unrecoverable happened"); // disables the mod +// Temporary mod data directory, wiped on startup +const char* cacheDir = svc_host->mod_dir(mod_ctx); + +// Persistent mod data directory +const char* dataDir = nullptr; +if (svc_host->data_dir(mod_ctx, &dataDir) == MOD_OK) { + // ... +} + +// Report an error and disable the mod +svc_host->fail(mod_ctx, MOD_ERROR, "something unrecoverable happened"); ``` `get_service`/`publish_service` provide dynamic service lookup; see [Exporting Services](#exporting-services). @@ -254,14 +262,16 @@ Installs hooks on game functions and resolves symbols by name. You'll rarely cal ### OverlayService (`mods/svc/overlay.h`) Registers DVD file overlays at runtime: the dynamic counterpart to the static `overlay/` directory (see -[Asset Overlays](#asset-overlays)). Overlay a disc path with a file from your bundle, or with a caller-owned buffer +[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): ```cpp IMPORT_SERVICE(OverlayService, svc_overlay); OverlayHandle handle = 0; -svc_overlay->add_file(mod_ctx, "/res/Msgus.arc", "res/replacement.arc", &handle); +svc_overlay->add_file(mod_ctx, "/Movie/demo_movie98_00.thp", "res/replacement.thp", &handle); // Replaces the demo movie +svc_overlay->add_file(mod_ctx, "/res/Object/Kmdl/archive/bmwr/al.bmd", "res/link_model.bmd", &handle); // Replaces link's model svc_overlay->add_buffer(mod_ctx, "/generated.txt", data, size, nullptr); svc_overlay->remove(mod_ctx, handle); ``` @@ -270,6 +280,9 @@ svc_overlay->remove(mod_ctx, handle); 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. +Dusklight reloads core archive files during scene transitions so modifications to Link, Midna or other globally-loaded +data get refreshed without a full restart. + See [Asset Overlays](#asset-overlays) for priority and conflict handling. ### TextureService (`mods/svc/texture.h`) @@ -340,6 +353,88 @@ 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`) + +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`. + +```cpp +IMPORT_SERVICE(SaveService, svc_save); + +struct MySaveData { + uint32_t version; + uint32_t counter; +}; + +MySaveData state{1, 42}; +svc_save->set_blob(mod_ctx, "state", &state, sizeof(state)); + +MySaveData loaded{}; +size_t loadedSize = sizeof(loaded); +if (svc_save->get_blob(mod_ctx, "state", &loaded, &loadedSize) == MOD_OK && + loadedSize == sizeof(loaded)) { + apply_state(loaded); +} +``` + +`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. + +`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`) + +Allows making changes to a stage's "stage info" (contents of .dzs/.dzr files). +(Currently only supports editing actor nodes.) + +```cpp +IMPORT_SERVICE(StageService, svc_stage); + +stage_actor_data_class record = { + "carry00", + 0xFF000000, + cXyz(0.0f, 0.0f, 0.0f), + csXyz(0, 0, 0), + 0, +}; + +StageActorHandle handle{}; +svc_stage->patch_actor(mod_ctx, "F_SP102", 0, -1, record_crc, &record, sizeof(record), &handle); +``` + +``` +StageActorHandle handle{}; +svc_stage->delete_actor(mod_ctx, "F_SP102", 0, -1, record_crc, &handle); +``` + +Patch or remove actors from the original actor list as the room loads. +Given records must be of either `stage_actor_data_class` or `stage_tgsc_data_class` types. +`record_crc` is the CRC-32 of the unmodified original record used to identify the record to replace or remove. + +``` +stage_actor_data_class record = { + "carry00", + 0xFF000000, + cXyz(0.0f, 0.0f, 0.0f), + csXyz(0, 0, 0), + 0, +}; + +StageActorHandle handle{}; +svc_stage->add_actor(mod_ctx, "F_SP102", 0, -1, &record, sizeof(record), &handle); +``` + +Add a new actor to the actor list as the room loads. +Given records must be of either `stage_actor_data_class` or `stage_tgsc_data_class` types. + +Stage names may contain up to 8 characters. For patches and deletions, room `0xff` and layer `-1` match any room or +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`) Integrate seamlessly with Dusklight's UI system: add controls and buttons to your mod's detail pane in the Mods window, @@ -379,8 +474,8 @@ per-window RCSS. A non-`MOD_OK` result from `build`/`update` fails your mod, as callback. **Controls:** `pane_add_control` adds an input row described by a `UiControlDesc`: `UI_CONTROL_BUTTON`, -`UI_CONTROL_TOGGLE`, `UI_CONTROL_NUMBER`, `UI_CONTROL_STRING`, or `UI_CONTROL_SELECT`. Values bind with callbacks or -directly to a config var. +`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. ```cpp UiControlDesc control = UI_CONTROL_DESC_INIT; @@ -393,9 +488,56 @@ svc_ui->pane_add_control(mod_ctx, leftPane, &control, nullptr); ``` `UI_BINDING_CONFIG_VAR` wires persistence, change notifications, and the modified indicator automatically. The var -type must match the control: `TOGGLE` = bool, `NUMBER` and `SELECT` = int, `STRING` = string. Float vars are not -bindable; use callbacks and convert. `help_rml` and `SELECT` option lists render in a help pane, so `SELECT` controls -are only available inside window tabs. +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. + +`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. + +**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. + +```cpp +UiListHandle locationList = 0; + +void replace_locations(std::string_view query) { + std::vector items; + for (uint64_t i = 0; i < locations.size(); ++i) { + if (matches(locations[i], query)) { + UiListItem item = UI_LIST_ITEM_INIT; + item.key = i; + item.label = locations[i].c_str(); + items.push_back(item); + } + } + svc_ui->list_set_items(mod_ctx, locationList, items.data(), items.size()); +} + +void set_filter(ModContext*, void*, const UiControlValue* value) { + replace_locations(value->string_value); +} + +bool location_selected(ModContext*, UiListHandle, uint64_t key, void*) { + return selected_locations.contains(key); +} + +UiControlDesc filter = UI_CONTROL_DESC_INIT; +filter.kind = UI_CONTROL_STRING; +filter.label = "Filter"; +filter.get = get_filter; +filter.set = set_filter; +filter.string_set_mode = UI_STRING_SET_ON_CHANGE; /* invoke `set` while typing */ +svc_ui->pane_add_control(mod_ctx, pane, &filter, nullptr); + +UiListDesc list = UI_LIST_DESC_INIT; +/* items may be passed as a part of list creation, or set afterwards */ +list.on_pressed = location_pressed; +list.is_selected = location_selected; +svc_ui->pane_add_list(mod_ctx, pane, &list, &locationList); +replace_locations(""); /* calls `list_set_items` */ +``` **Windows:** `window_push` pushes a tabbed two-pane window onto the document stack and shows it. Each tab's `build` receives the window handle plus fresh left and right pane handles on every activation. The optional per-tab `update` @@ -417,10 +559,36 @@ svc_ui->window_push(mod_ctx, &desc, &window); ``` **Dialogs:** `dialog_push` shows a modal dialog. `variant` picks the style, `icon` optionally overrides the variant's -default icon, and actions become buttons. After an action's `on_pressed` returns, the dialog closes unless the action -sets `keep_open`. A `keep_open` action can close it later (or immediately) with `dialog_close`. Cancel fires -`on_dismiss` if present and always closes. `dialog_set_body`, `dialog_set_icon`, and `dialog_add_action` mutate a live -dialog. +default icon, and actions become buttons. The optional `build` callback allows you to add controls to a pane between +the body and actions. It uses the same text, progress, and control builders as panels. + +```cpp +ModResult build_dialog(ModContext*, UiElementHandle pane, void*, ModError*) { + UiControlDesc input = UI_CONTROL_DESC_INIT; + input.kind = UI_CONTROL_STRING; + input.label = "Name"; + input.get = get_name; + input.set = set_name; + return svc_ui->pane_add_control(mod_ctx, pane, &input, nullptr); +} + +UiDialogAction action = UI_DIALOG_ACTION_INIT; +action.label = "Save"; +action.on_pressed = save; +action.is_disabled = is_save_disabled; + +UiDialogDesc dialog = UI_DIALOG_DESC_INIT; +dialog.title = "New Preset"; +dialog.body_rml = "Choose a name for the preset."; +dialog.actions = &action; +dialog.action_count = 1; +dialog.build = build_dialog; +svc_ui->dialog_push(mod_ctx, &dialog, nullptr); +``` + +After an action's `on_pressed`, the dialog closes unless the action sets `keep_open`. It can then be closed later +(or immediately) with `dialog_close`. Cancel fires `on_dismiss` and always closes. `dialog_set_body` and +`dialog_set_icon` mutate a live dialog. **Toasts:** `push_toast` enqueues a notification. Titles and bodies accept RML. The optional `type` is applied as an RCSS class; `warning` uses the built-in warning appearance, and mods can define their own types. A duration of 0 uses @@ -543,6 +711,128 @@ if (svc_camera->get_camera(mod_ctx, game_view, &camera) == MOD_OK) { first in-game frame. Projection matrices match the renderer's WebGPU clip convention and renderer depth convention (reversed-Z by default). +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`) + +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 +the vanilla `gczelda2` save. + +```cpp +IMPORT_SERVICE(LogService, svc_log); +IMPORT_SERVICE(HookService, svc_hook); +IMPORT_SERVICE(GameModeService, svc_game_mode); + +DEFINE_HOOK(fopAcM_createItem, CreateItem); + +#define MY_GAME_MODE_ID "game-mode-id" + +static HookAction my_function_hook(ModContext* ctx, void* args, void*, void*) { + // If we wish to have this hook only run while the gamemode is registered, we need to hook the function from the + // gamemode's onActivatedFunction, and uninstall the hook during the onDeactivatedFunction. Example below. + // Alternatively, check with `svc_game_mode->is_active(mod_ctx, MY_GAME_MODE_ID, &active) == MOD_OK && active`. + return HOOK_CONTINUE; +} + +ModResult on_game_mode_activated(void*, ModError* outError) { + // Setup the gamemode, Add any hooks that are gamemode specific + // Overlay any files that are gamemode specific + ModResult result = mods::hook_add_pre(svc_hook, my_function_hook); + if (result != MOD_OK) { + return mods::set_error(outError, result, "failed to install fopAcM_createItem hook"); + } + return MOD_OK; +} + +ModResult on_game_mode_deactivated(void*, ModError* outError) { + // Uninstall any hooks that are gamemode specific + // Remove any file overlays that are gamemode specific + ModResult result = mods::hook_uninstall(); + if (result != MOD_OK) { + return mods::set_error(outError, result, "failed to uninstall fopAcM_createItem hook"); + } + return MOD_OK; +} + +ModResult on_save_loaded(void*, ModError*) { + // This function will be invoked by the game as a save is loaded + return MOD_OK; +} + +const GameModeDesc gameModeDesc = { + .struct_size = sizeof(GameModeDesc), + .game_mode_id = MY_GAME_MODE_ID, + .full_name = "My Game Mode", + .save_name = "my-unique-save", + .user_data = nullptr, + .on_activated = on_game_mode_activated, + .on_deactivated = on_game_mode_deactivated, + .on_save_loaded = on_save_loaded, +}; +svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc); +``` + +A game mode can also open UI for per-save settings when creating a new file. The state begins as +`GAME_MODE_STATE_PENDING` and remains valid until the mod selects `PROCEED` or `RETURN`. + +```cpp +IMPORT_SERVICE(GameModeService, svc_game_mode); +IMPORT_SERVICE(UiService, svc_ui); + +ModResult on_new_save_select(void*, GameModeNewSaveState* state, ModError* outError) { + static GameModeNewSaveState* newSaveState; + static UiWindowHandle windowHandle; + + newSaveState = state; + + UiTabDesc tabs[1]{}; + + tabs[0].struct_size = sizeof(UiTabDesc); + tabs[0].title = "Play"; + tabs[0].build = [](ModContext* ctx, UiWindowHandle, UiElementHandle leftPane, UiElementHandle rightPane, void*, ModError*) { + UiControlDesc desc = UI_CONTROL_DESC_INIT; + desc.kind = UI_CONTROL_BUTTON; + desc.label = "Play"; + desc.help_rml = "Play Button"; + desc.on_pressed = [](ModContext* ctx, void* userdata) { + *newSaveState = GAME_MODE_STATE_PROCEED; + svc_ui->window_close(ctx, *static_cast(userdata)); + }; + desc.user_data = &windowHandle; + svc_ui->pane_add_control(mod_ctx, leftPane, &desc, nullptr); + return MOD_OK; + }; + + UiWindowDesc desc = UI_WINDOW_DESC_INIT; + desc.tabs = tabs; + desc.tab_count = 1; + desc.on_closed = [](ModContext *, UiWindowHandle, void *userdata) { + // If closing the window through backing out, return to file select + if (*newSaveState == GAME_MODE_STATE_PENDING) { + *newSaveState = GAME_MODE_STATE_RETURN; + } + }; + + ModResult result = svc_ui->window_push(mod_ctx, &desc, &windowHandle); + if (result != MOD_OK) { + return mods::set_error(outError, result, "failed to open new-save settings"); + } + return MOD_OK; +} + +const GameModeDesc gameModeDesc = { + .struct_size = sizeof(GameModeDesc), + .game_mode_id = "my-game-mode-id", + .full_name = "My Game Mode", + .save_name = "my-unique-save", + .on_new_save_select = on_new_save_select, +}; +svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc); + +``` + --- ## Hooking Game Functions @@ -670,6 +960,11 @@ For reference parameters (e.g. `const cXyz& pos`), `arg_ref` yields a dire Files placed under `overlay/` in the `.dusk` archive override game files at the corresponding path, equivalent to replacing files in the .iso. This requires no code: an archive with just `mod.json` and `overlay/` is a complete mod. +To replace a file within an `.arc` archive, replace the archive suffix with a directory and place the replacement at +its path within the archive. + +- `overlay/Audiores/Stream/menu_select.ast` replaces the main title's audio stream. +- `overlay/res/Layout/main2D/main2d/timg/midona64.bti` replaces Midna's UI icon inside `main2D.arc`. Files placed under `textures/` register as texture replacements, and act just like the user's general `texture_replacements/` directory: Dolphin-style naming, matched by texture hash @@ -845,6 +1140,6 @@ const char* nativeDir = svc_host->native_dir(mod_ctx); // read-only ``` Libraries loaded explicitly by the mod remain its responsibility: stop their threads and unload them during -`mod_shutdown`. Do not write into `native_dir`; use `mod_dir` for writable state. Native library namespaces are -process-wide on some platforms, so two mods cannot safely assume that incompatible libraries with the same filename -will remain isolated. +`mod_shutdown`. Do not write into `native_dir`; use `data_dir` for persistent storage or `mod_dir` for temporary +(session) storage. Native library namespaces are process-wide on some platforms, so two mods cannot safely assume that +incompatible libraries with the same filename will remain isolated. diff --git a/extern/aurora b/extern/aurora index 0bddb86249..2c9c95cfed 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 0bddb8624905d74cc47202a71962f9c9834c0933 +Subproject commit 2c9c95cfed9a06610addcb0c00ad5e366e4e8c0d diff --git a/extern/borealis b/extern/borealis new file mode 160000 index 0000000000..6fd955e7e6 --- /dev/null +++ b/extern/borealis @@ -0,0 +1 @@ +Subproject commit 6fd955e7e6a2d2d47c5a4eaf363fb49847e490ba diff --git a/files.cmake b/files.cmake index 443b060eeb..563184aafb 100644 --- a/files.cmake +++ b/files.cmake @@ -244,7 +244,7 @@ set(DOLZEL_FILES src/CaptureScreen.cpp ) if(DEBUG) - list(APPEND DOLZEL_FILES src/d/d_event_debug.cpp) + list(APPEND DOLZEL_FILES src/d/d_event_debug.cpp) endif(DEBUG) set(Z2AUDIOLIB_FILES @@ -1404,14 +1404,15 @@ set(REL_FILES ) set(DOLPHIN_FILES - libs/dolphin/src/gf/GFGeometry.cpp - libs/dolphin/src/gf/GFLight.cpp - libs/dolphin/src/gf/GFPixel.cpp - libs/dolphin/src/gf/GFTev.cpp + libs/dolphin/src/gf/GFGeometry.cpp + libs/dolphin/src/gf/GFLight.cpp + libs/dolphin/src/gf/GFPixel.cpp + libs/dolphin/src/gf/GFTev.cpp ) set(DUSK_FILES include/helpers/batch.hpp + include/helpers/bits.hpp include/helpers/endian_gx.hpp src/d/actor/d_a_alink_dusk.cpp src/dusk/OSContext.cpp @@ -1421,30 +1422,28 @@ set(DUSK_FILES src/dusk/achievements.cpp src/dusk/action_bindings.cpp src/dusk/action_bindings.h - src/dusk/android_frame_rate.cpp - src/dusk/android_frame_rate.hpp src/dusk/asserts.cpp src/dusk/autosave.cpp src/dusk/config.cpp src/dusk/config.hpp - src/dusk/crash_handler.cpp - src/dusk/crash_reporting.cpp src/dusk/data.cpp src/dusk/data.hpp - src/dusk/discord.cpp - src/dusk/discord.hpp src/dusk/discord_presence.cpp src/dusk/dvd_asset.cpp src/dusk/dvd_asset.hpp src/dusk/extras.c - src/dusk/file_select.cpp - src/dusk/file_select.hpp src/dusk/frame_interpolation.cpp + src/dusk/commands.cpp + src/dusk/commands.hpp src/dusk/game_clock.cpp + src/dusk/game_mode.cpp src/dusk/gamepad_color.cpp src/dusk/globals.cpp src/dusk/gyro.cpp - src/dusk/http/http.hpp + src/dusk/game_combos.cpp + src/dusk/trigger_viewer.cpp + #src/dusk/m_Do_ext_dusk.cpp + src/dusk/hq_minimap.cpp src/dusk/imgui/ImGuiActorSpawner.cpp src/dusk/imgui/ImGuiBloomWindow.cpp src/dusk/imgui/ImGuiBloomWindow.hpp @@ -1465,6 +1464,8 @@ set(DUSK_FILES src/dusk/imgui/ImGuiStubLog.cpp src/dusk/io.cpp src/dusk/iso_validate.cpp + src/dusk/language.cpp + src/dusk/language.hpp src/dusk/layout.cpp src/dusk/livesplit.cpp src/dusk/logging.cpp @@ -1481,6 +1482,9 @@ set(DUSK_FILES src/dusk/mods/loader/native_module.hpp src/dusk/mods/loader/prepatch.cpp src/dusk/mods/loader/prepatch.hpp + src/dusk/mods/item.hpp + src/dusk/mods/item_checks.cpp + src/dusk/mods/item_gives.cpp src/dusk/mods/log_buffer.cpp src/dusk/mods/log_buffer.hpp src/dusk/mods/manifest.cpp @@ -1498,8 +1502,11 @@ set(DUSK_FILES src/dusk/mods/svc/config.hpp src/dusk/mods/svc/game.cpp src/dusk/mods/svc/gfx.cpp + src/dusk/mods/svc/flow.cpp src/dusk/mods/svc/hook.cpp src/dusk/mods/svc/host.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 @@ -1510,9 +1517,16 @@ set(DUSK_FILES src/dusk/mods/svc/texture.cpp src/dusk/mods/svc/ui.cpp src/dusk/mods/svc/ui.hpp + src/dusk/mods/svc/game_mode.cpp src/dusk/mods/svc/window.cpp src/dusk/mods/svc/window.hpp + src/dusk/mods/svc/save.cpp + src/dusk/mods/svc/save.hpp + src/dusk/mods/svc/stage.cpp + src/dusk/mods/svc/stage.hpp src/dusk/mouse.cpp + src/dusk/presentation.cpp + src/dusk/presentation.hpp src/dusk/scope_guard.hpp src/dusk/settings.cpp src/dusk/speedrun.cpp @@ -1522,10 +1536,14 @@ set(DUSK_FILES src/dusk/touch_camera.cpp src/dusk/ui/achievements.cpp src/dusk/ui/achievements.hpp + src/dusk/ui/command_console.cpp + src/dusk/ui/command_console.hpp src/dusk/ui/bool_button.cpp src/dusk/ui/bool_button.hpp src/dusk/ui/button.cpp src/dusk/ui/button.hpp + src/dusk/ui/color_input.cpp + src/dusk/ui/color_input.hpp src/dusk/ui/component.cpp src/dusk/ui/component.hpp src/dusk/ui/controller_config.cpp @@ -1539,12 +1557,16 @@ set(DUSK_FILES src/dusk/ui/event.hpp src/dusk/ui/graphics_tuner.cpp src/dusk/ui/graphics_tuner.hpp + src/dusk/ui/group_button.cpp + src/dusk/ui/group_button.hpp src/dusk/ui/icon_provider.cpp src/dusk/ui/icon_provider.hpp src/dusk/ui/input.cpp src/dusk/ui/input.hpp src/dusk/ui/logs_window.cpp src/dusk/ui/logs_window.hpp + src/dusk/ui/list.cpp + src/dusk/ui/list.hpp src/dusk/ui/menu_bar.cpp src/dusk/ui/menu_bar.hpp src/dusk/ui/mod_texture_provider.cpp @@ -1556,12 +1578,16 @@ set(DUSK_FILES 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/number_button.cpp src/dusk/ui/number_button.hpp src/dusk/ui/overlay.cpp src/dusk/ui/overlay.hpp src/dusk/ui/pane.cpp src/dusk/ui/pane.hpp + src/dusk/ui/popover.cpp + src/dusk/ui/popover.hpp src/dusk/ui/prelaunch.cpp src/dusk/ui/prelaunch.hpp src/dusk/ui/preset.cpp @@ -1588,19 +1614,11 @@ set(DUSK_FILES src/dusk/ui/warp.hpp src/dusk/ui/window.cpp src/dusk/ui/window.hpp - src/dusk/update_check.cpp - src/dusk/update_check.hpp 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 ) - -set(DUSK_HTTP_BACKEND_FILES - src/dusk/http/no_backend.cpp - src/dusk/http/curl.cpp - src/dusk/http/winhttp.cpp - src/dusk/http/url_session.mm -) diff --git a/flake.nix b/flake.nix index c470446113..01a20fad2d 100644 --- a/flake.nix +++ b/flake.nix @@ -228,7 +228,7 @@ ninjaFlags = [ "dusklight" ]; cmakeFlags = [ - "-DDUSK_VERSION_OVERRIDE=${versionSuffix}" + "-DBOREALIS_APP_VERSION_OVERRIDE=${versionSuffix}" "-DFETCHCONTENT_FULLY_DISCONNECTED=ON" "-DAURORA_DAWN_PROVIDER=package" "-DAURORA_DAWN_LINKAGE=static" diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index 8728bc16a8..384891c8d5 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -8449,8 +8449,10 @@ struct daAlink_cutHorseParamTbl { /* 0xA */ u8 m_cutType; }; // Size: 0xC +#if !TARGET_PC inline BOOL dComIfGs_isTransformLV(int i_no); inline BOOL dComIfGs_isEventBit(const u16); +#endif static fopAc_ac_c* daAlink_searchPortal(fopAc_ac_c* i_actor, void* i_data); static fopAc_ac_c* daAlink_searchCanoe(fopAc_ac_c* i_actor, void* i_data); diff --git a/include/d/actor/d_a_e_hp.h b/include/d/actor/d_a_e_hp.h index 8941f1e2a6..86b092f915 100644 --- a/include/d/actor/d_a_e_hp.h +++ b/include/d/actor/d_a_e_hp.h @@ -96,6 +96,9 @@ public: /* 0xDE0 */ cXyz field_0xde0; /* 0xDEC */ u8 field_0xdec[0xdf9 - 0xdec]; /* 0xDF9 */ u8 field_0xdf9; +#if TARGET_PC + bool mItemCheckHandled; +#endif }; STATIC_ASSERT(sizeof(daE_HP_c) == 0xdfc); diff --git a/include/d/actor/d_a_itembase.h b/include/d/actor/d_a_itembase.h index da1f541cd7..374e4b7df2 100644 --- a/include/d/actor/d_a_itembase.h +++ b/include/d/actor/d_a_itembase.h @@ -32,6 +32,12 @@ struct daItemBase_data { class daItemBase_c : public fopAc_ac_c { public: u8 getItemNo(); +#if TARGET_PC + u8 getDisplayItemNo() const { + return mDisplayItemNo != 0xFF ? mDisplayItemNo : m_itemNo; + } + void setDisplayItemNo(u8 itemNo) { mDisplayItemNo = itemNo; } +#endif void hide(); void show(); void changeDraw(); @@ -79,6 +85,9 @@ public: /* 0x928 */ s16 m_get_timer; /* 0x92A */ u8 m_itemNo; /* 0x92B */ u8 field_0x92b; +#if TARGET_PC + u8 mDisplayItemNo = 0xFF; +#endif }; #endif /* D_A_D_A_ITEMBASE_H */ diff --git a/include/d/actor/d_a_midna.h b/include/d/actor/d_a_midna.h index 7793179066..2b07386ca7 100644 --- a/include/d/actor/d_a_midna.h +++ b/include/d/actor/d_a_midna.h @@ -423,7 +423,7 @@ public: static DUSK_GAME_DATA daMidna_texData_s const m_texDataTable[21]; static DUSK_GAME_DATA daMidna_anmData_s const m_anmDataTable[53]; -private: +// private: /* 0x568 */ request_of_phase_process_class mPhase; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ J3DModel* mpShadowModel; diff --git a/include/d/actor/d_a_obj_life_container.h b/include/d/actor/d_a_obj_life_container.h index 14187d8289..d832c63843 100644 --- a/include/d/actor/d_a_obj_life_container.h +++ b/include/d/actor/d_a_obj_life_container.h @@ -88,6 +88,12 @@ private: /* 0x99C */ dPa_followEcallBack mEffect2; /* 0x9B0 */ Z2SoundObjSimple mSound; /* 0x9D0 */ u8 mIsHookCarry; +#if TARGET_PC + u8 mOriginalItemNo; + bool mItemOverridden; + bool mOverrideHover; + bool mGoldenWolfItem; +#endif }; STATIC_ASSERT(sizeof(daObjLife_c) == 0x9d4); diff --git a/include/d/actor/d_a_player.h b/include/d/actor/d_a_player.h index 66139b54cf..ce6158fcf7 100644 --- a/include/d/actor/d_a_player.h +++ b/include/d/actor/d_a_player.h @@ -311,9 +311,11 @@ private: class daMidna_c; class daSpinner_c; class daPy_py_c; +#if !TARGET_PC inline daPy_py_c* dComIfGp_getLinkPlayer(); inline BOOL dComIfGs_isEventBit(const u16); inline u32 dComIfGs_getLastSceneMode(); +#endif class daPy_py_c : public fopAc_ac_c { public: diff --git a/include/d/actor/d_a_tbox.h b/include/d/actor/d_a_tbox.h index 2b48a156eb..6d0528d29d 100644 --- a/include/d/actor/d_a_tbox.h +++ b/include/d/actor/d_a_tbox.h @@ -194,6 +194,9 @@ private: /* 0x9FC */ u8 field_0x9fc; /* 0x9FD */ u8 field_0x9fd; /* 0xA00 */ Mtx mDrawMtx; +#if TARGET_PC + u8 mOriginalItemNo; +#endif }; STATIC_ASSERT(sizeof(daTbox_c) == 0xA30); diff --git a/include/d/actor/d_a_tbox2.h b/include/d/actor/d_a_tbox2.h index 3f176ade5e..9b06e685a3 100644 --- a/include/d/actor/d_a_tbox2.h +++ b/include/d/actor/d_a_tbox2.h @@ -83,6 +83,10 @@ private: /* 0xAC0 */ u8 field_0xAC0[0xAC4 - 0xAC0]; /* 0xAC4 */ int mStaffIdx; /* 0xAC8 */ dBgW* mBoxBgW; +#if TARGET_PC + bool mParamsInit; + u8 mOriginalItemNo; +#endif }; STATIC_ASSERT(sizeof(daTbox2_c) == 0xACC); diff --git a/include/d/d_a_item_static.h b/include/d/d_a_item_static.h index 36c445d511..6b8b321b2b 100644 --- a/include/d/d_a_item_static.h +++ b/include/d/d_a_item_static.h @@ -145,6 +145,10 @@ public: /* 0x9BC */ u8 field_0x9bc[4]; /* 0x9C0 */ u8 field_0x9c0; /* 0x9C1 */ u8 field_0x9c1; +#if TARGET_PC + u8 mOriginalItemNo; + bool mItemOverridden; +#endif }; // Size: 0x9C4 #endif /* D_A_D_A_ITEM_STATIC_H */ diff --git a/include/d/d_a_itembase_static.h b/include/d/d_a_itembase_static.h index 36a0f55899..38ecb323eb 100644 --- a/include/d/d_a_itembase_static.h +++ b/include/d/d_a_itembase_static.h @@ -1,10 +1,15 @@ #ifndef D_A_D_A_ITEMBASE_STATIC_H #define D_A_D_A_ITEMBASE_STATIC_H +#include "dolphin/types.h" class fopAc_ac_c; int CheckFieldItemCreateHeap(fopAc_ac_c* actor); int CheckItemCreateHeap(fopAc_ac_c* i_this); +#if TARGET_PC +const char* dItem_fieldModelArc(u8 itemNo); +#endif + #endif /* D_A_D_A_ITEMBASE_STATIC_H */ diff --git a/include/d/d_a_shop_item_static.h b/include/d/d_a_shop_item_static.h index eeda9185da..7126578092 100644 --- a/include/d/d_a_shop_item_static.h +++ b/include/d/d_a_shop_item_static.h @@ -50,6 +50,9 @@ public: csXyz* getRotateP(); cXyz* getPosP(); const char* getShopArcname(); +#if TARGET_PC + const ResourceData& getResourceData() const; +#endif u16 getHeapSize(); void CreateInit(); void set_mtx(); @@ -110,6 +113,10 @@ private: /* 0x960 */ s16 mAngleX; /* 0x962 */ s16 mAngleY; /* 0x964 */ u8 mShopItemID; +#if TARGET_PC + ResourceData mOverrideData; + bool mItemOverridden; +#endif }; int CheckShopItemCreateHeap(fopAc_ac_c* i_this); diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h index a598cbcc10..62b8744b51 100644 --- a/include/d/d_com_inf_game.h +++ b/include/d/d_com_inf_game.h @@ -20,11 +20,6 @@ #if defined(DUSK_BUILDING_GAME) #include #include "dusk/settings.h" -#else -#ifndef ZoneScoped -#define ZoneScoped -#define ZoneScopedN(name) -#endif #endif enum dComIfG_ButtonStatus { @@ -1276,6 +1271,938 @@ int dComIfGd_setShadow(u32 param_0, s8 param_1, J3DModel* param_2, cXyz* param_3 f32 param_5, f32 param_6, f32 param_7, cBgS_PolyInfo& param_8, dKy_tevstr_c* param_9, s16 param_10, f32 param_11, TGXTexObj* param_12); +#if TARGET_PC +DUSK_NOINLINE dSv_info_c* dComIfGs_getSaveInfo(); +DUSK_NOINLINE dSv_save_c* dComIfGs_getSaveData(); +DUSK_NOINLINE u16 dComIfGs_getMaxLife(); +DUSK_NOINLINE void dComIfGs_setMaxLife(u8 i_maxLife); +DUSK_NOINLINE u16 dComIfGs_getLife(); +DUSK_NOINLINE void dComIfGs_setLife(u16 i_life); +DUSK_NOINLINE u16 dComIfGs_getRupee(); +DUSK_NOINLINE void dComIfGs_setRupee(u16 i_rupees); +DUSK_NOINLINE u16 dComIfGs_getMaxOil(); +DUSK_NOINLINE void dComIfGs_setMaxOil(u16 i_maxOil); +DUSK_NOINLINE u16 dComIfGs_getOil(); +DUSK_NOINLINE void dComIfGs_setOil(u16 i_oil); +DUSK_NOINLINE u8 dComIfGs_getSelectEquipClothes(); +DUSK_NOINLINE u8 dComIfGs_getSelectEquipSword(); +DUSK_NOINLINE u8 dComIfGs_getSelectEquipShield(); +DUSK_NOINLINE u8 dComIfGs_getCollectSmell(); +DUSK_NOINLINE void dComIfGs_setCollectSmell(u8 smell); +DUSK_NOINLINE u8 dComIfGs_getBButtonItemKey(); +DUSK_NOINLINE void dComIfGs_setBButtonItemKey(u8 i_itemNo); +DUSK_NOINLINE u8 dComIfGs_getWalletSize(); +DUSK_NOINLINE void dComIfGs_setWalletSize(u8 i_size); +DUSK_NOINLINE u8 dComIfGs_getMaxMagic(); +DUSK_NOINLINE void dComIfGs_setMaxMagic(u8 i_maxMagic); +DUSK_NOINLINE u8 dComIfGs_getMagic(); +DUSK_NOINLINE void dComIfGs_setMagic(u8 i_magic); +DUSK_NOINLINE u8 dComIfGs_getTransformStatus(); +DUSK_NOINLINE void dComIfGs_setTransformStatus(u8 i_status); +DUSK_NOINLINE u8 dComIfGs_getSelectItemIndex(int i_no); +DUSK_NOINLINE u16 dComIfGs_getRupeeMax(); +DUSK_NOINLINE void dComIfGs_offGetMagicUseFlag(); +DUSK_NOINLINE s32 dComIfGs_isGetMagicUseFlag(); +DUSK_NOINLINE f32 dComIfGs_getTime(); +DUSK_NOINLINE void dComIfGs_setTime(f32 i_time); +DUSK_NOINLINE u16 dComIfGs_getDate(); +DUSK_NOINLINE void dComIfGs_setDate(u16 i_date); +DUSK_NOINLINE void dComIfGs_onDarkClearLV(int i_no); +DUSK_NOINLINE void dComIfGs_offDarkClearLV(int i_no); +DUSK_NOINLINE BOOL dComIfGs_isDarkClearLV(int i_no); +DUSK_NOINLINE void dComIfGs_onTransformLV(int i_no); +DUSK_NOINLINE void dComIfGs_offTransformLV(int i_no); +DUSK_NOINLINE BOOL dComIfGs_isTransformLV(int i_no); +DUSK_NOINLINE cXyz dComIfGs_getHorseRestartPos(); +DUSK_NOINLINE s16 dComIfGs_getHorseRestartAngleY(); +DUSK_NOINLINE const char* dComIfGs_getHorseRestartStageName(); +DUSK_NOINLINE s8 dComIfGs_getHorseRestartRoomNo(); +DUSK_NOINLINE void dComIfGs_setHorseRestart( + const char* i_stageName, cXyz& i_pos, s16 i_angle, s8 i_roomNo); +DUSK_NOINLINE cXyz dComIfGs_getPlayerFieldLastStayPos(); +DUSK_NOINLINE s16 dComIfGs_getPlayerFieldLastStayAngleY(); +DUSK_NOINLINE char* dComIfGs_getPlayerFieldLastStayName(); +DUSK_NOINLINE u8 dComIfGs_getPlayerFieldLastStayRegionNo(); +DUSK_NOINLINE bool dComIfGs_isPlayerFieldLastStayFieldDataExistFlag(); +DUSK_NOINLINE void dComIfGs_offPlayerFieldLastStayFieldDataExistFlag(); +DUSK_NOINLINE void dComIfGs_onPlayerFieldLastStayFieldDataExistFlag(); +DUSK_NOINLINE BOOL dComIfGs_isRegionBit(int i_region); +DUSK_NOINLINE void dComIfGs_onRegionBit(int i_region); +DUSK_NOINLINE void dComIfGs_setPlayerFieldLastStayInfo( + const char* i_stage, cXyz& i_pos, s16 i_angle, s8 i_point, u8 i_region); +DUSK_NOINLINE cXyz dComIfGs_getLastWarpMarkPlayerPos(); +DUSK_NOINLINE s16 dComIfGs_getLastWarpMarkPlayerAngleY(); +DUSK_NOINLINE const char* dComIfGs_getLastWarpMarkStageName(); +DUSK_NOINLINE u8 dComIfGs_getLastWarpMarkRoomNo(); +DUSK_NOINLINE s8 dComIfGs_getLastWarpAcceptStage(); +DUSK_NOINLINE void dComIfGs_setLastWarpAcceptStage(s8 param_0); +DUSK_NOINLINE void dComIfGs_resetLastWarpAcceptStage(); +DUSK_NOINLINE void dComIfGs_setItem(int i_slotNo, u8 i_itemNo); +DUSK_NOINLINE u8 dComIfGs_getItem(int i_slotNo, bool i_checkCombo); +DUSK_NOINLINE void dComIfGs_setLineUpItem(); +DUSK_NOINLINE u8 dComIfGs_getLineUpItem(int i_slotNo); +DUSK_NOINLINE void dComIfGs_setBottleItemIn(u8 curItem, u8 newItem); +DUSK_NOINLINE void dComIfGs_setEmptyBottleItemIn(u8 i_itemNo); +DUSK_NOINLINE void dComIfGs_setEmptyBottle(); +DUSK_NOINLINE void dComIfGs_setEmptyBottle(u8 i_itemNo); +DUSK_NOINLINE void dComIfGs_setEquipBottleItemIn(u8 i_curItem, u8 i_newItem); +DUSK_NOINLINE void dComIfGs_setEquipBottleItemEmpty(u8 i_curItem); +DUSK_NOINLINE u8 dComIfGs_checkBottle(u8 i_itemNo); +DUSK_NOINLINE u8 dComIfGs_checkInsectBottle(); +DUSK_NOINLINE u8 dComIfGs_checkEmptyBottle(); +DUSK_NOINLINE void dComIfGs_setEmptyBombBagItemIn(u8 i_newBomb, bool i_setNum); +DUSK_NOINLINE void dComIfGs_setEmptyBombBagItemIn(u8 i_newBomb, u8 i_bombNum, bool i_setNum); +DUSK_NOINLINE void dComIfGs_setEmptyBombBag(); +DUSK_NOINLINE void dComIfGs_setEmptyBombBag(u8 i_newBomb, u8 i_bombNum); +DUSK_NOINLINE u8 dComIfGs_checkBombBag(u8 i_itemNo); +DUSK_NOINLINE void dComIfGs_setWarashibeItem(u8 i_itemNo); +DUSK_NOINLINE void dComIfGs_setRodTypeLevelUp(); +DUSK_NOINLINE void dComIfGs_setBaitItem(u8 i_item); +DUSK_NOINLINE void dComIfGs_onItemFirstBit(u8 i_itemNo); +DUSK_NOINLINE void dComIfGs_offItemFirstBit(u8 i_itemNo); +DUSK_NOINLINE int dComIfGs_isItemFirstBit(u8 i_no); +DUSK_NOINLINE u8 dComIfGs_getArrowNum(); +DUSK_NOINLINE void dComIfGs_setArrowNum(u8 i_arrowNum); +DUSK_NOINLINE u8 dComIfGs_getPachinkoNum(); +DUSK_NOINLINE void dComIfGs_setPachinkoNum(u8 i_num); +DUSK_NOINLINE u8 dComIfGs_getPachinkoMax(); +DUSK_NOINLINE void dComIfGs_setBombNum(u8 i_num); +DUSK_NOINLINE void dComIfGs_setBombNum(u8 i_bagIdx, u8 i_bombNum); +DUSK_NOINLINE u8 dComIfGs_getBombNum(u8 i_bagIdx); +DUSK_NOINLINE void dComIfGs_setBottleNum(u8 i_bottleIdx, u8 i_bottleNum); +DUSK_NOINLINE void dComIfGs_addBottleNum(u8 i_bottleIdx, s16 i_num); +DUSK_NOINLINE u8 dComIfGs_getBottleNum(u8 i_bottleIdx); +DUSK_NOINLINE u8 dComIfGs_getArrowMax(); +DUSK_NOINLINE void dComIfGs_setArrowMax(u8 i_arrowMax); +DUSK_NOINLINE void dComIfGs_setBombMax(u8 i_max); +DUSK_NOINLINE void dComIfGs_setBombMax(u8 i_type, u8 i_max); +DUSK_NOINLINE u8 dComIfGs_getBombMax(); +DUSK_NOINLINE u8 dComIfGs_getBombMax(u8 i_bombType); +DUSK_NOINLINE void dComIfGs_setPohSpiritNum(u8 i_num); +DUSK_NOINLINE u8 dComIfGs_getPohSpiritNum(); +DUSK_NOINLINE void dComIfGs_addPohSpiritNum(); +DUSK_NOINLINE BOOL dComIfGs_isCollectClothes(u8 i_clothesNo); +DUSK_NOINLINE void dComIfGs_setCollectClothes(u8 i_clothesNo); +DUSK_NOINLINE void dComIfGs_setCollectSword(u8 i_swordNo); +DUSK_NOINLINE void dComIfGs_setCollectShield(u8 i_shieldNo); +DUSK_NOINLINE BOOL dComIfGs_isCollectClothing(u8 i_clothesNo); +DUSK_NOINLINE void dComIfGs_offCollectClothes(u8 i_clothesNo); +DUSK_NOINLINE BOOL dComIfGs_isCollectSword(u8 i_swordNo); +DUSK_NOINLINE void dComIfGs_offCollectSword(u8 i_swordNo); +DUSK_NOINLINE BOOL dComIfGs_isCollectShield(u8 i_item); +DUSK_NOINLINE void dComIfGs_offCollectShield(u8 i_shieldNo); +DUSK_NOINLINE void dComIfGs_onCollectCrystal(u8 i_item); +DUSK_NOINLINE void dComIfGs_offCollectCrystal(u8 i_item); +DUSK_NOINLINE bool dComIfGs_isCollectCrystal(u8 i_item); +DUSK_NOINLINE void dComIfGs_onCollectMirror(u8 i_item); +DUSK_NOINLINE void dComIfGs_offCollectMirror(u8 i_item); +DUSK_NOINLINE bool dComIfGs_isCollectMirror(u8 i_item); +DUSK_NOINLINE void dComIfGs_setLightDropNum(u8 i_level, u8 i_num); +DUSK_NOINLINE u8 dComIfGs_getLightDropNum(u8 i_nowLevel); +DUSK_NOINLINE void dComIfGs_onLightDropGetFlag(u8 i_nowLevel); +DUSK_NOINLINE void dComIfGs_offLightDropGetFlag(u8 i_level); +DUSK_NOINLINE BOOL dComIfGs_isLightDropGetFlag(u8 i_nowLevel); +DUSK_NOINLINE void dComIfGs_onLetterGetFlag(int i_no); +DUSK_NOINLINE BOOL dComIfGs_isLetterGetFlag(int i_no); +DUSK_NOINLINE void dComIfGs_onLetterReadFlag(int i_no); +DUSK_NOINLINE int dComIfGs_isLetterReadFlag(int i_no); +DUSK_NOINLINE u8 dComIfGs_getGetNumber(int i_no); +DUSK_NOINLINE void dComIfGs_setGetNumber(int i_no, u8 i_value); +DUSK_NOINLINE void dComIfGs_addFishNum(u8 param_0); +DUSK_NOINLINE u16 dComIfGs_getFishNum(u8 param_0); +DUSK_NOINLINE u8 dComIfGs_getFishSize(u8 param_0); +DUSK_NOINLINE void dComIfGs_setFishSize(u8 param_0, u8 param_1); +DUSK_NOINLINE OSTime dComIfGs_getTotalTime(); +DUSK_NOINLINE void dComIfGs_addDeathCount(); +DUSK_NOINLINE u16 dComIfGs_getDeathCount(); +DUSK_NOINLINE TEXT_SPAN dComIfGs_getPlayerName(); +DUSK_NOINLINE void dComIfGs_setPlayerName(const char* i_name); +DUSK_NOINLINE TEXT_SPAN dComIfGs_getHorseName(); +DUSK_NOINLINE void dComIfGs_setHorseName(const char* i_name); +DUSK_NOINLINE u8 dComIfGs_getClearCount(); + +// Ruby inline names are from TWW debug. +DUSK_NOINLINE u8 dComIfGs_getOptRuby(); +DUSK_NOINLINE void dComIfGs_setOptRuby(u8 i_ruby); +DUSK_NOINLINE u8 dComIfGs_getOptAttentionType(); +DUSK_NOINLINE void dComIfGs_setOptAttentionType(u8 i_attentionType); +DUSK_NOINLINE void dComIfGs_setOptCalibrateDist(u16 i_calibrateDist); +DUSK_NOINLINE void dComIfGs_setOptCalValue(s8 i_calValue); +DUSK_NOINLINE u8 dComIfGs_getOptCameraControl(); +DUSK_NOINLINE void dComIfGs_setOptCameraControl(u8 i_cameraControl); +DUSK_NOINLINE u8 dComIfGs_getOptPointer(); +DUSK_NOINLINE void dComIfGs_setOptPointer(u8 i_pointer); +DUSK_NOINLINE u8 dComIfGs_checkOptVibration(); +DUSK_NOINLINE u8 dComIfGs_getOptSound(); +DUSK_NOINLINE void dComIfGs_setOptSound(u8 i_mode); +DUSK_NOINLINE u8 dComIfGs_getOptVibration(); +DUSK_NOINLINE void dComIfGs_setOptVibration(u8 i_status); +DUSK_NOINLINE u8 dComIfGs_getPalLanguage(); +DUSK_NOINLINE BOOL dComIfGs_isSaveTbox(int i_stageNo, int i_no); +DUSK_NOINLINE void dComIfGs_onSaveSwitch(int i_stageNo, int i_no); +DUSK_NOINLINE void dComIfGs_offSaveSwitch(int i_stageNo, int i_no); +DUSK_NOINLINE BOOL dComIfGs_isSaveSwitch(int i_stageNo, int i_no); +DUSK_NOINLINE void dComIfGs_onSaveVisitedRoom(int param_0, int i_roomNo); +DUSK_NOINLINE void dComIfGs_offSaveVisitedRoom(int param_0, int i_roomNo); +DUSK_NOINLINE BOOL dComIfGs_isSaveVisitedRoom(int param_0, int i_roomNo); +DUSK_NOINLINE void* dComIfGs_getPEventBit(); +DUSK_NOINLINE void dComIfGs_onEventBit(const u16 i_flag); +DUSK_NOINLINE void dComIfGs_offEventBit(const u16 i_flag); +DUSK_NOINLINE BOOL dComIfGs_isEventBit(const u16 i_flag); +DUSK_NOINLINE void dComIfGs_setEventReg(u16 reg, u8 value); +DUSK_NOINLINE u8 dComIfGs_getEventReg(u16 reg); +DUSK_NOINLINE int dComIfGs_getHookGameTime(); +DUSK_NOINLINE void dComIfGs_setHookGameTime(u32 i_time); +DUSK_NOINLINE u32 dComIfGs_getBalloonScore(); +DUSK_NOINLINE void dComIfGs_setBalloonScore(u32 i_score); +DUSK_NOINLINE int dComIfGs_getRaceGameTime(); +DUSK_NOINLINE void dComIfGs_setRaceGameTime(int i_time); +DUSK_NOINLINE u8 dComIfGs_getKeyNum(); +DUSK_NOINLINE void dComIfGs_setKeyNum(u8 i_keyNum); +DUSK_NOINLINE void dComIfGs_onDungeonItemMap(); +DUSK_NOINLINE void dComIfGs_offDungeonItemMap(); +DUSK_NOINLINE s32 dComIfGs_isDungeonItemMap(); +DUSK_NOINLINE void dComIfGs_onDungeonItemCompass(); +DUSK_NOINLINE void dComIfGs_offDungeonItemCompass(); +DUSK_NOINLINE s32 dComIfGs_isDungeonItemCompass(); +DUSK_NOINLINE void dComIfGs_onDungeonItemBossKey(); +DUSK_NOINLINE void dComIfGs_offDungeonItemBossKey(); +DUSK_NOINLINE s32 dComIfGs_isDungeonItemBossKey(); +DUSK_NOINLINE void dComIfGs_onStageBossEnemy(); +DUSK_NOINLINE void dComIfGs_offStageBossEnemy(); +DUSK_NOINLINE s32 dComIfGs_isStageBossEnemy(); +DUSK_NOINLINE void dComIfGs_onStageLife(); +DUSK_NOINLINE void dComIfGs_offStageLife(); +DUSK_NOINLINE s32 dComIfGs_isStageLife(); +DUSK_NOINLINE void dComIfGs_onStageBossDemo(); +DUSK_NOINLINE void dComIfGs_offStageBossDemo(); +DUSK_NOINLINE s32 dComIfGs_isStageBossDemo(); +DUSK_NOINLINE void dComIfGs_onDungeonItemWarp(); +DUSK_NOINLINE void dComIfGs_offDungeonItemWarp(); +DUSK_NOINLINE s32 dComIfGs_isDungeonItemWarp(); +DUSK_NOINLINE void dComIfGs_onStageMiddleBoss(); +DUSK_NOINLINE void dComIfGs_offStageMiddleBoss(); +DUSK_NOINLINE BOOL dComIfGs_isStageMiddleBoss(); +DUSK_NOINLINE void dComIfGs_onTbox(int i_no); +DUSK_NOINLINE void dComIfGs_offTbox(int i_no); +DUSK_NOINLINE BOOL dComIfGs_isTbox(int i_no); +DUSK_NOINLINE void dComIfGs_onSaveSwitch(int i_no); +DUSK_NOINLINE void dComIfGs_offSaveSwitch(int i_no); +DUSK_NOINLINE BOOL dComIfGs_isSaveSwitch(int i_no); +DUSK_NOINLINE BOOL dComIfGs_isSaveItem(int i_no); +DUSK_NOINLINE void dComIfGs_onSaveDunSwitch(int i_swNo); +DUSK_NOINLINE void dComIfGs_offSaveDunSwitch(int i_swNo); +DUSK_NOINLINE BOOL dComIfGs_isSaveDunSwitch(int i_no); +DUSK_NOINLINE BOOL dComIfGs_isSaveDunItem(int i_no); +DUSK_NOINLINE void dComIfGs_resetDan(); +DUSK_NOINLINE void dComIfGs_initDan(s8 i_stageNo); +DUSK_NOINLINE void dComIfGs_clearRoomSwitch(int i_zoneNo); +DUSK_NOINLINE void dComIfGs_clearRoomItem(int i_zoneNo); +DUSK_NOINLINE void dComIfGs_onSvZoneSwitch(int i_zoneNo, int i_swBit); +DUSK_NOINLINE void dComIfGs_offSvZoneSwitch(int i_zoneNo, int i_swBit); +DUSK_NOINLINE BOOL dComIfGs_isSvZoneSwitch(int i_zoneNo, int i_swBit); +DUSK_NOINLINE void dComIfGs_onSvOneZoneSwitch(int i_zoneNo, int i_swBit); +DUSK_NOINLINE void dComIfGs_offSvOneZoneSwitch(int i_zoneNo, int i_swBit); +DUSK_NOINLINE BOOL dComIfGs_isSvOneZoneSwitch(int i_zoneNo, int i_swBit); +DUSK_NOINLINE void dComIfGs_onSvZoneItem(int i_zoneNo, int i_swBit); +DUSK_NOINLINE void dComIfGs_offSvZoneItem(int i_zoneNo, int i_swBit); +DUSK_NOINLINE BOOL dComIfGs_isSvZoneItem(int i_zoneNo, int i_swBit); +DUSK_NOINLINE void dComIfGs_onSvOneZoneItem(int i_zoneNo, int i_swBit); +DUSK_NOINLINE void dComIfGs_offSvOneZoneItem(int i_zoneNo, int i_swBit); +DUSK_NOINLINE BOOL dComIfGs_isSvOneZoneItem(int i_zoneNo, int i_swBit); +DUSK_NOINLINE void dComIfGs_removeZone(int i_zoneNo); +DUSK_NOINLINE s8 dComIfGs_getRestartRoomNo(); +DUSK_NOINLINE s16 dComIfGs_getStartPoint(); +DUSK_NOINLINE void dComIfGs_setStartPoint(s16 i_point); +DUSK_NOINLINE s16 dComIfGs_getRestartRoomAngleY(); +DUSK_NOINLINE const cXyz& dComIfGs_getRestartRoomPos(); +DUSK_NOINLINE u32 dComIfGs_getRestartRoomParam(); +DUSK_NOINLINE void dComIfGs_setRestartRoomParam(u32 i_param); +DUSK_NOINLINE f32 dComIfGs_getLastSceneSpeedF(); +DUSK_NOINLINE u32 dComIfGs_getLastSceneMode(); +DUSK_NOINLINE s16 dComIfGs_getLastSceneAngleY(); +DUSK_NOINLINE void dComIfGs_setRestartRoom(const cXyz& i_position, s16 i_angle, s8 i_roomNo); +DUSK_NOINLINE void dComIfGs_onTmpBit(const u16 i_flag); +DUSK_NOINLINE void dComIfGs_offTmpBit(const u16 i_flag); +DUSK_NOINLINE BOOL dComIfGs_isTmpBit(const u16 i_flag); +DUSK_NOINLINE void dComIfGs_setTmpReg(u16 i_reg, u8 i_no); +DUSK_NOINLINE u8 dComIfGs_getTmpReg(const u16 i_reg); +DUSK_NOINLINE dSv_turnRestart_c& dComIfGs_getTurnRestart(); +DUSK_NOINLINE const cXyz& dComIfGs_getTurnRestartPos(); +DUSK_NOINLINE u32 dComIfGs_getTurnRestartParam(); +DUSK_NOINLINE s16 dComIfGs_getTurnRestartAngleY(); +DUSK_NOINLINE s8 dComIfGs_getTurnRestartRoomNo(); +DUSK_NOINLINE void dComIfGs_setTurnRestart( + const cXyz& i_position, s16 i_angle, s8 param_2, u32 i_param); +DUSK_NOINLINE u8 dComIfGs_getDataNum(); +DUSK_NOINLINE void dComIfGs_setDataNum(u8 i_num); +DUSK_NOINLINE u8 dComIfGs_getNewFile(); +DUSK_NOINLINE void dComIfGs_setNewFile(u8 i_fileNo); +DUSK_NOINLINE u8 dComIfGs_getNoFile(); +DUSK_NOINLINE void dComIfGs_setNoFile(u8 i_file); +DUSK_NOINLINE s64 dComIfGs_getSaveStartTime(); +DUSK_NOINLINE void dComIfGs_setSaveStartTime(s64 i_time); +DUSK_NOINLINE s64 dComIfGs_getSaveTotalTime(); +DUSK_NOINLINE void dComIfGs_setSaveTotalTime(s64 i_time); +DUSK_NOINLINE void dComIfGs_init(); +DUSK_NOINLINE void dComIfGs_getSave(int i_stageNo); +DUSK_NOINLINE void dComIfGs_putSave(int i_stageNo); +DUSK_NOINLINE void dComIfGs_initZone(); +DUSK_NOINLINE int dComIfGs_createZone(int roomNo); +DUSK_NOINLINE void dComIfGs_onSwitch(int i_no, int i_roomNo); +DUSK_NOINLINE void dComIfGs_offSwitch(int i_no, int i_roomNo); +DUSK_NOINLINE BOOL dComIfGs_isSwitch(int i_no, int i_roomNo); +DUSK_NOINLINE void dComIfGs_revSwitch(int i_no, int i_roomNo); +DUSK_NOINLINE void dComIfGs_onItem(int i_bitNo, int i_roomNo); +DUSK_NOINLINE bool dComIfGs_isItem(int i_bitNo, int i_roomNo); +DUSK_NOINLINE void dComIfGs_onActor(int i_bitNo, int i_roomNo); +DUSK_NOINLINE void dComIfGs_offActor(int i_no, int i_roomNo); +DUSK_NOINLINE BOOL dComIfGs_isActor(int i_no, int i_roomNo); +DUSK_NOINLINE void dComIfGs_setMemoryToCard(u8* i_saveData, int i_dataNum); +DUSK_NOINLINE void dComIfGs_setCardToMemory(u8* card_ptr, int dataNum); +DUSK_NOINLINE void dComIfGs_setInitDataToCard(u8* i_saveData, int i_dataNum); +DUSK_NOINLINE u8 dComIfGs_getWolfAbility(int i_idx); +DUSK_NOINLINE s8 dComIfGs_Grass_hide_Check(); +DUSK_NOINLINE dStage_startStage_c* dComIfGp_getStartStage(); +DUSK_NOINLINE void dComIfGp_setStartStage(dStage_startStage_c* p_startStage); +DUSK_NOINLINE const char* dComIfGp_getStartStageName(); +DUSK_NOINLINE s16 dComIfGp_getStartStagePoint(); +DUSK_NOINLINE s8 dComIfGp_getStartStageRoomNo(); +DUSK_NOINLINE s8 dComIfGp_getStartStageLayer(); +DUSK_NOINLINE void dComIfGp_setStartStageLayer(s8 layer); +DUSK_NOINLINE s8 dComIfGp_getStartStageDarkArea(); +DUSK_NOINLINE void dComIfGp_setStartStageDarkArea(s8 darkArea); +DUSK_NOINLINE dStage_startStage_c* dComIfGp_getNextStartStage(); +DUSK_NOINLINE const char* dComIfGp_getNextStageName(); +DUSK_NOINLINE s16 dComIfGp_getNextStagePoint(); +DUSK_NOINLINE s16 dComIfGp_getNextStageRoomNo(); +DUSK_NOINLINE s16 dComIfGp_getNextStageLayer(); +DUSK_NOINLINE BOOL dComIfGp_isEnableNextStage(); +DUSK_NOINLINE void dComIfGp_offEnableNextStage(); +DUSK_NOINLINE s8 dComIfGp_getNextStageWipe(); +DUSK_NOINLINE u8 dComIfGp_getNextStageWipeSpeed(); +DUSK_NOINLINE dStage_stageDt_c* dComIfGp_getStage(); +DUSK_NOINLINE roomRead_class* dComIfGp_getStageRoom(); +DUSK_NOINLINE stage_palette_info_class* dComIfGp_getStagePaletteInfo(); +DUSK_NOINLINE stage_pselect_info_class* dComIfGp_getStagePselectInfo(); +DUSK_NOINLINE stage_envr_info_class* dComIfGp_getStageEnvrInfo(); +DUSK_NOINLINE stage_vrboxcol_info_class* dComIfGp_getStageVrboxcolInfo(); +DUSK_NOINLINE stage_plight_info_class* dComIfGp_getStagePlightInfo(); +DUSK_NOINLINE int dComIfGp_getStagePaletteNumInfo(); +DUSK_NOINLINE int dComIfGp_getStagePselectNumInfo(); +DUSK_NOINLINE int dComIfGp_getStageEnvrNumInfo(); +DUSK_NOINLINE int dComIfGp_getStageVrboxcolNumInfo(); +DUSK_NOINLINE int dComIfGp_getStagePlightNumInfo(); +DUSK_NOINLINE stage_stag_info_class* dComIfGp_getStageStagInfo(); +DUSK_NOINLINE stage_scls_info_dummy_class* dComIfGp_getStageSclsInfo(); +DUSK_NOINLINE dStage_Multi_c* dComIfGp_getMulti(); +DUSK_NOINLINE void dComIfGp_setOldMulti(); +DUSK_NOINLINE void dComIfGp_resetOldMulti(); +DUSK_NOINLINE s16 dComIfGp_getStageWorldRollAngleX(); +DUSK_NOINLINE s16 dComIfGp_getStageWorldRollDirAngleY(); +DUSK_NOINLINE int dComIfGp_roomControl_getStayNo(); +DUSK_NOINLINE BOOL dComIfGp_roomControl_getTimePass(); +DUSK_NOINLINE void dComIfGp_roomControl_setTimePass(int isPassing); +DUSK_NOINLINE dKy_tevstr_c* dComIfGp_roomControl_getTevStr(int i_roomNo); +DUSK_NOINLINE void dComIfGp_roomControl_setStatusFlag(int i_roomNo, u8 flag); +DUSK_NOINLINE void dComIfGp_roomControl_onStatusFlag(int i_roomNo, u8 flag); +DUSK_NOINLINE void dComIfGp_roomControl_offStatusFlag(int i_roomNo, u8 flag); +DUSK_NOINLINE u8 dComIfGp_roomControl_checkStatusFlag(int i_roomNo, u8 flag); +DUSK_NOINLINE s32 dComIfGp_roomControl_getZoneNo(int i_roomNo); +DUSK_NOINLINE void dComIfGp_roomControl_setZoneNo(int roomNo, int zoneNo); +DUSK_NOINLINE void dComIfGp_roomControl_init(); +DUSK_NOINLINE void dComIfGp_roomControl_initZone(); +DUSK_NOINLINE dStage_roomDt_c* dComIfGp_roomControl_getStatusRoomDt(int room_no); +DUSK_NOINLINE void dComIfGp_roomControl_setStayNo(int stayNo); +DUSK_NOINLINE BOOL dComIfGp_roomControl_checkRoomDisp(int i_roomNo); +DUSK_NOINLINE int dComIfGp_roomControl_loadRoom(int param_0, u8* param_1, bool param_2); +DUSK_NOINLINE void dComIfGp_roomControl_zoneCountCheck(int i_roomNo); +DUSK_NOINLINE dEvt_control_c* dComIfGp_getEvent(); +DUSK_NOINLINE u16 dComIfGp_event_chkEventFlag(u16 i_flag); +DUSK_NOINLINE void dComIfGp_event_onEventFlag(u16 i_flag); +DUSK_NOINLINE void dComIfGp_event_onHindFlag(u16 i_flag); +DUSK_NOINLINE void dComIfGp_event_offHindFlag(u16 i_flag); +DUSK_NOINLINE u8 dComIfGp_event_getMode(); +DUSK_NOINLINE BOOL dComIfGp_event_runCheck(); +DUSK_NOINLINE u16 dComIfGp_event_checkHind(u16 i_hindFlag); +DUSK_NOINLINE BOOL dComIfGp_event_chkTalkXY(); +DUSK_NOINLINE u8 dComIfGp_event_getPreItemNo(); +DUSK_NOINLINE f32 dComIfGp_event_getCullRate(); +DUSK_NOINLINE void dComIfGp_event_setCullRate(f32 i_rate); +DUSK_NOINLINE int dComIfGp_event_order(u16 i_type, u16 i_prio, u16 i_flags, u16 i_hindFlags, + void* i_requestActor, void* i_targetActor, s16 i_eventID, u8 i_mapToolId); +DUSK_NOINLINE void dComIfGp_event_reset(); +DUSK_NOINLINE int dComIfGp_event_moveApproval(void* i_actor); +DUSK_NOINLINE BOOL dComIfGp_event_compulsory( + void* i_actor, const char* i_eventname, u16 i_hindFlag); +DUSK_NOINLINE void dComIfGp_event_remove(); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_event_getPt1(); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_event_getPt2(); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_event_getTalkPartner(); +DUSK_NOINLINE void dComIfGp_event_setTalkPartner(void* i_actor); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_event_getItemPartner(); +DUSK_NOINLINE void dComIfGp_event_setItemPartner(void* i_actor); +DUSK_NOINLINE void dComIfGp_event_setItemPartnerId(fpc_ProcID i_id); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_event_getDoorPartner(); +DUSK_NOINLINE void dComIfGp_event_setDoorPartner(void* i_partner); +DUSK_NOINLINE u8 dComIfGp_event_getGtItm(); +DUSK_NOINLINE void dComIfGp_event_setGtItm(u8 i_itemNo); +DUSK_NOINLINE dEvent_manager_c& dComIfGp_getEventManager(); +DUSK_NOINLINE dEvent_manager_c* dComIfGp_getPEvtManager(); +DUSK_NOINLINE int dComIfGp_evmng_cameraPlay(); +DUSK_NOINLINE int dComIfGp_evmng_startDemo(int i_mapToolId); +DUSK_NOINLINE void dComIfGp_evmng_create(); +DUSK_NOINLINE void dComIfGp_evmng_remove(); +DUSK_NOINLINE bool dComIfGp_evmng_existence(s16 eventId); +DUSK_NOINLINE s16 dComIfGp_evmng_getEventIdx(const char* eventName, u8 mapToolID); +DUSK_NOINLINE bool dComIfGp_evmng_existence(const char* eventname); +DUSK_NOINLINE BOOL dComIfGp_evmng_isMapToolCamera(u8 mapToolID); +DUSK_NOINLINE BOOL dComIfGp_evmng_startCheck(s16 i_eventId); +DUSK_NOINLINE BOOL dComIfGp_evmng_startCheck(char const* i_eventname); +DUSK_NOINLINE BOOL dComIfGp_evmng_endCheck(s16 i_eventID); +DUSK_NOINLINE BOOL dComIfGp_evmng_endCheck(const char* i_eventname); +DUSK_NOINLINE int dComIfGp_evmng_getMyStaffId( + const char* i_staffname, fopAc_ac_c* i_actor, int i_tagId); +DUSK_NOINLINE int dComIfGp_evmng_getIsAddvance(int i_staffId); +DUSK_NOINLINE int dComIfGp_evmng_getMyActIdx(int i_staffId, DUSK_CONST char* DUSK_CONST* i_actions, + int i_actionNum, BOOL param_3, BOOL param_4); +DUSK_NOINLINE f32* dComIfGp_evmng_getMyFloatP(int i_staffId, DUSK_CONST char* i_dataname); +DUSK_NOINLINE cXyz* dComIfGp_evmng_getMyXyzP(int i_staffId, DUSK_CONST char* i_dataname); +DUSK_NOINLINE int* dComIfGp_evmng_getMyIntegerP(int i_staffId, DUSK_CONST char* i_dataname); +DUSK_NOINLINE char* dComIfGp_evmng_getMyStringP(int i_staffId, DUSK_CONST char* i_dataname); +DUSK_NOINLINE int dComIfGp_evmng_getMySubstanceNum(int i_staffId, DUSK_CONST char* i_dataname); +DUSK_NOINLINE void dComIfGp_evmng_cutEnd(int i_staffId); +DUSK_NOINLINE void dComIfGp_evmng_setGoal(cXyz* i_pos); +DUSK_NOINLINE cXyz* dComIfGp_evmng_getGoal(); +DUSK_NOINLINE BOOL dComIfGp_evmng_ChkPresentEnd(); +DUSK_NOINLINE int dComIfGp_evmng_checkStartDemo(); +DUSK_NOINLINE dAttention_c* dComIfGp_getAttention(); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_att_getZHint(); +DUSK_NOINLINE int dComIfGp_att_ZHintRequest(fopAc_ac_c* param_1, int param_2); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_att_getCatghTarget(); +DUSK_NOINLINE u8 dComIfGp_att_getCatchChgItem(); +DUSK_NOINLINE int dComIfGp_att_CatchRequest(fopAc_ac_c* param_0, u8 param_1, f32 i_horizontalDist, + f32 i_upDist, f32 i_downDist, s16 i_angle, int param_5); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_att_getLookTarget(); +DUSK_NOINLINE void dComIfGp_att_LookRequest(fopAc_ac_c* param_0, f32 i_horizontalDist, f32 i_upDist, + f32 i_downDist, s16 i_angle, int param_5); +DUSK_NOINLINE dVibration_c& dComIfGp_getVibration(); +DUSK_NOINLINE JKRAramArchive* dComIfGp_getFieldMapArchive2(); +DUSK_NOINLINE void dComIfGp_setFieldMapArchive2(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getMsgArchive(int idx); +DUSK_NOINLINE void dComIfGp_setMsgArchive(int i, JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getDemoMsgArchive(); +DUSK_NOINLINE void dComIfGp_setDemoMsgArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getMeterButtonArchive(); +DUSK_NOINLINE void dComIfGp_setMeterButtonArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getFontArchive(); +DUSK_NOINLINE void dComIfGp_setFontArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getRubyArchive(); +DUSK_NOINLINE void dComIfGp_setRubyArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getAnmArchive(); +DUSK_NOINLINE void dComIfGp_setAnmArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getCollectResArchive(); +DUSK_NOINLINE void dComIfGp_setCollectResArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getFmapResArchive(); +DUSK_NOINLINE void dComIfGp_setFmapResArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getDmapResArchive(); +DUSK_NOINLINE void dComIfGp_setDmapResArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getOptionResArchive(); +DUSK_NOINLINE void dComIfGp_setOptionResArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getItemIconArchive(); +DUSK_NOINLINE void dComIfGp_setItemIconArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getNameResArchive(); +DUSK_NOINLINE void dComIfGp_setNameResArchive(JKRArchive* arc); +DUSK_NOINLINE void dComIfGp_setErrorResArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getAllMapArchive(); +DUSK_NOINLINE void dComIfGp_setAllMapArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getMsgCommonArchive(); +DUSK_NOINLINE void dComIfGp_setMsgCommonArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getRingResArchive(); +DUSK_NOINLINE void dComIfGp_setRingResArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getCardIconResArchive(); +DUSK_NOINLINE void dComIfGp_setCardIconResArchive(JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getMsgDtArchive(); +DUSK_NOINLINE JKRArchive* dComIfGp_getMsgDtArchive(int idx); +DUSK_NOINLINE void dComIfGp_setMsgDtArchive(int i, JKRArchive* arc); +DUSK_NOINLINE JKRArchive* dComIfGp_getMain2DArchive(); +DUSK_NOINLINE void dComIfGp_setMain2DArchive(JKRArchive* arc); +DUSK_NOINLINE JKRExpHeap* dComIfGp_particle_getResHeap(); +DUSK_NOINLINE void dComIfGp_particle_levelExecute(u32 param_0); +DUSK_NOINLINE void dComIfGp_particle_createCommon(const void* data); +DUSK_NOINLINE void dComIfGp_particle_readScene(u8 particle_no, mDoDvdThd_toMainRam_c** param_1); +DUSK_NOINLINE void dComIfGp_particle_createScene(const void* param_0); +DUSK_NOINLINE void dComIfGp_particle_removeScene(bool param_0); +DUSK_NOINLINE void dComIfGp_particle_cleanup(); +DUSK_NOINLINE void dComIfGp_particle_calc3D(); +DUSK_NOINLINE void dComIfGp_particle_calc2D(); +DUSK_NOINLINE void dComIfGp_particle_calcMenu(); +DUSK_NOINLINE void dComIfGp_particle_draw(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawFog(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawP1(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawProjection(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawNormalPri0_A(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawNormalPri0_B(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawFogPri0_A(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawFogPri0_B(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawFogPri1(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawFogPri2(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawFogPri3(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawFogPri4(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawDarkworld(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_drawScreen(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_draw2Dgame(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_draw2Dfore(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_draw2Dback(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_draw2DmenuFore(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_particle_draw2DmenuBack(JPADrawInfo* i_drawInfo); +DUSK_NOINLINE void dComIfGp_setHitMark(u16 i_hitmark, fopAc_ac_c* param_1, const cXyz* param_2, + const csXyz* param_3, const cXyz* param_4, u32 i_atType); +DUSK_NOINLINE void dComIfGp_particle_setWaterRipple(u32* param_0, cBgS_PolyInfo& param_1, + const cXyz* param_2, f32 param_3, const dKy_tevstr_c* param_4, const cXyz* param_5, s8 param_6); +DUSK_NOINLINE u32 dComIfGp_particle_setPolyColor(u32 param_0, u16 param_1, cBgS_PolyInfo& param_2, + const cXyz* param_3, const dKy_tevstr_c* param_4, const csXyz* param_5, const cXyz* param_6, + int param_7, dPa_levelEcallBack* param_8, s8 param_9, const cXyz* param_10); +DUSK_NOINLINE JPABaseEmitter* dComIfGp_particle_setPolyColor(u16 param_1, cBgS_PolyInfo& param_2, + const cXyz* param_3, const dKy_tevstr_c* param_4, const csXyz* param_5, const cXyz* param_6, + int param_7, dPa_levelEcallBack* param_8, s8 param_9, const cXyz* param_10); +DUSK_NOINLINE void dComIfGp_particle_setSimple(u16 param_0, cXyz* i_pos, u8 param_2, + GXColor& param_3, GXColor& param_4, int param_5, float param_6); +DUSK_NOINLINE u32 dComIfGp_particle_setStopContinue(u32 param_0); +DUSK_NOINLINE u32 dComIfGp_particle_setSimpleFoot(u32 param_0, u32* param_1, cBgS_PolyInfo& param_2, + cXyz const* param_3, dKy_tevstr_c const* param_4, int param_5, csXyz const* param_6, + cXyz const* param_7, dPa_levelEcallBack* param_8, s8 param_9, cXyz const* param_10); +DUSK_NOINLINE u16 dComIfGp_particle_setCommonPoly(u32* param_0, cBgS_PolyInfo* param_1, + const cXyz* param_2, const cXyz* param_3, const dKy_tevstr_c* param_4, u32 param_5, u32 param_6, + const csXyz* param_7, const cXyz* param_8, s8 param_9); +DUSK_NOINLINE void dComIfGp_particle_levelEmitterOnEventMove(u32 param_0); +DUSK_NOINLINE JPABaseEmitter* dComIfGp_particle_getEmitter(u32 param_0); +DUSK_NOINLINE u32 dComIfGp_particle_set(u32 param_0, u16 param_1, const cXyz* i_pos, + const dKy_tevstr_c* param_3, const csXyz* i_rotation, const cXyz* i_scale, u8 i_alpha, + dPa_levelEcallBack* param_7, s8 param_8, const GXColor* param_9, const GXColor* param_10, + const cXyz* param_11); +DUSK_NOINLINE u32 dComIfGp_particle_set(u32 param_0, u16 param_1, const cXyz* i_pos, + const csXyz* i_rotation, const cXyz* i_scale, u8 param_5, dPa_levelEcallBack* param_6, + s8 param_7, const GXColor* param_8, const GXColor* param_9, const cXyz* param_10); +DUSK_NOINLINE JPABaseEmitter* dComIfGp_particle_set(u16 i_resID, const cXyz* i_pos, + const dKy_tevstr_c* param_3, const csXyz* i_rotation, const cXyz* i_scale, u8 i_alpha, + dPa_levelEcallBack* i_callback, s8 param_8, const GXColor* i_prmColor, + const GXColor* i_envColor, const cXyz* i_particleScale); +DUSK_NOINLINE JPABaseEmitter* dComIfGp_particle_set(u16 i_resID, const cXyz* i_pos, + const csXyz* i_rotation, const cXyz* i_scale, u8 i_alpha, dPa_levelEcallBack* i_callback, + s8 param_7, const GXColor* i_prmColor, const GXColor* i_envColor, const cXyz* i_particleScale); +DUSK_NOINLINE u32 dComIfGp_particle_set( + u32 param_0, u16 param_1, const cXyz* i_pos, const dKy_tevstr_c* param_3); +DUSK_NOINLINE JPABaseEmitter* dComIfGp_particle_set( + u16 i_resID, const cXyz* i_pos, const csXyz* i_rotation, const cXyz* i_scale); +DUSK_NOINLINE JPABaseEmitter* dComIfGp_particle_set(u16 i_resID, const cXyz* i_pos, + const dKy_tevstr_c* param_2, const csXyz* i_rotation, const cXyz* i_scale); +DUSK_NOINLINE u32 dComIfGp_particle_set( + u32 param_0, u16 param_1, const cXyz* i_pos, const csXyz* param_3, const cXyz* param_4); +DUSK_NOINLINE u32 dComIfGp_particle_setColor(u32 param_0, u16 param_1, const cXyz* i_pos, + const dKy_tevstr_c* param_3, const GXColor* param_4, const GXColor* param_5, f32 param_6, + u8 param_7, const csXyz* param_8, const cXyz* param_9, dPa_levelEcallBack* param_10, + s8 param_11, const cXyz* param_12); +DUSK_NOINLINE JPABaseEmitter* dComIfGp_particle_setColor(u16 param_0, const cXyz* i_pos, + const dKy_tevstr_c* param_2, const GXColor* param_3, const GXColor* param_4, f32 param_5, + u8 param_6, const csXyz* param_7, const cXyz* param_8, dPa_levelEcallBack* param_9, s8 param_10, + const cXyz* param_11); +DUSK_NOINLINE JPABaseEmitter* dComIfGp_particle_setColor(u16 param_0, const cXyz* i_pos, + const dKy_tevstr_c* param_2, const GXColor* param_3, const GXColor* param_4, f32 param_5, + u8 param_6); +DUSK_NOINLINE u32 dComIfGp_particle_getHeapSize(); +DUSK_NOINLINE u32 dComIfGp_particle_getSceneHeapSize(); +DUSK_NOINLINE int dComIfGp_particle_getEmitterNum(); +DUSK_NOINLINE int dComIfGp_particle_getParticleNum(); +DUSK_NOINLINE dSmplMdl_draw_c* dComIfGp_getSimpleModel(); +DUSK_NOINLINE int dComIfGp_getWindowNum(); +DUSK_NOINLINE void dComIfGp_setWindowNum(int num); +DUSK_NOINLINE s8 dComIfGp_getLayerOld(); +DUSK_NOINLINE s32 dComIfGp_checkStatus(u16 flags); +DUSK_NOINLINE void dComIfGp_setStatus(u16 status); +DUSK_NOINLINE void dComIfGp_onStatus(u16 i_status); +DUSK_NOINLINE dDlst_window_c* dComIfGp_getWindow(int i); +DUSK_NOINLINE void dComIfGp_setWindow(u8 i, f32 param_1, f32 param_2, f32 param_3, f32 param_4, + f32 param_5, f32 param_6, int camID, int mode); +DUSK_NOINLINE camera_process_class* dComIfGp_getCamera(int idx); +DUSK_NOINLINE void dComIfGp_setCamera(int i, camera_class* cam); +DUSK_NOINLINE int dComIfGp_getCameraWinID(int idx); +DUSK_NOINLINE int dComIfGp_getCameraPlayer1ID(int idx); +DUSK_NOINLINE int dComIfGp_getCameraPlayer2ID(int idx); +DUSK_NOINLINE u32 dComIfGp_getCameraAttentionStatus(int i_no); +DUSK_NOINLINE BOOL dComIfGp_checkCameraAttentionStatus(int i, u32 flag); +DUSK_NOINLINE void dComIfGp_onCameraAttentionStatus(int i, u32 flag); +DUSK_NOINLINE void dComIfGp_offCameraAttentionStatus(int i, u32 flag); +DUSK_NOINLINE void dComIfGp_setCameraInfo( + int camIdx, camera_class* p_cam, int param_2, int param_3, int param_4); +DUSK_NOINLINE f32 dComIfGp_getCameraZoomScale(int i_no); +DUSK_NOINLINE void dComIfGp_setCameraZoomScale(int i_no, f32 i_scale); +DUSK_NOINLINE f32 dComIfGp_getCameraZoomForcus(int i_no); +DUSK_NOINLINE void dComIfGp_setCameraZoomForcus(int i_no, f32 i_focus); +DUSK_NOINLINE const char* dComIfGp_getCameraParamFileName(int i); +DUSK_NOINLINE void dComIfGp_setCameraParamFileName(int i, char* name); +DUSK_NOINLINE void dComIfGp_saveCameraPosition( + int i, cXyz* i_pos, cXyz* i_target, f32 i_fovy, s16 i_bank); +DUSK_NOINLINE void dComIfGp_loadCameraPosition( + int i, cXyz* o_pos, cXyz* o_target, f32* o_fovy, s16* o_bank); +DUSK_NOINLINE fopAc_ac_c* dComIfGp_getPlayer(int idx); +DUSK_NOINLINE void dComIfGp_setPlayer(int i, fopAc_ac_c* player); +DUSK_NOINLINE int dComIfGp_getPlayerCameraID(int idx); +DUSK_NOINLINE void dComIfGp_setPlayerInfo(int plyrIdx, fopAc_ac_c* ptr, int camIdx); +DUSK_NOINLINE daPy_py_c* dComIfGp_getLinkPlayer(); +DUSK_NOINLINE daHorse_c* dComIfGp_getHorseActor(); +DUSK_NOINLINE void dComIfGp_setLinkPlayer(fopAc_ac_c* ptr); +DUSK_NOINLINE void dComIfGp_setHorseActor(fopAc_ac_c* i_horse); +DUSK_NOINLINE void dComIfGp_setPlayerPtr(int i, fopAc_ac_c* ptr); +DUSK_NOINLINE dMsgObject_c* dComIfGp_getMsgObjectClass(); +DUSK_NOINLINE void dComIfGp_setMsgObjectClass(dMsgObject_c* obj); +DUSK_NOINLINE f32 dComIfGp_getItemLifeCount(); +DUSK_NOINLINE u8 dComIfGp_getItemLifeCountType(); +DUSK_NOINLINE void dComIfGp_setItemLifeCount(f32 amount, u8 type); +DUSK_NOINLINE void dComIfGp_clearItemLifeCount(); +DUSK_NOINLINE s32 dComIfGp_getItemRupeeCount(); +DUSK_NOINLINE void dComIfGp_setItemRupeeCount(s32 amount); +DUSK_NOINLINE void dComIfGp_clearItemRupeeCount(); +DUSK_NOINLINE s16 dComIfGp_getItemKeyNumCount(); +DUSK_NOINLINE void dComIfGp_setItemKeyNumCount(s16 count); +DUSK_NOINLINE void dComIfGp_clearItemKeyNumCount(); +DUSK_NOINLINE s16 dComIfGp_getItemMaxLifeCount(); +DUSK_NOINLINE void dComIfGp_setItemMaxLifeCount(s16 count); +DUSK_NOINLINE void dComIfGp_clearItemMaxLifeCount(); +DUSK_NOINLINE void dComIfGp_setItemMagicCount(s16 count); +DUSK_NOINLINE void dComIfGp_setItemNowMagic(s16 magic); +DUSK_NOINLINE void dComIfGp_setItemMaxMagicCount(s16 count); +DUSK_NOINLINE s32 dComIfGp_getItemOilCount(); +DUSK_NOINLINE void dComIfGp_setItemOilCount(s32 oil); +DUSK_NOINLINE void dComIfGp_setItemMaxOilCount(s32 oil); +DUSK_NOINLINE void dComIfGp_clearItemOilCount(); +DUSK_NOINLINE s32 dComIfGp_getItemNowOil(); +DUSK_NOINLINE void dComIfGp_setItemNowOil(s32 oil); +DUSK_NOINLINE s32 dComIfGp_getItemMaxOilCount(); +DUSK_NOINLINE void dComIfGp_clearItemMaxOilCount(); +DUSK_NOINLINE int dComIfGp_getOxygen(); +DUSK_NOINLINE void dComIfGp_setOxygen(s32 oxygen); +DUSK_NOINLINE int dComIfGp_getNowOxygen(); +DUSK_NOINLINE void dComIfGp_setNowOxygen(s32 oxygen); +DUSK_NOINLINE s32 dComIfGp_getMaxOxygen(); +DUSK_NOINLINE void dComIfGp_setMaxOxygen(s32 i_oxygen); +DUSK_NOINLINE s32 dComIfGp_getOxygenCount(); +DUSK_NOINLINE void dComIfGp_setOxygenCount(s32 oxygen); +DUSK_NOINLINE void dComIfGp_clearOxygenCount(); +DUSK_NOINLINE void dComIfGp_setMaxOxygenCount(s32 oxygen); +DUSK_NOINLINE s32 dComIfGp_getMaxOxygenCount(); +DUSK_NOINLINE void dComIfGp_clearMaxOxygenCount(); +DUSK_NOINLINE s16 dComIfGp_getItemArrowNumCount(); +DUSK_NOINLINE void dComIfGp_setItemArrowNumCount(s16 count); +DUSK_NOINLINE void dComIfGp_clearItemArrowNumCount(); +DUSK_NOINLINE s16 dComIfGp_getItemPachinkoNumCount(); +DUSK_NOINLINE void dComIfGp_setItemPachinkoNumCount(s16 count); +DUSK_NOINLINE void dComIfGp_clearItemPachinkoNumCount(); +DUSK_NOINLINE s16 dComIfGp_getItemMaxArrowNumCount(); +DUSK_NOINLINE int dComIfGp_getMessageCountNumber(); +DUSK_NOINLINE void dComIfGp_setMessageCountNumber(s32 number); +DUSK_NOINLINE u16 dComIfGp_getItemNowLife(); +DUSK_NOINLINE void dComIfGp_setItemNowLife(u16 life); +DUSK_NOINLINE u8 dComIfGp_getMesgStatus(); +DUSK_NOINLINE u8 dComIfGp_getRStatus(); +DUSK_NOINLINE bool dComIfGp_isRSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setRStatus(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getRStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getRSetFlagForce(); +DUSK_NOINLINE void dComIfGp_setRStatusForce(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getAStatus(); +DUSK_NOINLINE bool dComIfGp_isASetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setAStatus(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getAStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getASetFlagForce(); +DUSK_NOINLINE void dComIfGp_setAStatusForce(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getNunStatus(); +DUSK_NOINLINE bool dComIfGp_isNunSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setNunStatus(u8 status, u8 param_1, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getBottleStatus(); +DUSK_NOINLINE bool dComIfGp_isBottleSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setBottleStatus(u8 param_0, u8 param_1); +DUSK_NOINLINE u8 dComIfGp_getBottleStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getBottleSetFlagForce(); +DUSK_NOINLINE void dComIfGp_setBottleStatusForce(u8 param_0, u8 param_1); +DUSK_NOINLINE u8 dComIfGp_getRemoConStatus(); +DUSK_NOINLINE bool dComIfGp_isRemoConSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setRemoConStatus(u8 status, u8 param_1, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getDoStatus(); +DUSK_NOINLINE bool dComIfGp_isDoSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setDoStatus(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getDoStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getDoSetFlagForce(); +DUSK_NOINLINE void dComIfGp_setDoStatusForce(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_get3DStatus(); +DUSK_NOINLINE u8 dComIfGp_get3DDirection(); +DUSK_NOINLINE bool dComIfGp_is3DSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_set3DStatus(u8 status, u8 direction, u8 flag); +DUSK_NOINLINE u8 dComIfGp_get3DStatusForce(); +DUSK_NOINLINE u8 dComIfGp_get3DDirectionForce(); +DUSK_NOINLINE u8 dComIfGp_get3DSetFlagForce(); +DUSK_NOINLINE void dComIfGp_set3DStatusForce(u8 status, u8 direction, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getCStickStatus(); +DUSK_NOINLINE u8 dComIfGp_getCStickDirection(); +DUSK_NOINLINE bool dComIfGp_isCStickSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setCStickStatus(u8 status, u8 param_1, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getCStickStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getCStickDirectionForce(); +DUSK_NOINLINE u8 dComIfGp_getCStickSetFlagForce(); +DUSK_NOINLINE void dComIfGp_setCStickStatusForce(u8 status, u8 param_1, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getSButtonStatus(); +DUSK_NOINLINE bool dComIfGp_isSButtonSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setSButtonStatus(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getSButtonStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getSButtonSetFlagForce(); +DUSK_NOINLINE void dComIfGp_setSButtonStatusForce(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getZStatus(); +DUSK_NOINLINE bool dComIfGp_isZSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setZStatus(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getZStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getZSetFlagForce(); +DUSK_NOINLINE void dComIfGp_setZStatusForce(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getXStatus(); +DUSK_NOINLINE bool dComIfGp_isXSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setXStatus(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getXStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getXSetFlagForce(); +DUSK_NOINLINE void dComIfGp_setXStatusForce(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getYStatus(); +DUSK_NOINLINE bool dComIfGp_isYSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setYStatus(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getYStatusForce(); +DUSK_NOINLINE u8 dComIfGp_getYSetFlagForce(); +DUSK_NOINLINE void dComIfGp_setYStatusForce(u8 status, u8 flag); +DUSK_NOINLINE u8 dComIfGp_getNunZStatus(); +DUSK_NOINLINE bool dComIfGp_isNunZSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setNunZStatus(u8 param_0, u8 param_1); +DUSK_NOINLINE u8 dComIfGp_getNunCStatus(); +DUSK_NOINLINE bool dComIfGp_isNunCSetFlag(u8 flag); +DUSK_NOINLINE void dComIfGp_setNunCStatus(u8 param_0, u8 param_1); +DUSK_NOINLINE void dComIfGp_setSelectEquipClothes(u8 i_clothNo); +DUSK_NOINLINE void dComIfGp_setSelectEquipSword(u8 i_swordNo); +DUSK_NOINLINE void dComIfGp_setSelectEquipShield(u8 i_shieldNo); +DUSK_NOINLINE u8 dComIfGp_getMesgAnimeAttrInfo(); +DUSK_NOINLINE void dComIfGp_setMesgAnimeAttrInfo(u8 param_1); +DUSK_NOINLINE void dComIfGp_clearMesgAnimeAttrInfo(); +DUSK_NOINLINE u8 dComIfGp_getMesgFaceAnimeAttrInfo(); +DUSK_NOINLINE void dComIfGp_setMesgFaceAnimeAttrInfo(u8 id); +DUSK_NOINLINE void dComIfGp_clearMesgFaceAnimeAttrInfo(); +DUSK_NOINLINE void dComIfGp_clearMesgAnimeTagInfo(); +DUSK_NOINLINE void dComIfGp_setItem(u8 slot, u8 i_no); +DUSK_NOINLINE u8 dComIfGp_getAdvanceDirection(); +DUSK_NOINLINE void dComIfGp_setAdvanceDirection(u8 i_dir); +DUSK_NOINLINE u8 dComIfGp_checkMesgCancelButton(); +DUSK_NOINLINE void dComIfGp_setMesgCancelButton(u8 button); +DUSK_NOINLINE u8 dComIfGp_getGameoverStatus(); +DUSK_NOINLINE void dComIfGp_setGameoverStatus(u8 i_status); +DUSK_NOINLINE u8 dComIfGp_isHeapLockFlag(); +DUSK_NOINLINE u8 dComIfGp_getSubHeapLockFlag(int idx); +DUSK_NOINLINE void dComIfGp_setSubHeapLockFlag(int idx, u8 status); +DUSK_NOINLINE u8 dComIfGp_getNeedLightDropNum(); +DUSK_NOINLINE void dComIfGp_setNeedLightDropNum(u8 i_num); +DUSK_NOINLINE u8 dComIfGp_checkMesgBgm(); +DUSK_NOINLINE void dComIfGp_setMesgBgmOff(); +DUSK_NOINLINE void dComIfGp_setMesgBgmOn(); +DUSK_NOINLINE u8 dComIfGp_isPauseFlag(); +DUSK_NOINLINE void dComIfGp_offPauseFlag(); +DUSK_NOINLINE void dComIfGp_onPauseFlag(); +DUSK_NOINLINE u8 dComIfGp_getOxygenShowFlag(); +DUSK_NOINLINE void dComIfGp_offOxygenShowFlag(); +DUSK_NOINLINE void dComIfGp_onOxygenShowFlag(); +DUSK_NOINLINE u8 dComIfGp_2dShowCheck(); +DUSK_NOINLINE void dComIfGp_2dShowOn(); +DUSK_NOINLINE void dComIfGp_2dShowOff(); +DUSK_NOINLINE JKRExpHeap* dComIfGp_getExpHeap2D(); +DUSK_NOINLINE void dComIfGp_setExpHeap2D(void* heap); +DUSK_NOINLINE JKRExpHeap* dComIfGp_getSubExpHeap2D(int idx); +DUSK_NOINLINE void dComIfGp_setSubExpHeap2D(int idx, void* heap); +DUSK_NOINLINE JKRExpHeap* dComIfGp_getMsgExpHeap(); +DUSK_NOINLINE void dComIfGp_setMsgExpHeap(void* heap); +DUSK_NOINLINE dComIfG_MesgCamInfo_c* dComIfGp_getMesgCameraInfo(); +DUSK_NOINLINE void dComIfGp_setMesgCameraTagInfo(int id); +DUSK_NOINLINE void dComIfGp_clearMesgCameraTagInfo(); +DUSK_NOINLINE void dComIfGp_setMesgCameraAttrInfo(int param_1); +DUSK_NOINLINE void dComIfGp_clearMesgCameraAttrInfo(); +DUSK_NOINLINE void dComIfGp_setMesgCameraInfoActor(fopAc_ac_c* param_1, fopAc_ac_c* param_2, + fopAc_ac_c* param_3, fopAc_ac_c* param_4, fopAc_ac_c* param_5, fopAc_ac_c* param_6, + fopAc_ac_c* param_7, fopAc_ac_c* param_8, fopAc_ac_c* param_9, fopAc_ac_c* param_10); +DUSK_NOINLINE u32 dComIfGp_checkPlayerStatus0(int param_0, u32 flag); +DUSK_NOINLINE u32 dComIfGp_checkPlayerStatus1(int param_0, u32 flag); +DUSK_NOINLINE void dComIfGp_setPlayerStatus0(int param_0, u32 flag); +DUSK_NOINLINE void dComIfGp_setPlayerStatus1(int param_0, u32 flag); +DUSK_NOINLINE void dComIfGp_clearPlayerStatus0(int param_0, u32 flag); +DUSK_NOINLINE void dComIfGp_clearPlayerStatus1(int param_0, u32 flag); +DUSK_NOINLINE void dComIfGp_setCurrentWindow(dDlst_window_c* i_window); +DUSK_NOINLINE void dComIfGp_setCurrentView(view_class* i_view); +DUSK_NOINLINE void dComIfGp_setCurrentViewport(view_port_class* i_viewport); +DUSK_NOINLINE J2DGrafContext* dComIfGp_getCurrentGrafPort(); +DUSK_NOINLINE void dComIfGp_setCurrentGrafPort(J2DOrthoGraph* i_graf); +DUSK_NOINLINE void* dComIfGp_getItemTable(); +DUSK_NOINLINE void dComIfGp_setItemTable(void* data); +DUSK_NOINLINE char* dComIfGp_getLastPlayStageName(); +DUSK_NOINLINE void dComIfGp_setLastPlayStageName(char* name); +DUSK_NOINLINE void dComIfGp_init(); +DUSK_NOINLINE void dComIfGp_itemDataInit(); +DUSK_NOINLINE void dComIfGp_setItemBombNumCount(u8 i_item, s16 count); +DUSK_NOINLINE s16 dComIfGp_getItemBombNumCount(u8 i_no); +DUSK_NOINLINE void dComIfGp_clearItemBombNumCount(u8 i_no); +DUSK_NOINLINE s16 dComIfGp_getItemMaxBombNumCount(); +DUSK_NOINLINE void dComIfGp_setNowVibration(u8 status); +DUSK_NOINLINE u32 dComIfGp_getNowVibration(); +DUSK_NOINLINE void dComIfGp_particle_create(); +DUSK_NOINLINE void dComIfGp_createSimpleModel(); +DUSK_NOINLINE void dComIfGp_deleteSimpleModel(); +DUSK_NOINLINE void dComIfGp_drawSimpleModel(); +DUSK_NOINLINE int dComIfGp_addSimpleModel(J3DModelData* i_modelData, int roomNo, u8 i_drawBG); +DUSK_NOINLINE void dComIfGp_removeSimpleModel(J3DModelData* i_modelData, int roomNo); +DUSK_NOINLINE void dComIfGp_entrySimpleModel(J3DModel* model, int roomNo); +DUSK_NOINLINE void dComIfG_ct(); +DUSK_NOINLINE dBgS& dComIfG_Bgsp(); +DUSK_NOINLINE dCcS* dComIfG_Ccsp(); +DUSK_NOINLINE dCcS& dComIfG_Ccsp2(); +DUSK_NOINLINE void dComIfG_setTimerNowTimeMs(int time); +DUSK_NOINLINE int dComIfG_getTimerNowTimeMs(); +DUSK_NOINLINE void dComIfG_setTimerLimitTimeMs(int i_time); +DUSK_NOINLINE int dComIfG_getTimerLimitTimeMs(); +DUSK_NOINLINE void dComIfG_setTimerMode(int mode); +DUSK_NOINLINE int dComIfG_getTimerMode(); +DUSK_NOINLINE void dComIfG_setTimerType(u8 i_type); +DUSK_NOINLINE u8 dComIfG_getTimerType(); +DUSK_NOINLINE void dComIfG_setTimerPtr(dTimer_c* i_ptr); +DUSK_NOINLINE dTimer_c* dComIfG_getTimerPtr(); + +/** + * Attempts to add a new Object Resource Archive (*.arc) into the Resource Control. + * @param i_arcName Name of archive to be added + * @param i_mountDirection The direction to mount the archive. mDoDvd_MOUNT_DIRECTION_HEAD or + * mDoDvd_MOUNT_DIRECTION_TAIL + * @param i_heap Pointer to heap to load resources into + * @return TRUE if successful, FALSE otherwise + */ +DUSK_NOINLINE int dComIfG_setObjectRes(const char* i_arcName, u8 i_mountDirection, JKRHeap* i_heap); +DUSK_NOINLINE int dComIfG_setObjectRes( + const char* i_arcName, void* i_archiveRes, u32 i_bufferSize, JKRHeap* i_heap); + +/** + * Attempts to add a new Stage Resource Archive (*.arc) into the Resource Control. + * @param i_arcName Name of archive to be added + * @param i_heap Pointer to heap to load resources into + * @return TRUE if successful, FALSE otherwise + */ +DUSK_NOINLINE int dComIfG_setStageRes(const char* i_arcName, JKRHeap* i_heap); +DUSK_NOINLINE int dComIfG_syncObjectRes(const char* i_arcName); +DUSK_NOINLINE int dComIfG_syncStageRes(const char* i_arcName); +DUSK_NOINLINE int dComIfG_deleteObjectResMain(const char* i_arcName); +DUSK_NOINLINE int dComIfG_deleteStageRes(const char* i_arcName); +DUSK_NOINLINE void* dComIfG_getStageRes(const char* i_arcName, const char* i_resName); +DUSK_NOINLINE void* dComIfG_getObjectRes(const char* i_arcName, const char* i_resName); +DUSK_NOINLINE void* dComIfG_getObjectRes(const char* i_arcName, int i_index); +DUSK_NOINLINE void dComIfG_dumpResControl(); +DUSK_NOINLINE dRes_info_c* dComIfG_getObjectResInfo(const char* i_arcName); +DUSK_NOINLINE dRes_info_c* dComIfG_getStageResInfo(const char* i_arcName); +DUSK_NOINLINE int dComIfG_syncAllObjectRes(); +DUSK_NOINLINE void* dComIfG_getObjectIDRes(const char* i_arcName, u16 i_resID); +DUSK_NOINLINE int dComIfG_getObjctResName2Index(const char* i_arcName, const char* i_resName); +DUSK_NOINLINE u8 dComIfG_getBrightness(); +DUSK_NOINLINE void dComIfG_setBrightness(u8 brightness); +DUSK_NOINLINE BOOL dComIfG_isDebugMode(); +DUSK_NOINLINE u32 dComIfG_getTrigB(u32 i_padNo); +#if DEBUG +DUSK_NOINLINE u32 dComIfG_getObjectAllSize(); +DUSK_NOINLINE u32 dComIfG_getStageAllSize(); +DUSK_NOINLINE u32 dComIfG_getObjectSize(const char* i_arcName); +DUSK_NOINLINE u32 dComIfG_getStageSize(const char* i_arcName); +DUSK_NOINLINE void dComIfG_initStopwatch(); +#endif + +DUSK_NOINLINE int dComIfGd_setRealShadow(u32 param_0, s8 param_1, J3DModel* param_2, cXyz* param_3, + f32 param_4, f32 param_5, dKy_tevstr_c* param_6); +DUSK_NOINLINE int dComIfGd_setSimpleShadow( + cXyz* pos, f32 param_1, f32 param_2, cXyz* param_3, s16 angle, f32 param_5, TGXTexObj* tex); +DUSK_NOINLINE bool dComIfGd_addRealShadow(u32 key, J3DModel* model); +DUSK_NOINLINE void dComIfGd_drawListItem3d(); + +#if VERSION > VERSION_GCN_JPN +DUSK_NOINLINE void dComIfGd_drawListCursor(); +#endif + +DUSK_NOINLINE void dComIfGd_reset(); +DUSK_NOINLINE void dComIfGd_set2DOpa(dDlst_base_c* dlst); +DUSK_NOINLINE void dComIfGd_set2DXlu(dDlst_base_c* dlst); +DUSK_NOINLINE void dComIfGd_set2DOpaTop(dDlst_base_c* dlst); +DUSK_NOINLINE void dComIfGd_setCopy2D(dDlst_base_c* dlst); +DUSK_NOINLINE view_class* dComIfGd_getView(); +DUSK_NOINLINE Mtx44* dComIfGd_getProjViewMtx(); +DUSK_NOINLINE MtxP dComIfGd_getInvViewMtx(); +DUSK_NOINLINE view_port_class* dComIfGd_getViewport(); +DUSK_NOINLINE MtxP dComIfGd_getViewRotMtx(); +DUSK_NOINLINE MtxP dComIfGd_getViewMtx(); +DUSK_NOINLINE J3DDrawBuffer* dComIfGd_getListFilter(); +DUSK_NOINLINE J3DDrawBuffer* dComIfGd_getOpaListIndScreen(); +DUSK_NOINLINE J3DDrawBuffer* dComIfGd_getListPacket(); +DUSK_NOINLINE void dComIfGd_setListSky(); +DUSK_NOINLINE void dComIfGd_setListDark(); +DUSK_NOINLINE void dComIfGd_setListInvisisble(); +DUSK_NOINLINE void dComIfGd_setListDarkBG(); +DUSK_NOINLINE void dComIfGd_setXluListDarkBG(); +DUSK_NOINLINE void dComIfGd_setList(); +DUSK_NOINLINE void dComIfGd_setListItem3D(); +DUSK_NOINLINE void dComIfGd_setList3Dlast(); +DUSK_NOINLINE void dComIfGd_setXluList2DScreen(); +DUSK_NOINLINE void dComIfGd_setXluListBG(); +DUSK_NOINLINE void dComIfGd_setListBG(); +DUSK_NOINLINE void dComIfGd_setListIndScreen(); +DUSK_NOINLINE void dComIfGd_setListMiddle(); +DUSK_NOINLINE void dComIfGd_setListZxlu(); +DUSK_NOINLINE J3DDrawBuffer* dComIfGd_getOpaList(); +DUSK_NOINLINE J3DDrawBuffer* dComIfGd_getOpaListBG(); +DUSK_NOINLINE J3DDrawBuffer* dComIfGd_getOpaListDark(); +DUSK_NOINLINE J3DDrawBuffer* dComIfGd_getXluListBG(); +DUSK_NOINLINE void dComIfGd_setListFilter(); +DUSK_NOINLINE void dComIfGd_init(); +DUSK_NOINLINE void dComIfGd_peekZ(s16 param_0, s16 param_1, u32* param_2); +DUSK_NOINLINE void dComIfGd_peekZdata(); +DUSK_NOINLINE void dComIfGd_setView(view_class* view); +DUSK_NOINLINE void dComIfGd_setWindow(dDlst_window_c* window); +DUSK_NOINLINE void dComIfGd_setViewport(view_port_class* port); +DUSK_NOINLINE void dComIfGd_entryZSortListZxlu(J3DPacket* i_packet, cXyz& param_1); +DUSK_NOINLINE void dComIfGd_entryZSortXluList(J3DPacket* i_packet, cXyz& param_1); +DUSK_NOINLINE void dComIfGd_drawCopy2D(); +DUSK_NOINLINE void dComIfGd_drawOpaListSky(); +DUSK_NOINLINE void dComIfGd_drawXluListSky(); +DUSK_NOINLINE void dComIfGd_drawOpaListBG(); +DUSK_NOINLINE void dComIfGd_drawOpaListDarkBG(); +DUSK_NOINLINE void dComIfGd_drawOpaListMiddle(); +DUSK_NOINLINE void dComIfGd_drawOpaList(); +DUSK_NOINLINE void dComIfGd_drawOpaListDark(); +DUSK_NOINLINE void dComIfGd_drawOpaListPacket(); +DUSK_NOINLINE void dComIfGd_drawXluListBG(); +DUSK_NOINLINE void dComIfGd_drawXluListDarkBG(); +DUSK_NOINLINE void dComIfGd_drawXluList(); +DUSK_NOINLINE void dComIfGd_drawXluListDark(); +DUSK_NOINLINE void dComIfGd_drawXluListInvisible(); +DUSK_NOINLINE void dComIfGd_drawOpaListInvisible(); +DUSK_NOINLINE void dComIfGd_drawXluListZxlu(); +DUSK_NOINLINE void dComIfGd_drawXluList2DScreen(); +DUSK_NOINLINE void dComIfGd_drawOpaList3Dlast(); +DUSK_NOINLINE void dComIfGd_draw2DOpa(); +DUSK_NOINLINE void dComIfGd_draw2DOpaTop(); +DUSK_NOINLINE void dComIfGd_draw2DXlu(); +DUSK_NOINLINE void dComIfGd_drawOpaListFilter(); +DUSK_NOINLINE void dComIfGd_drawIndScreen(); +DUSK_NOINLINE void dComIfGd_drawListZxlu(); +DUSK_NOINLINE void dComIfGd_drawShadow(Mtx param_0); +DUSK_NOINLINE void dComIfGd_imageDrawShadow(Mtx param_0); +DUSK_NOINLINE void dComIfGd_set3DlineMat(mDoExt_3DlineMat_c* param_0); +DUSK_NOINLINE void dComIfGd_set3DlineMatDark(mDoExt_3DlineMat_c* param_0); + +#if PLATFORM_WII || VERSION == VERSION_SHIELD_DEBUG +DUSK_NOINLINE void dComIfGd_setListCursor(); +#endif + +#else inline dSv_info_c* dComIfGs_getSaveInfo() { return &g_dComIfG_gameInfo.info; } @@ -1440,11 +2367,7 @@ inline BOOL dComIfGs_isTransformLV(int i_no) { return g_dComIfG_gameInfo.info.getPlayer().getPlayerStatusB().isTransformLV(i_no); } -#if TARGET_PC -inline cXyz dComIfGs_getHorseRestartPos() { -#else inline cXyz& dComIfGs_getHorseRestartPos() { -#endif return g_dComIfG_gameInfo.info.getPlayer().getHorsePlace().getPos(); } @@ -1465,11 +2388,7 @@ inline void dComIfGs_setHorseRestart(const char* i_stageName, cXyz& i_pos, s16 i g_dComIfG_gameInfo.info.getPlayer().getHorsePlace().set(i_stageName, i_pos, i_angle, i_roomNo); } -#if TARGET_PC -inline cXyz dComIfGs_getPlayerFieldLastStayPos() { -#else inline cXyz& dComIfGs_getPlayerFieldLastStayPos() { -#endif return g_dComIfG_gameInfo.info.getPlayer().getPlayerFieldLastStayInfo().getPos(); } @@ -1511,11 +2430,7 @@ inline void dComIfGs_setPlayerFieldLastStayInfo(const char* i_stage, cXyz& i_pos i_point, i_region); } -#if TARGET_PC -inline cXyz dComIfGs_getLastWarpMarkPlayerPos() { -#else inline cXyz& dComIfGs_getLastWarpMarkPlayerPos() { -#endif return g_dComIfG_gameInfo.info.getPlayer().getPlayerLastMarkInfo().getPos(); } @@ -1853,12 +2768,6 @@ inline void dComIfGs_addDeathCount() { g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().addDeathCount(); } -#if TARGET_PC -inline u16 dComIfGs_getDeathCount() { - return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getDeathCount(); -} -#endif - inline TEXT_SPAN dComIfGs_getPlayerName() { return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getPlayerName(); } @@ -3125,133 +4034,114 @@ inline void dComIfGp_particle_calcMenu() { } inline void dComIfGp_particle_draw(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormal(i_drawInfo); } } inline void dComIfGp_particle_drawFog(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormalFog(i_drawInfo); } } inline void dComIfGp_particle_drawP1(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormalP1(i_drawInfo); } } inline void dComIfGp_particle_drawProjection(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawProjection(i_drawInfo); } } inline void dComIfGp_particle_drawNormalPri0_A(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormalPri0_A(i_drawInfo); } } inline void dComIfGp_particle_drawNormalPri0_B(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormalPri0_B(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri0_A(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri0_A(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri0_B(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri0_B(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri1(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri1(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri2(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri2(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri3(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri3(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri4(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri4(i_drawInfo); } } inline void dComIfGp_particle_drawDarkworld(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawDarkworld(i_drawInfo); } } inline void dComIfGp_particle_drawScreen(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogScreen(i_drawInfo); } } inline void dComIfGp_particle_draw2Dgame(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2Dgame(i_drawInfo); } } inline void dComIfGp_particle_draw2Dfore(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2Dfore(i_drawInfo); } } inline void dComIfGp_particle_draw2Dback(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2Dback(i_drawInfo); } } inline void dComIfGp_particle_draw2DmenuFore(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2DmenuFore(i_drawInfo); } } inline void dComIfGp_particle_draw2DmenuBack(JPADrawInfo* i_drawInfo) { - ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2DmenuBack(i_drawInfo); } @@ -4771,150 +5661,114 @@ inline void dComIfGd_setViewport(view_port_class* port) { } inline void dComIfGd_entryZSortListZxlu(J3DPacket* i_packet, cXyz& param_1) { - ZoneScoped; g_dComIfG_gameInfo.drawlist.entryZSortListZxlu(i_packet, param_1); } inline void dComIfGd_entryZSortXluList(J3DPacket* i_packet, cXyz& param_1) { - ZoneScoped; g_dComIfG_gameInfo.drawlist.entryZSortXluList(i_packet, param_1); } inline void dComIfGd_drawCopy2D() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawCopy2D(); } inline void dComIfGd_drawOpaListSky() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListSky(); } inline void dComIfGd_drawXluListSky() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListSky(); } inline void dComIfGd_drawOpaListBG() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListBG(); } inline void dComIfGd_drawOpaListDarkBG() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListDarkBG(); } inline void dComIfGd_drawOpaListMiddle() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListMiddle(); } inline void dComIfGd_drawOpaList() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaList(); } inline void dComIfGd_drawOpaListDark() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListDark(); } inline void dComIfGd_drawOpaListPacket() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListPacket(); } inline void dComIfGd_drawXluListBG() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListBG(); } inline void dComIfGd_drawXluListDarkBG() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListDarkBG(); } inline void dComIfGd_drawXluList() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluList(); } inline void dComIfGd_drawXluListDark() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListDark(); } -#if TARGET_PC -void dComIfGd_drawXluListInvisible(); -#else inline void dComIfGd_drawXluListInvisible() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListInvisible(); } -#endif -#if TARGET_PC -void dComIfGd_drawOpaListInvisible(); -#else inline void dComIfGd_drawOpaListInvisible() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListInvisible(); } -#endif inline void dComIfGd_drawXluListZxlu() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListZxlu(); } inline void dComIfGd_drawXluList2DScreen() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluList2DScreen(); } inline void dComIfGd_drawOpaList3Dlast() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaList3Dlast(); } inline void dComIfGd_draw2DOpa() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.draw2DOpa(); } inline void dComIfGd_draw2DOpaTop() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.draw2DOpaTop(); } inline void dComIfGd_draw2DXlu() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.draw2DXlu(); } inline void dComIfGd_drawOpaListFilter() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListFilter(); } inline void dComIfGd_drawIndScreen() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListP0(); } inline void dComIfGd_drawListZxlu() { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListZxlu(); } inline void dComIfGd_drawShadow(Mtx param_0) { - ZoneScoped; g_dComIfG_gameInfo.drawlist.drawShadow(param_0); } inline void dComIfGd_imageDrawShadow(Mtx param_0) { - ZoneScoped; g_dComIfG_gameInfo.drawlist.imageDrawShadow(param_0); } @@ -4933,4 +5787,6 @@ inline void dComIfGd_setListCursor() { } #endif +#endif + #endif /* D_COM_D_COM_INF_GAME_H */ diff --git a/include/d/d_file_sel_info.h b/include/d/d_file_sel_info.h index 973349c469..37dadbeb1b 100644 --- a/include/d/d_file_sel_info.h +++ b/include/d/d_file_sel_info.h @@ -37,7 +37,7 @@ public: CPaneMgrAlpha* getNoDatBase() { return mNoDatBase; } void draw() { _draw(); } -private: +// private: /* 0x04 */ JKRArchive* mArchive; /* 0x08 */ dDlst_FileInfo_c mFileInfo; /* 0x1C */ u8 field_0x1c[4]; diff --git a/include/d/d_file_select.h b/include/d/d_file_select.h index 634c0db352..48d81d9ad5 100644 --- a/include/d/d_file_select.h +++ b/include/d/d_file_select.h @@ -9,42 +9,13 @@ #include "JSystem/J3DGraphLoader/J3DModelLoader.h" #include "JSystem/J3DGraphLoader/J3DAnmLoader.h" +#if TARGET_PC +#include "mods/svc/game_mode.h" +#endif + class dFile_info_c; class J2DPicture; -#if TARGET_PC -static PaneCache mSelDtPanes[] = { - {MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false}, - {MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false}, - {MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false}, - {MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false}, - {MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false}, - {MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false}, - {MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false}, - {MULTI_CHAR('gray_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('b_base'), 0.0f, 0.0f, false}, - {MULTI_CHAR('b_base1'), 0.0f, 0.0f, false}, -}; - -static PaneCache fileSelPanes[] = { - {MULTI_CHAR('w_uzu00'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu01'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu02'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu03'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu04'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu05'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu06'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu07'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu08'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_uzu09'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_er_msg'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_er_msE'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_er_msR'), 0.0f, 0.0f, false}, - {MULTI_CHAR('er_for0'), 0.0f, 0.0f, false}, - {MULTI_CHAR('er_for1'), 0.0f, 0.0f, false}, -}; -#endif - class dDlst_FileSel_c : public dDlst_base_c { public: void draw(); @@ -420,6 +391,13 @@ public: bool pointerMenuSelect(); bool pointerCopyDataToSelect(); bool pointerYesNoSelect(bool errorSelect); + void backToDataSelectMove() { + headerTxtSet(0x43, 1, 0); + fileRecScaleAnmInitSet2(0.0f, 1.0f); + nameMoveAnmInitSet(0xd29, 0xd1f); + modoruTxtDispAnmInit(0); + mDataSelProc = DATASELPROC_NAME_TO_DATA_SELECT_MOVE; + } #endif void _draw(); void errorMoveAnmInitSet(int, int); @@ -733,6 +711,8 @@ public: #endif #ifdef TARGET_PC dDlst_FileSelFade_c mFadeDlst; + bool mGameModeSaveStartBuildUi = true; + GameModeNewSaveState mGameModeNewSaveState = GAME_MODE_STATE_PENDING; #endif #if PLATFORM_WII || PLATFORM_SHIELD diff --git a/include/d/d_item.h b/include/d/d_item.h index d9bdfd18c9..bf1252f9d6 100644 --- a/include/d/d_item.h +++ b/include/d/d_item.h @@ -11,7 +11,9 @@ public: static DUSK_GAME_DATA u8* mData; }; -void execItemGet(u8 item_id); +class fopAc_ac_c; +void execItemGet( + u8 item_id IF_DUSK_ARG(u32 item_give_tag = 0) IF_DUSK_ARG(fopAc_ac_c* giver = NULL)); void item_func_HEART(); void item_func_GREEN_RUPEE(); diff --git a/include/d/d_item_data.h b/include/d/d_item_data.h index 0044573976..7672174c87 100644 --- a/include/d/d_item_data.h +++ b/include/d/d_item_data.h @@ -142,7 +142,12 @@ enum { /* 0x2F */ dItemNo_WEAR_KOKIRI_e, /* 0x30 */ dItemNo_ARMOR_e, /* 0x31 */ dItemNo_WEAR_ZORA_e, +#if TARGET_PC /* 0x32 */ dItemNo_MAGIC_LV1_e, + dItemNo_SHADOW_CRYSTAL_e = dItemNo_MAGIC_LV1_e, +#else + /* 0x32 */ dItemNo_MAGIC_LV1_e, +#endif /* 0x33 */ dItemNo_DUNGEON_EXIT_2_e, /* 0x34 */ dItemNo_WALLET_LV1_e, /* 0x35 */ dItemNo_WALLET_LV2_e, diff --git a/include/d/d_kantera_icon_meter.h b/include/d/d_kantera_icon_meter.h index 04466df8be..67e2dff278 100644 --- a/include/d/d_kantera_icon_meter.h +++ b/include/d/d_kantera_icon_meter.h @@ -30,7 +30,7 @@ public: void drawSelf() { mpKanteraIcon->draw(); } -private: +// private: /* 0x04 */ dDlst_KanteraIcon_c* mpKanteraIcon; /* 0x08 */ CPaneMgr* mpParent; /* 0x0C */ CPaneMgr* mpGauge; diff --git a/include/d/d_menu_collect.h b/include/d/d_menu_collect.h index b7d5ec2976..0811fb00a6 100644 --- a/include/d/d_menu_collect.h +++ b/include/d/d_menu_collect.h @@ -15,49 +15,6 @@ class dMenu_Fishing_c; class dMenu_Skill_c; class dMenu_Insect_c; class dSelect_cursor_c; - -#if TARGET_PC -struct PaneCache { - u64 tag; - f32 origTransX; - f32 origTransY; - bool cached; -}; - -static PaneCache mpScreenPanes[] = { - {MULTI_CHAR('sa_tex_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('op_tex_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('heart_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('wolf_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('item_0_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('item_1_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('item_2_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('fish_3_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('lett_4_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('maki_5_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false}, - {MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false}, - {MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false}, - {MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false}, - {MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false}, - {MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false}, - {MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false}, - {MULTI_CHAR('kabu_6n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('t_t00'), 0.0f, 0.0f, false}, - {MULTI_CHAR('f_t00'), 0.0f, 0.0f, false}, - {MULTI_CHAR('itemn_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('infotxtn'), 0.0f, 0.0f, false}, - {MULTI_CHAR('sa_op_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('title_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('menu_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('w_er_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('center_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('info_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('lavel_n'), 0.0f, 0.0f, false}, - {MULTI_CHAR('modelbgn'), 0.0f, 0.0f, false}, -}; -#endif - class dMenu_Collect2D_c; class dMenu_Collect2DTop_c : public dDlst_base_c { public: @@ -172,7 +129,7 @@ public: J2DPicture* getBlackTex() { return mpBlackTex; } u8 getSubWindowOpenCheck() { return mSubWindowOpenCheck; } -private: +// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRExpHeap* mpSubHeap; /* 0x00C */ void* field_0xc; diff --git a/include/d/d_menu_fishing.h b/include/d/d_menu_fishing.h index 972aa8f7c4..f2e9419ce3 100644 --- a/include/d/d_menu_fishing.h +++ b/include/d/d_menu_fishing.h @@ -43,7 +43,7 @@ public: u8 getStatus() { return mStatus; } -private: +// 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 110298798a..2e5ee95772 100644 --- a/include/d/d_menu_fmap.h +++ b/include/d/d_menu_fmap.h @@ -270,7 +270,7 @@ public: static DUSK_GAME_DATA dMenu_Fmap_c* MyClass; -private: +// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRExpHeap* mpTalkHeap; /* 0x00C */ STControl* mpStick; diff --git a/include/d/d_menu_insect.h b/include/d/d_menu_insect.h index 90cfca7d5a..5e0b453949 100644 --- a/include/d/d_menu_insect.h +++ b/include/d/d_menu_insect.h @@ -60,7 +60,7 @@ public: u8 getStatus() { return mStatus; } -private: +// private: /* 0x04 */ JKRExpHeap* mpHeap; /* 0x08 */ JKRArchive* mpArchive; /* 0x0C */ STControl* mpStick; diff --git a/include/d/d_menu_letter.h b/include/d/d_menu_letter.h index 208f8b65a9..21747c7e78 100644 --- a/include/d/d_menu_letter.h +++ b/include/d/d_menu_letter.h @@ -64,7 +64,7 @@ public: u8 getStatus() { return mStatus; } -private: +// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRArchive* mpArchive; /* 0x00C */ STControl* mpStick; diff --git a/include/d/d_menu_option.h b/include/d/d_menu_option.h index 49e81f98ae..f158498dd7 100644 --- a/include/d/d_menu_option.h +++ b/include/d/d_menu_option.h @@ -117,7 +117,7 @@ public: return ((u8)mUseFlag & (u8)i_flag) != 0 ? true : false; } -private: +// private: /* 0x004 */ J2DScreen* mpBackScreen; /* 0x008 */ J2DScreen* mpScreen; /* 0x00C */ J2DScreen* mpClipScreen; diff --git a/include/d/d_menu_ring.h b/include/d/d_menu_ring.h index c7467d2a4b..d0f021339e 100644 --- a/include/d/d_menu_ring.h +++ b/include/d/d_menu_ring.h @@ -84,7 +84,7 @@ public: void drawFlag0() { mDrawFlag = 0; } void setStatus(u8 i_status) { mStatus = i_status; } -private: +// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ STControl* mpStick; /* 0x00C */ CSTControl* mpCStick; diff --git a/include/d/d_menu_save.h b/include/d/d_menu_save.h index d9d052aa33..7e94d1d1c9 100644 --- a/include/d/d_menu_save.h +++ b/include/d/d_menu_save.h @@ -278,7 +278,7 @@ public: u8 getEndStatus() { return mEndStatus; } void setUseType(u8 type) { mUseType = type; } -private: +// private: /* 0x0004 */ JKRArchive* mpArchive; /* 0x0008 */ mDoDvdThd_mountArchive_c* mpMount; /* 0x000C */ STControl* stick; diff --git a/include/d/d_menu_skill.h b/include/d/d_menu_skill.h index 2f9097b165..d21332c341 100644 --- a/include/d/d_menu_skill.h +++ b/include/d/d_menu_skill.h @@ -58,7 +58,7 @@ public: u8 getStatus() { return mStatus; } -private: +// private: /* 0x004 */ JKRExpHeap* mpHeap; /* 0x008 */ JKRArchive* mpArchive; /* 0x00C */ STControl* mpStick; diff --git a/include/d/d_meter2_draw.h b/include/d/d_meter2_draw.h index 10cded803d..aaad9dec70 100644 --- a/include/d/d_meter2_draw.h +++ b/include/d/d_meter2_draw.h @@ -154,7 +154,7 @@ public: } #endif -private: +// private: /* 0x004 */ item_params mItemParams[4]; /* 0x074 */ JKRExpHeap* heap; /* 0x078 */ J2DScreen* mpScreen; diff --git a/include/d/d_meter2_info.h b/include/d/d_meter2_info.h index 410cf642b1..377f51c34b 100644 --- a/include/d/d_meter2_info.h +++ b/include/d/d_meter2_info.h @@ -328,6 +328,150 @@ void dMeter2Info_onWide2D(); void dMeter2Info_offWide2D(); #endif +#if TARGET_PC +DUSK_NOINLINE void dMeter2Info_Initialize(); +DUSK_NOINLINE dMeterMap_c* dMeter2Info_getMeterMapClass(); +DUSK_NOINLINE dMw_c* dMeter2Info_getMenuWindowClass(); +DUSK_NOINLINE void dMeter2Info_setWindowStatus(u8 i_status); +DUSK_NOINLINE CPaneMgr* dMeter2Info_getMeterItemPanePtr(s32 i_idx); +DUSK_NOINLINE void dMeter2Info_getString( + u32 i_stringID, TEXT_SPAN o_string, JMSMesgEntry_c* i_msgEntry); +DUSK_NOINLINE void dMeter2Info_getStringKanji( + u32 i_stringID, TEXT_SPAN o_string, JMSMesgEntry_c* i_msgEntry); +DUSK_NOINLINE void dMeter2Info_getStringKana( + u32 i_stringID, TEXT_SPAN o_string, JMSMesgEntry_c* i_msgEntry); +DUSK_NOINLINE f32 dMeter2Info_getStringLength( + JUTFont* i_font, f32 param_2, f32 param_3, char* i_string); +DUSK_NOINLINE f32 dMeter2Info_getStringLength(J2DTextBox* i_textbox, char* i_string); +DUSK_NOINLINE void dMeter2Info_setHotSpringTimer(u8 i_slotNo); +DUSK_NOINLINE u16 dMeter2Info_getOilGaugeBackUp(); +DUSK_NOINLINE void dMeter2Info_setOilGaugeBackUp(u16 param_1); +DUSK_NOINLINE void dMeter2Info_setSaveStageName(const char* i_stageName); +DUSK_NOINLINE u8 dMeter2Info_getInsectSelectType(); +DUSK_NOINLINE void dMeter2Info_setInsectSelectType(u8 i_type); +DUSK_NOINLINE u8 dMeter2Info_getWarpStatus(); +DUSK_NOINLINE void dMeter2Info_setWarpStatus(u8 i_status); +DUSK_NOINLINE int dMeter2Info_readItemTexture(u8 i_itemNo, void* i_texBuf1, J2DPicture* i_pic1, + void* i_texBuf2, J2DPicture* i_pic2, void* i_texBuf3, J2DPicture* i_pic3, void* i_texBuf4, + J2DPicture* i_pic4, int param_9); +DUSK_NOINLINE void dMeter2Info_setItemColor( + u8 i_itemNo, J2DPicture* i_pic1, J2DPicture* i_pic2, J2DPicture* i_pic3, J2DPicture* i_pic4); +DUSK_NOINLINE u8 dMeter2Info_getWarpRoomNo(); +DUSK_NOINLINE u8 dMeter2Info_getWarpPlayerNo(); +DUSK_NOINLINE cXyz& dMeter2Info_getWarpPos(); +DUSK_NOINLINE const char* dMeter2Info_getWarpStageName(); +DUSK_NOINLINE void dMeter2Info_setNowCount(u8 i_count); +DUSK_NOINLINE void dMeter2Info_setMaxCount(u8 i_count); +DUSK_NOINLINE BOOL dMeter2Info_isDirectUseItem(int param_0); +DUSK_NOINLINE bool dMeter2Info_isUseButton(int i_buttonBit); +DUSK_NOINLINE void dMeter2Info_setMeterMapClass(dMeterMap_c* i_map); +DUSK_NOINLINE void dMeter2Info_decHotSpringTimer(); +DUSK_NOINLINE void dMeter2Info_allUseButton(); +DUSK_NOINLINE void dMeter2Info_offUseButton(int i_buttonBit); +DUSK_NOINLINE void dMeter2Info_resetGameStatus(); +DUSK_NOINLINE void dMeter2Info_onGameStatus(int i_status); +DUSK_NOINLINE void dMeter2Info_setMapStatus(u8 i_status); +DUSK_NOINLINE u8 dMeter2Info_getMapStatus(); +DUSK_NOINLINE void dMeter2Info_setPauseStatus(u8 i_status); +DUSK_NOINLINE void dMeter2Info_resetPauseStatus(); +DUSK_NOINLINE u8 dMeter2Info_getPauseStatus(); +DUSK_NOINLINE bool dMeter2Info_isGameStatus(int i_status); +DUSK_NOINLINE bool dMeter2Info_isTouchKeyCheck(int i_status); +DUSK_NOINLINE void dMeter2Info_setMapKeyDirection(u16 i_direction); +DUSK_NOINLINE bool dMeter2Info_isSub2DStatus(int i_flag); +DUSK_NOINLINE void dMeter2Info_offMenuInForce(int i_flag); +DUSK_NOINLINE bool dMeter2Info_isMenuInForce(int i_flag); +DUSK_NOINLINE void dMeter2Info_setMenuWindowClass(dMw_c* i_menu); +DUSK_NOINLINE void dMeter2Info_resetWarpStatus(); +DUSK_NOINLINE u16 dMeter2Info_getMapKeyDirection(); +DUSK_NOINLINE u8 dMeter2Info_getWindowStatus(); +DUSK_NOINLINE void dMeter2Info_setMsgResource(void* i_res); +DUSK_NOINLINE void dMeter2Info_setStageMsgResource(void* i_res); +DUSK_NOINLINE void dMeter2Info_setMsgUnitResource(void* i_res); +DUSK_NOINLINE void* dMeter2Info_getMsgResource(); +DUSK_NOINLINE void* dMeter2Info_getStageMsgResource(); +DUSK_NOINLINE void* dMeter2Info_getMsgUnitResource(); +DUSK_NOINLINE u8 dMeter2Info_getLightDropGetFlag(int i_no); +DUSK_NOINLINE s32 dMeter2Info_getMeterStringType(); +DUSK_NOINLINE void dMeter2Info_setMeterClass(dMeter2_c* i_meter); +DUSK_NOINLINE void dMeter2Info_onLifeGaugeSE(); +DUSK_NOINLINE void dMeter2Info_offLifeGaugeSE(); +DUSK_NOINLINE u8 dMeter2Info_getLifeGaugeSE(); +DUSK_NOINLINE const char* dMeter2Info_getSaveStageName(); +DUSK_NOINLINE void dMeter2Info_onShopTalkFlag(); +DUSK_NOINLINE void dMeter2Info_setLightDropGetFlag(int i_no, u8 i_flag); +DUSK_NOINLINE u8 dMeter2Info_getRentalBombBag(); +DUSK_NOINLINE u8 dMeter2Info_getMiniGameItemSetFlag(); +DUSK_NOINLINE void dMeter2Info_setMiniGameItem(u8 i_minigameFlag); +DUSK_NOINLINE void dMeter2Info_resetMiniGameItem(bool i_saveItem); +DUSK_NOINLINE void dMeter2Info_setTableMapRegionNo(u8 i_regionNo); +DUSK_NOINLINE void dMeter2Info_setGoldWolfMapType(u8 i_mapType); +DUSK_NOINLINE void dMeter2Info_changeWater(u8 i_slotNo); +DUSK_NOINLINE void dMeter2Info_warpInProc(); +DUSK_NOINLINE void dMeter2Info_warpOutProc(); +DUSK_NOINLINE dMeter2_c* dMeter2Info_getMeterClass(); +DUSK_NOINLINE s16 dMeter2Info_getMsgKeyWaitTimer(); +DUSK_NOINLINE void dMeter2Info_onDirectUseItem(int param_0); +DUSK_NOINLINE void dMeter2Info_setFloatingFlow(u16 i_flowID, s16 i_msgTimer, bool i_wakuVisible); +DUSK_NOINLINE u8 dMeter2Info_getGameOverType(); +DUSK_NOINLINE void dMeter2Info_setGameOverType(u8 i_gameoverType); +DUSK_NOINLINE void dMeter2Info_setMsgKeyWaitTimer(s16 i_waitTimer); +DUSK_NOINLINE s32 dMeter2Info_getMsgTimeMs(); +DUSK_NOINLINE s32 dMeter2Info_getTimeMs(); +DUSK_NOINLINE void dMeter2Info_setMsgTimeMs(s32 i_msgTime); +DUSK_NOINLINE void dMeter2Info_setTimeMs(s32 i_time); +DUSK_NOINLINE u8 dMeter2Info_getNowCount(); +DUSK_NOINLINE u8 dMeter2Info_getMaxCount(); +DUSK_NOINLINE void dMeter2Info_setScopeZoomPointer(u8 param_0); +DUSK_NOINLINE void dMeter2Info_decMsgKeyWaitTimer(); +DUSK_NOINLINE bool dMeter2Info_isFloatingMessageVisible(); +DUSK_NOINLINE u8 dMeter2Info_getItemExplainWindowStatus(); +DUSK_NOINLINE void dMeter2Info_setItemExplainWindowStatus(u8 i_value); +DUSK_NOINLINE void dMeter2Info_resetDirectUseItem(); +DUSK_NOINLINE u16 dMeter2Info_getFloatingFlowID(); +DUSK_NOINLINE u8 dMeter2Info_getCollectCursorPosX(); +DUSK_NOINLINE u8 dMeter2Info_getCollectCursorPosY(); +DUSK_NOINLINE void dMeter2Info_setCollectCursorPosXY(u8 x, u8 y); +DUSK_NOINLINE void dMeter2Info_onBlinkButton(int i_flag); +DUSK_NOINLINE bool dMeter2Info_isFloatingMessageWakuVisible(); +DUSK_NOINLINE bool dMeter2Info_isBlinkButton(int i_flag); +DUSK_NOINLINE void dMeter2Info_resetBlinkButton(); +DUSK_NOINLINE void dMeter2Info_resetFloatingMessage(); +DUSK_NOINLINE s16 dMeter2Info_decFloatingMessageTimer(); +DUSK_NOINLINE s16 dMeter2Info_getFloatingMessageTimer(); +DUSK_NOINLINE u16 dMeter2Info_getFloatingMessageID(); +DUSK_NOINLINE s16 dMeter2Info_getHorseLifeCount(); +DUSK_NOINLINE void dMeter2Info_setMeterString(s32 i_string); +DUSK_NOINLINE void dMeter2Info_resetMeterString(); +DUSK_NOINLINE void dMeter2Info_setMeterItemPanePtr(int i_no, CPaneMgr* i_pane); +DUSK_NOINLINE bool dMeter2Info_isShopTalkFlag(); +DUSK_NOINLINE void dMeter2Info_setMapDrugFlag(bool i_flag); +DUSK_NOINLINE bool dMeter2Info_isTempBit(int i_bit); +DUSK_NOINLINE void dMeter2Info_offSub2DStatus(int i_bit); +DUSK_NOINLINE void dMeter2Info_onSub2DStatus(int i_bit); +DUSK_NOINLINE void dMeter2Info_set2DWidth(f32 i_width); +DUSK_NOINLINE void dMeter2Info_set2DHeight(f32 i_height); +DUSK_NOINLINE void dMeter2Info_set2DPosH(f32 i_posH); +DUSK_NOINLINE void dMeter2Info_set2DPosV(f32 i_posV); +DUSK_NOINLINE void dMeter2Info_offShopTalkFlag(); +DUSK_NOINLINE void dMeter2Info_onUseButton(int i_button); +DUSK_NOINLINE f32 dMeter2Info_get2DWidth(); +DUSK_NOINLINE f32 dMeter2Info_get2DHeight(); +DUSK_NOINLINE void dMeter2Info_setWarpInfo(const char* i_stageName, const cXyz& i_position, + s16 i_angle, u8 i_roomNo, u8 param_4, u8 i_warpPlayerNo); +DUSK_NOINLINE u8 dMeter2Info_getTableMapRegionNo(); +DUSK_NOINLINE u8 dMeter2Info_getGoldWolfMapType(); +DUSK_NOINLINE bool dMeter2Info_isWindowAccept(int param_0); +DUSK_NOINLINE void dMeter2Info_setHorseLifeCount(s16 i_count); +DUSK_NOINLINE void dMeter2Info_offTempBit(int i_bit); +DUSK_NOINLINE void dMeter2Info_onTempBit(int i_bit); +DUSK_NOINLINE void dMeter2Info_setFloatingMessage(u16 i_msgID, s16 i_msgTimer, bool i_wakuVisible); +DUSK_NOINLINE void dMeter2Info_setMiniGameCount(s8 i_count); +DUSK_NOINLINE void dMeter2Info_onWindowAccept(int param_0); +DUSK_NOINLINE void dMeter2Info_offWindowAccept(int param_0); +DUSK_NOINLINE void dMeter2Info_onMenuInForce(int param_0); + +#else inline void dMeter2Info_Initialize() { g_meter2_info.init(); } @@ -865,4 +1009,6 @@ inline void dMeter2Info_onMenuInForce(int param_0) { g_meter2_info.onMenuInForce(param_0); } +#endif + #endif /* D_METER_D_METER2_INFO_H */ diff --git a/include/d/d_msg_class.h b/include/d/d_msg_class.h index a8bba32351..c70345ca92 100644 --- a/include/d/d_msg_class.h +++ b/include/d/d_msg_class.h @@ -27,18 +27,20 @@ public: // Attributes /* 0x04 */ BE(u16) message_id; - /* 0x06 */ BE(u16) event_label_id; - /* 0x08 */ u8 se_speaker; - /* 0x09 */ u8 fuki_kind; - /* 0x0A */ u8 output_type; - /* 0x0B */ u8 fuki_pos_type; - /* 0x0C */ u8 unk_0xc; - /* 0x0D */ u8 unk_0xd; - /* 0x0E */ u8 se_mood; - /* 0x0F */ u8 camera_id; - /* 0x10 */ u8 base_anm_id; - /* 0x11 */ u8 face_anm_id; - /* 0x12 */ BE(u16) unk_0x12; + /* 0x06 */ BE(u16) event_label_id; // saveBitLabels index set when the message displays + /* 0x08 */ u8 speaker; // Z2SpeechMgr2 voice bank ID + /* 0x09 */ u8 box_kind; // screen class, see dMsgObject_c::talkStartInit + /* 0x0A */ u8 draw_type; // text pacing, see jmessage_tSequenceProcessor::do_begin + /* 0x0B */ u8 box_position; // see dMsgObject_c::fukiPosCalc + /* 0x0C */ u8 item_no; // unused; legacy dItemNo, 0xFF = none + /* 0x0D */ u8 line_alignment; // 0 centered (JP only), 1 left; also copied to + // jmessage_tReference::mForm + /* 0x0E */ u8 speaker_mood; // grunt emotion index for the voice bank + /* 0x0F */ u8 camera_attr; // 1-10 talk-actor slot, >=11 talk-camera style + /* 0x10 */ u8 talk_anim; // NPC talk motion attribute + /* 0x11 */ u8 face_anim; // NPC talk face attribute + /* 0x12 */ u8 lines_per_page; // unused; runtime uses getLineMax() + /* 0x13 */ u8 _pad; }; class JMSMesgInfo_c { @@ -119,6 +121,25 @@ struct jmessage_tReference : public JMessage::TReference { void setDemoFrame(u32 i_frame) { mDemoFrame = i_frame; } void setTopColorType(u8 i_colorType) { mTopColorType = i_colorType; } void setNowColorType(u8 i_colorType) { mNowColorType = i_colorType; } +#if TARGET_PC + void resetColors() { + mNowCCColor = 0xFFFFFFFF; + mNowGCColor = 0xFFFFFFFF; + mTopCCColor = 0xFFFFFFFF; + mTopGCColor = 0xFFFFFFFF; + mNowFullColor = false; + mTopFullColor = false; + } + void setFullColor(u32 ccColor, u32 gcColor) { + mNowCCColor = ccColor; + mNowGCColor = gcColor; + mNowFullColor = true; + } + void clearNowFullColor() { mNowFullColor = false; } + bool hasTopFullColor() { return mTopFullColor; } + u32 getTopCCColor() { return mTopCCColor; } + u32 getTopGCColor() { return mTopGCColor; } +#endif void setTopTagScale(u16 i_tagScale) { mTopTagScale = i_tagScale; } void setNowTagScale(u16 i_tagScale) { mNowTagScale = i_tagScale; } void setRevoMessageID(u32 i_msgID) { mRevoMessageID = i_msgID; } @@ -363,6 +384,14 @@ struct jmessage_tReference : public JMessage::TReference { /* 0x1274 */ bool mSelectSetCancelFlag; /* 0x1275 */ bool mBombNameUseFlag; /* 0x1276 */ u8 mBatchColorFlag; +#if TARGET_PC + u32 mNowCCColor; + u32 mNowGCColor; + u32 mTopCCColor; + u32 mTopGCColor; + bool mNowFullColor; + bool mTopFullColor; +#endif }; // Size: 0x1278 struct jmessage_tMeasureProcessor : public JMessage::TRenderingProcessor { @@ -460,6 +489,9 @@ struct jmessage_tRenderingProcessor : public JMessage::TRenderingProcessor { void do_selwidthcenter(int); void do_heightcenter(); void do_color(u8); +#if TARGET_PC + void do_fullcolor(u32 ccColor, u32 gcColor); +#endif void do_scale(f32); void do_linedown(s16); void do_transY(s16, bool); @@ -638,6 +670,9 @@ struct jmessage_string_tRenderingProcessor : public JMessage::TRenderingProcesso void do_rubystrcat(char*); void do_outfont(u8); void do_color(u8); +#if TARGET_PC + void do_fullcolor(u32 ccColor, u32 gcColor); +#endif void do_scale(f32); void do_linedown(s16); void do_numset(s16); diff --git a/include/d/d_msg_flow.h b/include/d/d_msg_flow.h index 233dad7170..cff5857cb7 100644 --- a/include/d/d_msg_flow.h +++ b/include/d/d_msg_flow.h @@ -22,7 +22,7 @@ struct msg_class; // all mesg_flow_node structs members might be wrong struct mesg_flow_node { /* 0x00 */ u8 type; - /* 0x01 */ u8 field_0x1; + /* 0x01 */ u8 subtype; /* 0x02 */ BE(u16) msg_index; /* 0x04 */ BE(u16) next_node_idx; /* 0x06 */ BE(u16) unk_0x6; @@ -30,7 +30,7 @@ struct mesg_flow_node { struct mesg_flow_node_branch { /* 0x00 */ u8 type; - /* 0x01 */ u8 field_0x1; + /* 0x01 */ u8 result_count; /* 0x02 */ BE(u16) query_idx; /* 0x04 */ BE(u16) param; /* 0x06 */ BE(u16) next_node_idx; diff --git a/include/d/d_msg_object.h b/include/d/d_msg_object.h index 7ca53064a7..3e8c535800 100644 --- a/include/d/d_msg_object.h +++ b/include/d/d_msg_object.h @@ -280,13 +280,13 @@ public: /* 0x150 */ f32 field_0x150; /* 0x154 */ u32 mMessageID; /* 0x158 */ u32 field_0x158; - /* 0x15C */ u32 field_0x15c; + /* 0x15C */ u32 mSelectMessageID; // message ID of the selection options message; 1000 = none /* 0x160 */ int mIdx; /* 0x164 */ u16 mNodeIdx; /* 0x166 */ u16 field_0x166; /* 0x168 */ u16 field_0x168; /* 0x16A */ s16 field_0x16a; - /* 0x16C */ s16 field_0x16c; + /* 0x16C */ s16 mCurrentGroupID; // group whose BMG is parsed; -1 none, 0 common, 1-8 stage /* 0x16E */ s16 field_0x16e; /* 0x170 */ s16 mNowTalkFlowNo; /* 0x172 */ s16 field_0x172; diff --git a/include/d/d_msg_out_font.h b/include/d/d_msg_out_font.h index 2b2d0fa33d..c6715757eb 100644 --- a/include/d/d_msg_out_font.h +++ b/include/d/d_msg_out_font.h @@ -25,7 +25,7 @@ public: void setTextBoxPtr(J2DTextBox* p_textBox) { mpTextBoxPtr = p_textBox; } void resetType() { mType = 0x47; } -private: +// private: /* 0x04 */ J2DTextBox* mpTextBoxPtr; /* 0x08 */ f32 mPosX; /* 0x0C */ f32 mPosY; @@ -53,7 +53,7 @@ public: void setRupeeColor(u8 color) { mRupeeColor = color; } -private: +// private: /* 0x004 */ COutFontSet_c* mpOfs[35]; /* 0x090 */ J2DPicture* mpPane[70]; /* 0x1A8 */ f32 mAlphaRatio; diff --git a/include/d/d_name.h b/include/d/d_name.h index 68a4930baf..ddd99127c6 100644 --- a/include/d/d_name.h +++ b/include/d/d_name.h @@ -12,34 +12,6 @@ class J2DTextBox; class JUTFont; class STControl; -#if TARGET_PC -struct PaneCache { - u64 tag; - f32 origTransX; - f32 origTransY; - bool cached; -}; - -static PaneCache l_tagName[] = { - {MULTI_CHAR('m_00_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_3'), 0.0f, 0.0f, false}, - {MULTI_CHAR('m_01_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_2'), 0.0f, 0.0f, false}, - {MULTI_CHAR('m03_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_1'), 0.0f, 0.0f, false}, - {MULTI_CHAR('m_05_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_0'), 0.0f, 0.0f, false}, - {MULTI_CHAR('m_07_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_4'), 0.0f, 0.0f, false}, - {MULTI_CHAR('m_09_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_3'), 0.0f, 0.0f, false}, - {MULTI_CHAR('m_10_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_2'), 0.0f, 0.0f, false}, - {MULTI_CHAR('m12_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_0'), 0.0f, 0.0f, false}, -}; - -static PaneCache l_nameTagName[] = { - {MULTI_CHAR('name_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_07'), 0.0f, 0.0f, false}, -}; - -static PaneCache l_nameCurTagName[] = { - {MULTI_CHAR('s__n_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_07'), 0.0f, 0.0f, false}, -}; -#endif - class dNm_HIO_c { public: dNm_HIO_c(); diff --git a/include/d/d_stage.h b/include/d/d_stage.h index 53fbbac0d9..487488d7c6 100644 --- a/include/d/d_stage.h +++ b/include/d/d_stage.h @@ -1351,6 +1351,9 @@ enum dStage_SaveTbl { const char* dStage_getName2(s16, s8); dStage_objectNameInf* dStage_searchName(const char*); +#if TARGET_PC +dStage_objectNameInf* dStage_searchNameCI(const char*); +#endif static int dStage_stageKeepTresureInit(dStage_dt_c*, void*, int, void*); static int dStage_filiInfo2Init(dStage_dt_c*, void*, int, void*); static int dStage_mapPathInitCommonLayer(dStage_dt_c*, void*, int, void*); diff --git a/include/f_op/f_op_actor.h b/include/f_op/f_op_actor.h index 1dce990866..2ac262ced1 100644 --- a/include/f_op/f_op_actor.h +++ b/include/f_op/f_op_actor.h @@ -318,6 +318,11 @@ public: /* 0x566 */ s8 field_0x566; /* 0x567 */ s8 field_0x567; +#if TARGET_PC + u32 mItemGiveTag; + u8 mItemGiveOriginalNo; +#endif + #if !__MWERKS__ s8 actor_last_base_field; #endif diff --git a/include/f_op/f_op_actor_mng.h b/include/f_op/f_op_actor_mng.h index 6461c823d1..7aa3bf2156 100644 --- a/include/f_op/f_op_actor_mng.h +++ b/include/f_op/f_op_actor_mng.h @@ -79,6 +79,10 @@ struct fopAcM_prm_class { /* 0x1C */ fpc_ProcID parent_id; /* 0x20 */ s8 argument; /* 0x21 */ s8 room_no; +#if TARGET_PC + u32 mItemGiveTag; + u8 mItemGiveOriginalNo; +#endif }; struct fopAcM_search4ev_prm { @@ -516,8 +520,9 @@ s32 fopAcM_SearchByName(s16 i_procName, fopAc_ac_c** i_outActor); fopAcM_prm_class* fopAcM_CreateAppend(); fopAcM_prm_class* createAppend(u16 i_setId, u32 i_parameters, const cXyz* i_pos, int i_roomNo, - const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, - fpc_ProcID i_parentId); + const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, + fpc_ProcID i_parentId IF_DUSK_ARG(u32 i_itemGiveTag = 0) + IF_DUSK_ARG(u8 i_itemOriginalNo = 0xFF)); void fopAcM_Log(fopAc_ac_c const* i_actor, char const* i_message); @@ -526,19 +531,22 @@ s32 fopAcM_delete(fopAc_ac_c* i_actor); s32 fopAcM_delete(fpc_ProcID i_actorID); fpc_ProcID fopAcM_create(s16 i_procName, u16 i_setId, u32 i_parameters, const cXyz* i_pos, - int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, - createFunc i_createFunc); + int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, + createFunc i_createFunc IF_DUSK_ARG(u32 i_itemGiveTag = 0) + IF_DUSK_ARG(u8 i_itemOriginalNo = 0xFF)); fpc_ProcID fopAcM_create(s16 i_procName, u32 i_parameters, const cXyz* i_pos, int i_roomNo, - const csXyz* i_angle, const cXyz* i_scale, s8 i_argument); + const csXyz* i_angle, const cXyz* i_scale, s8 i_argument IF_DUSK_ARG(u32 i_itemGiveTag = 0) + IF_DUSK_ARG(u8 i_itemOriginalNo = 0xFF)); inline fpc_ProcID fopAcM_Create(s16 i_procName, createFunc i_createFunc, void* params) { return fpcM_Create(i_procName, i_createFunc,params); } fopAc_ac_c* fopAcM_fastCreate(s16 i_procName, u32 i_parameters, const cXyz* i_pos, int i_roomNo, - const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, - createFunc i_createFunc, void* i_createFuncData); + const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, createFunc i_createFunc, + void* i_createFuncData IF_DUSK_ARG(u32 i_itemGiveTag = 0) + IF_DUSK_ARG(u8 i_itemOriginalNo = 0xFF)); fopAc_ac_c* fopAcM_fastCreate(const char* i_actorname, u32 i_parameters, const cXyz* i_pos, int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, @@ -623,11 +631,11 @@ fopAc_ac_c* fopAcM_getItemEventPartner(const fopAc_ac_c*); fopAc_ac_c* fopAcM_getEventPartner(const fopAc_ac_c*); fpc_ProcID fopAcM_createItemForPresentDemo(cXyz const* i_pos, int i_itemNo, u8 param_2, - int i_itemBitNo, int i_roomNo, csXyz const* i_angle, - cXyz const* i_scale); + int i_itemBitNo, int i_roomNo, csXyz const* i_angle, + cXyz const* i_scale IF_DUSK_ARG(u32 i_itemGiveTag = 0)); fpc_ProcID fopAcM_createItemForTrBoxDemo(cXyz const* i_pos, int i_itemNo, int i_itemBitNo, - int i_roomNo, csXyz const* i_angle, cXyz const* i_scale); + int i_roomNo, csXyz const* i_angle, cXyz const* i_scale IF_DUSK_ARG(u32 i_itemGiveTag = 0)); u8 fopAcM_getItemNoFromTableNo(u8 i_tableNo); @@ -641,11 +649,12 @@ fpc_ProcID fopAcM_createItemFromTable(cXyz const* i_pos, int i_tableNo, int i_it bool i_createDirect); fpc_ProcID fopAcM_createDemoItem(const cXyz* i_pos, int i_itemNo, int i_itemBitNo, - const csXyz* i_angle, int i_roomNo, const cXyz* scale, u8 param_7); + const csXyz* i_angle, int i_roomNo, const cXyz* scale, + u8 param_7 IF_DUSK_ARG(u32 i_itemGiveTag = 0)); fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomNo, - const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF, - f32 i_speedY, int param_8); + const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF, f32 i_speedY, + int param_8 IF_DUSK_ARG(const char* i_itemCheckName = NULL)); fpc_ProcID fopAcM_createItemForMidBoss(const cXyz* i_pos, int i_itemNo, int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, int param_6, diff --git a/include/f_op/f_op_msg.h b/include/f_op/f_op_msg.h index 21694f0af1..1338732a4b 100644 --- a/include/f_op/f_op_msg.h +++ b/include/f_op/f_op_msg.h @@ -30,7 +30,7 @@ struct msg_class { /* 0xDC */ fopAc_ac_c* talk_actor; /* 0xE0 */ cXyz pos; /* 0xEC */ u32 msg_idx; - /* 0xF0 */ u32 field_0xf0; + /* 0xF0 */ u32 select_msg_idx; // selection options message ID; 1000 = none /* 0xF4 */ u32 field_0xf4; /* 0xF8 */ u16 mode; /* 0xFA */ u8 select_idx; diff --git a/include/f_op/f_op_msg_mng.h b/include/f_op/f_op_msg_mng.h index 330347fc18..35d0e28ea7 100644 --- a/include/f_op/f_op_msg_mng.h +++ b/include/f_op/f_op_msg_mng.h @@ -19,7 +19,7 @@ struct fopMsg_prm_class { /* 0x00 */ fopAc_ac_c* talk_actor; /* 0x04 */ cXyz pos; /* 0x10 */ u32 msg_idx; - /* 0x14 */ u32 field_0x14; + /* 0x14 */ u32 select_msg_idx; // selection options message ID; 1000 = none /* 0x18 */ fpc_ProcID field_0x18; }; // Size: 0x1C @@ -46,8 +46,8 @@ void fopMsgM_setMessageID(fpc_ProcID msg_id); void fopMsgM_destroyExpHeap(JKRExpHeap* i_heap); f32 fopMsgM_valueIncrease(int param_0, int param_1, u8 i_type); s32 fopMsgM_setStageLayer(void* i_process); -fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, fopAc_ac_c* i_talkActor, u32 param_2); -fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, u32 param_1); +fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, fopAc_ac_c* i_talkActor, u32 i_selectMsgIdx); +fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, u32 i_selectMsgIdx); fpc_ProcID fopMsgM_messageSetDemo(u32 i_msgidx); msg_class* fopMsgM_SearchByID(fpc_ProcID i_id); TEXT_SPAN fopMsgM_messageGet(TEXT_SPAN i_stringBuf, u32 i_msgId); diff --git a/include/global.h b/include/global.h index f77694c133..c16020936e 100644 --- a/include/global.h +++ b/include/global.h @@ -123,6 +123,14 @@ inline int __builtin_clz(unsigned int v) { #endif #define DUSK_GAME_EXTERN extern DUSK_GAME_DATA +#if defined(_MSC_VER) +#define DUSK_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define DUSK_NOINLINE __attribute__((noinline)) +#else +#define DUSK_NOINLINE +#endif + #define FAST_DIV(x, n) (x >> (n / 2)) #define SQUARE(x) ((x) * (x)) @@ -256,4 +264,23 @@ using std::isnan; #define DUSK_CONST IF_DUSK(const) #define DUSK_CONSTEXPR IF_DUSK(constexpr) +#if TARGET_PC && defined(DUSK_BUILDING_GAME) +#include "dusk/mods/item.hpp" +#define DUSK_ITEM_CHECK(name, item_no, giver) \ + (item_no) = ::dusk::mods::item_check_commit(name, (item_no), giver).itemNo +#define DUSK_ITEM_CHECK_EXPR(name, item_no, giver) \ + (::dusk::mods::item_check_commit(name, (item_no), giver).itemNo) +#define DUSK_ITEM_CHECK_PREVIEW(name, item_no, giver) \ + (item_no) = ::dusk::mods::item_check(name, (item_no), giver) +#define DUSK_ITEM_CHECK_PREVIEW_EXPR(name, item_no, giver) \ + (::dusk::mods::item_check(name, (item_no), giver)) +#define DUSK_GIVE_TAG(name) IF_DUSK_ARG(::dusk::mods::item_give_tag(name)) +#else +#define DUSK_ITEM_CHECK(name, item_no, giver) +#define DUSK_ITEM_CHECK_EXPR(name, item_no, giver) (item_no) +#define DUSK_ITEM_CHECK_PREVIEW(name, item_no, giver) +#define DUSK_ITEM_CHECK_PREVIEW_EXPR(name, item_no, giver) (item_no) +#define DUSK_GIVE_TAG(name) +#endif + #endif diff --git a/include/helpers/bits.hpp b/include/helpers/bits.hpp new file mode 100644 index 0000000000..73442d337b --- /dev/null +++ b/include/helpers/bits.hpp @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace dusk { +namespace detail { + +template +struct uint_of_size; + +template <> +struct uint_of_size<1> { + using type = uint8_t; +}; + +template <> +struct uint_of_size<2> { + using type = uint16_t; +}; + +template <> +struct uint_of_size<4> { + using type = uint32_t; +}; + +template <> +struct uint_of_size<8> { + using type = uint64_t; +}; + +template +using uint_of_size_t = uint_of_size::type; + +template + requires(std::is_trivially_copyable_v) +T unaligned_load(const void* source) noexcept { + T value; + std::memcpy(&value, source, sizeof(value)); + return value; +} + +template + requires(std::is_trivially_copyable_v) +void unaligned_store(void* destination, T value) noexcept { + std::memcpy(destination, &value, sizeof(value)); +} + +} // namespace detail + +template + requires(std::is_unsigned_v) +constexpr T bswap(T value) noexcept { + if constexpr (sizeof(T) == 1) { + return value; + } else if constexpr (sizeof(T) == 2) { + return static_cast((value << 8) | (value >> 8)); + } else if constexpr (sizeof(T) == 4) { + return static_cast(((value & 0x000000ffU) << 24) | ((value & 0x0000ff00U) << 8) | + ((value & 0x00ff0000U) >> 8) | ((value & 0xff000000U) >> 24)); + } else { + static_assert(sizeof(T) == 8); + return static_cast( + ((value & 0x00000000000000ffULL) << 56) | ((value & 0x000000000000ff00ULL) << 40) | + ((value & 0x0000000000ff0000ULL) << 24) | ((value & 0x00000000ff000000ULL) << 8) | + ((value & 0x000000ff00000000ULL) >> 8) | ((value & 0x0000ff0000000000ULL) >> 24) | + ((value & 0x00ff000000000000ULL) >> 40) | ((value & 0xff00000000000000ULL) >> 56)); + } +} + +/// Reads an unaligned integral value in the specified byte order. +template + requires(std::is_integral_v && !std::is_same_v) +T read_bits(const void* source, std::endian endian = std::endian::big) noexcept { + using Bits = std::make_unsigned_t; + Bits value = detail::unaligned_load(source); + if constexpr (sizeof(Bits) > 1) { + if (endian != std::endian::native) { + value = bswap(value); + } + } + return std::bit_cast(value); +} + +template + requires(std::is_integral_v && !std::is_same_v) +constexpr T read_bits(const uint8_t* source, std::endian endian = std::endian::big) noexcept { + if (!std::is_constant_evaluated()) { + return read_bits(static_cast(source), endian); + } + using Bits = std::make_unsigned_t; + Bits value{}; + if (endian == std::endian::big) { + for (size_t i = 0; i < sizeof(Bits); ++i) { + value = static_cast((value << 8) | source[i]); + } + } else { + for (size_t i = 0; i < sizeof(Bits); ++i) { + value |= static_cast(source[i]) << (i * 8); + } + } + return std::bit_cast(value); +} + +/// Reads an unaligned floating-point value in the specified byte order. +template + requires( + std::is_floating_point_v && requires { typename detail::uint_of_size_t; }) +T read_bits(const void* source, std::endian endian = std::endian::big) noexcept { + using Bits = detail::uint_of_size_t; + return std::bit_cast(read_bits(source, endian)); +} + +template + requires( + std::is_floating_point_v && requires { typename detail::uint_of_size_t; }) +constexpr T read_bits(const uint8_t* source, std::endian endian = std::endian::big) noexcept { + using Bits = detail::uint_of_size_t; + return std::bit_cast(read_bits(source, endian)); +} + +/// Writes an unaligned integral value in the specified byte order. +template + requires(std::is_integral_v && !std::is_same_v) +void write_bits(void* destination, T value, std::endian endian = std::endian::big) noexcept { + using Bits = std::make_unsigned_t; + Bits bits = std::bit_cast(value); + if constexpr (sizeof(Bits) > 1) { + if (endian != std::endian::native) { + bits = bswap(bits); + } + } + detail::unaligned_store(destination, bits); +} + +template + requires(std::is_integral_v && !std::is_same_v) +constexpr void write_bits( + uint8_t* destination, T value, std::endian endian = std::endian::big) noexcept { + if (!std::is_constant_evaluated()) { + write_bits(static_cast(destination), value, endian); + return; + } + using Bits = std::make_unsigned_t; + const Bits bits = std::bit_cast(value); + if (endian == std::endian::big) { + for (size_t i = 0; i < sizeof(Bits); ++i) { + destination[sizeof(Bits) - i - 1] = static_cast(bits >> (i * 8)); + } + } else { + for (size_t i = 0; i < sizeof(Bits); ++i) { + destination[i] = static_cast(bits >> (i * 8)); + } + } +} + +/// Writes an unaligned floating-point value in the specified byte order. +template + requires( + std::is_floating_point_v && requires { typename detail::uint_of_size_t; }) +void write_bits(void* destination, T value, std::endian endian = std::endian::big) noexcept { + using Bits = detail::uint_of_size_t; + write_bits(destination, std::bit_cast(value), endian); +} + +template + requires( + std::is_floating_point_v && requires { typename detail::uint_of_size_t; }) +constexpr void write_bits( + uint8_t* destination, T value, std::endian endian = std::endian::big) noexcept { + using Bits = detail::uint_of_size_t; + write_bits(destination, std::bit_cast(value), endian); +} + +} // namespace dusk diff --git a/include/m_Do/m_Do_MemCard.h b/include/m_Do/m_Do_MemCard.h index 555d7a6a9f..84fc246e09 100644 --- a/include/m_Do/m_Do_MemCard.h +++ b/include/m_Do/m_Do_MemCard.h @@ -112,6 +112,11 @@ public: mSerialNo = serial_no; } +#ifdef TARGET_PC + void setFileName(const std::string& fileName); + const char* getFileName(); +#endif + /* 0x0000 */ u8 mData[SAVEFILE_SIZE]; /* 0x1FBC */ u8 mChannel; /* 0x1FBD */ u8 mCopyToPos; @@ -124,6 +129,11 @@ public: /* 0x1FEC */ s32 mNandState; /* 0x1FF0 */ u64 mSerialNo; /* 0x1FF8 */ u32 mDataVersion; +#ifdef TARGET_PC + bool mInitialized; + std::string mFileName; +#endif + }; // Size: 0x2000 STATIC_ASSERT(sizeof(mDoMemCd_Ctrl_c) == 8192); @@ -230,4 +240,14 @@ inline s32 mDoMemCd_checkNANDFile() { } #endif +#ifdef TARGET_PC +inline void mDoMemCd_SetFileName(const std::string& fileName) { + g_mDoMemCd_control.setFileName(fileName); +} + +inline const char* mDoMemCd_GetFileName() { + return g_mDoMemCd_control.getFileName(); +} +#endif + #endif /* M_DO_M_DO_MEMCARD_H */ diff --git a/include/m_Do/m_Do_dvd_thread.h b/include/m_Do/m_Do_dvd_thread.h index 83dd4dec1e..ebb4253838 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; @@ -130,8 +130,8 @@ private: }; // Size = 0x28 struct mDoDvdThdStack { - u8 stack[4096]; -} ATTRIBUTE_ALIGN(16); + ATTRIBUTE_ALIGN(16) u8 stack[4096]; +}; struct mDoDvdThd { static s32 main(void*); diff --git a/libs/JSystem/include/JSystem/J3DGraphLoader/J3DModelLoader.h b/libs/JSystem/include/JSystem/J3DGraphLoader/J3DModelLoader.h index 05fc99785b..a0711fd796 100644 --- a/libs/JSystem/include/JSystem/J3DGraphLoader/J3DModelLoader.h +++ b/libs/JSystem/include/JSystem/J3DGraphLoader/J3DModelLoader.h @@ -9,6 +9,7 @@ class J3DModelData; class J3DMaterialTable; struct J3DModelHierarchy; +class J3DVertexData; #if TARGET_PC #define OFFSET_PTR_V0 BE(u32) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASCalc.h b/libs/JSystem/include/JSystem/JAudio2/JASCalc.h index 118ca1eb6e..0dde666d33 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASCalc.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASCalc.h @@ -2,6 +2,7 @@ #define JASCALC_H #include +#include #include /** @@ -10,16 +11,31 @@ */ struct JASCalc { static void imixcopy(const s16*, const s16*, s16*, u32); - static void bcopyfast(const void* src, void* dest, u32 size); +#if TARGET_PC + static void bcopyfast(const void* src, void* dest, u32 size) { + std::memcpy(dest, src, size); + } #if TARGET_ANDROID - static void _bcopy(const void* src, void* dest, u32 size); + static void _bcopy(const void* src, void* dest, u32 size) { #else - static void bcopy(const void* src, void* dest, u32 size); + static void bcopy(const void* src, void* dest, u32 size) { #endif - static void bzerofast(void* dest, u32 size); + std::memcpy(dest, src, size); + } + static void bzerofast(void* dest, u32 size) { + std::memset(dest, 0, size); + } #if TARGET_ANDROID - static void _bzero(void* dest, u32 size); + static void _bzero(void* dest, u32 size) { #else + static void bzero(void* dest, u32 size) { +#endif + std::memset(dest, 0, size); + } +#else + static void bcopyfast(const void* src, void* dest, u32 size); + static void bcopy(const void* src, void* dest, u32 size); + static void bzerofast(void* dest, u32 size); static void bzero(void* dest, u32 size); #endif static f32 pow2(f32); diff --git a/libs/JSystem/include/JSystem/JKernel/JKRArchive.h b/libs/JSystem/include/JSystem/JKernel/JKRArchive.h index 4ac5de7a46..bcedc7834e 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRArchive.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRArchive.h @@ -6,11 +6,17 @@ #include "global.h" #include "helpers/endian.h" +#if TARGET_PC +#include +#include +#include +#endif + class JKRHeap; /** * @ingroup jsystem-jkernel - * + * */ struct SArcHeader { /* 0x00 */ BE(u32) signature; @@ -25,7 +31,7 @@ struct SArcHeader { /** * @ingroup jsystem-jkernel - * + * */ struct SArcDataInfo { /* 0x00 */ BE(u32) num_nodes; @@ -59,7 +65,7 @@ extern u32 sCurrentDirID__10JKRArchive; // JKRArchive::sCurrentDirID /** * @ingroup jsystem-jkernel - * + * */ class JKRArchive : public JKRFileLoader { public: @@ -192,9 +198,7 @@ public: u32 countFile() const { return mArcInfoBlock->num_file_entries; } s32 countDirectory() const { return mArcInfoBlock->num_nodes; } u8 getMountMode() const { return mMountMode; } - bool isFileEntry(u32 param_0) const { - return getFileAttribute(param_0) & 1; - } + bool isFileEntry(u32 param_0) const { return getFileAttribute(param_0) & 1; } public: /* 0x00 */ // vtable @@ -210,7 +214,36 @@ public: /* 0x54 */ const char* mStringTable; #if TARGET_PC + u32 getFileSize(SDIFileEntry* entry) const; + void* getOverlayData(SDIFileEntry* entry, u32* outSize); + bool getOverlayFileSize(SDIFileEntry* entry, u32* outSize) const; + static void notifyOverlayFilesChanged(); + +protected: void** mFileData; + + struct ArcOverlayResource { + u32 entryIndex; + u32 size; + u64 generation; + }; + + // Resource pointers remain valid until removed through the JKR resource APIs. + // That way, any pointers to old overlay data remain valid even when the overlay changed. + std::unordered_map mArcOverlayResources; + mutable std::unordered_map mActiveArcOverlayResources; + mutable std::string mArcOverlaysPath; + mutable std::unordered_map mIdxToPathMap; + mutable bool mArcOverlaysPathResolved = false; + + bool buildArcOverlaysPath() const; + void buildIndexToPathMap(u32 dirIndex, const std::string& currentPath) const; + bool getOverlayPath(SDIFileEntry* entry, std::string& path) const; + void* getActiveOverlayData(SDIFileEntry* entry, u32* outSize) const; + bool copyOverlayData(void* buffer, u32 bufferSize, SDIFileEntry* entry, u32* outSize); + bool getOverlayResourceSize(const void* data, u32* outSize) const; + bool removeOverlayResource(void* resource, bool freeResource); + void removeAllOverlayResources(); #endif protected: @@ -234,7 +267,7 @@ public: } else if (attr & JKRARCHIVE_ATTR_YAZ0) { return COMPRESSION_YAZ0; } else { - return COMPRESSION_YAY0; + return COMPRESSION_YAY0; } } @@ -243,6 +276,9 @@ public: protected: static DUSK_GAME_DATA u32 sCurrentDirID; +#if TARGET_PC + static std::atomic sArcOverlayGeneration; +#endif }; inline JKRCompression JKRConvertAttrToCompressionType(int attr) { @@ -261,8 +297,8 @@ inline bool JKRRemoveResource(void* resource, JKRFileLoader* fileLoader) { return JKRFileLoader::removeResource(resource, fileLoader); } -inline JKRArchive* JKRMountArchive(void* ptr, JKRHeap* heap, - JKRArchive::EMountDirection mountDirection) { +inline JKRArchive* JKRMountArchive( + void* ptr, JKRHeap* heap, JKRArchive::EMountDirection mountDirection) { return JKRArchive::mount(ptr, heap, mountDirection); } diff --git a/libs/JSystem/include/JSystem/JMessage/processor.h b/libs/JSystem/include/JSystem/JMessage/processor.h index cfb20c2c46..efd4c3629e 100644 --- a/libs/JSystem/include/JSystem/JMessage/processor.h +++ b/libs/JSystem/include/JSystem/JMessage/processor.h @@ -174,10 +174,14 @@ struct TProcessor { } void on_character(int iCharacter) { do_character(iCharacter); } +#if TARGET_PC + const char* on_message_limited(u16 u16Index) const; +#else const char* on_message_limited(u16 u16Index) const { JUT_ASSERT(482, pResourceCache_!=NULL); return pResourceCache_->getMessageText_messageIndex(u16Index); } +#endif bool on_setBegin_isReady_() const { return do_setBegin_isReady_(); } @@ -195,6 +199,10 @@ struct TProcessor { return 1; } +#if TARGET_PC + void* getMessageEntry_messageCode(u16 u16Code, u16 u16Index) const; + const char* getMessageText_messageCode(u16 u16Code, u16 u16Index) const; +#else void* getMessageEntry_messageCode(u16 u16Code, u16 u16Index) const { const TResource* pResource = getResource_groupID(u16Code); @@ -214,6 +222,7 @@ struct TProcessor { return pResourceCache_->getMessageText_messageEntry(pEntry); } +#endif void stack_pushCurrent_(const char* pszText) { oStack_.push(getCurrent()); diff --git a/libs/JSystem/src/J3DGraphBase/J3DSys.cpp b/libs/JSystem/src/J3DGraphBase/J3DSys.cpp index d37b4608bf..bdacff4276 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DSys.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DSys.cpp @@ -23,9 +23,9 @@ DUSK_GAME_DATA Vec J3DSys::mParentS; DUSK_GAME_DATA J3DTexCoordScaleInfo J3DSys::sTexCoordScaleTable[8]; #if TARGET_PC // Original game bug, array is too small. -static u8 NullTexData[0x20] ATTRIBUTE_ALIGN(32) = {0}; +ATTRIBUTE_ALIGN(32) static u8 NullTexData[0x20] = {0}; #else -static u8 NullTexData[0x10] ATTRIBUTE_ALIGN(32) = {0}; +ATTRIBUTE_ALIGN(32) static u8 NullTexData[0x10] = {0}; #endif static Mtx j3dIdentityMtx = { diff --git a/libs/JSystem/src/JAudio2/JASCalc.cpp b/libs/JSystem/src/JAudio2/JASCalc.cpp index ba13c983e6..de0cf02295 100644 --- a/libs/JSystem/src/JAudio2/JASCalc.cpp +++ b/libs/JSystem/src/JAudio2/JASCalc.cpp @@ -11,6 +11,7 @@ void JASCalc::imixcopy(const s16* s1, const s16* s2, s16* dst, u32 n) { } } +#if !TARGET_PC void JASCalc::bcopyfast(const void* src, void* dest, u32 size) { JUT_ASSERT(226, (reinterpret_cast(src) & 0x03) == 0); JUT_ASSERT(227, (reinterpret_cast(dest) & 0x03) == 0); @@ -33,11 +34,7 @@ void JASCalc::bcopyfast(const void* src, void* dest, u32 size) { } } -#if TARGET_ANDROID -void JASCalc::_bcopy(const void* src, void* dest, u32 size) { -#else void JASCalc::bcopy(const void* src, void* dest, u32 size) { -#endif u32* usrc; u32* udest; @@ -94,11 +91,7 @@ void JASCalc::bzerofast(void* dest, u32 size) { } } -#if TARGET_ANDROID -void JASCalc::_bzero(void* dest, u32 size) { -#else void JASCalc::bzero(void* dest, u32 size) { -#endif u32* udest; u8* bdest = (u8*)dest; if ((size & 0x1f) == 0 && (reinterpret_cast(dest) & 0x1f) == 0) { @@ -139,6 +132,7 @@ void JASCalc::bzero(void* dest, u32 size) { } } } +#endif #if AVOID_UB DUSK_GAME_DATA s16 const JASCalc::CUTOFF_TO_IIR_TABLE[129][4] = { diff --git a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp index 04bec5dcc7..375447dfbb 100644 --- a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp @@ -99,14 +99,14 @@ void JASDsp::invalChannelAll() { DCInvalidateRange(CH_BUF, sizeof(TChannel) * DSP_CHANNELS); } -DUSK_GAME_DATA u8 const ATTRIBUTE_ALIGN(32) JASDsp::DSPADPCM_FILTER[64] = { +ATTRIBUTE_ALIGN(32) DUSK_GAME_DATA u8 const JASDsp::DSPADPCM_FILTER[64] = { 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x10, 0x00, 0xF8, 0x00, 0x0E, 0x00, 0xFA, 0x00, 0x0C, 0x00, 0xFC, 0x00, 0x12, 0x00, 0xF6, 0x00, 0x10, 0x68, 0xF7, 0x38, 0x12, 0xC0, 0xF7, 0x04, 0x14, 0x00, 0xF4, 0x00, 0x08, 0x00, 0xF8, 0x00, 0x04, 0x00, 0xFC, 0x00, 0xFC, 0x00, 0x04, 0x00, 0xFC, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, }; -DUSK_GAME_DATA u32 const ATTRIBUTE_ALIGN(32) JASDsp::DSPRES_FILTER[320] = { +ATTRIBUTE_ALIGN(32) DUSK_GAME_DATA u32 const JASDsp::DSPRES_FILTER[320] = { 0x0C3966AD, 0x0D46FFDF, 0x0B396696, diff --git a/libs/JSystem/src/JAudio2/JASResArcLoader.cpp b/libs/JSystem/src/JAudio2/JASResArcLoader.cpp index a55c73469c..57e6742c51 100644 --- a/libs/JSystem/src/JAudio2/JASResArcLoader.cpp +++ b/libs/JSystem/src/JAudio2/JASResArcLoader.cpp @@ -17,7 +17,7 @@ size_t JASResArcLoader::getResSize(JKRArchive const* i_archiveP, u16 i_resourceI return 0; } - return file->data_size; + return DUSK_IF_ELSE(i_archiveP->getFileSize(file), file->data_size); } size_t JASResArcLoader::getResMaxSize(JKRArchive const* i_archiveP) { @@ -27,9 +27,16 @@ size_t JASResArcLoader::getResMaxSize(JKRArchive const* i_archiveP) { for (index = 0; index < fileEntries; index++) { JKRArchive::SDIFileEntry* file = i_archiveP->findIdxResource(index); if (file) { +#if TARGET_PC + const u32 fileSize = i_archiveP->getFileSize(file); + if (maxSize < fileSize) { + maxSize = fileSize; + } +#else if (maxSize < file->data_size) { maxSize = file->data_size; } +#endif } } diff --git a/libs/JSystem/src/JAudio2/dsptask.cpp b/libs/JSystem/src/JAudio2/dsptask.cpp index 7d2e8dd2ad..ce91ae2e78 100644 --- a/libs/JSystem/src/JAudio2/dsptask.cpp +++ b/libs/JSystem/src/JAudio2/dsptask.cpp @@ -25,7 +25,7 @@ void DspHandShake(void*) { Dsp_Running_Start(); } -static u8 jdsp[7936] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) static u8 jdsp[7936] = { 0x02, 0x9F, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x02, 0x9F, 0x06, 0xA5, 0x02, 0x9F, 0x00, 0x4E, 0x12, 0x05, 0x02, 0xBF, 0x00, 0x57, 0x81, 0x00, 0x00, 0x9F, 0x10, 0x00, @@ -524,9 +524,9 @@ static u8 jdsp[7936] ATTRIBUTE_ALIGN(32) = { 0x80, 0x01, 0x02, 0xBF, 0x00, 0xF4, 0x02, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -static DSPTaskInfo audio_task ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static DSPTaskInfo audio_task; -static u8 AUDIO_YIELD_BUFFER[8192] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u8 AUDIO_YIELD_BUFFER[8192]; void DspBoot(void (*requestCallback)(void*)) { DspInitWork(); diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 99b1e63364..466335eae9 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -38,7 +38,7 @@ void JFWDisplay::ctor_subroutine(bool enableAlpha) { mTickRate = 0; mCombinationRatio = 0.0f; field_0x30 = 0; - field_0x2c = OSGetTick(); + field_0x2c = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); field_0x34 = 0; field_0x48 = 0; field_0x4a = 0; @@ -258,7 +258,7 @@ void JFWDisplay::beginRender() { waitForTick(mTickRate, mFrameRate); JUTVideo::getManager()->waitRetraceIfNeed(); - OSTick tick = OSGetTick(); + OSTick tick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); field_0x30 = tick - field_0x2c; field_0x2c = tick; field_0x34 = field_0x2c - JUTVideo::getVideoLastTick(); @@ -371,7 +371,7 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast(sleepTime) / static_cast(targetNs)); } @@ -381,15 +381,12 @@ static void waitForTick(u32 p1, u16 p2) { #if TARGET_PC static Limiter limiter; - if (dusk::frame_interp::is_enabled() && !dusk::getTransientSettings().skipFrameRateLimit) { - dusk::frameUsagePct = 0.f; - return; + if (dusk::frame_interp::is_enabled() || dusk::getTransientSettings().turboMode) { + limiter.Reset(); + dusk::frameUsagePct = 0.f; + return; } - if (dusk::getTransientSettings().skipFrameRateLimit) { - p1 = OS_TIMER_CLOCK / 120; - } - if (fopOvlpM_IsPeek() && dusk::getTransientSettings().stateShareLoadActive) { return; } @@ -399,7 +396,6 @@ static void waitForTick(u32 p1, u16 p2) { if (p1 != 0) { #if TARGET_PC - static Limiter limiter; waitPrecise(limiter, static_cast(OSTicksToMicroseconds(p1)) * 1000ULL); #else static OSTime nextTick = OSGetTime(); @@ -413,7 +409,6 @@ static void waitForTick(u32 p1, u16 p2) { } else { u32 uVar1 = (p2 == 0) ? 1 : p2; #if TARGET_PC - static Limiter limiter; waitPrecise(limiter, static_cast((RETRACE_PERIOD * uVar1).count())); #else static u32 nextCount = VIGetRetraceCount(); @@ -452,13 +447,13 @@ static void dummy() { JUTXfb::getManager()->setDisplayingXfbIndex(0); } -static Mtx e_mtx ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) static Mtx e_mtx = { {1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}, }; -static u8 clear_z_TX[64] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) static u8 clear_z_TX[64] = { 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, diff --git a/libs/JSystem/src/JKernel/JKRAramArchive.cpp b/libs/JSystem/src/JKernel/JKRAramArchive.cpp index d6923ee06b..fcb54271e4 100644 --- a/libs/JSystem/src/JKernel/JKRAramArchive.cpp +++ b/libs/JSystem/src/JKernel/JKRAramArchive.cpp @@ -196,6 +196,13 @@ cleanup: void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) { JUT_ASSERT(442, isMounted()); + +#if TARGET_PC + if (void* data = getOverlayData(pEntry, pOutSize); data != nullptr) { + return data; + } +#endif + u32 outSize; u8* outBuf; if (pOutSize == NULL) { @@ -231,6 +238,13 @@ void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) { void* JKRAramArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* pEntry, u32* resourceSize) { JUT_ASSERT(515, isMounted()); + +#if TARGET_PC + if (copyOverlayData(buffer, bufferSize, pEntry, resourceSize)) { + return buffer; + } +#endif + u32 size = pEntry->data_size; if (size > bufferSize) { size = bufferSize; @@ -337,6 +351,12 @@ u32 JKRAramArchive::getExpandedResSize(const void* ptr) const { return this->getResSize(ptr); } +#if TARGET_PC + if (u32 size; getOverlayResourceSize(ptr, &size)) { + return size; + } +#endif + JKRArchive::SDIFileEntry* entry = this->findPtrResource(ptr); if (entry == NULL) { return 0xFFFFFFFF; diff --git a/libs/JSystem/src/JKernel/JKRArchivePri.cpp b/libs/JSystem/src/JKernel/JKRArchivePri.cpp index a14ab763b0..eed317a357 100644 --- a/libs/JSystem/src/JKernel/JKRArchivePri.cpp +++ b/libs/JSystem/src/JKernel/JKRArchivePri.cpp @@ -7,6 +7,37 @@ #if TARGET_PC #include +#include +#include +#include +#include +#include +#include "JSystem/JKernel/JKRDvdRipper.h" +#if _WIN32 +#include +#endif + +std::atomic JKRArchive::sArcOverlayGeneration{0}; + +namespace { + +void* alloc_overlay_buffer(u32 size) { +#if _WIN32 + return _aligned_malloc(size, alignof(std::max_align_t)); +#else + return std::malloc(size); +#endif +} + +void free_overlay_buffer(void* data) { +#if _WIN32 + _aligned_free(data); +#else + std::free(data); +#endif +} + +} // namespace #endif DUSK_GAME_DATA u32 JKRArchive::sCurrentDirID; @@ -42,6 +73,7 @@ JKRArchive::JKRArchive(s32 entryNumber, JKRArchive::EMountMode mountMode) { JKRArchive::~JKRArchive() { #if TARGET_PC + removeAllOverlayResources(); if (mFileData != nullptr) { JKRHeap::getSystemHeap()->free(mFileData); mFileData = nullptr; @@ -270,4 +302,240 @@ void JKRArchive::initFileDataPointers() { mFiles[i].index = i; } } + +void JKRArchive::notifyOverlayFilesChanged() { + sArcOverlayGeneration.fetch_add(1, std::memory_order_release); +} + +bool JKRArchive::buildArcOverlaysPath() const { + if (mArcOverlaysPathResolved) { + return !mArcOverlaysPath.empty(); + } + mArcOverlaysPathResolved = true; + + if (mEntryNum < 0) { + return false; + } + + char pathBuffer[1024]; + if (!DVDConvertEntrynumToPath(mEntryNum, pathBuffer, sizeof(pathBuffer))) { + return false; + } + + std::string path{pathBuffer}; + constexpr std::string_view extension{".arc"}; + if (path.size() < extension.size() || + path.compare(path.size() - extension.size(), extension.size(), extension) != 0) + { + return false; + } + path.resize(path.size() - extension.size()); + path.push_back('/'); + + mArcOverlaysPath = std::move(path); + return true; +} + +void JKRArchive::buildIndexToPathMap(u32 dirIndex, const std::string& currentPath) const { + const SDIDirEntry& dir = mNodes[dirIndex]; + for (int i = 0; i < dir.num_entries; i++) { + const SDIFileEntry& entry = mFiles[dir.first_file_index + i]; + std::string entryName{&mStringTable[entry.getNameOffset()]}; + if (entryName == "." || entryName == "..") { + continue; + } + if (entry.isDirectory()) { + buildIndexToPathMap(entry.data_offset, currentPath + entryName + "/"); + } else { + mIdxToPathMap[entry.index] = currentPath + entryName; + } + } +} + +bool JKRArchive::getOverlayPath(SDIFileEntry* entry, std::string& path) const { + if (entry == nullptr || !buildArcOverlaysPath()) { + return false; + } + + if (mIdxToPathMap.empty()) { + buildIndexToPathMap(0, std::string{&mStringTable[mNodes[0].name_offset]} + "/"); + } + + const auto pathIt = mIdxToPathMap.find(entry->index); + if (pathIt == mIdxToPathMap.end()) { + return false; + } + + path = mArcOverlaysPath + pathIt->second; + return true; +} + +void* JKRArchive::getActiveOverlayData(SDIFileEntry* entry, u32* outSize) const { + const auto activeIt = mActiveArcOverlayResources.find(entry->index); + if (activeIt == mActiveArcOverlayResources.end()) { + return nullptr; + } + + const auto resourceIt = mArcOverlayResources.find(activeIt->second); + const u64 generation = sArcOverlayGeneration.load(std::memory_order_acquire); + if (resourceIt == mArcOverlayResources.end() || resourceIt->second.generation != generation) { + // Keep the allocation owned so raw pointers returned by earlier fetches remain valid. + mActiveArcOverlayResources.erase(activeIt); + return nullptr; + } + + if (outSize != nullptr) { + *outSize = resourceIt->second.size; + } + return resourceIt->first; +} + +void* JKRArchive::getOverlayData(SDIFileEntry* entry, u32* outSize) { + if (entry == nullptr) { + return nullptr; + } + + if (void* data = getActiveOverlayData(entry, outSize)) { + return data; + } + + std::string path; + if (!getOverlayPath(entry, path)) { + return nullptr; + } + + constexpr u32 alignmentMask = 0x1f; + const u64 generation = sArcOverlayGeneration.load(std::memory_order_acquire); + DVDFileInfo fileInfo{}; + if (!DVDOpen(path.c_str(), &fileInfo)) { + return nullptr; + } + + const u32 logicalSize = fileInfo.length; + if (logicalSize > static_cast(std::numeric_limits::max()) - alignmentMask) { + DVDClose(&fileInfo); + return nullptr; + } + + const u32 readSize = ALIGN_NEXT(logicalSize, 0x20); + const u32 allocationSize = readSize == 0 ? 1 : readSize; + void* data = alloc_overlay_buffer(allocationSize); + if (data == nullptr) { + DVDClose(&fileInfo); + return nullptr; + } + + const s32 status = DVDReadPrio(&fileInfo, data, readSize, 0, 2); + DVDClose(&fileInfo); + if (status < DVD_RESULT_GOOD || static_cast(status) != logicalSize) { + free_overlay_buffer(data); + return nullptr; + } + + mArcOverlayResources.emplace(data, ArcOverlayResource{ + .entryIndex = entry->index, + .size = logicalSize, + .generation = generation, + }); + mActiveArcOverlayResources[entry->index] = data; + + if (outSize != nullptr) { + *outSize = logicalSize; + } + return data; +} + +bool JKRArchive::copyOverlayData(void* buffer, u32 bufferSize, SDIFileEntry* entry, u32* outSize) { + u32 overlaySize; + const void* overlayData = getOverlayData(entry, &overlaySize); + if (overlayData == nullptr) { + return false; + } + + const u32 copySize = overlaySize < bufferSize ? overlaySize : bufferSize; + if (copySize != 0) { + memcpy(buffer, overlayData, copySize); + } + if (outSize != nullptr) { + *outSize = copySize; + } + return true; +} + +bool JKRArchive::getOverlayResourceSize(const void* data, u32* outSize) const { + const auto resourceIt = mArcOverlayResources.find(const_cast(data)); + if (resourceIt == mArcOverlayResources.end()) { + return false; + } + + if (outSize != nullptr) { + *outSize = resourceIt->second.size; + } + return true; +} + +bool JKRArchive::getOverlayFileSize(SDIFileEntry* entry, u32* outSize) const { + if (entry == nullptr) { + return false; + } + + u32 activeSize; + if (getActiveOverlayData(entry, &activeSize) != nullptr) { + if (outSize != nullptr) { + *outSize = activeSize; + } + return true; + } + + std::string path; + if (!getOverlayPath(entry, path)) { + return false; + } + + DVDFileInfo fileInfo{}; + if (!DVDOpen(path.c_str(), &fileInfo)) { + return false; + } + + if (outSize != nullptr) { + *outSize = fileInfo.length; + } + DVDClose(&fileInfo); + return true; +} + +u32 JKRArchive::getFileSize(SDIFileEntry* entry) const { + u32 size; + if (getOverlayFileSize(entry, &size)) { + return size; + } + return entry != nullptr ? entry->getSize() : 0; +} + +bool JKRArchive::removeOverlayResource(void* resource, bool freeResource) { + const auto resourceIt = mArcOverlayResources.find(resource); + if (resourceIt == mArcOverlayResources.end()) { + return false; + } + + const auto activeIt = mActiveArcOverlayResources.find(resourceIt->second.entryIndex); + if (activeIt != mActiveArcOverlayResources.end() && activeIt->second == resource) { + mActiveArcOverlayResources.erase(activeIt); + } + + if (freeResource) { + free_overlay_buffer(resource); + } + mArcOverlayResources.erase(resourceIt); + return true; +} + +void JKRArchive::removeAllOverlayResources() { + mActiveArcOverlayResources.clear(); + for (const auto& key : mArcOverlayResources | std::views::keys) { + free_overlay_buffer(key); + } + mArcOverlayResources.clear(); +} + #endif diff --git a/libs/JSystem/src/JKernel/JKRArchivePub.cpp b/libs/JSystem/src/JKernel/JKRArchivePub.cpp index 731a049457..97306c9033 100644 --- a/libs/JSystem/src/JKernel/JKRArchivePub.cpp +++ b/libs/JSystem/src/JKernel/JKRArchivePub.cpp @@ -246,6 +246,7 @@ u32 JKRArchive::readResource(void* buffer, u32 bufferSize, u16 id) { void JKRArchive::removeResourceAll() { if (mArcInfoBlock && mMountMode != MOUNT_MEM) { + IF_DUSK(removeAllOverlayResources();) SDIFileEntry* fileEntry = mFiles; for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) { if (JKAR_DATA(fileEntry)) { @@ -259,6 +260,13 @@ void JKRArchive::removeResourceAll() { bool JKRArchive::removeResource(void* resource) { JUT_ASSERT(678, resource != NULL); + +#if TARGET_PC + if (removeOverlayResource(resource, true)) { + return true; + } +#endif + SDIFileEntry* fileEntry = findPtrResource(resource); if (fileEntry == NULL) return false; @@ -270,6 +278,13 @@ bool JKRArchive::removeResource(void* resource) { bool JKRArchive::detachResource(void* resource) { JUT_ASSERT(707, resource != NULL); + +#if TARGET_PC + if (removeOverlayResource(resource, false)) { + return true; + } +#endif + SDIFileEntry* fileEntry = findPtrResource(resource); if (fileEntry == NULL) return false; @@ -280,6 +295,13 @@ bool JKRArchive::detachResource(void* resource) { u32 JKRArchive::getResSize(const void* resource) const { JUT_ASSERT(732, resource != NULL); + +#if TARGET_PC + if (u32 size; getOverlayResourceSize(resource, &size)) { + return size; + } +#endif + SDIFileEntry* fileEntry = findPtrResource(resource); if (fileEntry == NULL) return -1; diff --git a/libs/JSystem/src/JKernel/JKRCompArchive.cpp b/libs/JSystem/src/JKernel/JKRCompArchive.cpp index 716aaa2838..acac590ec0 100644 --- a/libs/JSystem/src/JKernel/JKRCompArchive.cpp +++ b/libs/JSystem/src/JKernel/JKRCompArchive.cpp @@ -226,6 +226,13 @@ bool JKRCompArchive::open(s32 entryNum) { void* JKRCompArchive::fetchResource(SDIFileEntry *fileEntry, u32 *pSize) { JUT_ASSERT(597, isMounted()); + +#if TARGET_PC + if (void* data = getOverlayData(fileEntry, pSize); data != nullptr) { + return data; + } +#endif + u32 ptrSize; u32 size = fileEntry->data_size; int compression = JKRConvertAttrToCompressionType(u8(fileEntry->type_flags_and_name_offset >> 0x18)); @@ -274,6 +281,13 @@ void *JKRCompArchive::fetchResource(void *data, u32 compressedSize, SDIFileEntry { u32 size = 0; JUT_ASSERT(708, isMounted()); + +#if TARGET_PC + if (copyOverlayData(data, compressedSize, fileEntry, pSize)) { + return data; + } +#endif + u32 fileSize = fileEntry->data_size; u32 alignedSize = ALIGN_NEXT(fileSize, 32); u32 fileFlag = fileEntry->type_flags_and_name_offset >> 0x18; @@ -319,6 +333,7 @@ void *JKRCompArchive::fetchResource(void *data, u32 compressedSize, SDIFileEntry void JKRCompArchive::removeResourceAll() { if (mArcInfoBlock != NULL && mMountMode != MOUNT_MEM) { + IF_DUSK(removeAllOverlayResources();) SDIFileEntry* fileEntry = mFiles; for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) { int tmp = fileEntry->type_flags_and_name_offset >> 0x18; @@ -336,6 +351,12 @@ void JKRCompArchive::removeResourceAll() { } bool JKRCompArchive::removeResource(void* resource) { +#if TARGET_PC + if (removeOverlayResource(resource, true)) { + return true; + } +#endif + SDIFileEntry* fileEntry = findPtrResource(resource); if (!fileEntry) return false; @@ -356,6 +377,12 @@ u32 JKRCompArchive::getExpandedResSize(const void *resource) const return getResSize(resource); } +#if TARGET_PC + if (u32 size; getOverlayResourceSize(resource, &size)) { + return size; + } +#endif + SDIFileEntry *fileEntry = findPtrResource(resource); if(!fileEntry) { return 0xffffffff; diff --git a/libs/JSystem/src/JKernel/JKRDvdArchive.cpp b/libs/JSystem/src/JKernel/JKRDvdArchive.cpp index b82302c900..d599879f8f 100644 --- a/libs/JSystem/src/JKernel/JKRDvdArchive.cpp +++ b/libs/JSystem/src/JKernel/JKRDvdArchive.cpp @@ -147,6 +147,13 @@ cleanup: void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) { JUT_ASSERT(428, isMounted()); + +#if TARGET_PC + if (void* data = getOverlayData(fileEntry, returnSize); data != nullptr) { + return data; + } +#endif + u32 tempReturnSize; if (returnSize == NULL) { returnSize = &tempReturnSize; @@ -181,6 +188,13 @@ void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) { void* JKRDvdArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* fileEntry, u32* returnSize) { JUT_ASSERT(504, isMounted()); + +#if TARGET_PC + if (copyOverlayData(buffer, bufferSize, fileEntry, returnSize)) { + return buffer; + } +#endif + u32 size = fileEntry->data_size; JKRCompression fileCompression = JKRConvertAttrToCompressionType(u8(fileEntry->type_flags_and_name_offset >> 24)); @@ -344,6 +358,12 @@ u32 JKRDvdArchive::getExpandedResSize(const void* resource) const { return getResSize(resource); } +#if TARGET_PC + if (u32 size; getOverlayResourceSize(resource, &size)) { + return size; + } +#endif + SDIFileEntry* fileEntry = findPtrResource(resource); if (!fileEntry) { return -1; diff --git a/libs/JSystem/src/JKernel/JKRMemArchive.cpp b/libs/JSystem/src/JKernel/JKRMemArchive.cpp index d3b17d8a63..f0feabe3be 100644 --- a/libs/JSystem/src/JKernel/JKRMemArchive.cpp +++ b/libs/JSystem/src/JKernel/JKRMemArchive.cpp @@ -135,6 +135,13 @@ bool JKRMemArchive::open(void* buffer, u32 bufferSize, JKRMemBreakFlag flag) { void* JKRMemArchive::fetchResource(SDIFileEntry* fileEntry, u32* resourceSize) { JUT_ASSERT(555, isMounted()); + +#if TARGET_PC + if (void* data = getOverlayData(fileEntry, resourceSize); data != nullptr) { + return data; + } +#endif + if (!JKAR_DATA(fileEntry)) { JKAR_DATA(fileEntry) = mArchiveData + fileEntry->data_offset; } @@ -149,6 +156,13 @@ void* JKRMemArchive::fetchResource(SDIFileEntry* fileEntry, u32* resourceSize) { void* JKRMemArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* fileEntry, u32* resourceSize) { JUT_ASSERT(595, isMounted()); + +#if TARGET_PC + if (copyOverlayData(buffer, bufferSize, fileEntry, resourceSize)) { + return buffer; + } +#endif + u32 srcLength = fileEntry->data_size; if (srcLength > bufferSize) { srcLength = bufferSize; @@ -173,6 +187,8 @@ void* JKRMemArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* f void JKRMemArchive::removeResourceAll(void) { JUT_ASSERT(642, isMounted()); + IF_DUSK(removeAllOverlayResources();) + if (mArcInfoBlock == NULL) return; if (mMountMode == MOUNT_MEM) @@ -192,6 +208,12 @@ void JKRMemArchive::removeResourceAll(void) { bool JKRMemArchive::removeResource(void* resource) { JUT_ASSERT(673, isMounted()); +#if TARGET_PC + if (removeOverlayResource(resource, true)) { + return true; + } +#endif + SDIFileEntry* fileEntry = findPtrResource(resource); if (!fileEntry) return false; @@ -231,6 +253,12 @@ u32 JKRMemArchive::fetchResource_subroutine(u8* src, u32 srcLength, u8* dst, u32 } u32 JKRMemArchive::getExpandedResSize(const void* resource) const { +#if TARGET_PC + if (u32 overlaySize; getOverlayResourceSize(resource, &overlaySize)) { + return overlaySize; + } +#endif + SDIFileEntry* fileEntry = findPtrResource(resource); if (fileEntry == NULL) return -1; diff --git a/libs/JSystem/src/JMath/JMATrigonometric.cpp b/libs/JSystem/src/JMath/JMATrigonometric.cpp index 8ebdb57ebc..cc8ce129e6 100644 --- a/libs/JSystem/src/JMath/JMATrigonometric.cpp +++ b/libs/JSystem/src/JMath/JMATrigonometric.cpp @@ -16,10 +16,10 @@ inline f64 getConst2() { return 9.765625E-4; } -DUSK_GAME_DATA TSinCosTable<13, f32> sincosTable_ ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) DUSK_GAME_DATA TSinCosTable<13, f32> sincosTable_; -DUSK_GAME_DATA TAtanTable<1024, f32> atanTable_ ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) DUSK_GAME_DATA TAtanTable<1024, f32> atanTable_; -DUSK_GAME_DATA TAsinAcosTable<1024, f32> asinAcosTable_ ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) DUSK_GAME_DATA TAsinAcosTable<1024, f32> asinAcosTable_; } // namespace JMath diff --git a/libs/JSystem/src/JMessage/control.cpp b/libs/JSystem/src/JMessage/control.cpp index acd5387f77..e739d2f672 100644 --- a/libs/JSystem/src/JMessage/control.cpp +++ b/libs/JSystem/src/JMessage/control.cpp @@ -7,6 +7,10 @@ #include "JSystem/JMessage/control.h" +#if TARGET_PC +#include "dusk/mods/svc/flow.hpp" +#endif + JMessage::TControl::TControl() : pSequenceProcessor_(NULL), pRenderingProcessor_(NULL), @@ -19,9 +23,16 @@ JMessage::TControl::TControl() pMessageText_current_(NULL) {} -JMessage::TControl::~TControl() {} +JMessage::TControl::~TControl() { +#if TARGET_PC + dusk::flow::release_message_control(this); +#endif +} void JMessage::TControl::reset() { +#if TARGET_PC + dusk::flow::release_message_control(this); +#endif pEntry_ = NULL; pMessageText_begin_ = NULL; pszText_update_current_ = NULL; @@ -78,18 +89,57 @@ int JMessage::TControl::setMessageID(u32 uMsgID, u32 param_1, bool* pbValid) { } bool JMessage::TControl::setMessageCode_inSequence_(JMessage::TProcessor const* pProcessor, u16 u16GroupID, u16 u16Index) { +#if TARGET_PC + const TResource* resource = NULL; + const void* resolvedEntry = NULL; + const char* resolvedText = NULL; + if (u16Index >= dusk::flow::kCustomMessageMin) { + if (!dusk::flow::custom_message_for_processor( + this, pProcessor, u16Index, resource, resolvedEntry, resolvedText)) + { + return false; + } + const_cast(pProcessor)->setResourceCache(const_cast(resource)); + } else { + resource = pProcessor->getResource_groupID(u16GroupID); + if (resource == NULL) { + return false; + } + const void* nativeEntry = resource->getMessageEntry_messageIndex(u16Index); + const char* nativeText = + nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL; + resolvedEntry = nativeEntry; + resolvedText = nativeText; + dusk::flow::resolve_message_for_control(this, resource->oParse_THeader_.getRaw(), u16Index, + nativeEntry, nativeText, resolvedEntry, resolvedText); + } + pEntry_ = const_cast(resolvedEntry); + if (pEntry_ == NULL || resolvedText == NULL) { + return false; + } +#else pEntry_ = pProcessor->getMessageEntry_messageCode(u16GroupID, u16Index); if (pEntry_ == NULL) { return false; } +#endif +#if TARGET_PC + uMessageGroupID_ = resource->getGroupID(); + pResourceCache_ = resource; +#else uMessageGroupID_ = u16GroupID; - uMessageID_ = u16Index; - pResourceCache_ = pProcessor->getResourceCache(); +#endif + uMessageID_ = u16Index; + JUT_ASSERT(155, pResourceCache_!=NULL); +#if TARGET_PC + pMessageText_begin_ = resolvedText; +#else pMessageText_begin_ = pResourceCache_->getMessageText_messageEntry(pEntry_); +#endif pMessageText_current_ = pMessageText_begin_; oStack_renderingProcessor_.clear(); return true; diff --git a/libs/JSystem/src/JMessage/processor.cpp b/libs/JSystem/src/JMessage/processor.cpp index eef50e7516..08b0af0b7a 100644 --- a/libs/JSystem/src/JMessage/processor.cpp +++ b/libs/JSystem/src/JMessage/processor.cpp @@ -5,15 +5,26 @@ #include "JSystem/JUtility/JUTAssert.h" #include +#if TARGET_PC +#include "dusk/mods/svc/flow.hpp" +#endif + JMessage::TReference::~TReference() {} const char* JMessage::TReference::do_word(u32 param_0) const { return NULL; } -JMessage::TProcessor::~TProcessor() {} +JMessage::TProcessor::~TProcessor() { +#if TARGET_PC + dusk::flow::release_message_processor(this); +#endif +} void JMessage::TProcessor::reset() { +#if TARGET_PC + dusk::flow::release_message_processor(this); +#endif on_resetStatus_(NULL); do_reset(); } @@ -44,6 +55,15 @@ const JMessage::TResource* JMessage::TProcessor::getResource_groupID(u16 u16Grou } u32 JMessage::TProcessor::toMessageCode_messageID(u32 uMsgID, u32 param_1, bool* pbValid) const { +#if TARGET_PC + u32 customCode = 0; + if (dusk::flow::message_code_for_id(this, uMsgID, customCode)) { + if (pbValid != NULL) { + *pbValid = true; + } + return customCode; + } +#endif const TResource* pResourceCache = (const TResource*)getResourceCache(); if (pResourceCache != NULL) { u16 u16Index = pResourceCache->toMessageIndex_messageID(uMsgID, param_1, pbValid); @@ -73,6 +93,74 @@ u32 JMessage::TProcessor::toMessageCode_messageID(u32 uMsgID, u32 param_1, bool* return 0xFFFFFFFF; } +#if TARGET_PC +void* JMessage::TProcessor::getMessageEntry_messageCode(u16 groupId, u16 index) const { + if (index >= dusk::flow::kCustomMessageMin) { + const TResource* customResource = NULL; + const void* customEntry = NULL; + const char* customText = NULL; + if (!dusk::flow::custom_message_for_processor( + NULL, this, index, customResource, customEntry, customText)) + { + return NULL; + } + const_cast(this)->setResourceCache(const_cast(customResource)); + return const_cast(customEntry); + } + const TResource* resource = getResource_groupID(groupId); + if (resource == NULL) { + return NULL; + } + void* nativeEntry = resource->getMessageEntry_messageIndex(index); + const char* nativeText = + nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL; + const void* resolvedEntry = nativeEntry; + const char* resolvedText = nativeText; + dusk::flow::resolve_message(this, resource->oParse_THeader_.getRaw(), index, nativeEntry, + nativeText, resolvedEntry, resolvedText); + return const_cast(resolvedEntry); +} + +const char* JMessage::TProcessor::getMessageText_messageCode(u16 groupId, u16 index) const { + if (index >= dusk::flow::kCustomMessageMin) { + const TResource* customResource = NULL; + const void* customEntry = NULL; + const char* customText = NULL; + if (!dusk::flow::custom_message_for_processor( + NULL, this, index, customResource, customEntry, customText)) + { + return NULL; + } + const_cast(this)->setResourceCache(const_cast(customResource)); + return customText; + } + const TResource* resource = getResource_groupID(groupId); + if (resource == NULL) { + return NULL; + } + const void* nativeEntry = resource->getMessageEntry_messageIndex(index); + const char* nativeText = + nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL; + const void* resolvedEntry = nativeEntry; + const char* resolvedText = nativeText; + dusk::flow::resolve_message(this, resource->oParse_THeader_.getRaw(), index, nativeEntry, + nativeText, resolvedEntry, resolvedText); + return resolvedText; +} + +const char* JMessage::TProcessor::on_message_limited(u16 index) const { + JUT_ASSERT(482, pResourceCache_!=NULL); + const void* nativeEntry = pResourceCache_->getMessageEntry_messageIndex(index); + const char* nativeText = + nativeEntry != NULL ? pResourceCache_->getMessageText_messageEntry(nativeEntry) : NULL; + const void* resolvedEntry = nativeEntry; + const char* resolvedText = nativeText; + dusk::flow::resolve_message(this, pResourceCache_->oParse_THeader_.getRaw(), index, + nativeEntry, nativeText, resolvedEntry, resolvedText); + return resolvedText; +} +#endif + void JMessage::TProcessor::on_select_begin(char const* (*pfn)(JMessage::TProcessor*), void const* pOffset, char const* pcBase, u32 uNumber) { JUT_ASSERT(191, uNumber>0); diff --git a/libs/JSystem/src/JParticle/JPAResource.cpp b/libs/JSystem/src/JParticle/JPAResource.cpp index 9385b258e7..b67ac47489 100644 --- a/libs/JSystem/src/JParticle/JPAResource.cpp +++ b/libs/JSystem/src/JParticle/JPAResource.cpp @@ -46,7 +46,7 @@ JPAResource::JPAResource() { mUsrIdx = fldNum = keyNum = texNum = mpCalcEmitterFuncListNum = mpDrawEmitterFuncListNum = mpDrawEmitterChildFuncListNum = mpCalcParticleFuncListNum = mpDrawParticleFuncListNum = mpCalcParticleChildFuncListNum = mpDrawParticleChildFuncListNum = 0; } -static u8 jpa_pos[324] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) static u8 jpa_pos[324] = { 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x32, 0xCE, 0x00, 0x00, 0xCE, 0x00, 0xE7, 0x00, 0x00, 0x19, 0x00, 0x00, 0x19, 0xCE, 0x00, 0xE7, 0xCE, 0x00, 0xCE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0x00, 0xCE, 0xCE, 0x00, 0x00, 0x19, 0x00, 0x32, 0x19, 0x00, 0x32, 0xE7, 0x00, 0x00, 0xE7, 0x00, @@ -70,7 +70,7 @@ static u8 jpa_pos[324] ATTRIBUTE_ALIGN(32) = { 0x00, 0x00, 0x00, 0xCE, }; -static u8 jpa_crd[32] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) static u8 jpa_crd[32] = { 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x02, }; diff --git a/libs/JSystem/src/JUtility/JUTFontData_Ascfont_fix12.cpp b/libs/JSystem/src/JUtility/JUTFontData_Ascfont_fix12.cpp index 64ee2c61d8..0e500e377c 100644 --- a/libs/JSystem/src/JUtility/JUTFontData_Ascfont_fix12.cpp +++ b/libs/JSystem/src/JUtility/JUTFontData_Ascfont_fix12.cpp @@ -8,7 +8,7 @@ #endif #include "global.h" -DUSK_GAME_DATA u8 const JUTResFONT_Ascfont_fix12[] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) DUSK_GAME_DATA u8 const JUTResFONT_Ascfont_fix12[] = { 0x46, 0x4F, 0x4E, 0x54, 0x62, 0x66, 0x6E, 0x31, 0x00, 0x00, 0x41, 0x60, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x4E, 0x46, 0x31, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, diff --git a/libs/JSystem/src/JUtility/JUTResFont.cpp b/libs/JSystem/src/JUtility/JUTResFont.cpp index 4ec23dff44..3f31961d1f 100644 --- a/libs/JSystem/src/JUtility/JUTResFont.cpp +++ b/libs/JSystem/src/JUtility/JUTResFont.cpp @@ -71,7 +71,7 @@ void JUTResFont::initJoinedTexture() { int pageCount = 0; u32 pageNumCells = block.numRows * block.numColumns; - if (dusk::version::getGameVersion() == dusk::version::GameVersion::GcnJpn) { + if (dusk::version::isRegionJpn()) { pageCount = 1; if (pageNumCells > 0 && block.endCode > block.startCode) { pageCount = (block.endCode - block.startCode + pageNumCells - 1) / pageNumCells; diff --git a/libs/JSystem/src/JUtility/JUTVideo.cpp b/libs/JSystem/src/JUtility/JUTVideo.cpp index 2135c8fc22..663c357e17 100644 --- a/libs/JSystem/src/JUtility/JUTVideo.cpp +++ b/libs/JSystem/src/JUtility/JUTVideo.cpp @@ -42,7 +42,7 @@ JUTVideo::JUTVideo(GXRenderModeObj const* param_0) { mRetraceCount = VIGetRetraceCount(); field_0x10 = 1; field_0x18 = 0; - sVideoLastTick = OSGetTick(); + sVideoLastTick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); sVideoInterval = 670000; mPreRetraceCallback = VISetPreRetraceCallback(preRetraceProc); mPostRetraceCallback = VISetPostRetraceCallback(postRetraceProc); @@ -68,8 +68,13 @@ void JUTVideo::preRetraceProc(u32 retrace_count) { (*sManager->mPreCallback)(retrace_count); } - OSTick tick = OSGetTick(); + OSTick tick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); sVideoInterval = tick - sVideoLastTick; +#if TARGET_PC + if (sVideoInterval <= 0) { + sVideoInterval = 1; + } +#endif sVideoLastTick = tick; JUTXfb* xfb = JUTXfb::getManager(); diff --git a/libs/TRK_MINNOW_DOLPHIN/debugger/embedded/MetroTRK/Os/dolphin/dolphin_trk.c b/libs/TRK_MINNOW_DOLPHIN/debugger/embedded/MetroTRK/Os/dolphin/dolphin_trk.c index 13a30383ba..2564e1298c 100644 --- a/libs/TRK_MINNOW_DOLPHIN/debugger/embedded/MetroTRK/Os/dolphin/dolphin_trk.c +++ b/libs/TRK_MINNOW_DOLPHIN/debugger/embedded/MetroTRK/Os/dolphin/dolphin_trk.c @@ -136,7 +136,7 @@ void TRK__read_aram(__REGISTER int c, __REGISTER u32 p2, void* p3) { } void TRK__write_aram(__REGISTER int c, __REGISTER u32 p2, void* p3) { - u8 buff[32] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 buff[32]; u32 err; __REGISTER int count = c; __REGISTER u32 bf; diff --git a/libs/TRK_MINNOW_DOLPHIN/debugger/embedded/MetroTRK/Portable/msghndlr.c b/libs/TRK_MINNOW_DOLPHIN/debugger/embedded/MetroTRK/Portable/msghndlr.c index 36a33d75fa..f3b5901d5b 100644 --- a/libs/TRK_MINNOW_DOLPHIN/debugger/embedded/MetroTRK/Portable/msghndlr.c +++ b/libs/TRK_MINNOW_DOLPHIN/debugger/embedded/MetroTRK/Portable/msghndlr.c @@ -85,7 +85,7 @@ DSError TRKDoSupportMask(TRKBuffer*) { } DSError TRKDoReadMemory(TRKBuffer* buffer) { - u8 buf[0x820] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 buf[0x820]; size_t tempLength; int result; int replyErr; @@ -158,7 +158,7 @@ DSError TRKDoReadMemory(TRKBuffer* buffer) { } DSError TRKDoWriteMemory(TRKBuffer* b) { - u8 buf[0x820] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 buf[0x820]; size_t tempLength; int options; int result; diff --git a/libs/dolphin/src/ax/AXAux.c b/libs/dolphin/src/ax/AXAux.c index 7d392d8f1c..cf469197ee 100644 --- a/libs/dolphin/src/ax/AXAux.c +++ b/libs/dolphin/src/ax/AXAux.c @@ -3,8 +3,8 @@ #include "__ax.h" -static s32 __AXBufferAuxA[3][480] ATTRIBUTE_ALIGN(32); -static s32 __AXBufferAuxB[3][480] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static s32 __AXBufferAuxA[3][480]; +ATTRIBUTE_ALIGN(32) static s32 __AXBufferAuxB[3][480]; static void (* __AXCallbackAuxA)(void*, void*); static void (* __AXCallbackAuxB)(void*, void*); diff --git a/libs/dolphin/src/ax/AXSPB.c b/libs/dolphin/src/ax/AXSPB.c index 3729589b62..6fad9bae4b 100644 --- a/libs/dolphin/src/ax/AXSPB.c +++ b/libs/dolphin/src/ax/AXSPB.c @@ -3,7 +3,7 @@ #include "__ax.h" -static AXSPB __AXStudio ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static AXSPB __AXStudio; static s32 __AXSpbAL; static s32 __AXSpbAR; diff --git a/libs/dolphin/src/ax/AXVPB.c b/libs/dolphin/src/ax/AXVPB.c index e8cf663470..02b9db2c63 100644 --- a/libs/dolphin/src/ax/AXVPB.c +++ b/libs/dolphin/src/ax/AXVPB.c @@ -35,9 +35,9 @@ static u32 __AXAuxMixCycles[32] = { 0x000009E2, 0x00000E97 }; -static AXPB __AXPB[AX_MAX_VOICES] ATTRIBUTE_ALIGN(32); -static AXPBITDBUFFER __AXITD[AX_MAX_VOICES] ATTRIBUTE_ALIGN(32); -static AXPBU __AXUpdates[AX_MAX_VOICES] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static AXPB __AXPB[AX_MAX_VOICES]; +ATTRIBUTE_ALIGN(32) static AXPBITDBUFFER __AXITD[AX_MAX_VOICES]; +ATTRIBUTE_ALIGN(32) static AXPBU __AXUpdates[AX_MAX_VOICES]; static AXVPB __AXVPB[AX_MAX_VOICES]; static u32 __AXMaxDspCycles; diff --git a/libs/dolphin/src/ax/DSPCode.c b/libs/dolphin/src/ax/DSPCode.c index 7ca0d067ba..a2f77e2805 100644 --- a/libs/dolphin/src/ax/DSPCode.c +++ b/libs/dolphin/src/ax/DSPCode.c @@ -3,7 +3,7 @@ u16 axDspSlaveLength = (AX_DSP_SLAVE_LENGTH * 2); -u16 axDspSlave[AX_DSP_SLAVE_LENGTH] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) u16 axDspSlave[AX_DSP_SLAVE_LENGTH] = { 0x0000, 0x0000, 0x029F, 0x0E88, 0x029F, 0x0E97, 0x029F, 0x0EB3, 0x029F, 0x0ED3, 0x029F, 0x0ED9, 0x029F, 0x0F0B, 0x029F, 0x0F11, 0x1302, 0x1303, diff --git a/libs/dolphin/src/card/CARDUnlock.c b/libs/dolphin/src/card/CARDUnlock.c index 48f64e500a..31138c2995 100644 --- a/libs/dolphin/src/card/CARDUnlock.c +++ b/libs/dolphin/src/card/CARDUnlock.c @@ -4,7 +4,7 @@ #include "__card.h" -static u8 CardData[352] ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT) = { +ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT) static u8 CardData[352] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x02, 0xFF, 0x00, 0x21, 0x13, 0x06, 0x12, 0x03, 0x12, 0x04, 0x13, 0x05, 0x00, 0x92, 0x00, 0xFF, 0x00, 0x88, 0xFF, 0xFF, 0x00, 0x89, 0xFF, 0xFF, 0x00, 0x8A, 0xFF, 0xFF, 0x00, diff --git a/libs/dolphin/src/demo/DEMOAVX.c b/libs/dolphin/src/demo/DEMOAVX.c index 39f5d8f554..6de1cfd01d 100644 --- a/libs/dolphin/src/demo/DEMOAVX.c +++ b/libs/dolphin/src/demo/DEMOAVX.c @@ -3,7 +3,7 @@ #include #include "sdk_math.h" -static s16 __AVX_internal_buffer[3200] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static s16 __AVX_internal_buffer[3200]; static void (*__AVX_save_isr)(void); diff --git a/libs/dolphin/src/demo/DEMOFont.c b/libs/dolphin/src/demo/DEMOFont.c index 48299131d9..3ea63d291e 100644 --- a/libs/dolphin/src/demo/DEMOFont.c +++ b/libs/dolphin/src/demo/DEMOFont.c @@ -1,7 +1,7 @@ #include #include -u32 DEMOFontBitmap[768] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) u32 DEMOFontBitmap[768] = { 0x00000000, 0x00000000, 0x00000000, diff --git a/libs/dolphin/src/gx/GXInit.c b/libs/dolphin/src/gx/GXInit.c index c22f5acbd8..639c990d72 100644 --- a/libs/dolphin/src/gx/GXInit.c +++ b/libs/dolphin/src/gx/GXInit.c @@ -43,7 +43,7 @@ void* __piReg; GXBool __GXinBegin; #endif -static u16 DefaultTexData[] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) static u16 DefaultTexData[] = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, }; diff --git a/libs/dolphin/src/mcc/mcc.c b/libs/dolphin/src/mcc/mcc.c index fa75efe415..1d05d947a6 100644 --- a/libs/dolphin/src/mcc/mcc.c +++ b/libs/dolphin/src/mcc/mcc.c @@ -7,11 +7,11 @@ const char* __MCCVersion = "<< Dolphin SDK - MCC\tdebug build: Apr 5 2004 03:57 const char* __MCCVersion = "<< Dolphin SDK - MCC\trelease build: Apr 5 2004 04:15:49 (0x2301) >>"; #endif -static MCC_ChannelInfo gChannelInfo[16] ATTRIBUTE_ALIGN(32); -static char gStreamWork[32] ATTRIBUTE_ALIGN(32); -static char m_szAdapterMode[32] ATTRIBUTE_ALIGN(32); -static char m_szInitCode[32] ATTRIBUTE_ALIGN(32); -static MCC_Info channelInfo[16] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static MCC_ChannelInfo gChannelInfo[16]; +ATTRIBUTE_ALIGN(32) static char gStreamWork[32]; +ATTRIBUTE_ALIGN(32) static char m_szAdapterMode[32]; +ATTRIBUTE_ALIGN(32) static char m_szInitCode[32]; +ATTRIBUTE_ALIGN(32) static MCC_Info channelInfo[16]; volatile static BOOL gIsChannelinfoDirty = TRUE; diff --git a/libs/dolphin/src/os/OSRtc.c b/libs/dolphin/src/os/OSRtc.c index ee55fcc0e4..c4f9cb7233 100644 --- a/libs/dolphin/src/os/OSRtc.c +++ b/libs/dolphin/src/os/OSRtc.c @@ -3,7 +3,7 @@ #include "__os.h" -static SramControl Scb ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT); +ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT) static SramControl Scb; // prototypes static int GetRTC(u32* rtc); diff --git a/libs/revolution/src/card/CARDUnlock.c b/libs/revolution/src/card/CARDUnlock.c index 93298297c8..1fad65efe4 100644 --- a/libs/revolution/src/card/CARDUnlock.c +++ b/libs/revolution/src/card/CARDUnlock.c @@ -3,7 +3,7 @@ #include "__card.h" -static u8 CardData[352] ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT) = { +ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT) static u8 CardData[352] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x02, 0xFF, 0x00, 0x21, 0x13, 0x06, 0x12, 0x03, 0x12, 0x04, 0x13, 0x05, 0x00, 0x92, 0x00, 0xFF, 0x00, 0x88, 0xFF, 0xFF, 0x00, 0x89, 0xFF, 0xFF, 0x00, 0x8A, 0xFF, 0xFF, 0x00, diff --git a/libs/revolution/src/dvd/dvd.c b/libs/revolution/src/dvd/dvd.c index 0e5dbd0945..9e91f6e3f3 100644 --- a/libs/revolution/src/dvd/dvd.c +++ b/libs/revolution/src/dvd/dvd.c @@ -44,7 +44,7 @@ static u32 LastError; static BOOL ResetRequired; static u32 MotorState; static volatile OSTime LastResetEnd; -static u32 __DVDNumTmdBytes ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u32 __DVDNumTmdBytes; static DVDGameTOC* GameToc; static DVDPartitionInfo* PartInfo; static DVDPartitionInfo* BootGameInfo; @@ -80,10 +80,10 @@ static DVDCommandBlock DummyCommandBlock; static OSAlarm ResetAlarm; static OSAlarm CoverAlarm; -static u8 __DVDGameTocBuffer[OSRoundUp32B(sizeof(DVDGameTOC) * 4)] ATTRIBUTE_ALIGN(32); -static u8 __DVDPartInfoBuffer[OSRoundUp32B(sizeof(DVDPartitionInfo) * 4)] ATTRIBUTE_ALIGN(32); -static u8 __DVDTmdBuffer[OSRoundUp32B(sizeof(ESTitleMeta))] ATTRIBUTE_ALIGN(32); -static u8 __DVDTicketViewBuffer[OSRoundUp32B(sizeof(ESTicketView))] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u8 __DVDGameTocBuffer[OSRoundUp32B(sizeof(DVDGameTOC) * 4)]; +ATTRIBUTE_ALIGN(32) static u8 __DVDPartInfoBuffer[OSRoundUp32B(sizeof(DVDPartitionInfo) * 4)]; +ATTRIBUTE_ALIGN(32) static u8 __DVDTmdBuffer[OSRoundUp32B(sizeof(ESTitleMeta))]; +ATTRIBUTE_ALIGN(32) static u8 __DVDTicketViewBuffer[OSRoundUp32B(sizeof(ESTicketView))]; static OSAlarm FatalAlarm; DVDCommandBlock __DVDStopMotorCommandBlock; diff --git a/libs/revolution/src/dvd/dvdDeviceError.c b/libs/revolution/src/dvd/dvdDeviceError.c index 316346461f..6fa74234ee 100644 --- a/libs/revolution/src/dvd/dvdDeviceError.c +++ b/libs/revolution/src/dvd/dvdDeviceError.c @@ -5,7 +5,7 @@ #include "__os.h" #include "__dvd.h" -static u8 CheckBuffer[32] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u8 CheckBuffer[32]; static volatile BOOL lowDone = TRUE; static volatile u32 lowIntType = 0; diff --git a/libs/revolution/src/dvd/dvd_broadway.c b/libs/revolution/src/dvd/dvd_broadway.c index ab4840791f..c2e8aef89d 100644 --- a/libs/revolution/src/dvd/dvd_broadway.c +++ b/libs/revolution/src/dvd/dvd_broadway.c @@ -8,15 +8,15 @@ static volatile u8 requestInProgress = FALSE; static u8 breakRequested; static u8 callbackInProgress; -static u32 registerBuf[8] ATTRIBUTE_ALIGN(32); -static u32 statusRegister[8] ATTRIBUTE_ALIGN(32); -static u32 controlRegister[8] ATTRIBUTE_ALIGN(32); -static s32 lastTicketError[8] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u32 registerBuf[8]; +ATTRIBUTE_ALIGN(32) static u32 statusRegister[8]; +ATTRIBUTE_ALIGN(32) static u32 controlRegister[8]; +ATTRIBUTE_ALIGN(32) static s32 lastTicketError[8]; static u32 readLength; static u32 spinUpValue; -static diRegVals_t diRegValCache ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static diRegVals_t diRegValCache; static u8 DVDLowInitCalled = FALSE; @@ -42,8 +42,8 @@ typedef struct dvdContext { static int freeDvdContext = 0; static u8 dvdContextsInited = FALSE; -static dvdContext_t dvdContexts[4] ATTRIBUTE_ALIGN(32); -static IOSIoVector ioVec[10] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static dvdContext_t dvdContexts[4]; +ATTRIBUTE_ALIGN(32) static IOSIoVector ioVec[10]; static void* ddrAllocAligned32(const int size) { void* low, *high; diff --git a/libs/revolution/src/dvd/dvderror.c b/libs/revolution/src/dvd/dvderror.c index 7f1e8e52ad..8b12465825 100644 --- a/libs/revolution/src/dvd/dvderror.c +++ b/libs/revolution/src/dvd/dvderror.c @@ -11,8 +11,8 @@ static NANDCommandBlock NandCb; static NANDFileInfo NandInfo; static DVDCBCallback Callback; static u32 NextOffset; -DVDErrorInfo __ErrorInfo ATTRIBUTE_ALIGN(32); -DVDErrorInfo __FirstErrorInfo ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) DVDErrorInfo __ErrorInfo; +ATTRIBUTE_ALIGN(32) DVDErrorInfo __FirstErrorInfo; void cbForNandClose(s32 result, NANDCommandBlock* block) { if (Callback) { diff --git a/libs/revolution/src/esp/esp.c b/libs/revolution/src/esp/esp.c index 68899e2598..aec3e862b1 100644 --- a/libs/revolution/src/esp/esp.c +++ b/libs/revolution/src/esp/esp.c @@ -33,7 +33,7 @@ s32 ESP_CloseLib(void) { s32 ESP_LaunchTitle(u64 titleID, ESTicketView* pTicketView) { s32 ret = 0; - u8 buf[256] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 buf[256]; IOSIoVector* vec = (IOSIoVector*)(buf + 208); u64* id = (u64*)buf; @@ -63,7 +63,7 @@ out: s32 ESP_GetTicketViews(ESTitleId titleId, ESTicketView* ticketViewList, u32* ticketViewCnt) { s32 rv = 0; - u8 __esBuf[256] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 __esBuf[256]; IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); ESTitleId* p1 = (ESTitleId*)__esBuf; u32* p2 = (u32*)(__esBuf + 32); @@ -111,7 +111,7 @@ out: s32 ESP_DiGetTicketView(const void* ticket, ESTicketView* ticketView) { s32 rv = 0; - u8 __esBuf[256] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 __esBuf[256]; IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); if (__esFd < 0 || ticketView == NULL) { @@ -142,7 +142,7 @@ out: s32 ESP_DiGetTmd(ESTitleMeta* tmd, u32* tmdSize) { s32 rv = 0; - u8 __esBuf[256] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 __esBuf[256]; IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); u32* p1 = (u32*)__esBuf; @@ -184,7 +184,7 @@ out: s32 ESP_GetTmdView(ESTitleId titleId, ESTmdView* tmdView, u32* size) { s32 rv = 0; - u8 __esBuf[256] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 __esBuf[256]; IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); ESTitleId* p1 = (ESTitleId*)__esBuf; u32* p2 = (u32*)(__esBuf + 32); @@ -233,7 +233,7 @@ out: s32 ESP_GetDataDir(ESTitleId titleId, char* dataDir) { s32 rv = 0; - u8 __esBuf[256] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 __esBuf[256]; IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); ESTitleId* p1 = (ESTitleId*)__esBuf; @@ -260,7 +260,7 @@ out: s32 ESP_GetTitleId(ESTitleId* titleId) { s32 rv = 0; - u8 __esBuf[256] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 __esBuf[256]; IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); if (__esFd < 0 || titleId == NULL) { @@ -283,7 +283,7 @@ out: s32 ESP_GetConsumption(ESTicketId ticketId, ESLpEntry* entries, u32* nEntries) { s32 rv = 0; - u8 __esBuf[256] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 __esBuf[256]; IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); ESTicketId* p1 = (ESTicketId*)__esBuf; u32* p2 = (u32*)(__esBuf + 32); diff --git a/libs/revolution/src/fs/fs.c b/libs/revolution/src/fs/fs.c index 1e5f601198..34397254c1 100644 --- a/libs/revolution/src/fs/fs.c +++ b/libs/revolution/src/fs/fs.c @@ -25,7 +25,7 @@ typedef struct isfs_GetUsage { } isfs_GetUsage; typedef struct __isfsCtxt { - u8 ioBuf[ROUNDUP(256)] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 ioBuf[ROUNDUP(256)]; ISFSCallback cb; void* ctxt; u32 func; diff --git a/libs/revolution/src/gx/GXInit.c b/libs/revolution/src/gx/GXInit.c index 0504827170..0ae463d84d 100644 --- a/libs/revolution/src/gx/GXInit.c +++ b/libs/revolution/src/gx/GXInit.c @@ -49,7 +49,7 @@ volatile void* __piReg; GXBool __GXinBegin; #endif -static u16 DefaultTexData[] ATTRIBUTE_ALIGN(32) = { +ATTRIBUTE_ALIGN(32) static u16 DefaultTexData[] = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, }; diff --git a/libs/revolution/src/homebuttonLib/nw4hbm/lyt/picture.h b/libs/revolution/src/homebuttonLib/nw4hbm/lyt/picture.h index 4a77129ea7..c34a092a61 100644 --- a/libs/revolution/src/homebuttonLib/nw4hbm/lyt/picture.h +++ b/libs/revolution/src/homebuttonLib/nw4hbm/lyt/picture.h @@ -34,7 +34,7 @@ namespace nw4hbm { private: /* 0x00 (base) */ - /* 0xD4 */ ut::Color mVtxColors[VERTEXCOLOR_MAX] ATTRIBUTE_ALIGN(4); + /* 0xD4 */ ATTRIBUTE_ALIGN(4) ut::Color mVtxColors[VERTEXCOLOR_MAX]; /* 0xE4 */ detail::TexCoordAry mTexCoordAry; }; diff --git a/libs/revolution/src/homebuttonLib/nw4hbm/snd/StrmPlayer.h b/libs/revolution/src/homebuttonLib/nw4hbm/snd/StrmPlayer.h index eae08ba82d..45f682b4b9 100644 --- a/libs/revolution/src/homebuttonLib/nw4hbm/snd/StrmPlayer.h +++ b/libs/revolution/src/homebuttonLib/nw4hbm/snd/StrmPlayer.h @@ -58,7 +58,7 @@ namespace nw4hbm { public: /* 0x14 */ ut::LinkListNode mLinkNode; - static u8 mMramBuf[LOAD_BUFFER_SIZE] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) static u8 mMramBuf[LOAD_BUFFER_SIZE]; }; typedef ut::LinkList LoadCommandList; diff --git a/libs/revolution/src/ipc/ipcclt.c b/libs/revolution/src/ipc/ipcclt.c index 6976775218..449e94ecd0 100644 --- a/libs/revolution/src/ipc/ipcclt.c +++ b/libs/revolution/src/ipc/ipcclt.c @@ -24,7 +24,7 @@ static u32 __relnchFl = 0; typedef struct IOSRpcRequest { IOSResourceRequest request; - IOSIpcCb cb ATTRIBUTE_ALIGN(32); // I am assuming this is aligned due to where cbArg is stored, and I see nothing between cb and callback_arg? + ATTRIBUTE_ALIGN(32) IOSIpcCb cb; // I am assuming this is aligned due to where cbArg is stored, and I see nothing between cb and callback_arg? void* callback_arg; u32 relaunch_flag; OSThreadQueue thread_queue; @@ -37,7 +37,7 @@ static IOSRpcRequest* __relnchRpcSave = 0; #define ROUNDUP(sz) (((u32)(sz) + (IPC_BUF_CNT / 2 - 1)) & ~(u32)(IPC_BUF_CNT / 2 - 1)) -static u8 __rpcBuf[ROUNDUP(sizeof(IOSRpcRequest))] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u8 __rpcBuf[ROUNDUP(sizeof(IOSRpcRequest))]; static struct { u32 rcount; diff --git a/libs/revolution/src/nand/NANDCore.c b/libs/revolution/src/nand/NANDCore.c index c93ec26c9c..8a9818197f 100644 --- a/libs/revolution/src/nand/NANDCore.c +++ b/libs/revolution/src/nand/NANDCore.c @@ -33,8 +33,8 @@ enum LibState { }; static enum LibState s_libState = STATE_NOT_INITIALIZED; -static char s_currentDir[64] ATTRIBUTE_ALIGN(32) = "/"; -static char s_homeDir[64] ATTRIBUTE_ALIGN(32) = ""; +ATTRIBUTE_ALIGN(32) static char s_currentDir[64] = "/"; +ATTRIBUTE_ALIGN(32) static char s_homeDir[64] = ""; static BOOL nandOnShutdown(BOOL final, u32 event); void nandConvertPath(char* abspath, const char* wd, const char* relpath); @@ -262,7 +262,7 @@ s32 nandConvertErrorCode(const ISFSError err) { for (; i < sizeof(ERRMAP) / 4; i = i + 2) { if (ERRMAP[i] == err) { if (err == ISFS_ERROR_ECC_CRIT || err == ISFS_ERROR_HMAC || err == ISFS_ERROR_UNKNOWN || err == IOS_ERROR_UNKNOWN || err == IOS_ERROR_ECC_CRIT) { - char buf[128] ATTRIBUTE_ALIGN(64); + ATTRIBUTE_ALIGN(64) char buf[128]; sprintf(buf, "ISFS error code: %d", err); NANDLoggingAddMessageAsync(nandLoggingCallback, err, buf); } @@ -279,7 +279,7 @@ s32 nandConvertErrorCode(const ISFSError err) { OSReport("CAUTION! Unexpected error code [%d] was found.\n", err); { - char buf[128] ATTRIBUTE_ALIGN(64); + ATTRIBUTE_ALIGN(64) char buf[128]; sprintf(buf, "ISFS unexpected error code: %d", err); NANDLoggingAddMessageAsync(nandLoggingCallback, err, buf); } diff --git a/libs/revolution/src/nand/NANDLogging.c b/libs/revolution/src/nand/NANDLogging.c index 8e620895d5..409fbad527 100644 --- a/libs/revolution/src/nand/NANDLogging.c +++ b/libs/revolution/src/nand/NANDLogging.c @@ -7,7 +7,7 @@ static IOSFd s_fd = -255; static IOSError s_err = ISFS_ERROR_UNKNOWN; static int s_stage; -static char s_message[256] ATTRIBUTE_ALIGN(64); +ATTRIBUTE_ALIGN(64) static char s_message[256]; static NANDLoggingCallback s_callback = 0; static void asyncRoutine(ISFSError, void*); @@ -70,8 +70,8 @@ static void callbackRoutine(BOOL result) { static void asyncRoutine(ISFSError result, void *ctxt) { ISFSError ret = ISFS_ERROR_UNKNOWN; - static char s_rBuf[256] ATTRIBUTE_ALIGN(64); - static char s_wBuf[256] ATTRIBUTE_ALIGN(64); + ATTRIBUTE_ALIGN(64) static char s_rBuf[256]; + ATTRIBUTE_ALIGN(64) static char s_wBuf[256]; ++s_stage; if (s_stage == 2) { diff --git a/libs/revolution/src/nand/nand.c b/libs/revolution/src/nand/nand.c index 6155789690..1fd2640660 100644 --- a/libs/revolution/src/nand/nand.c +++ b/libs/revolution/src/nand/nand.c @@ -293,7 +293,7 @@ s32 NANDMove(const char* path, const char* destDir) { } static ISFSError nandGetFileStatus(IOSFd fd, u32* length, u32* pos) { - ISFSFileStats fstat ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) ISFSFileStats fstat; ISFSError result = ISFS_GetFileStats(fd, &fstat); if (result == ISFS_ERROR_OK) { if (length) { diff --git a/libs/revolution/src/os/OSLaunch.c b/libs/revolution/src/os/OSLaunch.c index 2c57906e95..f82f2ef19f 100644 --- a/libs/revolution/src/os/OSLaunch.c +++ b/libs/revolution/src/os/OSLaunch.c @@ -8,7 +8,7 @@ void __OSRelaunchTitle(u32 resetCode) { s32 rc = 0; u32 ticketCnt = 1; ESTicketView* tik = NULL; - ESTitleId titleId ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) ESTitleId titleId; __OSPlayTimeType type = OSPLAYTIME_PERMANENT; u32 remain = 0; u8* bi2 = NULL; diff --git a/libs/revolution/src/os/OSNet.c b/libs/revolution/src/os/OSNet.c index e68c789734..b749532494 100644 --- a/libs/revolution/src/os/OSNet.c +++ b/libs/revolution/src/os/OSNet.c @@ -85,7 +85,7 @@ NWC24Err NWC24iSynchronizeRtcCounter(BOOL param_0) { } NWC24Err NWC24SuspendScheduler(void) { - static u8 susResult[0x20] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) static u8 susResult[0x20]; NWC24Err rt = NWC24_OK; NWC24Err closeRt = NWC24_OK; IOSFd fd; diff --git a/libs/revolution/src/os/OSPlayRecord.c b/libs/revolution/src/os/OSPlayRecord.c index a1499e71b7..b9743d9946 100644 --- a/libs/revolution/src/os/OSPlayRecord.c +++ b/libs/revolution/src/os/OSPlayRecord.c @@ -2,7 +2,7 @@ #include static BOOL PlayRecordGet = FALSE; -static OSPlayRecord PlayRecord ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static OSPlayRecord PlayRecord; static NANDFileInfo FileInfo; static NANDCommandBlock Block; static s32 PlayRecordState = 9; diff --git a/libs/revolution/src/os/OSPlayTime.c b/libs/revolution/src/os/OSPlayTime.c index 653afad7db..675d88d020 100644 --- a/libs/revolution/src/os/OSPlayTime.c +++ b/libs/revolution/src/os/OSPlayTime.c @@ -7,7 +7,7 @@ #include "__os.h" -OSAlarm __OSExpireAlarm ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) OSAlarm __OSExpireAlarm; OSTime __OSExpireTime; OSPlayTimeCallbackFunc __OSExpireCallback; BOOL __OSExpireSetExpiredFlag; @@ -103,7 +103,7 @@ BOOL __OSWriteExpiredFlag(void) { s32 rv = 0; NANDFileInfo nInfo; BOOL openNInfo = FALSE; - u8 titleId[32] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) u8 titleId[32]; rv = NANDPrivateCreate("/shared2/expired", 63, 0); @@ -162,7 +162,7 @@ BOOL __OSWriteExpiredFlagIfSet(void) { void* __OSPlayTimeRebootThread(void* args) { BOOL enabled; u32 frames, fadeShift = 1; - __OSExpireAIFadeStruct aiFade ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) __OSExpireAIFadeStruct aiFade; __OSExpireAIFade = &aiFade; memset(__OSExpireAIFade, 0, sizeof(__OSExpireAIFadeStruct)); @@ -227,9 +227,9 @@ out: s32 __OSGetPlayTime(ESTicketView* ticket, __OSPlayTimeType* type, u32* playTime) { s32 rv; u32 i; - ESLpEntry lpEntry[8] ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) ESLpEntry lpEntry[8]; u32 numCc = 0, seenOther = 0; - ESTicketView ticketAligned ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) ESTicketView ticketAligned; ASSERTLINE(601, ticket && type && playTime); @@ -288,7 +288,7 @@ out: s32 __OSGetPlayTimeCurrent(__OSPlayTimeType* type, u32* playTime) { s32 rv; - ESTicketView ticket ATTRIBUTE_ALIGN(32); + ATTRIBUTE_ALIGN(32) ESTicketView ticket; ASSERTLINE(676, type && playTime); diff --git a/libs/revolution/src/os/OSRtc.c b/libs/revolution/src/os/OSRtc.c index 11fa04e05d..d5fa89ee54 100644 --- a/libs/revolution/src/os/OSRtc.c +++ b/libs/revolution/src/os/OSRtc.c @@ -3,7 +3,7 @@ #include "__os.h" -static SramControl Scb ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT); +ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT) static SramControl Scb; // prototypes static int GetRTC(u32* rtc); diff --git a/libs/revolution/src/os/OSStateFlags.c b/libs/revolution/src/os/OSStateFlags.c index 723069d5a6..9283c57499 100644 --- a/libs/revolution/src/os/OSStateFlags.c +++ b/libs/revolution/src/os/OSStateFlags.c @@ -2,7 +2,7 @@ #include #include -static OSStateFlags StateFlags ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static OSStateFlags StateFlags; static u32 CheckSum(OSStateFlags* flags) { u32* ptr, i, sum; diff --git a/libs/revolution/src/os/OSStateTM.c b/libs/revolution/src/os/OSStateTM.c index a81de612cf..d08bf5e6f6 100644 --- a/libs/revolution/src/os/OSStateTM.c +++ b/libs/revolution/src/os/OSStateTM.c @@ -5,14 +5,14 @@ #include -static u32 StmImInBuf[8] ATTRIBUTE_ALIGN(32); -static u32 StmImOutBuf[8] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u32 StmImInBuf[8]; +ATTRIBUTE_ALIGN(32) static u32 StmImOutBuf[8]; -static u32 StmVdInBuf[8] ATTRIBUTE_ALIGN(32); -static u32 StmVdOutBuf[8] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u32 StmVdInBuf[8]; +ATTRIBUTE_ALIGN(32) static u32 StmVdOutBuf[8]; -static u32 StmEhInBuf[8] ATTRIBUTE_ALIGN(32); -static u32 StmEhOutBuf[8] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u32 StmEhInBuf[8]; +ATTRIBUTE_ALIGN(32) static u32 StmEhOutBuf[8]; static OSResetCallback ResetCallback; static OSPowerCallback PowerCallback; diff --git a/libs/revolution/src/os/OSThread.c b/libs/revolution/src/os/OSThread.c index dbe745d17a..447709a17d 100644 --- a/libs/revolution/src/os/OSThread.c +++ b/libs/revolution/src/os/OSThread.c @@ -873,4 +873,4 @@ void* OSGetThreadSpecific(s32 index) { #include "global.h" extern u8 Debug_BBA_804516D0; -u8 Debug_BBA_804516D0 ATTRIBUTE_ALIGN(8); +ATTRIBUTE_ALIGN(8) u8 Debug_BBA_804516D0; diff --git a/libs/revolution/src/sc/scsystem.c b/libs/revolution/src/sc/scsystem.c index 74d78e6bfc..a03fe7b3b7 100644 --- a/libs/revolution/src/sc/scsystem.c +++ b/libs/revolution/src/sc/scsystem.c @@ -75,8 +75,8 @@ static const char ConfDirName[] = "/shared2/sys"; static const char ConfFileName[] = "/shared2/sys/SYSCONF"; static const char ProductInfoFileName[] = "/title/00000001/00000002/data/setting.txt"; -static u8 ConfBuf[0x4000] ATTRIBUTE_ALIGN(32); -static u8 ConfBufForFlush[0x4000] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) static u8 ConfBuf[0x4000]; +ATTRIBUTE_ALIGN(32) static u8 ConfBufForFlush[0x4000]; static u8 Initialized; static u8 DirtyFlag; diff --git a/libs/revolution/src/wpad/WPAD.c b/libs/revolution/src/wpad/WPAD.c index 20e7509da3..3a0591f396 100644 --- a/libs/revolution/src/wpad/WPAD.c +++ b/libs/revolution/src/wpad/WPAD.c @@ -262,7 +262,7 @@ void WPADiManageHandler(OSAlarm*, OSContext*) { BTA_HhGetAclQueueInfo(); } -u8 __WPADiManageHandlerStack[4096] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) u8 __WPADiManageHandlerStack[4096]; void WPADiManageHandler0(OSAlarm* alarm, OSContext* context) { OSSwitchFiberEx((u32)alarm, (u32)context, 0, 0, (u32)WPADiManageHandler, (u32)(__WPADiManageHandlerStack + sizeof(__WPADiManageHandlerStack))); diff --git a/libs/revolution/src/wud/WUD.c b/libs/revolution/src/wud/WUD.c index 72a2c9b60b..deef3b2624 100644 --- a/libs/revolution/src/wud/WUD.c +++ b/libs/revolution/src/wud/WUD.c @@ -41,7 +41,7 @@ WUDControlBlock _wcb; WUDDevInfo _work; static WUDDiscResp _discResp; SCBtDeviceInfoArray _scArray; -u8 __WUDHandlerStack[0x1000] ATTRIBUTE_ALIGN(32); +ATTRIBUTE_ALIGN(32) u8 __WUDHandlerStack[0x1000]; extern u8 _scFlush; diff --git a/mods/ao_mod/src/mod.cpp b/mods/ao_mod/src/mod.cpp index fb36ddb40c..8a96ae3c95 100644 --- a/mods/ao_mod/src/mod.cpp +++ b/mods/ao_mod/src/mod.cpp @@ -53,6 +53,7 @@ ResourceBuffer g_denoiseSource = RESOURCE_BUFFER_INIT; ResourceBuffer g_compositeSource = RESOURCE_BUFFER_INIT; GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT; +GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT; WGPUComputePipeline g_preprocessPipeline = nullptr; WGPUComputePipeline g_mip4Pipeline = nullptr; WGPUComputePipeline g_gtaoPipeline = nullptr; @@ -226,19 +227,17 @@ bool build_composite_pipeline( .dstFactor = WGPUBlendFactor_One, }, }; - WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT; - colorTarget.format = g_deviceInfo.color_format; - if (blend) { - colorTarget.blend = &blendState; - } + WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS]; + const uint32_t colorTargetCount = gfx_init_color_target_states( + &g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All); WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT; fragment.module = module; fragment.entryPoint = {"fs_main", WGPU_STRLEN}; - fragment.targetCount = 1; - fragment.targets = &colorTarget; + fragment.targetCount = colorTargetCount; + fragment.targets = colorTargets; // Depth state must match the EFB pass despite never touching depth. WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT; - depthStencil.format = g_deviceInfo.depth_format; + depthStencil.format = g_sceneTargetLayout.depth_stencil_format; depthStencil.depthWriteEnabled = WGPUOptionalBool_False; depthStencil.depthCompare = WGPUCompareFunction_Always; @@ -248,7 +247,7 @@ bool build_composite_pipeline( pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN}; pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; pipelineDesc.depthStencil = &depthStencil; - pipelineDesc.multisample.count = g_deviceInfo.sample_count; + pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count; pipelineDesc.fragment = &fragment; outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc); wgpuShaderModuleRelease(module); @@ -504,7 +503,7 @@ void on_compute( // Render worker thread: composite the AO over the scene (or show it, in debug view). void on_draw( ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) { - if (payloadSize != sizeof(CompositePayload)) { + if (payloadSize != sizeof(CompositePayload) || ctx->layout.key != g_sceneTargetLayout.key) { return; } CompositePayload data; @@ -816,6 +815,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) { if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) { return mods::set_error(error, MOD_ERROR, "failed to query device info"); } + if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) { + return mods::set_error(error, MOD_ERROR, "failed to query scene target layout"); + } if (!build_compute_pipeline("AO preprocess depth", g_preprocessSource, "preprocess_depth", g_preprocessPipeline, g_preprocessLayout) || !build_compute_pipeline("AO downsample mip4", g_preprocessSource, "downsample_mip4", diff --git a/mods/basic_cosmetics_mod/CMakeLists.txt b/mods/basic_cosmetics_mod/CMakeLists.txt new file mode 100644 index 0000000000..f00540864b --- /dev/null +++ b/mods/basic_cosmetics_mod/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.25) +project(basic_cosmetics_mod 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) + +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 + FEATURES game fmt + SOURCES src/mod.cpp ${COSMETIC_SOURCES} + MOD_JSON mod.json + RES_DIR res + BUNDLE +) + +target_link_libraries(basic_cosmetics_mod PRIVATE xxHash::xxhash) diff --git a/mods/basic_cosmetics_mod/mod.json b/mods/basic_cosmetics_mod/mod.json new file mode 100644 index 0000000000..d6c258bfe9 --- /dev/null +++ b/mods/basic_cosmetics_mod/mod.json @@ -0,0 +1,7 @@ +{ + "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/res/.gitkeep b/mods/basic_cosmetics_mod/res/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mods/basic_cosmetics_mod/src/color_utils.cpp b/mods/basic_cosmetics_mod/src/color_utils.cpp new file mode 100644 index 0000000000..d1dc2eb096 --- /dev/null +++ b/mods/basic_cosmetics_mod/src/color_utils.cpp @@ -0,0 +1,88 @@ +#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 new file mode 100644 index 0000000000..94e1ce62a8 --- /dev/null +++ b/mods/basic_cosmetics_mod/src/color_utils.hpp @@ -0,0 +1,30 @@ +#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 new file mode 100644 index 0000000000..e90e76d8b3 --- /dev/null +++ b/mods/basic_cosmetics_mod/src/hooks.cpp @@ -0,0 +1,731 @@ +#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_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 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 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( + 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(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 new file mode 100644 index 0000000000..62bb77cde9 --- /dev/null +++ b/mods/basic_cosmetics_mod/src/hooks.hpp @@ -0,0 +1,7 @@ +#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 new file mode 100644 index 0000000000..905a4345ff --- /dev/null +++ b/mods/basic_cosmetics_mod/src/midna_hair_color.cpp @@ -0,0 +1,115 @@ +#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 new file mode 100644 index 0000000000..ad08f7c8c6 --- /dev/null +++ b/mods/basic_cosmetics_mod/src/midna_hair_color.hpp @@ -0,0 +1,19 @@ +#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 new file mode 100644 index 0000000000..89a321da9c --- /dev/null +++ b/mods/basic_cosmetics_mod/src/mod.cpp @@ -0,0 +1,653 @@ +#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 new file mode 100644 index 0000000000..6d77949b03 --- /dev/null +++ b/mods/basic_cosmetics_mod/src/mod.hpp @@ -0,0 +1,65 @@ +#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 new file mode 100644 index 0000000000..98c60dbad3 --- /dev/null +++ b/mods/basic_cosmetics_mod/src/texture_utils.cpp @@ -0,0 +1,682 @@ +#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 new file mode 100644 index 0000000000..49dd0df81a --- /dev/null +++ b/mods/basic_cosmetics_mod/src/texture_utils.hpp @@ -0,0 +1,21 @@ +#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/flow_demo/CMakeLists.txt b/mods/flow_demo/CMakeLists.txt new file mode 100644 index 0000000000..8487073dae --- /dev/null +++ b/mods/flow_demo/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.25) +project(flow_demo 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 () + +add_mod(flow_demo + FEATURES fmt + SOURCES src/mod.cpp + MOD_JSON mod.json +) diff --git a/mods/flow_demo/mod.json b/mods/flow_demo/mod.json new file mode 100644 index 0000000000..90d395bdcc --- /dev/null +++ b/mods/flow_demo/mod.json @@ -0,0 +1,7 @@ +{ + "id": "dev.twilitrealm.flow_demo", + "name": "[Demo] Flow & Messages", + "version": "1.0.0", + "author": "Twilit Realm", + "description": "Adds a FlowService demonstration submenu to Midna's menu." +} diff --git a/mods/flow_demo/src/mod.cpp b/mods/flow_demo/src/mod.cpp new file mode 100644 index 0000000000..a05150458b --- /dev/null +++ b/mods/flow_demo/src/mod.cpp @@ -0,0 +1,302 @@ +#include "mods/service.hpp" +#include "mods/svc/flow.hpp" +#include "mods/svc/log.hpp" + +#include +#include +#include +#include +#include +#include + +DEFINE_MOD(); +IMPORT_SERVICE(LogService, svc_log); +IMPORT_SERVICE(FlowService, svc_flow); +IMPORT_SERVICE(MessageService, svc_message); + +namespace { + +// The Midna flow lives in BMG group 0. +constexpr uint16_t kMessageGroup = 0; +// Midna's "speaker" value for messages. +constexpr uint16_t kMidnaSpeaker = 21; + +// Vanilla flow node/branch/edge IDs that we reference or override. +constexpr uint16_t kMidnaPromptHumanNode = 0x018c; +constexpr uint16_t kMidnaPromptWolfNode = 0x018d; +constexpr uint16_t kMidnaHumanBranch = 0x0190; +constexpr uint16_t kMidnaWolfBranch = 0x0193; +constexpr uint16_t kMidnaTalkNode = 0x018f; +constexpr uint16_t kMidnaHumanTalkEdge = 0x0113; +constexpr uint16_t kMidnaWolfTalkEdge = 0x0119; + +// For vanilla messages, we need both the entry index and the message ID. +// For registered custom messages, they're the same. +constexpr uint16_t kMidnaMenuPromptEntry = 3003; // message index used in flow message data +constexpr uint16_t kMidnaMenuPromptId = 2042; // message ID used to override + +constexpr std::array kAllLanguages{ + MESSAGE_LANGUAGE_ENGLISH, + MESSAGE_LANGUAGE_GERMAN, + MESSAGE_LANGUAGE_FRENCH, + MESSAGE_LANGUAGE_SPANISH, + MESSAGE_LANGUAGE_ITALIAN, + MESSAGE_LANGUAGE_JAPANESE, +}; + +constexpr mods::flow::MessageStyle kResponseStyle = + mods::flow::MessageStyle{}.speaker(kMidnaSpeaker).box_kind(MESSAGE_BOX_MIDNA); +constexpr mods::flow::MessageStyle kPromptStyle = + kResponseStyle.draw_type(MESSAGE_DRAW_INSTANT).talk_anim(31).face_anim(31); + +mods::flow::Query g_demoQuery; +mods::flow::Event g_demoEvent; +mods::flow::Graph g_graph; +std::vector g_messages; +std::vector g_overrides; +std::vector g_visitedPromptText; +uint32_t g_queryExecutions = 0; +uint32_t g_eventExecutions = 0; + +mods::flow::MessageBuilder build_prompt(std::string_view suffix) { + return mods::flow::MessageBuilder{kPromptStyle} + .text("What is it, ") + .text_color(MESSAGE_COLOR_RED) + .player_name() + .text_color(MESSAGE_COLOR_DEFAULT) + .text("?\n") + .text_scale(85) + .text(suffix) + .text_scale(100) + .await_choice(); +} + +// Registers a single message for all languages. +mods::flow::RegisteredMessage register_message(const mods::flow::MessageBuilder& builder) { + std::vector variants; + variants.reserve(kAllLanguages.size()); + for (const MessageLanguage language : kAllLanguages) { + variants.push_back(builder.build(language)); + } + return mods::flow::register_message(kMessageGroup, variants); +} + +ModResult add_message(const mods::flow::MessageBuilder& builder, MessageId& outId) { + auto message = register_message(builder); + if (!message) { + return message.result(); + } + outId = message.id(); + g_messages.push_back(std::move(message)); + return MOD_OK; +} + +ModResult add_fixed_override( + uint16_t messageId, const std::vector& text, bool addCallback) { + for (const MessageLanguage language : kAllLanguages) { + auto fixed = + mods::flow::override_message(kMessageGroup, messageId, language, std::span{text}); + if (!fixed) { + return fixed.result(); + } + g_overrides.push_back(std::move(fixed)); + if (!addCallback) { + continue; + } + auto callback = mods::flow::override_message_fn(kMessageGroup, messageId, language, + [](ModContext*, const MessageOverrideContext*, MessageTextData* outText, + void*) -> bool { + if (g_eventExecutions == 0 || outText == nullptr) { + return false; + } + outText->text = g_visitedPromptText.data(); + outText->text_size = g_visitedPromptText.size(); + return true; + }); + if (!callback) { + return callback.result(); + } + g_overrides.push_back(std::move(callback)); + } + return MOD_OK; +} + +uint16_t demo_query(ModContext*, const FlowQueryContext* query, void*) { + if (query == nullptr || query->parameter != 1234 || query->result_count < 3) { + return 0; + } + const auto result = static_cast(g_queryExecutions % 3); + if (query->phase == FLOW_QUERY_PHASE_EXECUTE) { + ++g_queryExecutions; + mods::log::info("Flow demo query selected result {}", result); + } + return result; +} + +void demo_event(ModContext*, const FlowEventContext* event, void*) { + ++g_eventExecutions; + const uint8_t path = event != nullptr ? event->parameters[3] : 0; + mods::log::info("Flow demo event {} executed (activation {})", path, g_eventExecutions); +} + +} // namespace + +extern "C" { +MOD_EXPORT ModResult mod_initialize(ModError* outError) { + g_queryExecutions = 0; + g_eventExecutions = 0; + + MessageId humanSelectionId = 0; + MessageId wolfSelectionId = 0; + MessageId labPromptId = 0; + MessageId labSelectionId = 0; + MessageId formattedId = 0; + MessageId evenId = 0; + MessageId oddId = 0; + MessageId timeoutId = 0; + + auto result = add_message(mods::flow::MessageBuilder{} + .speaker(kMidnaSpeaker) + .options("Transform into human", "Warp", "Flow demo"), + humanSelectionId); + if (result == MOD_OK) { + result = add_message(mods::flow::MessageBuilder{} + .speaker(kMidnaSpeaker) + .options("Transform into wolf", "Warp", "Flow demo"), + wolfSelectionId); + } + if (result == MOD_OK) { + result = add_message(build_prompt("Choose a FlowService test."), labPromptId); + } + if (result == MOD_OK) { + result = add_message(mods::flow::MessageBuilder{} + .speaker(kMidnaSpeaker) + .options("Formatted message", "Callback branch", "Talk to Midna"), + labSelectionId); + } + if (result == MOD_OK) { + result = add_message(mods::flow::MessageBuilder{kResponseStyle} + .text_color(MESSAGE_COLOR_DEFAULT) + .text("Custom ") + .text_color(0xFF0000FF) + .text("colors") + .text_color(MESSAGE_COLOR_DEFAULT) + .text(" and even ") + .text_color(0x00FF00FF, 0x00FFFFFF) + .text("gradients") + .text_color(MESSAGE_COLOR_DEFAULT) + .text("!\n") + .character_delay(3) + .text("This text is slow. ") + .character_delay(0) + .pause(12) + .text_scale(150) + .text("Big") + .text_scale(100) + .text(" text too.") + .input_after_delay(15), + formattedId); + } + if (result == MOD_OK) { + result = add_message(mods::flow::MessageBuilder{kResponseStyle} + .text("Query returned ") + .text_color(MESSAGE_COLOR_GREEN) + .text("zero") + .text_color(MESSAGE_COLOR_DEFAULT) + .text(".") + .auto_advance(75), + evenId); + } + if (result == MOD_OK) { + result = add_message(mods::flow::MessageBuilder{kResponseStyle} + .box_kind(MESSAGE_BOX_LIGHT_SPIRIT) + .text("Query returned ") + .text_color(MESSAGE_COLOR_RED) + .text("one") + .text_color(MESSAGE_COLOR_DEFAULT) + .text(".") + .auto_advance(75), + oddId); + } + if (result == MOD_OK) { + result = add_message(mods::flow::MessageBuilder{kResponseStyle} + .text("Query returned ") + .text_color(MESSAGE_COLOR_YELLOW) + .text("three") + .text_color(MESSAGE_COLOR_DEFAULT) + .text(".\nThis will timeout after three seconds.") + .input_or_timeout(90), + timeoutId); + } + if (result != MOD_OK) { + return mods::set_error(outError, result, "failed to register custom demo messages"); + } + + g_visitedPromptText = + build_prompt("A custom event has run.").build(MESSAGE_LANGUAGE_ENGLISH).text(); + result = add_fixed_override(kMidnaMenuPromptId, + build_prompt("Flow demo is installed.").build(MESSAGE_LANGUAGE_ENGLISH).text(), true); + if (result != MOD_OK) { + return mods::set_error(outError, result, "failed to register demo message overrides"); + } + + g_demoQuery = mods::flow::register_query("flow_demo cycling branch", demo_query); + g_demoEvent = mods::flow::register_event("flow_demo activation", demo_event); + if (!g_demoQuery || !g_demoEvent) { + const auto callbackResult = !g_demoQuery ? g_demoQuery.result() : g_demoEvent.result(); + return mods::set_error(outError, callbackResult, "failed to register flow callbacks"); + } + + // Set up our demo graph + mods::flow::GraphBuilder graph{kMessageGroup}; + const auto demoSetup = graph.add_event(FLOW_EVENT_SELECT_VERTICAL, {0, 0, 0, 4}); + const auto formattedMessage = graph.add_message(formattedId).next(demoSetup); + const auto formattedEvent = + graph.add_event(g_demoEvent.id(), {0, 0, 0, 1}).next(formattedMessage); + const auto evenMessage = graph.add_message(evenId).next(demoSetup); + const auto evenEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 2}).next(evenMessage); + const auto oddMessage = graph.add_message(oddId).next(demoSetup); + const auto oddEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 3}).next(oddMessage); + const auto timeoutMessage = graph.add_message(timeoutId).next(demoSetup); + const auto timeoutEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 4}).next(timeoutMessage); + const auto callbackBranch = + graph.add_branch(g_demoQuery.id(), 1234).results({evenEvent, oddEvent, timeoutEvent}); + const auto choiceBranch = + graph.add_branch(FLOW_QUERY_SELECT_3_CANCEL, 0) + .results({formattedEvent, callbackBranch, kMidnaTalkNode, mods::flow::kEnd}); + const auto demoSelection = graph.add_message(labSelectionId).next(choiceBranch); + const auto demoPrompt = graph.add_message(labPromptId).next(demoSelection); + demoSetup.next(demoPrompt); + + // Patch original nodes and edges so they flow into our custom graph + const auto humanSelection = graph.add_message(humanSelectionId).next(kMidnaHumanBranch); + const auto wolfSelection = graph.add_message(wolfSelectionId).next(kMidnaWolfBranch); + graph.patch_node( + kMidnaPromptHumanNode, mods::flow::message(0, kMidnaMenuPromptEntry, humanSelection)); + graph.patch_node( + kMidnaPromptWolfNode, mods::flow::message(0, kMidnaMenuPromptEntry, wolfSelection)); + graph.patch_edge(kMidnaHumanTalkEdge, demoSetup); + graph.patch_edge(kMidnaWolfTalkEdge, demoSetup); + + g_graph = graph.commit(); + if (!g_graph) { + return mods::set_error(outError, g_graph.result(), "failed to commit the flow demo graph"); + } + + mods::log::info("Flow demo ready: {} custom messages", g_messages.size()); + return MOD_OK; +} + +MOD_EXPORT ModResult mod_update(ModError*) { + return MOD_OK; +} + +MOD_EXPORT ModResult mod_shutdown(ModError*) { + mods::log::info("Flow demo unloaded after {} events", g_eventExecutions); + g_graph.reset(); + g_overrides.clear(); + g_messages.clear(); + g_visitedPromptText.clear(); + return MOD_OK; +} +} diff --git a/mods/shadow_mod/CMakeLists.txt b/mods/shadow_mod/CMakeLists.txt index ae44eca49c..3f2e6b9412 100644 --- a/mods/shadow_mod/CMakeLists.txt +++ b/mods/shadow_mod/CMakeLists.txt @@ -17,5 +17,4 @@ add_mod(shadow_mod SOURCES src/mod.cpp MOD_JSON mod.json RES_DIR res - BUNDLE ) diff --git a/mods/shadow_mod/src/mod.cpp b/mods/shadow_mod/src/mod.cpp index 36f33de41d..fa4056040b 100644 --- a/mods/shadow_mod/src/mod.cpp +++ b/mods/shadow_mod/src/mod.cpp @@ -20,12 +20,12 @@ #include "dolphin/gx/GXPixel.h" #include "dolphin/gx/GXTransform.h" #include "m_Do/m_Do_mtx.h" -#include "mods/svc/hook.hpp" #include "mods/service.hpp" #include "mods/svc/camera.h" #include "mods/svc/config.h" #include "mods/svc/gfx.h" #include "mods/svc/hook.h" +#include "mods/svc/hook.hpp" #include "mods/svc/log.h" #include "mods/svc/resource.h" #include "mods/svc/ui.h" @@ -69,6 +69,7 @@ GfxStageHookHandle g_frameBeforeHudHook = 0; UiWindowHandle g_controlsWindow = 0; ResourceBuffer g_shaderSource = RESOURCE_BUFFER_INIT; GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT; +GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT; WGPURenderPipeline g_compositePipeline = nullptr; // multiply blend WGPURenderPipeline g_compositeDebugPipeline = nullptr; // no blend (debug views) WGPUBindGroupLayout g_compositeLayout = nullptr; @@ -399,18 +400,16 @@ bool build_composite_pipeline( .srcFactor = WGPUBlendFactor_Zero, .dstFactor = WGPUBlendFactor_One}, }; - WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT; - colorTarget.format = g_deviceInfo.color_format; - if (blend) { - colorTarget.blend = &blendState; - } + WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS]; + const uint32_t colorTargetCount = gfx_init_color_target_states( + &g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All); WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT; fragment.module = module; fragment.entryPoint = {"fs_main", WGPU_STRLEN}; - fragment.targetCount = 1; - fragment.targets = &colorTarget; + fragment.targetCount = colorTargetCount; + fragment.targets = colorTargets; WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT; - depthStencil.format = g_deviceInfo.depth_format; + depthStencil.format = g_sceneTargetLayout.depth_stencil_format; depthStencil.depthWriteEnabled = WGPUOptionalBool_False; depthStencil.depthCompare = WGPUCompareFunction_Always; @@ -420,7 +419,7 @@ bool build_composite_pipeline( pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN}; pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; pipelineDesc.depthStencil = &depthStencil; - pipelineDesc.multisample.count = g_deviceInfo.sample_count; + pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count; pipelineDesc.fragment = &fragment; outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc); wgpuShaderModuleRelease(module); @@ -518,7 +517,7 @@ WGPUBindGroup create_composite_bind_group(WGPUDevice device, WGPUBindGroupLayout // Render worker thread: fullscreen deferred-shadow composite. void on_draw( ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) { - if (payloadSize != sizeof(DrawPayload)) { + if (payloadSize != sizeof(DrawPayload) || ctx->layout.key != g_sceneTargetLayout.key) { return; } DrawPayload data; @@ -1243,6 +1242,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) { if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) { return mods::set_error(error, MOD_ERROR, "failed to query device info"); } + if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) { + return mods::set_error(error, MOD_ERROR, "failed to query scene target layout"); + } if (!build_composite_pipeline(true, g_compositePipeline, g_compositeLayout) || !build_composite_pipeline(false, g_compositeDebugPipeline, g_compositeDebugLayout)) { diff --git a/mods/window_demo/CMakeLists.txt b/mods/window_demo/CMakeLists.txt index 9150ec7de6..4257f9686e 100644 --- a/mods/window_demo/CMakeLists.txt +++ b/mods/window_demo/CMakeLists.txt @@ -16,5 +16,4 @@ add_mod(window_demo FEATURES fmt webgpu SOURCES src/logging.cpp src/mod.cpp MOD_JSON mod.json - BUNDLE ) diff --git a/mods/window_demo/src/mod.cpp b/mods/window_demo/src/mod.cpp index fb3cc31809..0a1e9333ff 100644 --- a/mods/window_demo/src/mod.cpp +++ b/mods/window_demo/src/mod.cpp @@ -188,14 +188,6 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) { g_stageHook = 0; return mods::set_error(error, MOD_ERROR, "failed to register mod panel"); } - - if (open_window() != MOD_OK) { - svc_gfx->unregister_stage_hook(mod_ctx, g_stageHook); - g_stageHook = 0; - return mods::set_error(error, MOD_ERROR, "failed to open auxiliary window"); - } - - mods::log::info("auxiliary WebGPU window ready"); return MOD_OK; } diff --git a/platforms/android/README.md b/platforms/android/README.md index 7a5d01ee44..6ebcac23cd 100644 --- a/platforms/android/README.md +++ b/platforms/android/README.md @@ -1,6 +1,6 @@ # Android Shell -This directory contains a minimal SDLActivity-based Android app wrapper for Dusklight. +This directory contains Dusklight's Android shell built on top of Borealis. ## Prerequisites @@ -25,24 +25,19 @@ cmake --build --preset android-arm64 This build produces `build/android-arm64/libmain.so` -## Refresh SDL Java Shim (Optional) - -If you update SDL and want to refresh the embedded Java shim files: - -```bash -./android/scripts/sync-sdl-java.sh -``` - ## Build APK ```bash -cd android +cd platforms/android ./gradlew :app:assembleDebug ``` Output APK: -- `android/app/build/outputs/apk/debug/app-debug.apk` +- `app/build/outputs/apk/debug/app-arm64-v8a-debug.apk` + +Aurora needs a hardware-backed graphics adapter. If an AVD has GPU +acceleration disabled, launch it with `-gpu host`. ## Launch With Runtime Args (adb) @@ -50,10 +45,13 @@ You can pass command-line args through the activity intent: ```bash adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \ - --es dusk_args "--backend vulkan" + --es borealis_args "--backend vulkan" ``` Supported extras: -- `dusk_args`: single shell-like argument string -- `dusk_argv`: string-array argv +- `borealis_args`: single shell-like argument string +- `borealis_argv`: string-array argv + +The legacy `dusk_args` and `dusk_argv` names remain accepted during the shell +transition. diff --git a/platforms/android/app/build.gradle b/platforms/android/app/build.gradle index 08d04bf35f..aba966b448 100644 --- a/platforms/android/app/build.gradle +++ b/platforms/android/app/build.gradle @@ -2,163 +2,32 @@ plugins { id 'com.android.application' } -def versionNameStr = (System.getenv("DUSK_VERSION") ?: "v0.1.0").replaceFirst("^v", "") -def versionCodeInt = (System.getenv("DUSK_VERSION_CODE") ?: "100000").toInteger() - def duskRepoDir = rootProject.projectDir.parentFile.parentFile -def androidNativeBuildDir = new File(duskRepoDir, 'build/android-arm64') -def nativeLibrary = new File(androidNativeBuildDir, 'libmain.so') -def stageStripValue = providers.gradleProperty('ANDROID_STAGE_STRIP') - .orElse(providers.gradleProperty('androidStageStrip')) - .orElse(providers.environmentVariable('ANDROID_STAGE_STRIP')) - .orElse('1') -def androidHome = { - def sdkPath = System.getenv('ANDROID_HOME') - if (!sdkPath) { - throw new GradleException('ANDROID_HOME is not available') - } - def sdkDir = new File(sdkPath) - if (!sdkDir.isDirectory()) { - throw new GradleException("ANDROID_HOME points to a missing directory: ${sdkDir}") - } - sdkDir -}.memoize() +def borealisDir = new File(duskRepoDir, 'extern/borealis') -def androidNdkVersion = { - def ndkVersion = System.getenv('ANDROID_NDK_VERSION') - if (!ndkVersion) { - throw new GradleException('ANDROID_NDK_VERSION is not available') - } - ndkVersion -}.memoize() +ext.borealisAndroid = [ + borealisDir: borealisDir, + propertiesFile: new File(duskRepoDir, 'build/android-arm64/borealis-android.properties'), + namespace: 'dev.twilitrealm.dusk', + applicationId: 'dev.twilitrealm.dusk', + abis: ['arm64-v8a'], + assets: [ + [ + from: new File(duskRepoDir, 'res'), + into: 'res', + excludes: ['**/.DS_Store'] + ], + [ + from: new File(duskRepoDir, 'build/android-arm64/bundled_mods'), + into: 'mods', + includes: ['*.dusk'] + ] + ], + proguardRules: file('proguard-rules.pro') +] -def androidNdkDir = { - def ndkDir = new File(androidHome(), "ndk/${androidNdkVersion()}") - if (!new File(ndkDir, 'build/cmake/android.toolchain.cmake').isFile()) { - throw new GradleException( - "Android NDK ${androidNdkVersion()} is missing or invalid at ${ndkDir}" - ) - } - ndkDir -}.memoize() - -def llvmPrebuiltDir = { - def prebuiltRoot = new File(androidNdkDir(), 'toolchains/llvm/prebuilt') - def prebuiltDir = (prebuiltRoot.listFiles()?.findAll { it.isDirectory() } ?: []) - .sort { it.name } - .find { candidate -> - new File( - candidate, - 'sysroot/usr/lib/aarch64-linux-android/libc++_shared.so' - ).isFile() - } - if (!prebuiltDir) { - throw new GradleException("Cannot find NDK libc++_shared.so under ${prebuiltRoot}") - } - prebuiltDir -}.memoize() - -def stlLibrary = { - def library = new File( - llvmPrebuiltDir(), - 'sysroot/usr/lib/aarch64-linux-android/libc++_shared.so' - ) - if (!library.isFile()) { - throw new GradleException("libc++_shared.so is missing") - } - library -}.memoize() - -def stagedJniLibsDir = layout.buildDirectory.dir('generated/jniLibs/dusklight') - -def stageJniLibs = tasks.register('stageJniLibs', Sync) { - group = 'build' - from(nativeLibrary) { - rename { 'libmain.so' } - into 'arm64-v8a' - } - from(providers.provider { stlLibrary() }) { - rename { 'libc++_shared.so' } - into 'arm64-v8a' - } - into(stagedJniLibsDir) - - doFirst { - if (!nativeLibrary.isFile()) { - throw new GradleException("Native library is missing") - } - } -} - -def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusklight') -def syncDuskAssets = tasks.register('syncDuskAssets', Sync) { - from(new File(duskRepoDir, 'res')) { - into 'res' - exclude '**/.DS_Store' - } - from(new File(androidNativeBuildDir, 'bundled_mods')) { - into 'mods' - include '*.dusk' - } - into(duskGeneratedAssetsDir) -} - -android { - namespace 'dev.twilitrealm.dusk' - compileSdk 36 - ndkVersion androidNdkVersion() - - defaultConfig { - applicationId 'dev.twilitrealm.dusk' - minSdk 26 - targetSdk 36 - versionCode versionCodeInt - versionName versionNameStr - } - - buildTypes { - debug { - minifyEnabled false - } - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - sourceSets { - main { - jniLibs.srcDirs = [stagedJniLibsDir] - assets.srcDirs = [duskGeneratedAssetsDir] - } - } - - packaging { - jniLibs { - if (stageStripValue.get() == '0') { - keepDebugSymbols += '**/libmain.so' - } - } - } - - splits { - abi { - enable true - reset() - include 'arm64-v8a' - universalApk false - } - } - - lint { - abortOnError false - } -} +apply from: new File(borealisDir, 'platforms/android/gradle/borealis-application.gradle') dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) } - -tasks.named('preBuild').configure { - dependsOn(stageJniLibs, syncDuskAssets) -} diff --git a/platforms/android/app/proguard-rules.pro b/platforms/android/app/proguard-rules.pro index 72b7ca16fa..308a46ab83 100644 --- a/platforms/android/app/proguard-rules.pro +++ b/platforms/android/app/proguard-rules.pro @@ -1,4 +1 @@ -# Keep SDL activity and related JNI bridge methods. --keep class org.libsdl.app.** { *; } --keep class dev.twilitrealm.dusk.DuskHttpClient { *; } --keep class dev.twilitrealm.dusk.DuskHttpClient$Response { *; } +-keep class dev.twilitrealm.dusk.DuskActivity { *; } diff --git a/platforms/android/app/src/main/AndroidManifest.xml b/platforms/android/app/src/main/AndroidManifest.xml index cd687963e1..b7270ee433 100644 --- a/platforms/android/app/src/main/AndroidManifest.xml +++ b/platforms/android/app/src/main/AndroidManifest.xml @@ -26,8 +26,8 @@ android:resource="@xml/game_mode_config" /> diff --git a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskActivity.java b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskActivity.java index 2fa6cbacb3..4c1d8d41eb 100644 --- a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskActivity.java +++ b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskActivity.java @@ -1,102 +1,23 @@ package dev.twilitrealm.dusk; -import android.app.ActionBar; -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.ClipData; -import android.content.Context; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.os.Environment; -import android.provider.DocumentsContract; -import android.provider.OpenableColumns; -import android.provider.Settings; import android.util.Log; -import android.view.Display; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.View; -import android.view.Window; -import android.view.WindowInsets; -import android.view.WindowInsetsController; -import org.libsdl.app.SDLActivity; -import org.libsdl.app.SDLSurface; +import dev.encounter.borealis.BorealisActivity; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -public class DuskActivity extends SDLActivity { +public class DuskActivity extends BorealisActivity { private static final String TAG = "DuskActivity"; - private static final float DEFAULT_SURFACE_FRAME_RATE = 60.0f; - private static final int FOLDER_DIALOG_REQUEST_CODE = 0x4455; - private static final int MANAGE_STORAGE_REQUEST_CODE = 0x4456; - private static final String EXTERNAL_STORAGE_AUTHORITY = - "com.android.externalstorage.documents"; - - private long folderDialogUserdata = 0; - private boolean awaitingManageStoragePermission = false; - - private static native void nativeFolderDialogResult(long userdata, String path, String error); - - private static String[] splitArgs(String raw) { - List out = new ArrayList<>(); - StringBuilder current = new StringBuilder(); - boolean inSingle = false; - boolean inDouble = false; - boolean escaped = false; - - for (int i = 0; i < raw.length(); ++i) { - char c = raw.charAt(i); - if (escaped) { - current.append(c); - escaped = false; - continue; - } - if (c == '\\' && !inSingle) { - escaped = true; - continue; - } - if (c == '"' && !inSingle) { - inDouble = !inDouble; - continue; - } - if (c == '\'' && !inDouble) { - inSingle = !inSingle; - continue; - } - if (!inSingle && !inDouble && Character.isWhitespace(c)) { - if (current.length() > 0) { - out.add(current.toString()); - current.setLength(0); - } - continue; - } - current.append(c); - } - - if (escaped) { - current.append('\\'); - } - if (current.length() > 0) { - out.add(current.toString()); - } - return out.toArray(new String[0]); - } - @Override protected void onCreate(Bundle savedInstanceState) { extractBundledMods(); super.onCreate(savedInstanceState); - hideSystemBars(); } // Bundled mod packages ship as APK assets, which the native loader cannot read directly; @@ -143,459 +64,23 @@ public class DuskActivity extends SDLActivity { file.delete(); } - @Override - protected SDLSurface createSDLSurface(Context context) { - return new DuskSurface(context); - } - - @Override - protected void onResume() { - super.onResume(); - hideSystemBars(); - if (awaitingManageStoragePermission) { - resumeFolderDialogAfterPermissionGrant(); - } - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) { - hideSystemBars(); - } - } - - private void hideSystemBars() { - Window window = getWindow(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - window.setDecorFitsSystemWindows(false); - WindowInsetsController ctrl = window.getDecorView().getWindowInsetsController(); - if (ctrl != null) { - ctrl.setSystemBarsBehavior( - WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); - ctrl.hide(WindowInsets.Type.systemBars()); - } - } else { - View decorView = window.getDecorView(); - int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_STABLE; - decorView.setSystemUiVisibility(uiOptions); - ActionBar actionBar = getActionBar(); - if (actionBar != null) { - actionBar.hide(); - } - } - } - - @Override - protected String[] getLibraries() { - // SDL3 is statically linked into libmain.so in this build. - return new String[] { - "main" - }; - } - - public void setPreferredSurfaceFrameRate(float frameRate) { - runOnUiThread(() -> { - if (mSurface instanceof DuskSurface) { - ((DuskSurface)mSurface).setPreferredFrameRate(frameRate); - } - }); - } - - private static final class DuskSurface extends SDLSurface { - private float preferredFrameRate = DEFAULT_SURFACE_FRAME_RATE; - - DuskSurface(Context context) { - super(context); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - super.surfaceChanged(holder, format, width, height); - setTargetFrameRate(holder); - } - - void setPreferredFrameRate(float frameRate) { - preferredFrameRate = frameRate; - setTargetFrameRate(getHolder()); - } - - private void setTargetFrameRate(SurfaceHolder holder) { - if (!mIsSurfaceReady || Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } - - Surface surface = holder != null ? holder.getSurface() : getHolder().getSurface(); - if (surface == null || !surface.isValid()) { - return; - } - - float targetFrameRate = getMaxSupportedFrameRate(); - if (preferredFrameRate > 0.0f) { - targetFrameRate = preferredFrameRate; - } - if (targetFrameRate <= 0.0f) { - return; - } - - try { - surface.setFrameRate( - targetFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); - Log.v(TAG, "Requested surface frame rate " + targetFrameRate + " fps"); - } catch (RuntimeException e) { - Log.w(TAG, "Failed to request surface frame rate", e); - } - } - - private float getMaxSupportedFrameRate() { - if (mDisplay == null) { - return 0.0f; - } - - float maxFrameRate = mDisplay.getRefreshRate(); - Display.Mode[] modes = mDisplay.getSupportedModes(); - if (modes == null) { - return maxFrameRate; - } - - for (Display.Mode mode : modes) { - maxFrameRate = Math.max(maxFrameRate, mode.getRefreshRate()); - } - return maxFrameRate; - } - } - @Override protected String[] getArguments() { + String[] arguments = super.getArguments(); + if (arguments.length > 0) { + return arguments; + } + Intent intent = getIntent(); - if (intent != null) { - String[] argv = intent.getStringArrayExtra("dusk_argv"); - if (argv != null && argv.length > 0) { - return argv; - } - - String rawArgs = intent.getStringExtra("dusk_args"); - if (rawArgs != null) { - String trimmed = rawArgs.trim(); - if (!trimmed.isEmpty()) { - return splitArgs(trimmed); - } - } + if (intent == null) { + return arguments; } - return new String[0]; + String[] argv = intent.getStringArrayExtra("dusk_argv"); + if (argv != null && argv.length > 0) { + return argv; + } + String rawArgs = intent.getStringExtra("dusk_args"); + return rawArgs == null ? arguments : splitArguments(rawArgs.trim()); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == RESULT_OK) { - persistUriPermissions(data); - } - if (requestCode == FOLDER_DIALOG_REQUEST_CODE) { - finishFolderDialog(resultCode, data); - return; - } - super.onActivityResult(requestCode, resultCode, data); - } - - public boolean showFolderDialog(long userdata) { - if (userdata == 0 || folderDialogUserdata != 0) { - return false; - } - - folderDialogUserdata = userdata; - if (requiresManageStoragePermission() && !hasManageStoragePermission()) { - if (!requestManageStoragePermission()) { - finishFolderDialogWithError("Unable to request Android file access permission"); - return false; - } - return true; - } - - openFolderDialog(); - return true; - } - - private void openFolderDialog() { - runOnUiThread(() -> { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | - Intent.FLAG_GRANT_WRITE_URI_PERMISSION | - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | - Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); - - try { - startActivityForResult(intent, FOLDER_DIALOG_REQUEST_CODE); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Unable to open folder dialog.", e); - finishFolderDialog(Activity.RESULT_CANCELED, null); - } - }); - } - - private boolean requiresManageStoragePermission() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R; - } - - private boolean hasManageStoragePermission() { - return !requiresManageStoragePermission() || Environment.isExternalStorageManager(); - } - - private boolean requestManageStoragePermission() { - if (!requiresManageStoragePermission()) { - return true; - } - - awaitingManageStoragePermission = true; - runOnUiThread(() -> { - if (tryStartManageStorageIntent( - new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) - .setData(Uri.parse("package:" + getPackageName()))) || - tryStartManageStorageIntent( - new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))) - { - return; - } - - finishFolderDialogWithError("Unable to request Android file access permission"); - }); - return true; - } - - private boolean tryStartManageStorageIntent(Intent intent) { - try { - startActivityForResult(intent, MANAGE_STORAGE_REQUEST_CODE); - return true; - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Unable to open all-files access settings.", e); - return false; - } - } - - private void resumeFolderDialogAfterPermissionGrant() { - awaitingManageStoragePermission = false; - if (folderDialogUserdata == 0) { - return; - } - - if (hasManageStoragePermission()) { - openFolderDialog(); - return; - } - - finishFolderDialogWithError( - "Allow \"All files access\" for Dusklight before choosing a custom data folder"); - } - - private void finishFolderDialogWithError(String error) { - long userdata = folderDialogUserdata; - folderDialogUserdata = 0; - awaitingManageStoragePermission = false; - if (userdata != 0) { - nativeFolderDialogResult(userdata, null, error); - } - } - - private void finishFolderDialog(int resultCode, Intent data) { - long userdata = folderDialogUserdata; - folderDialogUserdata = 0; - if (userdata == 0) { - return; - } - - if (resultCode == RESULT_OK && data != null && data.getData() != null) { - String path = getRealPathForUri(data.getData()); - if (path != null && !path.isEmpty()) { - nativeFolderDialogResult(userdata, path, null); - } else { - nativeFolderDialogResult( - userdata, null, "Selected folder is not available as a filesystem path"); - } - return; - } - - nativeFolderDialogResult(userdata, null, null); - } - - private String getRealPathForUri(Uri uri) { - if (uri == null) { - return null; - } - - String scheme = uri.getScheme(); - if ("file".equals(scheme)) { - return uri.getPath(); - } - - if (!"content".equals(scheme) || - !EXTERNAL_STORAGE_AUTHORITY.equals(uri.getAuthority()) || - Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - { - return null; - } - - try { - return getExternalStoragePathForDocumentId(getExternalStorageDocumentId(uri)); - } catch (IllegalArgumentException e) { - Log.w(TAG, "Unable to resolve URI: " + uri, e); - return null; - } - } - - private static String getExternalStorageDocumentId(Uri uri) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTreeDocumentUri(uri)) { - return DocumentsContract.getTreeDocumentId(uri); - } - - return DocumentsContract.getDocumentId(uri); - } - - private static boolean isTreeDocumentUri(Uri uri) { - List segments = uri.getPathSegments(); - return segments.size() >= 2 && "tree".equals(segments.get(0)); - } - - private String getExternalStoragePathForDocumentId(String documentId) { - if (documentId == null || documentId.isEmpty()) { - return null; - } - if (documentId.startsWith("raw:")) { - return documentId.substring("raw:".length()); - } - - String[] parts = documentId.split(":", 2); - String volumeId = parts[0]; - String relativePath = parts.length > 1 ? parts[1] : ""; - - File root = getExternalStorageRoot(volumeId); - if (root == null) { - return null; - } - - return relativePath.isEmpty() - ? root.getAbsolutePath() - : new File(root, relativePath).getAbsolutePath(); - } - - private File getExternalStorageRoot(String volumeId) { - if ("primary".equalsIgnoreCase(volumeId)) { - return Environment.getExternalStorageDirectory(); - } - if ("home".equalsIgnoreCase(volumeId)) { - return new File( - Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOCUMENTS); - } - - File[] externalFilesDirs = getExternalFilesDirs(null); - if (externalFilesDirs != null) { - for (File externalFilesDir : externalFilesDirs) { - File root = getStorageRootForExternalFilesDir(externalFilesDir); - if (root != null && volumeId.equalsIgnoreCase(root.getName())) { - return root; - } - } - } - - File fallback = new File("/storage", volumeId); - return fallback.exists() ? fallback : null; - } - - private File getStorageRootForExternalFilesDir(File externalFilesDir) { - if (externalFilesDir == null) { - return null; - } - - String path = externalFilesDir.getAbsolutePath(); - int androidDir = path.indexOf("/Android/"); - if (androidDir <= 0) { - return null; - } - - return new File(path.substring(0, androidDir)); - } - - private void persistUriPermissions(Intent data) { - if (data == null) { - return; - } - - int permissionFlags = - data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - if (permissionFlags == 0) { - return; - } - - Uri uri = data.getData(); - if (uri != null) { - persistUriPermission(uri, permissionFlags); - } - - ClipData clipData = data.getClipData(); - if (clipData == null) { - return; - } - for (int i = 0; i < clipData.getItemCount(); ++i) { - Uri itemUri = clipData.getItemAt(i).getUri(); - if (itemUri != null) { - persistUriPermission(itemUri, permissionFlags); - } - } - } - - private void persistUriPermission(Uri uri, int permissionFlags) { - if ((permissionFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { - persistUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, "read"); - } - if ((permissionFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { - persistUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, "write"); - } - } - - private void persistUriPermission(Uri uri, int permissionFlag, String permissionName) { - try { - getContentResolver().takePersistableUriPermission(uri, permissionFlag); - } catch (SecurityException | IllegalArgumentException e) { - Log.w(TAG, "Unable to persist " + permissionName + " URI permission for " + uri, e); - } - } - - public String getDisplayNameForUri(String uriString) { - if (uriString == null || uriString.isEmpty()) { - return ""; - } - - Uri uri = Uri.parse(uriString); - if ("content".equals(uri.getScheme())) { - try (Cursor cursor = getContentResolver().query( - uri, new String[] { OpenableColumns.DISPLAY_NAME }, null, null, null)) - { - if (cursor != null && cursor.moveToFirst()) { - int displayNameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); - if (displayNameColumn >= 0) { - String displayName = cursor.getString(displayNameColumn); - if (displayName != null && !displayName.isEmpty()) { - return displayName; - } - } - } - } catch (SecurityException | IllegalArgumentException e) { - Log.w(TAG, "Unable to query display name for " + uri, e); - } - } else if ("file".equals(uri.getScheme())) { - String path = uri.getPath(); - if (path != null && !path.isEmpty()) { - String name = new File(path).getName(); - if (!name.isEmpty()) { - return name; - } - } - } - - String lastSegment = uri.getLastPathSegment(); - return lastSegment != null ? lastSegment : ""; - } } diff --git a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskDocumentsProvider.java b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskDocumentsProvider.java deleted file mode 100644 index d4ed6a3041..0000000000 --- a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskDocumentsProvider.java +++ /dev/null @@ -1,467 +0,0 @@ -package dev.twilitrealm.dusk; - -import android.content.ContentResolver; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.CancellationSignal; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract; -import android.provider.DocumentsContract.Document; -import android.provider.DocumentsContract.Root; -import android.provider.DocumentsProvider; -import android.webkit.MimeTypeMap; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -public class DuskDocumentsProvider extends DocumentsProvider { - public static final String AUTHORITY = "dev.twilitrealm.dusk.documents"; - - private static final String ROOT_ID = "dusk"; - private static final String ROOT_DOCUMENT_ID = "root"; - private static final String LOCATION_DESCRIPTOR_NAME = "data_location.json"; - private static final String DIRECTORY_MIME_TYPE = Document.MIME_TYPE_DIR; - - private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { - Root.COLUMN_ROOT_ID, - Root.COLUMN_FLAGS, - Root.COLUMN_TITLE, - Root.COLUMN_DOCUMENT_ID, - Root.COLUMN_ICON, - Root.COLUMN_AVAILABLE_BYTES, - Root.COLUMN_SUMMARY - }; - - private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { - Document.COLUMN_DOCUMENT_ID, - Document.COLUMN_DISPLAY_NAME, - Document.COLUMN_FLAGS, - Document.COLUMN_MIME_TYPE, - Document.COLUMN_LAST_MODIFIED, - Document.COLUMN_SIZE - }; - - @Override - public boolean onCreate() { - if (!isCustomDataPathEnabled()) { - ensureUserDirectories(); - } - return true; - } - - @Override - public Cursor queryRoots(String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); - if (isCustomDataPathEnabled()) { - return result; - } - - final File root = getRootDirectory(); - final MatrixCursor.RowBuilder row = result.newRow(); - - row.add(Root.COLUMN_ROOT_ID, ROOT_ID); - row.add(Root.COLUMN_FLAGS, - Root.FLAG_LOCAL_ONLY | - Root.FLAG_SUPPORTS_CREATE | - Root.FLAG_SUPPORTS_IS_CHILD); - row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name)); - row.add(Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID); - row.add(Root.COLUMN_ICON, R.mipmap.icon); - row.add(Root.COLUMN_AVAILABLE_BYTES, root.getFreeSpace()); - row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.documents_provider_summary)); - - return result; - } - - @Override - public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - includeDocument(result, documentId, getFileForDocumentId(documentId)); - return result; - } - - @Override - public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) - throws FileNotFoundException - { - return queryChildDocumentsInternal(parentDocumentId, projection); - } - - @Override - public Cursor queryChildDocuments(String parentDocumentId, String[] projection, Bundle queryArgs) - throws FileNotFoundException - { - return queryChildDocumentsInternal(parentDocumentId, projection); - } - - private Cursor queryChildDocumentsInternal(String parentDocumentId, String[] projection) - throws FileNotFoundException - { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - final File parent = getFileForDocumentId(parentDocumentId); - final File[] files = parent.listFiles(); - result.setNotificationUri(getContext().getContentResolver(), getChildDocumentsUri(parentDocumentId)); - - if (files == null) { - return result; - } - - for (File file : files) { - includeDocument(result, getDocumentIdForFile(file), file); - } - - return result; - } - - @Override - public boolean isChildDocument(String parentDocumentId, String documentId) { - try { - final File parent = getFileForDocumentId(parentDocumentId); - final File child = getFileForDocumentId(documentId); - return isInside(parent, child); - } catch (FileNotFoundException e) { - return false; - } - } - - @Override - public String createDocument(String parentDocumentId, String mimeType, String displayName) - throws FileNotFoundException - { - final File parent = getFileForDocumentId(parentDocumentId); - if (!parent.isDirectory()) { - throw new FileNotFoundException("Parent is not a directory: " + parentDocumentId); - } - - final String safeDisplayName = sanitizeDisplayName(displayName); - final File file = buildUniqueFile(parent, safeDisplayName); - final boolean created; - if (DIRECTORY_MIME_TYPE.equals(mimeType)) { - created = file.mkdir(); - } else { - try { - created = file.createNewFile(); - } catch (IOException e) { - throw asFileNotFound("Unable to create document", e); - } - } - - if (!created) { - throw new FileNotFoundException("Unable to create document: " + displayName); - } - - notifyChildrenChanged(parentDocumentId); - return getDocumentIdForFile(file); - } - - @Override - public String renameDocument(String documentId, String displayName) throws FileNotFoundException { - final File file = getFileForDocumentId(documentId); - if (ROOT_DOCUMENT_ID.equals(documentId)) { - throw new FileNotFoundException("Cannot rename root document"); - } - - final File target = buildUniqueFile(file.getParentFile(), sanitizeDisplayName(displayName)); - final String parentDocumentId = getDocumentIdForFile(file.getParentFile()); - if (!file.renameTo(target)) { - throw new FileNotFoundException("Unable to rename document: " + documentId); - } - notifyDocumentChanged(documentId); - notifyDocumentChanged(getDocumentIdForFile(target)); - notifyChildrenChanged(parentDocumentId); - return getDocumentIdForFile(target); - } - - @Override - public void deleteDocument(String documentId) throws FileNotFoundException { - if (ROOT_DOCUMENT_ID.equals(documentId)) { - throw new FileNotFoundException("Cannot delete root document"); - } - - final File file = getFileForDocumentId(documentId); - final String parentDocumentId = getDocumentIdForFile(file.getParentFile()); - deleteRecursively(file); - notifyDocumentChanged(documentId); - notifyChildrenChanged(parentDocumentId); - } - - @Override - public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) - throws FileNotFoundException - { - return ParcelFileDescriptor.open(getFileForDocumentId(documentId), modeToParcelMode(mode)); - } - - @Override - public AssetFileDescriptor openDocumentThumbnail(String documentId, android.graphics.Point sizeHint, - CancellationSignal signal) throws FileNotFoundException - { - throw new FileNotFoundException("Thumbnails are not supported"); - } - - private void includeDocument(MatrixCursor result, String documentId, File file) throws FileNotFoundException { - final MatrixCursor.RowBuilder row = result.newRow(); - final boolean isDirectory = file.isDirectory(); - final String displayName = ROOT_DOCUMENT_ID.equals(documentId) - ? getContext().getString(R.string.documents_provider_root_name) - : file.getName(); - - int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME; - if (isDirectory) { - flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - } else if (file.canWrite()) { - flags |= Document.FLAG_SUPPORTS_WRITE; - } - if (ROOT_DOCUMENT_ID.equals(documentId)) { - flags &= ~(Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME); - } - - row.add(Document.COLUMN_DOCUMENT_ID, documentId); - row.add(Document.COLUMN_DISPLAY_NAME, displayName); - row.add(Document.COLUMN_FLAGS, flags); - row.add(Document.COLUMN_MIME_TYPE, isDirectory ? DIRECTORY_MIME_TYPE : getMimeType(file)); - row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); - row.add(Document.COLUMN_SIZE, isDirectory ? null : file.length()); - } - - private File getRootDirectory() throws FileNotFoundException { - if (isCustomDataPathEnabled()) { - throw new FileNotFoundException( - "Dusk DocumentsProvider is disabled while a custom data path is configured"); - } - - final File root = getContext().getFilesDir(); - if (root == null) { - throw new FileNotFoundException("Dusklight files directory is unavailable"); - } - return root; - } - - private File getFileForDocumentId(String documentId) throws FileNotFoundException { - final File root = getRootDirectory(); - if (ROOT_DOCUMENT_ID.equals(documentId)) { - return root; - } - if (!documentId.startsWith(ROOT_DOCUMENT_ID + "/")) { - throw new FileNotFoundException("Invalid document id: " + documentId); - } - - final String relativePath = documentId.substring(ROOT_DOCUMENT_ID.length() + 1); - final File file = new File(root, relativePath); - if (!isInside(root, file)) { - throw new FileNotFoundException("Document escapes Dusklight files directory: " + documentId); - } - if (!file.exists()) { - throw new FileNotFoundException("Document does not exist: " + documentId); - } - return file; - } - - private String getDocumentIdForFile(File file) throws FileNotFoundException { - final File root = getRootDirectory(); - if (sameFile(root, file)) { - return ROOT_DOCUMENT_ID; - } - if (!isInside(root, file)) { - throw new FileNotFoundException("File escapes Dusklight files directory: " + file); - } - - final String rootPath = canonicalPath(root); - final String filePath = canonicalPath(file); - return ROOT_DOCUMENT_ID + "/" + filePath.substring(rootPath.length() + 1); - } - - private void ensureUserDirectories() { - final File root = getContext().getFilesDir(); - if (root == null) { - return; - } - new File(root, "texture_replacements").mkdirs(); - new File(root, "USA/Card A").mkdirs(); - new File(root, "EUR/Card A").mkdirs(); - } - - private boolean isCustomDataPathEnabled() { - if (getContext() == null) { - return false; - } - - final File filesDir = getContext().getFilesDir(); - if (filesDir == null) { - return false; - } - - final File descriptor = new File(filesDir, LOCATION_DESCRIPTOR_NAME); - if (!descriptor.isFile()) { - return false; - } - - try { - final JSONObject json = new JSONObject(readText(descriptor)); - return "custom".equals(json.optString("mode", "default")); - } catch (IOException | JSONException e) { - return false; - } - } - - private static String readText(File file) throws IOException { - try (FileInputStream input = new FileInputStream(file); - ByteArrayOutputStream output = new ByteArrayOutputStream()) - { - byte[] buffer = new byte[4096]; - int bytesRead; - while ((bytesRead = input.read(buffer)) != -1) { - output.write(buffer, 0, bytesRead); - } - return output.toString(StandardCharsets.UTF_8.name()); - } - } - - private static String[] resolveRootProjection(String[] projection) { - return projection != null ? projection : DEFAULT_ROOT_PROJECTION; - } - - private static String[] resolveDocumentProjection(String[] projection) { - return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; - } - - private static String sanitizeDisplayName(String displayName) throws FileNotFoundException { - if (displayName == null) { - throw new FileNotFoundException("Document name is empty"); - } - - final String sanitized = displayName.trim(); - if (sanitized.isEmpty() || ".".equals(sanitized) || "..".equals(sanitized) || - sanitized.contains("/") || sanitized.contains("\\")) - { - throw new FileNotFoundException("Invalid document name: " + displayName); - } - return sanitized; - } - - private static File buildUniqueFile(File parent, String displayName) { - File file = new File(parent, displayName); - if (!file.exists()) { - return file; - } - - final int dot = displayName.lastIndexOf('.'); - final String baseName = dot > 0 ? displayName.substring(0, dot) : displayName; - final String extension = dot > 0 ? displayName.substring(dot) : ""; - for (int i = 1; i < 100; ++i) { - file = new File(parent, baseName + " (" + i + ")" + extension); - if (!file.exists()) { - return file; - } - } - return new File(parent, baseName + " (" + System.currentTimeMillis() + ")" + extension); - } - - private static int modeToParcelMode(String mode) { - if ("r".equals(mode)) { - return ParcelFileDescriptor.MODE_READ_ONLY; - } - if ("w".equals(mode) || "wt".equals(mode)) { - return ParcelFileDescriptor.MODE_WRITE_ONLY | - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE; - } - if ("wa".equals(mode)) { - return ParcelFileDescriptor.MODE_WRITE_ONLY | - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_APPEND; - } - if ("rw".equals(mode)) { - return ParcelFileDescriptor.MODE_READ_WRITE | - ParcelFileDescriptor.MODE_CREATE; - } - if ("rwt".equals(mode)) { - return ParcelFileDescriptor.MODE_READ_WRITE | - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE; - } - return ParcelFileDescriptor.MODE_READ_ONLY; - } - - private static String getMimeType(File file) { - final int dot = file.getName().lastIndexOf('.'); - if (dot >= 0) { - final String extension = file.getName().substring(dot + 1).toLowerCase(); - final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mimeType != null) { - return mimeType; - } - } - return "application/octet-stream"; - } - - private Uri getChildDocumentsUri(String parentDocumentId) { - return DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId); - } - - private void notifyChildrenChanged(String parentDocumentId) { - final ContentResolver resolver = getContext().getContentResolver(); - resolver.notifyChange(getChildDocumentsUri(parentDocumentId), null, false); - } - - private void notifyDocumentChanged(String documentId) { - final ContentResolver resolver = getContext().getContentResolver(); - resolver.notifyChange(DocumentsContract.buildDocumentUri(AUTHORITY, documentId), null, false); - } - - private static void deleteRecursively(File file) throws FileNotFoundException { - if (file.isDirectory()) { - final File[] children = file.listFiles(); - if (children != null) { - for (File child : children) { - deleteRecursively(child); - } - } - } - if (!file.delete()) { - throw new FileNotFoundException("Unable to delete document: " + file); - } - } - - private static boolean isInside(File parent, File child) { - try { - final String parentPath = canonicalPath(parent); - final String childPath = canonicalPath(child); - return childPath.equals(parentPath) || childPath.startsWith(parentPath + File.separator); - } catch (FileNotFoundException e) { - return false; - } - } - - private static boolean sameFile(File a, File b) { - try { - return canonicalPath(a).equals(canonicalPath(b)); - } catch (FileNotFoundException e) { - return false; - } - } - - private static String canonicalPath(File file) throws FileNotFoundException { - try { - return file.getCanonicalPath(); - } catch (IOException e) { - throw asFileNotFound("Unable to resolve path", e); - } - } - - private static FileNotFoundException asFileNotFound(String message, IOException cause) { - final FileNotFoundException exception = new FileNotFoundException(message + ": " + cause.getMessage()); - exception.initCause(cause); - return exception; - } -} diff --git a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskHttpClient.java b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskHttpClient.java deleted file mode 100644 index be160d6a2d..0000000000 --- a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskHttpClient.java +++ /dev/null @@ -1,237 +0,0 @@ -package dev.twilitrealm.dusk; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.net.ssl.HttpsURLConnection; - -public final class DuskHttpClient { - public static final int ERROR_NONE = 0; - public static final int ERROR_INVALID_URL = 1; - public static final int ERROR_UNSUPPORTED_SCHEME = 2; - public static final int ERROR_TIMEOUT = 3; - public static final int ERROR_TOO_LARGE = 4; - public static final int ERROR_NETWORK = 5; - - private static final int MAX_REDIRECTS = 5; - - public static final class Response { - public int error; - public String message; - public int statusCode; - public String[] headerNames; - public String[] headerValues; - public byte[] body; - - Response(int error, String message, int statusCode, String[] headerNames, - String[] headerValues, byte[] body) { - this.error = error; - this.message = message; - this.statusCode = statusCode; - this.headerNames = headerNames != null ? headerNames : new String[0]; - this.headerValues = headerValues != null ? headerValues : new String[0]; - this.body = body != null ? body : new byte[0]; - } - } - - private DuskHttpClient() { - } - - public static Response get(String url, String[] headerNames, String[] headerValues, - int timeoutMs, long maxBodyBytes) { - if (url == null || url.isEmpty()) { - return fail(ERROR_INVALID_URL, "URL is empty"); - } - - try { - URL currentUrl = new URL(url); - if (!isHttps(currentUrl)) { - return fail(ERROR_UNSUPPORTED_SCHEME, "Only https:// URLs are supported"); - } - - for (int redirect = 0; redirect <= MAX_REDIRECTS; ++redirect) { - HttpsURLConnection connection = - (HttpsURLConnection) currentUrl.openConnection(); - try { - connection.setRequestMethod("GET"); - connection.setConnectTimeout(timeoutMs); - connection.setReadTimeout(timeoutMs); - connection.setUseCaches(false); - connection.setInstanceFollowRedirects(false); - applyHeaders(connection, headerNames, headerValues); - - int statusCode = connection.getResponseCode(); - if (isRedirect(statusCode)) { - String location = connection.getHeaderField("Location"); - if (location == null || location.isEmpty()) { - return fail(ERROR_NETWORK, "Redirect response did not include Location", - statusCode, connection, new byte[0]); - } - - URL nextUrl = new URL(currentUrl, location); - if (!isHttps(nextUrl)) { - return fail(ERROR_UNSUPPORTED_SCHEME, - "Only https:// redirects are supported", statusCode, - connection, new byte[0]); - } - currentUrl = nextUrl; - continue; - } - - byte[] body = readBody(connection, statusCode, maxBodyBytes); - return success(statusCode, connection, body); - } catch (ResponseTooLargeException e) { - return fail(ERROR_TOO_LARGE, "Response body exceeded the configured limit", - safeStatusCode(connection), connection, e.partialBody); - } finally { - connection.disconnect(); - } - } - - return fail(ERROR_NETWORK, "Too many redirects"); - } catch (MalformedURLException e) { - return fail(ERROR_INVALID_URL, "Failed to parse URL"); - } catch (SocketTimeoutException e) { - return fail(ERROR_TIMEOUT, "Request timed out"); - } catch (IOException e) { - String message = e.getMessage(); - return fail(ERROR_NETWORK, message != null ? message : e.toString()); - } catch (ClassCastException e) { - return fail(ERROR_UNSUPPORTED_SCHEME, "Only https:// URLs are supported"); - } - } - - private static void applyHeaders(HttpsURLConnection connection, String[] names, - String[] values) { - if (names == null || values == null) { - return; - } - - int count = Math.min(names.length, values.length); - for (int i = 0; i < count; ++i) { - if (names[i] != null && values[i] != null) { - connection.setRequestProperty(names[i], values[i]); - } - } - } - - private static boolean isHttps(URL url) { - return "https".equalsIgnoreCase(url.getProtocol()); - } - - private static boolean isRedirect(int statusCode) { - return statusCode == HttpURLConnection.HTTP_MOVED_PERM || - statusCode == HttpURLConnection.HTTP_MOVED_TEMP || - statusCode == HttpURLConnection.HTTP_SEE_OTHER || - statusCode == 307 || - statusCode == 308; - } - - private static byte[] readBody(HttpsURLConnection connection, int statusCode, - long maxBodyBytes) throws IOException, - ResponseTooLargeException { - InputStream stream = statusCode >= HttpURLConnection.HTTP_BAD_REQUEST ? - connection.getErrorStream() : connection.getInputStream(); - if (stream == null) { - return new byte[0]; - } - - try (InputStream bodyStream = stream; - ByteArrayOutputStream out = new ByteArrayOutputStream()) { - byte[] buffer = new byte[8192]; - long total = 0; - while (true) { - int read = bodyStream.read(buffer); - if (read < 0) { - return out.toByteArray(); - } - if (read == 0) { - continue; - } - if (read > maxBodyBytes || total > maxBodyBytes - read) { - throw new ResponseTooLargeException(out.toByteArray()); - } - out.write(buffer, 0, read); - total += read; - } - } - } - - private static int safeStatusCode(HttpsURLConnection connection) { - try { - return connection.getResponseCode(); - } catch (IOException e) { - return 0; - } - } - - private static Response success(int statusCode, HttpsURLConnection connection, byte[] body) { - HeaderLists headers = readHeaders(connection); - return new Response(ERROR_NONE, "", statusCode, headers.names, headers.values, body); - } - - private static Response fail(int error, String message) { - return new Response(error, message, 0, null, null, null); - } - - private static Response fail(int error, String message, int statusCode, - HttpsURLConnection connection, byte[] body) { - HeaderLists headers = readHeaders(connection); - return new Response(error, message, statusCode, headers.names, headers.values, body); - } - - private static HeaderLists readHeaders(HttpsURLConnection connection) { - List names = new ArrayList<>(); - List values = new ArrayList<>(); - - Map> headerFields = connection.getHeaderFields(); - if (headerFields == null) { - return new HeaderLists(new String[0], new String[0]); - } - - for (Map.Entry> entry : headerFields.entrySet()) { - String name = entry.getKey(); - if (name == null) { - continue; - } - List entryValues = entry.getValue(); - if (entryValues == null || entryValues.isEmpty()) { - names.add(name); - values.add(""); - continue; - } - for (String value : entryValues) { - names.add(name); - values.add(value != null ? value : ""); - } - } - - return new HeaderLists(names.toArray(new String[0]), values.toArray(new String[0])); - } - - private static final class HeaderLists { - final String[] names; - final String[] values; - - HeaderLists(String[] names, String[] values) { - this.names = names; - this.values = values; - } - } - - private static final class ResponseTooLargeException extends Exception { - final byte[] partialBody; - - ResponseTooLargeException(byte[] partialBody) { - this.partialBody = partialBody; - } - } -} diff --git a/platforms/android/app/src/main/java/org/libsdl/app/HIDDevice.java b/platforms/android/app/src/main/java/org/libsdl/app/HIDDevice.java deleted file mode 100644 index f96095324b..0000000000 --- a/platforms/android/app/src/main/java/org/libsdl/app/HIDDevice.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.libsdl.app; - -import android.hardware.usb.UsbDevice; - -interface HIDDevice -{ - public int getId(); - public int getVendorId(); - public int getProductId(); - public String getSerialNumber(); - public int getVersion(); - public String getManufacturerName(); - public String getProductName(); - public UsbDevice getDevice(); - public boolean open(); - public int writeReport(byte[] report, boolean feature); - public boolean readReport(byte[] report, boolean feature); - public void setFrozen(boolean frozen); - public void close(); - public void shutdown(); -} diff --git a/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java deleted file mode 100644 index bcd8806c41..0000000000 --- a/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java +++ /dev/null @@ -1,829 +0,0 @@ -package org.libsdl.app; - -import android.content.Context; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.BluetoothManager; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothGattService; -import android.hardware.usb.UsbDevice; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.os.*; - -//import com.android.internal.util.HexDump; - -import java.lang.Runnable; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.UUID; - -import java.util.regex.Pattern; -import java.util.regex.Matcher; - -class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { - - private static final String TAG = "hidapi"; - private HIDDeviceManager mManager; - private BluetoothDevice mDevice; - private int mDeviceId; - private BluetoothGatt mGatt; - private boolean mIsRegistered = false; - private boolean mIsConnected = false; - private boolean mIsChromebook = false; - private boolean mIsReconnecting = false; - private boolean mHasEnabledNotifications = false; - private boolean mHasSeenInputUpdate = false; - private boolean mFrozen = false; - private LinkedList mOperations; - GattOperation mCurrentOperation = null; - private Handler mHandler; - private int mProductId = -1; - private int mReportId = 0; - private UUID mInputCharacteristic; - - private static final int D0G_BLE2_PID = 0x1106; - private static final int TRITON_BLE_PID = 0x1303; - - - private static final int TRANSPORT_AUTO = 0; - private static final int TRANSPORT_BREDR = 1; - private static final int TRANSPORT_LE = 2; - - private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; - - static final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); - static final UUID inputCharacteristicD0G = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); - static final UUID inputCharacteristicTriton_0x45 = UUID.fromString("100F6C7A-1735-4313-B402-38567131E5F3"); - static final UUID inputCharacteristicTriton_0x47 = UUID.fromString("100F6C7C-1735-4313-B402-38567131E5F3"); - static final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); - static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; - - private HashMap mOutputReportChars = new HashMap(); - - static class GattOperation { - private enum Operation { - CHR_READ, - CHR_WRITE, - ENABLE_NOTIFICATION - } - - Operation mOp; - UUID mUuid; - byte[] mValue; - BluetoothGatt mGatt; - boolean mResult = true; - int mDelayMs = 0; - - private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) { - mGatt = gatt; - mOp = operation; - mUuid = uuid; - } - - private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, int delayMs) { - mGatt = gatt; - mOp = operation; - mUuid = uuid; - mDelayMs = delayMs; - } - - private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) { - mGatt = gatt; - mOp = operation; - mUuid = uuid; - mValue = value; - } - - private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value, int delayMs) { - mGatt = gatt; - mOp = operation; - mUuid = uuid; - mValue = value; - mDelayMs = delayMs; - } - - public void run() { - // This is executed in main thread - BluetoothGattCharacteristic chr; - - switch (mOp) { - case CHR_READ: - chr = getCharacteristic(mUuid); - //Log.v(TAG, "Reading characteristic " + chr.getUuid()); - if (!mGatt.readCharacteristic(chr)) { - Log.e(TAG, "Unable to read characteristic " + mUuid.toString()); - mResult = false; - break; - } - mResult = true; - break; - case CHR_WRITE: - chr = getCharacteristic(mUuid); - //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value)); - chr.setValue(mValue); - if (!mGatt.writeCharacteristic(chr)) { - Log.e(TAG, "Unable to write characteristic " + mUuid.toString()); - mResult = false; - break; - } - mResult = true; - break; - case ENABLE_NOTIFICATION: - chr = getCharacteristic(mUuid); - //Log.v(TAG, "Writing descriptor of " + chr.getUuid()); - if (chr != null) { - BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); - if (cccd != null) { - int properties = chr.getProperties(); - byte[] value; - if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { - value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; - } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { - value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; - } else { - Log.e(TAG, "Unable to start notifications on input characteristic"); - mResult = false; - return; - } - - mGatt.setCharacteristicNotification(chr, true); - cccd.setValue(value); - if (!mGatt.writeDescriptor(cccd)) { - Log.e(TAG, "Unable to write descriptor " + mUuid.toString()); - mResult = false; - return; - } - mResult = true; - } - } - } - } - - public boolean finish() { - return mResult; - } - - public int getDelayMs() { return mDelayMs; } - - private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { - BluetoothGattService valveService = mGatt.getService(steamControllerService); - if (valveService == null) - return null; - return valveService.getCharacteristic(uuid); - } - - static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) { - return new GattOperation(gatt, Operation.CHR_READ, uuid); - } - - static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) { - return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value); - } - - static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) { - return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid); - } - - static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid, int delayMs) { - return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid, delayMs); - } - } - - HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { - mManager = manager; - mDevice = device; - mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); - mIsRegistered = false; - mIsChromebook = SDLActivity.isChromebook(); - mOperations = new LinkedList(); - mHandler = new Handler(Looper.getMainLooper()); - - mGatt = connectGatt(); - mHasEnabledNotifications = false; - mHasSeenInputUpdate = false; - // final HIDDeviceBLESteamController finalThis = this; - // mHandler.postDelayed(new Runnable() { - // @Override - // void run() { - // finalThis.checkConnectionForChromebookIssue(); - // } - // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); - } - - String getIdentifier() { - return String.format("SteamController.%s", mDevice.getAddress()); - } - - BluetoothGatt getGatt() { - return mGatt; - } - - // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead - // of TRANSPORT_LE. Let's force ourselves to connect low energy. - private BluetoothGatt connectGatt(boolean managed) { - if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) { - try { - return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); - } catch (Exception e) { - return mDevice.connectGatt(mManager.getContext(), managed, this); - } - } else { - return mDevice.connectGatt(mManager.getContext(), managed, this); - } - } - - private BluetoothGatt connectGatt() { - return connectGatt(false); - } - - protected int getConnectionState() { - - Context context = mManager.getContext(); - if (context == null) { - // We are lacking any context to get our Bluetooth information. We'll just assume disconnected. - return BluetoothProfile.STATE_DISCONNECTED; - } - - BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); - if (btManager == null) { - // This device doesn't support Bluetooth. We should never be here, because how did - // we instantiate a device to start with? - return BluetoothProfile.STATE_DISCONNECTED; - } - - return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); - } - - void reconnect() { - - if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { - mGatt.disconnect(); - mGatt = connectGatt(); - } - - } - - protected void checkConnectionForChromebookIssue() { - if (!mIsChromebook) { - // We only do this on Chromebooks, because otherwise it's really annoying to just attempt - // over and over. - return; - } - - int connectionState = getConnectionState(); - - switch (connectionState) { - case BluetoothProfile.STATE_CONNECTED: - if (!mIsConnected) { - // We are in the Bad Chromebook Place. We can force a disconnect - // to try to recover. - Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect."); - mIsReconnecting = true; - mGatt.disconnect(); - mGatt = connectGatt(false); - break; - } - else if (!isRegistered()) { - if (mGatt.getServices().size() > 0) { - Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover."); - probeService(this); - } - else { - Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover."); - mIsReconnecting = true; - mGatt.disconnect(); - mGatt = connectGatt(false); - break; - } - } - else { - Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!"); - return; - } - break; - - case BluetoothProfile.STATE_DISCONNECTED: - Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover."); - - mIsReconnecting = true; - mGatt.disconnect(); - mGatt = connectGatt(false); - break; - - case BluetoothProfile.STATE_CONNECTING: - Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer."); - break; - } - - final HIDDeviceBLESteamController finalThis = this; - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - finalThis.checkConnectionForChromebookIssue(); - } - }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); - } - - private boolean isRegistered() { - return mIsRegistered; - } - - private void setRegistered() { - mIsRegistered = true; - } - - private boolean probeService(HIDDeviceBLESteamController controller) { - - if (isRegistered()) { - return true; - } - - if (!mIsConnected) { - return false; - } - - Log.v(TAG, "probeService controller=" + controller); - - for (BluetoothGattService service : mGatt.getServices()) { - if (service.getUuid().equals(steamControllerService)) { - Log.v(TAG, "Found Valve steam controller service " + service.getUuid()); - - for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { - if (chr.getUuid().equals(inputCharacteristicTriton_0x45)) { - Log.v(TAG, "Found Triton input characteristic 0x45"); - mProductId = TRITON_BLE_PID; - mReportId = 0x45; - mInputCharacteristic = chr.getUuid(); - } else if (chr.getUuid().equals(inputCharacteristicTriton_0x47)) { - Log.v(TAG, "Found Triton input characteristic 0x47"); - mProductId = TRITON_BLE_PID; - mReportId = 0x47; - mInputCharacteristic = chr.getUuid(); - } else if (chr.getUuid().equals(inputCharacteristicD0G)) { - Log.v(TAG, "Found D0G input characteristic"); - mProductId = D0G_BLE2_PID; - mReportId = 0x03; - mInputCharacteristic = chr.getUuid(); - } else { - Pattern reportPattern = Pattern.compile("100F6C([0-9A-Z]{2})", Pattern.CASE_INSENSITIVE); - Matcher matcher = reportPattern.matcher(chr.getUuid().toString()); - - if (matcher.find()) { - try { - int reportId = Integer.parseInt(matcher.group(1), 16); - - reportId -= 0x35; - if (reportId >= 0x80) { - // This is a Triton output report characteristic that we need to care about. - Log.v(TAG, "Found Triton output report 0x" + Integer.toString(reportId, 16)); - mOutputReportChars.put(reportId, chr); - } - } - catch (NumberFormatException nfe) { - Log.w(TAG, "Could not parse report characteristic " + chr.getUuid().toString() + ": " + nfe.toString()); - } - } - } - } - - for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { - if (chr.getUuid().equals(mInputCharacteristic)) { - // Start notifications - BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); - if (cccd != null) { - enableNotification(chr.getUuid()); - } - } - } - return true; - } - } - - if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) { - Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us."); - mIsConnected = false; - mIsReconnecting = true; - mGatt.disconnect(); - mGatt = connectGatt(false); - } - - return false; - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - private void finishCurrentGattOperation() { - GattOperation op = null; - synchronized (mOperations) { - if (mCurrentOperation != null) { - op = mCurrentOperation; - mCurrentOperation = null; - } - } - if (op != null) { - boolean result = op.finish(); // TODO: Maybe in main thread as well? - - // Our operation failed, let's add it back to the beginning of our queue. - if (!result) { - mOperations.addFirst(op); - } - } - executeNextGattOperation(); - } - - private void executeNextGattOperation() { - synchronized (mOperations) { - if (mCurrentOperation != null) - return; - - if (mOperations.isEmpty()) - return; - - mCurrentOperation = mOperations.removeFirst(); - } - - Runnable gattOperationRunnable = new Runnable() { - @Override - public void run() { - synchronized (mOperations) { - if (mCurrentOperation == null) { - Log.e(TAG, "Current operation null in executor?"); - return; - } - - mCurrentOperation.run(); - // now wait for the GATT callback and when it comes, finish this operation - } - } - }; - - if (mCurrentOperation.getDelayMs() == 0) { - // Run in main thread - mHandler.post(gattOperationRunnable); - } - else { - // If we have a delay on this operation, wait before we post it. - mHandler.postDelayed(gattOperationRunnable, mCurrentOperation.getDelayMs()); - } - - } - - private void queueGattOperation(GattOperation op) { - synchronized (mOperations) { - mOperations.add(op); - } - executeNextGattOperation(); - } - - private void enableNotification(UUID chrUuid) { - // Add a 500ms delay to notification write for Amazon Fire TV devices, as otherwise if we do this too quickly after connecting - // it will return success and then silently drop the operation on the floor. - GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid, 500); - queueGattOperation(op); - - // Amazon Fire devices can also silently timeout on writeDescriptor, so - // set up a little delayed check that will attempt to write a second time. - // - // While this only seems to be needed on Amazon Fire TV devices at present, it - // doesn't hurt to have a retry on other devices as well. - // - final HIDDeviceBLESteamController finalThis = this; - final UUID finalUuid = chrUuid; - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (!finalThis.mHasEnabledNotifications) { - - if (finalThis.mHasSeenInputUpdate) { - // Amazon Five devices may have enabled notifications on the input characteristic and not given us a callback. If we've seen - // input reports, though, somewhat by definition notifications are enabled. - Log.w(TAG, "WriteDescriptor has never returned, but we've seen input reports. Moving on with controller initialization."); - finalThis.mHasEnabledNotifications = true; - finalThis.enableValveMode(); - return; - } - - // Give one more try. - GattOperation retry = HIDDeviceBLESteamController.GattOperation.enableNotification(finalThis.mGatt, finalUuid, 500); - finalThis.queueGattOperation(retry); - } - } - }, 1000); - } - - void writeCharacteristic(UUID uuid, byte[] value) { - GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); - queueGattOperation(op); - } - - void readCharacteristic(UUID uuid) { - GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); - queueGattOperation(op); - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////// BluetoothGattCallback overridden methods - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { - //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); - mIsReconnecting = false; - if (newState == 2) { - mIsConnected = true; - // Run directly, without GattOperation - if (!isRegistered()) { - mHandler.post(new Runnable() { - @Override - public void run() { - mGatt.discoverServices(); - } - }); - } - } - else if (newState == 0) { - mIsConnected = false; - } - - // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. - } - - @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - //Log.v(TAG, "onServicesDiscovered status=" + status); - if (status == 0) { - if (gatt.getServices().size() == 0) { - Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack."); - mIsReconnecting = true; - mIsConnected = false; - gatt.disconnect(); - mGatt = connectGatt(false); - } else { - if (getProductId() == TRITON_BLE_PID) { - // Android will not properly play well with Data Length Extensions without manually requesting a large MTU, - // and Triton controllers require DLE support. - // - // 517 is basically a "magic number" as far as Android's bluetooth code is concerned, so do not change - // this value. It is functionally "please enable data length extensions" on some Android builds. - mGatt.requestMtu(517); - } - - probeService(this); - } - } - } - - @Override - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); - - if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) { - mManager.HIDDeviceReportResponse(getId(), characteristic.getValue()); - } - - finishCurrentGattOperation(); - } - - @Override - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); - - if (characteristic.getUuid().equals(reportCharacteristic)) { - // Only register controller with the native side once it has been fully configured - if (!isRegistered()) { - Log.v(TAG, "Registering Steam Controller with ID: " + getId()); - mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true, mReportId); - setRegistered(); - } - } - - finishCurrentGattOperation(); - } - - @Override - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - // Enable this for verbose logging of controller input reports - //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); - - if (characteristic.getUuid().equals(mInputCharacteristic) && !mFrozen) { - mHasSeenInputUpdate = true; - mManager.HIDDeviceInputReport(getId(), characteristic.getValue()); - } - } - - @Override - public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - //Log.v(TAG, "onDescriptorRead status=" + status); - } - - private void enableValveMode() - { - BluetoothGattService valveService = mGatt.getService(steamControllerService); - if (valveService == null) - return; - - BluetoothGattCharacteristic reportChr = valveService.getCharacteristic(reportCharacteristic); - if (reportChr != null) { - if (getProductId() == TRITON_BLE_PID) { - // For Triton we just mark things registered. - Log.v(TAG, "Registering Triton Steam Controller with ID: " + getId()); - mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true, mReportId); - setRegistered(); - } else { - // For the original controller, we need to manually enter Valve mode. - Log.v(TAG, "Writing report characteristic to enter valve mode"); - reportChr.setValue(enterValveMode); - mGatt.writeCharacteristic(reportChr); - } - } - } - - @Override - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); - //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); - - if (chr.getUuid().equals(mInputCharacteristic)) { - mHasEnabledNotifications = true; - enableValveMode(); - } - - finishCurrentGattOperation(); - } - - @Override - public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { - //Log.v(TAG, "onReliableWriteCompleted status=" + status); - } - - @Override - public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { - //Log.v(TAG, "onReadRemoteRssi status=" + status); - } - - @Override - public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { - //Log.v(TAG, "onMtuChanged status=" + status); - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - //////// Public API - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public int getId() { - return mDeviceId; - } - - @Override - public int getVendorId() { - // Valve Corporation - final int VALVE_USB_VID = 0x28DE; - return VALVE_USB_VID; - } - - @Override - public int getProductId() { - if (mProductId > 0) { - // We've already set a product ID. - return mProductId; - } - - if (mDevice.getName().startsWith("Steam Ctrl")) { - // We're a newer Triton device - mProductId = TRITON_BLE_PID; - } else { - // We're an OG Steam Controller - mProductId = D0G_BLE2_PID; - } - - return mProductId; - } - - @Override - public String getSerialNumber() { - // This will be read later via feature report by Steam - return "12345"; - } - - @Override - public int getVersion() { - return 0; - } - - @Override - public String getManufacturerName() { - return "Valve Corporation"; - } - - @Override - public String getProductName() { - return "Steam Controller"; - } - - @Override - public UsbDevice getDevice() { - return null; - } - - @Override - public boolean open() { - return true; - } - - @Override - public int writeReport(byte[] report, boolean feature) { - if (!isRegistered()) { - Log.e(TAG, "Attempted writeReport before Steam Controller is registered!"); - if (mIsConnected) { - probeService(this); - } - return -1; - } - - if (feature) { - // We need to skip the first byte, as that doesn't go over the air - byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); - //Log.v(TAG, "writeFeatureReport " + HexDump.dumpHexString(actual_report)); - writeCharacteristic(reportCharacteristic, actual_report); - return report.length; - } else { - // If we're an original-recipe Steam Controller we just write to the characteristic directly. - if (getProductId() == D0G_BLE2_PID) { - //Log.v(TAG, "writeOutputReport " + HexDump.dumpHexString(report)); - writeCharacteristic(reportCharacteristic, report); - return report.length; - } - - // If we're a Triton, we need to find the correct report characteristic. - if (report.length > 0) { - int reportId = report[0] & 0xFF; - BluetoothGattCharacteristic targetedReportCharacteristic = mOutputReportChars.get(reportId); - if (targetedReportCharacteristic != null) { - byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); - //Log.v(TAG, "writeOutputReport 0x" + Integer.toString(reportId, 16) + " " + HexDump.dumpHexString(report)); - writeCharacteristic(targetedReportCharacteristic.getUuid(), actual_report); - return report.length; - } else { - Log.w(TAG, "Got report write request for unknown report type 0x" + Integer.toString(reportId, 16)); - } - } - } - - return -1; - } - - @Override - public boolean readReport(byte[] report, boolean feature) { - if (!isRegistered()) { - Log.e(TAG, "Attempted readReport before Steam Controller is registered!"); - if (mIsConnected) { - probeService(this); - } - return false; - } - - if (feature) { - readCharacteristic(reportCharacteristic); - return true; - } else { - // Not implemented - return false; - } - } - - @Override - public void close() { - } - - @Override - public void setFrozen(boolean frozen) { - mFrozen = frozen; - } - - @Override - public void shutdown() { - close(); - - BluetoothGatt g = mGatt; - if (g != null) { - g.disconnect(); - g.close(); - mGatt = null; - } - mManager = null; - mIsRegistered = false; - mIsConnected = false; - mOperations.clear(); - } - -} - diff --git a/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java deleted file mode 100644 index 691416c1c9..0000000000 --- a/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ /dev/null @@ -1,698 +0,0 @@ -package org.libsdl.app; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.PendingIntent; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothManager; -import android.bluetooth.BluetoothProfile; -import android.os.Build; -import android.util.Log; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.hardware.usb.*; -import android.os.Handler; -import android.os.Looper; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - -public class HIDDeviceManager { - private static final String TAG = "hidapi"; - private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION"; - - private static HIDDeviceManager sManager; - private static int sManagerRefCount = 0; - - static public HIDDeviceManager acquire(Context context) { - if (sManagerRefCount == 0) { - sManager = new HIDDeviceManager(context); - } - ++sManagerRefCount; - return sManager; - } - - static public void release(HIDDeviceManager manager) { - if (manager == sManager) { - --sManagerRefCount; - if (sManagerRefCount == 0) { - sManager.close(); - sManager = null; - } - } - } - - private Context mContext; - private HashMap mDevicesById = new HashMap(); - private HashMap mBluetoothDevices = new HashMap(); - private int mNextDeviceId = 0; - private SharedPreferences mSharedPreferences = null; - private boolean mIsChromebook = false; - private UsbManager mUsbManager; - private Handler mHandler; - private BluetoothManager mBluetoothManager; - private List mLastBluetoothDevices; - - private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - handleUsbDeviceAttached(usbDevice); - } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - handleUsbDeviceDetached(usbDevice); - } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)); - } - } - }; - - private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - // Bluetooth device was connected. If it was a Steam Controller, handle it - if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - Log.d(TAG, "Bluetooth device connected: " + device); - - if (isSteamController(device)) { - connectBluetoothDevice(device); - } - } - - // Bluetooth device was disconnected, remove from controller manager (if any) - if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - Log.d(TAG, "Bluetooth device disconnected: " + device); - - disconnectBluetoothDevice(device); - } - } - }; - - private HIDDeviceManager(final Context context) { - mContext = context; - - HIDDeviceRegisterCallback(); - - mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); - mIsChromebook = SDLActivity.isChromebook(); - -// if (shouldClear) { -// SharedPreferences.Editor spedit = mSharedPreferences.edit(); -// spedit.clear(); -// spedit.apply(); -// } -// else - { - mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); - } - } - - Context getContext() { - return mContext; - } - - int getDeviceIDForIdentifier(String identifier) { - SharedPreferences.Editor spedit = mSharedPreferences.edit(); - - int result = mSharedPreferences.getInt(identifier, 0); - if (result == 0) { - result = mNextDeviceId++; - spedit.putInt("next_device_id", mNextDeviceId); - } - - spedit.putInt(identifier, result); - spedit.apply(); - return result; - } - - private void initializeUSB() { - mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); - if (mUsbManager == null) { - return; - } - - /* - // Logging - for (UsbDevice device : mUsbManager.getDeviceList().values()) { - Log.i(TAG,"Path: " + device.getDeviceName()); - Log.i(TAG,"Manufacturer: " + device.getManufacturerName()); - Log.i(TAG,"Product: " + device.getProductName()); - Log.i(TAG,"ID: " + device.getDeviceId()); - Log.i(TAG,"Class: " + device.getDeviceClass()); - Log.i(TAG,"Protocol: " + device.getDeviceProtocol()); - Log.i(TAG,"Vendor ID " + device.getVendorId()); - Log.i(TAG,"Product ID: " + device.getProductId()); - Log.i(TAG,"Interface count: " + device.getInterfaceCount()); - Log.i(TAG,"---------------------------------------"); - - // Get interface details - for (int index = 0; index < device.getInterfaceCount(); index++) { - UsbInterface mUsbInterface = device.getInterface(index); - Log.i(TAG," ***** *****"); - Log.i(TAG," Interface index: " + index); - Log.i(TAG," Interface ID: " + mUsbInterface.getId()); - Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass()); - Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass()); - Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); - Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); - - // Get endpoint details - for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) - { - UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); - Log.i(TAG," ++++ ++++ ++++"); - Log.i(TAG," Endpoint index: " + epi); - Log.i(TAG," Attributes: " + mEndpoint.getAttributes()); - Log.i(TAG," Direction: " + mEndpoint.getDirection()); - Log.i(TAG," Number: " + mEndpoint.getEndpointNumber()); - Log.i(TAG," Interval: " + mEndpoint.getInterval()); - Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize()); - Log.i(TAG," Type: " + mEndpoint.getType()); - } - } - } - Log.i(TAG," No more devices connected."); - */ - - // Register for USB broadcasts and permission completions - IntentFilter filter = new IntentFilter(); - filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); - filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); - filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION); - if (Build.VERSION.SDK_INT >= 33) { /* Android 13.0 (TIRAMISU) */ - mContext.registerReceiver(mUsbBroadcast, filter, Context.RECEIVER_EXPORTED); - } else { - mContext.registerReceiver(mUsbBroadcast, filter); - } - - for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { - handleUsbDeviceAttached(usbDevice); - } - } - - UsbManager getUSBManager() { - return mUsbManager; - } - - private void shutdownUSB() { - try { - mContext.unregisterReceiver(mUsbBroadcast); - } catch (Exception e) { - // We may not have registered, that's okay - } - } - - private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) { - if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { - return true; - } - if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) { - return true; - } - return false; - } - - private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) { - final int XB360_IFACE_SUBCLASS = 93; - final int XB360_IFACE_PROTOCOL = 1; // Wired - final int XB360W_IFACE_PROTOCOL = 129; // Wireless - final int[] SUPPORTED_VENDORS = { - 0x0079, // GPD Win 2 - 0x044f, // Thrustmaster - 0x045e, // Microsoft - 0x046d, // Logitech - 0x056e, // Elecom - 0x06a3, // Saitek - 0x0738, // Mad Catz - 0x07ff, // Mad Catz - 0x0e6f, // PDP - 0x0f0d, // Hori - 0x1038, // SteelSeries - 0x11c9, // Nacon - 0x12ab, // Unknown - 0x1430, // RedOctane - 0x146b, // BigBen - 0x1532, // Razer Sabertooth - 0x15e4, // Numark - 0x162e, // Joytech - 0x1689, // Razer Onza - 0x1949, // Lab126, Inc. - 0x1bad, // Harmonix - 0x20d6, // PowerA - 0x24c6, // PowerA - 0x2c22, // Qanba - 0x2dc8, // 8BitDo - 0x3537, // GameSir - 0x37d7, // Flydigi - 0x9886, // ASTRO Gaming - }; - - if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && - usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS && - (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL || - usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) { - int vendor_id = usbDevice.getVendorId(); - for (int supportedVid : SUPPORTED_VENDORS) { - if (vendor_id == supportedVid) { - return true; - } - } - } - return false; - } - - private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { - final int XB1_IFACE_SUBCLASS = 71; - final int XB1_IFACE_PROTOCOL = 208; - final int[] SUPPORTED_VENDORS = { - 0x03f0, // HP - 0x044f, // Thrustmaster - 0x045e, // Microsoft - 0x0738, // Mad Catz - 0x0b05, // ASUS - 0x0e6f, // PDP - 0x0f0d, // Hori - 0x10f5, // Turtle Beach - 0x1532, // Razer Wildcat - 0x20d6, // PowerA - 0x24c6, // PowerA - 0x294b, // Snakebyte - 0x2dc8, // 8BitDo - 0x2e24, // Hyperkin - 0x2e95, // SCUF - 0x3285, // Nacon - 0x3537, // GameSir - 0x366c, // ByoWave - }; - - if (usbInterface.getId() == 0 && - usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && - usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS && - usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) { - int vendor_id = usbDevice.getVendorId(); - for (int supportedVid : SUPPORTED_VENDORS) { - if (vendor_id == supportedVid) { - return true; - } - } - } - return false; - } - - private void handleUsbDeviceAttached(UsbDevice usbDevice) { - connectHIDDeviceUSB(usbDevice); - } - - private void handleUsbDeviceDetached(UsbDevice usbDevice) { - List devices = new ArrayList(); - for (HIDDevice device : mDevicesById.values()) { - if (usbDevice.equals(device.getDevice())) { - devices.add(device.getId()); - } - } - for (int id : devices) { - HIDDevice device = mDevicesById.get(id); - mDevicesById.remove(id); - device.shutdown(); - HIDDeviceDisconnected(id); - } - } - - private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) { - for (HIDDevice device : mDevicesById.values()) { - if (usbDevice.equals(device.getDevice())) { - boolean opened = false; - if (permission_granted) { - opened = device.open(); - } - HIDDeviceOpenResult(device.getId(), opened); - } - } - } - - private void connectHIDDeviceUSB(UsbDevice usbDevice) { - synchronized (this) { - int interface_mask = 0; - for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { - UsbInterface usbInterface = usbDevice.getInterface(interface_index); - if (isHIDDeviceInterface(usbDevice, usbInterface)) { - // Check to see if we've already added this interface - // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive - int interface_id = usbInterface.getId(); - if ((interface_mask & (1 << interface_id)) != 0) { - continue; - } - interface_mask |= (1 << interface_id); - - HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); - int id = device.getId(); - mDevicesById.put(id, device); - HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol(), false, 0); - } - } - } - } - - private void initializeBluetooth() { - Log.d(TAG, "Initializing Bluetooth"); - - if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ && - mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT"); - return; - } - - if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ && - mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); - return; - } - - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { - Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); - return; - } - - // Find bonded bluetooth controllers and create SteamControllers for them - mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); - if (mBluetoothManager == null) { - // This device doesn't support Bluetooth. - return; - } - - BluetoothAdapter btAdapter = mBluetoothManager.getAdapter(); - if (btAdapter == null) { - // This device has Bluetooth support in the codebase, but has no available adapters. - return; - } - - // Get our bonded devices. - for (BluetoothDevice device : btAdapter.getBondedDevices()) { - - Log.d(TAG, "Bluetooth device available: " + device); - if (isSteamController(device)) { - connectBluetoothDevice(device); - } - - } - - // NOTE: These don't work on Chromebooks, to my undying dismay. - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); - filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); - if (Build.VERSION.SDK_INT >= 33) { /* Android 13.0 (TIRAMISU) */ - mContext.registerReceiver(mBluetoothBroadcast, filter, Context.RECEIVER_EXPORTED); - } else { - mContext.registerReceiver(mBluetoothBroadcast, filter); - } - - if (mIsChromebook) { - mHandler = new Handler(Looper.getMainLooper()); - mLastBluetoothDevices = new ArrayList(); - - // final HIDDeviceManager finalThis = this; - // mHandler.postDelayed(new Runnable() { - // @Override - // public void run() { - // finalThis.chromebookConnectionHandler(); - // } - // }, 5000); - } - } - - private void shutdownBluetooth() { - try { - mContext.unregisterReceiver(mBluetoothBroadcast); - } catch (Exception e) { - // We may not have registered, that's okay - } - } - - // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. - // This function provides a sort of dummy version of that, watching for changes in the - // connected devices and attempting to add controllers as things change. - void chromebookConnectionHandler() { - if (!mIsChromebook) { - return; - } - - ArrayList disconnected = new ArrayList(); - ArrayList connected = new ArrayList(); - - List currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); - - for (BluetoothDevice bluetoothDevice : currentConnected) { - if (!mLastBluetoothDevices.contains(bluetoothDevice)) { - connected.add(bluetoothDevice); - } - } - for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) { - if (!currentConnected.contains(bluetoothDevice)) { - disconnected.add(bluetoothDevice); - } - } - - mLastBluetoothDevices = currentConnected; - - for (BluetoothDevice bluetoothDevice : disconnected) { - disconnectBluetoothDevice(bluetoothDevice); - } - for (BluetoothDevice bluetoothDevice : connected) { - connectBluetoothDevice(bluetoothDevice); - } - - final HIDDeviceManager finalThis = this; - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - finalThis.chromebookConnectionHandler(); - } - }, 10000); - } - - boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { - Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); - synchronized (this) { - if (mBluetoothDevices.containsKey(bluetoothDevice)) { - Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect"); - - HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); - device.reconnect(); - - return false; - } - HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice); - int id = device.getId(); - mBluetoothDevices.put(bluetoothDevice, device); - mDevicesById.put(id, device); - - // The Steam Controller will mark itself connected once initialization is complete - } - return true; - } - - void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { - synchronized (this) { - HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); - if (device == null) - return; - - int id = device.getId(); - mBluetoothDevices.remove(bluetoothDevice); - mDevicesById.remove(id); - device.shutdown(); - HIDDeviceDisconnected(id); - } - } - - boolean isSteamController(BluetoothDevice bluetoothDevice) { - // Sanity check. If you pass in a null device, by definition it is never a Steam Controller. - if (bluetoothDevice == null) { - return false; - } - - // If the device has no local name, we really don't want to try an equality check against it. - if (bluetoothDevice.getName() == null) { - return false; - } - - // Steam Controllers will always support Bluetooth Low Energy - if ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) == 0) { - return false; - } - - // Match on the name either the original Steam Controller or the new second-generation one advertise with. - return bluetoothDevice.getName().equals("SteamController") || bluetoothDevice.getName().startsWith("Steam Ctrl"); - } - - private void close() { - shutdownUSB(); - shutdownBluetooth(); - synchronized (this) { - for (HIDDevice device : mDevicesById.values()) { - device.shutdown(); - } - mDevicesById.clear(); - mBluetoothDevices.clear(); - HIDDeviceReleaseCallback(); - } - } - - public void setFrozen(boolean frozen) { - synchronized (this) { - for (HIDDevice device : mDevicesById.values()) { - device.setFrozen(frozen); - } - } - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - private HIDDevice getDevice(int id) { - synchronized (this) { - HIDDevice result = mDevicesById.get(id); - if (result == null) { - Log.v(TAG, "No device for id: " + id); - Log.v(TAG, "Available devices: " + mDevicesById.keySet()); - } - return result; - } - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////// JNI interface functions - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - boolean initialize(boolean usb, boolean bluetooth) { - Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")"); - - if (usb) { - initializeUSB(); - } - if (bluetooth) { - initializeBluetooth(); - } - return true; - } - - boolean openDevice(int deviceID) { - Log.v(TAG, "openDevice deviceID=" + deviceID); - HIDDevice device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return false; - } - - // Look to see if this is a USB device and we have permission to access it - UsbDevice usbDevice = device.getDevice(); - if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { - HIDDeviceOpenPending(deviceID); - try { - final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31 - int flags; - if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) { - flags = FLAG_MUTABLE; - } else { - flags = 0; - } - - Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION); - intent.setPackage(mContext.getPackageName()); - mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags)); - } catch (Exception e) { - Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); - HIDDeviceOpenResult(deviceID, false); - } - return false; - } - - try { - return device.open(); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - return false; - } - - int writeReport(int deviceID, byte[] report, boolean feature) { - try { - //Log.v(TAG, "writeReport deviceID=" + deviceID + " length=" + report.length); - HIDDevice device; - device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return -1; - } - - return device.writeReport(report, feature); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - return -1; - } - - boolean readReport(int deviceID, byte[] report, boolean feature) { - try { - //Log.v(TAG, "readReport deviceID=" + deviceID); - HIDDevice device; - device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return false; - } - - return device.readReport(report, feature); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - return false; - } - - void closeDevice(int deviceID) { - try { - Log.v(TAG, "closeDevice deviceID=" + deviceID); - HIDDevice device; - device = getDevice(deviceID); - if (device == null) { - HIDDeviceDisconnected(deviceID); - return; - } - - device.close(); - } catch (Exception e) { - Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); - } - } - - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////// Native methods - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - private native void HIDDeviceRegisterCallback(); - private native void HIDDeviceReleaseCallback(); - - native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol, boolean bBluetooth, int reportID); - native void HIDDeviceOpenPending(int deviceID); - native void HIDDeviceOpenResult(int deviceID, boolean opened); - native void HIDDeviceDisconnected(int deviceID); - - native void HIDDeviceInputReport(int deviceID, byte[] report); - native void HIDDeviceReportResponse(int deviceID, byte[] report); -} diff --git a/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java deleted file mode 100644 index 8954639733..0000000000 --- a/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java +++ /dev/null @@ -1,354 +0,0 @@ -package org.libsdl.app; - -import android.hardware.usb.*; -import android.os.Build; -import android.util.Log; -import java.util.Arrays; -import java.util.Locale; - -class HIDDeviceUSB implements HIDDevice { - - private static final String TAG = "hidapi"; - - protected HIDDeviceManager mManager; - protected UsbDevice mDevice; - protected int mInterfaceIndex; - protected int mInterface; - protected int mDeviceId; - protected UsbDeviceConnection mConnection; - protected UsbEndpoint mInputEndpoint; - protected UsbEndpoint mOutputEndpoint; - protected InputThread mInputThread; - protected boolean mRunning; - protected boolean mFrozen; - protected boolean mClaimed; - - public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) { - mManager = manager; - mDevice = usbDevice; - mInterfaceIndex = interface_index; - mInterface = mDevice.getInterface(mInterfaceIndex).getId(); - mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier()); - mRunning = false; - mClaimed = false; - } - - String getIdentifier() { - return String.format(Locale.ENGLISH, "%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); - } - - @Override - public int getId() { - return mDeviceId; - } - - @Override - public int getVendorId() { - return mDevice.getVendorId(); - } - - @Override - public int getProductId() { - return mDevice.getProductId(); - } - - @Override - public String getSerialNumber() { - String result = null; - try { - result = mDevice.getSerialNumber(); - } - catch (SecurityException exception) { - //Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage()); - } - if (result == null) { - result = ""; - } - return result; - } - - @Override - public int getVersion() { - return 0; - } - - @Override - public String getManufacturerName() { - String result; - result = mDevice.getManufacturerName(); - if (result == null) { - result = String.format("%x", getVendorId()); - } - return result; - } - - @Override - public String getProductName() { - String result; - result = mDevice.getProductName(); - if (result == null) { - result = String.format("%x", getProductId()); - } - return result; - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - String getDeviceName() { - return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; - } - - @Override - public boolean open() { - mConnection = mManager.getUSBManager().openDevice(mDevice); - if (mConnection == null) { - Log.w(TAG, "Unable to open USB device " + getDeviceName()); - return false; - } - - // Force claim our interface - UsbInterface iface = mDevice.getInterface(mInterfaceIndex); - if (!mConnection.claimInterface(iface, true)) { - Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName()); - close(); - return false; - } - mClaimed = true; - - // Find the endpoints - for (int j = 0; j < iface.getEndpointCount(); j++) { - UsbEndpoint endpt = iface.getEndpoint(j); - switch (endpt.getDirection()) { - case UsbConstants.USB_DIR_IN: - if (mInputEndpoint == null) { - mInputEndpoint = endpt; - } - break; - case UsbConstants.USB_DIR_OUT: - if (mOutputEndpoint == null) { - mOutputEndpoint = endpt; - } - break; - } - } - - // Make sure the required endpoints were present. The original Steam Controller and the wireless dongle for it do NOT - // actually have -- or require -- output endpoints, so we need to accept only an input one for them or else we'll fall - // back to the Android system gamepad functionality (and lose our paddles et al). - if (mInputEndpoint == null) { - Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName()); - mConnection.releaseInterface(iface); - close(); - return false; - } - - // Start listening for input - mRunning = true; - mInputThread = new InputThread(); - mInputThread.start(); - - return true; - } - - @Override - public int writeReport(byte[] report, boolean feature) { - if (mConnection == null) { - Log.w(TAG, "writeReport() called with no device connection"); - return -1; - } - - if (!mClaimed) { - Log.w(TAG, "writeReport() called but some other process currently owns the USB device"); - return -1; - } - - if (feature) { - int res = -1; - int offset = 0; - int length = report.length; - boolean skipped_report_id = false; - byte report_number = report[0]; - - if (report_number == 0x0) { - ++offset; - --length; - skipped_report_id = true; - } - - res = mConnection.controlTransfer( - UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT, - 0x09/*HID set_report*/, - (3/*HID feature*/ << 8) | report_number, - mInterface, - report, offset, length, - 1000/*timeout millis*/); - - if (res < 0) { - Log.w(TAG, "writeFeatureReport() returned " + res + " on device " + getDeviceName()); - return -1; - } - - if (skipped_report_id) { - ++length; - } - return length; - } else { - if (mOutputEndpoint == null) - { - Log.e(TAG, "Tried to write an output report to an interface with no output endpoint!"); - return -1; - } - int res = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000); - if (res != report.length) { - Log.w(TAG, "writeOutputReport() returned " + res + " on device " + getDeviceName()); - } - return res; - } - } - - @Override - public boolean readReport(byte[] report, boolean feature) { - int res = -1; - int offset = 0; - int length = report.length; - boolean skipped_report_id = false; - byte report_number = report[0]; - - if (mConnection == null) { - Log.w(TAG, "readReport() called with no device connection"); - return false; - } - if (!mClaimed) { - if (feature) { - return false; - } - return true; - } - - if (report_number == 0x0) { - /* Offset the return buffer by 1, so that the report ID - will remain in byte 0. */ - ++offset; - --length; - skipped_report_id = true; - } - - res = mConnection.controlTransfer( - UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN, - 0x01/*HID get_report*/, - ((feature ? 3/*HID feature*/ : 1/*HID Input*/) << 8) | report_number, - mInterface, - report, offset, length, - 1000/*timeout millis*/); - - if (res < 0) { - Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName()); - return false; - } - - if (skipped_report_id) { - ++res; - ++length; - } - - byte[] data; - if (res == length) { - data = report; - } else { - data = Arrays.copyOfRange(report, 0, res); - } - mManager.HIDDeviceReportResponse(mDeviceId, data); - - return true; - } - - @Override - public void close() { - mRunning = false; - if (mInputThread != null) { - while (mInputThread.isAlive()) { - mInputThread.interrupt(); - try { - mInputThread.join(); - } catch (InterruptedException e) { - // Keep trying until we're done - } - } - mInputThread = null; - } - if (mConnection != null) { - if (mClaimed) { - UsbInterface iface = mDevice.getInterface(mInterfaceIndex); - mConnection.releaseInterface(iface); - } - mConnection.close(); - mConnection = null; - mClaimed = false; - } - } - - @Override - public void shutdown() { - close(); - mManager = null; - } - - @Override - public void setFrozen(boolean frozen) { - mFrozen = frozen; - - /* If we have a valid device connection and the claim state doesn't match what we want, try to correct that. */ - if (mConnection != null && mClaimed == mFrozen) { - UsbInterface iface = mDevice.getInterface(mInterfaceIndex); - if (frozen) { - mClaimed = !mConnection.releaseInterface(iface); - if (mClaimed) { - Log.e(TAG, "Tried to release claim on USB device, but failed!"); - } - } else { - mClaimed = mConnection.claimInterface(iface, true); - if (!mClaimed) { - Log.e(TAG, "Tried to regain claim on USB device, but failed!"); - } - } - } - } - - protected class InputThread extends Thread { - @Override - public void run() { - int packetSize = mInputEndpoint.getMaxPacketSize(); - byte[] packet = new byte[packetSize]; - while (mRunning) { - int r; - try - { - r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000); - } - catch (Exception e) - { - Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e); - break; - } - if (r < 0) { - // Could be a timeout or an I/O error - } - if (r > 0) { - byte[] data; - if (r == packetSize) { - data = packet; - } else { - data = Arrays.copyOfRange(packet, 0, r); - } - - if (!mFrozen) { - mManager.HIDDeviceInputReport(mDeviceId, data); - } - } - } - } - } -} diff --git a/platforms/android/app/src/main/java/org/libsdl/app/SDL.java b/platforms/android/app/src/main/java/org/libsdl/app/SDL.java deleted file mode 100644 index d9650a72e4..0000000000 --- a/platforms/android/app/src/main/java/org/libsdl/app/SDL.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.libsdl.app; - -import android.app.Activity; -import android.content.Context; - -import java.lang.reflect.Method; - -/** - SDL library initialization -*/ -public class SDL { - - // This function should be called first and sets up the native code - // so it can call into the Java classes - static public void setupJNI() { - SDLActivity.nativeSetupJNI(); - SDLAudioManager.nativeSetupJNI(); - SDLControllerManager.nativeSetupJNI(); - } - - // This function should be called each time the activity is started - static public void initialize() { - setContext(null); - - SDLActivity.initialize(); - SDLAudioManager.initialize(); - SDLControllerManager.initialize(); - } - - // This function stores the current activity (SDL or not) - static public void setContext(Activity context) { - SDLAudioManager.setContext(context); - mContext = context; - } - - static public Activity getContext() { - return mContext; - } - - static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { - loadLibrary(libraryName, mContext); - } - - static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException { - - if (libraryName == null) { - throw new NullPointerException("No library name provided."); - } - - try { - // Let's see if we have ReLinker available in the project. This is necessary for - // some projects that have huge numbers of local libraries bundled, and thus may - // trip a bug in Android's native library loader which ReLinker works around. (If - // loadLibrary works properly, ReLinker will simply use the normal Android method - // internally.) - // - // To use ReLinker, just add it as a dependency. For more information, see - // https://github.com/KeepSafe/ReLinker for ReLinker's repository. - // - Class relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); - Class relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); - Class contextClass = context.getClassLoader().loadClass("android.content.Context"); - Class stringClass = context.getClassLoader().loadClass("java.lang.String"); - - // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if - // they've changed during updates. - Method forceMethod = relinkClass.getDeclaredMethod("force"); - Object relinkInstance = forceMethod.invoke(null); - Class relinkInstanceClass = relinkInstance.getClass(); - - // Actually load the library! - Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); - loadMethod.invoke(relinkInstance, context, libraryName, null, null); - } - catch (final Throwable e) { - // Fall back - try { - System.loadLibrary(libraryName); - } - catch (final UnsatisfiedLinkError ule) { - throw ule; - } - catch (final SecurityException se) { - throw se; - } - } - } - - protected static Activity mContext; -} diff --git a/platforms/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/platforms/android/app/src/main/java/org/libsdl/app/SDLActivity.java deleted file mode 100644 index dcc49852ac..0000000000 --- a/platforms/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ /dev/null @@ -1,2240 +0,0 @@ -package org.libsdl.app; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.UiModeManager; -import android.content.ActivityNotFoundException; -import android.content.ClipboardManager; -import android.content.ClipData; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.hardware.Sensor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.LocaleList; -import android.os.Message; -import android.os.ParcelFileDescriptor; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.SparseArray; -import android.view.Display; -import android.view.Gravity; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.PointerIcon; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; -import android.webkit.MimeTypeMap; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; -import android.widget.Toast; - -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.Locale; - - -/** - SDL Activity -*/ -public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { - private static final String TAG = "SDL"; - private static final int SDL_MAJOR_VERSION = 3; - private static final int SDL_MINOR_VERSION = 4; - private static final int SDL_MICRO_VERSION = 10; -/* - // Display InputType.SOURCE/CLASS of events and devices - // - // SDLActivity.debugSource(device.getSources(), "device[" + device.getName() + "]"); - // SDLActivity.debugSource(event.getSource(), "event"); - public static void debugSource(int sources, String prefix) { - int s = sources; - int s_copy = sources; - String cls = ""; - String src = ""; - int tst = 0; - int FLAG_TAINTED = 0x80000000; - - if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0) cls += " BUTTON"; - if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) cls += " JOYSTICK"; - if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0) cls += " POINTER"; - if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0) cls += " POSITION"; - if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) cls += " TRACKBALL"; - - - int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits - s2 &= ~( InputDevice.SOURCE_CLASS_BUTTON - | InputDevice.SOURCE_CLASS_JOYSTICK - | InputDevice.SOURCE_CLASS_POINTER - | InputDevice.SOURCE_CLASS_POSITION - | InputDevice.SOURCE_CLASS_TRACKBALL); - - if (s2 != 0) cls += "Some_Unknown"; - - s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; - - if (Build.VERSION.SDK_INT >= 23) { - tst = InputDevice.SOURCE_BLUETOOTH_STYLUS; - if ((s & tst) == tst) src += " BLUETOOTH_STYLUS"; - s2 &= ~tst; - } - - tst = InputDevice.SOURCE_DPAD; - if ((s & tst) == tst) src += " DPAD"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_GAMEPAD; - if ((s & tst) == tst) src += " GAMEPAD"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_HDMI; - if ((s & tst) == tst) src += " HDMI"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_JOYSTICK; - if ((s & tst) == tst) src += " JOYSTICK"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_KEYBOARD; - if ((s & tst) == tst) src += " KEYBOARD"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_MOUSE; - if ((s & tst) == tst) src += " MOUSE"; - s2 &= ~tst; - - if (Build.VERSION.SDK_INT >= 26) { - tst = InputDevice.SOURCE_MOUSE_RELATIVE; - if ((s & tst) == tst) src += " MOUSE_RELATIVE"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_ROTARY_ENCODER; - if ((s & tst) == tst) src += " ROTARY_ENCODER"; - s2 &= ~tst; - } - tst = InputDevice.SOURCE_STYLUS; - if ((s & tst) == tst) src += " STYLUS"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_TOUCHPAD; - if ((s & tst) == tst) src += " TOUCHPAD"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_TOUCHSCREEN; - if ((s & tst) == tst) src += " TOUCHSCREEN"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_TOUCH_NAVIGATION; - if ((s & tst) == tst) src += " TOUCH_NAVIGATION"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_TRACKBALL; - if ((s & tst) == tst) src += " TRACKBALL"; - s2 &= ~tst; - - tst = InputDevice.SOURCE_ANY; - if ((s & tst) == tst) src += " ANY"; - s2 &= ~tst; - - if (s == FLAG_TAINTED) src += " FLAG_TAINTED"; - s2 &= ~FLAG_TAINTED; - - if (s2 != 0) src += " Some_Unknown"; - - Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src); - } -*/ - - public static boolean mIsResumedCalled, mHasFocus; - public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */); - - // Cursor types - // private static final int SDL_SYSTEM_CURSOR_NONE = -1; - private static final int SDL_SYSTEM_CURSOR_ARROW = 0; - private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; - private static final int SDL_SYSTEM_CURSOR_WAIT = 2; - private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3; - private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4; - private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5; - private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6; - private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7; - private static final int SDL_SYSTEM_CURSOR_SIZENS = 8; - private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; - private static final int SDL_SYSTEM_CURSOR_NO = 10; - private static final int SDL_SYSTEM_CURSOR_HAND = 11; - private static final int SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT = 12; - private static final int SDL_SYSTEM_CURSOR_WINDOW_TOP = 13; - private static final int SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT = 14; - private static final int SDL_SYSTEM_CURSOR_WINDOW_RIGHT = 15; - private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT = 16; - private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOM = 17; - private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT = 18; - private static final int SDL_SYSTEM_CURSOR_WINDOW_LEFT = 19; - - protected static final int SDL_ORIENTATION_UNKNOWN = 0; - protected static final int SDL_ORIENTATION_LANDSCAPE = 1; - protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2; - protected static final int SDL_ORIENTATION_PORTRAIT = 3; - protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; - - protected static int mCurrentRotation; - protected static Locale mCurrentLocale; - - // Handle the state of the native layer - public enum NativeState { - INIT, RESUMED, PAUSED - } - - public static NativeState mNextNativeState; - public static NativeState mCurrentNativeState; - - /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ - public static boolean mBrokenLibraries = true; - - // Main components - protected static SDLActivity mSingleton; - protected static SDLSurface mSurface; - protected static SDLDummyEdit mTextEdit; - protected static ViewGroup mLayout; - protected static SDLClipboardHandler mClipboardHandler; - protected static Hashtable mCursors; - protected static int mLastCursorID; - protected static SDLGenericMotionListener_API14 mMotionListener; - protected static HIDDeviceManager mHIDDeviceManager; - - // This is what SDL runs in. It invokes SDL_main(), eventually - protected static Thread mSDLThread; - protected static boolean mSDLMainFinished = false; - protected static boolean mActivityCreated = false; - private static SDLFileDialogState mFileDialogState = null; - protected static boolean mDispatchingKeyEvent = false; - - public static SDLGenericMotionListener_API14 getMotionListener() { - if (mMotionListener == null) { - if (Build.VERSION.SDK_INT >= 29 /* Android 10 (Q) */) { - mMotionListener = new SDLGenericMotionListener_API29(); - } else if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { - mMotionListener = new SDLGenericMotionListener_API26(); - } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { - mMotionListener = new SDLGenericMotionListener_API24(); - } else { - mMotionListener = new SDLGenericMotionListener_API14(); - } - } - - return mMotionListener; - } - - /** - * The application entry point, called on a dedicated thread (SDLThread). - * The default implementation uses the getMainSharedObject() and getMainFunction() methods - * to invoke native code from the specified shared library. - * It can be overridden by derived classes. - */ - protected void main() { - String library = SDLActivity.mSingleton.getMainSharedObject(); - String function = SDLActivity.mSingleton.getMainFunction(); - String[] arguments = SDLActivity.mSingleton.getArguments(); - - Log.v("SDL", "Running main function " + function + " from library " + library); - SDLActivity.nativeRunMain(library, function, arguments); - Log.v("SDL", "Finished main function"); - } - - /** - * This method returns the name of the shared object with the application entry point - * It can be overridden by derived classes. - */ - protected String getMainSharedObject() { - String library; - String[] libraries = SDLActivity.mSingleton.getLibraries(); - if (libraries.length > 0) { - library = "lib" + libraries[libraries.length - 1] + ".so"; - } else { - library = "libmain.so"; - } - return getContext().getApplicationInfo().nativeLibraryDir + "/" + library; - } - - /** - * This method returns the name of the application entry point - * It can be overridden by derived classes. - */ - protected String getMainFunction() { - return "SDL_main"; - } - - /** - * This method is called by SDL before loading the native shared libraries. - * It can be overridden to provide names of shared libraries to be loaded. - * The default implementation returns the defaults. It never returns null. - * An array returned by a new implementation must at least contain "SDL3". - * Also keep in mind that the order the libraries are loaded may matter. - * @return names of shared libraries to be loaded (e.g. "SDL3", "main"). - */ - protected String[] getLibraries() { - return new String[] { - "SDL3", - // "SDL3_image", - // "SDL3_mixer", - // "SDL3_net", - // "SDL3_ttf", - "main" - }; - } - - // Load the .so - public void loadLibraries() { - for (String lib : getLibraries()) { - SDL.loadLibrary(lib, this); - } - } - - /** - * This method is called by SDL before starting the native application thread. - * It can be overridden to provide the arguments after the application name. - * The default implementation returns an empty array. It never returns null. - * @return arguments for the native application. - */ - protected String[] getArguments() { - return new String[0]; - } - - public static void initialize() { - // The static nature of the singleton and Android quirkyness force us to initialize everything here - // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values - mSingleton = null; - mSurface = null; - mTextEdit = null; - mLayout = null; - mClipboardHandler = null; - mCursors = new Hashtable(); - mLastCursorID = 0; - mSDLThread = null; - mIsResumedCalled = false; - mHasFocus = true; - mNextNativeState = NativeState.INIT; - mCurrentNativeState = NativeState.INIT; - } - - protected SDLSurface createSDLSurface(Context context) { - return new SDLSurface(context); - } - - // Setup - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.v(TAG, "Manufacturer: " + Build.MANUFACTURER); - Log.v(TAG, "Device: " + Build.DEVICE); - Log.v(TAG, "Model: " + Build.MODEL); - Log.v(TAG, "onCreate()"); - super.onCreate(savedInstanceState); - - - /* Control activity re-creation */ - if (mSDLMainFinished || mActivityCreated) { - boolean allow_recreate = SDLActivity.nativeAllowRecreateActivity(); - if (mSDLMainFinished) { - Log.v(TAG, "SDL main() finished"); - } - if (allow_recreate) { - Log.v(TAG, "activity re-created"); - } else { - Log.v(TAG, "activity finished"); - System.exit(0); - return; - } - } - - mActivityCreated = true; - - try { - Thread.currentThread().setName("SDLActivity"); - } catch (Exception e) { - Log.v(TAG, "modify thread properties failed " + e.toString()); - } - - // Load shared libraries - String errorMsgBrokenLib = ""; - try { - loadLibraries(); - mBrokenLibraries = false; /* success */ - } catch(UnsatisfiedLinkError e) { - System.err.println(e.getMessage()); - mBrokenLibraries = true; - errorMsgBrokenLib = e.getMessage(); - } catch(Exception e) { - System.err.println(e.getMessage()); - mBrokenLibraries = true; - errorMsgBrokenLib = e.getMessage(); - } - - if (!mBrokenLibraries) { - String expected_version = String.valueOf(SDL_MAJOR_VERSION) + "." + - String.valueOf(SDL_MINOR_VERSION) + "." + - String.valueOf(SDL_MICRO_VERSION); - String version = nativeGetVersion(); - if (!version.equals(expected_version)) { - mBrokenLibraries = true; - errorMsgBrokenLib = "SDL C/Java version mismatch (expected " + expected_version + ", got " + version + ")"; - } - } - - if (mBrokenLibraries) { - mSingleton = this; - AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); - dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." - + System.getProperty("line.separator") - + System.getProperty("line.separator") - + "Error: " + errorMsgBrokenLib); - dlgAlert.setTitle("SDL Error"); - dlgAlert.setPositiveButton("Exit", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog,int id) { - // if this button is clicked, close current activity - SDLActivity.mSingleton.finish(); - } - }); - dlgAlert.setCancelable(false); - dlgAlert.create().show(); - - return; - } - - - /* Control activity re-creation */ - /* Robustness: check that the native code is run for the first time. - * (Maybe Activity was reset, but not the native code.) */ - { - int run_count = SDLActivity.nativeCheckSDLThreadCounter(); /* get and increment a native counter */ - if (run_count != 0) { - boolean allow_recreate = SDLActivity.nativeAllowRecreateActivity(); - if (allow_recreate) { - Log.v(TAG, "activity re-created // run_count: " + run_count); - } else { - Log.v(TAG, "activity finished // run_count: " + run_count); - System.exit(0); - return; - } - } - } - - // Set up JNI - SDL.setupJNI(); - - // Initialize state - SDL.initialize(); - - // So we can call stuff from static callbacks - mSingleton = this; - SDL.setContext(this); - - mClipboardHandler = new SDLClipboardHandler(); - - mHIDDeviceManager = HIDDeviceManager.acquire(this); - - // Set up the surface - mSurface = createSDLSurface(this); - - mLayout = new RelativeLayout(this); - mLayout.addView(mSurface); - - // Get our current screen orientation and pass it down. - SDLActivity.nativeSetNaturalOrientation(SDLActivity.getNaturalOrientation()); - mCurrentRotation = SDLActivity.getCurrentRotation(); - SDLActivity.onNativeRotationChanged(mCurrentRotation); - - try { - if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { - mCurrentLocale = getContext().getResources().getConfiguration().locale; - } else { - mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); - } - } catch(Exception ignored) { - } - - switch (getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) { - case Configuration.UI_MODE_NIGHT_NO: - SDLActivity.onNativeDarkModeChanged(false); - break; - case Configuration.UI_MODE_NIGHT_YES: - SDLActivity.onNativeDarkModeChanged(true); - break; - } - - setContentView(mLayout); - - setWindowStyle(false); - - getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); - - // Get filename from "Open with" of another application - Intent intent = getIntent(); - if (intent != null && intent.getData() != null) { - String filename = intent.getData().getPath(); - if (filename != null) { - Log.v(TAG, "Got filename: " + filename); - SDLActivity.onNativeDropFile(filename); - } - } - } - - protected void pauseNativeThread() { - mNextNativeState = NativeState.PAUSED; - mIsResumedCalled = false; - - if (SDLActivity.mBrokenLibraries) { - return; - } - - SDLActivity.handleNativeState(); - } - - protected void resumeNativeThread() { - mNextNativeState = NativeState.RESUMED; - mIsResumedCalled = true; - - if (SDLActivity.mBrokenLibraries) { - return; - } - - SDLActivity.handleNativeState(); - } - - // Events - @Override - protected void onPause() { - Log.v(TAG, "onPause()"); - super.onPause(); - - if (mHIDDeviceManager != null) { - mHIDDeviceManager.setFrozen(true); - } - - if (!mHasMultiWindow) { - pauseNativeThread(); - } - } - - @Override - protected void onResume() { - Log.v(TAG, "onResume()"); - super.onResume(); - - if (mHIDDeviceManager != null) { - mHIDDeviceManager.setFrozen(false); - } - - if (!mHasMultiWindow) { - resumeNativeThread(); - } - } - - @Override - protected void onStop() { - Log.v(TAG, "onStop()"); - super.onStop(); - if (mHasMultiWindow) { - pauseNativeThread(); - } - } - - @Override - protected void onStart() { - Log.v(TAG, "onStart()"); - super.onStart(); - if (mHasMultiWindow) { - resumeNativeThread(); - } - } - - public static int getNaturalOrientation() { - int result = SDL_ORIENTATION_UNKNOWN; - - Activity activity = getContext(); - if (activity != null) { - Configuration config = activity.getResources().getConfiguration(); - Display display = activity.getWindowManager().getDefaultDisplay(); - int rotation = display.getRotation(); - if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) && - config.orientation == Configuration.ORIENTATION_LANDSCAPE) || - ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) && - config.orientation == Configuration.ORIENTATION_PORTRAIT)) { - result = SDL_ORIENTATION_LANDSCAPE; - } else { - result = SDL_ORIENTATION_PORTRAIT; - } - } - return result; - } - - public static int getCurrentRotation() { - int result = 0; - - Activity activity = getContext(); - if (activity != null) { - Display display = activity.getWindowManager().getDefaultDisplay(); - switch (display.getRotation()) { - case Surface.ROTATION_0: - result = 0; - break; - case Surface.ROTATION_90: - result = 90; - break; - case Surface.ROTATION_180: - result = 180; - break; - case Surface.ROTATION_270: - result = 270; - break; - } - } - return result; - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); - - // If we are gaining focus, we can always try to restore our USB devices. If we are losing focus, - // only try to relinquish them if we don't have background events allowed (for multi-window Android setups). - if (hasFocus || !SDLActivity.nativeGetHintBoolean("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", false)) { - if (mHIDDeviceManager != null) { - mHIDDeviceManager.setFrozen(!hasFocus); - } - } - - if (SDLActivity.mBrokenLibraries) { - return; - } - - mHasFocus = hasFocus; - if (hasFocus) { - mNextNativeState = NativeState.RESUMED; - SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded(); - - SDLActivity.handleNativeState(); - nativeFocusChanged(true); - - } else { - nativeFocusChanged(false); - if (!mHasMultiWindow) { - mNextNativeState = NativeState.PAUSED; - SDLActivity.handleNativeState(); - } - } - } - - @Override - public void onTrimMemory(int level) { - Log.v(TAG, "onTrimMemory()"); - super.onTrimMemory(level); - - if (SDLActivity.mBrokenLibraries) { - return; - } - - SDLActivity.nativeLowMemory(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - Log.v(TAG, "onConfigurationChanged()"); - super.onConfigurationChanged(newConfig); - - if (SDLActivity.mBrokenLibraries) { - return; - } - - if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) { - mCurrentLocale = newConfig.locale; - SDLActivity.onNativeLocaleChanged(); - } - - switch (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) { - case Configuration.UI_MODE_NIGHT_NO: - SDLActivity.onNativeDarkModeChanged(false); - break; - case Configuration.UI_MODE_NIGHT_YES: - SDLActivity.onNativeDarkModeChanged(true); - break; - } - } - - @Override - protected void onDestroy() { - Log.v(TAG, "onDestroy()"); - - if (mHIDDeviceManager != null) { - HIDDeviceManager.release(mHIDDeviceManager); - mHIDDeviceManager = null; - } - - SDLAudioManager.release(this); - - if (SDLActivity.mBrokenLibraries) { - super.onDestroy(); - return; - } - - if (SDLActivity.mSDLThread != null) { - - // Send Quit event to "SDLThread" thread - SDLActivity.nativeSendQuit(); - - // Wait for "SDLThread" thread to end - try { - // Use a timeout because: - // C SDLmain() thread might have started (mSDLThread.start() called) - // while the SDL_Init() might not have been called yet, - // and so the previous QUIT event will be discarded by SDL_Init() and app is running, not exiting. - SDLActivity.mSDLThread.join(1000); - } catch(Exception e) { - Log.v(TAG, "Problem stopping SDLThread: " + e); - } - } - - SDLActivity.nativeQuit(); - - super.onDestroy(); - } - - @Override - public void onBackPressed() { - // Check if we want to block the back button in case of mouse right click. - // - // If we do, the normal hardware back button will no longer work and people have to use home, - // but the mouse right click will work. - // - boolean trapBack = SDLActivity.nativeGetHintBoolean("SDL_ANDROID_TRAP_BACK_BUTTON", false); - if (trapBack) { - // Exit and let the mouse handler handle this button (if appropriate) - return; - } - - // Default system back button behavior. - if (!isFinishing()) { - super.onBackPressed(); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (mFileDialogState != null && mFileDialogState.requestCode == requestCode) { - /* This is our file dialog */ - String[] filelist = null; - - if (data != null) { - Uri singleFileUri = data.getData(); - - if (singleFileUri == null) { - /* Use Intent.getClipData to get multiple choices */ - ClipData clipData = data.getClipData(); - assert clipData != null; - - filelist = new String[clipData.getItemCount()]; - - for (int i = 0; i < filelist.length; i++) { - String uri = clipData.getItemAt(i).getUri().toString(); - filelist[i] = uri; - } - } else { - /* Only one file is selected. */ - filelist = new String[]{singleFileUri.toString()}; - } - } else { - /* User cancelled the request. */ - filelist = new String[0]; - } - - // TODO: Detect the file MIME type and pass the filter value accordingly. - SDLActivity.onNativeFileDialog(requestCode, filelist, -1); - mFileDialogState = null; - } - } - - // Called by JNI from SDL. - public static void manualBackButton() { - mSingleton.pressBackButton(); - } - - // Used to get us onto the activity's main thread - public void pressBackButton() { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (!SDLActivity.this.isFinishing()) { - SDLActivity.this.superOnBackPressed(); - } - } - }); - } - - // Used to access the system back behavior. - public void superOnBackPressed() { - super.onBackPressed(); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - - if (SDLActivity.mBrokenLibraries) { - return false; - } - - int keyCode = event.getKeyCode(); - // Ignore certain special keys so they're handled by Android - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || - keyCode == KeyEvent.KEYCODE_VOLUME_UP || - keyCode == KeyEvent.KEYCODE_CAMERA || - keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ - keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ - ) { - return false; - } - mDispatchingKeyEvent = true; - boolean result = super.dispatchKeyEvent(event); - mDispatchingKeyEvent = false; - return result; - } - - public static boolean dispatchingKeyEvent() { - return mDispatchingKeyEvent; - } - - /* Transition to next state */ - public static void handleNativeState() { - - if (mNextNativeState == mCurrentNativeState) { - // Already in same state, discard. - return; - } - - // Try a transition to init state - if (mNextNativeState == NativeState.INIT) { - - mCurrentNativeState = mNextNativeState; - return; - } - - // Try a transition to paused state - if (mNextNativeState == NativeState.PAUSED) { - if (mSDLThread != null) { - nativePause(); - } - if (mSurface != null) { - mSurface.handlePause(); - } - mCurrentNativeState = mNextNativeState; - return; - } - - // Try a transition to resumed state - if (mNextNativeState == NativeState.RESUMED) { - if (mSurface.mIsSurfaceReady && (mHasFocus || mHasMultiWindow) && mIsResumedCalled) { - if (mSDLThread == null) { - // This is the entry point to the C app. - // Start up the C app thread and enable sensor input for the first time - // FIXME: Why aren't we enabling sensor input at start? - - mSDLThread = new Thread(new SDLMain(), "SDLThread"); - mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); - mSDLThread.start(); - - // No nativeResume(), don't signal Android_ResumeSem - } else { - nativeResume(); - } - mSurface.handleResume(); - - mCurrentNativeState = mNextNativeState; - } - } - } - - // Messages from the SDLMain thread - protected static final int COMMAND_CHANGE_TITLE = 1; - protected static final int COMMAND_CHANGE_WINDOW_STYLE = 2; - protected static final int COMMAND_TEXTEDIT_HIDE = 3; - protected static final int COMMAND_SET_KEEP_SCREEN_ON = 5; - protected static final int COMMAND_USER = 0x8000; - - protected static boolean mFullscreenModeActive; - - /** - * This method is called by SDL if SDL did not handle a message itself. - * This happens if a received message contains an unsupported command. - * Method can be overwritten to handle Messages in a different class. - * @param command the command of the message. - * @param param the parameter of the message. May be null. - * @return if the message was handled in overridden method. - */ - protected boolean onUnhandledMessage(int command, Object param) { - return false; - } - - /** - * A Handler class for Messages from native SDL applications. - * It uses current Activities as target (e.g. for the title). - * static to prevent implicit references to enclosing object. - */ - protected static class SDLCommandHandler extends Handler { - @Override - public void handleMessage(Message msg) { - Context context = getContext(); - if (context == null) { - Log.e(TAG, "error handling message, getContext() returned null"); - return; - } - switch (msg.arg1) { - case COMMAND_CHANGE_TITLE: - if (context instanceof Activity) { - ((Activity) context).setTitle((String)msg.obj); - } else { - Log.e(TAG, "error handling message, getContext() returned no Activity"); - } - break; - case COMMAND_CHANGE_WINDOW_STYLE: - if (context instanceof Activity) { - Window window = ((Activity) context).getWindow(); - if (window != null) { - if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { - int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; - window.getDecorView().setSystemUiVisibility(flags); - window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - SDLActivity.mFullscreenModeActive = true; - } else { - int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; - window.getDecorView().setSystemUiVisibility(flags); - window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - SDLActivity.mFullscreenModeActive = false; - } - if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) { - window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - } - if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */ && - Build.VERSION.SDK_INT < 35 /* Android 15 */) { - SDLActivity.onNativeInsetsChanged(0, 0, 0, 0); - } - } - } else { - Log.e(TAG, "error handling message, getContext() returned no Activity"); - } - break; - case COMMAND_TEXTEDIT_HIDE: - if (mTextEdit != null) { - // Note: On some devices setting view to GONE creates a flicker in landscape. - // Setting the View's sizes to 0 is similar to GONE but without the flicker. - // The sizes will be set to useful values when the keyboard is shown again. - mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); - - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); - - onNativeScreenKeyboardHidden(); - - mSurface.requestFocus(); - } - break; - case COMMAND_SET_KEEP_SCREEN_ON: - { - if (context instanceof Activity) { - Window window = ((Activity) context).getWindow(); - if (window != null) { - if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - } - } - break; - } - default: - if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { - Log.e(TAG, "error handling message, command is " + msg.arg1); - } - } - } - } - - // Handler for the messages - Handler commandHandler = new SDLCommandHandler(); - - // Send a message from the SDLMain thread - protected boolean sendCommand(int command, Object data) { - Message msg = commandHandler.obtainMessage(); - msg.arg1 = command; - msg.obj = data; - boolean result = commandHandler.sendMessage(msg); - - if (command == COMMAND_CHANGE_WINDOW_STYLE) { - // Ensure we don't return until the resize has actually happened, - // or 500ms have passed. - - boolean bShouldWait = false; - - if (data instanceof Integer) { - // Let's figure out if we're already laid out fullscreen or not. - Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - DisplayMetrics realMetrics = new DisplayMetrics(); - display.getRealMetrics(realMetrics); - - boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && - (realMetrics.heightPixels == mSurface.getHeight())); - - if ((Integer) data == 1) { - // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going - // to change size and should wait for surfaceChanged() before we return, so the size - // is right back in native code. If we're already laid out fullscreen, though, we're - // not going to change size even if we change decor modes, so we shouldn't wait for - // surfaceChanged() -- which may not even happen -- and should return immediately. - bShouldWait = !bFullscreenLayout; - } else { - // If we're laid out fullscreen (even if the status bar and nav bar are present), - // or are actively in fullscreen, we're going to change size and should wait for - // surfaceChanged before we return, so the size is right back in native code. - bShouldWait = bFullscreenLayout; - } - } - - if (bShouldWait && (getContext() != null)) { - // We'll wait for the surfaceChanged() method, which will notify us - // when called. That way, we know our current size is really the - // size we need, instead of grabbing a size that's still got - // the navigation and/or status bars before they're hidden. - // - // We'll wait for up to half a second, because some devices - // take a surprisingly long time for the surface resize, but - // then we'll just give up and return. - // - synchronized (getContext()) { - try { - getContext().wait(500); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - } - } - } - - return result; - } - - // C functions we call - public static native String nativeGetVersion(); - public static native void nativeSetupJNI(); - public static native void nativeInitMainThread(); - public static native void nativeCleanupMainThread(); - public static native int nativeRunMain(String library, String function, Object arguments); - public static native void nativeLowMemory(); - public static native void nativeSendQuit(); - public static native void nativeQuit(); - public static native void nativePause(); - public static native void nativeResume(); - public static native void nativeFocusChanged(boolean hasFocus); - public static native void onNativeDropFile(String filename); - public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float density, float rate); - public static native void onNativeResize(); - public static native void onNativeKeyDown(int keycode); - public static native void onNativeKeyUp(int keycode); - public static native boolean onNativeSoftReturnKey(); - public static native void onNativeKeyboardFocusLost(); - public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); - public static native void onNativeTouch(int touchDevId, int pointerFingerId, - int action, float x, - float y, float p); - public static native void onNativePen(int penId, int device_type, int button, int action, float x, float y, float p); - public static native void onNativeAccel(float x, float y, float z); - public static native void onNativeClipboardChanged(); - public static native void onNativeSurfaceCreated(); - public static native void onNativeSurfaceChanged(); - public static native void onNativeSurfaceDestroyed(); - public static native void onNativeScreenKeyboardShown(); - public static native void onNativeScreenKeyboardHidden(); - public static native String nativeGetHint(String name); - public static native boolean nativeGetHintBoolean(String name, boolean default_value); - public static native void nativeSetenv(String name, String value); - public static native void nativeSetNaturalOrientation(int orientation); - public static native void onNativeRotationChanged(int rotation); - public static native void onNativeInsetsChanged(int left, int right, int top, int bottom); - public static native void nativeAddTouch(int touchId, String name); - public static native void nativePermissionResult(int requestCode, boolean result); - public static native void onNativeLocaleChanged(); - public static native void onNativeDarkModeChanged(boolean enabled); - public static native boolean nativeAllowRecreateActivity(); - public static native int nativeCheckSDLThreadCounter(); - public static native void onNativeFileDialog(int requestCode, String[] filelist, int filter); - public static native void onNativePinchStart(); - public static native void onNativePinchUpdate(float scale); - public static native void onNativePinchEnd(); - - /** - * This method is called by SDL using JNI. - */ - public static boolean setActivityTitle(String title) { - // Called from SDLMain() thread and can't directly affect the view - return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); - } - - /** - * This method is called by SDL using JNI. - */ - public static void setWindowStyle(boolean fullscreen) { - // Called from SDLMain() thread and can't directly affect the view - mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0); - } - - /** - * This method is called by SDL using JNI. - * This is a static method for JNI convenience, it calls a non-static method - * so that is can be overridden - */ - public static void setOrientation(int w, int h, boolean resizable, String hint) - { - if (mSingleton != null) { - mSingleton.setOrientationBis(w, h, resizable, hint); - } - } - - /** - * This can be overridden - */ - public void setOrientationBis(int w, int h, boolean resizable, String hint) - { - int orientation_landscape = -1; - int orientation_portrait = -1; - - if (w <= 1 || h <= 1) { - // Invalid width/height, ignore this request - return; - } - - /* If set, hint "explicitly controls which UI orientations are allowed". */ - if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { - orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE; - } else if (hint.contains("LandscapeLeft")) { - orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - } else if (hint.contains("LandscapeRight")) { - orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - } - - /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */ - boolean contains_Portrait = hint.contains("Portrait ") || hint.endsWith("Portrait"); - - if (contains_Portrait && hint.contains("PortraitUpsideDown")) { - orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT; - } else if (contains_Portrait) { - orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - } else if (hint.contains("PortraitUpsideDown")) { - orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; - } - - boolean is_landscape_allowed = (orientation_landscape != -1); - boolean is_portrait_allowed = (orientation_portrait != -1); - int req; /* Requested orientation */ - - /* No valid hint, nothing is explicitly allowed */ - if (!is_portrait_allowed && !is_landscape_allowed) { - if (resizable) { - /* All orientations are allowed, respecting user orientation lock setting */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; - } else { - /* Fixed window and nothing specified. Get orientation from w/h of created window */ - req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); - } - } else { - /* At least one orientation is allowed */ - if (resizable) { - if (is_portrait_allowed && is_landscape_allowed) { - /* hint allows both landscape and portrait, promote to full user */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; - } else { - /* Use the only one allowed "orientation" */ - req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); - } - } else { - /* Fixed window and both orientations are allowed. Choose one. */ - if (is_portrait_allowed && is_landscape_allowed) { - req = (w > h ? orientation_landscape : orientation_portrait); - } else { - /* Use the only one allowed "orientation" */ - req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); - } - } - } - - Log.v(TAG, "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); - mSingleton.setRequestedOrientation(req); - } - - /** - * This method is called by SDL using JNI. - */ - public static void minimizeWindow() { - - if (mSingleton == null) { - return; - } - - Intent startMain = new Intent(Intent.ACTION_MAIN); - startMain.addCategory(Intent.CATEGORY_HOME); - startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mSingleton.startActivity(startMain); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean shouldMinimizeOnFocusLoss() { - return false; - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean supportsRelativeMouse() - { - // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under - // Android 7 APIs, and simply returns no data under Android 8 APIs. - // - // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and - // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, - // we should stick to relative mode. - // - if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) { - return false; - } - - return SDLActivity.getMotionListener().supportsRelativeMouse(); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean setRelativeMouseEnabled(boolean enabled) - { - if (enabled && !supportsRelativeMouse()) { - return false; - } - - return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean sendMessage(int command, int param) { - if (mSingleton == null) { - return false; - } - return mSingleton.sendCommand(command, param); - } - - /** - * This method is called by SDL using JNI. - */ - public static Activity getContext() { - return SDL.getContext(); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isAndroidTV() { - UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE); - if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { - return true; - } - if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) { - return true; - } - if (Build.MANUFACTURER.equals("Amlogic") && - (Build.MODEL.startsWith("TV") || - Build.MODEL.equals("X96-W") || - Build.MODEL.equals("A95X-R1"))) { - return true; - } - return false; - } - - public static boolean isVRHeadset() { - if (Build.MANUFACTURER.equals("Oculus") && Build.MODEL.startsWith("Quest")) { - return true; - } - if (Build.MANUFACTURER.equals("Pico")) { - return true; - } - return false; - } - - public static double getDiagonal() - { - DisplayMetrics metrics = new DisplayMetrics(); - Activity activity = getContext(); - if (activity == null) { - return 0.0; - } - activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); - - double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; - double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; - - return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isTablet() { - // If our diagonal size is seven inches or greater, we consider ourselves a tablet. - return (getDiagonal() >= 7.0); - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isChromebook() { - // https://stackoverflow.com/questions/39784415/how-to-detect-programmatically-if-android-app-is-running-in-chrome-book-or-in - if (getContext() != null) { - if (getContext().getPackageManager().hasSystemFeature("org.chromium.arc") - || getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management")) { - return true; - } - } - - // Running on AVD emulator - boolean isChromebookEmulator = (Build.MODEL != null && Build.MODEL.startsWith("sdk_gpc_")); - return isChromebookEmulator; - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean isDeXMode() { - if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { - return false; - } - try { - final Configuration config = getContext().getResources().getConfiguration(); - final Class configClass = config.getClass(); - return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) - == configClass.getField("semDesktopModeEnabled").getInt(config); - } catch(Exception ignored) { - return false; - } - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean getManifestEnvironmentVariables() { - try { - if (getContext() == null) { - return false; - } - - ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); - Bundle bundle = applicationInfo.metaData; - if (bundle == null) { - return false; - } - String prefix = "SDL_ENV."; - final int trimLength = prefix.length(); - for (String key : bundle.keySet()) { - if (key.startsWith(prefix)) { - String name = key.substring(trimLength); - String value = bundle.get(key).toString(); - nativeSetenv(name, value); - } - } - /* environment variables set! */ - return true; - } catch (Exception e) { - Log.v(TAG, "exception " + e.toString()); - } - return false; - } - - // This method is called by SDLControllerManager's API 26 Generic Motion Handler. - public static View getContentView() { - return mLayout; - } - - static class ShowTextInputTask implements Runnable { - /* - * This is used to regulate the pan&scan method to have some offset from - * the bottom edge of the input region and the top edge of an input - * method (soft keyboard) - */ - static final int HEIGHT_PADDING = 15; - - public int input_type; - public int x, y, w, h; - - public ShowTextInputTask(int input_type, int x, int y, int w, int h) { - this.input_type = input_type; - this.x = x; - this.y = y; - this.w = w; - this.h = h; - - /* Minimum size of 1 pixel, so it takes focus. */ - if (this.w <= 0) { - this.w = 1; - } - if (this.h + HEIGHT_PADDING <= 0) { - this.h = 1 - HEIGHT_PADDING; - } - } - - @Override - public void run() { - RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); - params.leftMargin = x; - params.topMargin = y; - - if (mTextEdit == null) { - mTextEdit = new SDLDummyEdit(getContext()); - - mLayout.addView(mTextEdit, params); - } else { - mTextEdit.setLayoutParams(params); - } - mTextEdit.setInputType(input_type); - - mTextEdit.setVisibility(View.VISIBLE); - mTextEdit.requestFocus(); - - InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mTextEdit, 0); - - if (imm.isAcceptingText()) { - onNativeScreenKeyboardShown(); - } - } - } - - /** - * This method is called by SDL using JNI. - */ - public static boolean showTextInput(int input_type, int x, int y, int w, int h) { - // Transfer the task to the main thread as a Runnable - return mSingleton.commandHandler.post(new ShowTextInputTask(input_type, x, y, w, h)); - } - - public static boolean isTextInputEvent(KeyEvent event) { - - // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT - if (event.isCtrlPressed()) { - return false; - } - - return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; - } - - public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) { - int deviceId = event.getDeviceId(); - int source = event.getSource(); - - if (source == InputDevice.SOURCE_UNKNOWN) { - InputDevice device = InputDevice.getDevice(deviceId); - if (device != null) { - source = device.getSources(); - } - } - -// if (event.getAction() == KeyEvent.ACTION_DOWN) { -// Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); -// } else if (event.getAction() == KeyEvent.ACTION_UP) { -// Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); -// } - - // Dispatch the different events depending on where they come from - // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD - // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD - // - // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and - // SOURCE_JOYSTICK, while its key events arrive from the keyboard source - // So, retrieve the device itself and check all of its sources - if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { - // Note that we process events with specific key codes here - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLControllerManager.onNativePadDown(deviceId, keyCode, event.getScanCode())) { - return true; - } - } else if (event.getAction() == KeyEvent.ACTION_UP) { - if (SDLControllerManager.onNativePadUp(deviceId, keyCode, event.getScanCode())) { - return true; - } - } - } - - if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { - if (SDLActivity.isVRHeadset()) { - // The Oculus Quest controller back button comes in as source mouse, so accept that - } else { - // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses - // they are ignored here because sending them as mouse input to SDL is messy - if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { - switch (event.getAction()) { - case KeyEvent.ACTION_DOWN: - case KeyEvent.ACTION_UP: - // mark the event as handled or it will be handled by system - // handling KEYCODE_BACK by system will call onBackPressed() - return true; - } - } - } - } - - if (event.getAction() == KeyEvent.ACTION_DOWN) { - onNativeKeyDown(keyCode); - - if (isTextInputEvent(event)) { - if (ic != null) { - ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); - } else { - SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); - } - } - return true; - } else if (event.getAction() == KeyEvent.ACTION_UP) { - onNativeKeyUp(keyCode); - return true; - } - - return false; - } - - /** - * This method is called by SDL using JNI. - */ - public static Surface getNativeSurface() { - if (SDLActivity.mSurface == null) { - return null; - } - return SDLActivity.mSurface.getNativeSurface(); - } - - // Input - - /** - * This method is called by SDL using JNI. - */ - public static void initTouch() { - int[] ids = InputDevice.getDeviceIds(); - - for (int id : ids) { - InputDevice device = InputDevice.getDevice(id); - /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */ - if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN - || device.isVirtual())) { - - nativeAddTouch(device.getId(), device.getName()); - } - } - } - - // Messagebox - - /** Result of current messagebox. Also used for blocking the calling thread. */ - protected final int[] messageboxSelection = new int[1]; - - /** - * This method is called by SDL using JNI. - * Shows the messagebox from UI thread and block calling thread. - * buttonFlags, buttonIds and buttonTexts must have same length. - * @param buttonFlags array containing flags for every button. - * @param buttonIds array containing id for every button. - * @param buttonTexts array containing text for every button. - * @param colors null for default or array of length 5 containing colors. - * @return button id or -1. - */ - public int messageboxShowMessageBox( - final int flags, - final String title, - final String message, - final int[] buttonFlags, - final int[] buttonIds, - final String[] buttonTexts, - final int[] colors) { - - messageboxSelection[0] = -1; - - // sanity checks - - if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { - return -1; // implementation broken - } - - // collect arguments for Dialog - - final Bundle args = new Bundle(); - args.putInt("flags", flags); - args.putString("title", title); - args.putString("message", message); - args.putIntArray("buttonFlags", buttonFlags); - args.putIntArray("buttonIds", buttonIds); - args.putStringArray("buttonTexts", buttonTexts); - args.putIntArray("colors", colors); - - // trigger Dialog creation on UI thread - - runOnUiThread(new Runnable() { - @Override - public void run() { - messageboxCreateAndShow(args); - } - }); - - // block the calling thread - - synchronized (messageboxSelection) { - try { - messageboxSelection.wait(); - } catch (InterruptedException ex) { - ex.printStackTrace(); - return -1; - } - } - - // return selected value - - return messageboxSelection[0]; - } - - protected void messageboxCreateAndShow(Bundle args) { - - // TODO set values from "flags" to messagebox dialog - - // get colors - - int[] colors = args.getIntArray("colors"); - int backgroundColor; - int textColor; - int buttonBorderColor; - int buttonBackgroundColor; - int buttonSelectedColor; - if (colors != null) { - int i = -1; - backgroundColor = colors[++i]; - textColor = colors[++i]; - buttonBorderColor = colors[++i]; - buttonBackgroundColor = colors[++i]; - buttonSelectedColor = colors[++i]; - } else { - backgroundColor = Color.TRANSPARENT; - textColor = Color.TRANSPARENT; - buttonBorderColor = Color.TRANSPARENT; - buttonBackgroundColor = Color.TRANSPARENT; - buttonSelectedColor = Color.TRANSPARENT; - } - - // create dialog with title and a listener to wake up calling thread - - final AlertDialog dialog = new AlertDialog.Builder(this).create(); - dialog.setTitle(args.getString("title")); - dialog.setCancelable(false); - dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface unused) { - synchronized (messageboxSelection) { - messageboxSelection.notify(); - } - } - }); - - // create text - - TextView message = new TextView(this); - message.setGravity(Gravity.CENTER); - message.setText(args.getString("message")); - if (textColor != Color.TRANSPARENT) { - message.setTextColor(textColor); - } - - // create buttons - - int[] buttonFlags = args.getIntArray("buttonFlags"); - int[] buttonIds = args.getIntArray("buttonIds"); - String[] buttonTexts = args.getStringArray("buttonTexts"); - - final SparseArray