Compare commits

...

15 Commits

Author SHA1 Message Date
TakaRikka 5abc956448 daTbox2 handling 2026-08-11 14:45:53 -07:00
TakaRikka 0c3bbbaba0 standardize some check names 2026-08-10 12:35:08 -07:00
TakaRikka 71b2eedc82 initial item service implementation 2026-08-09 16:40:26 -07:00
Giorgio Mendieta 7434a0f834 Enable color diagnostics for CMake (#2273) 2026-08-08 16:14:52 -06:00
TakaRikka 82e2fd4bea Merge pull request #2283 from encounter/camera-operator
Add camera operator API to CameraService
2026-08-07 03:44:59 -07:00
TakaRikka fcd9dd0f70 Merge pull request #2282 from encounter/mods-data-dir
Add HostService.data_dir
2026-08-07 03:42:53 -07:00
TakaRikka 8afe479d0f Merge pull request #2281 from encounter/hook-uninstall
Add mods::hook::uninstall
2026-08-07 03:39:24 -07:00
Luke Street c5933be631 Add camera operator API to CameraService 2026-08-07 00:34:57 -06:00
Luke Street 928e9b4bc7 Add HostService.data_dir 2026-08-07 00:20:09 -06:00
Luke Street 3ac0fcb156 Add mods::hook::uninstall 2026-08-06 23:59:26 -06:00
TakaRikka 2c7ab755a0 Merge pull request #2276 from encounter/align
Fix ATTRIBUTE_ALIGN placement
2026-08-06 08:58:15 -07:00
Luke Street cf8ab52f55 Fix ATTRIBUTE_ALIGN placement 2026-08-05 21:16:06 -06:00
Luke Street 13b3b68fe5 Migrate to Borealis (#2266) 2026-08-05 07:54:31 -06:00
TakaRikka 4604304305 mods: stage service (#2268)
* initial stage service ported from encounter's original impl

* Review cleanup

* initial stage service ported from encounter's original impl

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-08-04 21:51:52 -06:00
Pwootage 794bb130e2 Fix for Linux distributions that use lib64: force libturbojpeg to use 'lib' (#2269) 2026-08-04 21:49:33 -06:00
230 changed files with 3207 additions and 14433 deletions
+4 -4
View File
@@ -76,7 +76,7 @@ jobs:
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: dusklight-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}} name: dusklight-${{env.APP_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
path: | path: |
build/install/Dusklight-*.AppImage build/install/Dusklight-*.AppImage
build/install/debug.tar.* build/install/debug.tar.*
@@ -145,7 +145,7 @@ jobs:
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: dusklight-${{env.DUSK_VERSION}}-${{matrix.artifact_name}} name: dusklight-${{env.APP_VERSION}}-${{matrix.artifact_name}}
path: | path: |
build/install/Dusklight.app build/install/Dusklight.app
build/install/debug.tar.* build/install/debug.tar.*
@@ -221,7 +221,7 @@ jobs:
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: dusklight-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}} name: dusklight-${{env.APP_VERSION}}-android-${{matrix.artifact_arch}}
path: upload/ path: upload/
build-windows: build-windows:
@@ -283,7 +283,7 @@ jobs:
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: dusklight-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}} name: dusklight-${{env.APP_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
path: | path: |
build/install/*.exe build/install/*.exe
build/install/*.dll build/install/*.dll
+3
View File
@@ -1,3 +1,6 @@
[submodule "extern/aurora"] [submodule "extern/aurora"]
path = extern/aurora path = extern/aurora
url = https://github.com/encounter/aurora.git url = https://github.com/encounter/aurora.git
[submodule "extern/borealis"]
path = extern/borealis
url = https://github.com/encounter/borealis.git
+14 -127
View File
@@ -5,10 +5,11 @@ if (NOT CMAKE_BUILD_TYPE)
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE) "Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
endif () endif ()
include(cmake/DetectVersion.cmake) include(extern/borealis/cmake/DetectVersion.cmake)
detect_version() borealis_detect_version()
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") 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) if (APPLE)
enable_language(OBJC OBJCXX) enable_language(OBJC OBJCXX)
endif () endif ()
@@ -32,6 +33,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_COLOR_DIAGNOSTICS ON)
# Folder-based instead of target-based organization # Folder-based instead of target-based organization
# in Visual Studio and Xcode generators # 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) add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
target_compile_definitions(aurora_mtx PRIVATE MTX_USE_PS=1) target_compile_definitions(aurora_mtx PRIVATE MTX_USE_PS=1)
add_subdirectory(extern/borealis EXCLUDE_FROM_ALL)
add_subdirectory(libs/freeverb) add_subdirectory(libs/freeverb)
if (CMAKE_BUILD_TYPE STREQUAL "Debug") if (CMAKE_BUILD_TYPE STREQUAL "Debug")
@@ -85,8 +89,6 @@ endif ()
option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)") 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_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_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_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_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) option(DUSK_ENABLE_CODE_MODS "Enable code mods" OFF)
@@ -150,6 +152,7 @@ if (DUSK_MOVIE_SUPPORT)
-DENABLE_SHARED=OFF -DENABLE_SHARED=OFF
-DWITH_TURBOJPEG=ON -DWITH_TURBOJPEG=ON
-DWITH_JAVA=OFF -DWITH_JAVA=OFF
-DCMAKE_INSTALL_LIBDIR=lib
) )
if (CMAKE_TOOLCHAIN_FILE) if (CMAKE_TOOLCHAIN_FILE)
get_filename_component(_jpeg_toolchain_file "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") get_filename_component(_jpeg_toolchain_file "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}")
@@ -234,18 +237,6 @@ endif ()
include(FetchContent) include(FetchContent)
# Declare all dependencies first so CMake can download them in parallel # 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") message(STATUS "dusklight: Fetching miniz")
FetchContent_Declare(miniz FetchContent_Declare(miniz
URL https://github.com/richgel999/miniz/releases/download/3.0.2/miniz-3.0.2.zip URL https://github.com/richgel999/miniz/releases/download/3.0.2/miniz-3.0.2.zip
@@ -253,7 +244,7 @@ FetchContent_Declare(miniz
EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL
) )
set(_fetch_content_deps cxxopts json miniz) set(_fetch_content_deps miniz)
if (DUSK_HAS_FUNCHOOK) if (DUSK_HAS_FUNCHOOK)
message(STATUS "dusklight: Fetching funchook") message(STATUS "dusklight: Fetching funchook")
# cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a # cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a
@@ -288,40 +279,12 @@ if (DUSK_HAS_FUNCHOOK)
endif () endif ()
FetchContent_MakeAvailable(${_fetch_content_deps}) 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) # Use signed char on ARM to match the original game (and x86)
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _arch) string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _arch)
if(_arch MATCHES "^(arm|aarch64)" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") if(_arch MATCHES "^(arm|aarch64)" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
add_compile_options(-fsigned-char) add_compile_options(-fsigned-char)
endif() endif()
configure_version_header()
include(files.cmake) include(files.cmake)
# TODO: version handling for res includes # TODO: version handling for res includes
@@ -334,67 +297,23 @@ set(DUSK_PRODUCT_NAME "Dusklight")
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors") set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES}) source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
source_group("dusklight" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES}) source_group("dusklight" FILES ${DUSK_FILES})
include(cmake/GameABIConfig.cmake) include(cmake/GameABIConfig.cmake)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
set(GAME_COMPILE_DEFS DUSK_BUILDING_GAME=1) 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 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) Threads::Threads zstd::libzstd dusklight_game_headers)
if (DUSK_HAS_FUNCHOOK) if (DUSK_HAS_FUNCHOOK)
list(APPEND GAME_LIBS funchook-static) list(APPEND GAME_LIBS funchook-static)
endif () 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) if (WIN32)
list(APPEND GAME_LIBS Ws2_32) 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 () 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 (DUSK_MOVIE_SUPPORT)
if (TARGET libjpeg-turbo::turbojpeg-static) if (TARGET libjpeg-turbo::turbojpeg-static)
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
@@ -404,15 +323,6 @@ if (DUSK_MOVIE_SUPPORT)
list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1)
endif () 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) if (DUSK_ENABLE_CODE_MODS)
list(APPEND GAME_COMPILE_DEFS DUSK_CODE_MODS=1) list(APPEND GAME_COMPILE_DEFS DUSK_CODE_MODS=1)
endif () endif ()
@@ -477,10 +387,10 @@ endif ()
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES} ${miniz_SOURCE_DIR}/miniz.c) set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES} ${miniz_SOURCE_DIR}/miniz.c)
if(ANDROID) if(ANDROID)
add_library(dusklight SHARED ${DUSK_FILES}) add_library(dusklight SHARED ${DUSK_FILES})
set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
else () else ()
add_executable(dusklight ${DUSK_FILES}) add_executable(dusklight ${DUSK_FILES})
endif () endif ()
borealis_configure_android_application(dusklight)
if (ENABLE_ASAN) if (ENABLE_ASAN)
target_sources(dusklight PRIVATE src/dusk/asan_options.c) target_sources(dusklight PRIVATE src/dusk/asan_options.c)
endif () endif ()
@@ -558,17 +468,9 @@ if (TARGET crashpad_handler)
) )
endif () 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) if (CMAKE_SYSTEM_NAME STREQUAL Linux)
target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1") target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1")
target_link_libraries(dusklight PRIVATE dl) target_link_libraries(dusklight PRIVATE dl)
elseif (ANDROID)
target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1")
endif () endif ()
if (NOT APPLE) if (NOT APPLE)
@@ -650,8 +552,8 @@ if (APPLE)
MACOSX_BUNDLE TRUE MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME} MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER} MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING} MACOSX_BUNDLE_BUNDLE_VERSION ${BOREALIS_APP_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING} MACOSX_BUNDLE_SHORT_VERSION_STRING ${BOREALIS_APP_SHORT_VERSION}
MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST} MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}
OUTPUT_NAME Dusklight OUTPUT_NAME Dusklight
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
@@ -684,21 +586,6 @@ if (APPLE)
endif () endif ()
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) include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
aurora_copy_runtime_dlls(dusklight) aurora_copy_runtime_dlls(dusklight)
+4 -4
View File
@@ -28,12 +28,12 @@
"cacheVariables": { "cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_C_COMPILER_LAUNCHER": "sccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
"DUSK_ENABLE_SENTRY_NATIVE": { "BOREALIS_ENABLE_SENTRY": {
"type": "BOOL", "type": "BOOL",
"value": true "value": true
}, },
"DUSK_SENTRY_DSN": "$env{SENTRY_DSN}", "BOREALIS_SENTRY_DSN": "$env{SENTRY_DSN}",
"DUSK_SENTRY_ENVIRONMENT": "production", "BOREALIS_SENTRY_ENVIRONMENT": "production",
"Rust_RUSTUP_INSTALL_MISSING_TARGET": { "Rust_RUSTUP_INSTALL_MISSING_TARGET": {
"type": "BOOL", "type": "BOOL",
"value": true "value": true
@@ -448,7 +448,7 @@
"ci" "ci"
], ],
"cacheVariables": { "cacheVariables": {
"DUSK_ENABLE_SENTRY_NATIVE": { "BOREALIS_ENABLE_SENTRY": {
"type": "BOOL", "type": "BOOL",
"value": false "value": false
} }
+1 -1
View File
@@ -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 cp platforms/freedesktop/dev.twilitrealm.dusk.desktop build/appdir/usr/share/applications
cd build/install 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 -l "$lib_dir/libusb-1.0.so" --appdir "$build_dir/appdir" --output appimage
-121
View File
@@ -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()
+68 -8
View File
@@ -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 Missing files return `MOD_UNAVAILABLE`. Always `free` what you `load`. The bundle is read-only; use
storage, use the directory from `svc_host->mod_dir(mod_ctx)`. `HostService::data_dir` for persistent storage.
### HostService (`mods/svc/host.h`) ### HostService (`mods/svc/host.h`)
@@ -219,9 +219,17 @@ Mod metadata and runtime interaction with the loader:
```cpp ```cpp
IMPORT_SERVICE(HostService, svc_host); IMPORT_SERVICE(HostService, svc_host);
const char* id = svc_host->mod_id(mod_ctx); // Temporary mod data directory, wiped on startup
const char* dir = svc_host->mod_dir(mod_ctx); // writable per-mod directory const char* cacheDir = svc_host->mod_dir(mod_ctx);
svc_host->fail(mod_ctx, MOD_ERROR, "something unrecoverable happened"); // disables the mod
// 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). `get_service`/`publish_service` provide dynamic service lookup; see [Exporting Services](#exporting-services).
@@ -373,6 +381,55 @@ buffer contract as `get_blob`. Pass a `NULL` buffer to either read function to q
are cleared. Observers are removed automatically when the mod is detached, so the output handle is only needed for 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. 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`) ### 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, Integrate seamlessly with Dusklight's UI system: add controls and buttons to your mod's detail pane in the Mods window,
@@ -576,6 +633,9 @@ 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 first in-game frame. Projection matrices match the renderer's WebGPU clip convention and renderer depth convention
(reversed-Z by default). (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`.
--- ---
## Hooking Game Functions ## Hooking Game Functions
@@ -878,6 +938,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 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 `mod_shutdown`. Do not write into `native_dir`; use `data_dir` for persistent storage or `mod_dir` for temporary
process-wide on some platforms, so two mods cannot safely assume that incompatible libraries with the same filename (session) storage. Native library namespaces are process-wide on some platforms, so two mods cannot safely assume that
will remain isolated. incompatible libraries with the same filename will remain isolated.
+1 -1
Vendored Submodule
+1
Submodule extern/borealis added at 6fd955e7e6
+9 -18
View File
@@ -1421,30 +1421,21 @@ set(DUSK_FILES
src/dusk/achievements.cpp src/dusk/achievements.cpp
src/dusk/action_bindings.cpp src/dusk/action_bindings.cpp
src/dusk/action_bindings.h src/dusk/action_bindings.h
src/dusk/android_frame_rate.cpp
src/dusk/android_frame_rate.hpp
src/dusk/asserts.cpp src/dusk/asserts.cpp
src/dusk/autosave.cpp src/dusk/autosave.cpp
src/dusk/config.cpp src/dusk/config.cpp
src/dusk/config.hpp src/dusk/config.hpp
src/dusk/crash_handler.cpp
src/dusk/crash_reporting.cpp
src/dusk/data.cpp src/dusk/data.cpp
src/dusk/data.hpp src/dusk/data.hpp
src/dusk/discord.cpp
src/dusk/discord.hpp
src/dusk/discord_presence.cpp src/dusk/discord_presence.cpp
src/dusk/dvd_asset.cpp src/dusk/dvd_asset.cpp
src/dusk/dvd_asset.hpp src/dusk/dvd_asset.hpp
src/dusk/extras.c src/dusk/extras.c
src/dusk/file_select.cpp
src/dusk/file_select.hpp
src/dusk/frame_interpolation.cpp src/dusk/frame_interpolation.cpp
src/dusk/game_clock.cpp src/dusk/game_clock.cpp
src/dusk/gamepad_color.cpp src/dusk/gamepad_color.cpp
src/dusk/globals.cpp src/dusk/globals.cpp
src/dusk/gyro.cpp src/dusk/gyro.cpp
src/dusk/http/http.hpp
src/dusk/imgui/ImGuiActorSpawner.cpp src/dusk/imgui/ImGuiActorSpawner.cpp
src/dusk/imgui/ImGuiBloomWindow.cpp src/dusk/imgui/ImGuiBloomWindow.cpp
src/dusk/imgui/ImGuiBloomWindow.hpp src/dusk/imgui/ImGuiBloomWindow.hpp
@@ -1481,6 +1472,9 @@ set(DUSK_FILES
src/dusk/mods/loader/native_module.hpp src/dusk/mods/loader/native_module.hpp
src/dusk/mods/loader/prepatch.cpp src/dusk/mods/loader/prepatch.cpp
src/dusk/mods/loader/prepatch.hpp 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.cpp
src/dusk/mods/log_buffer.hpp src/dusk/mods/log_buffer.hpp
src/dusk/mods/manifest.cpp src/dusk/mods/manifest.cpp
@@ -1492,6 +1486,8 @@ set(DUSK_FILES
src/dusk/mods/svc/gfx.cpp src/dusk/mods/svc/gfx.cpp
src/dusk/mods/svc/hook.cpp src/dusk/mods/svc/hook.cpp
src/dusk/mods/svc/host.cpp src/dusk/mods/svc/host.cpp
src/dusk/mods/svc/item.cpp
src/dusk/mods/svc/item.hpp
src/dusk/mods/svc/log.cpp src/dusk/mods/svc/log.cpp
src/dusk/mods/svc/overlay.cpp src/dusk/mods/svc/overlay.cpp
src/dusk/mods/svc/registry.cpp src/dusk/mods/svc/registry.cpp
@@ -1504,7 +1500,11 @@ set(DUSK_FILES
src/dusk/mods/svc/window.hpp src/dusk/mods/svc/window.hpp
src/dusk/mods/svc/save.cpp src/dusk/mods/svc/save.cpp
src/dusk/mods/svc/save.hpp src/dusk/mods/svc/save.hpp
src/dusk/mods/svc/stage.cpp
src/dusk/mods/svc/stage.hpp
src/dusk/mouse.cpp src/dusk/mouse.cpp
src/dusk/presentation.cpp
src/dusk/presentation.hpp
src/dusk/scope_guard.hpp src/dusk/scope_guard.hpp
src/dusk/settings.cpp src/dusk/settings.cpp
src/dusk/speedrun.cpp src/dusk/speedrun.cpp
@@ -1580,8 +1580,6 @@ set(DUSK_FILES
src/dusk/ui/warp.hpp src/dusk/ui/warp.hpp
src/dusk/ui/window.cpp src/dusk/ui/window.cpp
src/dusk/ui/window.hpp src/dusk/ui/window.hpp
src/dusk/update_check.cpp
src/dusk/update_check.hpp
src/dusk/version.cpp src/dusk/version.cpp
src/dusk/utilities.cpp src/dusk/utilities.cpp
src/helpers/batch.cpp src/helpers/batch.cpp
@@ -1589,10 +1587,3 @@ set(DUSK_FILES
src/helpers/offset_ptr.cpp src/helpers/offset_ptr.cpp
src/helpers/string.cpp src/helpers/string.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
)
+1 -1
View File
@@ -228,7 +228,7 @@
ninjaFlags = [ "dusklight" ]; ninjaFlags = [ "dusklight" ];
cmakeFlags = [ cmakeFlags = [
"-DDUSK_VERSION_OVERRIDE=${versionSuffix}" "-DBOREALIS_APP_VERSION_OVERRIDE=${versionSuffix}"
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON" "-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
"-DAURORA_DAWN_PROVIDER=package" "-DAURORA_DAWN_PROVIDER=package"
"-DAURORA_DAWN_LINKAGE=static" "-DAURORA_DAWN_LINKAGE=static"
+3
View File
@@ -96,6 +96,9 @@ public:
/* 0xDE0 */ cXyz field_0xde0; /* 0xDE0 */ cXyz field_0xde0;
/* 0xDEC */ u8 field_0xdec[0xdf9 - 0xdec]; /* 0xDEC */ u8 field_0xdec[0xdf9 - 0xdec];
/* 0xDF9 */ u8 field_0xdf9; /* 0xDF9 */ u8 field_0xdf9;
#if TARGET_PC
bool mItemCheckOverridden;
#endif
}; };
STATIC_ASSERT(sizeof(daE_HP_c) == 0xdfc); STATIC_ASSERT(sizeof(daE_HP_c) == 0xdfc);
+5
View File
@@ -88,6 +88,11 @@ private:
/* 0x99C */ dPa_followEcallBack mEffect2; /* 0x99C */ dPa_followEcallBack mEffect2;
/* 0x9B0 */ Z2SoundObjSimple mSound; /* 0x9B0 */ Z2SoundObjSimple mSound;
/* 0x9D0 */ u8 mIsHookCarry; /* 0x9D0 */ u8 mIsHookCarry;
#if TARGET_PC
u8 mOriginalItemNo;
bool mItemOverridden;
bool mOverrideHover;
#endif
}; };
STATIC_ASSERT(sizeof(daObjLife_c) == 0x9d4); STATIC_ASSERT(sizeof(daObjLife_c) == 0x9d4);
+3
View File
@@ -194,6 +194,9 @@ private:
/* 0x9FC */ u8 field_0x9fc; /* 0x9FC */ u8 field_0x9fc;
/* 0x9FD */ u8 field_0x9fd; /* 0x9FD */ u8 field_0x9fd;
/* 0xA00 */ Mtx mDrawMtx; /* 0xA00 */ Mtx mDrawMtx;
#if TARGET_PC
u8 mOriginalItemNo;
#endif
}; };
STATIC_ASSERT(sizeof(daTbox_c) == 0xA30); STATIC_ASSERT(sizeof(daTbox_c) == 0xA30);
+3
View File
@@ -83,6 +83,9 @@ private:
/* 0xAC0 */ u8 field_0xAC0[0xAC4 - 0xAC0]; /* 0xAC0 */ u8 field_0xAC0[0xAC4 - 0xAC0];
/* 0xAC4 */ int mStaffIdx; /* 0xAC4 */ int mStaffIdx;
/* 0xAC8 */ dBgW* mBoxBgW; /* 0xAC8 */ dBgW* mBoxBgW;
#if TARGET_PC
u8 mOriginalItemNo;
#endif
}; };
STATIC_ASSERT(sizeof(daTbox2_c) == 0xACC); STATIC_ASSERT(sizeof(daTbox2_c) == 0xACC);
+4
View File
@@ -145,6 +145,10 @@ public:
/* 0x9BC */ u8 field_0x9bc[4]; /* 0x9BC */ u8 field_0x9bc[4];
/* 0x9C0 */ u8 field_0x9c0; /* 0x9C0 */ u8 field_0x9c0;
/* 0x9C1 */ u8 field_0x9c1; /* 0x9C1 */ u8 field_0x9c1;
#if TARGET_PC
u8 mOriginalItemNo;
bool mItemOverridden;
#endif
}; // Size: 0x9C4 }; // Size: 0x9C4
#endif /* D_A_D_A_ITEM_STATIC_H */ #endif /* D_A_D_A_ITEM_STATIC_H */
+7
View File
@@ -50,6 +50,9 @@ public:
csXyz* getRotateP(); csXyz* getRotateP();
cXyz* getPosP(); cXyz* getPosP();
const char* getShopArcname(); const char* getShopArcname();
#if TARGET_PC
const ResourceData& getResourceData() const;
#endif
u16 getHeapSize(); u16 getHeapSize();
void CreateInit(); void CreateInit();
void set_mtx(); void set_mtx();
@@ -110,6 +113,10 @@ private:
/* 0x960 */ s16 mAngleX; /* 0x960 */ s16 mAngleX;
/* 0x962 */ s16 mAngleY; /* 0x962 */ s16 mAngleY;
/* 0x964 */ u8 mShopItemID; /* 0x964 */ u8 mShopItemID;
#if TARGET_PC
ResourceData mOverrideData;
bool mItemOverridden;
#endif
}; };
int CheckShopItemCreateHeap(fopAc_ac_c* i_this); int CheckShopItemCreateHeap(fopAc_ac_c* i_this);
+3 -1
View File
@@ -11,7 +11,9 @@ public:
static DUSK_GAME_DATA u8* mData; 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_HEART();
void item_func_GREEN_RUPEE(); void item_func_GREEN_RUPEE();
+5
View File
@@ -318,6 +318,11 @@ public:
/* 0x566 */ s8 field_0x566; /* 0x566 */ s8 field_0x566;
/* 0x567 */ s8 field_0x567; /* 0x567 */ s8 field_0x567;
#if TARGET_PC
u32 mItemGiveTag;
u8 mItemGiveOriginalNo;
#endif
#if !__MWERKS__ #if !__MWERKS__
s8 actor_last_base_field; s8 actor_last_base_field;
#endif #endif
+20 -13
View File
@@ -79,6 +79,10 @@ struct fopAcM_prm_class {
/* 0x1C */ fpc_ProcID parent_id; /* 0x1C */ fpc_ProcID parent_id;
/* 0x20 */ s8 argument; /* 0x20 */ s8 argument;
/* 0x21 */ s8 room_no; /* 0x21 */ s8 room_no;
#if TARGET_PC
u32 mItemGiveTag;
u8 mItemGiveOriginalNo;
#endif
}; };
struct fopAcM_search4ev_prm { 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* fopAcM_CreateAppend();
fopAcM_prm_class* createAppend(u16 i_setId, u32 i_parameters, const cXyz* i_pos, int i_roomNo, 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, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
fpc_ProcID i_parentId); 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); void fopAcM_Log(fopAc_ac_c const* i_actor, char const* i_message);
@@ -526,19 +531,20 @@ s32 fopAcM_delete(fopAc_ac_c* i_actor);
s32 fopAcM_delete(fpc_ProcID i_actorID); s32 fopAcM_delete(fpc_ProcID i_actorID);
fpc_ProcID fopAcM_create(s16 i_procName, u16 i_setId, u32 i_parameters, const cXyz* i_pos, 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, int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
createFunc i_createFunc); createFunc i_createFunc IF_DUSK_ARG(u32 i_itemGiveTag = 0));
fpc_ProcID fopAcM_create(s16 i_procName, u32 i_parameters, const cXyz* i_pos, int i_roomNo, 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));
inline fpc_ProcID fopAcM_Create(s16 i_procName, createFunc i_createFunc, void* params) { inline fpc_ProcID fopAcM_Create(s16 i_procName, createFunc i_createFunc, void* params) {
return fpcM_Create(i_procName, i_createFunc,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, 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, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, createFunc i_createFunc,
createFunc i_createFunc, void* i_createFuncData); 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, 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, int i_roomNo, const csXyz* i_angle, const cXyz* i_scale,
@@ -623,11 +629,11 @@ fopAc_ac_c* fopAcM_getItemEventPartner(const fopAc_ac_c*);
fopAc_ac_c* fopAcM_getEventPartner(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, fpc_ProcID fopAcM_createItemForPresentDemo(cXyz const* i_pos, int i_itemNo, u8 param_2,
int i_itemBitNo, int i_roomNo, csXyz const* i_angle, int i_itemBitNo, int i_roomNo, csXyz const* i_angle,
cXyz const* i_scale); 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, 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); u8 fopAcM_getItemNoFromTableNo(u8 i_tableNo);
@@ -641,11 +647,12 @@ fpc_ProcID fopAcM_createItemFromTable(cXyz const* i_pos, int i_tableNo, int i_it
bool i_createDirect); bool i_createDirect);
fpc_ProcID fopAcM_createDemoItem(const cXyz* i_pos, int i_itemNo, int i_itemBitNo, 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, 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, const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF, f32 i_speedY,
f32 i_speedY, int param_8); 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, 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, const csXyz* i_angle, const cXyz* i_scale, int param_6,
+13
View File
@@ -256,4 +256,17 @@ using std::isnan;
#define DUSK_CONST IF_DUSK(const) #define DUSK_CONST IF_DUSK(const)
#define DUSK_CONSTEXPR IF_DUSK(constexpr) #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(name, (item_no), giver)
#define DUSK_ITEM_CHECK_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_GIVE_TAG(name)
#endif
#endif #endif
+2 -2
View File
@@ -130,8 +130,8 @@ private:
}; // Size = 0x28 }; // Size = 0x28
struct mDoDvdThdStack { struct mDoDvdThdStack {
u8 stack[4096]; ATTRIBUTE_ALIGN(16) u8 stack[4096];
} ATTRIBUTE_ALIGN(16); };
struct mDoDvdThd { struct mDoDvdThd {
static s32 main(void*); static s32 main(void*);
+2 -2
View File
@@ -23,9 +23,9 @@ DUSK_GAME_DATA Vec J3DSys::mParentS;
DUSK_GAME_DATA J3DTexCoordScaleInfo J3DSys::sTexCoordScaleTable[8]; DUSK_GAME_DATA J3DTexCoordScaleInfo J3DSys::sTexCoordScaleTable[8];
#if TARGET_PC // Original game bug, array is too small. #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 #else
static u8 NullTexData[0x10] ATTRIBUTE_ALIGN(32) = {0}; ATTRIBUTE_ALIGN(32) static u8 NullTexData[0x10] = {0};
#endif #endif
static Mtx j3dIdentityMtx = { static Mtx j3dIdentityMtx = {
+2 -2
View File
@@ -99,14 +99,14 @@ void JASDsp::invalChannelAll() {
DCInvalidateRange(CH_BUF, sizeof(TChannel) * DSP_CHANNELS); 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, 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, 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, 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, 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, 0x0C3966AD,
0x0D46FFDF, 0x0D46FFDF,
0x0B396696, 0x0B396696,
+3 -3
View File
@@ -25,7 +25,7 @@ void DspHandShake(void*) {
Dsp_Running_Start(); 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, 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, 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, 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, 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*)) { void DspBoot(void (*requestCallback)(void*)) {
DspInitWork(); DspInitWork();
+2 -2
View File
@@ -452,13 +452,13 @@ static void dummy() {
JUTXfb::getManager()->setDisplayingXfbIndex(0); 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}, {1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 1.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,
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, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+3 -3
View File
@@ -16,10 +16,10 @@ inline f64 getConst2() {
return 9.765625E-4; return 9.765625E-4;
} }
DUSK_GAME_DATA TSinCosTable<13, f32> sincosTable_ ATTRIBUTE_ALIGN(32); 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 } // namespace JMath
+2 -2
View File
@@ -46,7 +46,7 @@ JPAResource::JPAResource() {
mUsrIdx = fldNum = keyNum = texNum = mpCalcEmitterFuncListNum = mpDrawEmitterFuncListNum = mpDrawEmitterChildFuncListNum = mpCalcParticleFuncListNum = mpDrawParticleFuncListNum = mpCalcParticleChildFuncListNum = mpDrawParticleChildFuncListNum = 0; 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, 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, 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, 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, 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, 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, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x02,
}; };
@@ -8,7 +8,7 @@
#endif #endif
#include "global.h" #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, 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, 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, 0x49, 0x4E, 0x46, 0x31, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C,
@@ -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) { 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; u32 err;
__REGISTER int count = c; __REGISTER int count = c;
__REGISTER u32 bf; __REGISTER u32 bf;
@@ -85,7 +85,7 @@ DSError TRKDoSupportMask(TRKBuffer*) {
} }
DSError TRKDoReadMemory(TRKBuffer* buffer) { DSError TRKDoReadMemory(TRKBuffer* buffer) {
u8 buf[0x820] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 buf[0x820];
size_t tempLength; size_t tempLength;
int result; int result;
int replyErr; int replyErr;
@@ -158,7 +158,7 @@ DSError TRKDoReadMemory(TRKBuffer* buffer) {
} }
DSError TRKDoWriteMemory(TRKBuffer* b) { DSError TRKDoWriteMemory(TRKBuffer* b) {
u8 buf[0x820] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 buf[0x820];
size_t tempLength; size_t tempLength;
int options; int options;
int result; int result;
+2 -2
View File
@@ -3,8 +3,8 @@
#include "__ax.h" #include "__ax.h"
static s32 __AXBufferAuxA[3][480] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static s32 __AXBufferAuxA[3][480];
static s32 __AXBufferAuxB[3][480] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static s32 __AXBufferAuxB[3][480];
static void (* __AXCallbackAuxA)(void*, void*); static void (* __AXCallbackAuxA)(void*, void*);
static void (* __AXCallbackAuxB)(void*, void*); static void (* __AXCallbackAuxB)(void*, void*);
+1 -1
View File
@@ -3,7 +3,7 @@
#include "__ax.h" #include "__ax.h"
static AXSPB __AXStudio ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static AXSPB __AXStudio;
static s32 __AXSpbAL; static s32 __AXSpbAL;
static s32 __AXSpbAR; static s32 __AXSpbAR;
+3 -3
View File
@@ -35,9 +35,9 @@ static u32 __AXAuxMixCycles[32] = {
0x000009E2, 0x00000E97 0x000009E2, 0x00000E97
}; };
static AXPB __AXPB[AX_MAX_VOICES] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static AXPB __AXPB[AX_MAX_VOICES];
static AXPBITDBUFFER __AXITD[AX_MAX_VOICES] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static AXPBITDBUFFER __AXITD[AX_MAX_VOICES];
static AXPBU __AXUpdates[AX_MAX_VOICES] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static AXPBU __AXUpdates[AX_MAX_VOICES];
static AXVPB __AXVPB[AX_MAX_VOICES]; static AXVPB __AXVPB[AX_MAX_VOICES];
static u32 __AXMaxDspCycles; static u32 __AXMaxDspCycles;
+1 -1
View File
@@ -3,7 +3,7 @@
u16 axDspSlaveLength = (AX_DSP_SLAVE_LENGTH * 2); 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, 0x0000, 0x0000, 0x029F, 0x0E88, 0x029F, 0x0E97,
0x029F, 0x0EB3, 0x029F, 0x0ED3, 0x029F, 0x0ED9, 0x029F, 0x0EB3, 0x029F, 0x0ED3, 0x029F, 0x0ED9,
0x029F, 0x0F0B, 0x029F, 0x0F11, 0x1302, 0x1303, 0x029F, 0x0F0B, 0x029F, 0x0F11, 0x1302, 0x1303,
+1 -1
View File
@@ -4,7 +4,7 @@
#include "__card.h" #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, 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, 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, 0x13, 0x05, 0x00, 0x92, 0x00, 0xFF, 0x00, 0x88, 0xFF, 0xFF, 0x00, 0x89, 0xFF, 0xFF, 0x00, 0x8A, 0xFF, 0xFF, 0x00,
+1 -1
View File
@@ -3,7 +3,7 @@
#include <dolphin/demo.h> #include <dolphin/demo.h>
#include "sdk_math.h" #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); static void (*__AVX_save_isr)(void);
+1 -1
View File
@@ -1,7 +1,7 @@
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include <dolphin/demo.h> #include <dolphin/demo.h>
u32 DEMOFontBitmap[768] ATTRIBUTE_ALIGN(32) = { ATTRIBUTE_ALIGN(32) u32 DEMOFontBitmap[768] = {
0x00000000, 0x00000000,
0x00000000, 0x00000000,
0x00000000, 0x00000000,
+1 -1
View File
@@ -43,7 +43,7 @@ void* __piReg;
GXBool __GXinBegin; GXBool __GXinBegin;
#endif #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,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
}; };
+5 -5
View File
@@ -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) >>"; const char* __MCCVersion = "<< Dolphin SDK - MCC\trelease build: Apr 5 2004 04:15:49 (0x2301) >>";
#endif #endif
static MCC_ChannelInfo gChannelInfo[16] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static MCC_ChannelInfo gChannelInfo[16];
static char gStreamWork[32] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static char gStreamWork[32];
static char m_szAdapterMode[32] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static char m_szAdapterMode[32];
static char m_szInitCode[32] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static char m_szInitCode[32];
static MCC_Info channelInfo[16] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static MCC_Info channelInfo[16];
volatile static BOOL gIsChannelinfoDirty = TRUE; volatile static BOOL gIsChannelinfoDirty = TRUE;
+1 -1
View File
@@ -3,7 +3,7 @@
#include "__os.h" #include "__os.h"
static SramControl Scb ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT); ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT) static SramControl Scb;
// prototypes // prototypes
static int GetRTC(u32* rtc); static int GetRTC(u32* rtc);
+1 -1
View File
@@ -3,7 +3,7 @@
#include "__card.h" #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, 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, 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, 0x13, 0x05, 0x00, 0x92, 0x00, 0xFF, 0x00, 0x88, 0xFF, 0xFF, 0x00, 0x89, 0xFF, 0xFF, 0x00, 0x8A, 0xFF, 0xFF, 0x00,
+5 -5
View File
@@ -44,7 +44,7 @@ static u32 LastError;
static BOOL ResetRequired; static BOOL ResetRequired;
static u32 MotorState; static u32 MotorState;
static volatile OSTime LastResetEnd; static volatile OSTime LastResetEnd;
static u32 __DVDNumTmdBytes ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 __DVDNumTmdBytes;
static DVDGameTOC* GameToc; static DVDGameTOC* GameToc;
static DVDPartitionInfo* PartInfo; static DVDPartitionInfo* PartInfo;
static DVDPartitionInfo* BootGameInfo; static DVDPartitionInfo* BootGameInfo;
@@ -80,10 +80,10 @@ static DVDCommandBlock DummyCommandBlock;
static OSAlarm ResetAlarm; static OSAlarm ResetAlarm;
static OSAlarm CoverAlarm; static OSAlarm CoverAlarm;
static u8 __DVDGameTocBuffer[OSRoundUp32B(sizeof(DVDGameTOC) * 4)] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u8 __DVDGameTocBuffer[OSRoundUp32B(sizeof(DVDGameTOC) * 4)];
static u8 __DVDPartInfoBuffer[OSRoundUp32B(sizeof(DVDPartitionInfo) * 4)] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u8 __DVDPartInfoBuffer[OSRoundUp32B(sizeof(DVDPartitionInfo) * 4)];
static u8 __DVDTmdBuffer[OSRoundUp32B(sizeof(ESTitleMeta))] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u8 __DVDTmdBuffer[OSRoundUp32B(sizeof(ESTitleMeta))];
static u8 __DVDTicketViewBuffer[OSRoundUp32B(sizeof(ESTicketView))] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u8 __DVDTicketViewBuffer[OSRoundUp32B(sizeof(ESTicketView))];
static OSAlarm FatalAlarm; static OSAlarm FatalAlarm;
DVDCommandBlock __DVDStopMotorCommandBlock; DVDCommandBlock __DVDStopMotorCommandBlock;
+1 -1
View File
@@ -5,7 +5,7 @@
#include "__os.h" #include "__os.h"
#include "__dvd.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 BOOL lowDone = TRUE;
static volatile u32 lowIntType = 0; static volatile u32 lowIntType = 0;
+7 -7
View File
@@ -8,15 +8,15 @@ static volatile u8 requestInProgress = FALSE;
static u8 breakRequested; static u8 breakRequested;
static u8 callbackInProgress; static u8 callbackInProgress;
static u32 registerBuf[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 registerBuf[8];
static u32 statusRegister[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 statusRegister[8];
static u32 controlRegister[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 controlRegister[8];
static s32 lastTicketError[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static s32 lastTicketError[8];
static u32 readLength; static u32 readLength;
static u32 spinUpValue; static u32 spinUpValue;
static diRegVals_t diRegValCache ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static diRegVals_t diRegValCache;
static u8 DVDLowInitCalled = FALSE; static u8 DVDLowInitCalled = FALSE;
@@ -42,8 +42,8 @@ typedef struct dvdContext {
static int freeDvdContext = 0; static int freeDvdContext = 0;
static u8 dvdContextsInited = FALSE; static u8 dvdContextsInited = FALSE;
static dvdContext_t dvdContexts[4] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static dvdContext_t dvdContexts[4];
static IOSIoVector ioVec[10] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static IOSIoVector ioVec[10];
static void* ddrAllocAligned32(const int size) { static void* ddrAllocAligned32(const int size) {
void* low, *high; void* low, *high;
+2 -2
View File
@@ -11,8 +11,8 @@ static NANDCommandBlock NandCb;
static NANDFileInfo NandInfo; static NANDFileInfo NandInfo;
static DVDCBCallback Callback; static DVDCBCallback Callback;
static u32 NextOffset; static u32 NextOffset;
DVDErrorInfo __ErrorInfo ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) DVDErrorInfo __ErrorInfo;
DVDErrorInfo __FirstErrorInfo ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) DVDErrorInfo __FirstErrorInfo;
void cbForNandClose(s32 result, NANDCommandBlock* block) { void cbForNandClose(s32 result, NANDCommandBlock* block) {
if (Callback) { if (Callback) {
+8 -8
View File
@@ -33,7 +33,7 @@ s32 ESP_CloseLib(void) {
s32 ESP_LaunchTitle(u64 titleID, ESTicketView* pTicketView) { s32 ESP_LaunchTitle(u64 titleID, ESTicketView* pTicketView) {
s32 ret = 0; s32 ret = 0;
u8 buf[256] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 buf[256];
IOSIoVector* vec = (IOSIoVector*)(buf + 208); IOSIoVector* vec = (IOSIoVector*)(buf + 208);
u64* id = (u64*)buf; u64* id = (u64*)buf;
@@ -63,7 +63,7 @@ out:
s32 ESP_GetTicketViews(ESTitleId titleId, ESTicketView* ticketViewList, u32* ticketViewCnt) { s32 ESP_GetTicketViews(ESTitleId titleId, ESTicketView* ticketViewList, u32* ticketViewCnt) {
s32 rv = 0; s32 rv = 0;
u8 __esBuf[256] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __esBuf[256];
IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector));
ESTitleId* p1 = (ESTitleId*)__esBuf; ESTitleId* p1 = (ESTitleId*)__esBuf;
u32* p2 = (u32*)(__esBuf + 32); u32* p2 = (u32*)(__esBuf + 32);
@@ -111,7 +111,7 @@ out:
s32 ESP_DiGetTicketView(const void* ticket, ESTicketView* ticketView) { s32 ESP_DiGetTicketView(const void* ticket, ESTicketView* ticketView) {
s32 rv = 0; s32 rv = 0;
u8 __esBuf[256] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __esBuf[256];
IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector));
if (__esFd < 0 || ticketView == NULL) { if (__esFd < 0 || ticketView == NULL) {
@@ -142,7 +142,7 @@ out:
s32 ESP_DiGetTmd(ESTitleMeta* tmd, u32* tmdSize) { s32 ESP_DiGetTmd(ESTitleMeta* tmd, u32* tmdSize) {
s32 rv = 0; s32 rv = 0;
u8 __esBuf[256] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __esBuf[256];
IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector));
u32* p1 = (u32*)__esBuf; u32* p1 = (u32*)__esBuf;
@@ -184,7 +184,7 @@ out:
s32 ESP_GetTmdView(ESTitleId titleId, ESTmdView* tmdView, u32* size) { s32 ESP_GetTmdView(ESTitleId titleId, ESTmdView* tmdView, u32* size) {
s32 rv = 0; s32 rv = 0;
u8 __esBuf[256] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __esBuf[256];
IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector));
ESTitleId* p1 = (ESTitleId*)__esBuf; ESTitleId* p1 = (ESTitleId*)__esBuf;
u32* p2 = (u32*)(__esBuf + 32); u32* p2 = (u32*)(__esBuf + 32);
@@ -233,7 +233,7 @@ out:
s32 ESP_GetDataDir(ESTitleId titleId, char* dataDir) { s32 ESP_GetDataDir(ESTitleId titleId, char* dataDir) {
s32 rv = 0; s32 rv = 0;
u8 __esBuf[256] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __esBuf[256];
IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector));
ESTitleId* p1 = (ESTitleId*)__esBuf; ESTitleId* p1 = (ESTitleId*)__esBuf;
@@ -260,7 +260,7 @@ out:
s32 ESP_GetTitleId(ESTitleId* titleId) { s32 ESP_GetTitleId(ESTitleId* titleId) {
s32 rv = 0; s32 rv = 0;
u8 __esBuf[256] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __esBuf[256];
IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector));
if (__esFd < 0 || titleId == NULL) { if (__esFd < 0 || titleId == NULL) {
@@ -283,7 +283,7 @@ out:
s32 ESP_GetConsumption(ESTicketId ticketId, ESLpEntry* entries, u32* nEntries) { s32 ESP_GetConsumption(ESTicketId ticketId, ESLpEntry* entries, u32* nEntries) {
s32 rv = 0; s32 rv = 0;
u8 __esBuf[256] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __esBuf[256];
IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector)); IOSIoVector* v = (IOSIoVector*)(__esBuf + 256 - 6 * sizeof(IOSIoVector));
ESTicketId* p1 = (ESTicketId*)__esBuf; ESTicketId* p1 = (ESTicketId*)__esBuf;
u32* p2 = (u32*)(__esBuf + 32); u32* p2 = (u32*)(__esBuf + 32);
+1 -1
View File
@@ -25,7 +25,7 @@ typedef struct isfs_GetUsage {
} isfs_GetUsage; } isfs_GetUsage;
typedef struct __isfsCtxt { typedef struct __isfsCtxt {
u8 ioBuf[ROUNDUP(256)] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 ioBuf[ROUNDUP(256)];
ISFSCallback cb; ISFSCallback cb;
void* ctxt; void* ctxt;
u32 func; u32 func;
+1 -1
View File
@@ -49,7 +49,7 @@ volatile void* __piReg;
GXBool __GXinBegin; GXBool __GXinBegin;
#endif #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,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
}; };
@@ -34,7 +34,7 @@ namespace nw4hbm {
private: private:
/* 0x00 (base) */ /* 0x00 (base) */
/* 0xD4 */ ut::Color mVtxColors[VERTEXCOLOR_MAX] ATTRIBUTE_ALIGN(4); /* 0xD4 */ ATTRIBUTE_ALIGN(4) ut::Color mVtxColors[VERTEXCOLOR_MAX];
/* 0xE4 */ detail::TexCoordAry mTexCoordAry; /* 0xE4 */ detail::TexCoordAry mTexCoordAry;
}; };
@@ -58,7 +58,7 @@ namespace nw4hbm {
public: public:
/* 0x14 */ ut::LinkListNode mLinkNode; /* 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<LoadCommand, offsetof(LoadCommand, mLinkNode)> LoadCommandList; typedef ut::LinkList<LoadCommand, offsetof(LoadCommand, mLinkNode)> LoadCommandList;
+2 -2
View File
@@ -24,7 +24,7 @@ static u32 __relnchFl = 0;
typedef struct IOSRpcRequest { typedef struct IOSRpcRequest {
IOSResourceRequest request; 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; void* callback_arg;
u32 relaunch_flag; u32 relaunch_flag;
OSThreadQueue thread_queue; 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)) #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 { static struct {
u32 rcount; u32 rcount;
+4 -4
View File
@@ -33,8 +33,8 @@ enum LibState {
}; };
static enum LibState s_libState = STATE_NOT_INITIALIZED; static enum LibState s_libState = STATE_NOT_INITIALIZED;
static char s_currentDir[64] ATTRIBUTE_ALIGN(32) = "/"; ATTRIBUTE_ALIGN(32) static char s_currentDir[64] = "/";
static char s_homeDir[64] ATTRIBUTE_ALIGN(32) = ""; ATTRIBUTE_ALIGN(32) static char s_homeDir[64] = "";
static BOOL nandOnShutdown(BOOL final, u32 event); static BOOL nandOnShutdown(BOOL final, u32 event);
void nandConvertPath(char* abspath, const char* wd, const char* relpath); 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) { for (; i < sizeof(ERRMAP) / 4; i = i + 2) {
if (ERRMAP[i] == err) { 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) { 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); sprintf(buf, "ISFS error code: %d", err);
NANDLoggingAddMessageAsync(nandLoggingCallback, err, buf); NANDLoggingAddMessageAsync(nandLoggingCallback, err, buf);
} }
@@ -279,7 +279,7 @@ s32 nandConvertErrorCode(const ISFSError err) {
OSReport("CAUTION! Unexpected error code [%d] was found.\n", 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); sprintf(buf, "ISFS unexpected error code: %d", err);
NANDLoggingAddMessageAsync(nandLoggingCallback, err, buf); NANDLoggingAddMessageAsync(nandLoggingCallback, err, buf);
} }
+3 -3
View File
@@ -7,7 +7,7 @@
static IOSFd s_fd = -255; static IOSFd s_fd = -255;
static IOSError s_err = ISFS_ERROR_UNKNOWN; static IOSError s_err = ISFS_ERROR_UNKNOWN;
static int s_stage; 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 NANDLoggingCallback s_callback = 0;
static void asyncRoutine(ISFSError, void*); static void asyncRoutine(ISFSError, void*);
@@ -70,8 +70,8 @@ static void callbackRoutine(BOOL result) {
static void asyncRoutine(ISFSError result, void *ctxt) { static void asyncRoutine(ISFSError result, void *ctxt) {
ISFSError ret = ISFS_ERROR_UNKNOWN; ISFSError ret = ISFS_ERROR_UNKNOWN;
static char s_rBuf[256] ATTRIBUTE_ALIGN(64); ATTRIBUTE_ALIGN(64) static char s_rBuf[256];
static char s_wBuf[256] ATTRIBUTE_ALIGN(64); ATTRIBUTE_ALIGN(64) static char s_wBuf[256];
++s_stage; ++s_stage;
if (s_stage == 2) { if (s_stage == 2) {
+1 -1
View File
@@ -293,7 +293,7 @@ s32 NANDMove(const char* path, const char* destDir) {
} }
static ISFSError nandGetFileStatus(IOSFd fd, u32* length, u32* pos) { 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); ISFSError result = ISFS_GetFileStats(fd, &fstat);
if (result == ISFS_ERROR_OK) { if (result == ISFS_ERROR_OK) {
if (length) { if (length) {
+1 -1
View File
@@ -8,7 +8,7 @@ void __OSRelaunchTitle(u32 resetCode) {
s32 rc = 0; s32 rc = 0;
u32 ticketCnt = 1; u32 ticketCnt = 1;
ESTicketView* tik = NULL; ESTicketView* tik = NULL;
ESTitleId titleId ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) ESTitleId titleId;
__OSPlayTimeType type = OSPLAYTIME_PERMANENT; __OSPlayTimeType type = OSPLAYTIME_PERMANENT;
u32 remain = 0; u32 remain = 0;
u8* bi2 = NULL; u8* bi2 = NULL;
+1 -1
View File
@@ -85,7 +85,7 @@ NWC24Err NWC24iSynchronizeRtcCounter(BOOL param_0) {
} }
NWC24Err NWC24SuspendScheduler(void) { NWC24Err NWC24SuspendScheduler(void) {
static u8 susResult[0x20] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u8 susResult[0x20];
NWC24Err rt = NWC24_OK; NWC24Err rt = NWC24_OK;
NWC24Err closeRt = NWC24_OK; NWC24Err closeRt = NWC24_OK;
IOSFd fd; IOSFd fd;
+1 -1
View File
@@ -2,7 +2,7 @@
#include <revolution/nand.h> #include <revolution/nand.h>
static BOOL PlayRecordGet = FALSE; static BOOL PlayRecordGet = FALSE;
static OSPlayRecord PlayRecord ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static OSPlayRecord PlayRecord;
static NANDFileInfo FileInfo; static NANDFileInfo FileInfo;
static NANDCommandBlock Block; static NANDCommandBlock Block;
static s32 PlayRecordState = 9; static s32 PlayRecordState = 9;
+6 -6
View File
@@ -7,7 +7,7 @@
#include "__os.h" #include "__os.h"
OSAlarm __OSExpireAlarm ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) OSAlarm __OSExpireAlarm;
OSTime __OSExpireTime; OSTime __OSExpireTime;
OSPlayTimeCallbackFunc __OSExpireCallback; OSPlayTimeCallbackFunc __OSExpireCallback;
BOOL __OSExpireSetExpiredFlag; BOOL __OSExpireSetExpiredFlag;
@@ -103,7 +103,7 @@ BOOL __OSWriteExpiredFlag(void) {
s32 rv = 0; s32 rv = 0;
NANDFileInfo nInfo; NANDFileInfo nInfo;
BOOL openNInfo = FALSE; BOOL openNInfo = FALSE;
u8 titleId[32] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 titleId[32];
rv = NANDPrivateCreate("/shared2/expired", 63, 0); rv = NANDPrivateCreate("/shared2/expired", 63, 0);
@@ -162,7 +162,7 @@ BOOL __OSWriteExpiredFlagIfSet(void) {
void* __OSPlayTimeRebootThread(void* args) { void* __OSPlayTimeRebootThread(void* args) {
BOOL enabled; BOOL enabled;
u32 frames, fadeShift = 1; u32 frames, fadeShift = 1;
__OSExpireAIFadeStruct aiFade ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) __OSExpireAIFadeStruct aiFade;
__OSExpireAIFade = &aiFade; __OSExpireAIFade = &aiFade;
memset(__OSExpireAIFade, 0, sizeof(__OSExpireAIFadeStruct)); memset(__OSExpireAIFade, 0, sizeof(__OSExpireAIFadeStruct));
@@ -227,9 +227,9 @@ out:
s32 __OSGetPlayTime(ESTicketView* ticket, __OSPlayTimeType* type, u32* playTime) { s32 __OSGetPlayTime(ESTicketView* ticket, __OSPlayTimeType* type, u32* playTime) {
s32 rv; s32 rv;
u32 i; u32 i;
ESLpEntry lpEntry[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) ESLpEntry lpEntry[8];
u32 numCc = 0, seenOther = 0; u32 numCc = 0, seenOther = 0;
ESTicketView ticketAligned ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) ESTicketView ticketAligned;
ASSERTLINE(601, ticket && type && playTime); ASSERTLINE(601, ticket && type && playTime);
@@ -288,7 +288,7 @@ out:
s32 __OSGetPlayTimeCurrent(__OSPlayTimeType* type, u32* playTime) { s32 __OSGetPlayTimeCurrent(__OSPlayTimeType* type, u32* playTime) {
s32 rv; s32 rv;
ESTicketView ticket ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) ESTicketView ticket;
ASSERTLINE(676, type && playTime); ASSERTLINE(676, type && playTime);
+1 -1
View File
@@ -3,7 +3,7 @@
#include "__os.h" #include "__os.h"
static SramControl Scb ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT); ATTRIBUTE_ALIGN(DOLPHIN_ALIGNMENT) static SramControl Scb;
// prototypes // prototypes
static int GetRTC(u32* rtc); static int GetRTC(u32* rtc);
+1 -1
View File
@@ -2,7 +2,7 @@
#include <revolution/nand.h> #include <revolution/nand.h>
#include <cstring> #include <cstring>
static OSStateFlags StateFlags ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static OSStateFlags StateFlags;
static u32 CheckSum(OSStateFlags* flags) { static u32 CheckSum(OSStateFlags* flags) {
u32* ptr, i, sum; u32* ptr, i, sum;
+6 -6
View File
@@ -5,14 +5,14 @@
#include <revolution/private/iosrestypes.h> #include <revolution/private/iosrestypes.h>
static u32 StmImInBuf[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 StmImInBuf[8];
static u32 StmImOutBuf[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 StmImOutBuf[8];
static u32 StmVdInBuf[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 StmVdInBuf[8];
static u32 StmVdOutBuf[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 StmVdOutBuf[8];
static u32 StmEhInBuf[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 StmEhInBuf[8];
static u32 StmEhOutBuf[8] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u32 StmEhOutBuf[8];
static OSResetCallback ResetCallback; static OSResetCallback ResetCallback;
static OSPowerCallback PowerCallback; static OSPowerCallback PowerCallback;
+1 -1
View File
@@ -873,4 +873,4 @@ void* OSGetThreadSpecific(s32 index) {
#include "global.h" #include "global.h"
extern u8 Debug_BBA_804516D0; extern u8 Debug_BBA_804516D0;
u8 Debug_BBA_804516D0 ATTRIBUTE_ALIGN(8); ATTRIBUTE_ALIGN(8) u8 Debug_BBA_804516D0;
+2 -2
View File
@@ -75,8 +75,8 @@ static const char ConfDirName[] = "/shared2/sys";
static const char ConfFileName[] = "/shared2/sys/SYSCONF"; static const char ConfFileName[] = "/shared2/sys/SYSCONF";
static const char ProductInfoFileName[] = "/title/00000001/00000002/data/setting.txt"; static const char ProductInfoFileName[] = "/title/00000001/00000002/data/setting.txt";
static u8 ConfBuf[0x4000] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u8 ConfBuf[0x4000];
static u8 ConfBufForFlush[0x4000] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) static u8 ConfBufForFlush[0x4000];
static u8 Initialized; static u8 Initialized;
static u8 DirtyFlag; static u8 DirtyFlag;
+1 -1
View File
@@ -262,7 +262,7 @@ void WPADiManageHandler(OSAlarm*, OSContext*) {
BTA_HhGetAclQueueInfo(); BTA_HhGetAclQueueInfo();
} }
u8 __WPADiManageHandlerStack[4096] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __WPADiManageHandlerStack[4096];
void WPADiManageHandler0(OSAlarm* alarm, OSContext* context) { void WPADiManageHandler0(OSAlarm* alarm, OSContext* context) {
OSSwitchFiberEx((u32)alarm, (u32)context, 0, 0, (u32)WPADiManageHandler, (u32)(__WPADiManageHandlerStack + sizeof(__WPADiManageHandlerStack))); OSSwitchFiberEx((u32)alarm, (u32)context, 0, 0, (u32)WPADiManageHandler, (u32)(__WPADiManageHandlerStack + sizeof(__WPADiManageHandlerStack)));
+1 -1
View File
@@ -41,7 +41,7 @@ WUDControlBlock _wcb;
WUDDevInfo _work; WUDDevInfo _work;
static WUDDiscResp _discResp; static WUDDiscResp _discResp;
SCBtDeviceInfoArray _scArray; SCBtDeviceInfoArray _scArray;
u8 __WUDHandlerStack[0x1000] ATTRIBUTE_ALIGN(32); ATTRIBUTE_ALIGN(32) u8 __WUDHandlerStack[0x1000];
extern u8 _scFlush; extern u8 _scFlush;
+12 -14
View File
@@ -1,6 +1,6 @@
# Android Shell # 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 ## Prerequisites
@@ -25,24 +25,19 @@ cmake --build --preset android-arm64
This build produces `build/android-arm64/libmain.so` 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 ## Build APK
```bash ```bash
cd android cd platforms/android
./gradlew :app:assembleDebug ./gradlew :app:assembleDebug
``` ```
Output APK: 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) ## Launch With Runtime Args (adb)
@@ -50,10 +45,13 @@ You can pass command-line args through the activity intent:
```bash ```bash
adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \ adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \
--es dusk_args "--backend vulkan" --es borealis_args "--backend vulkan"
``` ```
Supported extras: Supported extras:
- `dusk_args`: single shell-like argument string - `borealis_args`: single shell-like argument string
- `dusk_argv`: string-array argv - `borealis_argv`: string-array argv
The legacy `dusk_args` and `dusk_argv` names remain accepted during the shell
transition.
+22 -153
View File
@@ -2,163 +2,32 @@ plugins {
id 'com.android.application' 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 duskRepoDir = rootProject.projectDir.parentFile.parentFile
def androidNativeBuildDir = new File(duskRepoDir, 'build/android-arm64') def borealisDir = new File(duskRepoDir, 'extern/borealis')
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 androidNdkVersion = { ext.borealisAndroid = [
def ndkVersion = System.getenv('ANDROID_NDK_VERSION') borealisDir: borealisDir,
if (!ndkVersion) { propertiesFile: new File(duskRepoDir, 'build/android-arm64/borealis-android.properties'),
throw new GradleException('ANDROID_NDK_VERSION is not available') namespace: 'dev.twilitrealm.dusk',
} applicationId: 'dev.twilitrealm.dusk',
ndkVersion abis: ['arm64-v8a'],
}.memoize() 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 = { apply from: new File(borealisDir, 'platforms/android/gradle/borealis-application.gradle')
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
}
}
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
} }
tasks.named('preBuild').configure {
dependsOn(stageJniLibs, syncDuskAssets)
}
+1 -4
View File
@@ -1,4 +1 @@
# Keep SDL activity and related JNI bridge methods. -keep class dev.twilitrealm.dusk.DuskActivity { *; }
-keep class org.libsdl.app.** { *; }
-keep class dev.twilitrealm.dusk.DuskHttpClient { *; }
-keep class dev.twilitrealm.dusk.DuskHttpClient$Response { *; }
@@ -26,8 +26,8 @@
android:resource="@xml/game_mode_config" /> android:resource="@xml/game_mode_config" />
<provider <provider
android:name="dev.twilitrealm.dusk.DuskDocumentsProvider" android:name="dev.encounter.borealis.BorealisDocumentsProvider"
android:authorities="dev.twilitrealm.dusk.documents" android:authorities="${applicationId}.documents"
android:exported="true" android:exported="true"
android:grantUriPermissions="true" android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"> android:permission="android.permission.MANAGE_DOCUMENTS">
@@ -1,102 +1,23 @@
package dev.twilitrealm.dusk; 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.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle; 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.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 dev.encounter.borealis.BorealisActivity;
import org.libsdl.app.SDLSurface;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; 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 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<String> 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
extractBundledMods(); extractBundledMods();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
hideSystemBars();
} }
// Bundled mod packages ship as APK assets, which the native loader cannot read directly; // 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(); 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 @Override
protected String[] getArguments() { protected String[] getArguments() {
String[] arguments = super.getArguments();
if (arguments.length > 0) {
return arguments;
}
Intent intent = getIntent(); Intent intent = getIntent();
if (intent != null) { if (intent == null) {
String[] argv = intent.getStringArrayExtra("dusk_argv"); return arguments;
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);
}
}
} }
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<String> 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 : "";
}
} }
@@ -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;
}
}
@@ -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<String> names = new ArrayList<>();
List<String> values = new ArrayList<>();
Map<String, List<String>> headerFields = connection.getHeaderFields();
if (headerFields == null) {
return new HeaderLists(new String[0], new String[0]);
}
for (Map.Entry<String, List<String>> entry : headerFields.entrySet()) {
String name = entry.getKey();
if (name == null) {
continue;
}
List<String> 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;
}
}
}
@@ -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();
}
@@ -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<GattOperation> 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<Integer, BluetoothGattCharacteristic> mOutputReportChars = new HashMap<Integer, BluetoothGattCharacteristic>();
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<GattOperation>();
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();
}
}
@@ -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<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
private int mNextDeviceId = 0;
private SharedPreferences mSharedPreferences = null;
private boolean mIsChromebook = false;
private UsbManager mUsbManager;
private Handler mHandler;
private BluetoothManager mBluetoothManager;
private List<BluetoothDevice> 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<Integer> devices = new ArrayList<Integer>();
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<BluetoothDevice>();
// 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<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
List<BluetoothDevice> 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);
}
@@ -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);
}
}
}
}
}
}
@@ -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;
}
File diff suppressed because it is too large Load Diff
@@ -1,126 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
import java.util.ArrayList;
class SDLAudioManager {
protected static final String TAG = "SDLAudio";
protected static Context mContext;
private static AudioDeviceCallback mAudioDeviceCallback;
static void initialize() {
mAudioDeviceCallback = null;
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
{
mAudioDeviceCallback = new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
for (AudioDeviceInfo deviceInfo : addedDevices) {
nativeAddAudioDevice(deviceInfo.isSink(), deviceInfo.getProductName().toString(), deviceInfo.getId());
}
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
for (AudioDeviceInfo deviceInfo : removedDevices) {
nativeRemoveAudioDevice(deviceInfo.isSink(), deviceInfo.getId());
}
}
};
}
}
static void setContext(Context context) {
mContext = context;
}
static void release(Context context) {
// no-op atm
}
// Audio
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
for (AudioDeviceInfo deviceInfo : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
if (deviceInfo.getId() == deviceId) {
return deviceInfo;
}
}
}
return null;
}
private static AudioDeviceInfo getPlaybackAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
for (AudioDeviceInfo deviceInfo : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
if (deviceInfo.getId() == deviceId) {
return deviceInfo;
}
}
}
return null;
}
static void registerAudioDeviceCallback() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
// get an initial list now, before hotplug callbacks fire.
for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
if (dev.getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
continue; // Device cannot be opened
}
nativeAddAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId());
}
for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
nativeAddAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId());
}
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
}
}
static void unregisterAudioDeviceCallback() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
}
}
/** This method is called by SDL using JNI. */
static void audioSetThreadPriority(boolean recording, int device_id) {
try {
/* Set thread name */
if (recording) {
Thread.currentThread().setName("SDLAudioC" + device_id);
} else {
Thread.currentThread().setName("SDLAudioP" + device_id);
}
/* Set thread priority */
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
} catch (Exception e) {
Log.v(TAG, "modify thread properties failed " + e.toString());
}
}
static native void nativeSetupJNI();
static native void nativeRemoveAudioDevice(boolean recording, int deviceId);
static native void nativeAddAudioDevice(boolean recording, String name, int deviceId);
}
File diff suppressed because it is too large Load Diff
@@ -1,66 +0,0 @@
package org.libsdl.app;
import android.content.*;
import android.text.InputType;
import android.view.*;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
/* This is a fake invisible editor view that receives the input and defines the
* pan&scan region
*/
public class SDLDummyEdit extends View implements View.OnKeyListener
{
InputConnection ic;
int input_type;
SDLDummyEdit(Context context) {
super(context);
setFocusableInTouchMode(true);
setFocusable(true);
setOnKeyListener(this);
}
void setInputType(int input_type) {
this.input_type = input_type;
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return SDLActivity.handleKeyEvent(v, keyCode, event, ic);
}
//
@Override
public boolean onKeyPreIme (int keyCode, KeyEvent event) {
// As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
// FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
// FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
// FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
// FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
// FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
SDLActivity.onNativeKeyboardFocusLost();
}
}
return super.onKeyPreIme(keyCode, event);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
ic = new SDLInputConnection(this, true);
outAttrs.inputType = input_type;
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |
EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
return ic;
}
}
@@ -1,136 +0,0 @@
package org.libsdl.app;
import android.content.*;
import android.os.Build;
import android.text.Editable;
import android.view.*;
import android.view.inputmethod.BaseInputConnection;
import android.widget.EditText;
class SDLInputConnection extends BaseInputConnection
{
protected EditText mEditText;
protected String mCommittedText = "";
SDLInputConnection(View targetView, boolean fullEditor) {
super(targetView, fullEditor);
mEditText = new EditText(SDL.getContext());
}
@Override
public Editable getEditable() {
return mEditText.getEditableText();
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
/*
* This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
* However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses
* and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys
* that still do, we empty this out.
*/
/*
* Return DOES still generate a key event, however. So rather than using it as the 'click a button' key
* as we do with physical keyboards, let's just use it to hide the keyboard.
*/
if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
if (SDLActivity.onNativeSoftReturnKey()) {
return true;
}
}
return super.sendKeyEvent(event);
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
if (!super.commitText(text, newCursorPosition)) {
return false;
}
updateText();
return true;
}
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
if (!super.setComposingText(text, newCursorPosition)) {
return false;
}
updateText();
return true;
}
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
if (beforeLength > 0 && afterLength == 0) {
// backspace(s)
while (beforeLength-- > 0) {
nativeGenerateScancodeForUnichar('\b');
}
return true;
}
if (!super.deleteSurroundingText(beforeLength, afterLength)) {
return false;
}
updateText();
return true;
}
protected void updateText() {
final Editable content = getEditable();
if (content == null) {
return;
}
String text = content.toString();
int compareLength = Math.min(text.length(), mCommittedText.length());
int matchLength, offset;
/* Backspace over characters that are no longer in the string */
for (matchLength = 0; matchLength < compareLength; ) {
int codePoint = mCommittedText.codePointAt(matchLength);
if (codePoint != text.codePointAt(matchLength)) {
break;
}
matchLength += Character.charCount(codePoint);
}
/* FIXME: This doesn't handle graphemes, like '🌬️' */
for (offset = matchLength; offset < mCommittedText.length(); ) {
int codePoint = mCommittedText.codePointAt(offset);
nativeGenerateScancodeForUnichar('\b');
offset += Character.charCount(codePoint);
}
if (matchLength < text.length()) {
String pendingText = text.subSequence(matchLength, text.length()).toString();
if (!SDLActivity.dispatchingKeyEvent()) {
for (offset = 0; offset < pendingText.length(); ) {
int codePoint = pendingText.codePointAt(offset);
if (codePoint == '\n') {
if (SDLActivity.onNativeSoftReturnKey()) {
return;
}
}
/* Higher code points don't generate simulated scancodes */
if (codePoint > 0 && codePoint < 128) {
nativeGenerateScancodeForUnichar((char)codePoint);
}
offset += Character.charCount(codePoint);
}
}
SDLInputConnection.nativeCommitText(pendingText, 0);
}
mCommittedText = text;
}
public static native void nativeCommitText(String text, int newCursorPosition);
public static native void nativeGenerateScancodeForUnichar(char c);
}
@@ -1,32 +0,0 @@
package org.libsdl.app;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
// This class coordinates synchronized access to sensor manager registration
//
// This prevents a java.util.ConcurrentModificationException exception on
// Android 16, specifically on the Samsung Tab S9 Ultra.
class SDLSensorManager
{
static private SDLSensorManager mManager = new SDLSensorManager();
public static void registerListener(SensorManager manager, SensorEventListener listener, Sensor sensor, int samplingPeriodUs) {
mManager.RegisterListener(manager, listener, sensor, samplingPeriodUs);
}
public static void unregisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor) {
mManager.UnregisterListener(manager, listener, sensor);
}
private synchronized void RegisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor, int samplingPeriodUs) {
manager.registerListener(listener, sensor, samplingPeriodUs, null);
}
private synchronized void UnregisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor) {
manager.unregisterListener(listener, sensor);
}
}
@@ -1,469 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Insets;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.ScaleGestureDetector;
/**
SDLSurface. This is what we draw on, so we need to know when it's created
in order to do anything useful.
Because of this, that's where we set up the SDL thread
*/
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
private static native void auroraNativeSetSurfaceReady(boolean ready);
// Sensors
protected SensorManager mSensorManager;
protected Display mDisplay;
// Keep track of the surface size to normalize touch events
protected float mWidth, mHeight;
// Is SurfaceView ready for rendering
protected boolean mIsSurfaceReady;
// Is on-screen keyboard visible
protected boolean mKeyboardVisible;
// Pinch events
private final ScaleGestureDetector scaleGestureDetector;
// Startup
protected SDLSurface(Context context) {
super(context);
getHolder().addCallback(this);
scaleGestureDetector = new ScaleGestureDetector(context, this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnApplyWindowInsetsListener(this);
setOnKeyListener(this);
setOnTouchListener(this);
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
setOnGenericMotionListener(SDLActivity.getMotionListener());
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1.0f;
mHeight = 1.0f;
mIsSurfaceReady = false;
}
protected void handlePause() {
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
protected void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnApplyWindowInsetsListener(this);
setOnKeyListener(this);
setOnTouchListener(this);
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
protected Surface getNativeSurface() {
return getHolder().getSurface();
}
// Called when we have a valid drawing surface
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
auroraNativeSetSurfaceReady(false);
SDLActivity.onNativeSurfaceCreated();
}
// Called when we lose the surface
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
auroraNativeSetSurfaceReady(false);
// Transition to pause, if needed
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
SDLActivity.handleNativeState();
mIsSurfaceReady = false;
SDLActivity.onNativeSurfaceDestroyed();
}
// Called when the surface is resized
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
Log.v("SDL", "surfaceChanged()");
if (SDLActivity.mSingleton == null) {
return;
}
mWidth = width;
mHeight = height;
int nDeviceWidth = width;
int nDeviceHeight = height;
float density = 1.0f;
try
{
DisplayMetrics realMetrics = new DisplayMetrics();
mDisplay.getRealMetrics( realMetrics );
nDeviceWidth = realMetrics.widthPixels;
nDeviceHeight = realMetrics.heightPixels;
// Use densityDpi instead of density to more closely match what the UI scale is
density = (float)realMetrics.densityDpi / 160.0f;
} catch(Exception ignored) {
}
synchronized(SDLActivity.getContext()) {
// In case we're waiting on a size change after going fullscreen, send a notification.
SDLActivity.getContext().notifyAll();
}
Log.v("SDL", "Window size: " + width + "x" + height);
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate());
SDLActivity.onNativeResize();
// Prevent a screen distortion glitch,
// for instance when the device is in Landscape and a Portrait App is resumed.
boolean skip = false;
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
if (mWidth > mHeight) {
skip = true;
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
if (mWidth < mHeight) {
skip = true;
}
}
// Special Patch for Square Resolution: Black Berry Passport
if (skip) {
double min = Math.min(mWidth, mHeight);
double max = Math.max(mWidth, mHeight);
if (max / min < 1.20) {
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
skip = false;
}
}
// Don't skip if we might be multi-window or have popup dialogs
if (skip) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
skip = false;
}
}
if (skip) {
Log.v("SDL", "Skip .. Surface is not ready.");
mIsSurfaceReady = false;
return;
}
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
SDLActivity.onNativeSurfaceChanged();
/* Surface is ready */
mIsSurfaceReady = true;
auroraNativeSetSurfaceReady(true);
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
SDLActivity.handleNativeState();
}
// Window inset
@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) {
Insets combined = insets.getInsets(WindowInsets.Type.systemBars() |
WindowInsets.Type.systemGestures() |
WindowInsets.Type.mandatorySystemGestures() |
WindowInsets.Type.tappableElement() |
WindowInsets.Type.displayCutout());
SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom);
if (insets.isVisible(WindowInsets.Type.ime())) {
if (!mKeyboardVisible) {
mKeyboardVisible = true;
SDLActivity.onNativeScreenKeyboardShown();
}
} else {
if (mKeyboardVisible) {
mKeyboardVisible = false;
SDLActivity.onNativeScreenKeyboardHidden();
}
}
}
// Pass these to any child views in case they need them
return insets;
}
// Key events
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
}
private float getNormalizedX(float x)
{
if (mWidth <= 1) {
return 0.5f;
} else {
return (x / (mWidth - 1));
}
}
private float getNormalizedY(float y)
{
if (mHeight <= 1) {
return 0.5f;
} else {
return (y / (mHeight - 1));
}
}
// Touch events
@Override
public boolean onTouch(View v, MotionEvent event) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int pointerId;
int i = 0;
float x,y,p;
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)
i = event.getActionIndex();
do {
int toolType = event.getToolType(i);
if (toolType == MotionEvent.TOOL_TYPE_MOUSE) {
int buttonState = event.getButtonState();
boolean relative = false;
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
// if we are. We'll leverage our existing mouse motion listener
SDLGenericMotionListener_API14 motionListener = SDLActivity.getMotionListener();
x = motionListener.getEventX(event, i);
y = motionListener.getEventY(event, i);
relative = motionListener.inRelativeMode();
SDLActivity.onNativeMouse(buttonState, action, x, y, relative);
} else if (toolType == MotionEvent.TOOL_TYPE_STYLUS || toolType == MotionEvent.TOOL_TYPE_ERASER) {
pointerId = event.getPointerId(i);
x = event.getX(i);
y = event.getY(i);
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
// BUTTON_STYLUS_PRIMARY is 2^5, so shift by 4, and apply SDL_PEN_INPUT_DOWN/SDL_PEN_INPUT_ERASER_TIP
int buttonState = (event.getButtonState() >> 4) | (1 << (toolType == MotionEvent.TOOL_TYPE_STYLUS ? 0 : 30));
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
buttonState |= 0x08;
}
SDLActivity.onNativePen(pointerId, SDLActivity.getMotionListener().getPenDeviceType(event.getDevice()), buttonState, action, x, y, p);
} else { // MotionEvent.TOOL_TYPE_FINGER or MotionEvent.TOOL_TYPE_UNKNOWN
pointerId = event.getPointerId(i);
x = getNormalizedX(event.getX(i));
y = getNormalizedY(event.getY(i));
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerId, action, x, y, p);
}
// Non-primary up/down
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)
break;
} while (++i < pointerCount);
scaleGestureDetector.onTouchEvent(event);
return true;
}
// Sensor events
protected void enableSensor(int sensortype, boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
SDLSensorManager.registerListener(mSensorManager, this,
mSensorManager.getDefaultSensor(sensortype),
SensorManager.SENSOR_DELAY_GAME);
} else {
SDLSensorManager.unregisterListener(mSensorManager, this,
mSensorManager.getDefaultSensor(sensortype));
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
// We thus should check here.
int newRotation;
float x, y;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_0:
default:
x = event.values[0];
y = event.values[1];
newRotation = 0;
break;
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
newRotation = 90;
break;
case Surface.ROTATION_180:
x = -event.values[0];
y = -event.values[1];
newRotation = 180;
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
newRotation = 270;
break;
}
if (newRotation != SDLActivity.mCurrentRotation) {
SDLActivity.mCurrentRotation = newRotation;
SDLActivity.onNativeRotationChanged(newRotation);
}
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
y / SensorManager.GRAVITY_EARTH,
event.values[2] / SensorManager.GRAVITY_EARTH);
}
}
// Prevent android internal NullPointerException (https://github.com/libsdl-org/SDL/issues/13306)
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
try {
return super.onResolvePointerIcon(event, pointerIndex);
} catch (NullPointerException e) {
return null;
}
}
// Captured pointer events for API 26.
@Override
public boolean onCapturedPointerEvent(MotionEvent event)
{
int action = event.getActionMasked();
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
float x, y;
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, i);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, i);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
x = event.getX(i);
y = event.getY(i);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
case MotionEvent.ACTION_BUTTON_RELEASE:
// Change our action value to what SDL's code expects.
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
action = MotionEvent.ACTION_DOWN;
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
action = MotionEvent.ACTION_UP;
}
x = event.getX(i);
y = event.getY(i);
int button = event.getButtonState();
SDLActivity.onNativeMouse(button, action, x, y, true);
return true;
}
}
return false;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = detector.getScaleFactor();
SDLActivity.onNativePinchUpdate(scale);
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
SDLActivity.onNativePinchStart();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
SDLActivity.onNativePinchEnd();
}
}
@@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
SRC_DEFAULT="$ROOT_DIR/build/android-arm64/_deps/sdl-src/android-project/app/src/main/java/org/libsdl/app"
SRC_DIR="${1:-$SRC_DEFAULT}"
DST_DIR="$ROOT_DIR/platforms/android/app/src/main/java/org/libsdl/app"
if [[ ! -d "$SRC_DIR" ]]; then
echo "SDL Java source directory not found: $SRC_DIR" >&2
exit 1
fi
mkdir -p "$DST_DIR"
cp -f "$SRC_DIR"/*.java "$DST_DIR"/
echo "Synced SDL Java sources from $SRC_DIR to $DST_DIR"
+2 -2
View File
@@ -23,12 +23,12 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0" VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0"
VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0" VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0"
VALUE "FileVersion", "@DUSK_VERSION_STRING@\0" VALUE "FileVersion", "@BOREALIS_APP_VERSION@\0"
VALUE "InternalName", "dusklight\0" VALUE "InternalName", "dusklight\0"
VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0" VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0"
VALUE "OriginalFilename", "dusklight.exe\0" VALUE "OriginalFilename", "dusklight.exe\0"
VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0" VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0"
VALUE "ProductVersion", "@DUSK_VERSION_STRING@\0" VALUE "ProductVersion", "@BOREALIS_APP_VERSION@\0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"
-5
View File
@@ -25,11 +25,6 @@ endif ()
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Version detection & version.h
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/DetectVersion.cmake")
detect_version()
configure_version_header()
# Mod API and optional feature header surfaces # Mod API and optional feature header surfaces
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GameABIConfig.cmake") include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GameABIConfig.cmake")
+10
View File
@@ -53,4 +53,14 @@ ModResult hook_replace(HookReplaceFn callback, const HookOptions* options = null
return hook::replace<Entry>(callback, options); return hook::replace<Entry>(callback, options);
} }
template <class Entry>
ModResult hook_uninstall(const HookService* hooks) {
return hook::uninstall<Entry>(hooks);
}
template <class Entry>
ModResult hook_uninstall() {
return hook::uninstall<Entry>();
}
} // namespace mods } // namespace mods
+43 -1
View File
@@ -8,7 +8,7 @@
#define CAMERA_SERVICE_ID "dev.twilitrealm.dusklight.camera" #define CAMERA_SERVICE_ID "dev.twilitrealm.dusklight.camera"
#define CAMERA_SERVICE_MAJOR 1u #define CAMERA_SERVICE_MAJOR 1u
#define CAMERA_SERVICE_MINOR 0u #define CAMERA_SERVICE_MINOR 1u
/* /*
* Snapshot of a game camera for the frame currently being recorded. * Snapshot of a game camera for the frame currently being recorded.
@@ -46,6 +46,37 @@ typedef struct CameraInfo {
#define CAMERA_INFO_INIT {sizeof(CameraInfo)} #define CAMERA_INFO_INIT {sizeof(CameraInfo)}
/* 0 is never a valid handle. */
typedef uint64_t CameraOperatorHandle;
typedef struct CameraOperatorState {
uint32_t struct_size;
/* Host inputs. */
uint64_t frame_counter;
uint64_t ticks;
float aspect;
/* Initial camera state and callback output. */
float eye[3];
float center[3];
float fovy;
float bank_degrees;
} CameraOperatorState;
/* Return true to use state for the current frame. Game thread only. */
typedef bool (*CameraOperateFn)(ModContext* ctx, CameraOperatorState* state, void* user_data);
typedef struct CameraOperatorDesc {
uint32_t struct_size;
const char* debug_name;
int32_t priority;
CameraOperateFn operate;
void* user_data;
} CameraOperatorDesc;
#define CAMERA_OPERATOR_DESC_INIT {sizeof(CameraOperatorDesc), NULL, 0, NULL, NULL}
typedef struct CameraService { typedef struct CameraService {
ServiceHeader header; ServiceHeader header;
@@ -55,6 +86,17 @@ typedef struct CameraService {
* perspective camera. * perspective camera.
*/ */
ModResult (*get_camera)(ModContext* ctx, const void* game_view, CameraInfo* out_info); ModResult (*get_camera)(ModContext* ctx, const void* game_view, CameraInfo* out_info);
/* Minor version 1 */
/*
* Register an operator for the main camera. Operators run by descending priority, then
* registration order, until one returns true. debug_name and operate must be set; debug_name
* is copied. out_handle must not be NULL.
*/
ModResult (*register_camera_operator)(
ModContext* ctx, const CameraOperatorDesc* desc, CameraOperatorHandle* out_handle);
ModResult (*unregister_camera_operator)(ModContext* ctx, CameraOperatorHandle handle);
} CameraService; } CameraService;
MOD_DECLARE_SERVICE( MOD_DECLARE_SERVICE(
+33 -14
View File
@@ -7,20 +7,22 @@
#endif #endif
/* /*
* Intercept game functions by address. Prefer the typed helpers in mods/svc/hook.hpp * Hooks allow intercepting calls to game functions, allowing you to:
* (mods::hook::add_pre/add_post/replace over a &Class::method): they generate the * - Modify arguments
* trampoline and hide install/dispatch, which are the low-level primitives those helpers * - Perform your own work before (pre), after (post) or instead of (replace) the original call
* build. resolve() maps a symbol name to an address for targets you can't name at compile time * - From a pre hook, conditionally skip the original call and return your own value
* (file-local statics included).
* *
* Every call is game-thread-only. Install and removal must run with no hooked function on the * In most cases, you'll want to instead use the C++ helpers in mods/svc/hook.hpp
* stack; the loader guarantees this by applying mod lifecycle changes between frames, which is * (mods::hook::add_pre/add_post/replace). They generate the trampoline passed to
* why hooking a function that never returns (the outermost loop) makes a mod un-unloadable. * install and provide compile-time type checking.
*
* resolve() resolves an address by symbol name for targets you can't name at compile time
* (file-local statics included).
*/ */
#define HOOK_SERVICE_ID "dev.twilitrealm.dusklight.hook" #define HOOK_SERVICE_ID "dev.twilitrealm.dusklight.hook"
#define HOOK_SERVICE_MAJOR 1u #define HOOK_SERVICE_MAJOR 1u
#define HOOK_SERVICE_MINOR 0u #define HOOK_SERVICE_MINOR 1u
/* Symbol flags reported by resolve() */ /* Symbol flags reported by resolve() */
typedef enum HookSymbolFlags { typedef enum HookSymbolFlags {
@@ -72,11 +74,18 @@ typedef struct HookService {
ServiceHeader header; ServiceHeader header;
/* /*
* Install a trampoline detour on fn_addr and return the address to call the original through in * Install a hook on fn_addr.
* *out_original_fn. The typed helpers generate the trampoline and call this; mods normally *
* don't. The first mod to install a given target owns the live detour; later mods register as * trampoline_fn must point to a function that matches the original function's signature and
* candidates so a hook survives the owner unloading (the detour is handed off and every * dispatches pre- and post- hooks. This dispatch trampoline is normally generated at compile
* original pointer is rewritten). Idempotent per (mod, out slot). * time using C++ template instantiation (see mods/svc/hook.hpp).
*
* The first hook install on a target will implicitly install a detour (patched instructions
* on the target that jump to the dispatch trampoline). When all hooks are uninstalled from a
* target, the detour is completely uninstalled.
*
* The address that the dispatch trampoline should call the original function through is written
* to out_original_fn.
*/ */
ModResult (*install)( ModResult (*install)(
ModContext* ctx, void* fn_addr, void* trampoline_fn, void** out_original_fn); ModContext* ctx, void* fn_addr, void* trampoline_fn, void** out_original_fn);
@@ -116,6 +125,16 @@ typedef struct HookService {
*/ */
ModResult (*resolve)( ModResult (*resolve)(
ModContext* ctx, const char* symbol, void** out_addr, HookSymbolFlags* out_flags); ModContext* ctx, const char* symbol, void** out_addr, HookSymbolFlags* out_flags);
/* Minor version 1 */
/*
* Uninstall the current mod's hook on fn_addr and unregister all callbacks.
* If no other mods have a hook installed on the target, the detour is uninstalled entirely.
*
* original_fn_slot must match the out_original_fn passed to install.
*/
ModResult (*uninstall)(ModContext* ctx, void* fn_addr, void** original_fn_slot);
} HookService; } HookService;
MOD_DECLARE_SERVICE(HookService, svc_hook, HOOK_SERVICE_ID, HOOK_SERVICE_MAJOR, HOOK_SERVICE_MINOR); MOD_DECLARE_SERVICE(HookService, svc_hook, HOOK_SERVICE_ID, HOOK_SERVICE_MAJOR, HOOK_SERVICE_MINOR);
+22
View File
@@ -238,5 +238,27 @@ ModResult replace(HookReplaceFn callback, const HookOptions* options = nullptr)
return replace<Entry>(svc_hook, callback, options); return replace<Entry>(svc_hook, callback, options);
} }
template <class Entry>
ModResult uninstall(const HookService* hooks) {
if (hooks == nullptr || !SERVICE_HAS(hooks, HookService, uninstall) ||
hooks->uninstall == nullptr || Entry::target == nullptr)
{
return MOD_UNAVAILABLE;
}
const ModResult result =
hooks->uninstall(mod_ctx, Entry::target, reinterpret_cast<void**>(&Entry::g_orig));
if (result == MOD_OK) {
Entry::hooks = nullptr;
Entry::g_orig = nullptr;
}
return result;
}
template <class Entry>
ModResult uninstall() {
return uninstall<Entry>(svc_hook);
}
} // namespace hook } // namespace hook
} // namespace mods } // namespace mods
+12 -3
View File
@@ -13,7 +13,7 @@
#define HOST_SERVICE_ID "dev.twilitrealm.dusklight.host" #define HOST_SERVICE_ID "dev.twilitrealm.dusklight.host"
#define HOST_SERVICE_MAJOR 2u #define HOST_SERVICE_MAJOR 2u
#define HOST_SERVICE_MINOR 1u #define HOST_SERVICE_MINOR 2u
/* /*
* Ignore unknown values: later service minors may add events. * Ignore unknown values: later service minors may add events.
@@ -96,15 +96,24 @@ typedef struct HostService {
ModContext* ctx, ModLifecycleFn fn, void* user_data, uint64_t* out_handle); ModContext* ctx, ModLifecycleFn fn, void* user_data, uint64_t* out_handle);
ModResult (*unwatch_mod_lifecycle)(ModContext* ctx, uint64_t handle); ModResult (*unwatch_mod_lifecycle)(ModContext* ctx, uint64_t handle);
/* Minor version 1 */
/* /*
* Read-only directory containing this platform's packaged native runtime: the mod module * Read-only directory containing this platform's packaged native runtime: the mod module
* and any RUNTIME_LIBRARIES. The path is absolute and remains valid until mod_shutdown * and any RUNTIME_LIBRARIES. The path is absolute and remains valid until mod_shutdown
* returns. Libraries loaded dynamically from here are owned by the mod and must be unloaded * returns. Libraries loaded dynamically from here are owned by the mod and must be unloaded
* during mod_shutdown. * during mod_shutdown.
*
* Added in minor version 1.
*/ */
const char* (*native_dir)(ModContext* ctx); const char* (*native_dir)(ModContext* ctx);
/* Minor version 2 */
/*
* A persistent writable directory reserved for the calling mod, created on first use.
*
* The returned path remains valid until mod_shutdown returns. *out_path is null on failure.
*/
ModResult (*data_dir)(ModContext* ctx, const char** out_path);
} HostService; } HostService;
MOD_DECLARE_SERVICE(HostService, svc_host, HOST_SERVICE_ID, HOST_SERVICE_MAJOR, HOST_SERVICE_MINOR); MOD_DECLARE_SERVICE(HostService, svc_host, HOST_SERVICE_ID, HOST_SERVICE_MAJOR, HOST_SERVICE_MINOR);
+94
View File
@@ -0,0 +1,94 @@
#pragma once
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define ITEM_SERVICE_ID "dev.twilitrealm.dusklight.item"
#define ITEM_SERVICE_MAJOR 2u
#define ITEM_SERVICE_MINOR 0u
/* 0 is never a valid handle. */
typedef uint64_t ItemCheckHandle;
typedef uint64_t ItemGiveHandle;
/*
* Item check resolution and inventory grants.
*
* Check names are case-sensitive. Resolvers must be free of side effects because the game may
* resolve a check more than once, including once for display and again when granting the item.
* Registrations and pending grants are removed when the calling mod is detached. Callbacks run
* on the game thread.
*/
/* Host-owned callback data, valid only for the duration of the callback. */
typedef struct ItemCheckInfo {
const char* name;
const void* giver_actor; /* fopAc_ac_c*, or NULL when unavailable */
uint8_t vanilla_item;
uint8_t current_item;
} ItemCheckInfo;
/* Return true and write out_item to replace current_item, or false to leave it unchanged. */
typedef bool (*ItemCheckResolveFn)(
ModContext* ctx, const ItemCheckInfo* info, uint8_t* out_item, void* user_data);
typedef enum ItemGiveOrigin {
ITEM_GIVE_ORIGIN_GAME = 0,
ITEM_GIVE_ORIGIN_QUEUE = 1,
ITEM_GIVE_ORIGIN_QUEUE_SILENT = 2,
} ItemGiveOrigin;
/* Host-owned callback data, valid only for the duration of the callback. */
typedef struct ItemGiveInfo {
const char* check_name; /* NULL when the grant is not attributed to a check */
const void* giver_actor; /* fopAc_ac_c*, or NULL when unavailable */
uint8_t item;
uint8_t origin; /* ItemGiveOrigin */
} ItemGiveInfo;
typedef void (*ItemGiveObserveFn)(ModContext* ctx, const ItemGiveInfo* info, void* user_data);
enum {
/* Apply the inventory change without a get-item demo. */
ITEM_GIVE_SILENT = 1u << 0,
/* Resolve item_no as the named check's vanilla item when the queue entry is dispatched. */
ITEM_GIVE_RESOLVE = 1u << 1,
};
typedef struct ItemService {
ServiceHeader header;
/* Set or replace the calling mod's fixed override for name. */
ModResult (*set_check_override)(ModContext* ctx, const char* name, uint8_t item_no);
ModResult (*clear_check_override)(ModContext* ctx, const char* name);
/* Register for name, or for every check when name is NULL. out_handle may be NULL. */
ModResult (*set_check_resolver)(ModContext* ctx, const char* name, ItemCheckResolveFn fn,
void* user_data, ItemCheckHandle* out_handle);
ModResult (*clear_check_resolver)(ModContext* ctx, ItemCheckHandle handle);
/* Resolve without granting an item or notifying give observers. */
ModResult (*resolve_check)(
ModContext* ctx, const char* name, uint8_t vanilla_item, uint8_t* out_item);
/*
* Add a grant to the global FIFO. check_name may be NULL unless ITEM_GIVE_RESOLVE is set.
* Returns MOD_UNAVAILABLE when the queue is full. Entries wait until gameplay is in a safe
* state and are cleared when the active save slot changes.
*/
ModResult (*give_item)(
ModContext* ctx, const char* check_name, uint8_t item_no, uint32_t flags);
/* Observe inventory grants. out_handle may be NULL. */
ModResult (*observe_gives)(
ModContext* ctx, ItemGiveObserveFn fn, void* user_data, ItemGiveHandle* out_handle);
ModResult (*unobserve_gives)(ModContext* ctx, ItemGiveHandle handle);
} ItemService;
MOD_DECLARE_SERVICE(ItemService, svc_item, ITEM_SERVICE_ID, ITEM_SERVICE_MAJOR, ITEM_SERVICE_MINOR);
+2 -1
View File
@@ -8,7 +8,8 @@
/* /*
* Read-only access to the res/ tree of the calling mod's own bundle. Reload serves the new * Read-only access to the res/ tree of the calling mod's own bundle. Reload serves the new
* bundle's contents. For writable storage, use HostService::mod_dir. * bundle's contents. Use HostService::data_dir for persistent storage or HostService::mod_dir
* for temporary storage.
*/ */
#define RESOURCE_SERVICE_ID "dev.twilitrealm.dusklight.resource" #define RESOURCE_SERVICE_ID "dev.twilitrealm.dusklight.resource"
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define STAGE_SERVICE_ID "dev.twilitrealm.dusklight.stage"
#define STAGE_SERVICE_MAJOR 1u
#define STAGE_SERVICE_MINOR 0u
/* 0 is never a valid handle. */
typedef uint64_t StageActorHandle;
/*
* Runtime edits to Stage Info (.dzs/.dzr) data.
*/
typedef struct StageService {
ServiceHeader header;
/*
* Actor Node (ACTR/TGSC/SCOB/Door) Editing:
* stage must be a non-empty name of at most 8 characters. room 0xff and layer -1 match any room
* or layer for patch and delete operations; add_actor requires a specific room. Later-loaded mods
* win conflicts. record_crc is the CRC-32 of the unmodified record. Registrations are removed when
* the calling mod is detached. out_handle may be NULL.
*/
ModResult (*patch_actor)(ModContext* ctx, const char* stage, uint8_t room, int8_t layer,
uint32_t record_crc, const void* record, size_t record_size, StageActorHandle* out_handle);
ModResult (*delete_actor)(ModContext* ctx, const char* stage, uint8_t room, int8_t layer,
uint32_t record_crc, StageActorHandle* out_handle);
ModResult (*add_actor)(ModContext* ctx, const char* stage, uint8_t room, int8_t layer,
const void* record, size_t record_size, StageActorHandle* out_handle);
ModResult (*remove_actor_edit)(ModContext* ctx, StageActorHandle handle);
} StageService;
MOD_DECLARE_SERVICE(
StageService, svc_stage, STAGE_SERVICE_ID, STAGE_SERVICE_MAJOR, STAGE_SERVICE_MINOR);
+7 -1
View File
@@ -2260,6 +2260,12 @@ int daAlink_c::procCoGetItemInit() {
s16 var_r22 = 0; s16 var_r22 = 0;
BOOL var_r31 = FALSE; BOOL var_r31 = FALSE;
BOOL var_r30 = FALSE; BOOL var_r30 = FALSE;
#if TARGET_PC
// Queued grants have no event partner, so create the demo item from the event's GtItm.
if (dusk::mods::item_give_queue_dispatching()) {
mDemo.setParam0(0x100);
}
#endif
if (mProcID == PROC_GET_ITEM || mProcID == PROC_INSECT_CATCH || if (mProcID == PROC_GET_ITEM || mProcID == PROC_INSECT_CATCH ||
(mProcID == PROC_PREACTION_UNEQUIP && !checkNoUpperAnime())) (mProcID == PROC_PREACTION_UNEQUIP && !checkNoUpperAnime()))
{ {
@@ -2299,7 +2305,7 @@ int daAlink_c::procCoGetItemInit() {
} }
fpc_ProcID item_partner_id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1, fpc_ProcID item_partner_id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1,
fopAcM_GetRoomNo(this), NULL, NULL); fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(dusk::mods::item_give_queue_take_tag()));
if (item_partner_id != fpcM_ERROR_PROCESS_ID_e) { if (item_partner_id != fpcM_ERROR_PROCESS_ID_e) {
dComIfGp_event_setItemPartnerId(item_partner_id); dComIfGp_event_setItemPartnerId(item_partner_id);
} }
+4
View File
@@ -4084,6 +4084,10 @@ void daB_DS_c::executeBattle2Dead() {
camera->mCamera.SetTrimSize(0); camera->mCamera.SetTrimSize(0);
dComIfGp_event_reset(); dComIfGp_event_reset();
dComIfGs_onStageBossEnemy(0x13); dComIfGs_onStageBossEnemy(0x13);
#if TARGET_PC
// This reward has no original grant at this point in the cutscene.
dusk::mods::item_check_enqueue("Arbiters Grounds Dungeon Reward", dItemNo_NONE_e);
#endif
/* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */ /* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */
dComIfGs_onEventBit(0x2010); dComIfGs_onEventBit(0x2010);
fopAcM_delete(this); fopAcM_delete(this);
+1 -1
View File
@@ -170,7 +170,7 @@ void daDitem_c::actionEvent() {
if (chkDead()) { if (chkDead()) {
if (!chkArgFlag(0x1)) { if (!chkArgFlag(0x1)) {
execItemGet(m_itemNo); execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
} }
if (m_itemNo == dItemNo_KAKERA_HEART_e) { if (m_itemNo == dItemNo_KAKERA_HEART_e) {
+12 -5
View File
@@ -726,11 +726,18 @@ void daE_HP_c::executeDead() {
fopAcM_onSwitch(this, bitSw); fopAcM_onSwitch(this, bitSw);
} }
dComIfGs_addPohSpiritNum(); #if TARGET_PC
mItemCheckOverridden =
dusk::mods::item_check_poe(bitSw, dItemNo_POU_SPIRIT_e, this) != dItemNo_POU_SPIRIT_e;
if (mItemCheckOverridden) {
dusk::mods::item_check_enqueue_poe(bitSw, dItemNo_POU_SPIRIT_e);
} else
#endif
dComIfGs_addPohSpiritNum();
field_0x784 = -1; field_0x784 = -1;
if (dComIfGs_getPohSpiritNum() == 20) { if (dComIfGs_getPohSpiritNum() == 20 IF_DUSK(&&!mItemCheckOverridden)) {
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[0x1c9]); dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[0x1c9]);
} }
@@ -752,13 +759,13 @@ void daE_HP_c::executeDead() {
field_0x788 = 1; field_0x788 = 1;
} }
} }
} else if (field_0x788 != 0) { } else if (field_0x788 != 0 IF_DUSK(|| mItemCheckOverridden)) {
fopAcM_createDisappear(this, &current.pos, 8, 3, 0xff); fopAcM_createDisappear(this, &current.pos, 8, 3, 0xff);
fopAcM_delete(this); fopAcM_delete(this);
} else { } else {
if (field_0x784 == -1) { if (field_0x784 == -1) {
field_0x784 = fopAcM_createItemForPresentDemo(&current.pos, dItemNo_POU_SPIRIT_e, 0, -1, field_0x784 = fopAcM_createItemForPresentDemo(&current.pos, dItemNo_POU_SPIRIT_e, 0,
-1, 0, 0); -1, -1, 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag_poe(bitSw)));
} }
if (fopAcM_IsExecuting(field_0x784) != FALSE) { if (fopAcM_IsExecuting(field_0x784) != FALSE) {

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