mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-14 03:52:13 -04:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0da58ac91d | |||
| c29cafa8a2 | |||
| 5e669c8f0f | |||
| 3e625cf006 | |||
| c6f497b525 | |||
| 79d3c7c76a | |||
| 16dd1118d1 | |||
| e9918cc8ed | |||
| 058304dc26 | |||
| d55b44b5a5 | |||
| 7434a0f834 | |||
| 469575fd65 | |||
| 4883c11182 | |||
| 7ac2340312 | |||
| 3352ea3dde | |||
| 82e2fd4bea | |||
| fcd9dd0f70 | |||
| 8afe479d0f | |||
| c5933be631 | |||
| 928e9b4bc7 | |||
| 3ac0fcb156 | |||
| 6481d12a3d | |||
| 2c7ab755a0 | |||
| cf8ab52f55 | |||
| 960d016113 | |||
| 13b3b68fe5 | |||
| 4604304305 | |||
| 794bb130e2 | |||
| e02251a669 | |||
| 2c9f814ec6 | |||
| 76079b6294 | |||
| 519305b7d4 |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+34
-140
@@ -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}")
|
||||||
@@ -193,24 +196,21 @@ if (DUSK_MOVIE_SUPPORT)
|
|||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
|
||||||
# -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers
|
# -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers
|
||||||
# (CW, GCC, Clang, MSVC) encode them identically in big-endian order.
|
# (CW, GCC, Clang, MSVC) encode them identically in big-endian order.
|
||||||
# For >4-char literals (which GCC/Clang truncate to int), use the MULTI_CHAR() macro.
|
# For >4-char literals (which GCC/Clang truncate to int), use the MULTI_CHAR() macro.
|
||||||
# -Wdeprecated-declarations: JSystem uses std::iterator, deprecated in C++17
|
# -Wdeprecated-declarations: JSystem uses std::iterator, deprecated in C++17
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-multichar")
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -Wno-trigraphs -Wno-deprecated-declarations")
|
|
||||||
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
|
||||||
set(CMAKE_BUILD_RPATH "$ORIGIN")
|
|
||||||
elseif (APPLE)
|
|
||||||
add_compile_options(-Wno-declaration-after-statement -Wno-non-pod-varargs)
|
|
||||||
set(CMAKE_INSTALL_RPATH "@loader_path")
|
|
||||||
set(CMAKE_BUILD_RPATH "@loader_path")
|
|
||||||
elseif (MSVC)
|
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:/bigobj>
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-multichar>
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:/MP>
|
$<$<COMPILE_LANGUAGE:CXX>:-Wno-trigraphs>
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:/FS>
|
$<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-declarations>
|
||||||
|
)
|
||||||
|
elseif (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||||
|
add_compile_options(
|
||||||
|
$<$<COMPILE_LANGUAGE:C,CXX>:/bigobj>
|
||||||
|
$<$<COMPILE_LANGUAGE:C,CXX>:/MP>
|
||||||
|
$<$<COMPILE_LANGUAGE:C,CXX>:/FS>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (NOT DUSK_BUILD_WARNINGS)
|
if (NOT DUSK_BUILD_WARNINGS)
|
||||||
@@ -225,21 +225,18 @@ elseif (MSVC)
|
|||||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:/utf-8>)
|
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:/utf-8>)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||||
|
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
||||||
|
set(CMAKE_BUILD_RPATH "$ORIGIN")
|
||||||
|
elseif (APPLE)
|
||||||
|
add_compile_options(-Wno-declaration-after-statement -Wno-non-pod-varargs)
|
||||||
|
set(CMAKE_INSTALL_RPATH "@loader_path")
|
||||||
|
set(CMAKE_BUILD_RPATH "@loader_path")
|
||||||
|
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
|
||||||
@@ -247,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
|
||||||
@@ -282,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
|
||||||
@@ -328,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)
|
||||||
@@ -398,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 ()
|
||||||
@@ -471,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 ()
|
||||||
@@ -552,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)
|
||||||
@@ -611,6 +519,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
|
|||||||
add_subdirectory(mods/ao_mod)
|
add_subdirectory(mods/ao_mod)
|
||||||
add_subdirectory(mods/shadow_mod)
|
add_subdirectory(mods/shadow_mod)
|
||||||
add_subdirectory(mods/window_demo)
|
add_subdirectory(mods/window_demo)
|
||||||
|
add_subdirectory(mods/basic_cosmetics_mod)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
@@ -644,8 +553,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"
|
||||||
@@ -678,21 +587,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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
|
||||||
+101
-8
@@ -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).
|
||||||
@@ -340,6 +348,88 @@ Change callbacks fire on the game thread whenever the value changes at runtime (
|
|||||||
Writes that store the same value are silent. Values applied from `config.json` or `--cvar` at registration do
|
Writes that store the same value are silent. Values applied from `config.json` or `--cvar` at registration do
|
||||||
**not** fire callbacks; read the value after `register_var` for the starting state.
|
**not** fire callbacks; read the value after `register_var` for the starting state.
|
||||||
|
|
||||||
|
### SaveService (`mods/svc/save.h`)
|
||||||
|
|
||||||
|
Stores named binary blobs for each save slot. Blob names are scoped to the calling mod, and each mod may store up to
|
||||||
|
`SAVE_BLOB_BUDGET_BYTES` per slot. The service copies data passed to `set_blob`.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
IMPORT_SERVICE(SaveService, svc_save);
|
||||||
|
|
||||||
|
struct MySaveData {
|
||||||
|
uint32_t version;
|
||||||
|
uint32_t counter;
|
||||||
|
};
|
||||||
|
|
||||||
|
MySaveData state{1, 42};
|
||||||
|
svc_save->set_blob(mod_ctx, "state", &state, sizeof(state));
|
||||||
|
|
||||||
|
MySaveData loaded{};
|
||||||
|
size_t loadedSize = sizeof(loaded);
|
||||||
|
if (svc_save->get_blob(mod_ctx, "state", &loaded, &loadedSize) == MOD_OK &&
|
||||||
|
loadedSize == sizeof(loaded)) {
|
||||||
|
apply_state(loaded);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`set_blob`, `get_blob`, and `delete_blob` operate on the current slot, which is available after creating or loading a
|
||||||
|
save and unavailable at file select. Blob changes are written with the next game save. File-select copy and erase
|
||||||
|
operations update the blob data as well. Use `peek_blob` to read the calling mod's data from any slot; it uses the same
|
||||||
|
buffer contract as `get_blob`. Pass a `NULL` buffer to either read function to query the blob size.
|
||||||
|
|
||||||
|
`observe_saves` registers callbacks for new, loaded, and written saves. New-save callbacks run after the slot's blobs
|
||||||
|
are cleared. Observers are removed automatically when the mod is detached, so the output handle is only needed for
|
||||||
|
manual unregistration. Save callbacks run on the game thread.
|
||||||
|
|
||||||
|
### StageService (`mods/svc/stage.h`)
|
||||||
|
|
||||||
|
Allows making changes to a stage's "stage info" (contents of .dzs/.dzr files).
|
||||||
|
(Currently only supports editing actor nodes.)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
IMPORT_SERVICE(StageService, svc_stage);
|
||||||
|
|
||||||
|
stage_actor_data_class record = {
|
||||||
|
"carry00",
|
||||||
|
0xFF000000,
|
||||||
|
cXyz(0.0f, 0.0f, 0.0f),
|
||||||
|
csXyz(0, 0, 0),
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
|
||||||
|
StageActorHandle handle{};
|
||||||
|
svc_stage->patch_actor(mod_ctx, "F_SP102", 0, -1, record_crc, &record, sizeof(record), &handle);
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
StageActorHandle handle{};
|
||||||
|
svc_stage->delete_actor(mod_ctx, "F_SP102", 0, -1, record_crc, &handle);
|
||||||
|
```
|
||||||
|
|
||||||
|
Patch or remove actors from the original actor list as the room loads.
|
||||||
|
Given records must be of either `stage_actor_data_class` or `stage_tgsc_data_class` types.
|
||||||
|
`record_crc` is the CRC-32 of the unmodified original record used to identify the record to replace or remove.
|
||||||
|
|
||||||
|
```
|
||||||
|
stage_actor_data_class record = {
|
||||||
|
"carry00",
|
||||||
|
0xFF000000,
|
||||||
|
cXyz(0.0f, 0.0f, 0.0f),
|
||||||
|
csXyz(0, 0, 0),
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
|
||||||
|
StageActorHandle handle{};
|
||||||
|
svc_stage->add_actor(mod_ctx, "F_SP102", 0, -1, &record, sizeof(record), &handle);
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a new actor to the actor list as the room loads.
|
||||||
|
Given records must be of either `stage_actor_data_class` or `stage_tgsc_data_class` types.
|
||||||
|
|
||||||
|
Stage names may contain up to 8 characters. For patches and deletions, room `0xff` and layer `-1` match any room or
|
||||||
|
layer; additions require a specific room. Edits are removed when the mod is detached. If multiple mods edit the same
|
||||||
|
record, the later-loaded mod wins.
|
||||||
|
|
||||||
### UiService (`mods/svc/ui.h`)
|
### 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,
|
||||||
@@ -543,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
|
||||||
@@ -845,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.
|
||||||
|
|||||||
Vendored
+1
-1
Submodule extern/aurora updated: 0bddb86249...8005d336ab
+1
Submodule extern/borealis added at 6fd955e7e6
+7
-18
@@ -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
|
||||||
@@ -1502,7 +1493,13 @@ set(DUSK_FILES
|
|||||||
src/dusk/mods/svc/ui.hpp
|
src/dusk/mods/svc/ui.hpp
|
||||||
src/dusk/mods/svc/window.cpp
|
src/dusk/mods/svc/window.cpp
|
||||||
src/dusk/mods/svc/window.hpp
|
src/dusk/mods/svc/window.hpp
|
||||||
|
src/dusk/mods/svc/save.cpp
|
||||||
|
src/dusk/mods/svc/save.hpp
|
||||||
|
src/dusk/mods/svc/stage.cpp
|
||||||
|
src/dusk/mods/svc/stage.hpp
|
||||||
src/dusk/mouse.cpp
|
src/dusk/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
|
||||||
@@ -1578,18 +1575,10 @@ 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/helpers/batch.cpp
|
src/helpers/batch.cpp
|
||||||
src/helpers/endian.cpp
|
src/helpers/endian.cpp
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ public:
|
|||||||
static DUSK_GAME_DATA daMidna_texData_s const m_texDataTable[21];
|
static DUSK_GAME_DATA daMidna_texData_s const m_texDataTable[21];
|
||||||
static DUSK_GAME_DATA daMidna_anmData_s const m_anmDataTable[53];
|
static DUSK_GAME_DATA daMidna_anmData_s const m_anmDataTable[53];
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x568 */ request_of_phase_process_class mPhase;
|
/* 0x568 */ request_of_phase_process_class mPhase;
|
||||||
/* 0x570 */ J3DModel* mpModel;
|
/* 0x570 */ J3DModel* mpModel;
|
||||||
/* 0x574 */ J3DModel* mpShadowModel;
|
/* 0x574 */ J3DModel* mpShadowModel;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public:
|
|||||||
CPaneMgrAlpha* getNoDatBase() { return mNoDatBase; }
|
CPaneMgrAlpha* getNoDatBase() { return mNoDatBase; }
|
||||||
void draw() { _draw(); }
|
void draw() { _draw(); }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x04 */ JKRArchive* mArchive;
|
/* 0x04 */ JKRArchive* mArchive;
|
||||||
/* 0x08 */ dDlst_FileInfo_c mFileInfo;
|
/* 0x08 */ dDlst_FileInfo_c mFileInfo;
|
||||||
/* 0x1C */ u8 field_0x1c[4];
|
/* 0x1C */ u8 field_0x1c[4];
|
||||||
|
|||||||
@@ -13,36 +13,8 @@ class dFile_info_c;
|
|||||||
class J2DPicture;
|
class J2DPicture;
|
||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
static PaneCache mSelDtPanes[] = {
|
|
||||||
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('gray_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('b_base'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('b_base1'), 0.0f, 0.0f, false},
|
|
||||||
};
|
|
||||||
|
|
||||||
static PaneCache fileSelPanes[] = {
|
|
||||||
{MULTI_CHAR('w_uzu00'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu01'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu02'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu03'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu04'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu05'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu06'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu07'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu08'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_uzu09'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_er_msg'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_er_msE'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_er_msR'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('er_for0'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('er_for1'), 0.0f, 0.0f, false},
|
|
||||||
};
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class dDlst_FileSel_c : public dDlst_base_c {
|
class dDlst_FileSel_c : public dDlst_base_c {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public:
|
|||||||
|
|
||||||
void drawSelf() { mpKanteraIcon->draw(); }
|
void drawSelf() { mpKanteraIcon->draw(); }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x04 */ dDlst_KanteraIcon_c* mpKanteraIcon;
|
/* 0x04 */ dDlst_KanteraIcon_c* mpKanteraIcon;
|
||||||
/* 0x08 */ CPaneMgr* mpParent;
|
/* 0x08 */ CPaneMgr* mpParent;
|
||||||
/* 0x0C */ CPaneMgr* mpGauge;
|
/* 0x0C */ CPaneMgr* mpGauge;
|
||||||
|
|||||||
@@ -15,49 +15,6 @@ class dMenu_Fishing_c;
|
|||||||
class dMenu_Skill_c;
|
class dMenu_Skill_c;
|
||||||
class dMenu_Insect_c;
|
class dMenu_Insect_c;
|
||||||
class dSelect_cursor_c;
|
class dSelect_cursor_c;
|
||||||
|
|
||||||
#if TARGET_PC
|
|
||||||
struct PaneCache {
|
|
||||||
u64 tag;
|
|
||||||
f32 origTransX;
|
|
||||||
f32 origTransY;
|
|
||||||
bool cached;
|
|
||||||
};
|
|
||||||
|
|
||||||
static PaneCache mpScreenPanes[] = {
|
|
||||||
{MULTI_CHAR('sa_tex_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('op_tex_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('heart_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('wolf_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('item_0_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('item_1_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('item_2_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('fish_3_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('lett_4_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('maki_5_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('kabu_6n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('t_t00'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('f_t00'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('itemn_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('infotxtn'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('sa_op_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('title_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('menu_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('w_er_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('center_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('info_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('lavel_n'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('modelbgn'), 0.0f, 0.0f, false},
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class dMenu_Collect2D_c;
|
class dMenu_Collect2D_c;
|
||||||
class dMenu_Collect2DTop_c : public dDlst_base_c {
|
class dMenu_Collect2DTop_c : public dDlst_base_c {
|
||||||
public:
|
public:
|
||||||
@@ -172,7 +129,7 @@ public:
|
|||||||
J2DPicture* getBlackTex() { return mpBlackTex; }
|
J2DPicture* getBlackTex() { return mpBlackTex; }
|
||||||
u8 getSubWindowOpenCheck() { return mSubWindowOpenCheck; }
|
u8 getSubWindowOpenCheck() { return mSubWindowOpenCheck; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||||
/* 0x008 */ JKRExpHeap* mpSubHeap;
|
/* 0x008 */ JKRExpHeap* mpSubHeap;
|
||||||
/* 0x00C */ void* field_0xc;
|
/* 0x00C */ void* field_0xc;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
|
|
||||||
u8 getStatus() { return mStatus; }
|
u8 getStatus() { return mStatus; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||||
/* 0x008 */ JKRArchive* mpArchive;
|
/* 0x008 */ JKRArchive* mpArchive;
|
||||||
/* 0x00C */ STControl* mpStick;
|
/* 0x00C */ STControl* mpStick;
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ public:
|
|||||||
|
|
||||||
static DUSK_GAME_DATA dMenu_Fmap_c* MyClass;
|
static DUSK_GAME_DATA dMenu_Fmap_c* MyClass;
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||||
/* 0x008 */ JKRExpHeap* mpTalkHeap;
|
/* 0x008 */ JKRExpHeap* mpTalkHeap;
|
||||||
/* 0x00C */ STControl* mpStick;
|
/* 0x00C */ STControl* mpStick;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public:
|
|||||||
|
|
||||||
u8 getStatus() { return mStatus; }
|
u8 getStatus() { return mStatus; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x04 */ JKRExpHeap* mpHeap;
|
/* 0x04 */ JKRExpHeap* mpHeap;
|
||||||
/* 0x08 */ JKRArchive* mpArchive;
|
/* 0x08 */ JKRArchive* mpArchive;
|
||||||
/* 0x0C */ STControl* mpStick;
|
/* 0x0C */ STControl* mpStick;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public:
|
|||||||
|
|
||||||
u8 getStatus() { return mStatus; }
|
u8 getStatus() { return mStatus; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||||
/* 0x008 */ JKRArchive* mpArchive;
|
/* 0x008 */ JKRArchive* mpArchive;
|
||||||
/* 0x00C */ STControl* mpStick;
|
/* 0x00C */ STControl* mpStick;
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ public:
|
|||||||
return ((u8)mUseFlag & (u8)i_flag) != 0 ? true : false;
|
return ((u8)mUseFlag & (u8)i_flag) != 0 ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ J2DScreen* mpBackScreen;
|
/* 0x004 */ J2DScreen* mpBackScreen;
|
||||||
/* 0x008 */ J2DScreen* mpScreen;
|
/* 0x008 */ J2DScreen* mpScreen;
|
||||||
/* 0x00C */ J2DScreen* mpClipScreen;
|
/* 0x00C */ J2DScreen* mpClipScreen;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ public:
|
|||||||
void drawFlag0() { mDrawFlag = 0; }
|
void drawFlag0() { mDrawFlag = 0; }
|
||||||
void setStatus(u8 i_status) { mStatus = i_status; }
|
void setStatus(u8 i_status) { mStatus = i_status; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||||
/* 0x008 */ STControl* mpStick;
|
/* 0x008 */ STControl* mpStick;
|
||||||
/* 0x00C */ CSTControl* mpCStick;
|
/* 0x00C */ CSTControl* mpCStick;
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ public:
|
|||||||
u8 getEndStatus() { return mEndStatus; }
|
u8 getEndStatus() { return mEndStatus; }
|
||||||
void setUseType(u8 type) { mUseType = type; }
|
void setUseType(u8 type) { mUseType = type; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x0004 */ JKRArchive* mpArchive;
|
/* 0x0004 */ JKRArchive* mpArchive;
|
||||||
/* 0x0008 */ mDoDvdThd_mountArchive_c* mpMount;
|
/* 0x0008 */ mDoDvdThd_mountArchive_c* mpMount;
|
||||||
/* 0x000C */ STControl* stick;
|
/* 0x000C */ STControl* stick;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public:
|
|||||||
|
|
||||||
u8 getStatus() { return mStatus; }
|
u8 getStatus() { return mStatus; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||||
/* 0x008 */ JKRArchive* mpArchive;
|
/* 0x008 */ JKRArchive* mpArchive;
|
||||||
/* 0x00C */ STControl* mpStick;
|
/* 0x00C */ STControl* mpStick;
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ item_params mItemParams[4];
|
/* 0x004 */ item_params mItemParams[4];
|
||||||
/* 0x074 */ JKRExpHeap* heap;
|
/* 0x074 */ JKRExpHeap* heap;
|
||||||
/* 0x078 */ J2DScreen* mpScreen;
|
/* 0x078 */ J2DScreen* mpScreen;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public:
|
|||||||
void setTextBoxPtr(J2DTextBox* p_textBox) { mpTextBoxPtr = p_textBox; }
|
void setTextBoxPtr(J2DTextBox* p_textBox) { mpTextBoxPtr = p_textBox; }
|
||||||
void resetType() { mType = 0x47; }
|
void resetType() { mType = 0x47; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x04 */ J2DTextBox* mpTextBoxPtr;
|
/* 0x04 */ J2DTextBox* mpTextBoxPtr;
|
||||||
/* 0x08 */ f32 mPosX;
|
/* 0x08 */ f32 mPosX;
|
||||||
/* 0x0C */ f32 mPosY;
|
/* 0x0C */ f32 mPosY;
|
||||||
@@ -53,7 +53,7 @@ public:
|
|||||||
|
|
||||||
void setRupeeColor(u8 color) { mRupeeColor = color; }
|
void setRupeeColor(u8 color) { mRupeeColor = color; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x004 */ COutFontSet_c* mpOfs[35];
|
/* 0x004 */ COutFontSet_c* mpOfs[35];
|
||||||
/* 0x090 */ J2DPicture* mpPane[70];
|
/* 0x090 */ J2DPicture* mpPane[70];
|
||||||
/* 0x1A8 */ f32 mAlphaRatio;
|
/* 0x1A8 */ f32 mAlphaRatio;
|
||||||
|
|||||||
@@ -12,34 +12,6 @@ class J2DTextBox;
|
|||||||
class JUTFont;
|
class JUTFont;
|
||||||
class STControl;
|
class STControl;
|
||||||
|
|
||||||
#if TARGET_PC
|
|
||||||
struct PaneCache {
|
|
||||||
u64 tag;
|
|
||||||
f32 origTransX;
|
|
||||||
f32 origTransY;
|
|
||||||
bool cached;
|
|
||||||
};
|
|
||||||
|
|
||||||
static PaneCache l_tagName[] = {
|
|
||||||
{MULTI_CHAR('m_00_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_3'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('m_01_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_2'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('m03_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_1'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('m_05_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_0'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('m_07_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_4'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('m_09_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_3'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('m_10_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_2'), 0.0f, 0.0f, false},
|
|
||||||
{MULTI_CHAR('m12_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_0'), 0.0f, 0.0f, false},
|
|
||||||
};
|
|
||||||
|
|
||||||
static PaneCache l_nameTagName[] = {
|
|
||||||
{MULTI_CHAR('name_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_07'), 0.0f, 0.0f, false},
|
|
||||||
};
|
|
||||||
|
|
||||||
static PaneCache l_nameCurTagName[] = {
|
|
||||||
{MULTI_CHAR('s__n_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_07'), 0.0f, 0.0f, false},
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class dNm_HIO_c {
|
class dNm_HIO_c {
|
||||||
public:
|
public:
|
||||||
dNm_HIO_c();
|
dNm_HIO_c();
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public:
|
|||||||
JKRMemArchive* getArchive() const { return mArchive; }
|
JKRMemArchive* getArchive() const { return mArchive; }
|
||||||
JKRHeap* getHeap() const { return mHeap; }
|
JKRHeap* getHeap() const { return mHeap; }
|
||||||
|
|
||||||
private:
|
// private:
|
||||||
/* 0x14 */ u8 mMountDirection;
|
/* 0x14 */ u8 mMountDirection;
|
||||||
/* 0x18 */ s32 mEntryNumber;
|
/* 0x18 */ s32 mEntryNumber;
|
||||||
/* 0x1C */ JKRMemArchive* mArchive;
|
/* 0x1C */ JKRMemArchive* mArchive;
|
||||||
@@ -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,6 +2,7 @@
|
|||||||
#define JASCALC_H
|
#define JASCALC_H
|
||||||
|
|
||||||
#include <types.h>
|
#include <types.h>
|
||||||
|
#include <cstring>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,16 +11,31 @@
|
|||||||
*/
|
*/
|
||||||
struct JASCalc {
|
struct JASCalc {
|
||||||
static void imixcopy(const s16*, const s16*, s16*, u32);
|
static void imixcopy(const s16*, const s16*, s16*, u32);
|
||||||
static void bcopyfast(const void* src, void* dest, u32 size);
|
#if TARGET_PC
|
||||||
|
static void bcopyfast(const void* src, void* dest, u32 size) {
|
||||||
|
std::memcpy(dest, src, size);
|
||||||
|
}
|
||||||
#if TARGET_ANDROID
|
#if TARGET_ANDROID
|
||||||
static void _bcopy(const void* src, void* dest, u32 size);
|
static void _bcopy(const void* src, void* dest, u32 size) {
|
||||||
#else
|
#else
|
||||||
static void bcopy(const void* src, void* dest, u32 size);
|
static void bcopy(const void* src, void* dest, u32 size) {
|
||||||
#endif
|
#endif
|
||||||
static void bzerofast(void* dest, u32 size);
|
std::memcpy(dest, src, size);
|
||||||
|
}
|
||||||
|
static void bzerofast(void* dest, u32 size) {
|
||||||
|
std::memset(dest, 0, size);
|
||||||
|
}
|
||||||
#if TARGET_ANDROID
|
#if TARGET_ANDROID
|
||||||
static void _bzero(void* dest, u32 size);
|
static void _bzero(void* dest, u32 size) {
|
||||||
#else
|
#else
|
||||||
|
static void bzero(void* dest, u32 size) {
|
||||||
|
#endif
|
||||||
|
std::memset(dest, 0, size);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void bcopyfast(const void* src, void* dest, u32 size);
|
||||||
|
static void bcopy(const void* src, void* dest, u32 size);
|
||||||
|
static void bzerofast(void* dest, u32 size);
|
||||||
static void bzero(void* dest, u32 size);
|
static void bzero(void* dest, u32 size);
|
||||||
#endif
|
#endif
|
||||||
static f32 pow2(f32);
|
static f32 pow2(f32);
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ void JASCalc::imixcopy(const s16* s1, const s16* s2, s16* dst, u32 n) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !TARGET_PC
|
||||||
void JASCalc::bcopyfast(const void* src, void* dest, u32 size) {
|
void JASCalc::bcopyfast(const void* src, void* dest, u32 size) {
|
||||||
JUT_ASSERT(226, (reinterpret_cast<uintptr_t>(src) & 0x03) == 0);
|
JUT_ASSERT(226, (reinterpret_cast<uintptr_t>(src) & 0x03) == 0);
|
||||||
JUT_ASSERT(227, (reinterpret_cast<uintptr_t>(dest) & 0x03) == 0);
|
JUT_ASSERT(227, (reinterpret_cast<uintptr_t>(dest) & 0x03) == 0);
|
||||||
@@ -33,11 +34,7 @@ void JASCalc::bcopyfast(const void* src, void* dest, u32 size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TARGET_ANDROID
|
|
||||||
void JASCalc::_bcopy(const void* src, void* dest, u32 size) {
|
|
||||||
#else
|
|
||||||
void JASCalc::bcopy(const void* src, void* dest, u32 size) {
|
void JASCalc::bcopy(const void* src, void* dest, u32 size) {
|
||||||
#endif
|
|
||||||
u32* usrc;
|
u32* usrc;
|
||||||
u32* udest;
|
u32* udest;
|
||||||
|
|
||||||
@@ -94,11 +91,7 @@ void JASCalc::bzerofast(void* dest, u32 size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TARGET_ANDROID
|
|
||||||
void JASCalc::_bzero(void* dest, u32 size) {
|
|
||||||
#else
|
|
||||||
void JASCalc::bzero(void* dest, u32 size) {
|
void JASCalc::bzero(void* dest, u32 size) {
|
||||||
#endif
|
|
||||||
u32* udest;
|
u32* udest;
|
||||||
u8* bdest = (u8*)dest;
|
u8* bdest = (u8*)dest;
|
||||||
if ((size & 0x1f) == 0 && (reinterpret_cast<uintptr_t>(dest) & 0x1f) == 0) {
|
if ((size & 0x1f) == 0 && (reinterpret_cast<uintptr_t>(dest) & 0x1f) == 0) {
|
||||||
@@ -139,6 +132,7 @@ void JASCalc::bzero(void* dest, u32 size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if AVOID_UB
|
#if AVOID_UB
|
||||||
DUSK_GAME_DATA s16 const JASCalc::CUTOFF_TO_IIR_TABLE[129][4] = {
|
DUSK_GAME_DATA s16 const JASCalc::CUTOFF_TO_IIR_TABLE[129][4] = {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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*);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.25)
|
||||||
|
project(basic_cosmetics_mod CXX)
|
||||||
|
|
||||||
|
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||||
|
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root")
|
||||||
|
option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF)
|
||||||
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
if (DUSK_MOD_USE_FULL_TREE)
|
||||||
|
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
|
||||||
|
else ()
|
||||||
|
add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(COSMETIC_SOURCES src/color_utils.cpp src/midna_hair_color.cpp src/texture_utils.cpp src/hooks.cpp)
|
||||||
|
|
||||||
|
add_mod(basic_cosmetics_mod
|
||||||
|
FEATURES game fmt
|
||||||
|
SOURCES src/mod.cpp ${COSMETIC_SOURCES}
|
||||||
|
MOD_JSON mod.json
|
||||||
|
RES_DIR res
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"id": "dev.twilitrealm.basic_cosmetics",
|
||||||
|
"name": "Basic Cosmetics",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Twilit Realm",
|
||||||
|
"description": "Change Basic Cosmetic Colors"
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
#include "color_utils.hpp"
|
||||||
|
|
||||||
|
uint8_t desaturate_rgb_565(uint16_t rgb565Val)
|
||||||
|
{
|
||||||
|
const uint32_t r = (rgb565Val & 0xf800) >> 11;
|
||||||
|
const uint32_t g = (rgb565Val & 0x7e0) >> 5;
|
||||||
|
const uint32_t b = rgb565Val & 0x1f;
|
||||||
|
|
||||||
|
// Here we are doing a quicker (0.22 * r + 0.72 * g + 0.06 * b) which
|
||||||
|
// uses multiplies and shifts rather than division.
|
||||||
|
const uint32_t combined = 30480413 * r + 49085341 * g + 8312839 * b;
|
||||||
|
uint8_t shifted = (combined >> 24) & 0xff;
|
||||||
|
|
||||||
|
// Check if should round up shifted value.
|
||||||
|
if (shifted < 0xff && combined & 0x00800000)
|
||||||
|
{
|
||||||
|
shifted += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shifted;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t blend_overlay_rgb_565(uint8_t grayVal, GXColor color)
|
||||||
|
{
|
||||||
|
uint32_t rTimes255, gTimes255, bTimes255;
|
||||||
|
|
||||||
|
if (grayVal <= 0x7f)
|
||||||
|
{
|
||||||
|
const uint32_t grayTimesTwo = 2 * grayVal;
|
||||||
|
|
||||||
|
rTimes255 = grayTimesTwo * color.r;
|
||||||
|
gTimes255 = grayTimesTwo * color.g;
|
||||||
|
bTimes255 = grayTimesTwo * color.b;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const uint32_t multiplier = 2 * (255 - grayVal);
|
||||||
|
|
||||||
|
rTimes255 = 255 * 255 - multiplier * (255 - color.r);
|
||||||
|
gTimes255 = 255 * 255 - multiplier * (255 - color.g);
|
||||||
|
bTimes255 = 255 * 255 - multiplier * (255 - color.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divide each by 255
|
||||||
|
const uint32_t r = (rTimes255 + 1 + (rTimes255 >> 8)) >> 8;
|
||||||
|
const uint32_t g = (gTimes255 + 1 + (gTimes255 >> 8)) >> 8;
|
||||||
|
const uint32_t b = (bTimes255 + 1 + (bTimes255 >> 8)) >> 8;
|
||||||
|
|
||||||
|
return ((r & 0xf8) << 8) | ((g & 0xfc) << 3) | ((b & 0xf8) >> 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to perform overlay blending on a single 8-bit color channel
|
||||||
|
uint8_t blend_overlay_channel(uint8_t base, uint8_t blend)
|
||||||
|
{
|
||||||
|
if (base < 128) {
|
||||||
|
return static_cast<uint8_t>((2 * base * blend) / 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<uint8_t>(255 - (2 * (255 - base) * (255 - blend)) / 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_valid_hex_color_str(std::string_view hexStr) {
|
||||||
|
return hexStr.find_first_not_of("0123456789ABCDEFabcdef") == std::string_view::npos && hexStr.length() == 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
GXColor hex_color_str_to_gx_color(const std::string& hexColorStr) {
|
||||||
|
u8 r = std::stoi(hexColorStr.substr(0, 2), nullptr, 16);
|
||||||
|
u8 g = std::stoi(hexColorStr.substr(2, 2), nullptr, 16);
|
||||||
|
u8 b = std::stoi(hexColorStr.substr(4, 2), nullptr, 16);
|
||||||
|
return GXColor{r, g, b};
|
||||||
|
}
|
||||||
|
|
||||||
|
static f32 rainbowPhaseAngle = 0.f;
|
||||||
|
|
||||||
|
GXColor get_rainbow_rgb(f32 amplitude) {
|
||||||
|
f32 phase_rad = rainbowPhaseAngle * M_PI / 180.0f;
|
||||||
|
|
||||||
|
u8 r_val = (u8)(amplitude * (sinf(phase_rad) + 1.0f) + 0.5f);
|
||||||
|
u8 g_val = (u8)(amplitude * (sinf(phase_rad + 2.0f * M_PI / 3.0f) + 1.0f) + 0.5f);
|
||||||
|
u8 b_val = (u8)(amplitude * (sinf(phase_rad + 4.0f * M_PI / 3.0f) + 1.0f));
|
||||||
|
GXColor rgbColor;
|
||||||
|
rgbColor.r = r_val;
|
||||||
|
rgbColor.g = g_val;
|
||||||
|
rgbColor.b = b_val;
|
||||||
|
rgbColor.a = 0xff;
|
||||||
|
return rgbColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_rainbow_rgb(f32 increment) {
|
||||||
|
rainbowPhaseAngle += increment;
|
||||||
|
if (rainbowPhaseAngle >= 360.0f) {
|
||||||
|
rainbowPhaseAngle -= 360.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File originally copied from console TPR with permission from isaac
|
||||||
|
* https://github.com/zsrtp/libtp_rel/blob/master/include/util/color_utils.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gx.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Desaturates an RGB565 color to a u8 gray value (0xFF being white and 0x00
|
||||||
|
// being black).
|
||||||
|
uint8_t desaturate_rgb_565(uint16_t rgb565Val);
|
||||||
|
|
||||||
|
// Performs an "Overlay" blend of a u8 gray value and a pointer to a u8
|
||||||
|
// array of {r,g,b}. Returns the result as an RGB565.
|
||||||
|
uint16_t blend_overlay_rgb_565(uint8_t grayVal, GXColor color);
|
||||||
|
|
||||||
|
// Perform overlay blending on a single 8-bit color channel
|
||||||
|
uint8_t blend_overlay_channel(uint8_t base, uint8_t blend);
|
||||||
|
|
||||||
|
bool is_valid_hex_color_str(std::string_view hexStr);
|
||||||
|
|
||||||
|
GXColor hex_color_str_to_gx_color(const std::string& hexColorStr);
|
||||||
|
|
||||||
|
GXColor get_rainbow_rgb(f32 amplitude);
|
||||||
|
|
||||||
|
void update_rainbow_rgb(f32 increment);
|
||||||
@@ -0,0 +1,694 @@
|
|||||||
|
#include "hooks.hpp"
|
||||||
|
|
||||||
|
#include "mod.hpp"
|
||||||
|
#include "color_utils.hpp"
|
||||||
|
#include "midna_hair_color.hpp"
|
||||||
|
#include "texture_utils.hpp"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
#include "mods/svc/hook.hpp"
|
||||||
|
#include "mods/svc/log.hpp"
|
||||||
|
|
||||||
|
#include "d/actor/d_a_alink.h"
|
||||||
|
#include "d/actor/d_a_midna.h"
|
||||||
|
#include "d/d_a_item_static.h"
|
||||||
|
#include "d/d_bright_check.h"
|
||||||
|
#include "d/d_file_sel_info.h"
|
||||||
|
#include "d/d_file_select.h"
|
||||||
|
#include "d/d_kankyo.h"
|
||||||
|
#include "d/d_kantera_icon_meter.h"
|
||||||
|
#include "d/d_menu_collect.h"
|
||||||
|
#include "d/d_menu_fishing.h"
|
||||||
|
#include "d/d_menu_fmap2D.h"
|
||||||
|
#include "d/d_menu_insect.h"
|
||||||
|
#include "d/d_menu_letter.h"
|
||||||
|
#include "d/d_menu_option.h"
|
||||||
|
#include "d/d_menu_ring.h"
|
||||||
|
#include "d/d_menu_save.h"
|
||||||
|
#include "d/d_menu_skill.h"
|
||||||
|
#include "d/d_meter_button.h"
|
||||||
|
#include "d/d_meter_hakusha.h"
|
||||||
|
#include "d/d_meter2.h"
|
||||||
|
#include "d/d_meter2_draw.h"
|
||||||
|
#include "d/d_meter2_info.h"
|
||||||
|
#include "d/d_msg_object.h"
|
||||||
|
#include "d/d_msg_out_font.h"
|
||||||
|
#include "d/d_msg_scrn_base.h"
|
||||||
|
#include "d/d_pane_class.h"
|
||||||
|
#include "m_Do/m_Do_dvd_thread.h"
|
||||||
|
#include "SSystem/SComponent/c_phase.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
// Main hook for recoloring textures on load
|
||||||
|
DEFINE_HOOK_SYMBOL(
|
||||||
|
"mDoDvdThd_mountArchive_c::execute", s32(mDoDvdThd_mountArchive_c*), MountArchiveExecute);
|
||||||
|
void mount_archive_execute_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
auto archive = mods::arg<mDoDvdThd_mountArchive_c*>(args, 0);
|
||||||
|
handle_texture_overrides_on_load(archive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for getting configVar color
|
||||||
|
static std::optional<GXColor> get_config_var_color(ConfigVarHandle handle, bool allowRainbow = false) {
|
||||||
|
auto colorStr = get_str_option(handle, "");
|
||||||
|
|
||||||
|
// Convert to lowercase
|
||||||
|
for (auto& c : colorStr) {
|
||||||
|
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_valid_hex_color_str(colorStr) || (colorStr == "rainbow" && allowRainbow)) {
|
||||||
|
if (is_valid_hex_color_str(colorStr)) {
|
||||||
|
return hex_color_str_to_gx_color(colorStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowRainbow) {
|
||||||
|
// Assume rainbow if not a valid hex str
|
||||||
|
auto color = get_rainbow_rgb(127.5f);
|
||||||
|
color.r /= 2;
|
||||||
|
color.g /= 2;
|
||||||
|
color.b /= 2;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lantern ambience color
|
||||||
|
DEFINE_HOOK(&dKy_WolfEyeLight_set, WolfEyeLightSet);
|
||||||
|
void wolf_eye_light_set_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true);
|
||||||
|
if (maybeLanternColor.has_value()) {
|
||||||
|
auto lanternColor = maybeLanternColor.value();
|
||||||
|
|
||||||
|
dScnKy_env_light_c* kankyo = dKy_getEnvlight();
|
||||||
|
kankyo->field_0x0c18[0].mColor.r = lanternColor.r;
|
||||||
|
kankyo->field_0x0c18[0].mColor.g = lanternColor.g;
|
||||||
|
kankyo->field_0x0c18[0].mColor.b = lanternColor.b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lantern Sphere color
|
||||||
|
DEFINE_HOOK(&daAlink_c::preKandelaarDraw, PreKandelaarDraw);
|
||||||
|
void pre_kandelaar_draw_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true);
|
||||||
|
if (maybeLanternColor.has_value()) {
|
||||||
|
auto lanternColor = maybeLanternColor.value();
|
||||||
|
|
||||||
|
J3DMaterial* mat_p = daAlink_getAlinkActorClass()->mpKanteraGlowModel->getModelData()->getMaterialNodePointer(0);
|
||||||
|
|
||||||
|
J3DGXColorS10 color;
|
||||||
|
color.r = lanternColor.r;
|
||||||
|
color.g = lanternColor.g;
|
||||||
|
color.b = lanternColor.b;
|
||||||
|
color.a = 255;
|
||||||
|
mat_p->setTevColor(1, &color);
|
||||||
|
|
||||||
|
color.r = lanternColor.r;
|
||||||
|
color.g = lanternColor.g;
|
||||||
|
color.b = lanternColor.b;
|
||||||
|
mat_p->setTevColor(2, &color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main Lantern Meter Color
|
||||||
|
DEFINE_HOOK(&CPaneMgr::setBlackWhite, CPaneMgrSetBlackWhite);
|
||||||
|
HookAction cpane_mgr_set_black_white_pre(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
// Check for magic meter
|
||||||
|
auto pane = mods::arg<CPaneMgr*>(args, 0);
|
||||||
|
if (dMeter2Info_getMeterClass() == NULL || pane != dMeter2Info_getMeterClass()->getMeterDrawPtr()->mpMagicMeter) {
|
||||||
|
return HOOK_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If magic meter, check to see we're setting lantern colors
|
||||||
|
auto& black = mods::arg_ref<JUtility::TColor>(args, 1);
|
||||||
|
auto& white = mods::arg_ref<JUtility::TColor>(args, 2);
|
||||||
|
if (black != JUtility::TColor(255, 255, 140, 255) &&
|
||||||
|
white != JUtility::TColor(230, 170, 0, 255)) {
|
||||||
|
return HOOK_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true);
|
||||||
|
if (maybeLanternColor.has_value()) {
|
||||||
|
auto lanternColor = maybeLanternColor.value();
|
||||||
|
black = JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255);
|
||||||
|
white = JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HOOK_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lantern Icon Meter Color
|
||||||
|
DEFINE_HOOK(&dKantera_icon_c::setNowGauge, KanteraIconSetNowGauge);
|
||||||
|
void kantera_icon_set_now_gauge_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true);
|
||||||
|
if (maybeLanternColor.has_value()) {
|
||||||
|
auto lanternColor = maybeLanternColor.value();
|
||||||
|
|
||||||
|
auto kanteraIcon = mods::arg<dKantera_icon_c*>(args, 0);
|
||||||
|
kanteraIcon->mpGauge->setBlackWhite(
|
||||||
|
JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255),
|
||||||
|
JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Light Sword Effect Color
|
||||||
|
DEFINE_HOOK(&daAlink_c::setLightningSwordEffect, SetLightningSwordEffect);
|
||||||
|
void set_lightning_sword_effect_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
auto maybeGlowColor = get_config_var_color(get_cvars().lightSwordGlowColor, true);
|
||||||
|
if (maybeGlowColor.has_value()) {
|
||||||
|
auto glowColor = maybeGlowColor.value();
|
||||||
|
|
||||||
|
auto link = mods::arg<daAlink_c*>(args, 0);
|
||||||
|
// Check copied from inside the hooked function
|
||||||
|
if (link->mEquipItem == 0x103 && link->checkNoResetFlg3(daPy_py_c::FLG3_UNK_100000)) {
|
||||||
|
for (size_t i = 0; i < 3; i++) {
|
||||||
|
auto emitter = dComIfGp_particle_getEmitter(link->field_0x327c[i]);
|
||||||
|
if (emitter != NULL) {
|
||||||
|
emitter->setGlobalEnvColor(glowColor.r, glowColor.g, glowColor.b);
|
||||||
|
emitter->setGlobalPrmColor(glowColor.r, glowColor.g, glowColor.b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recolor_ui_button(ConfigVarHandle option, u64 tag, J2DScreen* screen) {
|
||||||
|
auto buttonColorStr = get_str_option(option, "");
|
||||||
|
if (is_valid_hex_color_str(buttonColorStr)) {
|
||||||
|
auto color = hex_color_str_to_gx_color(buttonColorStr);
|
||||||
|
auto element = static_cast<J2DPicture*>(screen->search(tag));
|
||||||
|
if (element != nullptr) {
|
||||||
|
element->setBlackWhite(JUtility::TColor(0, 0, 0, 0),
|
||||||
|
JUtility::TColor(color.r, color.g, color.b, 0xFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main gameplay UI. Top left hearts and top right buttons
|
||||||
|
DEFINE_HOOK(&dMeter2Draw_c::init, dMeter2Init);
|
||||||
|
void d_meter_2_init_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto dMeter2Draw = mods::arg<dMeter2Draw_c*>(args, 0);
|
||||||
|
auto screen = dMeter2Draw->getMainScreenPtr();
|
||||||
|
|
||||||
|
// Heart tags on main UI
|
||||||
|
static constexpr std::array kHeartTags = {
|
||||||
|
MULTI_CHAR('hear_00'), MULTI_CHAR('hear_01'), MULTI_CHAR('hear_02'), MULTI_CHAR('hear_03'), MULTI_CHAR('hear_04'), MULTI_CHAR('hear_05'), MULTI_CHAR('hear_06'),
|
||||||
|
MULTI_CHAR('hear_07'), MULTI_CHAR('hear_08'), MULTI_CHAR('hear_09'), MULTI_CHAR('hear_10'), MULTI_CHAR('hear_11'), MULTI_CHAR('hear_12'), MULTI_CHAR('hear_13'),
|
||||||
|
MULTI_CHAR('hear_14'), MULTI_CHAR('hear_15'), MULTI_CHAR('hear_16'), MULTI_CHAR('hear_17'), MULTI_CHAR('hear_18'), MULTI_CHAR('hear_19'), MULTI_CHAR('bigh_00'),
|
||||||
|
MULTI_CHAR('bigh_01'), MULTI_CHAR('bigh_02'), MULTI_CHAR('bigh_03')
|
||||||
|
};
|
||||||
|
|
||||||
|
auto maybeHeartColor = get_config_var_color(get_cvars().heartColor);
|
||||||
|
if (maybeHeartColor.has_value()) {
|
||||||
|
auto heartColor = maybeHeartColor.value();
|
||||||
|
for (auto tag : kHeartTags) {
|
||||||
|
auto element = static_cast<J2DPicture*>(screen->search(tag));
|
||||||
|
if (element != nullptr) {
|
||||||
|
element->setBlackWhite(heartColor, JUtility::TColor(200, 200, 200, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('x_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('y_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hearts on the file select screen
|
||||||
|
DEFINE_HOOK(&dFile_info_c::setHeartCnt, FileInfoSetHeartCount);
|
||||||
|
void file_info_set_heart_count_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto fileInfo = mods::arg<dFile_info_c*>(args, 0);
|
||||||
|
|
||||||
|
// Heart tags on file select
|
||||||
|
static constexpr std::array kFileSelectHeartTags {
|
||||||
|
MULTI_CHAR('hear_20'), MULTI_CHAR('hear_21'), MULTI_CHAR('hear_22'), MULTI_CHAR('hear_23'), MULTI_CHAR('hear_24'), MULTI_CHAR('hear_25'), MULTI_CHAR('hear_26'),
|
||||||
|
MULTI_CHAR('hear_27'), MULTI_CHAR('hear_28'), MULTI_CHAR('hear_29'), MULTI_CHAR('hear_30'), MULTI_CHAR('hear_31'), MULTI_CHAR('hear_32'), MULTI_CHAR('hear_33'),
|
||||||
|
MULTI_CHAR('hear_34'), MULTI_CHAR('hear_35'), MULTI_CHAR('hear_36'), MULTI_CHAR('hear_37'), MULTI_CHAR('hear_38'), MULTI_CHAR('hear_39'),
|
||||||
|
};
|
||||||
|
|
||||||
|
auto maybeHeartColor = get_config_var_color(get_cvars().heartColor);
|
||||||
|
if (maybeHeartColor.has_value()) {
|
||||||
|
auto heartColor = maybeHeartColor.value();
|
||||||
|
for (auto tag : kFileSelectHeartTags) {
|
||||||
|
auto element = static_cast<J2DPicture*>(fileInfo->mFileInfo.Scr->search(tag));
|
||||||
|
if (element != nullptr) {
|
||||||
|
element->setBlackWhite(heartColor, JUtility::TColor(200, 200, 200, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons that appear contextually at the bottom of the screen during gameplay
|
||||||
|
// (Are X, Y ever used there?)
|
||||||
|
DEFINE_HOOK(&dMeterButton_c::screenInitButton, ScreenInitButton);
|
||||||
|
void screen_init_button_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto meterButton = mods::arg<dMeterButton_c*>(args, 0);
|
||||||
|
auto screen = meterButton->mpButtonScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('x_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('y_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A and B buttons when saving a file
|
||||||
|
DEFINE_HOOK(&dMenu_save_c::screenSet, MenuSaveScreenSet);
|
||||||
|
void menu_save_screen_set_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuSave = mods::arg<dMenu_save_c*>(args, 0);
|
||||||
|
auto screen = menuSave->mSaveSel.Scr;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('wabtn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('wbbtn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A and B buttons when selecting a file
|
||||||
|
DEFINE_HOOK(&dFile_select_c::screenSet, FileSelectScreenSet);
|
||||||
|
void file_select_screen_set_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto fileSelect = mods::arg<dFile_select_c*>(args, 0);
|
||||||
|
auto screen = fileSelect->fileSel.Scr;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('wabtn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('wbbtn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A button on brightness check screen
|
||||||
|
DEFINE_HOOK(&dBrightCheck_c::screenSet, BrightCheckScreenSet);
|
||||||
|
void bright_check_screen_set_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto brightCheck = mods::arg<dBrightCheck_c*>(args, 0);
|
||||||
|
auto screen = brightCheck->mBrightCheck.Scr;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// X and Y buttons on the item wheel screen
|
||||||
|
DEFINE_HOOK(&dMenu_Ring_c::_create, MenuRingCreate);
|
||||||
|
void menu_ring_create_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuRing = mods::arg<dMenu_Ring_c*>(args, 0);
|
||||||
|
auto screen = menuRing->mpScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('xbtn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('ybtn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A and B buttons on the main pause screen
|
||||||
|
DEFINE_HOOK(&dMenu_Collect2D_c::_create, MenuCollect2DCreate);
|
||||||
|
void menu_collect_2D_create_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuCollect2D = mods::arg<dMenu_Collect2D_c*>(args, 0);
|
||||||
|
auto screen = menuCollect2D->mpScreenIcon;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A and B buttons on the fishing journal screen
|
||||||
|
DEFINE_HOOK(&dMenu_Fishing_c::screenSetDoIcon, MenuFishingScreenSetDoIcon);
|
||||||
|
void menu_fishing_screen_set_do_icon_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuFishing = mods::arg<dMenu_Fishing_c*>(args, 0);
|
||||||
|
auto screen = menuFishing->mpIconScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A and B buttons on the insect collection screen
|
||||||
|
DEFINE_HOOK(&dMenu_Insect_c::screenSetDoIcon, MenuInsectScreenSetDoIcon);
|
||||||
|
void menu_insect_screen_set_do_icon_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuInsect = mods::arg<dMenu_Insect_c*>(args, 0);
|
||||||
|
auto screen = menuInsect->mpIconScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A and B buttons on the letters screen
|
||||||
|
DEFINE_HOOK(&dMenu_Letter_c::screenSetDoIcon, MenuLetterScreenSetDoIcon);
|
||||||
|
void menu_letter_screen_set_do_icon_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuLetter = mods::arg<dMenu_Letter_c*>(args, 0);
|
||||||
|
auto screen = menuLetter->mpIconScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A, B, and Z buttons on option screen
|
||||||
|
DEFINE_HOOK(&dMenu_Option_c::_create, MenuOptionCreate);
|
||||||
|
void menu_option_create_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuOption = mods::arg<dMenu_Option_c*>(args, 0);
|
||||||
|
auto screen = menuOption->mpScreenIcon;
|
||||||
|
auto backScreen = menuOption->mpBackScreen;
|
||||||
|
auto tvScreen = menuOption->mpTVScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('g_zbtn'), backScreen);
|
||||||
|
|
||||||
|
// A Button on option's brightness check screen
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), tvScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A and B buttons on the hidden skills screen
|
||||||
|
DEFINE_HOOK(&dMenu_Skill_c::screenSetDoIcon, MenuSkillScreenSetDoIcon);
|
||||||
|
void menu_skill_screen_set_do_icon_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuSkill = mods::arg<dMenu_Skill_c*>(args, 0);
|
||||||
|
auto screen = menuSkill->mpIconScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A, B, and Z buttons on the map screen
|
||||||
|
DEFINE_HOOK(&dMenu_Fmap_c::_create, MenuFMapCreate);
|
||||||
|
void menu_fmap_create_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto menuFMap = mods::arg<dMenu_Fmap_c*>(args, 0);
|
||||||
|
auto screen = menuFMap->mpDraw2DTop->mpTitleScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||||
|
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn1'), screen);
|
||||||
|
recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A button on the howling screen
|
||||||
|
DEFINE_HOOK(&dMsgObject_c::talkStartInit, MsgObjectTalkStartInit);
|
||||||
|
void msg_object_talk_start_init_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto msgObject = mods::arg<dMsgObject_c*>(args, 0);
|
||||||
|
|
||||||
|
// If textbox kind is howling
|
||||||
|
if (msgObject->mFukiKind == 17) {
|
||||||
|
auto screen = msgObject->mpScrnDraw->mpScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('abtn'), screen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A button when on Epona
|
||||||
|
DEFINE_HOOK(&dMeterHakusha_c::_create, MeterHakushaCreate);
|
||||||
|
void meter_hakusha_create_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto meterHakusha = mods::arg<dMeterHakusha_c*>(args, 0);
|
||||||
|
auto screen = meterHakusha->mpButtonScreen;
|
||||||
|
|
||||||
|
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A, B, X, and Y button icons in text
|
||||||
|
DEFINE_HOOK(&COutFont_c::createPane, OutFontCreatePane);
|
||||||
|
void out_font_create_pane_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto outFont = mods::arg<COutFont_c*>(args, 0);
|
||||||
|
auto paneArr = outFont->mpPane;
|
||||||
|
|
||||||
|
auto maybeAButtonColor = get_config_var_color(get_cvars().aButtonColor);
|
||||||
|
if (maybeAButtonColor.has_value()) {
|
||||||
|
auto aButtonColor = maybeAButtonColor.value();
|
||||||
|
paneArr[0]->setBlackWhite(
|
||||||
|
JUtility::TColor(255, 255, 255, 0),
|
||||||
|
JUtility::TColor(aButtonColor.r,aButtonColor.g,aButtonColor.b, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maybeBButtonColor = get_config_var_color(get_cvars().bButtonColor);
|
||||||
|
if (maybeBButtonColor.has_value()) {
|
||||||
|
auto bButtonColor = maybeBButtonColor.value();
|
||||||
|
paneArr[1]->setBlackWhite(
|
||||||
|
JUtility::TColor(255, 255, 255, 0),
|
||||||
|
JUtility::TColor(bButtonColor.r,bButtonColor.g,bButtonColor.b, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition copied from hooked function
|
||||||
|
auto xyBlack = JUtility::TColor(255, 255, 255, 0);
|
||||||
|
if (outFont->field_0x242 == 1) {
|
||||||
|
xyBlack = JUtility::TColor(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maybeXButtonColor = get_config_var_color(get_cvars().xButtonColor);
|
||||||
|
if (maybeXButtonColor.has_value()) {
|
||||||
|
auto xButtonColor = maybeXButtonColor.value();
|
||||||
|
paneArr[5]->setBlackWhite(xyBlack, JUtility::TColor(xButtonColor.r,xButtonColor.g,xButtonColor.b, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maybeYButtonColor = get_config_var_color(get_cvars().yButtonColor);
|
||||||
|
if (maybeYButtonColor.has_value()) {
|
||||||
|
auto yButtonColor = maybeYButtonColor.value();
|
||||||
|
paneArr[6]->setBlackWhite(xyBlack, JUtility::TColor(yButtonColor.r,yButtonColor.g,yButtonColor.b, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heart icon in text
|
||||||
|
DEFINE_HOOK(&COutFontSet_c::drawFont, OutFontSetDrawFont);
|
||||||
|
void out_font_set_draw_font_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto outFontSet = mods::arg<COutFontSet_c*>(args, 0);
|
||||||
|
|
||||||
|
auto maybeHeartColor = get_config_var_color(get_cvars().heartColor);
|
||||||
|
if (maybeHeartColor.has_value()) {
|
||||||
|
auto heartColor = maybeHeartColor.value();
|
||||||
|
u32 heartColor_u32 = heartColor.r << 24 | heartColor.g << 16 | heartColor.b << 8 | 0xFF;
|
||||||
|
if (outFontSet->getType() == 0x1B) { // Heart icon type
|
||||||
|
outFontSet->mColor = heartColor_u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heart Drop/Piece of Heart/Heart Container model color
|
||||||
|
DEFINE_HOOK(&daItemBase_c::CreateItemHeap, ItemBaseCreateItemHeap);
|
||||||
|
void item_base_create_item_heap_post(ModContext*, void* args, void*, void*) {
|
||||||
|
auto itemBase = mods::arg<daItemBase_c*>(args, 0);
|
||||||
|
if (itemBase == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto itemNo = itemBase->m_itemNo;
|
||||||
|
if (itemNo == dItemNo_HEART_e || itemNo == dItemNo_UTAWA_HEART_e || itemNo == dItemNo_KAKERA_HEART_e) {
|
||||||
|
auto maybeHeartColor = get_config_var_color(get_cvars().heartColor);
|
||||||
|
if (maybeHeartColor.has_value()) {
|
||||||
|
auto heartColor = maybeHeartColor.value();
|
||||||
|
auto heartColorS10 = GXColorS10(heartColor.r, heartColor.g, heartColor.b, heartColor.a);
|
||||||
|
|
||||||
|
// Edit inner heart material color for Piece of Heart / Heart Container
|
||||||
|
if (itemNo == dItemNo_UTAWA_HEART_e || itemNo == dItemNo_KAKERA_HEART_e) {
|
||||||
|
*itemBase->mpModel->getModelData()->getMaterialNodePointer(3)->getTevKColor(1) = heartColor;
|
||||||
|
*itemBase->mpModel->getModelData()->getMaterialNodePointer(3)->getTevColor(1) = heartColorS10;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u8 heartColorRGB[3] = {heartColor.r,heartColor.g,heartColor.b};
|
||||||
|
u8** cRegTable = reinterpret_cast<u8**>(&itemBase->mpBrkAnm->getBrkAnm()->mAnmCRegDataR);
|
||||||
|
u8** kRegTable = reinterpret_cast<u8**>(&itemBase->mpBrkAnm->getBrkAnm()->mAnmKRegDataR);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
u8* cReg = cRegTable[i];
|
||||||
|
u8* kReg = kRegTable[i];
|
||||||
|
|
||||||
|
auto curColor = heartColorRGB[i];
|
||||||
|
|
||||||
|
// Set heart drop inner heart color
|
||||||
|
cReg[0x3] = curColor;
|
||||||
|
cReg[0xB] = curColor;
|
||||||
|
|
||||||
|
// Set heart drop outer heart color
|
||||||
|
kReg[0x3] = curColor;
|
||||||
|
kReg[0xB] = curColor;
|
||||||
|
|
||||||
|
if (itemNo == dItemNo_KAKERA_HEART_e) {
|
||||||
|
cReg[0x13] = curColor;
|
||||||
|
cReg[0x1B] = curColor;
|
||||||
|
kReg[0x13] = curColor;
|
||||||
|
kReg[0x1B] = curColor;
|
||||||
|
|
||||||
|
}
|
||||||
|
if (itemNo == dItemNo_UTAWA_HEART_e) {
|
||||||
|
cReg[0x13] = curColor;
|
||||||
|
kReg[0x13] = curColor;
|
||||||
|
kReg[0x1B] = curColor;
|
||||||
|
kReg[0x23] = curColor;
|
||||||
|
kReg[0x2B] = curColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Midna Hair Color
|
||||||
|
DEFINE_HOOK(&daMidna_c::create, MidnaCreate);
|
||||||
|
void midna_create_post(ModContext*, void* args, void* retval, void*) {
|
||||||
|
auto step = reinterpret_cast<int*>(retval);
|
||||||
|
// Don't set colors if midna isn't done loading
|
||||||
|
if (*step != cPhs_COMPLEATE_e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto midna = mods::arg<daMidna_c*>(args, 0);
|
||||||
|
midna->field_0x6e0 = *get_midna_hair_normalColor();
|
||||||
|
if (dKy_darkworld_check()) {
|
||||||
|
midna->field_0x6e8 = *get_midna_hair_normalKColor();
|
||||||
|
midna->field_0x6ec = *get_midna_hair_normalKColor2();
|
||||||
|
} else {
|
||||||
|
midna->field_0x6e8 = *get_midna_hair_lNormalKColor();
|
||||||
|
midna->field_0x6ec = *get_midna_hair_lNormalKColor2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override Midna Hair Color Part 2
|
||||||
|
DEFINE_HOOK(&daMidna_c::setBodyPartMatrix, MidnaSetBodyPartMatrix);
|
||||||
|
|
||||||
|
static GXColorS10 midnaField0x6e0{};
|
||||||
|
static GXColor midnaField0x6e8{};
|
||||||
|
static GXColor midnaField0x6ec{};
|
||||||
|
|
||||||
|
// Copy the original values before setBodyPartMatrix runs
|
||||||
|
HookAction midna_set_body_part_matrix_pre(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
auto midna = mods::arg<daMidna_c*>(args, 0);
|
||||||
|
|
||||||
|
midnaField0x6e0 = midna->field_0x6e0;
|
||||||
|
midnaField0x6e8 = midna->field_0x6e8;
|
||||||
|
midnaField0x6ec = midna->field_0x6ec;
|
||||||
|
|
||||||
|
return HOOK_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void midna_set_body_part_matrix_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
auto midna = mods::arg<daMidna_c*>(args, 0);
|
||||||
|
if (midna->mpHairhandBmd != NULL) {
|
||||||
|
// Restore the original values from before setBodyPartMatrix ran (this undoes the chase)
|
||||||
|
midna->field_0x6e0 = midnaField0x6e0;
|
||||||
|
midna->field_0x6e8 = midnaField0x6e8;
|
||||||
|
midna->field_0x6ec = midnaField0x6ec;
|
||||||
|
|
||||||
|
// Statement copied from inside function to determine colors
|
||||||
|
bool bigColors = midna->checkStateFlg0(daMidna_c::FLG0_UNK_10000000) ||
|
||||||
|
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_HAIR].mResID ||
|
||||||
|
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_S_TAKES].mResID ||
|
||||||
|
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_S_WAITS].mResID ||
|
||||||
|
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_S_PACKAWAY].mResID ||
|
||||||
|
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_GRABST].mResID ||
|
||||||
|
midna->checkEndResetStateFlg0(daMidna_c::ERFLG0_UNK_40) ||
|
||||||
|
dComIfGp_checkPlayerStatus1(0, 0x800000);
|
||||||
|
|
||||||
|
GXColorS10 color{};
|
||||||
|
GXColor kcolor1{};
|
||||||
|
GXColor kcolor2{};
|
||||||
|
|
||||||
|
// Set our own colors
|
||||||
|
if (bigColors) {
|
||||||
|
kcolor1 = *get_midna_hair_bigKColor();
|
||||||
|
if (dKy_darkworld_check()) {
|
||||||
|
color = *get_midna_hair_bigColor();
|
||||||
|
kcolor2 = *get_midna_hair_normalKColor2();
|
||||||
|
} else {
|
||||||
|
color = *get_midna_hair_lBigColor();
|
||||||
|
kcolor2 = *get_midna_hair_lBigKColor2();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
color = *get_midna_hair_normalColor();
|
||||||
|
if (dKy_darkworld_check()) {
|
||||||
|
kcolor1 = *get_midna_hair_normalKColor();
|
||||||
|
kcolor2 = *get_midna_hair_normalKColor2();
|
||||||
|
} else {
|
||||||
|
kcolor1 = *get_midna_hair_lNormalKColor();
|
||||||
|
kcolor2 = *get_midna_hair_lNormalKColor2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reapply the chase that happens in the function
|
||||||
|
cLib_chaseS(&midna->field_0x6e0.r, color.r, 10);
|
||||||
|
cLib_chaseS(&midna->field_0x6e0.g, color.g, 10);
|
||||||
|
cLib_chaseS(&midna->field_0x6e0.b, color.b, 10);
|
||||||
|
cLib_chaseUC(&midna->field_0x6e8.r, kcolor1.r, 10);
|
||||||
|
cLib_chaseUC(&midna->field_0x6e8.g, kcolor1.g, 10);
|
||||||
|
cLib_chaseUC(&midna->field_0x6e8.b, kcolor1.b, 10);
|
||||||
|
cLib_chaseUC(&midna->field_0x6ec.r, kcolor2.r, 10);
|
||||||
|
cLib_chaseUC(&midna->field_0x6ec.g, kcolor2.g, 10);
|
||||||
|
cLib_chaseUC(&midna->field_0x6ec.b, kcolor2.b, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Midna Charge Ring Color
|
||||||
|
DEFINE_HOOK(&daAlink_c::setWolfLockDomeModel, SetWolfLockDomeModel);
|
||||||
|
void wolf_lock_dome_model_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||||
|
auto domeRingColorStr = get_str_option(get_cvars().midnaChargeRingColor, "");
|
||||||
|
if (is_valid_hex_color_str(domeRingColorStr)) {
|
||||||
|
auto domeRingColor = hex_color_str_to_gx_color(domeRingColorStr);
|
||||||
|
const u8 domeWave1RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b};
|
||||||
|
const u8 domeWave2RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b};
|
||||||
|
u8** chromaRegisterTable = reinterpret_cast<u8**>(&daAlink_getAlinkActorClass()->field_0x0724->mAnmCRegDataR);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
u8* currentTable = chromaRegisterTable[i];
|
||||||
|
const u8 currentWave1Color = domeWave1RGBA[i];
|
||||||
|
const u8 currentWave2Color = domeWave2RGBA[i];
|
||||||
|
const u8 currentBaseColor = (currentWave1Color + currentWave2Color) / 2;
|
||||||
|
|
||||||
|
currentTable[0x3] = currentBaseColor; // Set Alpha for the ring base
|
||||||
|
currentTable[0x13] = currentWave1Color; // Set Alpha for ring wave 1
|
||||||
|
currentTable[0x23] = currentWave2Color; // Set Alpha for ring wave 2
|
||||||
|
currentTable[0xB] = currentBaseColor; // Set Alpha for darkworld ring base
|
||||||
|
currentTable[0x1B] = currentWave1Color; // Set Alpha for darkworld ring wave 1
|
||||||
|
currentTable[0x2B] = currentWave2Color; // Set Alpha for darkworld ring wave 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ADD_POST_HOOK(defined_hook, function, original) \
|
||||||
|
result = mods::hook::add_post<defined_hook>(function); \
|
||||||
|
if (result != MOD_OK) { \
|
||||||
|
mods::log::debug("failed to add post hook to" #original ", Result {}", static_cast<int>(result)); \
|
||||||
|
return result; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ADD_PRE_HOOK(defined_hook, function, original) \
|
||||||
|
result = mods::hook::add_pre<defined_hook>(function); \
|
||||||
|
if (result != MOD_OK) { \
|
||||||
|
mods::log::debug("failed to add pre hook to" #original ", Result {}", static_cast<int>(result)); \
|
||||||
|
return result; \
|
||||||
|
}
|
||||||
|
|
||||||
|
ModResult add_all_hooks() {
|
||||||
|
ModResult result{};
|
||||||
|
|
||||||
|
// Main hook for texture recoloring
|
||||||
|
ADD_POST_HOOK(MountArchiveExecute, mount_archive_execute_post, mountArchive_execute)
|
||||||
|
|
||||||
|
// Hooks for lantern glow
|
||||||
|
ADD_POST_HOOK(WolfEyeLightSet, wolf_eye_light_set_post, dKy_WolfEyeLight_set)
|
||||||
|
ADD_POST_HOOK(PreKandelaarDraw, pre_kandelaar_draw_post, daAlink_c::preKandelaarDraw)
|
||||||
|
|
||||||
|
// Hooks for lantern meter color
|
||||||
|
ADD_PRE_HOOK(CPaneMgrSetBlackWhite, cpane_mgr_set_black_white_pre, CPaneMgr::setBlackWhite)
|
||||||
|
ADD_POST_HOOK(KanteraIconSetNowGauge, kantera_icon_set_now_gauge_post, dKantera_icon_c::setNowGauge)
|
||||||
|
|
||||||
|
// Hook for midna charge ring
|
||||||
|
ADD_POST_HOOK(SetWolfLockDomeModel, wolf_lock_dome_model_post, daAlink_c::setWolfLockDomeModel)
|
||||||
|
|
||||||
|
// Hook for light sword glow
|
||||||
|
ADD_POST_HOOK(SetLightningSwordEffect, set_lightning_sword_effect_post, daAlink_c::setLightningSwordEffect)
|
||||||
|
|
||||||
|
// Hooks for Midna Hair Color
|
||||||
|
ADD_POST_HOOK(MidnaCreate, midna_create_post, daMidna_c::create)
|
||||||
|
ADD_PRE_HOOK(MidnaSetBodyPartMatrix, midna_set_body_part_matrix_pre, daMidna_c::setBodyPartMatrix)
|
||||||
|
ADD_POST_HOOK(MidnaSetBodyPartMatrix, midna_set_body_part_matrix_post, daMidna_c::setBodyPartMatrix)
|
||||||
|
|
||||||
|
// Hooks for UI colors
|
||||||
|
ADD_POST_HOOK(dMeter2Init, d_meter_2_init_post, dMeter2Draw_c::init)
|
||||||
|
ADD_POST_HOOK(FileInfoSetHeartCount, file_info_set_heart_count_post, dFile_info_c::setHeartCnt)
|
||||||
|
ADD_POST_HOOK(ScreenInitButton, screen_init_button_post, dMeterButton_c::screenInitButton)
|
||||||
|
ADD_POST_HOOK(MenuSaveScreenSet, menu_save_screen_set_post, dMenu_save_c::screenSet)
|
||||||
|
ADD_POST_HOOK(FileSelectScreenSet, file_select_screen_set_post, dFile_select_c::screenSet)
|
||||||
|
ADD_POST_HOOK(BrightCheckScreenSet, bright_check_screen_set_post, dBrightCheck_c::screenSet)
|
||||||
|
ADD_POST_HOOK(MenuRingCreate, menu_ring_create_post, dMenu_Ring_c::_create)
|
||||||
|
ADD_POST_HOOK(MenuCollect2DCreate, menu_collect_2D_create_post, dMenu_Collect2D_c::_create)
|
||||||
|
ADD_POST_HOOK(MenuFishingScreenSetDoIcon, menu_fishing_screen_set_do_icon_post, dMenu_Fishing_c::screenSetDoIcon)
|
||||||
|
ADD_POST_HOOK(MenuInsectScreenSetDoIcon, menu_insect_screen_set_do_icon_post, dMenu_Insect_c::screenSetDoIcon)
|
||||||
|
ADD_POST_HOOK(MenuLetterScreenSetDoIcon, menu_letter_screen_set_do_icon_post, dMenu_Letter_c::screenSetDoIcon)
|
||||||
|
ADD_POST_HOOK(MenuOptionCreate, menu_option_create_post, dMenu_Option_c::_create)
|
||||||
|
ADD_POST_HOOK(MenuSkillScreenSetDoIcon, menu_skill_screen_set_do_icon_post, dMenu_Skill_c::screenSetDoIcon)
|
||||||
|
ADD_POST_HOOK(OutFontCreatePane, out_font_create_pane_post, COutFont_c::createPane)
|
||||||
|
ADD_POST_HOOK(OutFontSetDrawFont, out_font_set_draw_font_post, COutFontSet_c::drawFont)
|
||||||
|
ADD_POST_HOOK(MenuFMapCreate, menu_fmap_create_post, dMenu_Fmap_c::_create)
|
||||||
|
ADD_POST_HOOK(MsgObjectTalkStartInit, msg_object_talk_start_init_post, dMsgObject_c::talkStartInit)
|
||||||
|
ADD_POST_HOOK(MeterHakushaCreate, meter_hakusha_create_post, dMeterHakusha_c::_create)
|
||||||
|
|
||||||
|
// Heart Model Color
|
||||||
|
ADD_POST_HOOK(ItemBaseCreateItemHeap, item_base_create_item_heap_post, daItemBase_c::CreateItemHeap)
|
||||||
|
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mods/service.hpp"
|
||||||
|
|
||||||
|
ModResult add_all_hooks();
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#include "midna_hair_color.hpp"
|
||||||
|
#include "mod.hpp"
|
||||||
|
|
||||||
|
#include "mods/svc/log.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
static constexpr std::array<std::array<GXColor, 9>, 10> midnaHairColors = {{
|
||||||
|
// Default
|
||||||
|
{{
|
||||||
|
/*l_lNormalKColor*/ /*l_normalKColor*/ /*l_normalColor*/
|
||||||
|
{0xFF, 0xDC, 0x00}, {0xB4, 0x87, 0x00}, {0x50, 0x00, 0x00},
|
||||||
|
/*l_bigKColor*/ /*l_lBigColor*/ /*l_bigColor*/
|
||||||
|
{0x50, 0x00, 0x00}, {0xFF, 0x78, 0x00}, {0xFF, 0x64, 0x78},
|
||||||
|
/*l_lNormalKColor2*//*l_normalKColor2*/ /*l_lBigKColor2*/
|
||||||
|
{0x00, 0xC3, 0xEB}, {0xC3, 0xC3, 0x00}, {0xAA, 0xFF, 0xC3}
|
||||||
|
}},
|
||||||
|
// Pink
|
||||||
|
{{
|
||||||
|
{0xF5, 0xCF, 0xF3}, {0xAD, 0x7F, 0x7F}, {0x1B, 0x00, 0x20},
|
||||||
|
{0x3C, 0x02, 0x58}, {0xE3, 0x72, 0xF2}, {0xE3, 0x5F, 0xF8},
|
||||||
|
{0xDD, 0x00, 0xEB}, {0xDD, 0x00, 0xC3}, {0xF6, 0x4C, 0xFF}
|
||||||
|
}},
|
||||||
|
// Red
|
||||||
|
{{
|
||||||
|
{0xE4, 0x65, 0x41}, {0xA1, 0x3E, 0x22}, {0x21, 0x00, 0x00},
|
||||||
|
{0x4F, 0x02, 0x01}, {0xF0, 0x3A, 0x25}, {0xF0, 0x30, 0x8C},
|
||||||
|
{0xEB, 0x00, 0x00}, {0xEB, 0x00, 0x00}, {0xFF, 0x4F, 0x3A}
|
||||||
|
}},
|
||||||
|
// Yellow
|
||||||
|
{{
|
||||||
|
{0x91, 0x83, 0x0E}, {0x66, 0x50, 0x07}, {0x0E, 0x0B, 0x00},
|
||||||
|
{0x24, 0x25, 0x02}, {0xCB, 0xB7, 0x00}, {0xCB, 0x99, 0x78},
|
||||||
|
{0xEB, 0xDE, 0x00}, {0xEB, 0xDE, 0x00}, {0xFF, 0xF8, 0xBF}
|
||||||
|
}},
|
||||||
|
// Green
|
||||||
|
{{
|
||||||
|
{0x35, 0x79, 0x53}, {0x25, 0x4A, 0x2B}, {0x00, 0x0E, 0x05},
|
||||||
|
{0x0A, 0x29, 0x10}, {0x00, 0xB6, 0x6F}, {0x00, 0x98, 0xB3},
|
||||||
|
{0x1F, 0xEB, 0x00}, {0x1F, 0xEB, 0x00}, {0x9A, 0xFF, 0x81}
|
||||||
|
}},
|
||||||
|
// Blue
|
||||||
|
{{
|
||||||
|
{0x00, 0x72, 0xFF}, {0x00, 0x46, 0x85}, {0x00, 0x08, 0x28},
|
||||||
|
{0x16, 0x1B, 0x5D}, {0x00, 0x60, 0xFF}, {0x00, 0x50, 0xFF},
|
||||||
|
{0x00, 0x48, 0xEB}, {0x00, 0x48, 0xC3}, {0x3A, 0x66, 0xFF}
|
||||||
|
}},
|
||||||
|
// Purple
|
||||||
|
{{
|
||||||
|
{0x6F, 0x34, 0xFF}, {0x4E, 0x20, 0x85}, {0x0D, 0x00, 0x34},
|
||||||
|
{0x15, 0x08, 0x79}, {0x62, 0x00, 0xFF}, {0x62, 0x00, 0xFF},
|
||||||
|
{0x7B, 0x00, 0xEB}, {0x7B, 0x00, 0xC3}, {0x94, 0x3E, 0xFF}
|
||||||
|
}},
|
||||||
|
// Brown
|
||||||
|
{{
|
||||||
|
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x1A, 0x05, 0x00},
|
||||||
|
{0x3C, 0x19, 0x0E}, {0x3F, 0x1D, 0x0B}, {0x3F, 0x18, 0x7E},
|
||||||
|
{0x3F, 0x1D, 0x0B}, {0x3F, 0x1D, 0x09}, {0x59, 0x32, 0x1E}
|
||||||
|
}},
|
||||||
|
// White
|
||||||
|
{{
|
||||||
|
{0xF0, 0xF1, 0xF1}, {0xA9, 0x94, 0x7E}, {0x09, 0x0B, 0x0C},
|
||||||
|
{0x22, 0x24, 0x24}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xD5, 0xFF},
|
||||||
|
{0xEA, 0xEA, 0xEA}, {0xEA, 0xEA, 0xC2}, {0xF3, 0xF3, 0xF3}
|
||||||
|
}},
|
||||||
|
// Black
|
||||||
|
{{
|
||||||
|
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x0B, 0x0B, 0x0B},
|
||||||
|
{0x23, 0x23, 0x23}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x78},
|
||||||
|
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}
|
||||||
|
}}
|
||||||
|
}};
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
GXColorS10 l_normalColor = { 0x50, 0x00, 0x00, 0x00 };
|
||||||
|
GXColor l_normalKColor = { 0xB4, 0x87, 0x00, 0x00 };
|
||||||
|
GXColor l_normalKColor2 = { 0x00, 0xC3, 0xC3, 0x00 };
|
||||||
|
GXColorS10 l_bigColor = { 0xFF, 0x64, 0x78, 0x00 };
|
||||||
|
GXColor l_bigKColor = { 0x1E, 0x00, 0x00, 0x00 };
|
||||||
|
GXColor l_lNormalKColor = { 0xFF, 0xDC, 0x00, 0x00 };
|
||||||
|
GXColor l_lNormalKColor2 = { 0x00, 0xC3, 0xEB, 0x00 };
|
||||||
|
GXColorS10 l_lBigColor = { 0xFF, 0x78, 0x00, 0x00 };
|
||||||
|
GXColor l_lBigKColor2 = { 0xAA, 0xFF, 0xC3, 0x00 };
|
||||||
|
} currentMidnaHairColors;
|
||||||
|
|
||||||
|
void set_all_midna_hair_colors() {
|
||||||
|
auto& g_cvars = get_cvars();
|
||||||
|
auto hairBaseColor = get_int_option(g_cvars.midnaHairBaseColor, 0);
|
||||||
|
auto hairTipsColor = get_int_option(g_cvars.midnaHairTipsColor, 0);
|
||||||
|
|
||||||
|
// Colors we have to convert to GXColorS10
|
||||||
|
auto& normalColor = midnaHairColors.at(hairBaseColor)[2];
|
||||||
|
auto& bigColor = midnaHairColors.at(hairBaseColor)[5];
|
||||||
|
auto& lBigColor = midnaHairColors.at(hairBaseColor)[4];
|
||||||
|
|
||||||
|
currentMidnaHairColors.l_normalColor = GXColorS10{normalColor.r, normalColor.g, normalColor.b};
|
||||||
|
currentMidnaHairColors.l_normalKColor = midnaHairColors.at(hairBaseColor)[1];
|
||||||
|
currentMidnaHairColors.l_normalKColor2 = midnaHairColors.at(hairTipsColor)[7];
|
||||||
|
currentMidnaHairColors.l_bigColor = GXColorS10{bigColor.r, bigColor.g, bigColor.b};
|
||||||
|
currentMidnaHairColors.l_bigKColor = midnaHairColors.at(hairBaseColor)[3];
|
||||||
|
currentMidnaHairColors.l_lNormalKColor = midnaHairColors.at(hairBaseColor)[0];
|
||||||
|
currentMidnaHairColors.l_lNormalKColor2 = midnaHairColors.at(hairTipsColor)[6];
|
||||||
|
currentMidnaHairColors.l_lBigColor = GXColorS10{lBigColor.r, lBigColor.g, lBigColor.b};
|
||||||
|
currentMidnaHairColors.l_lBigKColor2 = midnaHairColors.at(hairTipsColor)[8];
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColorS10* get_midna_hair_normalColor() {
|
||||||
|
return ¤tMidnaHairColors.l_normalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColor* get_midna_hair_normalKColor() {
|
||||||
|
return ¤tMidnaHairColors.l_normalKColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColor* get_midna_hair_normalKColor2() {
|
||||||
|
return ¤tMidnaHairColors.l_normalKColor2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColorS10* get_midna_hair_bigColor() {
|
||||||
|
return ¤tMidnaHairColors.l_bigColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColor* get_midna_hair_bigKColor() {
|
||||||
|
return ¤tMidnaHairColors.l_bigKColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColor* get_midna_hair_lNormalKColor() {
|
||||||
|
return ¤tMidnaHairColors.l_lNormalKColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColor* get_midna_hair_lNormalKColor2() {
|
||||||
|
return ¤tMidnaHairColors.l_lNormalKColor2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColorS10* get_midna_hair_lBigColor() {
|
||||||
|
return ¤tMidnaHairColors.l_lBigColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GXColor* get_midna_hair_lBigKColor2() {
|
||||||
|
return ¤tMidnaHairColors.l_lBigKColor2;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gx.h>
|
||||||
|
|
||||||
|
void set_all_midna_hair_colors();
|
||||||
|
|
||||||
|
const GXColorS10* get_midna_hair_normalColor();
|
||||||
|
const GXColor* get_midna_hair_normalKColor();
|
||||||
|
const GXColor* get_midna_hair_normalKColor2();
|
||||||
|
const GXColorS10* get_midna_hair_bigColor();
|
||||||
|
const GXColor* get_midna_hair_bigKColor();
|
||||||
|
const GXColor* get_midna_hair_lNormalKColor();
|
||||||
|
const GXColor* get_midna_hair_lNormalKColor2();
|
||||||
|
const GXColorS10* get_midna_hair_lBigColor();
|
||||||
|
const GXColor* get_midna_hair_lBigKColor2();
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
#include "mod.hpp"
|
||||||
|
#include "hooks.hpp"
|
||||||
|
#include "color_utils.hpp"
|
||||||
|
#include "midna_hair_color.hpp"
|
||||||
|
#include "option_descriptions.hpp"
|
||||||
|
|
||||||
|
#include "mods/service.hpp"
|
||||||
|
#include "mods/svc/config.h"
|
||||||
|
#include "mods/svc/hook.hpp"
|
||||||
|
#include "mods/svc/log.h"
|
||||||
|
#include "mods/svc/log.hpp"
|
||||||
|
#include "mods/svc/ui.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
DEFINE_MOD();
|
||||||
|
IMPORT_SERVICE(LogService, svc_log);
|
||||||
|
IMPORT_SERVICE(ConfigService, svc_config);
|
||||||
|
IMPORT_SERVICE(HookService, svc_hook);
|
||||||
|
IMPORT_SERVICE(UiService, svc_ui);
|
||||||
|
|
||||||
|
static cvars g_cvars;
|
||||||
|
|
||||||
|
cvars& get_cvars() {
|
||||||
|
return g_cvars;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_str_option(ConfigVarHandle handle, const std::string& fallback) {
|
||||||
|
std::string value{};
|
||||||
|
size_t outLength{};
|
||||||
|
svc_config->get_string(mod_ctx, handle, NULL, 0, &outLength);
|
||||||
|
value.resize(outLength);
|
||||||
|
if (handle == 0 || svc_config->get_string(mod_ctx, handle, value.data(), value.size() + 1, &outLength) != MOD_OK) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t get_int_option(ConfigVarHandle handle, int64_t fallback) {
|
||||||
|
int64_t value = fallback;
|
||||||
|
if (handle == 0 || svc_config->get_int(mod_ctx, handle, &value) != MOD_OK) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
UiWindowHandle g_cosmeticsWindow = 0;
|
||||||
|
|
||||||
|
ModResult register_str_option(
|
||||||
|
const char* name, const char* defaultValue, ConfigVarHandle& outHandle, ModError* error) {
|
||||||
|
ConfigVarDesc cvarDesc = CONFIG_VAR_DESC_INIT;
|
||||||
|
cvarDesc.name = name;
|
||||||
|
cvarDesc.type = CONFIG_VAR_STRING;
|
||||||
|
cvarDesc.default_string = defaultValue;
|
||||||
|
if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) {
|
||||||
|
return mods::set_error(error, MOD_ERROR, "failed to register cosmetics option");
|
||||||
|
}
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModResult register_int_option(
|
||||||
|
const char* name, int64_t defaultValue, ConfigVarHandle& outHandle, ModError* error) {
|
||||||
|
ConfigVarDesc cvarDesc = CONFIG_VAR_DESC_INIT;
|
||||||
|
cvarDesc.name = name;
|
||||||
|
cvarDesc.type = CONFIG_VAR_INT;
|
||||||
|
cvarDesc.default_int = defaultValue;
|
||||||
|
if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) {
|
||||||
|
return mods::set_error(error, MOD_ERROR, "failed to register cosmetics option");
|
||||||
|
}
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_control(UiElementHandle pane, const UiControlDesc& desc) {
|
||||||
|
auto result = svc_ui->pane_add_control(mod_ctx, pane, &desc, nullptr);
|
||||||
|
if (result != MOD_OK) {
|
||||||
|
mods::log::debug("pane_add_control failed {}", static_cast<int>(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_cosmetic_option(UiElementHandle left, ConfigVarHandle cvar, const char* name, const char* helpRml) {
|
||||||
|
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||||
|
control.kind = UI_CONTROL_STRING;
|
||||||
|
control.label = name;
|
||||||
|
control.help_rml = helpRml;
|
||||||
|
control.binding = UI_BINDING_CONFIG_VAR;
|
||||||
|
control.config_var = cvar;
|
||||||
|
control.max_length = 6;
|
||||||
|
add_control(left, control);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_cosmetic_option_with_rainbow(UiElementHandle left, ConfigVarHandle cvar, const char* name, const char* helpRml) {
|
||||||
|
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||||
|
control.kind = UI_CONTROL_STRING;
|
||||||
|
control.label = name;
|
||||||
|
control.help_rml = helpRml;
|
||||||
|
control.binding = UI_BINDING_CONFIG_VAR;
|
||||||
|
control.config_var = cvar;
|
||||||
|
control.max_length = 7;
|
||||||
|
add_control(left, control);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_midna_hair_option(UiElementHandle left, ConfigVarHandle cvar, const std::string& name) {
|
||||||
|
static const char* kMidnaHairOptions[] = {
|
||||||
|
"Default", "Pink", "Red", "Yellow", "Green", "Blue", "Purple", "Brown", "White", "Black"
|
||||||
|
};
|
||||||
|
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||||
|
control.kind = UI_CONTROL_SELECT;
|
||||||
|
control.label = name.c_str();
|
||||||
|
control.help_rml = "Choose Midna's hair color.";
|
||||||
|
control.binding = UI_BINDING_CONFIG_VAR;
|
||||||
|
control.config_var = cvar;
|
||||||
|
control.options = kMidnaHairOptions;
|
||||||
|
control.option_count = 10;
|
||||||
|
add_control(left, control);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModResult build_equipment_colors_tab(
|
||||||
|
ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) {
|
||||||
|
(void)right;
|
||||||
|
|
||||||
|
add_cosmetic_option(left, g_cvars.herosTunicCapColor, "Hero's Tunic Cap Color", kLinksCapColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.herosTunicTorsoColor, "Hero's Tunic Body Color", kLinksShirtColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.herosTunicSkirtColor, "Hero's Tunic Skirt Color", kLinksSkirtColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.zoraArmorCapColor, "Zora Armor Cap Color", kZoraCapColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.zoraArmorHelmetColor, "Zora Armor Helmet Color", kZoraHelmetColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.zoraArmorTorsoColor, "Zora Armor Torso Color", kZoraTorsoColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.zoraArmorScalesColor, "Zora Armor Scales Color", kZoraScalesColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.zoraArmorFlippersColor, "Zora Armor Flippers Color", kZoraFlippersColorsExplanation);
|
||||||
|
add_cosmetic_option_with_rainbow(left, g_cvars.lanternGlowColor, "Lantern Glow Color", kLanternGlowColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.woodenSwordColor, "Wooden Sword Color", kWoodenSwordColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.ordonSwordBladeColor, "Ordon Sword Blade Color", kOrdonBladeColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.ordonSwordHandleColor, "Ordon Sword Handle Color", kOrdonHandleColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.msBladeColor, "Master Sword Blade Color", kMSBladeColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.msHandleColor, "Master Sword Handle Color", kMSHandleColorsExplanation);
|
||||||
|
add_cosmetic_option_with_rainbow(left, g_cvars.lightSwordGlowColor, "Light Sword Glow Color", kSwordGlowColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.boomerangColor, "Boomerang Color", kBoomerangColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.ironBootsColor, "Iron Boots Color", kIronBootsColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.spinnerColor, "Spinner Color", kSpinnerColorsExplanation);
|
||||||
|
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModResult build_ui_colors_tab(
|
||||||
|
ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) {
|
||||||
|
(void)right;
|
||||||
|
|
||||||
|
add_cosmetic_option(left, g_cvars.aButtonColor, "A Button Color", kAButtonColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.bButtonColor, "B Button Color", kBButtonColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.xButtonColor, "X Button Color", kXButtonColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.yButtonColor, "Y Button Color", kYButtonColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.zButtonColor, "Z Button Color", kZButtonColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.heartColor, "Heart Color", kHeartColorsExplanation);
|
||||||
|
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModResult build_misc_colors_tab(
|
||||||
|
ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) {
|
||||||
|
(void)right;
|
||||||
|
|
||||||
|
add_midna_hair_option(left, g_cvars.midnaHairBaseColor, "Midna's Hair Base Color");
|
||||||
|
add_midna_hair_option(left, g_cvars.midnaHairTipsColor, "Midna's Hair Tips Color");
|
||||||
|
add_cosmetic_option(left, g_cvars.midnaChargeRingColor, "Midna Charge Ring Color", kChargeRingColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.linkHairColor, "Link's Hair Color", kLinksHairColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.wolfLinkColor, "Wolf Link Color", kWolfLinkColorsExplanation);
|
||||||
|
add_cosmetic_option(left, g_cvars.eponaColor, "Epona Color", kEponaColorsExplanation);
|
||||||
|
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_cosmetics_menu_window_closed(ModContext*, UiWindowHandle, void*) {
|
||||||
|
g_cosmeticsWindow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_open_cosmetics_menu(ModContext*, void*) {
|
||||||
|
if (g_cosmeticsWindow != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UiTabDesc tabs[] = {UI_TAB_DESC_INIT, UI_TAB_DESC_INIT, UI_TAB_DESC_INIT};
|
||||||
|
tabs[0].title = "Equipment Colors";
|
||||||
|
tabs[0].build = build_equipment_colors_tab;
|
||||||
|
tabs[1].title = "UI Colors";
|
||||||
|
tabs[1].build = build_ui_colors_tab;
|
||||||
|
tabs[2].title = "Misc Colors";
|
||||||
|
tabs[2].build = build_misc_colors_tab;
|
||||||
|
UiWindowDesc desc = UI_WINDOW_DESC_INIT;
|
||||||
|
desc.tabs = tabs;
|
||||||
|
desc.tab_count = ARRAY_SIZE(tabs);
|
||||||
|
desc.on_closed = on_cosmetics_menu_window_closed;
|
||||||
|
if (svc_ui->window_push(mod_ctx, &desc, &g_cosmeticsWindow) != MOD_OK) {
|
||||||
|
svc_log->error(mod_ctx, "failed to open basic cosmetics window");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModResult build_panel(ModContext*, UiElementHandle panel, void*, ModError*) {
|
||||||
|
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||||
|
control.kind = UI_CONTROL_BUTTON;
|
||||||
|
control.label = "Open Cosmetics Menu";
|
||||||
|
control.on_pressed = on_open_cosmetics_menu;
|
||||||
|
add_control(panel, control);
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define REGISTER_COSMETIC_OPTION(option) \
|
||||||
|
result = register_str_option(#option, NULL, g_cvars.option, error); \
|
||||||
|
if (result != MOD_OK) { \
|
||||||
|
return result; \
|
||||||
|
} \
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||||
|
svc_log->info(mod_ctx, "basic_cosmetics_mod initialized");
|
||||||
|
|
||||||
|
ModResult result{};
|
||||||
|
|
||||||
|
REGISTER_COSMETIC_OPTION(herosTunicCapColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(herosTunicTorsoColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(herosTunicSkirtColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(zoraArmorCapColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(zoraArmorHelmetColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(zoraArmorTorsoColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(zoraArmorScalesColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(zoraArmorFlippersColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(lanternGlowColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(woodenSwordColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(ordonSwordBladeColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(ordonSwordHandleColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(msBladeColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(msHandleColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(lightSwordGlowColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(boomerangColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(ironBootsColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(spinnerColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(aButtonColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(bButtonColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(xButtonColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(yButtonColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(zButtonColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(heartColor)
|
||||||
|
|
||||||
|
result = register_int_option("midnaHairBaseColor", 0, g_cvars.midnaHairBaseColor, error);
|
||||||
|
if (result != MOD_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = register_int_option("midnaHairTipsColor", 0, g_cvars.midnaHairTipsColor, error);
|
||||||
|
if (result != MOD_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COSMETIC_OPTION(midnaChargeRingColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(linkHairColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(wolfLinkColor)
|
||||||
|
REGISTER_COSMETIC_OPTION(eponaColor)
|
||||||
|
|
||||||
|
UiModsPanelDesc panelDesc = UI_MODS_PANEL_DESC_INIT;
|
||||||
|
panelDesc.build = build_panel;
|
||||||
|
svc_ui->register_mods_panel(mod_ctx, &panelDesc);
|
||||||
|
|
||||||
|
// Add all our hooks
|
||||||
|
result = add_all_hooks();
|
||||||
|
if (result != MOD_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOD_EXPORT ModResult mod_update(ModError*) {
|
||||||
|
update_rainbow_rgb(1.0f);
|
||||||
|
set_all_midna_hair_colors();
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOD_EXPORT ModResult mod_shutdown(ModError*) {
|
||||||
|
svc_log->info(mod_ctx, "basic_cosmetics_mod unloaded");
|
||||||
|
g_cosmeticsWindow = 0;
|
||||||
|
return MOD_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mods/svc/config.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct cvars {
|
||||||
|
ConfigVarHandle herosTunicCapColor = 0;
|
||||||
|
ConfigVarHandle herosTunicTorsoColor = 0;
|
||||||
|
ConfigVarHandle herosTunicSkirtColor = 0;
|
||||||
|
ConfigVarHandle zoraArmorCapColor = 0;
|
||||||
|
ConfigVarHandle zoraArmorHelmetColor = 0;
|
||||||
|
ConfigVarHandle zoraArmorTorsoColor = 0;
|
||||||
|
ConfigVarHandle zoraArmorScalesColor = 0;
|
||||||
|
ConfigVarHandle zoraArmorFlippersColor = 0;
|
||||||
|
ConfigVarHandle lanternGlowColor = 0;
|
||||||
|
ConfigVarHandle woodenSwordColor = 0;
|
||||||
|
ConfigVarHandle ordonSwordBladeColor = 0;
|
||||||
|
ConfigVarHandle ordonSwordHandleColor = 0;
|
||||||
|
ConfigVarHandle msBladeColor = 0;
|
||||||
|
ConfigVarHandle msHandleColor = 0;
|
||||||
|
ConfigVarHandle lightSwordGlowColor = 0;
|
||||||
|
ConfigVarHandle boomerangColor = 0;
|
||||||
|
ConfigVarHandle ironBootsColor = 0;
|
||||||
|
ConfigVarHandle spinnerColor = 0;
|
||||||
|
ConfigVarHandle heartColor = 0;
|
||||||
|
ConfigVarHandle aButtonColor = 0;
|
||||||
|
ConfigVarHandle bButtonColor = 0;
|
||||||
|
ConfigVarHandle xButtonColor = 0;
|
||||||
|
ConfigVarHandle yButtonColor = 0;
|
||||||
|
ConfigVarHandle zButtonColor = 0;
|
||||||
|
ConfigVarHandle midnaHairBaseColor = 0;
|
||||||
|
ConfigVarHandle midnaHairTipsColor = 0;
|
||||||
|
ConfigVarHandle midnaChargeRingColor = 0;
|
||||||
|
ConfigVarHandle linkHairColor = 0;
|
||||||
|
ConfigVarHandle wolfLinkColor = 0;
|
||||||
|
ConfigVarHandle eponaColor = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
cvars& get_cvars();
|
||||||
|
|
||||||
|
std::string get_str_option(ConfigVarHandle handle, const std::string& fallback);
|
||||||
|
|
||||||
|
int64_t get_int_option(ConfigVarHandle handle, int64_t fallback);
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
constexpr const char* kLinksCapColorsExplanation =
|
||||||
|
"Set the color of the Hero's Tunic cap with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kLinksShirtColorsExplanation =
|
||||||
|
"Set the color of the Hero's Tunic shirt with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kLinksSkirtColorsExplanation =
|
||||||
|
"Set the color of the Hero's Tunic skirt with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kZoraCapColorsExplanation =
|
||||||
|
"Set the color of the Zora Armor cap with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kZoraHelmetColorsExplanation =
|
||||||
|
"Set the color of the Zora Armor helmet with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kZoraTorsoColorsExplanation =
|
||||||
|
"Set the color of the Zora Armor torso with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kZoraScalesColorsExplanation =
|
||||||
|
"Set the color of the Zora Armor scales with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kZoraFlippersColorsExplanation =
|
||||||
|
"Set the color of the Zora Armor flippers with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kWoodenSwordColorsExplanation =
|
||||||
|
"Set the color with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kLanternGlowColorsExplanation =
|
||||||
|
"Set the color of the lantern light with any 6 digit hex code, or with \"rainbow\" for a rainbow effect.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: f68821<br/>"
|
||||||
|
"- Yellow: f6f321<br/>"
|
||||||
|
"- Green: 00ff00<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 8000ff<br/>"
|
||||||
|
"- White: a0a0a0<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kOrdonBladeColorsExplanation =
|
||||||
|
"Set the color of the Ordon Sword's blade with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: f68821<br/>"
|
||||||
|
"- Yellow: f6f321<br/>"
|
||||||
|
"- Green: 00ff00<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 8000ff<br/>"
|
||||||
|
"- White: a0a0a0<br/>"
|
||||||
|
"- Cyan: 30d0d0<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kOrdonHandleColorsExplanation =
|
||||||
|
"Set the color of the Ordon Sword's handle with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: f68821<br/>"
|
||||||
|
"- Yellow: f6f321<br/>"
|
||||||
|
"- Green: 00ff00<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 8000ff<br/>"
|
||||||
|
"- White: a0a0a0<br/>"
|
||||||
|
"- Cyan: 30d0d0<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kMSBladeColorsExplanation =
|
||||||
|
"Set the color of the Master Sword's blade with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: f68821<br/>"
|
||||||
|
"- Yellow: f6f321<br/>"
|
||||||
|
"- Green: 00ff00<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 8000ff<br/>"
|
||||||
|
"- White: a0a0a0<br/>"
|
||||||
|
"- Cyan: 30d0d0<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kMSHandleColorsExplanation =
|
||||||
|
"Set the color of the Master Sword's handle with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: f68821<br/>"
|
||||||
|
"- Yellow: f6f321<br/>"
|
||||||
|
"- Green: 00ff00<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 8000ff<br/>"
|
||||||
|
"- White: a0a0a0<br/>"
|
||||||
|
"- Cyan: 30d0d0<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kSwordGlowColorsExplanation =
|
||||||
|
"Set the color of the light sword glow with any 6 digit hex code, or with \"rainbow\" for a rainbow effect.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: f68821<br/>"
|
||||||
|
"- Yellow: f6f321<br/>"
|
||||||
|
"- Green: 00ff00<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 8000ff<br/>"
|
||||||
|
"- White: a0a0a0<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kBoomerangColorsExplanation =
|
||||||
|
"Set the color of the Gale Boomerang with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kIronBootsColorsExplanation =
|
||||||
|
"Set the color of the Iron Boots with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kSpinnerColorsExplanation =
|
||||||
|
"Set the color of the Spinner with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
|
||||||
|
constexpr const char* kAButtonColorsExplanation =
|
||||||
|
"Set the color of the A button with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: ff5000<br/>"
|
||||||
|
"- Yellow: ffaf00<br/>"
|
||||||
|
"- Dark Green: 0080ff<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 8000ff<br/>"
|
||||||
|
"- Grey: 5555ff<br/>"
|
||||||
|
"- Pink: ff20ff<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kBButtonColorsExplanation =
|
||||||
|
"Set the color of the B button with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Orange: ffff40<br/>"
|
||||||
|
"- Pink: ffa0ff<br/>"
|
||||||
|
"- Green: 00e87b<br/>"
|
||||||
|
"- Blue: 00aaff<br/>"
|
||||||
|
"- Purple: 6078ff<br/>"
|
||||||
|
"- Black: 000000<br/>"
|
||||||
|
"- Teal: 00f3ff<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kXButtonColorsExplanation =
|
||||||
|
"Set the color of the X button with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: ff8200<br/>"
|
||||||
|
"- Yellow: f7df00<br/>"
|
||||||
|
"- Lime Green: 70ff00<br/>"
|
||||||
|
"- Dark Green: 00bd11<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 800088<br/>"
|
||||||
|
"- Black: 000000<br/>"
|
||||||
|
"- Pink: ff00aa<br/>"
|
||||||
|
"- Cyan: 00ffff<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kYButtonColorsExplanation =
|
||||||
|
"Set the color of the Y button with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: ff8200<br/>"
|
||||||
|
"- Yellow: f7df00<br/>"
|
||||||
|
"- Lime Green: 70ff00<br/>"
|
||||||
|
"- Dark Green: 00bd11<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: 800088<br/>"
|
||||||
|
"- Black: 000000<br/>"
|
||||||
|
"- Pink: ff00aa<br/>"
|
||||||
|
"- Cyan: 00ffff<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kZButtonColorsExplanation =
|
||||||
|
"Set the Z button color with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Orange: ff8200<br/>"
|
||||||
|
"- Yellow: f7df00<br/>"
|
||||||
|
"- Lime Green: 70ff00<br/>"
|
||||||
|
"- Dark Green: 00bd11<br/>"
|
||||||
|
"- Purple: 800088<br/>"
|
||||||
|
"- Black: 000000<br/>"
|
||||||
|
"- Light Blue: 00ffff<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kHeartColorsExplanation =
|
||||||
|
"Set the heart color with any 6 digit hex code. This will also change the color of heart drops. A reload may be necessary to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Orange: ffff40<br/>"
|
||||||
|
"- Pink: ffa0ff<br/>"
|
||||||
|
"- Green: 00e87b<br/>"
|
||||||
|
"- Blue: 00aaff<br/>"
|
||||||
|
"- Purple: 6078ff<br/>"
|
||||||
|
"- Black: 000000<br/>"
|
||||||
|
"- Teal: 00f3ff<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kChargeRingColorsExplanation =
|
||||||
|
"Set the color of Midna's charge ring with any 6 digit hex code.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Pink: ff9f9f<br/>"
|
||||||
|
"- Red: ff0000<br/>"
|
||||||
|
"- Yellow: ffff00<br/>"
|
||||||
|
"- Green: 00ff00<br/>"
|
||||||
|
"- Blue: 0000ff<br/>"
|
||||||
|
"- Purple: ff00ff<br/>"
|
||||||
|
"- Brown: 331900<br/>"
|
||||||
|
"- White: feffff<br/>"
|
||||||
|
"- Black: 000000<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kLinksHairColorsExplanation =
|
||||||
|
"Set the color of Link's hair with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kWolfLinkColorsExplanation =
|
||||||
|
"Set the color of Wolf Link with any 6 digit hex code. Loading a new area or changing forms is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
|
|
||||||
|
constexpr const char* kEponaColorsExplanation =
|
||||||
|
"Set the color of Wolf Link with any 6 digit hex code. Loading a new area while *not* riding Epona is required to see changes.<br/>"
|
||||||
|
"Common colors:<br/>"
|
||||||
|
"- Red: ab706e<br/>"
|
||||||
|
"- Blue: 6382a0<br/>"
|
||||||
|
"- Purple: 94749a<br/>"
|
||||||
|
"- Orange: ec8644<br/>"
|
||||||
|
"- Yellow: b9ab00<br/>"
|
||||||
|
"- Pink: ec9fc8<br/>"
|
||||||
|
"- Black: 505154<br/>"
|
||||||
|
"- White: f8f7f4<br/>"
|
||||||
|
"- Brown: 91723e<br/>";
|
||||||
@@ -0,0 +1,549 @@
|
|||||||
|
#include "texture_utils.hpp"
|
||||||
|
#include "color_utils.hpp"
|
||||||
|
#include "mod.hpp"
|
||||||
|
|
||||||
|
#include "mods/svc/config.h"
|
||||||
|
#include "mods/svc/log.hpp"
|
||||||
|
|
||||||
|
// Forward declaration needed for J3DModelLoader to be happy
|
||||||
|
class J3DVertexData;
|
||||||
|
#include "JSystem/J3DGraphLoader/J3DModelLoader.h"
|
||||||
|
#include "JSystem/JKernel/JKRMemArchive.h"
|
||||||
|
#include "JSystem/JSupport/JSupport.h"
|
||||||
|
#include "JSystem/JUtility/JUTNameTab.h"
|
||||||
|
#include "JSystem/JUtility/JUTTexture.h"
|
||||||
|
#include "d/actor/d_a_alink.h"
|
||||||
|
#include "global.h"
|
||||||
|
#include "gx/GXEnum.h"
|
||||||
|
#include "m_Do/m_Do_dvd_thread.h"
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
ResTIMG* find_tex_header_in_tex_1_section(J3DTextureBlock* tex1Ptr, const char* textureName) {
|
||||||
|
if (tex1Ptr == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto strTable = JSUConvertOffsetToPtr<ResNTAB>(tex1Ptr, tex1Ptr->mpNameTable);
|
||||||
|
for (size_t i = 0; i < strTable->mEntryNum && i < tex1Ptr->mTextureNum; i++) {
|
||||||
|
const char* str = strTable->getName(i);
|
||||||
|
|
||||||
|
if (strcmp(str, textureName) == 0) {
|
||||||
|
return &JSUConvertOffsetToPtr<ResTIMG>(tex1Ptr, tex1Ptr->mpTextureRes)[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recolor_rgb5a3_texture(ResTIMG* texHeaderPtr, GXColor color)
|
||||||
|
{
|
||||||
|
// Precompute lookup tables for both RGB555 (opaque) and RGB444 (translucent) modes
|
||||||
|
uint16_t recolors_rgb555[0x100];
|
||||||
|
uint16_t recolors_rgb444[0x100];
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < 0x100; i++) {
|
||||||
|
const uint8_t r = blend_overlay_channel(i, color.r);
|
||||||
|
const uint8_t g = blend_overlay_channel(i, color.g);
|
||||||
|
const uint8_t b = blend_overlay_channel(i, color.b);
|
||||||
|
|
||||||
|
// Pack as RGB555: Bit 15 set to 1 + 5 bits R, G, B
|
||||||
|
recolors_rgb555[i] = 0x8000 | ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3);
|
||||||
|
|
||||||
|
// Pack as RGB444: 4 bits R, G, B (Bit 15 remains 0)
|
||||||
|
recolors_rgb444[i] = ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int32_t blockWidth = 4;
|
||||||
|
constexpr int32_t blockHeight = 4;
|
||||||
|
|
||||||
|
const int32_t roundedWidth = texHeaderPtr->width + ((blockWidth - (texHeaderPtr->width % blockWidth)) % blockWidth);
|
||||||
|
const int32_t roundedHeight = texHeaderPtr->height + ((blockHeight - (texHeaderPtr->height % blockHeight)) % blockHeight);
|
||||||
|
|
||||||
|
const int32_t totalPixels = roundedWidth * roundedHeight;
|
||||||
|
|
||||||
|
auto* pixelPtr = reinterpret_cast<BE<uint16_t>*>(JSUConvertOffsetToPtr<u8>(texHeaderPtr, texHeaderPtr->imageOffset));
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < totalPixels; i++) {
|
||||||
|
const uint16_t rawPixel = pixelPtr[i];
|
||||||
|
|
||||||
|
// MSB determines if pixel is opaque or translucent
|
||||||
|
if (rawPixel & 0x8000) {
|
||||||
|
// Pixel is opaque
|
||||||
|
const uint8_t r5 = (rawPixel >> 10) & 0x1F;
|
||||||
|
const uint8_t g5 = (rawPixel >> 5) & 0x1F;
|
||||||
|
const uint8_t b5 = rawPixel & 0x1F;
|
||||||
|
|
||||||
|
// Expand 5-bit to 8-bit
|
||||||
|
const uint8_t r8 = (r5 << 3) | (r5 >> 2);
|
||||||
|
const uint8_t g8 = (g5 << 3) | (g5 >> 2);
|
||||||
|
const uint8_t b8 = (b5 << 3) | (b5 >> 2);
|
||||||
|
|
||||||
|
const uint8_t grayVal = static_cast<uint8_t>((r8 * 77 + g8 * 150 + b8 * 29) >> 8);
|
||||||
|
|
||||||
|
pixelPtr[i] = recolors_rgb555[grayVal];
|
||||||
|
} else {
|
||||||
|
// Pixel is translucent
|
||||||
|
const uint16_t alpha3 = rawPixel & 0x7000;
|
||||||
|
|
||||||
|
const uint8_t r4 = (rawPixel >> 8) & 0x0F;
|
||||||
|
const uint8_t g4 = (rawPixel >> 4) & 0x0F;
|
||||||
|
const uint8_t b4 = rawPixel & 0x0F;
|
||||||
|
|
||||||
|
// Expand 4-bit to 8-bit
|
||||||
|
const uint8_t r8 = (r4 << 4) | r4;
|
||||||
|
const uint8_t g8 = (g4 << 4) | g4;
|
||||||
|
const uint8_t b8 = (b4 << 4) | b4;
|
||||||
|
|
||||||
|
const uint8_t grayVal = static_cast<uint8_t>((r8 * 77 + g8 * 150 + b8 * 29) >> 8);
|
||||||
|
|
||||||
|
// Combine original alpha with recolored RGB444
|
||||||
|
pixelPtr[i] = alpha3 | recolors_rgb444[grayVal];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When left is greater than right
|
||||||
|
// 0b00 points to the left color
|
||||||
|
// 0b01 points to the right color
|
||||||
|
// 0b10 is closer to left color
|
||||||
|
// 0b11 is closer to right color
|
||||||
|
|
||||||
|
// When left is not greater than right
|
||||||
|
// 0b00 points to the left color
|
||||||
|
// 0b01 points to the right color
|
||||||
|
// 0b10 is midway between the colors
|
||||||
|
// 0b11 is transparent
|
||||||
|
|
||||||
|
// That means when maintaining the relative order, if we have to swap the colors:
|
||||||
|
|
||||||
|
// in the case of left being greater than right:
|
||||||
|
// 0b00 will swap to 0b01
|
||||||
|
// 0b01 will swap to 0b00
|
||||||
|
// 0b10 will swap to 0b11
|
||||||
|
// 0b11 will swap to 0b10
|
||||||
|
// So the left bit stays the same, and the right bit changes
|
||||||
|
// Can do xor (^) like 0b01010101 or 0x55 for each u16
|
||||||
|
|
||||||
|
// in the case of left not being greater than right:
|
||||||
|
// 0b00 will swap to 0b01
|
||||||
|
// 0b01 will swap to 0b00
|
||||||
|
// 0b10 will stay the same
|
||||||
|
// 0b11 will stay the same
|
||||||
|
// so if the left bit is a 0, the right bit will change
|
||||||
|
uint32_t swap_index_bits(bool leftIsGreater, uint32_t bits) {
|
||||||
|
if (leftIsGreater) {
|
||||||
|
return bits ^ 0x55555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t mask = ((bits >> 1) & 0x55555555) ^ 0x55555555;
|
||||||
|
return bits ^ mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recolor_cmpr_texture(ResTIMG* texHeaderPtr, GXColor color)
|
||||||
|
{
|
||||||
|
uint16_t recolors[0x100];
|
||||||
|
for (int32_t i = 0; i < 0x100; i++) {
|
||||||
|
recolors[i] = blend_overlay_rgb_565(i, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int32_t blockWidth = 8;
|
||||||
|
constexpr int32_t blockHeight = 8;
|
||||||
|
|
||||||
|
const int32_t roundedWidth = texHeaderPtr->width + ((blockWidth - (texHeaderPtr->width % blockWidth)) % blockWidth);
|
||||||
|
const int32_t roundedHeight = texHeaderPtr->height + ((blockHeight - (texHeaderPtr->height % blockHeight)) % blockHeight);
|
||||||
|
|
||||||
|
const int32_t numBlocks = roundedWidth / blockWidth * roundedHeight / blockHeight;
|
||||||
|
|
||||||
|
const int32_t iterations = numBlocks * 4;
|
||||||
|
|
||||||
|
uint8_t* currentAddr = JSUConvertOffsetToPtr<u8>(texHeaderPtr, texHeaderPtr->imageOffset);
|
||||||
|
for (int32_t i = 0; i < iterations; i++) {
|
||||||
|
auto* rgb565Ptr = reinterpret_cast<BE<uint16_t>*>(currentAddr);
|
||||||
|
|
||||||
|
auto leftRgb565 = rgb565Ptr[0];
|
||||||
|
auto rightRgb565 = rgb565Ptr[1];
|
||||||
|
const bool leftIsGreater = leftRgb565 > rightRgb565;
|
||||||
|
|
||||||
|
const uint32_t leftGrayVal = desaturate_rgb_565(leftRgb565);
|
||||||
|
const uint32_t rightGrayVal = desaturate_rgb_565(rightRgb565);
|
||||||
|
|
||||||
|
uint16_t leftNewRgb565 = recolors[leftGrayVal];
|
||||||
|
uint16_t rightNewRgb565 = recolors[rightGrayVal];
|
||||||
|
|
||||||
|
bool needsBitSwap = false;
|
||||||
|
|
||||||
|
if (leftIsGreater) {
|
||||||
|
if (leftNewRgb565 == rightNewRgb565) {
|
||||||
|
// Need to make sure that subtracting 1 does not mess
|
||||||
|
// everything up. For example, 0x1000 - 1 => 0x0fff which is
|
||||||
|
// a completely different color.
|
||||||
|
if ((leftNewRgb565 & 0x1f) == 0)
|
||||||
|
{
|
||||||
|
// If left value has 0 blue, we change its blue to 1.
|
||||||
|
leftNewRgb565 += 1;
|
||||||
|
}
|
||||||
|
rightNewRgb565 = leftNewRgb565 - 1;
|
||||||
|
}
|
||||||
|
else if (leftNewRgb565 < rightNewRgb565) {
|
||||||
|
needsBitSwap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (leftNewRgb565 > rightNewRgb565) {
|
||||||
|
needsBitSwap = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsBitSwap) {
|
||||||
|
// The left and right colors are swapping so that their values
|
||||||
|
// are relative in the same way. We need to update the bits
|
||||||
|
// referencing the palette entries to handle the swap.
|
||||||
|
|
||||||
|
const uint16_t temp = leftNewRgb565;
|
||||||
|
leftNewRgb565 = rightNewRgb565;
|
||||||
|
rightNewRgb565 = temp;
|
||||||
|
|
||||||
|
auto wordPtr = reinterpret_cast<BE<uint32_t>*>(currentAddr);
|
||||||
|
const uint32_t bits = wordPtr[1];
|
||||||
|
|
||||||
|
const uint32_t newBits = swap_index_bits(leftIsGreater, bits);
|
||||||
|
wordPtr[1] = newBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb565Ptr[0] = leftNewRgb565;
|
||||||
|
rgb565Ptr[1] = rightNewRgb565;
|
||||||
|
|
||||||
|
currentAddr += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to encode a single 4x4 sub-block (16 pixels) into an 8-byte CMPR block
|
||||||
|
static void encode_cmpr_sub_block(uint8_t* dst, const uint8_t pixels[16]) {
|
||||||
|
uint8_t min_val = 255;
|
||||||
|
uint8_t max_val = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
if (pixels[i] < min_val) min_val = pixels[i];
|
||||||
|
if (pixels[i] > max_val) max_val = pixels[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto intensity_to_rgb565 = [](uint8_t val) -> uint16_t {
|
||||||
|
uint16_t r5 = val >> 3;
|
||||||
|
uint16_t g6 = val >> 2;
|
||||||
|
uint16_t b5 = val >> 3;
|
||||||
|
return static_cast<uint16_t>((r5 << 11) | (g6 << 5) | b5);
|
||||||
|
};
|
||||||
|
|
||||||
|
uint16_t c0_565 = intensity_to_rgb565(max_val);
|
||||||
|
uint16_t c1_565 = intensity_to_rgb565(min_val);
|
||||||
|
uint32_t indices = 0;
|
||||||
|
|
||||||
|
if (max_val > min_val) {
|
||||||
|
// Enforce c0_565 > c1_565 in unsigned 16-bit representation to use 4-color mode
|
||||||
|
if (c0_565 == c1_565) {
|
||||||
|
if ((c0_565 & 0x001F) < 0x001F) {
|
||||||
|
c0_565 += 1;
|
||||||
|
} else {
|
||||||
|
c1_565 -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolated 8-bit intensity values for quantization
|
||||||
|
const int c0 = max_val;
|
||||||
|
const int c1 = min_val;
|
||||||
|
const int c2 = (2 * max_val + min_val) / 3;
|
||||||
|
const int c3 = (max_val + 2 * min_val) / 3;
|
||||||
|
|
||||||
|
// Map each pixel to the nearest palette entry
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
const int p = pixels[i];
|
||||||
|
const int d0 = std::abs(p - c0);
|
||||||
|
const int d1 = std::abs(p - c1);
|
||||||
|
const int d2 = std::abs(p - c2);
|
||||||
|
const int d3 = std::abs(p - c3);
|
||||||
|
|
||||||
|
uint32_t best_idx = 0;
|
||||||
|
int min_d = d0;
|
||||||
|
|
||||||
|
if (d1 < min_d) { min_d = d1; best_idx = 1; }
|
||||||
|
if (d2 < min_d) { min_d = d2; best_idx = 2; }
|
||||||
|
if (d3 < min_d) { min_d = d3; best_idx = 3; }
|
||||||
|
|
||||||
|
indices |= (best_idx << (30 - (2 * i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account for big endian data expectation
|
||||||
|
dst[0] = static_cast<uint8_t>(c0_565 >> 8);
|
||||||
|
dst[1] = static_cast<uint8_t>(c0_565 & 0xFF);
|
||||||
|
dst[2] = static_cast<uint8_t>(c1_565 >> 8);
|
||||||
|
dst[3] = static_cast<uint8_t>(c1_565 & 0xFF);
|
||||||
|
dst[4] = static_cast<uint8_t>(indices >> 24);
|
||||||
|
dst[5] = static_cast<uint8_t>((indices >> 16) & 0xFF);
|
||||||
|
dst[6] = static_cast<uint8_t>((indices >> 8) & 0xFF);
|
||||||
|
dst[7] = static_cast<uint8_t>(indices & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool convert_i8_to_cmpr(ResTIMG* texHeaderPtr) {
|
||||||
|
|
||||||
|
const uint16_t width = texHeaderPtr->width;
|
||||||
|
const uint16_t height = texHeaderPtr->height;
|
||||||
|
|
||||||
|
if (width % 8 != 0 || height % 8 != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t tilesX = width / 8;
|
||||||
|
const uint32_t blocksY_CMPR = height / 8;
|
||||||
|
|
||||||
|
auto* imageData = JSUConvertOffsetToPtr<uint8_t>(texHeaderPtr, texHeaderPtr->imageOffset);
|
||||||
|
uint8_t* writePtr = imageData;
|
||||||
|
|
||||||
|
// Process image in 8x8 blocks
|
||||||
|
for (uint32_t by = 0; by < blocksY_CMPR; ++by) {
|
||||||
|
for (uint32_t bx = 0; bx < tilesX; ++bx) {
|
||||||
|
// Locate the two stacked 8x4 I8 tiles (32 bytes each) making up the 8x8 block
|
||||||
|
const uint32_t topTileIdx = (2 * by) * tilesX + bx;
|
||||||
|
const uint32_t bottomTileIdx = (2 * by + 1) * tilesX + bx;
|
||||||
|
|
||||||
|
const uint8_t* topTileData = imageData + (topTileIdx * 32);
|
||||||
|
const uint8_t* bottomTileData = imageData + (bottomTileIdx * 32);
|
||||||
|
|
||||||
|
uint8_t subBlockPixels[4][16];
|
||||||
|
|
||||||
|
// Extract Sub-block 0 (Top-Left) and Sub-block 1 (Top-Right)
|
||||||
|
for (int row = 0; row < 4; ++row) {
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
subBlockPixels[0][row * 4 + col] = topTileData[row * 8 + col];
|
||||||
|
subBlockPixels[1][row * 4 + col] = topTileData[row * 8 + col + 4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract Sub-block 2 (Bottom-Left) and Sub-block 3 (Bottom-Right)
|
||||||
|
for (int row = 0; row < 4; ++row) {
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
subBlockPixels[2][row * 4 + col] = bottomTileData[row * 8 + col];
|
||||||
|
subBlockPixels[3][row * 4 + col] = bottomTileData[row * 8 + col + 4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the 4 sub-blocks in CMPR order (32 bytes per 8x8 block)
|
||||||
|
for (const auto& subBlockPixel : subBlockPixels) {
|
||||||
|
encode_cmpr_sub_block(writePtr, subBlockPixel);
|
||||||
|
writePtr += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ResTIMG header metadata
|
||||||
|
texHeaderPtr->format = GX_TF_CMPR; // 0x0E
|
||||||
|
texHeaderPtr->colorFormat = 0;
|
||||||
|
texHeaderPtr->numColors = 0;
|
||||||
|
texHeaderPtr->paletteOffset = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recolor_texture(J3DTextureBlock* tex1Ptr, const char* textureName, GXColor color) {
|
||||||
|
ResTIMG* texHeaderPtr = find_tex_header_in_tex_1_section(tex1Ptr, textureName);
|
||||||
|
if (texHeaderPtr == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (texHeaderPtr->format) {
|
||||||
|
case GX_TF_CMPR:
|
||||||
|
recolor_cmpr_texture(texHeaderPtr, color);
|
||||||
|
break;
|
||||||
|
case GX_TF_RGB5A3:
|
||||||
|
recolor_rgb5a3_texture(texHeaderPtr, color);
|
||||||
|
break;
|
||||||
|
case GX_TF_I8:
|
||||||
|
if (convert_i8_to_cmpr(texHeaderPtr)) {
|
||||||
|
recolor_cmpr_texture(texHeaderPtr, color);
|
||||||
|
} else {
|
||||||
|
mods::log::debug("Could not convert {} from i8 to cmpr", textureName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
J3DTextureBlock* find_tex_1_in_bmd(J3DModelFileData* bmdPtr)
|
||||||
|
{
|
||||||
|
if (bmdPtr == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bmdPtr->mMagic1 != MULTI_CHAR('J3D2')) {
|
||||||
|
// Model was not a BMD or BDL!
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bmdPtr->mMagic2 != MULTI_CHAR('bmd3') && bmdPtr->mMagic2 != MULTI_CHAR('bdl4')) {
|
||||||
|
// Model was not a BMD or BDL!
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
J3DModelBlock* curBlock = bmdPtr->mBlocks;
|
||||||
|
for (int32_t i = 0; i < bmdPtr->mBlockNum; i++) {
|
||||||
|
if (curBlock->mBlockType == MULTI_CHAR('TEX1')) {
|
||||||
|
return static_cast<J3DTextureBlock*>(curBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line taken from J3DModelLoader.cpp
|
||||||
|
curBlock = (J3DModelBlock*)((uintptr_t)curBlock + curBlock->mBlockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CosmeticOverride {
|
||||||
|
std::list<std::string_view> textures{};
|
||||||
|
ConfigVarHandle hexColor{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
auto& get_cosmetic_overrides() {
|
||||||
|
static std::unordered_map<s32, std::unordered_map<std::string_view, std::list<CosmeticOverride>>> cosmeticOverrides{};
|
||||||
|
if (cosmeticOverrides.empty()) {
|
||||||
|
auto& g_cvars = get_cvars();
|
||||||
|
// Ordon Clothes Link Model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Bmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||||
|
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||||
|
};
|
||||||
|
// Main Link Model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_head.bmd"] = {
|
||||||
|
{.textures = {"al_cap"}, .hexColor = g_cvars.herosTunicCapColor},
|
||||||
|
{.textures = {"al_hair"}, .hexColor = g_cvars.linkHairColor},
|
||||||
|
};
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al.bmd"] = {
|
||||||
|
{.textures = {"al_upbody"}, .hexColor = g_cvars.herosTunicTorsoColor},
|
||||||
|
{.textures = {"al_lowbody"}, .hexColor = g_cvars.herosTunicSkirtColor},
|
||||||
|
};
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||||
|
{.textures = {"al_bootsH"}, .hexColor = g_cvars.ironBootsColor},
|
||||||
|
};
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||||
|
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||||
|
};
|
||||||
|
// Zora Armor Link Model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/zl_head.bmd"] = {
|
||||||
|
{.textures = {"zl_cap"}, .hexColor = g_cvars.zoraArmorCapColor},
|
||||||
|
{.textures = {"zl_helmet"}, .hexColor = g_cvars.zoraArmorHelmetColor},
|
||||||
|
};
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/zl.bmd"] = {
|
||||||
|
{.textures = {"zl_armor", "zl_armL"}, .hexColor = g_cvars.zoraArmorTorsoColor},
|
||||||
|
{.textures = {"zl_body"}, .hexColor = g_cvars.zoraArmorScalesColor},
|
||||||
|
{.textures = {"zl_boots"}, .hexColor = g_cvars.zoraArmorFlippersColor},
|
||||||
|
};
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||||
|
{.textures = {"al_bootsH"}, .hexColor = g_cvars.ironBootsColor},
|
||||||
|
};
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||||
|
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||||
|
};
|
||||||
|
// Zora Armor field model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_zora.arc")]["bmdr/o_gd_al_zora.bmd"] = {
|
||||||
|
{.textures = {"zl_armor"}, .hexColor = g_cvars.zoraArmorTorsoColor},
|
||||||
|
{.textures = {"zl_body"}, .hexColor = g_cvars.zoraArmorScalesColor},
|
||||||
|
{.textures = {"zl_helmet"}, .hexColor = g_cvars.zoraArmorHelmetColor},
|
||||||
|
{.textures = {"zl_cap"}, .hexColor = g_cvars.zoraArmorCapColor},
|
||||||
|
};
|
||||||
|
// Magic Armor Model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Mmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||||
|
{.textures = {"al_bootsH"}, .hexColor = g_cvars.ironBootsColor},
|
||||||
|
};
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Mmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||||
|
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||||
|
};
|
||||||
|
// Ordon Sword Colors
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmwr/al_swa.bmd"] = {
|
||||||
|
{.textures = {"al_SWA", "hilight01"}, .hexColor = g_cvars.ordonSwordBladeColor},
|
||||||
|
{.textures = {"al_SWgripA"}, .hexColor = g_cvars.ordonSwordHandleColor},
|
||||||
|
};
|
||||||
|
// Master Sword Colors
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmwe/al_swm.bmd"] = {
|
||||||
|
{.textures = {"al_SWM"}, .hexColor = g_cvars.msBladeColor},
|
||||||
|
{.textures = {"al_SWgripM"}, .hexColor = g_cvars.msHandleColor},
|
||||||
|
};
|
||||||
|
// Boomerang Color
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmdr/al_boom.bmd"] = {
|
||||||
|
{.textures = {"L_al_boom00"}, .hexColor = g_cvars.boomerangColor},
|
||||||
|
};
|
||||||
|
// Spinner Color
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmdr/al_sp.bmd"] = {
|
||||||
|
{.textures = {"al_SP"}, .hexColor = g_cvars.spinnerColor},
|
||||||
|
};
|
||||||
|
// Epona Color
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Horse.arc")]["bmdr/hs.bmd"] = {
|
||||||
|
{.textures = {"hs_body", "hs_eye.1", "hs_eye.2", "hs_eye.3"}, .hexColor = g_cvars.eponaColor},
|
||||||
|
};
|
||||||
|
// Wolf Link Color
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Wmdl.arc")]["bmwr/wl.bmd"] = {
|
||||||
|
{.textures = {"wl_body", "wl_eye.1", "wl_eye.2", "wl_eye.3", "wl_eye.4", "wl_eye.5"}, .hexColor = g_cvars.wolfLinkColor},
|
||||||
|
};
|
||||||
|
// Wooden Sword Item Model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_SWB.arc")]["bmdr/o_gd_al_swb.bmd"] = {
|
||||||
|
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||||
|
};
|
||||||
|
// Boomerang Item Model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_boom.arc")]["bmdr/o_gd_boom.bmd"] = {
|
||||||
|
{.textures = {"L_al_boom00"}, .hexColor = g_cvars.boomerangColor},
|
||||||
|
};
|
||||||
|
// Iron Boots Item Model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_boot.arc")]["bmwr/o_gd_al_bootsh.bmd"] = {
|
||||||
|
{.textures = {"al_bootsH"}, .hexColor = g_cvars.ironBootsColor},
|
||||||
|
};
|
||||||
|
// Spinner Item Model
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_SP.arc")]["bmdr/o_gd_al_sp.bmd"] = {
|
||||||
|
{.textures = {"al_SP"}, .hexColor = g_cvars.spinnerColor},
|
||||||
|
};
|
||||||
|
// Gale Boomerang for Ook
|
||||||
|
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/E_mk.arc")]["bmdr/bm.bmd"] = {
|
||||||
|
{.textures = {"L_al_boom00", "bm_boom"}, .hexColor = g_cvars.boomerangColor},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return cosmeticOverrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 get_entry_number(mDoDvdThd_mountArchive_c* mountArchive) {
|
||||||
|
return mountArchive->mEntryNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_texture_overrides_on_load(mDoDvdThd_mountArchive_c* mountArchive) {
|
||||||
|
|
||||||
|
auto entryNum = get_entry_number(mountArchive);
|
||||||
|
auto& cosmeticOverrides = get_cosmetic_overrides();
|
||||||
|
if (!cosmeticOverrides.contains(entryNum)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [resName, overrides] : cosmeticOverrides[entryNum]) {
|
||||||
|
|
||||||
|
auto* archive = mountArchive->getArchive();
|
||||||
|
auto* entry = archive->findFsResource(resName.data(), 0);
|
||||||
|
if (!entry) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* tex1Addr = find_tex_1_in_bmd(static_cast<J3DModelFileData*>(archive->fetchResource(entry, NULL)));
|
||||||
|
if (!tex1Addr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& cosmeticOverride : overrides) {
|
||||||
|
const auto& [textures, hexColorVar] = cosmeticOverride;
|
||||||
|
const auto& hexColorStr = get_str_option(hexColorVar, "");
|
||||||
|
if (!is_valid_hex_color_str(hexColorStr)) {
|
||||||
|
mods::log::debug("Invalid Hex Str {}", hexColorStr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto color = hex_color_str_to_gx_color(hexColorStr);
|
||||||
|
if (tex1Addr) {
|
||||||
|
for (const auto& textureName : textures) {
|
||||||
|
recolor_texture(tex1Addr, textureName.data(), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File originally copied from console TPR with permission from isaac
|
||||||
|
* https://github.com/zsrtp/libtp_rel/blob/master/include/util/texture_utils.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct ResTIMG;
|
||||||
|
struct J3DTextureBlock;
|
||||||
|
class J3DModelFileData;
|
||||||
|
class mDoDvdThd_mountArchive_c;
|
||||||
|
|
||||||
|
ResTIMG* find_tex_header_in_tex_1_section(J3DTextureBlock* tex1Ptr, const char* textureName);
|
||||||
|
|
||||||
|
uint32_t swap_index_bits(bool leftIsGreater, uint32_t bits);
|
||||||
|
|
||||||
|
void recolor_cmpr_texture(J3DTextureBlock* tex1Ptr, const char* textureName, const uint8_t* rgb);
|
||||||
|
|
||||||
|
J3DTextureBlock* find_tex_1_in_bmd(J3DModelFileData* bmdPtr);
|
||||||
|
|
||||||
|
void handle_texture_overrides_on_load(mDoDvdThd_mountArchive_c* mountArchive);
|
||||||
+12
-14
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user