Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 957ab06fc2 | |||
| fe270d976d | |||
| a913f1699f | |||
| 01b4eaa2fa | |||
| 9d2ba3eb49 | |||
| 6026b4bb9b | |||
| 7a77d48954 | |||
| 4ee0d8ed4b | |||
| ce554a107d | |||
| 9ce1ab7d5a | |||
| 8830760d34 | |||
| 9abe89f47f | |||
| 3e62c1e96e | |||
| d9bbea300d | |||
| 1a951511be | |||
| ab0efb7a3b | |||
| 5fdc3c7a54 | |||
| 2978ae145d | |||
| e93773757f | |||
| 6be742b15f | |||
| 861efaa053 | |||
| 8e41e0195e | |||
| e9359a92d7 | |||
| 0c78376ba8 | |||
| 8c001f7968 | |||
| ef43b94370 | |||
| 76efa02beb | |||
| aeeb1ccdd2 | |||
| 93c7d0d64d | |||
| 1b76b2650c | |||
| 45196886b0 | |||
| 80af15c95b | |||
| 4c5e3b933e | |||
| 5eddcb9653 | |||
| 6dd50c955c | |||
| 4db65b9845 | |||
| ede6827369 | |||
| 2c9b20841d | |||
| 2b9ed729a3 | |||
| f39195c5e0 | |||
| a0f42c0c80 | |||
| d1e9d5af2f | |||
| 61b2e6ce4d | |||
| e73244bca5 | |||
| a4f25ecb28 | |||
| 39b546b81f | |||
| 1bd4585994 | |||
| c896bb39ea | |||
| 3366613354 | |||
| 79b1f4ab4d | |||
| 157f4f9df2 | |||
| 9d5d8dd13a | |||
| b0f1fbee1c | |||
| 40e3f7d057 | |||
| 764fc0b96f | |||
| 1b4a842eec | |||
| 08c7261262 | |||
| 07b440a1c9 | |||
| e7ab978a30 | |||
| 3a02e129e7 | |||
| 93c6106770 | |||
| b08d994e32 | |||
| 0665a78c84 | |||
| 4db8b51f24 | |||
| 800da8dbff | |||
| d6cbc9b6d5 | |||
| a0ecdb1735 | |||
| b10211c4c2 | |||
| c948bffd32 | |||
| 4bcf4ca354 | |||
| bfd9917ca1 | |||
| 7f6212f9b7 | |||
| 80245387f3 | |||
| 0a1fea4bc7 | |||
| 4ec7b01213 | |||
| 5187fe90c3 | |||
| a86fa9c162 | |||
| 4ec8c1aaee | |||
| 97d032f8b5 | |||
| 286532904a | |||
| 04b5861f29 | |||
| 453e958068 | |||
| e7d2fbcc0b | |||
| 8f71c70d14 | |||
| df23edcb69 | |||
| daff157027 | |||
| 0c23bd4332 | |||
| 7562486449 | |||
| 5e08b810fc | |||
| c66cccf660 | |||
| 3b1118229b | |||
| 491da372a1 | |||
| a2f463d146 | |||
| 63b3ce4849 | |||
| 8280ac00a0 | |||
| 45ef0d72b1 | |||
| ad9c460ec9 | |||
| 23dc9bc39a | |||
| bf23d44389 | |||
| d0b9b6d10f | |||
| 2f83753260 | |||
| eeb0ad77a4 | |||
| 594cadcf7d | |||
| 4290726691 | |||
| 80dd5ff278 | |||
| 08efb9a3cf | |||
| d8a1dd1da4 | |||
| 13dd3c3932 | |||
| dd7885da9c | |||
| 5a05433a2b | |||
| c3ff3884d7 | |||
| e42c4d3174 | |||
| 06c77a6818 | |||
| 4d4a80891f | |||
| 71c892368d | |||
| d2a1dda523 | |||
| 78179eb93f | |||
| 34e10d3844 | |||
| 65e8577253 | |||
| a4fcc10f5f | |||
| a2c2988666 | |||
| abec043249 | |||
| 699d069b0a | |||
| 4d67033ff8 | |||
| 84ffd67622 | |||
| 1c85ee63eb | |||
| 9e0966e069 | |||
| b51e672540 | |||
| 81c7213993 | |||
| be82e606b2 | |||
| 44da1a9f7d | |||
| d0f8ea56f9 | |||
| e472b36cef | |||
| 3934e09c8f | |||
| 3136816ce9 | |||
| 6fd3762ffc | |||
| 97a1190713 | |||
| 73a3bd9ae8 | |||
| fc533dbdc7 | |||
| 6217e071d2 | |||
| 673ca7f686 | |||
| 29a1cff7ea | |||
| 3c5152a67b | |||
| c844eb2b4a | |||
| 416bfaa90b | |||
| 44f3828f68 | |||
| 3f560b060c | |||
| 365a0a2188 | |||
| 4ff41fce4e | |||
| a470b73b1b | |||
| 120864a5c9 | |||
| ba7fc14e87 | |||
| c42d10a74b |
@@ -8,7 +8,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# SCCACHE_GHA_ENABLED: "true"
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- name: GCC x86_64
|
||||
runner: [self-hosted, Linux]
|
||||
runner: ubuntu-latest
|
||||
preset: gcc
|
||||
artifact_arch: x86_64
|
||||
# - name: GCC aarch64
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
if: 'false' # disabled for self-hosted
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \
|
||||
@@ -51,8 +50,7 @@ jobs:
|
||||
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
|
||||
|
||||
- name: Setup sccache
|
||||
if: 'false' # disabled for self-hosted
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Print sccache stats
|
||||
run: sccache --show-stats
|
||||
@@ -67,17 +65,16 @@ jobs:
|
||||
run: ci/build-appimage.sh
|
||||
|
||||
- name: Upload artifacts
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
||||
name: dusklight-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
||||
path: |
|
||||
build/install/Dusk-*.AppImage
|
||||
build/install/Dusklight-*.AppImage
|
||||
build/install/debug.tar.*
|
||||
|
||||
build-apple:
|
||||
name: Build Apple (${{matrix.name}})
|
||||
runs-on: [self-hosted, macOS]
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -86,14 +83,14 @@ jobs:
|
||||
platform: macos
|
||||
preset: x-macos-ci-arm64
|
||||
artifact_name: macos-appleclang-arm64
|
||||
# - name: AppleClang macOS x86_64
|
||||
# platform: macos
|
||||
# preset: x-macos-ci-x86_64
|
||||
# artifact_name: macos-appleclang-x86_64
|
||||
# - name: AppleClang iOS arm64
|
||||
# platform: ios
|
||||
# preset: x-ios-ci
|
||||
# artifact_name: ios-appleclang-arm64
|
||||
- name: AppleClang macOS x86_64
|
||||
platform: macos
|
||||
preset: x-macos-ci-x86_64
|
||||
artifact_name: macos-appleclang-x86_64
|
||||
- name: AppleClang iOS arm64
|
||||
platform: ios
|
||||
preset: x-ios-ci
|
||||
artifact_name: ios-appleclang-arm64
|
||||
# - name: AppleClang tvOS arm64
|
||||
# platform: tvos
|
||||
# preset: x-tvos-ci
|
||||
@@ -106,7 +103,6 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
if: 'false'
|
||||
run: brew install cmake ninja
|
||||
|
||||
- name: Install Rust iOS target
|
||||
@@ -128,7 +124,7 @@ jobs:
|
||||
rustup target add x86_64-apple-darwin
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake --preset ${{matrix.preset}}
|
||||
@@ -137,14 +133,80 @@ jobs:
|
||||
run: cmake --build --preset ${{matrix.preset}}
|
||||
|
||||
- name: Upload artifacts
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
|
||||
name: dusklight-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
|
||||
path: |
|
||||
build/install/Dusk.app
|
||||
build/install/Dusklight.app
|
||||
build/install/debug.tar.*
|
||||
|
||||
build-android:
|
||||
name: Build Android (${{matrix.name}})
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Clang arm64-v8a
|
||||
preset: x-android-ci-arm64
|
||||
abi: arm64-v8a
|
||||
artifact_arch: arm64
|
||||
rust_target: aarch64-linux-android
|
||||
|
||||
env:
|
||||
ANDROID_NDK_VERSION: "29.0.14206865"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ninja-build
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
|
||||
- name: Install Android SDK packages
|
||||
run: sdkmanager "platforms;android-36" "build-tools;36.1.0" "ndk;${ANDROID_NDK_VERSION}"
|
||||
|
||||
- name: Install Rust Android target
|
||||
run: |
|
||||
rustup toolchain install stable
|
||||
rustup target add ${{matrix.rust_target}}
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake --preset ${{matrix.preset}}
|
||||
|
||||
- name: Build native library
|
||||
run: cmake --build --preset ${{matrix.preset}} --target dusklight
|
||||
|
||||
- name: Stage stripped JNI library
|
||||
run: ANDROID_STAGE_ABIS="${{matrix.abi}}" platforms/android/scripts/stage-jni-libs.sh
|
||||
|
||||
- name: Build APK
|
||||
working-directory: platforms/android
|
||||
run: ./gradlew :app:assembleRelease --rerun-tasks
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusklight-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}}
|
||||
path: platforms/android/app/build/outputs/apk/release/app-${{matrix.abi}}-release-unsigned.apk
|
||||
|
||||
build-windows:
|
||||
name: Build Windows (${{matrix.name}})
|
||||
runs-on: ${{matrix.runner}}
|
||||
@@ -154,7 +216,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- name: MSVC x86_64
|
||||
runner: [self-hosted, Windows]
|
||||
runner: windows-latest
|
||||
preset: msvc
|
||||
msvc_arch: amd64
|
||||
vcpkg_arch: x64
|
||||
@@ -191,7 +253,6 @@ jobs:
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- name: Install dependencies
|
||||
if: 'false' # disabled for self-hosted
|
||||
run: |
|
||||
choco install ninja
|
||||
vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static
|
||||
@@ -203,10 +264,9 @@ jobs:
|
||||
run: cmake --build --preset x-windows-ci-${{matrix.preset}}
|
||||
|
||||
- name: Upload artifacts
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
||||
name: dusklight-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
||||
path: |
|
||||
build/install/*.exe
|
||||
build/install/*.dll
|
||||
|
||||
@@ -41,6 +41,10 @@ compile_commands.json
|
||||
# MacOS
|
||||
.DS_Store
|
||||
|
||||
# direnv / nix
|
||||
.direnv/
|
||||
.envrc
|
||||
|
||||
# ISOs
|
||||
*.iso
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "(gdb) Launch Dusk MSVC",
|
||||
"name": "(gdb) Launch Dusklight MSVC",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"cmake.buildDirectory": "${workspaceFolder}/build/dusk/${buildType}/${variant:tp_version}",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/build/dusklight/${buildType}/${variant:tp_version}",
|
||||
"cmake.generator": "Ninja",
|
||||
"cmake.configureSettings": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
|
||||
@@ -5,8 +5,19 @@ if (NOT CMAKE_BUILD_TYPE)
|
||||
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
|
||||
endif ()
|
||||
|
||||
# obtain revision info from git
|
||||
find_package(Git)
|
||||
set(DUSK_VERSION_OVERRIDE "" CACHE STRING "Override version string (skips git detection and format validation)")
|
||||
|
||||
if (DUSK_VERSION_OVERRIDE)
|
||||
set(DUSK_WC_DESCRIBE "${DUSK_VERSION_OVERRIDE}")
|
||||
set(DUSK_VERSION_STRING "0.0.0.0")
|
||||
set(DUSK_SHORT_VERSION_STRING "0.0.0")
|
||||
set(DUSK_WC_REVISION "")
|
||||
set(DUSK_WC_BRANCH "")
|
||||
set(DUSK_WC_DATE "")
|
||||
message(STATUS "Dusklight version overridden to ${DUSK_WC_DESCRIBE}")
|
||||
else ()
|
||||
# obtain revision info from git
|
||||
find_package(Git)
|
||||
if (GIT_FOUND)
|
||||
# make sure version information gets re-run when the current Git HEAD changes
|
||||
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD
|
||||
@@ -48,28 +59,32 @@ 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]+)(-([0-9]+).*)?$")
|
||||
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}")
|
||||
if (CMAKE_MATCH_5)
|
||||
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${CMAKE_MATCH_5}")
|
||||
else ()
|
||||
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.0")
|
||||
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}")
|
||||
else ()
|
||||
set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION")
|
||||
set(DUSK_VERSION_STRING "0.0.0.0")
|
||||
set(DUSK_SHORT_VERSION_STRING "0.0.0")
|
||||
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")
|
||||
endif()
|
||||
message(STATUS "Dusk version set to ${DUSK_WC_DESCRIBE}")
|
||||
message(STATUS "Dusklight version set to ${DUSK_WC_DESCRIBE}")
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
project(dusk LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
|
||||
project(dusklight LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
|
||||
if (APPLE)
|
||||
enable_language(OBJC)
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif ()
|
||||
if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS)
|
||||
# ios.toolchain.cmake hack for SDL
|
||||
@@ -109,25 +124,27 @@ add_subdirectory(libs/freeverb)
|
||||
option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)")
|
||||
option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.")
|
||||
option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON)
|
||||
|
||||
if(ANDROID)
|
||||
set(DUSK_MOVIE_SUPPORT OFF)
|
||||
set(NOD_COMPRESS_BZIP2 OFF CACHE BOOL "" FORCE)
|
||||
set(NOD_COMPRESS_LZMA OFF CACHE BOOL "" FORCE)
|
||||
set(NOD_COMPRESS_ZLIB OFF CACHE BOOL "" FORCE)
|
||||
set(NOD_COMPRESS_ZSTD OFF CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
|
||||
option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON)
|
||||
option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
|
||||
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
|
||||
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
|
||||
|
||||
# Edit & Continue
|
||||
if (MSVC)
|
||||
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
|
||||
endif ()
|
||||
if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
|
||||
add_link_options("/INCREMENTAL")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (DUSK_MOVIE_SUPPORT)
|
||||
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
|
||||
if (libjpeg-turbo_FOUND)
|
||||
message(STATUS "dusk: Using system libjpeg-turbo")
|
||||
message(STATUS "dusklight: Using system libjpeg-turbo")
|
||||
else ()
|
||||
message(STATUS "dusk: Fetching libjpeg-turbo")
|
||||
message(STATUS "dusklight: Fetching libjpeg-turbo")
|
||||
include(ExternalProject)
|
||||
set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install)
|
||||
if (WIN32)
|
||||
@@ -146,11 +163,14 @@ if (DUSK_MOVIE_SUPPORT)
|
||||
list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file})
|
||||
endif ()
|
||||
set(_jpeg_passthrough_vars
|
||||
ANDROID_ABI
|
||||
ANDROID_PLATFORM
|
||||
CMAKE_BUILD_TYPE
|
||||
CMAKE_C_COMPILER
|
||||
CMAKE_C_COMPILER_LAUNCHER
|
||||
CMAKE_MAKE_PROGRAM
|
||||
CMAKE_MSVC_RUNTIME_LIBRARY
|
||||
CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||
CMAKE_OSX_ARCHITECTURES
|
||||
DEPLOYMENT_TARGET
|
||||
ENABLE_ARC
|
||||
@@ -216,13 +236,13 @@ endif ()
|
||||
include(FetchContent)
|
||||
|
||||
# Declare all dependencies first so CMake can download them in parallel
|
||||
message(STATUS "dusk: Fetching cxxopts")
|
||||
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 TRUE
|
||||
)
|
||||
message(STATUS "dusk: Fetching nlohmann/json")
|
||||
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
|
||||
@@ -231,7 +251,7 @@ FetchContent_Declare(json
|
||||
FetchContent_MakeAvailable(cxxopts json)
|
||||
|
||||
if (DUSK_ENABLE_SENTRY_NATIVE)
|
||||
message(STATUS "dusk: Fetching 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)
|
||||
@@ -276,15 +296,15 @@ include(files.cmake)
|
||||
|
||||
# TODO: version handling for res includes
|
||||
|
||||
set(DUSK_BUNDLE_NAME Dusk)
|
||||
set(DUSK_BUNDLE_NAME Dusklight)
|
||||
set(DUSK_BUNDLE_IDENTIFIER dev.twilitrealm.dusk)
|
||||
set(DUSK_COMPANY_NAME "Twilit Realm")
|
||||
set(DUSK_FILE_DESCRIPTION "Dusk")
|
||||
set(DUSK_PRODUCT_NAME "Dusk")
|
||||
set(DUSK_FILE_DESCRIPTION "Dusklight")
|
||||
set(DUSK_PRODUCT_NAME "Dusklight")
|
||||
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
|
||||
|
||||
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
|
||||
source_group("dusk" FILES ${DUSK_FILES})
|
||||
source_group("dusklight" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES})
|
||||
|
||||
set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1)
|
||||
|
||||
@@ -314,6 +334,41 @@ if (WIN32)
|
||||
list(APPEND GAME_LIBS Ws2_32)
|
||||
endif ()
|
||||
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/no_backend.cpp)
|
||||
if (DUSK_ENABLE_UPDATE_CHECKER)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_UPDATE_CHECKER=1)
|
||||
if (WIN32)
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/winhttp.cpp)
|
||||
list(APPEND GAME_LIBS winhttp)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_WINHTTP=1)
|
||||
message(STATUS "dusklight: Enabled update checker (WinHTTP)")
|
||||
elseif (ANDROID)
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/android.cpp)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_ANDROID=1)
|
||||
message(STATUS "dusklight: Enabled update checker (Android)")
|
||||
elseif (APPLE)
|
||||
find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED)
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/url_session.mm)
|
||||
set_source_files_properties(src/dusk/http/url_session.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
list(APPEND GAME_LIBS ${FOUNDATION_FRAMEWORK})
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_URLSESSION=1)
|
||||
message(STATUS "dusklight: Enabled update checker (NSURLSession)")
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||
find_package(CURL QUIET OPTIONAL_COMPONENTS HTTPS SSL)
|
||||
if (CURL_FOUND AND CURL_HTTPS_FOUND AND CURL_SSL_FOUND)
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/curl.cpp)
|
||||
list(APPEND GAME_LIBS CURL::libcurl)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_LIBCURL=1)
|
||||
message(STATUS "dusklight: Enabled update checker (libcurl)")
|
||||
else ()
|
||||
message(STATUS "dusklight: Disabled update checker (libcurl + HTTPS/SSL not found)")
|
||||
endif ()
|
||||
else ()
|
||||
message(STATUS "dusklight: Disabled update checker (unsupported platform)")
|
||||
endif ()
|
||||
endif ()
|
||||
list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE})
|
||||
|
||||
if (DUSK_MOVIE_SUPPORT)
|
||||
if (TARGET libjpeg-turbo::turbojpeg-static)
|
||||
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
|
||||
@@ -332,16 +387,6 @@ if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
|
||||
endif ()
|
||||
|
||||
# Edit & Continue
|
||||
if (MSVC)
|
||||
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
|
||||
endif ()
|
||||
if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
|
||||
add_link_options("/INCREMENTAL")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if(ANDROID)
|
||||
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
|
||||
endif ()
|
||||
@@ -397,31 +442,37 @@ endif ()
|
||||
|
||||
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES})
|
||||
if(ANDROID)
|
||||
add_library(dusk SHARED ${DUSK_FILES})
|
||||
set_target_properties(dusk PROPERTIES OUTPUT_NAME main)
|
||||
add_library(dusklight SHARED ${DUSK_FILES})
|
||||
set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
|
||||
else ()
|
||||
add_executable(dusk ${DUSK_FILES})
|
||||
add_executable(dusklight ${DUSK_FILES})
|
||||
endif ()
|
||||
|
||||
target_compile_definitions(dusk PRIVATE ${GAME_COMPILE_DEFS})
|
||||
target_include_directories(dusk PRIVATE ${GAME_INCLUDE_DIRS})
|
||||
target_link_libraries(dusk PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
|
||||
target_precompile_headers(dusk PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
|
||||
target_compile_definitions(dusklight PRIVATE ${GAME_COMPILE_DEFS})
|
||||
target_include_directories(dusklight PRIVATE ${GAME_INCLUDE_DIRS})
|
||||
target_link_libraries(dusklight PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
|
||||
target_precompile_headers(dusklight PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
|
||||
if (TARGET crashpad_handler)
|
||||
add_dependencies(dusk crashpad_handler)
|
||||
add_dependencies(dusklight crashpad_handler)
|
||||
add_custom_command(TARGET dusklight POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE:crashpad_handler>"
|
||||
"$<TARGET_FILE_DIR:dusklight>"
|
||||
COMMENT "Copying crashpad handler"
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (ANDROID)
|
||||
# SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static
|
||||
# archive, force an undefined reference so the linker keeps the SDL_main object.
|
||||
target_link_options(dusk PRIVATE "-Wl,-u,SDL_main")
|
||||
target_link_options(dusklight PRIVATE "-Wl,-u,SDL_main")
|
||||
endif ()
|
||||
|
||||
if (NOT APPLE)
|
||||
add_custom_command(TARGET dusk POST_BUILD
|
||||
add_custom_command(TARGET dusklight POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${CMAKE_SOURCE_DIR}/res"
|
||||
"$<TARGET_FILE_DIR:dusk>/res"
|
||||
"$<TARGET_FILE_DIR:dusklight>/res"
|
||||
COMMENT "Copying resources"
|
||||
)
|
||||
endif ()
|
||||
@@ -429,9 +480,9 @@ endif ()
|
||||
if (WIN32)
|
||||
set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows)
|
||||
set(DUSK_WINDOWS_ICON_PNG ${CMAKE_CURRENT_SOURCE_DIR}/res/icon.png)
|
||||
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusk.ico)
|
||||
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusk.rc)
|
||||
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusk.manifest)
|
||||
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusklight.ico)
|
||||
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusklight.rc)
|
||||
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusklight.manifest)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${DUSK_WINDOWS_ICON_ICO}
|
||||
@@ -444,14 +495,14 @@ if (WIN32)
|
||||
COMMENT "Generating Windows icon"
|
||||
)
|
||||
|
||||
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY)
|
||||
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.rc.in ${DUSK_WINDOWS_RC} @ONLY)
|
||||
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY)
|
||||
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.rc.in ${DUSK_WINDOWS_RC} @ONLY)
|
||||
|
||||
target_sources(dusk PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
|
||||
set_target_properties(dusk PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
target_sources(dusklight PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
|
||||
set_target_properties(dusklight PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
|
||||
if (MSVC)
|
||||
target_link_options(dusk PRIVATE /MANIFEST:NO)
|
||||
target_link_options(dusklight PRIVATE /MANIFEST:NO)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
@@ -467,10 +518,10 @@ if (APPLE)
|
||||
file(GLOB_RECURSE DUSK_RESOURCE_FILES
|
||||
"${DUSK_RESOURCE_DIR}/Assets.car"
|
||||
"${DUSK_RESOURCE_DIR}/Base.lproj/*"
|
||||
"${DUSK_RESOURCE_DIR}/Dusk.icns")
|
||||
"${DUSK_RESOURCE_DIR}/Dusklight.icns")
|
||||
file(GLOB_RECURSE DUSK_APP_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/*")
|
||||
target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES})
|
||||
target_sources(dusk PRIVATE ${DUSK_APP_RESOURCE_FILES})
|
||||
target_sources(dusklight PRIVATE ${DUSK_RESOURCE_FILES})
|
||||
target_sources(dusklight PRIVATE ${DUSK_APP_RESOURCE_FILES})
|
||||
foreach (FILE ${DUSK_RESOURCE_FILES})
|
||||
file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE})
|
||||
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
|
||||
@@ -482,29 +533,36 @@ if (APPLE)
|
||||
set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}")
|
||||
endforeach ()
|
||||
set_target_properties(
|
||||
dusk PROPERTIES
|
||||
dusklight PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}
|
||||
MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}
|
||||
OUTPUT_NAME Dusk
|
||||
OUTPUT_NAME Dusklight
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES"
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (APPLE AND NOT IOS AND NOT TVOS)
|
||||
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(dusk PRIVATE src/dusk/ios/FileSelectDialog.m)
|
||||
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(dusk PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
|
||||
target_link_libraries(dusklight PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
|
||||
endif ()
|
||||
|
||||
include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
|
||||
aurora_copy_runtime_dlls(dusk)
|
||||
aurora_copy_runtime_dlls(dusklight)
|
||||
|
||||
if (DUSK_SELECTED_OPT)
|
||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
@@ -542,13 +600,13 @@ function(get_target_prefix target result_var)
|
||||
endif ()
|
||||
endif ()
|
||||
endfunction()
|
||||
list(APPEND BINARY_TARGETS dusk)
|
||||
list(APPEND BINARY_TARGETS dusklight)
|
||||
set(EXTRA_TARGETS "")
|
||||
if (TARGET crashpad_handler)
|
||||
list(APPEND EXTRA_TARGETS crashpad_handler)
|
||||
endif ()
|
||||
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX})
|
||||
aurora_install_runtime_dlls(dusk ${CMAKE_INSTALL_PREFIX})
|
||||
aurora_install_runtime_dlls(dusklight ${CMAKE_INSTALL_PREFIX})
|
||||
if (NOT APPLE)
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX})
|
||||
endif ()
|
||||
|
||||
@@ -249,22 +249,11 @@
|
||||
"type": "BOOL",
|
||||
"value": false
|
||||
},
|
||||
"CMAKE_DISABLE_FIND_PACKAGE_BZip2": {
|
||||
"CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
},
|
||||
"CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
},
|
||||
"CMAKE_DISABLE_FIND_PACKAGE_zstd": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
},
|
||||
"CMAKE_DISABLE_FIND_PACKAGE_Freetype": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
}
|
||||
"CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew"
|
||||
},
|
||||
"vendor": {
|
||||
"microsoft.com/VisualStudioSettings/CMake/1.0": {
|
||||
@@ -329,7 +318,11 @@
|
||||
"cacheVariables": {
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_HOME}/ndk/$env{ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake",
|
||||
"ANDROID_PLATFORM": "android-28"
|
||||
"ANDROID_PLATFORM": "android-28",
|
||||
"BUILD_SHARED_LIBS": {
|
||||
"type": "BOOL",
|
||||
"value": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -352,6 +345,31 @@
|
||||
"ANDROID_ABI": "x86_64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x-android-ci",
|
||||
"hidden": true,
|
||||
"inherits": [
|
||||
"android-base",
|
||||
"ci"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"DUSK_ENABLE_SENTRY_NATIVE": {
|
||||
"type": "BOOL",
|
||||
"value": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x-android-ci-arm64",
|
||||
"binaryDir": "${sourceDir}/build/android-arm64",
|
||||
"inherits": [
|
||||
"x-android-ci"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "arm64-v8a",
|
||||
"Rust_CARGO_TARGET": "aarch64-linux-android"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x-linux-ci",
|
||||
"hidden": true,
|
||||
@@ -391,7 +409,7 @@
|
||||
},
|
||||
"CMAKE_OSX_DEPLOYMENT_TARGET": "11.0",
|
||||
"CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew",
|
||||
"DUSK_MOVIE_SUPPORT": {
|
||||
"BUILD_SHARED_LIBS": {
|
||||
"type": "BOOL",
|
||||
"value": false
|
||||
}
|
||||
@@ -412,6 +430,7 @@
|
||||
"x-macos-ci"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"AURORA_DAWN_PROVIDER": "vendor",
|
||||
"CMAKE_OSX_ARCHITECTURES": "x86_64",
|
||||
"Rust_CARGO_TARGET": "x86_64-apple-darwin"
|
||||
}
|
||||
@@ -423,11 +442,7 @@
|
||||
],
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
|
||||
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
|
||||
"DUSK_MOVIE_SUPPORT": {
|
||||
"type": "BOOL",
|
||||
"value": false
|
||||
}
|
||||
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -437,11 +452,7 @@
|
||||
],
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
|
||||
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
|
||||
"DUSK_MOVIE_SUPPORT": {
|
||||
"type": "BOOL",
|
||||
"value": false
|
||||
}
|
||||
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -525,7 +536,7 @@
|
||||
"description": "iOS release build with debug info",
|
||||
"displayName": "iOS RelWithDebInfo",
|
||||
"targets": [
|
||||
"dusk"
|
||||
"dusklight"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -534,7 +545,7 @@
|
||||
"description": "tvOS release build with debug info",
|
||||
"displayName": "tvOS RelWithDebInfo",
|
||||
"targets": [
|
||||
"dusk"
|
||||
"dusklight"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -543,7 +554,7 @@
|
||||
"description": "Android arm64-v8a release build with debug info",
|
||||
"displayName": "Android arm64-v8a RelWithDebInfo",
|
||||
"targets": [
|
||||
"dusk"
|
||||
"dusklight"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -552,7 +563,16 @@
|
||||
"description": "Android x86_64 release build with debug info",
|
||||
"displayName": "Android x86_64 RelWithDebInfo",
|
||||
"targets": [
|
||||
"dusk"
|
||||
"dusklight"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "x-android-ci-arm64",
|
||||
"configurePreset": "x-android-ci-arm64",
|
||||
"description": "(Internal) Android CI arm64-v8a",
|
||||
"displayName": "(Internal) Android CI arm64-v8a",
|
||||
"targets": [
|
||||
"dusklight"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,51 +1,65 @@
|
||||
<div align="center">
|
||||
<img src="res/logo-mascot.png" alt="Logo" width="640">
|
||||
<img src="res/logo.png" alt="Logo" width="640">
|
||||
|
||||
<p align="center">
|
||||
<a href="https://twilitrealm.dev">Official Website</a>
|
||||
•
|
||||
<a href="https://discord.gg/QACynxeyna">Discord</a>
|
||||
<a href="https://discord.gg/6NpMhefCK9">Discord</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
# Overview
|
||||
|
||||
Dusk is a reverse-engineered reimplementation of Twilight Princess.
|
||||
Dusklight is a reverse-engineered reimplementation of Twilight Princess.
|
||||
|
||||
It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
|
||||
|
||||
# Setup
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game.
|
||||
> Dusklight does *not* provide any copyrighted assets. You must provide your own copy of the original game.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> At a minimum, Dusklight requires a GPU with support for either D3D12, Vulkan, or Metal. Your experience with specific hardware, operating systems, and drivers may vary. In particular, older Intel iGPUs have a high likelihood of incompatibility. We are also aware of a number of issues on devices with Adreno GPUs and are working to resolve them.
|
||||
|
||||
### 1. Verify your dump
|
||||
|
||||
First, make sure your dump of the game is clean and supported by Dusk. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
|
||||
First, make sure your dump of the game is clean and supported by Dusklight. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
|
||||
|
||||
| Version | SHA-1 hash |
|
||||
|--------------| ------------------------------------------ |
|
||||
| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` |
|
||||
| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` |
|
||||
|
||||
### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases)
|
||||
*Support for other versions of the game is planned in the future.
|
||||
|
||||
### 2. Download [Dusklight](https://github.com/TwilitRealm/dusklight/releases)
|
||||
|
||||
### 3. Setup the game
|
||||
|
||||
**Windows / macOS / Linux**
|
||||
- Extract the .zip file
|
||||
- Launch Dusk
|
||||
- Press **Select Disc Image** and provide the path to your supported game dump.
|
||||
- Launch Dusklight
|
||||
- Press **Select Disc Image** and provide the path to your supported game dump
|
||||
- Press **Play**!
|
||||
|
||||
**iOS**
|
||||
- Follow the [iOS setup guide](docs/ios-install-altstore.md)
|
||||
|
||||
**Android**
|
||||
- Install the Dusklight APK
|
||||
- Launch Dusklight
|
||||
- Press **Select Disc Image** and provide the path to your supported game dump
|
||||
- Press **Play**!
|
||||
|
||||
# Building
|
||||
|
||||
If you'd like to build Dusk from source, please read the [build instructions](docs/building.md).
|
||||
If you'd like to build Dusklight from source, please read the [build instructions](docs/building.md).
|
||||
|
||||
Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much.
|
||||
Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much. Please also see the [code conventions](docs/code-conventions.md).
|
||||
|
||||
# Credits
|
||||
|
||||
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors).
|
||||
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusklight/graphs/contributors).
|
||||
|
||||
<br/>
|
||||
<div align="center">
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
#!/bin/bash -ex
|
||||
shopt -s extglob
|
||||
|
||||
if [[ -n "${GITHUB_WORKSPACE:-}" ]]; then
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
fi
|
||||
|
||||
build_dir="$PWD/build"
|
||||
linuxdeploy="$build_dir/linuxdeploy-$(uname -m).AppImage"
|
||||
|
||||
# Get linuxdeploy
|
||||
cd "$RUNNER_WORKSPACE"
|
||||
curl -fOL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage
|
||||
chmod +x linuxdeploy-$(uname -m).AppImage
|
||||
mkdir -p "$build_dir"
|
||||
curl -fL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage" -o "$linuxdeploy"
|
||||
chmod +x "$linuxdeploy"
|
||||
|
||||
# Build AppImage
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}}
|
||||
cp -r build/install/!(*.*) build/appdir/usr/bin
|
||||
for install_path in build/install/*; do
|
||||
[[ "$(basename "$install_path")" == *.* ]] && continue
|
||||
cp -r "$install_path" build/appdir/usr/bin
|
||||
done
|
||||
cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor
|
||||
cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications
|
||||
cp platforms/freedesktop/dusklight.desktop build/appdir/usr/share/applications
|
||||
|
||||
cd build/install
|
||||
VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \
|
||||
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage
|
||||
VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \
|
||||
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$build_dir/appdir" --output appimage
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
sudo dnf groupinstall "Development Tools" "Development Libraries"
|
||||
```
|
||||
#### Setup
|
||||
Clone and initialize the Dusk repository
|
||||
Clone and initialize the Dusklight repository
|
||||
```sh
|
||||
git clone --recursive https://github.com/TwilitRealm/dusk.git
|
||||
cd dusk
|
||||
git clone --recursive https://github.com/TwilitRealm/dusklight.git
|
||||
cd dusklight
|
||||
git pull
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
@@ -93,6 +93,6 @@ Alternate presets available:
|
||||
#### Running
|
||||
Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
|
||||
```sh
|
||||
build/{preset}/dusk /path/to/game.rvz
|
||||
build/{preset}/dusklight/path/to/game.rvz
|
||||
```
|
||||
If no path is specified, Dusk defaults to `game.iso` in the current working directory.
|
||||
If no path is specified, Dusklight defaults to `game.iso` in the current working directory.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Code conventions for Dusk
|
||||
|
||||
## Upstream when appropriate
|
||||
|
||||
Bug fixes, documentation improvements, code cleanup, etc that also apply to the [original decompilation project](https://github.com/zeldaret/tp) should preferably be PR'd there.
|
||||
|
||||
## Properly indicate Dusk-modified code
|
||||
|
||||
When modifying original game code (i.e. in decomp) for Dusk's purposes, please clearly delineate such code as being Dusk-specific. Generally, this can be done by using `#if TARGET_PC` and keeping the original code in place. Use `#if AVOID_UB` for Undefined Behavior fixes to the original codebase.
|
||||
|
||||
## Miscellaneous things
|
||||
|
||||
* The original codebase makes heavy use of global `operator new` and similar overloads to allocate into a strict tree of heaps. This would cause many linkage headaches for us, so effectively all uses of `new` or `delete` in the original game code have been replaced with `JKR_NEW`, `JKR_DELETE`, or similar macros. See `JKRHeap.h` for the full list.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Installing Dusklight on iOS via AltStore
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Mac with Homebrew installed
|
||||
- iPhone connected via USB
|
||||
- Dusklight IPA file (download the latest `Dusklight-vX.X.X-ios-arm64.ipa` from the [releases page](https://github.com/TwilitRealm/dusk/releases))
|
||||
- Game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL)
|
||||
|
||||
## 1. Install AltServer
|
||||
|
||||
```sh
|
||||
brew install altserver
|
||||
open -a AltServer
|
||||
```
|
||||
|
||||
AltServer will appear in your menu bar.
|
||||
|
||||
## 2. Enable Developer Mode (iOS 16+)
|
||||
|
||||
- On your iPhone, go to **Settings > Privacy & Security > Developer Mode**
|
||||
- Toggle it on and restart when prompted
|
||||
|
||||
## 3. Install AltStore on Your iPhone
|
||||
|
||||
- Click AltServer in the menu bar
|
||||
- Click **Install AltStore > [Your iPhone]**
|
||||
- Enter your Apple ID credentials when prompted
|
||||
- On your iPhone, go to **Settings > General > VPN & Device Management**
|
||||
- Tap your Apple ID under "Developer App" and tap **Trust**
|
||||
|
||||
## 4. Copy Files to Your iPhone
|
||||
|
||||
Transfer the IPA and game disc to your iPhone so they're accessible in the Files app. A few ways to do this:
|
||||
|
||||
- **AirDrop** - Right-click the files on your Mac and choose Share > AirDrop
|
||||
- **iCloud Drive** - Place files in iCloud Drive on your Mac and they'll sync to Files on your iPhone
|
||||
- **USB transfer** - Connect your iPhone and drag files via Finder's sidebar
|
||||
- **Cloud storage** - Upload to Google Drive, Dropbox, etc. and download on your iPhone
|
||||
|
||||
## 5. Install via AltStore
|
||||
|
||||
- Open **AltStore** on your iPhone
|
||||
- Go to the **My Apps** tab
|
||||
- Tap the **+** button (top left)
|
||||
- Open the **Files** app and select the `.ipa` file
|
||||
@@ -1411,6 +1411,7 @@ set(DOLPHIN_FILES
|
||||
)
|
||||
|
||||
set(DUSK_FILES
|
||||
include/dusk/action_bindings.h
|
||||
include/dusk/endian_gx.hpp
|
||||
include/dusk/config.hpp
|
||||
include/dusk/dvd_asset.hpp
|
||||
@@ -1420,6 +1421,8 @@ set(DUSK_FILES
|
||||
src/dusk/asserts.cpp
|
||||
src/dusk/config.cpp
|
||||
src/dusk/crash_reporting.cpp
|
||||
src/dusk/data.cpp
|
||||
src/dusk/data.hpp
|
||||
src/dusk/endian.cpp
|
||||
src/dusk/extras.c
|
||||
src/dusk/file_select.cpp
|
||||
@@ -1430,29 +1433,31 @@ set(DUSK_FILES
|
||||
src/dusk/gyro.cpp
|
||||
src/dusk/gamepad_color.cpp
|
||||
src/dusk/autosave.cpp
|
||||
src/dusk/http/http.hpp
|
||||
src/dusk/io.cpp
|
||||
src/dusk/layout.cpp
|
||||
src/dusk/logging.cpp
|
||||
src/dusk/settings.cpp
|
||||
src/dusk/speedrun.cpp
|
||||
src/dusk/stubs.cpp
|
||||
src/dusk/update_check.cpp
|
||||
src/dusk/update_check.hpp
|
||||
#src/dusk/m_Do_ext_dusk.cpp
|
||||
src/dusk/imgui/ImGuiConfig.hpp
|
||||
src/dusk/imgui/ImGuiConsole.hpp
|
||||
src/dusk/imgui/ImGuiConsole.cpp
|
||||
src/dusk/imgui/ImGuiEngine.cpp
|
||||
src/dusk/imgui/ImGuiEngine.hpp
|
||||
src/dusk/imgui/ImGuiMenuGame.cpp
|
||||
src/dusk/imgui/ImGuiMenuGame.hpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.hpp
|
||||
src/dusk/imgui/ImGuiMenuTools.cpp
|
||||
src/dusk/imgui/ImGuiMenuTools.hpp
|
||||
src/dusk/imgui/ImGuiActorSpawner.cpp
|
||||
src/dusk/imgui/ImGuiProcessOverlay.cpp
|
||||
src/dusk/imgui/ImGuiCameraOverlay.cpp
|
||||
src/dusk/imgui/ImGuiHeapOverlay.cpp
|
||||
src/dusk/imgui/ImGuiControllerOverlay.cpp
|
||||
src/dusk/imgui/ImGuiStubLog.cpp
|
||||
src/dusk/imgui/ImGuiMapLoader.cpp
|
||||
src/dusk/imgui/ImGuiSaveEditor.cpp
|
||||
src/dusk/imgui/ImGuiStateShare.hpp
|
||||
src/dusk/imgui/ImGuiStateShare.cpp
|
||||
@@ -1491,6 +1496,8 @@ set(DUSK_FILES
|
||||
src/dusk/ui/prelaunch.hpp
|
||||
src/dusk/ui/preset.cpp
|
||||
src/dusk/ui/preset.hpp
|
||||
src/dusk/ui/reporting.cpp
|
||||
src/dusk/ui/reporting.hpp
|
||||
src/dusk/ui/select_button.cpp
|
||||
src/dusk/ui/select_button.hpp
|
||||
src/dusk/ui/settings.cpp
|
||||
@@ -1501,6 +1508,8 @@ set(DUSK_FILES
|
||||
src/dusk/ui/tab_bar.hpp
|
||||
src/dusk/ui/ui.cpp
|
||||
src/dusk/ui/ui.hpp
|
||||
src/dusk/ui/warp.cpp
|
||||
src/dusk/ui/warp.hpp
|
||||
src/dusk/ui/window.cpp
|
||||
src/dusk/ui/window.hpp
|
||||
src/dusk/achievements.cpp
|
||||
@@ -1515,4 +1524,12 @@ set(DUSK_FILES
|
||||
src/dusk/discord.hpp
|
||||
src/dusk/discord_presence.cpp
|
||||
src/dusk/version.cpp
|
||||
src/dusk/action_bindings.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
|
||||
)
|
||||
|
||||
@@ -4,30 +4,225 @@
|
||||
};
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
dusk = pkgs.stdenv.mkDerivation {
|
||||
name = "dusk";
|
||||
src = ./.;
|
||||
nativeBuildInputs = [
|
||||
pkgs.cmake
|
||||
pkgs.pkg-config
|
||||
pkgs.wayland
|
||||
];
|
||||
buildInputs = [
|
||||
pkgs.libGL
|
||||
pkgs.libX11
|
||||
pkgs.libXcursor
|
||||
pkgs.libxi
|
||||
pkgs.libxcb
|
||||
pkgs.libxrandr
|
||||
pkgs.libxscrnsaver
|
||||
pkgs.libxtst
|
||||
pkgs.libjpeg8
|
||||
pkgs.libxkbcommon
|
||||
pkgs.libglvnd
|
||||
];
|
||||
supportedSystems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||
pkgsFor = system: import nixpkgs { inherit system; };
|
||||
|
||||
# Dependencies that are not packaged in nixpkgs (used by the Linux package build):
|
||||
buildSources = pkgs: {
|
||||
aurora-src = pkgs.fetchFromGitHub {
|
||||
owner = "encounter";
|
||||
repo = "aurora";
|
||||
rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
|
||||
hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
|
||||
};
|
||||
dawn-src = pkgs.fetchzip {
|
||||
url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
|
||||
hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
|
||||
stripRoot = false;
|
||||
};
|
||||
nod-src = pkgs.fetchzip {
|
||||
url = "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.8/libnod-linux-x86_64.tar.gz";
|
||||
hash = "sha256-mUqvLsbsqaZ+HAjMmHYPYO+MgtanGRTw7Gzn5uXR5rE=";
|
||||
stripRoot = false;
|
||||
};
|
||||
# The version of imgui on nixpkgs does not map cleanly.
|
||||
imgui-src = pkgs.fetchFromGitHub {
|
||||
owner = "ocornut";
|
||||
repo = "imgui";
|
||||
rev = "v1.91.9b-docking";
|
||||
hash = "sha256-mQOJ6jCN+7VopgZ61yzaCnt4R1QLrW7+47xxMhFRHLQ=";
|
||||
};
|
||||
sqlite-src = pkgs.fetchzip {
|
||||
url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip";
|
||||
hash = "sha256-pNMR8zxaaqfAzQ0AQBOXMct4usdjey1Q0Gnitg06UhM=";
|
||||
};
|
||||
rmlui-src = pkgs.fetchzip {
|
||||
url = "https://github.com/mikke89/RmlUi/archive/f9b8c9e2935d5df2c7dff2c190d3968e99b0c3dc.tar.gz";
|
||||
hash = "sha256-g4O/JZUrrcseOz8o2QJRt+2CeuiLnVeuDJc906xvuIg=";
|
||||
};
|
||||
};
|
||||
|
||||
# Dusklight Actual (Linux x86_64 only — relies on prebuilt dawn/nod binaries)
|
||||
mkDusklight = pkgs:
|
||||
let srcs = buildSources pkgs;
|
||||
versionSuffix = if self ? shortRev && self.shortRev != null
|
||||
then "nix-${self.shortRev}"
|
||||
else "nix-dirty";
|
||||
in
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "dusklight";
|
||||
src = ./.;
|
||||
postUnpack = ''
|
||||
mkdir -p $sourceRoot/extern/aurora
|
||||
cp -r ${srcs.aurora-src}/. $sourceRoot/extern/aurora/
|
||||
chmod -R u+w $sourceRoot/extern/aurora
|
||||
sed -i '/add_subdirectory(tests)/d' $sourceRoot/extern/aurora/CMakeLists.txt
|
||||
'';
|
||||
# Remove last line to re-enable tests
|
||||
cmakeFlags = [
|
||||
"-DDUSK_VERSION_OVERRIDE=${versionSuffix}"
|
||||
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_CXXOPTS=${pkgs.cxxopts.src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_JSON=${pkgs.nlohmann_json.src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${srcs.dawn-src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_XXHASH=${pkgs.xxHash.src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_FMT=${pkgs.fmt.src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_TRACY=${pkgs.tracy.src}"
|
||||
"-DAURORA_SDL3_PROVIDER=system"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_NOD_PREBUILT=${srcs.nod-src}"
|
||||
"-DAURORA_NOD_PROVIDER=package"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_FREETYPE=${pkgs.freetype.src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_ZSTD=${pkgs.zstd.src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_SQLITE3=${srcs.sqlite-src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_IMGUI=${srcs.imgui-src}"
|
||||
"-DFETCHCONTENT_SOURCE_DIR_RMLUI=${srcs.rmlui-src}"
|
||||
"-DCMAKE_CROSSCOMPILING=ON" # Tests are not working as I didn't want to work through getting google's test suite working as well. This is the only guard I could find to disable it.
|
||||
];
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp dusklight $out/bin/dusklight
|
||||
cp -r ./res $out/bin/res
|
||||
|
||||
mkdir -p $out/share/applications
|
||||
cp $src/platforms/freedesktop/dusklight.desktop $out/share/applications/dusklight.desktop
|
||||
|
||||
for size in 16 32 48 64 128 256 512 1024; do
|
||||
install -Dm644 $src/platforms/freedesktop/''${size}x''${size}/apps/dusklight.png \
|
||||
$out/share/icons/hicolor/''${size}x''${size}/apps/dusklight.png
|
||||
done
|
||||
'';
|
||||
nativeBuildInputs = [
|
||||
pkgs.cmake
|
||||
pkgs.pkg-config
|
||||
pkgs.wayland
|
||||
];
|
||||
buildInputs = [
|
||||
pkgs.libGL
|
||||
pkgs.libX11
|
||||
pkgs.libXcursor
|
||||
pkgs.libxi
|
||||
pkgs.libxcb
|
||||
pkgs.libxrandr
|
||||
pkgs.libxscrnsaver
|
||||
pkgs.libxtst
|
||||
pkgs.libjpeg8
|
||||
pkgs.libxkbcommon
|
||||
pkgs.libglvnd
|
||||
pkgs.cxxopts
|
||||
pkgs.abseil-cpp
|
||||
pkgs.sdl3
|
||||
pkgs.fmt
|
||||
pkgs.tracy
|
||||
pkgs.freetype
|
||||
pkgs.zstd
|
||||
];
|
||||
};
|
||||
|
||||
# Tooling common to every supported host (Linux and macOS).
|
||||
commonDevTools = pkgs: [
|
||||
pkgs.cmake
|
||||
pkgs.ninja
|
||||
pkgs.pkg-config
|
||||
pkgs.git
|
||||
pkgs.python3
|
||||
pkgs.python3Packages.markupsafe
|
||||
pkgs.rustc
|
||||
pkgs.cargo
|
||||
pkgs.sccache
|
||||
];
|
||||
|
||||
# Linux-only system libraries — mirrors the apt deps from .github/workflows/build.yml
|
||||
# so the cmake presets resolve the same set of headers as CI.
|
||||
linuxDevDeps = pkgs: [
|
||||
# Compilers / linkers
|
||||
pkgs.clang
|
||||
pkgs.lld
|
||||
# C/C++ utilities
|
||||
pkgs.curl
|
||||
pkgs.openssl
|
||||
pkgs.zlib
|
||||
pkgs.libpng
|
||||
pkgs.libjpeg_turbo
|
||||
pkgs.freetype
|
||||
pkgs.zstd
|
||||
pkgs.fmt
|
||||
pkgs.tracy
|
||||
pkgs.cxxopts
|
||||
pkgs.abseil-cpp
|
||||
pkgs.sdl3
|
||||
pkgs.ncurses
|
||||
pkgs.libunwind
|
||||
pkgs.libusb1
|
||||
pkgs.fuse
|
||||
# Wayland / display server
|
||||
pkgs.wayland
|
||||
pkgs.wayland-protocols
|
||||
pkgs.libxkbcommon
|
||||
pkgs.libdecor
|
||||
# OpenGL / Vulkan
|
||||
pkgs.libGL
|
||||
pkgs.libGLU
|
||||
pkgs.libglvnd
|
||||
pkgs.vulkan-headers
|
||||
pkgs.vulkan-loader
|
||||
# X11
|
||||
pkgs.libX11
|
||||
pkgs.libxcb
|
||||
pkgs.libXcursor
|
||||
pkgs.libxi
|
||||
pkgs.libxrandr
|
||||
pkgs.libxscrnsaver
|
||||
pkgs.libxtst
|
||||
pkgs.libxinerama
|
||||
# Audio
|
||||
pkgs.alsa-lib
|
||||
pkgs.libpulseaudio
|
||||
pkgs.pipewire
|
||||
# System integration
|
||||
pkgs.dbus
|
||||
pkgs.udev
|
||||
pkgs.gtk3
|
||||
];
|
||||
|
||||
# On macOS we deliberately avoid pulling Nix's cc-wrapper so CMake picks up
|
||||
# Apple Clang and the Xcode SDK directly, matching the macOS CI workflow.
|
||||
mkDarwinShell = pkgs:
|
||||
pkgs.mkShellNoCC {
|
||||
packages = commonDevTools pkgs;
|
||||
shellHook = ''
|
||||
echo "Dusklight dev shell (macOS)"
|
||||
echo "Requires Xcode Command Line Tools for Apple Clang and the macOS SDK."
|
||||
echo "Configure: cmake --preset macos-default-relwithdebinfo"
|
||||
echo "Build: cmake --build --preset macos-default-relwithdebinfo"
|
||||
'';
|
||||
};
|
||||
|
||||
mkLinuxShell = pkgs:
|
||||
pkgs.mkShell {
|
||||
packages = (commonDevTools pkgs) ++ (linuxDevDeps pkgs);
|
||||
shellHook = ''
|
||||
echo "Dusklight dev shell (Linux)"
|
||||
echo "Configure: cmake --preset linux-default-relwithdebinfo"
|
||||
echo " cmake --preset linux-clang-relwithdebinfo"
|
||||
echo "Build: cmake --build --preset <preset>"
|
||||
'';
|
||||
};
|
||||
|
||||
mkDevShell = pkgs:
|
||||
if pkgs.stdenv.isDarwin
|
||||
then mkDarwinShell pkgs
|
||||
else mkLinuxShell pkgs;
|
||||
in {
|
||||
packages.x86_64-linux.default = dusk;
|
||||
packages.x86_64-linux.default = mkDusklight (pkgsFor "x86_64-linux");
|
||||
|
||||
devShells = forAllSystems (system: {
|
||||
default = mkDevShell (pkgsFor system);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1733,6 +1733,9 @@ public:
|
||||
int checkNewItemChange(u8 i_selItemIdx);
|
||||
void deleteEquipItem(BOOL i_isPlaySound, BOOL i_isDeleteKantera);
|
||||
void setLight();
|
||||
#if TARGET_PC
|
||||
void setGhostLanternLight();
|
||||
#endif
|
||||
void setFrontRollCrashShock(u8);
|
||||
void changeWarpMaterial(daAlink_c::daAlink_WARP_MAT_MODE i_matMode);
|
||||
void commonProcInit(daAlink_c::daAlink_PROC i_procID);
|
||||
@@ -2656,8 +2659,14 @@ public:
|
||||
void checkWaterInKandelaar(f32);
|
||||
void offKandelaarModel();
|
||||
int kandelaarModelCallBack();
|
||||
int ghostLanternModelCallBack();
|
||||
BOOL checkKandelaarEquipAnime() const;
|
||||
void preKandelaarDraw();
|
||||
void preGhostLanternDraw();
|
||||
static void* srchPoe(void*, void*);
|
||||
#if TARGET_PC
|
||||
void setGhostLanternModel();
|
||||
#endif
|
||||
void setKandelaarModel();
|
||||
void resetOilBottleModel();
|
||||
void commonKandelaarPourInit();
|
||||
@@ -3977,6 +3986,9 @@ public:
|
||||
/* 0x006FC */ J3DModel* mpKanteraModel;
|
||||
/* 0x00700 */ J3DModel* mpKanteraGlowModel;
|
||||
/* 0x00704 */ J3DAnmTextureSRTKey* mpKanteraGlowBtk;
|
||||
/* 0x006FC */ J3DModel* mpGhostLanternModel;
|
||||
/* 0x00700 */ J3DModel* mpGhostLanternGlowModel;
|
||||
/* 0x00704 */ J3DAnmTextureSRTKey* mpGhostLanternGlowBtk;
|
||||
/* 0x00708 */ J3DModel* mHeldItemModel;
|
||||
/* 0x0070C */ J3DModel* mpHookTipModel; // related to held item
|
||||
/* 0x00710 */ J3DModel* field_0x0710; // related to held item
|
||||
@@ -4373,6 +4385,7 @@ public:
|
||||
/* 0x03194 */ int field_0x3194;
|
||||
/* 0x03198 */ int field_0x3198;
|
||||
/* 0x0319C */ int field_0x319c;
|
||||
/* 0x0319C */ s32 ghostLanternFlameEffect[2];
|
||||
/* 0x031A0 */ u32 mModeFlg;
|
||||
/* 0x031A4 */ int field_0x31a4;
|
||||
/* 0x031A8 */ u32 field_0x31a8[2];
|
||||
@@ -4442,6 +4455,9 @@ public:
|
||||
/* 0x033F4 */ f32 field_0x33f4;
|
||||
/* 0x033F8 */ f32 field_0x33f8;
|
||||
/* 0x033FC */ f32 field_0x33fc;
|
||||
#if TARGET_PC
|
||||
/* 0x033FC */ f32 current_ghost_lantern_light_power;
|
||||
#endif
|
||||
/* 0x03400 */ f32 field_0x3400;
|
||||
/* 0x03404 */ f32 field_0x3404;
|
||||
/* 0x03408 */ f32 field_0x3408;
|
||||
@@ -4509,9 +4525,13 @@ public:
|
||||
/* 0x035F4 */ cXyz mMidnaAtnPos;
|
||||
/* 0x03600 */ cXyz mMidnaHairAtnPos;
|
||||
/* 0x0360C */ cXyz mKandelaarFlamePos;
|
||||
/* 0x0360C */ cXyz mGhostLanternFlamePos;
|
||||
/* 0x03618 */ cXyz field_0x3618;
|
||||
/* 0x03624 */ cXyz field_0x3624;
|
||||
/* 0x03630 */ cXyz field_0x3630;
|
||||
/* 0x03618 */ cXyz ghost_lantern_0x3618;
|
||||
/* 0x03624 */ cXyz ghost_lantern_0x3624;
|
||||
/* 0x03630 */ cXyz ghost_lantern_0x3630;
|
||||
/* 0x0363C */ cXyz field_0x363c[4];
|
||||
/* 0x0366C */ cXyz field_0x366c[4];
|
||||
/* 0x0369C */ cXyz field_0x369c;
|
||||
@@ -4552,6 +4572,18 @@ public:
|
||||
void handleWolfHowl();
|
||||
void handleQuickTransform();
|
||||
bool checkGyroAimContext();
|
||||
|
||||
void onIronBallChainInterpCallback();
|
||||
|
||||
static const int IRON_BALL_CHAIN_COUNT = 102;
|
||||
cXyz mIBChainInterpPrevPos[IRON_BALL_CHAIN_COUNT];
|
||||
cXyz mIBChainInterpCurrPos[IRON_BALL_CHAIN_COUNT];
|
||||
csXyz mIBChainInterpPrevAngle[IRON_BALL_CHAIN_COUNT];
|
||||
csXyz mIBChainInterpCurrAngle[IRON_BALL_CHAIN_COUNT];
|
||||
cXyz mIBChainInterpPrevHandRoot;
|
||||
cXyz mIBChainInterpCurrHandRoot;
|
||||
bool mIBChainInterpPrevValid;
|
||||
bool mIBChainInterpCurrValid;
|
||||
#endif
|
||||
}; // Size: 0x385C
|
||||
|
||||
@@ -6809,6 +6841,9 @@ public:
|
||||
static daAlinkHIO_boom_c0 const mBoomerang;
|
||||
static daAlinkHIO_bomb_c0 const mBomb;
|
||||
static daAlinkHIO_huLight_c0 const mLanternPL;
|
||||
#if TARGET_PC
|
||||
static daAlinkHIO_huLight_c0 const mGhostLanternPL;
|
||||
#endif
|
||||
static daAlinkHIO_kandelaar_c0 const mLantern;
|
||||
static daAlinkHIO_fmChain_c0 const mFmChain;
|
||||
static daAlinkHIO_magneBoots_c0 const mIronBoots;
|
||||
|
||||
@@ -80,6 +80,12 @@ public:
|
||||
/* 0x125C */ u32 field_0x125c;
|
||||
/* 0x1260 */ u8 field_0x1260[0x126C - 0x1260];
|
||||
/* 0x126C */ u8 HIOInit;
|
||||
#if TARGET_PC
|
||||
cXyz mStalkLineInterpPrev[12];
|
||||
cXyz mStalkLineInterpCurr[12];
|
||||
bool mStalkLineInterpPrevValid;
|
||||
bool mStalkLineInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_db_class) == 0x1270);
|
||||
|
||||
@@ -73,6 +73,12 @@ public:
|
||||
/* 0x124C */ f32 field_0x124c;
|
||||
/* 0x1250 */ u8 field_0x1250[0x1264 - 0x1250];
|
||||
/* 0x1264 */ u8 HIOInit;
|
||||
#if TARGET_PC
|
||||
cXyz mStalkLineInterpPrev[12];
|
||||
cXyz mStalkLineInterpCurr[12];
|
||||
bool mStalkLineInterpPrevValid;
|
||||
bool mStalkLineInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_hb_class) == 0x1268);
|
||||
|
||||
@@ -81,6 +81,15 @@ public:
|
||||
/* 0x306D */ u8 field_0x306D[0x307C - 0x306D];
|
||||
/* 0x307C */ u32 mBodyEffEmtrID;
|
||||
/* 0x3080 */ u8 mInitHIO;
|
||||
|
||||
#if TARGET_PC
|
||||
static const int HAIR_STRAND_COUNT = 22;
|
||||
static const int HAIR_SEGMENT_COUNT = 16;
|
||||
cXyz mHairInterpPrev[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT];
|
||||
cXyz mHairInterpCurr[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT];
|
||||
bool mHairInterpPrevValid;
|
||||
bool mHairInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_s1_class) == 0x3084);
|
||||
|
||||
@@ -74,6 +74,12 @@ public:
|
||||
/* 0x1250 */ f32 field_0x1250;
|
||||
/* 0x1254 */ u8 field_0x1254[0x1268 - 0x1254];
|
||||
/* 0x1268 */ u8 field_0x1268;
|
||||
#if TARGET_PC
|
||||
cXyz mLineMatInterpPrev[12];
|
||||
cXyz mLineMatInterpCurr[12];
|
||||
bool mLineMatInterpPrevValid;
|
||||
bool mLineMatInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_yd_class) == 0x126c);
|
||||
|
||||
@@ -63,6 +63,15 @@ public:
|
||||
/* 0x0BB4 */ yg_ke_s mYgKes[13];
|
||||
/* 0x1880 */ mDoExt_3DlineMat0_c mLineMat;
|
||||
/* 0x189C */ u8 mIsFirstSpawn;
|
||||
|
||||
#if TARGET_PC
|
||||
static const int TENTACLE_STRAND_COUNT = 13;
|
||||
static const int TENTACLE_SEGMENT_COUNT = 10;
|
||||
cXyz mTentacleInterpPrev[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT];
|
||||
cXyz mTentacleInterpCurr[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT];
|
||||
bool mTentacleInterpPrevValid;
|
||||
bool mTentacleInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_yg_class) == 0x18a0);
|
||||
|
||||
@@ -77,6 +77,12 @@ public:
|
||||
/* 0x1260 */ u32 field_0x1260;
|
||||
/* 0x1260 */ u8 field_0x1264[0x1270 - 0x1264];
|
||||
/* 0x1270 */ bool mIsHIOOwner;
|
||||
#if TARGET_PC
|
||||
cXyz mLineInterpPrev[12];
|
||||
cXyz mLineInterpCurr[12];
|
||||
bool mLineInterpPrevValid;
|
||||
bool mLineInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_yh_class) == 0x1274);
|
||||
|
||||
@@ -31,6 +31,10 @@ public:
|
||||
csXyz* getAngle() { return field_0x8a4; }
|
||||
J3DModelData* getModelData() { return mModelData; }
|
||||
|
||||
#if TARGET_PC
|
||||
void onInterpCallback();
|
||||
#endif
|
||||
|
||||
private:
|
||||
/* 0x568 */ request_of_phase_process_class mPhase;
|
||||
/* 0x570 */ J3DModelData* mModelData;
|
||||
@@ -42,6 +46,14 @@ private:
|
||||
/* 0x694 */ cXyz field_0x694[22];
|
||||
/* 0x79C */ cXyz field_0x79c[22];
|
||||
/* 0x8A4 */ csXyz field_0x8a4[22];
|
||||
|
||||
#if TARGET_PC
|
||||
static const int CHAIN_COUNT = 22;
|
||||
cXyz mChainInterpPrev[CHAIN_COUNT];
|
||||
cXyz mChainInterpCurr[CHAIN_COUNT];
|
||||
bool mChainInterpPrevValid;
|
||||
bool mChainInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(daObjFchain_c) == 0x928);
|
||||
|
||||
@@ -1845,6 +1845,12 @@ inline void dComIfGs_addDeathCount() {
|
||||
g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().addDeathCount();
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
inline u16 dComIfGs_getDeathCount() {
|
||||
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getDeathCount();
|
||||
}
|
||||
#endif
|
||||
|
||||
inline char* dComIfGs_getPlayerName() {
|
||||
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getPlayerName();
|
||||
}
|
||||
|
||||
@@ -196,7 +196,11 @@ public:
|
||||
/* 0x108 */ int mSkipTimer;
|
||||
/* 0x10C */ int mSkipParameter;
|
||||
/* 0x110 */ BOOL mIsSkipFade;
|
||||
#if TARGET_PC
|
||||
/* 0x114 */ char mSkipEventName[21];
|
||||
#else
|
||||
/* 0x114 */ char mSkipEventName[20];
|
||||
#endif
|
||||
/* 0x128 */ u8 mCompulsory;
|
||||
/* 0x129 */ bool mRoomInfoSet;
|
||||
/* 0x12C */ int mRoomNo;
|
||||
|
||||
@@ -71,6 +71,7 @@ void item_func_WOOD_STICK();
|
||||
void item_func_BOOMERANG();
|
||||
void item_func_SPINNER();
|
||||
void item_func_IRONBALL();
|
||||
void item_func_GHOST_LANTERN();
|
||||
void item_func_BOW();
|
||||
void item_func_HOOKSHOT();
|
||||
void item_func_HVY_BOOTS();
|
||||
@@ -260,6 +261,7 @@ int item_getcheck_func_WOOD_STICK();
|
||||
int item_getcheck_func_BOOMERANG();
|
||||
int item_getcheck_func_SPINNER();
|
||||
int item_getcheck_func_IRONBALL();
|
||||
int item_getcheck_func_GHOST_LANTERN();
|
||||
int item_getcheck_func_BOW();
|
||||
int item_getcheck_func_HOOKSHOT();
|
||||
int item_getcheck_func_HVY_BOOTS();
|
||||
|
||||
@@ -317,7 +317,7 @@ enum {
|
||||
/* 0xDE */ dItemNo_NOENTRY_222_e,
|
||||
/* 0xDF */ dItemNo_NOENTRY_223_e,
|
||||
/* 0xE0 */ dItemNo_POU_SPIRIT_e,
|
||||
/* 0xE1 */ dItemNo_NOENTRY_225_e,
|
||||
/* 0xE1 */ dItemNo_GHOST_LANTERN_e,
|
||||
/* 0xE2 */ dItemNo_NOENTRY_226_e,
|
||||
/* 0xE3 */ dItemNo_NOENTRY_227_e,
|
||||
/* 0xE4 */ dItemNo_NOENTRY_228_e,
|
||||
|
||||
@@ -223,6 +223,9 @@ private:
|
||||
/* 0x8F */ u8 field_0x8f;
|
||||
/* 0x90 */ u8 field_0x90;
|
||||
/* 0x91 */ u8 field_0x91;
|
||||
#if TARGET_PC
|
||||
bool previousMirror;
|
||||
#endif
|
||||
}; // Size: 0x94
|
||||
|
||||
class dMap_HIO_list_c : public dMpath_HIO_n::hioList_c {
|
||||
|
||||
@@ -86,8 +86,8 @@ public:
|
||||
/* 0x047 */ u8 field_0x47;
|
||||
/* 0x048 */ u8 mPoes;
|
||||
/* 0x049 */ u8 mPoesCopy;
|
||||
/* 0x04A */ u8 mItems[24];
|
||||
/* 0x062 */ u8 mItemsCopy[24];
|
||||
/* 0x04A */ u8 mItems[25];
|
||||
/* 0x062 */ u8 mItemsCopy[25];
|
||||
/* 0x07A */ u8 field_0x7a[24];
|
||||
/* 0x092 */ u8 mSwords[4];
|
||||
/* 0x096 */ u8 mShields[3];
|
||||
|
||||
@@ -486,6 +486,9 @@ public:
|
||||
mDeathCount++;
|
||||
}
|
||||
}
|
||||
#if TARGET_PC
|
||||
u16 getDeathCount() const { return mDeathCount; }
|
||||
#endif
|
||||
char* getPlayerName() const { return const_cast<char*>(mPlayerName); }
|
||||
void setPlayerName(const char* i_name) {
|
||||
#if AVOID_UB
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
namespace dusk {
|
||||
|
||||
enum class AchievementCategory : uint8_t {
|
||||
Story,
|
||||
Collection,
|
||||
Challenge,
|
||||
Collection,
|
||||
Minigame,
|
||||
Misc,
|
||||
Glitched
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "dusk/config_var.hpp"
|
||||
|
||||
namespace dusk {
|
||||
|
||||
enum class ActionBinds {
|
||||
FIRST_PERSON_CAMERA,
|
||||
CALL_MIDNA,
|
||||
OPEN_DUSKLIGHT_MENU,
|
||||
TURBO_SPEED_BUTTON,
|
||||
COUNT,
|
||||
};
|
||||
|
||||
struct ActionBindData {
|
||||
std::array<config::ActionBindConfigVar, 4>* configVars{};
|
||||
std::string actionName{};
|
||||
};
|
||||
|
||||
struct ActionBindPressData {
|
||||
bool pressedCurFrame{false};
|
||||
bool pressedPrevFrame{false};
|
||||
};
|
||||
|
||||
using ActionBindsMap = std::unordered_map<ActionBinds, ActionBindData>;
|
||||
|
||||
ActionBindsMap& getActionBinds();
|
||||
|
||||
bool isActionBound(ActionBinds action, u32 port);
|
||||
|
||||
void updateActionBindings();
|
||||
|
||||
bool getActionBindTrig(ActionBinds action, u32 port);
|
||||
|
||||
bool getActionBindHold(ActionBinds action, u32 port);
|
||||
|
||||
bool getActionBindHoldAnyPort(ActionBinds action);
|
||||
|
||||
int getActionBindButton(ActionBinds action, u32 port);
|
||||
|
||||
}
|
||||
@@ -7,7 +7,12 @@ namespace dusk {
|
||||
*
|
||||
* This gets used for file paths and such, and cannot be changed!
|
||||
*/
|
||||
constexpr auto AppName = "Dusk";
|
||||
constexpr auto AppName = "Dusklight";
|
||||
|
||||
/**
|
||||
* Previous AppName to migrate data from.
|
||||
*/
|
||||
constexpr auto LegacyAppName = "Dusk";
|
||||
|
||||
/**
|
||||
* \brief The internal organization name for the game.
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <dolphin/types.h>
|
||||
|
||||
namespace dusk::audio {
|
||||
|
||||
// Converts a 0-1 volume to a linear amplitude multiplier.
|
||||
// The curve is -4 dB per 10% step: 100% = 0 dB, 90% = -4 dB, ..., 0% = -inf dB
|
||||
inline f32 MasterVolumeToLinear(f32 v) {
|
||||
if (v <= 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
return std::pow(10.0f, (v - 1.0f) * 2.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the audio system and start playing audio.
|
||||
*/
|
||||
|
||||
@@ -13,5 +13,6 @@ void enterAutoSave();
|
||||
void autoSaving();
|
||||
void waitingForWrite();
|
||||
void endAutoSave();
|
||||
void toggleAutoSave(bool enabled);
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef DUSK_CONFIG_HPP
|
||||
#define DUSK_CONFIG_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "config_var.hpp"
|
||||
@@ -111,6 +112,18 @@ void Save();
|
||||
*/
|
||||
ConfigVarBase* GetConfigVar(std::string_view name);
|
||||
|
||||
/**
|
||||
* \brief Resets all custom action bindings for a specific port to nothing
|
||||
*
|
||||
* @param port The port to be cleared of action bindings
|
||||
*/
|
||||
void ClearAllActionBindings(int port);
|
||||
|
||||
/**
|
||||
* \brief Call a function on every registered CVar.
|
||||
*/
|
||||
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback);
|
||||
|
||||
template <ConfigValue T>
|
||||
const ConfigImplBase* GetConfigImpl() {
|
||||
static ConfigImpl<T> config;
|
||||
|
||||
@@ -48,6 +48,13 @@ enum class ConfigVarLayer : u8 {
|
||||
* Will not get saved to config.
|
||||
*/
|
||||
Override,
|
||||
|
||||
/**
|
||||
* The CVar is temporarily overridden by speedrun mode.
|
||||
* Will not get saved to config. Cleared when speedrun mode is disabled.
|
||||
* Lower priority than Override, so launch args still win.
|
||||
*/
|
||||
Speedrun,
|
||||
};
|
||||
|
||||
class ConfigImplBase;
|
||||
@@ -113,6 +120,12 @@ public:
|
||||
* This is necessary to make it legal to access.
|
||||
*/
|
||||
void markRegistered();
|
||||
|
||||
/**
|
||||
* Clear a speedrun-mode override if one is active on this CVar.
|
||||
* Safe to call on any CVar, no-op if not at the Speedrun layer.
|
||||
*/
|
||||
virtual void clearSpeedrunOverride() {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -162,6 +175,7 @@ class ConfigVar : public ConfigVarBase {
|
||||
T defaultValue;
|
||||
T value;
|
||||
T overrideValue;
|
||||
ConfigVarLayer priorLayer = ConfigVarLayer::Default;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -189,6 +203,7 @@ public:
|
||||
case ConfigVarLayer::Value:
|
||||
return value;
|
||||
case ConfigVarLayer::Override:
|
||||
case ConfigVarLayer::Speedrun:
|
||||
return overrideValue;
|
||||
default:
|
||||
abort();
|
||||
@@ -239,8 +254,54 @@ public:
|
||||
overrideValue = std::move(newValue);
|
||||
layer = ConfigVarLayer::Override;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Give a CVar a speedrun-mode override value.
|
||||
*
|
||||
* Lower priority than a launch-arg override. Cleared when speedrun mode is disabled.
|
||||
* The overridden value will not get saved to config.
|
||||
*
|
||||
* @param newValue The new value the CVar will get.
|
||||
*/
|
||||
void setSpeedrunValue(T newValue) {
|
||||
checkRegistered();
|
||||
if (layer != ConfigVarLayer::Override) {
|
||||
priorLayer = layer;
|
||||
overrideValue = std::move(newValue);
|
||||
layer = ConfigVarLayer::Speedrun;
|
||||
}
|
||||
}
|
||||
|
||||
void clearOverride() {
|
||||
checkRegistered();
|
||||
if (layer == ConfigVarLayer::Override) {
|
||||
overrideValue = {};
|
||||
layer = ConfigVarLayer::Value;
|
||||
}
|
||||
}
|
||||
|
||||
void clearSpeedrunOverride() override {
|
||||
checkRegistered();
|
||||
if (layer == ConfigVarLayer::Speedrun) {
|
||||
overrideValue = {};
|
||||
layer = priorLayer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the user-persisted value, ignoring any temporary overrides.
|
||||
*
|
||||
* Used by Save() to write the correct value even when a speedrun override is active.
|
||||
*/
|
||||
[[nodiscard]] constexpr const T& getValueForSave() const noexcept {
|
||||
checkRegistered();
|
||||
const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer;
|
||||
return effectiveLayer == ConfigVarLayer::Default ? defaultValue : value;
|
||||
}
|
||||
};
|
||||
|
||||
using ActionBindConfigVar = ConfigVar<int>;
|
||||
|
||||
}
|
||||
|
||||
#endif // DUSK_CONFIG_VAR_HPP
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
namespace dusk {
|
||||
namespace dusk::crash_reporting {
|
||||
|
||||
void InitializeCrashReporting();
|
||||
void ShutdownCrashReporting();
|
||||
enum class Consent {
|
||||
Unavailable,
|
||||
Unknown,
|
||||
Given,
|
||||
Revoked,
|
||||
};
|
||||
|
||||
} // namespace dusk
|
||||
void initialize();
|
||||
void shutdown();
|
||||
Consent get_consent();
|
||||
void set_consent(bool enabled);
|
||||
|
||||
} // namespace dusk::crash_reporting
|
||||
|
||||
@@ -12,6 +12,7 @@ void rollgoalTableOffset(s16& out_ax, s16& out_az);
|
||||
extern bool s_sensor_keep_alive;
|
||||
bool get_sensor_keep_alive();
|
||||
void set_sensor_keep_alive(bool value);
|
||||
bool rollgoal_gyro_enabled();
|
||||
} // namespace dusk::gyro
|
||||
|
||||
#endif
|
||||
|
||||
@@ -14,7 +14,6 @@ constexpr const char* SHOW_DEBUG_OVERLAY = "F3";
|
||||
constexpr const char* SHOW_HEAP_VIEWER = "F4";
|
||||
constexpr const char* SHOW_PLAYER_INFO = "F5";
|
||||
constexpr const char* SHOW_SAVE_EDITOR = "F6";
|
||||
constexpr const char* SHOW_MAP_LOADER = "F7";
|
||||
constexpr const char* SHOW_STATE_SHARE = "F8";
|
||||
constexpr const char* SHOW_DEBUG_CAMERA = "F9";
|
||||
constexpr const char* SHOW_AUDIO_DEBUG = "F10";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef DUSK_IO_HPP
|
||||
#define DUSK_IO_HPP
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
// I can't believe it's 2026 and neither SDL (no error codes) nor
|
||||
@@ -15,7 +16,7 @@ namespace dusk::io {
|
||||
* Methods on this class throw appropriate C++ exceptions when an error occurs.
|
||||
*/
|
||||
class FileStream {
|
||||
void* file;
|
||||
FILE* file;
|
||||
|
||||
public:
|
||||
FileStream() noexcept;
|
||||
@@ -23,7 +24,7 @@ public:
|
||||
/**
|
||||
* \brief Take ownership of a FILE* handle.
|
||||
*/
|
||||
explicit FileStream(void* file);
|
||||
explicit FileStream(FILE* file);
|
||||
FileStream(const FileStream& other) = delete;
|
||||
FileStream(FileStream&& other) noexcept;
|
||||
|
||||
@@ -34,6 +35,11 @@ public:
|
||||
*/
|
||||
static FileStream OpenRead(const char* utf8Path);
|
||||
|
||||
/**
|
||||
* \brief Open a file for reading at the given path.
|
||||
*/
|
||||
static FileStream OpenRead(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* \brief Create a file for writing.
|
||||
*
|
||||
@@ -41,16 +47,33 @@ public:
|
||||
*/
|
||||
static FileStream Create(const char* utf8Path);
|
||||
|
||||
/**
|
||||
* \brief Create a file for writing.
|
||||
*
|
||||
* If there is an existing file, its contents are demolished.
|
||||
*/
|
||||
static FileStream Create(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* \brief Read the byte contents of a file directly into a vector.
|
||||
*/
|
||||
static std::vector<u8> ReadAllBytes(const char* utf8Path);
|
||||
|
||||
/**
|
||||
* \brief Read the byte contents of a file directly into a vector.
|
||||
*/
|
||||
static std::vector<u8> ReadAllBytes(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* \brief Read the byte contents of a file directly into a vector.
|
||||
*/
|
||||
static void WriteAllText(const char* utf8Path, std::string_view text);
|
||||
|
||||
/**
|
||||
* \brief Read the byte contents of a file directly into a vector.
|
||||
*/
|
||||
static void WriteAllText(const std::filesystem::path& path, std::string_view text);
|
||||
|
||||
/**
|
||||
* \brief Read the remaining contents of the file directly into a vector.
|
||||
*/
|
||||
@@ -59,17 +82,24 @@ public:
|
||||
/**
|
||||
* Get direct access to the underlying FILE* handle.
|
||||
*/
|
||||
[[nodiscard]] void* GetFileHandle() const noexcept {
|
||||
return file;
|
||||
}
|
||||
[[nodiscard]] void* GetFileHandle() const noexcept { return file; }
|
||||
|
||||
/**
|
||||
* Write data to the file.
|
||||
*/
|
||||
void Write(const char* data, size_t dataLen);
|
||||
|
||||
FILE* ToInner();
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a std::filesystem::path to a std::string, UTF-8, without exploding on Windows.
|
||||
*/
|
||||
inline std::string fs_path_to_string(const std::filesystem::path& path) {
|
||||
const auto u8str = path.u8string();
|
||||
return {reinterpret_cast<const char*>(u8str.c_str())};
|
||||
}
|
||||
|
||||
} // namespace dusk::io
|
||||
|
||||
#endif // DUSK_IO_HPP
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
#ifndef DUSK_MAIN_H
|
||||
#define DUSK_MAIN_H
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace dusk {
|
||||
extern bool IsRunning;
|
||||
extern bool IsShuttingDown;
|
||||
extern bool IsGameLaunched;
|
||||
extern bool IsFocusPaused;
|
||||
extern bool RestartRequested;
|
||||
extern std::filesystem::path ConfigPath;
|
||||
|
||||
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
|
||||
extern bool IsRunning;
|
||||
extern bool IsShuttingDown;
|
||||
extern bool IsGameLaunched;
|
||||
extern bool RestartRequested;
|
||||
extern std::filesystem::path ConfigPath;
|
||||
extern std::filesystem::path CachePath;
|
||||
|
||||
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
|
||||
(defined(TARGET_OS_TV) && TARGET_OS_TV)
|
||||
inline constexpr bool SupportsProcessRestart = false;
|
||||
inline constexpr bool SupportsProcessRestart = false;
|
||||
#else
|
||||
inline constexpr bool SupportsProcessRestart = true;
|
||||
inline constexpr bool SupportsProcessRestart = true;
|
||||
#endif
|
||||
|
||||
void RequestRestart() noexcept;
|
||||
}
|
||||
void RequestRestart() noexcept;
|
||||
|
||||
} // namespace dusk
|
||||
|
||||
#endif // DUSK_MAIN_H
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef DUSK_CONFIG_H
|
||||
#define DUSK_CONFIG_H
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "dusk/config_var.hpp"
|
||||
|
||||
namespace dusk {
|
||||
@@ -27,6 +29,11 @@ enum class DiscVerificationState : u8 {
|
||||
HashMismatch,
|
||||
};
|
||||
|
||||
enum class GyroMode : u8 {
|
||||
Sensor = 0,
|
||||
Mouse = 1,
|
||||
};
|
||||
|
||||
namespace config {
|
||||
template <>
|
||||
struct ConfigEnumRange<BloomMode> {
|
||||
@@ -45,6 +52,12 @@ struct ConfigEnumRange<DiscVerificationState> {
|
||||
static constexpr auto min = DiscVerificationState::Unknown;
|
||||
static constexpr auto max = DiscVerificationState::HashMismatch;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ConfigEnumRange<GyroMode> {
|
||||
static constexpr auto min = GyroMode::Sensor;
|
||||
static constexpr auto max = GyroMode::Mouse;
|
||||
};
|
||||
}
|
||||
|
||||
// Persistent user settings
|
||||
@@ -102,8 +115,9 @@ struct UserSettings {
|
||||
ConfigVar<bool> minimalHUD;
|
||||
ConfigVar<bool> pauseOnFocusLost;
|
||||
ConfigVar<bool> enableLinkDollRotation;
|
||||
ConfigVar<bool> enableAchievementNotifications;
|
||||
|
||||
ConfigVar<bool> enableAchievementToasts;
|
||||
ConfigVar<bool> enableControllerToasts;
|
||||
ConfigVar<bool> enableDiscordPresence;
|
||||
|
||||
// Graphics
|
||||
ConfigVar<BloomMode> bloomMode;
|
||||
@@ -114,12 +128,14 @@ struct UserSettings {
|
||||
ConfigVar<int> shadowResolutionMultiplier;
|
||||
ConfigVar<bool> enableDepthOfField;
|
||||
ConfigVar<bool> enableMapBackground;
|
||||
ConfigVar<bool> disableCutscenePillarboxing;
|
||||
|
||||
// Audio
|
||||
ConfigVar<bool> noLowHpSound;
|
||||
ConfigVar<bool> midnasLamentNonStop;
|
||||
|
||||
// Input
|
||||
ConfigVar<GyroMode> gyroMode;
|
||||
ConfigVar<bool> enableGyroAim;
|
||||
ConfigVar<bool> enableGyroRollgoal;
|
||||
ConfigVar<float> gyroSensitivityX;
|
||||
@@ -132,13 +148,17 @@ struct UserSettings {
|
||||
ConfigVar<bool> freeCamera;
|
||||
ConfigVar<bool> invertCameraXAxis;
|
||||
ConfigVar<bool> invertCameraYAxis;
|
||||
ConfigVar<bool> invertFirstPersonXAxis;
|
||||
ConfigVar<bool> invertFirstPersonYAxis;
|
||||
ConfigVar<float> freeCameraSensitivity;
|
||||
ConfigVar<bool> debugFlyCam;
|
||||
ConfigVar<bool> debugFlyCamLockEvents;
|
||||
ConfigVar<bool> allowBackgroundInput;
|
||||
|
||||
// Cheats
|
||||
ConfigVar<bool> infiniteHearts;
|
||||
ConfigVar<bool> infiniteArrows;
|
||||
ConfigVar<bool> infiniteSeeds;
|
||||
ConfigVar<bool> infiniteBombs;
|
||||
ConfigVar<bool> infiniteOil;
|
||||
ConfigVar<bool> infiniteOxygen;
|
||||
@@ -149,19 +169,25 @@ struct UserSettings {
|
||||
ConfigVar<bool> alwaysGreatspin;
|
||||
ConfigVar<bool> enableFastIronBoots;
|
||||
ConfigVar<bool> canTransformAnywhere;
|
||||
ConfigVar<bool> fastRoll;
|
||||
ConfigVar<bool> fastSpinner;
|
||||
ConfigVar<bool> freeMagicArmor;
|
||||
ConfigVar<bool> invincibleEnemies;
|
||||
|
||||
// Technical
|
||||
ConfigVar<bool> restoreWiiGlitches;
|
||||
|
||||
// Controls
|
||||
ConfigVar<bool> enableTurboKeybind;
|
||||
ConfigVar<bool> enableResetKeybind;
|
||||
|
||||
// Tools
|
||||
ConfigVar<bool> speedrunMode;
|
||||
ConfigVar<bool> liveSplitEnabled;
|
||||
ConfigVar<bool> showSpeedrunRTATimer;
|
||||
ConfigVar<bool> recordingMode;
|
||||
ConfigVar<bool> showInputViewer;
|
||||
ConfigVar<bool> showInputViewerGyro;
|
||||
} game;
|
||||
|
||||
struct {
|
||||
@@ -171,10 +197,18 @@ struct UserSettings {
|
||||
ConfigVar<bool> skipPreLaunchUI;
|
||||
ConfigVar<bool> showPipelineCompilation;
|
||||
ConfigVar<bool> wasPresetChosen;
|
||||
ConfigVar<bool> enableCrashReporting;
|
||||
ConfigVar<bool> checkForUpdates;
|
||||
ConfigVar<int> cardFileType;
|
||||
ConfigVar<bool> enableAdvancedSettings;
|
||||
} backend;
|
||||
|
||||
// Arrays of size 4 for 4 ports
|
||||
struct {
|
||||
std::array<ActionBindConfigVar, 4> firstPersonCamera;
|
||||
std::array<ActionBindConfigVar, 4> callMidna;
|
||||
std::array<ActionBindConfigVar, 4> openDusklightMenu;
|
||||
std::array<ActionBindConfigVar, 4> turboSpeedButton;
|
||||
} actionBindings;
|
||||
};
|
||||
|
||||
UserSettings& getSettings();
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
struct SpeedrunInfo {
|
||||
void startRun() {
|
||||
m_isRunStarted = true;
|
||||
m_startTimestamp = OSGetTime();
|
||||
}
|
||||
|
||||
void stopRun() {
|
||||
m_isRunStarted = false;
|
||||
m_endTimestamp = OSGetTime() - m_startTimestamp;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_isRunStarted = false;
|
||||
m_startTimestamp = 0;
|
||||
m_endTimestamp = 0;
|
||||
m_isPauseIGT = false;
|
||||
m_loadStartTimestamp = 0;
|
||||
m_totalLoadTime = 0;
|
||||
m_igtTimer = 0;
|
||||
}
|
||||
|
||||
bool m_isRunStarted = false;
|
||||
OSTime m_startTimestamp = 0;
|
||||
OSTime m_endTimestamp = 0;
|
||||
|
||||
bool m_isPauseIGT = false;
|
||||
OSTime m_loadStartTimestamp = 0;
|
||||
OSTime m_totalLoadTime = 0;
|
||||
OSTime m_igtTimer = 0;
|
||||
};
|
||||
|
||||
extern SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
void resetForSpeedrunMode();
|
||||
|
||||
} // namespace dusk
|
||||
@@ -8,6 +8,10 @@
|
||||
#include "JSystem/JUtility/JUTFader.h"
|
||||
#include "JSystem/J2DGraph/J2DOrthoGraph.h"
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include <algorithm>
|
||||
#endif
|
||||
|
||||
JUTFader::JUTFader(int x, int y, int width, int height, JUtility::TColor pColor)
|
||||
: mColor(pColor), mBox(x, y, x + width, y + height) {
|
||||
mStatus = None;
|
||||
@@ -63,14 +67,24 @@ void JUTFader::advance() {
|
||||
|
||||
void JUTFader::control() {
|
||||
advance();
|
||||
#ifndef TARGET_PC
|
||||
// FRAME INTERP NOTE: Draw is called by JFWDisplay when interpolation is active
|
||||
draw();
|
||||
#endif
|
||||
}
|
||||
|
||||
void JUTFader::draw() {
|
||||
if (mColor.a != 0) {
|
||||
#ifdef TARGET_PC
|
||||
if (dusk::frame_interp::is_enabled() && mDuration != 0) {
|
||||
const auto step = dusk::frame_interp::get_interpolation_step();
|
||||
const auto progress = static_cast<f32>(mTimer) / static_cast<f32>(mDuration);
|
||||
const auto timer = mTimer - 1 + step + progress;
|
||||
auto alpha = timer / mDuration;
|
||||
if (mStatus == FadeIn) {
|
||||
alpha = 1.0f - alpha;
|
||||
}
|
||||
alpha = std::clamp(alpha, 0.0f, 1.0f);
|
||||
mColor.a = static_cast<u8>(alpha * 255.0f);
|
||||
}
|
||||
#endif
|
||||
J2DOrthoGraph orthograph;
|
||||
orthograph.setColor(mColor);
|
||||
orthograph.fillBox(mBox);
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
#include <cmath>
|
||||
#include "os_report.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/action_bindings.h"
|
||||
#endif
|
||||
|
||||
u32 JUTGamePad::CRumble::sChannelMask[4] = {
|
||||
PAD_CHAN0_BIT,
|
||||
PAD_CHAN1_BIT,
|
||||
@@ -85,6 +89,9 @@ u32 JUTGamePad::sRumbleSupported;
|
||||
|
||||
u32 JUTGamePad::read() {
|
||||
sRumbleSupported = PADRead(mPadStatus);
|
||||
#if TARGET_PC
|
||||
dusk::updateActionBindings();
|
||||
#endif
|
||||
|
||||
switch (sClampMode) {
|
||||
case EClampStick:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Android Shell
|
||||
|
||||
This directory contains a minimal SDLActivity-based Android app wrapper for Dusk.
|
||||
This directory contains a minimal SDLActivity-based Android app wrapper for Dusklight.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -66,12 +66,11 @@ Output APK:
|
||||
You can pass command-line args through the activity intent:
|
||||
|
||||
```bash
|
||||
adb shell am start -n com.twilitrealm.dusk/.DuskActivity \
|
||||
--es dusk_args "'/sdcard/Download/The Legend of Zelda: Twilight Princess (USA).iso'"
|
||||
adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \
|
||||
--es dusk_args "--backend vulkan"
|
||||
```
|
||||
|
||||
Supported extras:
|
||||
|
||||
- `dusk_args`: single shell-like argument string
|
||||
- `dusk_argv`: string-array argv
|
||||
- `dusk_disc`: compatibility shortcut (single ISO path)
|
||||
|
||||
@@ -2,12 +2,22 @@ plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
def duskRepoDir = rootProject.projectDir.parentFile.parentFile
|
||||
def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusklight').get().asFile
|
||||
def syncDuskAssets = tasks.register('syncDuskAssets', Sync) {
|
||||
from(new File(duskRepoDir, 'res')) {
|
||||
into 'res'
|
||||
exclude '**/.DS_Store'
|
||||
}
|
||||
into duskGeneratedAssetsDir
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.twilitrealm.dusk'
|
||||
namespace 'dev.twilitrealm.dusk'
|
||||
compileSdk 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.twilitrealm.dusk'
|
||||
applicationId 'dev.twilitrealm.dusk'
|
||||
minSdk 26
|
||||
targetSdk 36
|
||||
versionCode 1
|
||||
@@ -27,7 +37,7 @@ android {
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['src/main/jniLibs']
|
||||
assets.srcDirs = ['../../assets']
|
||||
assets.srcDirs = [duskGeneratedAssetsDir]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,3 +58,10 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
tasks.configureEach { task ->
|
||||
if ((task.name.startsWith('merge') && task.name.endsWith('Assets')) ||
|
||||
task.name.toLowerCase().contains('lint')) {
|
||||
task.dependsOn(syncDuskAssets)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# Keep SDL activity and related JNI bridge methods.
|
||||
-keep class org.libsdl.app.** { *; }
|
||||
-keep class dev.twilitrealm.dusk.DuskHttpClient { *; }
|
||||
-keep class dev.twilitrealm.dusk.DuskHttpClient$Response { *; }
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<uses-feature android:name="android.hardware.type.pc" android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -23,8 +24,19 @@
|
||||
<meta-data android:name="android.game_mode_config"
|
||||
android:resource="@xml/game_mode_config" />
|
||||
|
||||
<provider
|
||||
android:name="dev.twilitrealm.dusk.DuskDocumentsProvider"
|
||||
android:authorities="dev.twilitrealm.dusk.documents"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name="com.twilitrealm.dusk.DuskActivity"
|
||||
android:name="dev.twilitrealm.dusk.DuskActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||
android:exported="true"
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
package com.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.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DuskActivity extends SDLActivity {
|
||||
private static final String TAG = "DuskActivity";
|
||||
private static final int FOLDER_DIALOG_REQUEST_CODE = 0x4455;
|
||||
private static final String EXTERNAL_STORAGE_AUTHORITY =
|
||||
"com.android.externalstorage.documents";
|
||||
|
||||
private long folderDialogUserdata = 0;
|
||||
|
||||
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();
|
||||
@@ -61,18 +82,46 @@ public class DuskActivity extends SDLActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
hideSystemBars();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
hideSystemBars();
|
||||
}
|
||||
|
||||
@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) {
|
||||
getWindow().getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
|
||||
}else {
|
||||
View decorView = getWindow().getDecorView();
|
||||
// Hide the status bar.
|
||||
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
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);
|
||||
// Remember that you should never show the action bar if the
|
||||
// status bar is hidden, so hide that too if necessary.
|
||||
ActionBar actionBar = getActionBar();
|
||||
actionBar.hide();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +149,241 @@ public class DuskActivity extends SDLActivity {
|
||||
return splitArgs(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
String discPath = intent.getStringExtra("dusk_disc");
|
||||
if (discPath != null && !discPath.isEmpty()) {
|
||||
return new String[] { discPath };
|
||||
}
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@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;
|
||||
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);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
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 : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,6 +256,7 @@ public class HIDDeviceManager {
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
0x2dc8, // 8BitDo
|
||||
0x37d7, // Flydigi
|
||||
0x9886, // ASTRO Gaming
|
||||
};
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
private static final String TAG = "SDL";
|
||||
private static final int SDL_MAJOR_VERSION = 3;
|
||||
private static final int SDL_MINOR_VERSION = 4;
|
||||
private static final int SDL_MICRO_VERSION = 2;
|
||||
private static final int SDL_MICRO_VERSION = 4;
|
||||
/*
|
||||
// Display InputType.SOURCE/CLASS of events and devices
|
||||
//
|
||||
@@ -2032,7 +2032,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
try {
|
||||
ParcelFileDescriptor pfd = mSingleton.getContentResolver().openFileDescriptor(Uri.parse(uri), mode);
|
||||
return pfd != null ? pfd.detachFd() : -1;
|
||||
} catch (FileNotFoundException e) {
|
||||
} catch (FileNotFoundException | SecurityException e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
@@ -2227,4 +2227,3 @@ class SDLClipboardHandler implements
|
||||
SDLActivity.onNativeClipboardChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,17 +65,15 @@ class SDLInputConnection extends BaseInputConnection
|
||||
|
||||
@Override
|
||||
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
|
||||
if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {
|
||||
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
|
||||
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
|
||||
if (beforeLength > 0 && afterLength == 0) {
|
||||
// backspace(s)
|
||||
while (beforeLength-- > 0) {
|
||||
nativeGenerateScancodeForUnichar('\b');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
|
||||
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
|
||||
if (beforeLength > 0 && afterLength == 0) {
|
||||
// backspace(s)
|
||||
while (beforeLength-- > 0) {
|
||||
nativeGenerateScancodeForUnichar('\b');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!super.deleteSurroundingText(beforeLength, afterLength)) {
|
||||
return false;
|
||||
|
||||
@@ -35,6 +35,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
|
||||
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
|
||||
|
||||
private static native void auroraNativeSetSurfaceReady(boolean ready);
|
||||
|
||||
// Sensors
|
||||
protected SensorManager mSensorManager;
|
||||
protected Display mDisplay;
|
||||
@@ -96,6 +98,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceCreated()");
|
||||
auroraNativeSetSurfaceReady(false);
|
||||
SDLActivity.onNativeSurfaceCreated();
|
||||
}
|
||||
|
||||
@@ -103,6 +106,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceDestroyed()");
|
||||
auroraNativeSetSurfaceReady(false);
|
||||
|
||||
// Transition to pause, if needed
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||
@@ -192,6 +196,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
|
||||
/* Surface is ready */
|
||||
mIsSurfaceReady = true;
|
||||
auroraNativeSetSurfaceReady(true);
|
||||
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||
SDLActivity.handleNativeState();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Dusk</string>
|
||||
<string name="app_name">Dusklight</string>
|
||||
<string name="documents_provider_root_name">Dusklight Data</string>
|
||||
<string name="documents_provider_summary">Saves, texture packs, settings, and logs</string>
|
||||
</resources>
|
||||
|
||||
@@ -14,9 +14,22 @@ if [[ -z "$ANDROID_NDK_VER" ]] && [[ -d "$ANDROID_HOME_DIR/ndk" ]]; then
|
||||
fi
|
||||
|
||||
if [[ -n "$ANDROID_NDK_VER" ]]; then
|
||||
TOOLCHAIN_BIN="$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
if [[ -x "$TOOLCHAIN_BIN/llvm-strip" ]]; then
|
||||
STRIP_TOOL="$TOOLCHAIN_BIN/llvm-strip"
|
||||
case "$(uname -s)" in
|
||||
Darwin) HOST_TAG="darwin-x86_64" ;;
|
||||
Linux) HOST_TAG="linux-x86_64" ;;
|
||||
*) HOST_TAG="" ;;
|
||||
esac
|
||||
|
||||
PREBUILT_DIR="$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt"
|
||||
if [[ -n "$HOST_TAG" && -x "$PREBUILT_DIR/$HOST_TAG/bin/llvm-strip" ]]; then
|
||||
STRIP_TOOL="$PREBUILT_DIR/$HOST_TAG/bin/llvm-strip"
|
||||
else
|
||||
for candidate in "$PREBUILT_DIR"/*/bin/llvm-strip; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
STRIP_TOOL="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -25,29 +38,35 @@ copy_lib() {
|
||||
local src="$2"
|
||||
local dst_dir="$APP_DIR/$abi"
|
||||
local dst="$dst_dir/libmain.so"
|
||||
local tmp="$dst_dir/.libmain.so.$$"
|
||||
if [[ ! -f "$src" ]]; then
|
||||
echo "Missing native library for $abi: $src" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$dst_dir"
|
||||
cp -f "$src" "$dst"
|
||||
cp -f "$src" "$tmp"
|
||||
if [[ "$ANDROID_STAGE_STRIP" != "0" ]] && [[ -n "$STRIP_TOOL" ]]; then
|
||||
"$STRIP_TOOL" --strip-debug "$dst"
|
||||
echo "Staged and stripped $src -> $dst"
|
||||
"$STRIP_TOOL" --strip-unneeded "$tmp"
|
||||
mv -f "$tmp" "$dst"
|
||||
echo "Stripped and staged $src -> $dst"
|
||||
else
|
||||
mv -f "$tmp" "$dst"
|
||||
echo "Staged $src -> $dst (strip disabled or strip tool unavailable)"
|
||||
fi
|
||||
}
|
||||
|
||||
declare -A ABI_TO_LIB=(
|
||||
["arm64-v8a"]="$ROOT_DIR/build/android-arm64/libmain.so"
|
||||
["x86_64"]="$ROOT_DIR/build/android-x86_64/libmain.so"
|
||||
)
|
||||
|
||||
# Drop any previously staged ABI directories to avoid stale APK contents.
|
||||
rm -rf "$APP_DIR/x86" "$APP_DIR/arm64-v8a" "$APP_DIR/x86_64"
|
||||
|
||||
for abi in $ANDROID_STAGE_ABIS; do
|
||||
src="${ABI_TO_LIB[$abi]:-}"
|
||||
if [[ -z "$src" ]]; then
|
||||
echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
|
||||
exit 1
|
||||
fi
|
||||
case "$abi" in
|
||||
arm64-v8a) src="$ROOT_DIR/build/android-arm64/libmain.so" ;;
|
||||
x86_64) src="$ROOT_DIR/build/android-x86_64/libmain.so" ;;
|
||||
*)
|
||||
echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
copy_lib "$abi" "$src"
|
||||
done
|
||||
|
||||
@@ -14,5 +14,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "dusk-android"
|
||||
rootProject.name = "dusklight-android"
|
||||
include ':app'
|
||||
|
||||
|
Before Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 928 KiB |
|
Before Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 79 B |
|
After Width: | Height: | Size: 1014 B |
|
Before Width: | Height: | Size: 761 B |
|
After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 99 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 123 B |
|
After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 179 B |
|
After Width: | Height: | Size: 8.7 KiB |
@@ -1,9 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=Dusk
|
||||
GenericName=Dusk
|
||||
Comment=The Legend of Zelda: Twilight Princess
|
||||
Exec=dusk
|
||||
Icon=dusk
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Graphics;3DGraphics;Game
|
||||
@@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Name=Dusklight
|
||||
GenericName=Dusklight
|
||||
Comment=PC port of a classic adventure game
|
||||
Exec=dusklight
|
||||
Icon=dusklight
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Game;
|
||||
@@ -79,5 +79,11 @@
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>LSSupportsGameMode</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>Dusk</string>
|
||||
<string>Dusklight</string>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>Dusk</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -28,5 +28,7 @@
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>LSSupportsGameMode</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -45,5 +45,7 @@
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Automatic</string>
|
||||
<key>LSSupportsGameMode</key>
|
||||
<true />
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -24,9 +24,9 @@ BEGIN
|
||||
VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0"
|
||||
VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0"
|
||||
VALUE "FileVersion", "@DUSK_VERSION_STRING@\0"
|
||||
VALUE "InternalName", "dusk\0"
|
||||
VALUE "InternalName", "dusklight\0"
|
||||
VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0"
|
||||
VALUE "OriginalFilename", "dusk.exe\0"
|
||||
VALUE "OriginalFilename", "dusklight.exe\0"
|
||||
VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0"
|
||||
VALUE "ProductVersion", "@DUSK_VERSION_STRING@\0"
|
||||
END
|
||||
|
Before Width: | Height: | Size: 457 KiB |
|
Before Width: | Height: | Size: 340 KiB After Width: | Height: | Size: 642 KiB |
@@ -163,30 +163,30 @@ icon {
|
||||
}
|
||||
|
||||
icon.arrow-forward {
|
||||
width: 24dp;
|
||||
height: 24dp;
|
||||
font-size: 24dp;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
font-size: 1.2em;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
icon.trophy {
|
||||
width: 24dp;
|
||||
height: 24dp;
|
||||
font-size: 24dp;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
font-size: 1.2em;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
icon.controller {
|
||||
width: 24dp;
|
||||
height: 24dp;
|
||||
font-size: 24dp;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
font-size: 1.2em;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
icon.warning {
|
||||
width: 24dp;
|
||||
height: 24dp;
|
||||
font-size: 24dp;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
font-size: 1.2em;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
@@ -201,6 +201,37 @@ fps {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
speedrun-timer {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 99;
|
||||
background-color: rgba(0, 0, 0, 65%);
|
||||
padding: 2dp 4dp;
|
||||
pointer-events: none;
|
||||
font-family: "Noto Mono";
|
||||
font-size: 16dp;
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
speedrun-timer[open] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
speedrun-rta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
speedrun-rta[open] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
speedrun-igt {
|
||||
display: block;
|
||||
}
|
||||
|
||||
fps[open] {
|
||||
display: block;
|
||||
}
|
||||
@@ -274,3 +305,18 @@ logo img.outer {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 640dp) {
|
||||
toast {
|
||||
top: 20dp;
|
||||
right: 20dp;
|
||||
}
|
||||
|
||||
toast.controller-warning {
|
||||
bottom: 20dp;
|
||||
}
|
||||
|
||||
toast.menu-notification {
|
||||
top: 20dp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ menu {
|
||||
right: auto;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
/* Scale based on a reference screen width, 428/1216 */
|
||||
width: 35.230264vw;
|
||||
/* Scale based on a reference screen width, 856/1216 */
|
||||
width: 70.394736vw;
|
||||
min-width: 428dp;
|
||||
max-width: 856dp;
|
||||
max-width: 50vw;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -83,9 +83,8 @@ body.mirrored menu {
|
||||
hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 8dp;
|
||||
gap: 4dp;
|
||||
}
|
||||
|
||||
body.mirrored hero {
|
||||
@@ -96,19 +95,19 @@ hero img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
eyebrow {
|
||||
font-family: "Alegreya SC";
|
||||
font-size: 32dp;
|
||||
}
|
||||
|
||||
@media (min-width: 1216dp) {
|
||||
.eyebrow {
|
||||
eyebrow {
|
||||
/* Same logic as .menu, 32/1216 */
|
||||
font-size: 2.631579vw;
|
||||
}
|
||||
}
|
||||
|
||||
.eyebrow span {
|
||||
eyebrow span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -273,20 +272,60 @@ body.mirrored version-info {
|
||||
font-size: 20dp;
|
||||
}
|
||||
|
||||
/* TODO: Hidden until an actual update checker is introduced */
|
||||
.update {
|
||||
display: none;
|
||||
font-size: 16dp;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
color: #D8F999;
|
||||
color: #A6A09B;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8dp;
|
||||
font-size: 20dp;
|
||||
}
|
||||
|
||||
.detail,
|
||||
.update span {
|
||||
.update[state=checking],
|
||||
.update[state=failed] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.update[state=available] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#update-download {
|
||||
display: none;
|
||||
margin: 0dp;
|
||||
padding: 0dp;
|
||||
border-width: 0dp;
|
||||
background-color: transparent;
|
||||
color: #D8F999;
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
decorator: horizontal-gradient(#00000000 #00000000);
|
||||
}
|
||||
|
||||
.update[state=available] #update-download {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2dp;
|
||||
}
|
||||
|
||||
#update-download icon {
|
||||
display: block;
|
||||
width: 18dp;
|
||||
height: 18dp;
|
||||
font-family: "Material Symbols Rounded";
|
||||
font-weight: normal;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
.detail {
|
||||
color: #A6A09B;
|
||||
}
|
||||
|
||||
body.mirrored .update {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Startup animation */
|
||||
.intro-item {
|
||||
opacity: 0;
|
||||
@@ -334,8 +373,8 @@ body.animate-in .intro-item {
|
||||
}
|
||||
|
||||
menu {
|
||||
left: 20dp;
|
||||
right: 20dp;
|
||||
left: 32dp;
|
||||
right: 32dp;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
@@ -346,8 +385,8 @@ body.animate-in .intro-item {
|
||||
}
|
||||
|
||||
body.mirrored menu {
|
||||
left: 20dp;
|
||||
right: 20dp;
|
||||
left: 32dp;
|
||||
right: 32dp;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
@@ -355,7 +394,7 @@ body.animate-in .intro-item {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
max-width: 48%;
|
||||
|
||||
margin-left: 32dp;
|
||||
}
|
||||
|
||||
body.mirrored hero {
|
||||
@@ -397,9 +436,61 @@ body.animate-in .intro-item {
|
||||
decorator: horizontal-gradient(#FEE685FF #FEE68500);
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
disc-info,
|
||||
version-info {
|
||||
eyebrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
disc-info {
|
||||
right: 32dp;
|
||||
left: auto;
|
||||
bottom: 32dp;
|
||||
top: auto;
|
||||
text-align: right;
|
||||
font-size: 16dp;
|
||||
}
|
||||
|
||||
#disc-status {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#disc-status icon {
|
||||
font-size: 20dp;
|
||||
}
|
||||
|
||||
#disc-version {
|
||||
font-size: 16dp;
|
||||
}
|
||||
|
||||
version-info {
|
||||
right: 32dp;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
top: 32dp;
|
||||
text-align: right;
|
||||
font-size: 16dp;
|
||||
}
|
||||
|
||||
.update {
|
||||
font-size: 16dp;
|
||||
}
|
||||
|
||||
body.mirrored disc-info {
|
||||
right: auto;
|
||||
left: 32dp;
|
||||
bottom: 32dp;
|
||||
top: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body.mirrored version-info {
|
||||
right: auto;
|
||||
left: 32dp;
|
||||
bottom: auto;
|
||||
top: 32dp;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body.mirrored #disc-status {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ body {
|
||||
window {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 1088dp;
|
||||
@@ -104,6 +105,12 @@ window content pane:last-of-type > div {
|
||||
line-height: 1.625;
|
||||
}
|
||||
|
||||
.data-folder-current {
|
||||
display: block;
|
||||
font-size: 16dp;
|
||||
color: rgba(224, 219, 200, 65%);
|
||||
}
|
||||
|
||||
window content pane > spacer {
|
||||
display: block;
|
||||
/* Completes the 24dp bottom inset after the pane's 8dp gap. */
|
||||
@@ -198,6 +205,11 @@ button:not(:disabled):active {
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
button.modal-btn {
|
||||
flex: 1 1 0;
|
||||
text-align: center;
|
||||
@@ -291,6 +303,19 @@ icon.question-mark {
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
.achievement-total {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 64dp;
|
||||
height: 64dp;
|
||||
line-height: 64dp;
|
||||
font-family: "Fira Sans Condensed";
|
||||
font-weight: bold;
|
||||
font-size: 16dp;
|
||||
color: rgba(224, 219, 200, 55%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.achievement-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -484,7 +509,6 @@ progress.verification-progress-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: stretch;
|
||||
align-items: stretch;
|
||||
gap: 12dp;
|
||||
width: 100%;
|
||||
|
||||
@@ -51,9 +51,13 @@
|
||||
#include "d/actor/d_a_ni.h"
|
||||
#include "d/d_s_play.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "res/Object/Alink.h"
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
static int daAlink_Create(fopAc_ac_c* i_this);
|
||||
static int daAlink_Delete(daAlink_c* i_this);
|
||||
@@ -6096,6 +6100,21 @@ void daAlink_c::setItemMatrix(int param_0) {
|
||||
modelCalc(mpKanteraGlowModel);
|
||||
}
|
||||
|
||||
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
|
||||
simpleAnmPlay(mpGhostLanternGlowBtk);
|
||||
|
||||
mDoMtx_stack_c::copy(mpLinkModel->getAnmMtx(mLeftItemJntNo));
|
||||
mDoMtx_stack_c::transM(-2.0f, -0.1f, -0.7f);
|
||||
mDoMtx_stack_c::XYZrotM(cM_deg2s(100.0f), cM_deg2s(9.3f), cM_deg2s(183.0f));
|
||||
mDoMtx_stack_c::scaleM(0.75f, 0.75f, 0.75f);
|
||||
mpGhostLanternModel->setBaseTRMtx(mDoMtx_stack_c::get());
|
||||
|
||||
modelCalc(mpGhostLanternModel);
|
||||
mDoMtx_stack_c::transS(mGhostLanternFlamePos);
|
||||
mpGhostLanternGlowModel->setBaseTRMtx(mDoMtx_stack_c::get());
|
||||
modelCalc(mpGhostLanternGlowModel);
|
||||
}
|
||||
|
||||
setSwordPos();
|
||||
simpleAnmPlay(m_mSwordBtk);
|
||||
simpleAnmPlay(m_mSwordBrk);
|
||||
@@ -6916,7 +6935,7 @@ const daAlink_BckData* daAlink_c::getMainBckData(daAlink_c::daAlink_ANM i_anmID)
|
||||
{dRes_ID_ALANM_BCK_WAITHS_e, dRes_ID_ALANM_BCK_WAITHK_e},
|
||||
};
|
||||
|
||||
if (mEquipItem == dItemNo_KANTERA_e) {
|
||||
if (mEquipItem == dItemNo_KANTERA_e || mEquipItem == dItemNo_GHOST_LANTERN_e) {
|
||||
if (i_anmID == ANM_WAIT) {
|
||||
return &kandelaarAnm[0];
|
||||
}
|
||||
@@ -9362,6 +9381,12 @@ BOOL daAlink_c::spActionTrigger() {
|
||||
}
|
||||
|
||||
BOOL daAlink_c::midnaTalkTrigger() const {
|
||||
#if TARGET_PC
|
||||
// If we have a custom bind for Midna, check that instead
|
||||
if (dusk::isActionBound(dusk::ActionBinds::CALL_MIDNA, 0)) {
|
||||
return dusk::getActionBindTrig(dusk::ActionBinds::CALL_MIDNA, 0);
|
||||
}
|
||||
#endif
|
||||
return mItemTrigger & BTN_Z;
|
||||
}
|
||||
|
||||
@@ -12057,7 +12082,8 @@ void daAlink_c::itemUnequip(u16 i_itemID, f32 i_playSpeed) {
|
||||
u16 anm_id;
|
||||
const daAlinkHIO_anm_c* anm_data;
|
||||
|
||||
if (i_itemID == dItemNo_BOOMERANG_e || i_itemID == dItemNo_COPY_ROD_e || i_itemID == dItemNo_KANTERA_e ||
|
||||
if (i_itemID == dItemNo_BOOMERANG_e || i_itemID == dItemNo_COPY_ROD_e ||
|
||||
i_itemID == dItemNo_KANTERA_e || i_itemID == dItemNo_GHOST_LANTERN_e ||
|
||||
i_itemID == dItemNo_HOOKSHOT_e || checkBottleItem(i_itemID))
|
||||
{
|
||||
anm_id = dRes_ID_ALANM_BCK_TAKEL_e;
|
||||
@@ -14147,6 +14173,11 @@ BOOL daAlink_c::setItemModel() {
|
||||
}
|
||||
}
|
||||
|
||||
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
|
||||
setGhostLanternModel();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mEquipItem == dItemNo_KANTERA_e) {
|
||||
setKandelaarModel();
|
||||
return 1;
|
||||
@@ -14488,6 +14519,7 @@ enum daAlink_ItemProc {
|
||||
/* 13 */ ITEM_PROC_BOTTLE_SWING,
|
||||
/* 14 */ ITEM_PROC_NOT_USE_ITEM,
|
||||
/* 15 */ ITEM_PROC_GRASS_WHISTLE,
|
||||
/* 16 */ ITEM_PROC_GHOST_LANTERN,
|
||||
};
|
||||
|
||||
int daAlink_c::changeItemTriggerKeepProc(u8 i_selItemIdx, int i_procType) {
|
||||
@@ -14512,7 +14544,8 @@ int daAlink_c::changeItemTriggerKeepProc(u8 i_selItemIdx, int i_procType) {
|
||||
} else {
|
||||
procKandelaarPourInit();
|
||||
}
|
||||
} else if (i_procType == ITEM_PROC_FISHING_FOOD) {
|
||||
}
|
||||
else if (i_procType == ITEM_PROC_FISHING_FOOD) {
|
||||
procFishingFoodInit();
|
||||
} else if (i_procType == ITEM_PROC_BOOTS_EQUIP) {
|
||||
procBootsEquipInit();
|
||||
@@ -14787,6 +14820,10 @@ void daAlink_c::deleteEquipItem(BOOL i_isPlaySound, BOOL i_isDeleteKantera) {
|
||||
mIronBallChainPos = NULL;
|
||||
mIronBallChainAngle = NULL;
|
||||
field_0x3848 = NULL;
|
||||
#if TARGET_PC
|
||||
mIBChainInterpPrevValid = false;
|
||||
mIBChainInterpCurrValid = false;
|
||||
#endif
|
||||
field_0x0774 = NULL;
|
||||
field_0x0778 = NULL;
|
||||
mpHookshotLinChk = NULL;
|
||||
@@ -14813,6 +14850,43 @@ void daAlink_c::deleteEquipItem(BOOL i_isPlaySound, BOOL i_isDeleteKantera) {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
void daAlink_c::setGhostLanternLight() {
|
||||
const daAlinkHIO_huLight_c1* light_m = &mpHIO->mItem.mGhostLanternPL.m;
|
||||
|
||||
BOOL var_r28 = false;
|
||||
|
||||
f32 lightPower = 0.0f;
|
||||
daE_HP_c* foundPoe = (daE_HP_c*)fopAcM_Search(srchPoe, this);
|
||||
if (mEquipItem == dItemNo_GHOST_LANTERN_e && foundPoe != NULL) {
|
||||
lightPower = 1.0f;
|
||||
}
|
||||
|
||||
cLib_chaseF(¤t_ghost_lantern_light_power, lightPower, 0.25f);
|
||||
|
||||
cXyz spB8;
|
||||
f32 var_f27;
|
||||
if (current_ghost_lantern_light_power > 0.0f) {
|
||||
GXColor sp30 = {(u8)light_m->mColorR, (u8)light_m->mColorG, (u8)light_m->mColorB, 0xFF};
|
||||
sp30.r = 120;
|
||||
sp30.g = 150;
|
||||
sp30.b = 150;
|
||||
|
||||
Vec sp5C = {0.0f, light_m->mYOffset, light_m->mZOffset};
|
||||
|
||||
f32 var_f26;
|
||||
spB8 = mGhostLanternFlamePos;
|
||||
var_f27 = 0.0f;
|
||||
var_f26 = cM_sht2d(-shape_angle.y);
|
||||
|
||||
dKy_WolfEyeLight_set(&spB8, var_f27 + light_m->mXAngle, var_f26,
|
||||
(light_m->mWidth * current_ghost_lantern_light_power) / light_m->mPower, &sp30, current_ghost_lantern_light_power,
|
||||
light_m->mAngleAttenuationType,
|
||||
light_m->mDistanceAttenuationType);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void daAlink_c::setLight() {
|
||||
const daAlinkHIO_huLight_c1* light_m = &mpHIO->mItem.mLanternPL.m;
|
||||
BOOL var_r28 = false;
|
||||
@@ -16109,6 +16183,9 @@ int daAlink_c::procSlideLand() {
|
||||
|
||||
int daAlink_c::procFrontRollInit() {
|
||||
BOOL is_guard_anime = checkUpperGuardAnime();
|
||||
#ifdef TARGET_PC
|
||||
const f32 fastRollMultiplier = dusk::getSettings().game.fastRoll ? 2.0f : 1.0f;
|
||||
#endif
|
||||
|
||||
if (mProcID == PROC_FRONT_ROLL && mDemo.getDemoMode() == daPy_demo_c::DEMO_FRONT_ROLL_e) {
|
||||
return 0;
|
||||
@@ -16124,10 +16201,16 @@ int daAlink_c::procFrontRollInit() {
|
||||
roll_anm_speed = mpHIO->mFrontRoll.m.mRollAnm.mStartFrame;
|
||||
}
|
||||
|
||||
setSingleAnime(ANM_FRONT_ROLL, mpHIO->mFrontRoll.m.mRollAnm.mSpeed, roll_anm_speed,
|
||||
setSingleAnime(ANM_FRONT_ROLL,
|
||||
#ifdef TARGET_PC
|
||||
mpHIO->mFrontRoll.m.mRollAnm.mSpeed * fastRollMultiplier,
|
||||
#else
|
||||
mpHIO->mFrontRoll.m.mRollAnm.mSpeed,
|
||||
#endif
|
||||
roll_anm_speed,
|
||||
mpHIO->mFrontRoll.m.mRollAnm.mEndFrame,
|
||||
mpHIO->mFrontRoll.m.mRollAnm.mInterpolation);
|
||||
|
||||
|
||||
mNormalSpeed = speedF * mpHIO->mFrontRoll.m.mSpeedRate + mpHIO->mFrontRoll.m.mInitSpeed;
|
||||
|
||||
f32 max_speed = mpHIO->mFrontRoll.m.mInitSpeed + mpHIO->mMove.m.mMaxSpeed * mpHIO->mFrontRoll.m.mSpeedRate;
|
||||
@@ -16140,11 +16223,20 @@ int daAlink_c::procFrontRollInit() {
|
||||
}
|
||||
|
||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
#if TARGET_PC
|
||||
if (!(dusk::getSettings().game.enableFastIronBoots))
|
||||
#endif
|
||||
{
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
}
|
||||
} else if (checkHeavyStateOn(TRUE, TRUE)) {
|
||||
mNormalSpeed *= mHeavySpeedMultiplier;
|
||||
}
|
||||
|
||||
#ifdef TARGET_PC
|
||||
mNormalSpeed *= fastRollMultiplier;
|
||||
#endif
|
||||
|
||||
current.angle.y = shape_angle.y;
|
||||
voiceStart(Z2SE_AL_V_BACKTEN);
|
||||
mProcVar2.field_0x300c = 0;
|
||||
@@ -16275,8 +16367,13 @@ int daAlink_c::procFrontRollCrashInit() {
|
||||
speed.y = mpHIO->mFrontRoll.m.mCrashSpeedV;
|
||||
|
||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY;
|
||||
#if TARGET_PC
|
||||
if (!(dusk::getSettings().game.enableFastIronBoots))
|
||||
#endif
|
||||
{
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY;
|
||||
}
|
||||
}
|
||||
|
||||
ANGLE_ADD_2(current.angle.y, 0x8000);
|
||||
@@ -16370,6 +16467,9 @@ int daAlink_c::procFrontRollSuccess() {
|
||||
|
||||
int daAlink_c::procSideRollInit(int param_0) {
|
||||
BOOL is_prev_guardAnm = checkUpperGuardAnime();
|
||||
#ifdef TARGET_PC
|
||||
const f32 fastRollMultiplier = dusk::getSettings().game.fastRoll ? 2.0f : 1.0f;
|
||||
#endif
|
||||
|
||||
if (!commonProcInitNotSameProc(PROC_SIDE_ROLL)) {
|
||||
return 0;
|
||||
@@ -16386,17 +16486,30 @@ int daAlink_c::procSideRollInit(int param_0) {
|
||||
current.angle.y = shape_angle.y + -0x4000;
|
||||
}
|
||||
|
||||
setSingleAnime(anmID, mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed,
|
||||
setSingleAnime(anmID,
|
||||
#ifdef TARGET_PC
|
||||
mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed * fastRollMultiplier,
|
||||
#else
|
||||
mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed,
|
||||
#endif
|
||||
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mStartFrame,
|
||||
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mEndFrame,
|
||||
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mInterpolation);
|
||||
mNormalSpeed = mpHIO->mGuard.mTurnMove.m.mSideRollSpeed;
|
||||
|
||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
#if TARGET_PC
|
||||
if (!(dusk::getSettings().game.enableFastIronBoots))
|
||||
#endif
|
||||
{
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
}
|
||||
} else if (checkHeavyStateOn(TRUE, TRUE)) {
|
||||
mNormalSpeed *= mHeavySpeedMultiplier;
|
||||
}
|
||||
#ifdef TARGET_PC
|
||||
mNormalSpeed *= fastRollMultiplier;
|
||||
#endif
|
||||
|
||||
setFootEffectProcType(0);
|
||||
field_0x2f9d = 4;
|
||||
@@ -18212,7 +18325,8 @@ int daAlink_c::execute() {
|
||||
resetUpperAnime(UPPER_2, 5.0f);
|
||||
}
|
||||
|
||||
if (mEquipItem == dItemNo_KANTERA_e && checkNoUpperAnime() && !checkKandelaarEquipAnime() &&
|
||||
if ((mEquipItem == dItemNo_KANTERA_e || mEquipItem == dItemNo_GHOST_LANTERN_e) &&
|
||||
checkNoUpperAnime() && !checkKandelaarEquipAnime() &&
|
||||
(checkModeFlg(MODE_UNK_1000) || mProcID == PROC_CROUCH))
|
||||
{
|
||||
if (checkReinRide()) {
|
||||
@@ -18563,8 +18677,29 @@ int daAlink_c::execute() {
|
||||
setCollision();
|
||||
setAttentionPos();
|
||||
setLight();
|
||||
#if TARGET_PC
|
||||
setGhostLanternLight();
|
||||
#endif
|
||||
setEffect();
|
||||
|
||||
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
|
||||
cXyz effscale(0.75f, 0.75f, 0.75f);
|
||||
|
||||
static u16 particleNmaeDt[2] = {
|
||||
0x8789,
|
||||
0x878A,
|
||||
};
|
||||
|
||||
for (s32 i = 0; i < 2; i++) {
|
||||
ghostLanternFlameEffect[i] = dComIfGp_particle_set(ghostLanternFlameEffect[i], particleNmaeDt[i],
|
||||
&mGhostLanternFlamePos, 0, &effscale);
|
||||
}
|
||||
} else {
|
||||
for (s32 i = 0; i < 2; i++) {
|
||||
stopDrawParticle(ghostLanternFlameEffect[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (mClothesChangeWaitTimer != 0) {
|
||||
mDoMtx_stack_c::copy(mpLinkModel->getBaseTRMtx());
|
||||
|
||||
@@ -19266,6 +19401,10 @@ void daAlink_c::shadowDraw() {
|
||||
dComIfGd_addRealShadow(shadowID, mpKanteraModel);
|
||||
}
|
||||
|
||||
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
|
||||
dComIfGd_addRealShadow(shadowID, mpGhostLanternModel);
|
||||
}
|
||||
|
||||
if (checkEquipHeavyBoots()) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
dComIfGd_addRealShadow(shadowID, mpLinkBootModels[i]);
|
||||
@@ -19426,6 +19565,22 @@ int daAlink_c::draw() {
|
||||
}
|
||||
}
|
||||
|
||||
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
|
||||
daE_HP_c* foundPoe = (daE_HP_c*)fopAcM_Search(srchPoe, this);
|
||||
|
||||
for (s32 i = 0; i < 2; i++) {
|
||||
JPABaseEmitter* emitter_gl = dComIfGp_particle_getEmitter(ghostLanternFlameEffect[i]);
|
||||
if (emitter_gl != NULL) {
|
||||
emitter_gl->setLocalTranslation({2.5f, -15.0f, 0.0f});
|
||||
if (checkPlayerNoDraw() || foundPoe == NULL) {
|
||||
emitter_gl->stopDrawParticle();
|
||||
} else {
|
||||
emitter_gl->playDrawParticle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mClothesChangeWaitTimer != 0) {
|
||||
if (mProcID == PROC_METAMORPHOSE) {
|
||||
if (mProcVar3.field_0x300e > 0) {
|
||||
@@ -19717,6 +19872,27 @@ int daAlink_c::draw() {
|
||||
)
|
||||
{
|
||||
dComIfGd_getOpaListDark()->entryImm(mpHookChain, 0);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableFrameInterpolation &&
|
||||
mEquipItem == dItemNo_IRONBALL_e &&
|
||||
mIronBallChainPos != NULL && mIronBallChainAngle != NULL)
|
||||
{
|
||||
if (mIBChainInterpCurrValid) {
|
||||
memcpy(mIBChainInterpPrevPos, mIBChainInterpCurrPos, IRON_BALL_CHAIN_COUNT * sizeof(cXyz));
|
||||
memcpy(mIBChainInterpPrevAngle, mIBChainInterpCurrAngle, IRON_BALL_CHAIN_COUNT * sizeof(csXyz));
|
||||
mIBChainInterpPrevHandRoot = mIBChainInterpCurrHandRoot;
|
||||
mIBChainInterpPrevValid = true;
|
||||
}
|
||||
|
||||
memcpy(mIBChainInterpCurrPos, mIronBallChainPos, IRON_BALL_CHAIN_COUNT * sizeof(cXyz));
|
||||
memcpy(mIBChainInterpCurrAngle, mIronBallChainAngle, IRON_BALL_CHAIN_COUNT * sizeof(csXyz));
|
||||
mIBChainInterpCurrHandRoot = mHookshotTopPos;
|
||||
mIBChainInterpCurrValid = true;
|
||||
|
||||
dusk::frame_interp::add_interpolation_callback(&ironBallChainInterpCallback, this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19730,6 +19906,15 @@ int daAlink_c::draw() {
|
||||
basicModelDraw(mpKanteraGlowModel);
|
||||
}
|
||||
|
||||
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
|
||||
daE_HP_c* foundPoe = (daE_HP_c*)fopAcM_Search(srchPoe, this);
|
||||
modelDraw(mpGhostLanternModel, isPlayerNoDraw);
|
||||
if (foundPoe != NULL) {
|
||||
preGhostLanternDraw();
|
||||
modelDraw(mpGhostLanternGlowModel, isPlayerNoDraw);
|
||||
}
|
||||
}
|
||||
|
||||
if (checkEquipHeavyBoots()) {
|
||||
GXColorS10 color = tevStr.TevColor;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
|
||||
@@ -205,6 +205,9 @@ int daAlink_c::setDamagePoint(int i_dmgAmount, BOOL i_checkZoraMag, BOOL i_setDm
|
||||
dComIfGp_setItemLifeCount(-i_dmgAmount, 0);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
dusk::AchievementSystem::get().signal("player_damaged");
|
||||
#endif
|
||||
onResetFlg1(RFLG1_DAMAGE_IMPACT);
|
||||
mSwordUpTimer = 0;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/speedrun.h"
|
||||
|
||||
BOOL daAlink_c::checkEventRun() const {
|
||||
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
|
||||
@@ -2183,7 +2184,7 @@ void daAlink_c::setGetSubBgm(int i_itemNo) {
|
||||
/* dItemNo_NOENTRY_222_e */ SETYPE_NONE,
|
||||
/* dItemNo_NOENTRY_223_e */ SETYPE_NONE,
|
||||
/* dItemNo_POU_SPIRIT_e */ SETYPE_ITEM_GET_POU,
|
||||
/* dItemNo_NOENTRY_225_e */ SETYPE_NONE,
|
||||
/* dItemNo_GHOST_LANTERN_e */ SETYPE_ITEM_GET,
|
||||
/* dItemNo_NOENTRY_226_e */ SETYPE_NONE,
|
||||
/* dItemNo_NOENTRY_227_e */ SETYPE_NONE,
|
||||
/* dItemNo_NOENTRY_228_e */ SETYPE_NONE,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "d/actor/d_a_obj_swhang.h"
|
||||
#include "d/actor/d_a_obj_chandelier.h"
|
||||
#include "JSystem/J3DGraphBase/J3DMaterial.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
enum {
|
||||
HS_MODE_NONE_e,
|
||||
@@ -17,11 +19,11 @@ enum {
|
||||
HS_MODE_RETURN_e = 6,
|
||||
};
|
||||
|
||||
void daAlink_c::hsChainShape_c::draw() {
|
||||
if (dusk::getSettings().game.superClawshot) {
|
||||
return;
|
||||
}
|
||||
#if TARGET_PC
|
||||
static const int HS_CHAIN_MAX_LINKS = 600;
|
||||
#endif
|
||||
|
||||
void daAlink_c::hsChainShape_c::draw() {
|
||||
static const int dummy = 0;
|
||||
|
||||
daAlink_c* alink = (daAlink_c*)getUserArea();
|
||||
@@ -165,7 +167,11 @@ void daAlink_c::hsChainShape_c::draw() {
|
||||
}
|
||||
(void)0;
|
||||
|
||||
while (maxDistanceF > var_f30) {
|
||||
#if TARGET_PC
|
||||
int chainLinks = 0;
|
||||
#endif
|
||||
|
||||
while (maxDistanceF > var_f30 IF_DUSK(&&chainLinks < HS_CHAIN_MAX_LINKS)) {
|
||||
temp_f27 = var_f28 * cM_fsin(sp34 * var_f30);
|
||||
s16 spC = cM_atan2s(temp_f27 - var_f26, 5.0f);
|
||||
sp64.x = sp6C.x + spC;
|
||||
@@ -187,6 +193,10 @@ void daAlink_c::hsChainShape_c::draw() {
|
||||
|
||||
var_f26 = temp_f27;
|
||||
var_f30 += fabsf(cM_scos(spC)) * 5.0f;
|
||||
|
||||
#if TARGET_PC
|
||||
chainLinks++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +212,11 @@ void daAlink_c::hsChainShape_c::draw() {
|
||||
sp98 = subChainTopPos;
|
||||
sp6C.set(maxDistance.atan2sY_XZ(), maxDistance.atan2sX_Z(), 0);
|
||||
|
||||
while (maxDistanceF > var_f30) {
|
||||
#if TARGET_PC
|
||||
int subChainLinks = 0;
|
||||
#endif
|
||||
|
||||
while (maxDistanceF > var_f30 IF_DUSK(&&subChainLinks < HS_CHAIN_MAX_LINKS)) {
|
||||
mDoMtx_stack_c::copy(j3dSys.getViewMtx());
|
||||
mDoMtx_stack_c::transM(sp98);
|
||||
mDoMtx_stack_c::ZXYrotM(sp6C);
|
||||
@@ -215,11 +229,39 @@ void daAlink_c::hsChainShape_c::draw() {
|
||||
sp98 += maxDistance * 5.0f;
|
||||
ANGLE_ADD_2(sp6C.z, 0x3000);
|
||||
var_f30 += 5.0f;
|
||||
#if TARGET_PC
|
||||
subChainLinks++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static void ironBallChainInterpCallback(bool isSimFrame, void* pUserWork) {
|
||||
static_cast<daAlink_c*>(pUserWork)->onIronBallChainInterpCallback();
|
||||
}
|
||||
|
||||
void daAlink_c::onIronBallChainInterpCallback() {
|
||||
if (!mIBChainInterpPrevValid || !mIBChainInterpCurrValid) {
|
||||
return;
|
||||
}
|
||||
if (mIronBallChainPos == NULL || mIronBallChainAngle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||
|
||||
for (int i = 0; i < IRON_BALL_CHAIN_COUNT; i++) {
|
||||
mIronBallChainPos[i] = mIBChainInterpPrevPos[i] + (mIBChainInterpCurrPos[i] - mIBChainInterpPrevPos[i]) * alpha;
|
||||
mIronBallChainAngle[i].x = mIBChainInterpPrevAngle[i].x + (s16)((s16)(mIBChainInterpCurrAngle[i].x - mIBChainInterpPrevAngle[i].x) * alpha);
|
||||
mIronBallChainAngle[i].y = mIBChainInterpPrevAngle[i].y + (s16)((s16)(mIBChainInterpCurrAngle[i].y - mIBChainInterpPrevAngle[i].y) * alpha);
|
||||
mIronBallChainAngle[i].z = mIBChainInterpPrevAngle[i].z + (s16)((s16)(mIBChainInterpCurrAngle[i].z - mIBChainInterpPrevAngle[i].z) * alpha);
|
||||
}
|
||||
mHookshotTopPos = mIBChainInterpPrevHandRoot + (mIBChainInterpCurrHandRoot - mIBChainInterpPrevHandRoot) * alpha;
|
||||
}
|
||||
#endif
|
||||
|
||||
void daAlink_c::hookshotAtHitCallBack(dCcD_GObjInf* i_atObjInf, fopAc_ac_c* i_tgActor,
|
||||
dCcD_GObjInf* i_tgObjInf) {
|
||||
if (i_tgActor != NULL && fopAcM_IsActor(i_tgActor) && !i_tgObjInf->ChkTgHookshotThrough()) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "d/actor/d_a_tag_ss_drink.h"
|
||||
#include "m_Do/m_Do_lib.h"
|
||||
#include "SSystem/SComponent/c_math.h"
|
||||
#include "d/actor/d_a_e_hp.h"
|
||||
|
||||
static fopAc_ac_c* daAlink_searchTagKandelaar(fopAc_ac_c* i_actor, void* i_data) {
|
||||
UNUSED(i_data);
|
||||
@@ -138,6 +139,50 @@ int daAlink_c::kandelaarModelCallBack() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int daAlink_c::ghostLanternModelCallBack() {
|
||||
cXyz sp44;
|
||||
mDoMtx_multVecZero(J3DSys::mCurrentMtx, &sp44);
|
||||
|
||||
ghost_lantern_0x3630 = ghost_lantern_0x3624;
|
||||
ghost_lantern_0x3624 = mGhostLanternFlamePos;
|
||||
|
||||
cXyz sp38 = (mGhostLanternFlamePos - sp44) + ghost_lantern_0x3618;
|
||||
sp38.y -= 3.0f;
|
||||
|
||||
cXyz sp2C;
|
||||
mDoMtx_multVec(J3DSys::mCurrentMtx, &cXyz::BaseZ, &sp2C);
|
||||
|
||||
s16 var_r29 = sp2C.atan2sX_Z();
|
||||
mDoMtx_stack_c::YrotS(-var_r29);
|
||||
mDoMtx_stack_c::multVec(&sp38, &sp38);
|
||||
|
||||
s16 var_r28 = cLib_minMaxLimit<s16>(cM_atan2s(-sp38.z, -sp38.y), -0x1800, 0x1800);
|
||||
s16 var_r27 = cLib_minMaxLimit<s16>(cM_atan2s(sp38.x, JMAFastSqrt(SQUARE(sp38.y) + SQUARE(sp38.z))), -0x1800, 0x1800);
|
||||
|
||||
mDoMtx_stack_c::transS(sp44);
|
||||
if (mProcID != PROC_OPEN_TREASURE && (mProcID != PROC_GET_ITEM || mProcVar4.field_0x3010 == 0)) {
|
||||
mDoMtx_stack_c::ZXYrotM(var_r28, var_r29, var_r27);
|
||||
}
|
||||
|
||||
static Vec const ghostLanternOffset = {0.0f, -20.0f, 0.0f};
|
||||
mDoMtx_stack_c::multVec(&ghostLanternOffset, &mGhostLanternFlamePos);
|
||||
|
||||
ghost_lantern_0x3618 = (mGhostLanternFlamePos - ghost_lantern_0x3624) * 0.9f;
|
||||
|
||||
if (!checkEndResetFlg1(ERFLG1_UNK_8) && mpGhostLanternModel != nullptr) {
|
||||
f32 scale = JMAFastSqrt(SQUARE(J3DSys::mCurrentMtx[0][0]) + SQUARE(J3DSys::mCurrentMtx[1][0]) + SQUARE(J3DSys::mCurrentMtx[2][0]));
|
||||
mDoMtx_stack_c::transS(J3DSys::mCurrentMtx[0][3], J3DSys::mCurrentMtx[1][3], J3DSys::mCurrentMtx[2][3]);
|
||||
mDoMtx_stack_c::ZXYrotM(var_r28, var_r29, var_r27);
|
||||
mDoMtx_stack_c::ZXYrotM(0, 0, -16384);
|
||||
mDoMtx_stack_c::scaleM(scale, scale, scale);
|
||||
|
||||
mpGhostLanternModel->setAnmMtx(1, mDoMtx_stack_c::get());
|
||||
cMtx_copy(mDoMtx_stack_c::get(), J3DSys::mCurrentMtx);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int daAlink_kandelaarModelCallBack(J3DJoint* i_joint, int param_1) {
|
||||
UNUSED(i_joint);
|
||||
|
||||
@@ -150,6 +195,18 @@ static int daAlink_kandelaarModelCallBack(J3DJoint* i_joint, int param_1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int daAlink_ghostLanternModelCallBack(J3DJoint* i_joint, int param_1) {
|
||||
UNUSED(i_joint);
|
||||
|
||||
daAlink_c* player_p = (daAlink_c*)j3dSys.getModel()->getUserArea();
|
||||
|
||||
if (param_1 == 0) {
|
||||
player_p->ghostLanternModelCallBack();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
cXyz* daAlink_c::getKandelaarFlamePos() {
|
||||
if ((!checkNoResetFlg2(daPy_FLG2(FLG2_UNK_20000 | FLG2_UNK_1)) && !checkEndResetFlg1(ERFLG1_UNK_4)) ||
|
||||
checkWolf() || !checkNoResetFlg1(FLG1_UNK_80))
|
||||
@@ -164,6 +221,60 @@ BOOL daAlink_c::checkKandelaarEquipAnime() const {
|
||||
return checkUpperAnime(dRes_ID_ALANM_BCK_WAITHK_e) || checkUpperAnime(dRes_ID_ALANM_BCK_WAITK_e);
|
||||
}
|
||||
|
||||
void* daAlink_c::srchPoe(void* i_actor, void* i_this) {
|
||||
if (i_actor != NULL && i_actor != i_this) {
|
||||
if (fopAcM_IsExecuting(fopAcM_GetID(i_actor)) && fopAcM_GetName(i_actor) == fpcNm_E_HP_e) {
|
||||
return i_actor;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void daAlink_c::preGhostLanternDraw() {
|
||||
J3DMaterial* mat_p = mpGhostLanternGlowModel->getModelData()->getMaterialNodePointer(0);
|
||||
|
||||
J3DGXColorS10 color;
|
||||
color.r = 120;
|
||||
color.g = 150;
|
||||
color.b = 150;
|
||||
color.a = 255;
|
||||
mat_p->setTevColor(1, &color);
|
||||
|
||||
color.r = 0;
|
||||
color.g = 60;
|
||||
color.b = 101;
|
||||
mat_p->setTevColor(2, &color);
|
||||
|
||||
cXyz proj;
|
||||
mDoLib_project(&mGhostLanternFlamePos, &proj);
|
||||
|
||||
camera_process_class* camera_p = dComIfGp_getCamera(0);
|
||||
f32 trimHeight;
|
||||
if (camera_p != NULL) {
|
||||
trimHeight = camera_p->mCamera.TrimHeight();
|
||||
} else {
|
||||
trimHeight = 0.0f;
|
||||
}
|
||||
|
||||
if (proj.x > 0.0f && proj.x < FB_WIDTH && proj.y > trimHeight && proj.y < FB_HEIGHT - trimHeight) {
|
||||
dComIfGd_peekZ(proj.x, proj.y, &field_0x32c8);
|
||||
} else {
|
||||
field_0x32c8 = 0;
|
||||
}
|
||||
|
||||
f32 near_ = dComIfGd_getView()->near_;
|
||||
f32 far_ = dComIfGd_getView()->far_;
|
||||
|
||||
mDoLib_pos2camera(&mGhostLanternFlamePos, &proj);
|
||||
|
||||
proj.z += 30.0f;
|
||||
if (proj.z > -0.01f) {
|
||||
proj.z = -0.01f;
|
||||
}
|
||||
|
||||
field_0x344c = ((near_ + (far_ * near_) / proj.z) / (far_ - near_) + 1.0f) * 1.6777215E7f;
|
||||
}
|
||||
|
||||
void daAlink_c::preKandelaarDraw() {
|
||||
J3DMaterial* mat_p = mpKanteraGlowModel->getModelData()->getMaterialNodePointer(0);
|
||||
|
||||
@@ -208,6 +319,16 @@ void daAlink_c::preKandelaarDraw() {
|
||||
field_0x344c = ((near_ + (far_ * near_) / proj.z) / (far_ - near_) + 1.0f) * 1.6777215E7f;
|
||||
}
|
||||
|
||||
void daAlink_c::setGhostLanternModel() {
|
||||
field_0x2f94 = 0;
|
||||
|
||||
mDoMtx_multVecZero(mpLinkModel->getAnmMtx(mLeftItemJntNo), &mGhostLanternFlamePos);
|
||||
|
||||
ghost_lantern_0x3630 = ghost_lantern_0x3624;
|
||||
ghost_lantern_0x3624 = mGhostLanternFlamePos;
|
||||
ghost_lantern_0x3618 = cXyz::Zero;
|
||||
}
|
||||
|
||||
void daAlink_c::setKandelaarModel() {
|
||||
field_0x2f94 = 0;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/gyro.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
#endif
|
||||
|
||||
bool daAlink_c::checkNoSubjectModeCamera() {
|
||||
@@ -119,8 +120,8 @@ BOOL daAlink_c::setBodyAngleToCamera() {
|
||||
var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c);
|
||||
}
|
||||
|
||||
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle));
|
||||
sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle));
|
||||
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f)));
|
||||
sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f)));
|
||||
|
||||
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
|
||||
sp8 = mBodyAngle.x;
|
||||
@@ -192,7 +193,9 @@ BOOL daAlink_c::subjectCancelTrigger() {
|
||||
BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) {
|
||||
setDoStatus(BUTTON_STATUS_BACK);
|
||||
|
||||
if (checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
|
||||
// Allow pressing the first person binding to also leave first person
|
||||
if (IF_DUSK(dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, 0)) ||
|
||||
checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
|
||||
if (i_isPlaySe) {
|
||||
seStartSystem(Z2SE_SUBJ_VIEW_OUT);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,12 @@ int daAlink_c::loadModelDVD() {
|
||||
mpWlMidnaHairModel = NULL;
|
||||
|
||||
if (!checkNoResetFlg2(FLG2_UNK_280000)) {
|
||||
dComIfG_resDelete(&mPhaseReq, mArcName);
|
||||
if (!dComIfG_resDelete(&mPhaseReq, mArcName)) {
|
||||
#if TARGET_PC
|
||||
// resDelete no-ops if load was in-progress; force-unregister before freeAll
|
||||
dComIfG_deleteObjectResMain(mArcName);
|
||||
#endif
|
||||
}
|
||||
cPhs_Reset(&mPhaseReq);
|
||||
mpArcHeap->freeAll();
|
||||
|
||||
|
||||
@@ -300,6 +300,7 @@ void daAlink_c::changeWolf() {
|
||||
mAtSph.SetR(40.0f);
|
||||
|
||||
mpKanteraModel = NULL;
|
||||
mpGhostLanternModel = NULL;
|
||||
mpLinkHatModel = NULL;
|
||||
mpLinkFaceModel = NULL;
|
||||
mpLinkHandModel = NULL;
|
||||
@@ -406,12 +407,20 @@ void daAlink_c::changeLink(int param_0) {
|
||||
|
||||
modelData = static_cast<J3DModelData*>(dComIfG_getObjectRes(mArcName, "ef_ktGlow.bmd"));
|
||||
mpKanteraGlowModel = initModel(modelData, 0x200);
|
||||
|
||||
mpKanteraGlowBtk = static_cast<J3DAnmTextureSRTKey*>(dComIfG_getObjectRes(mArcName, "ef_ktGlow.btk"));
|
||||
mpKanteraGlowBtk->setFrame(0.0f);
|
||||
mpKanteraGlowBtk->searchUpdateMaterialID(modelData);
|
||||
modelData->entryTexMtxAnimator(mpKanteraGlowBtk);
|
||||
|
||||
mpGhostLanternModel = initModelEnv(static_cast<J3DModelData*>(dComIfG_getObjectRes("E_HP", "hp_ori.bmd")), 0);
|
||||
|
||||
modelData = static_cast<J3DModelData*>(dComIfG_getObjectRes(mArcName, "ef_ktGlow.bmd"));
|
||||
mpGhostLanternGlowModel = initModel(modelData, 0x200);
|
||||
mpGhostLanternGlowBtk = static_cast<J3DAnmTextureSRTKey*>(dComIfG_getObjectRes(mArcName, "ef_ktGlow.btk"));
|
||||
mpGhostLanternGlowBtk->setFrame(0.0f);
|
||||
mpGhostLanternGlowBtk->searchUpdateMaterialID(modelData);
|
||||
modelData->entryTexMtxAnimator(mpGhostLanternGlowBtk);
|
||||
|
||||
mWoodSwordModel = initModel(static_cast<J3DModelData*>(dComIfG_getObjectRes(mArcName, "al_SWB.bmd")), 0);
|
||||
|
||||
mDoExt_setCurrentHeap(current_heap);
|
||||
@@ -504,6 +513,10 @@ void daAlink_c::changeLink(int param_0) {
|
||||
|
||||
mpKanteraModel->setUserArea((uintptr_t)this);
|
||||
mpKanteraModel->getModelData()->getJointNodePointer(1)->setCallBack(daAlink_kandelaarModelCallBack);
|
||||
|
||||
mpGhostLanternModel->setUserArea((uintptr_t)this);
|
||||
mpGhostLanternModel->getModelData()->getJointNodePointer(1)->setCallBack(daAlink_ghostLanternModelCallBack);
|
||||
|
||||
mZ2Link.setLinkState(var_r27);
|
||||
|
||||
#if VERSION != VERSION_SHIELD_DEBUG
|
||||
@@ -8723,6 +8736,12 @@ int daAlink_c::procWolfCargoCarry() {
|
||||
return checkNextActionWolf(0);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (field_0x280c.getActor() == NULL) {
|
||||
return checkNextActionWolf(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
mDoMtx_stack_c::copy(((e_yc_class*)field_0x280c.getActor())->getLegR3Mtx());
|
||||
mDoMtx_stack_c::transM(-9.0f, -7.0f, -30.0f);
|
||||
mDoMtx_stack_c::multVecZero(¤t.pos);
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#include "d/actor/d_a_e_pz.h"
|
||||
#include "d/actor/d_a_horse.h"
|
||||
#include "d/actor/d_a_hozelda.h"
|
||||
#if TARGET_PC
|
||||
#include "dusk/achievements.h"
|
||||
#endif
|
||||
|
||||
int daArrow_c::createHeap() {
|
||||
J3DModelData* model_data;
|
||||
@@ -92,7 +95,12 @@ void daArrow_c::atHitCallBack(dCcD_GObjInf* i_atObjInf, fopAc_ac_c* i_tgActor, d
|
||||
if (dist_to_hitpos < field_0x998) {
|
||||
field_0x998 = dist_to_hitpos;
|
||||
mHitAcID = fopAcM_GetID(i_tgActor);
|
||||
|
||||
#if TARGET_PC
|
||||
if (fopAcM_GetGroup(i_tgActor) == fopAc_ENEMY_e &&
|
||||
current.pos.abs(mStartPos) > 10000.0f) {
|
||||
dusk::AchievementSystem::get().signal("arrow_hit_100m");
|
||||
}
|
||||
#endif
|
||||
if (mArrowType == 1) {
|
||||
field_0x9a8 = *hit_pos_p;
|
||||
} else if (i_tgObjInf->ChkTgShield()) {
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
#if TARGET_PC
|
||||
#include "dusk/achievements.h"
|
||||
#endif
|
||||
|
||||
class daB_GND_HIO_c : public JORReflexible {
|
||||
public:
|
||||
@@ -1289,6 +1292,9 @@ static void b_gnd_g_wait(b_gnd_class* i_this) {
|
||||
if (i_this->mMoveMode < 5 && i_this->mPlayerDistXZ < 600.0f) {
|
||||
i_this->mMoveMode = 5;
|
||||
i_this->field_0xc44[0] = 10;
|
||||
#if TARGET_PC
|
||||
dusk::AchievementSystem::get().signal("ganondorf_fishing_rod");
|
||||
#endif
|
||||
}
|
||||
} else if (i_this->mMoveMode == 5) {
|
||||
i_this->mMoveMode = 6;
|
||||
|
||||