Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6f1fbf074 | |||
| 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 | |||
| 81c7213993 | |||
| be82e606b2 | |||
| 44da1a9f7d | |||
| d0f8ea56f9 | |||
| e472b36cef | |||
| 3934e09c8f | |||
| 3136816ce9 | |||
| 6fd3762ffc | |||
| 97a1190713 | |||
| 73a3bd9ae8 | |||
| fc533dbdc7 | |||
| 6217e071d2 | |||
| 673ca7f686 | |||
| 29a1cff7ea | |||
| 3c5152a67b | |||
| 1c1a849095 | |||
| 89d393e57a | |||
| 2f27687d80 | |||
| 167a50c01d | |||
| 313f03f5e5 | |||
| 6cfdc3d8a3 | |||
| 9c24a0bc4b | |||
| b99ad920c4 | |||
| 912b18eca1 | |||
| 9e651a51db | |||
| c8b6e997a7 | |||
| 928e187524 | |||
| cf12d19860 | |||
| e3a3ac56fb | |||
| d15ed81cd5 | |||
| ff054f6f47 | |||
| 667cf70fa0 | |||
| 73eb401c93 | |||
| b11f3add06 | |||
| 92f888a152 | |||
| 78ed5cc716 | |||
| 44f3828f68 | |||
| 3f560b060c |
@@ -8,7 +8,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# SCCACHE_GHA_ENABLED: "true"
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
RUSTC_WRAPPER: "sccache"
|
RUSTC_WRAPPER: "sccache"
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: GCC x86_64
|
- name: GCC x86_64
|
||||||
runner: [self-hosted, Linux]
|
runner: ubuntu-latest
|
||||||
preset: gcc
|
preset: gcc
|
||||||
artifact_arch: x86_64
|
artifact_arch: x86_64
|
||||||
# - name: GCC aarch64
|
# - name: GCC aarch64
|
||||||
@@ -41,7 +41,6 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: 'false' # disabled for self-hosted
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \
|
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
|
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
|
||||||
|
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
if: 'false' # disabled for self-hosted
|
uses: mozilla-actions/sccache-action@v0.0.10
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
|
||||||
|
|
||||||
- name: Print sccache stats
|
- name: Print sccache stats
|
||||||
run: sccache --show-stats
|
run: sccache --show-stats
|
||||||
@@ -67,17 +65,16 @@ jobs:
|
|||||||
run: ci/build-appimage.sh
|
run: ci/build-appimage.sh
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
name: dusklight-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
||||||
path: |
|
path: |
|
||||||
build/install/Dusk-*.AppImage
|
build/install/Dusklight-*.AppImage
|
||||||
build/install/debug.tar.*
|
build/install/debug.tar.*
|
||||||
|
|
||||||
build-apple:
|
build-apple:
|
||||||
name: Build Apple (${{matrix.name}})
|
name: Build Apple (${{matrix.name}})
|
||||||
runs-on: [self-hosted, macOS]
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -86,14 +83,14 @@ jobs:
|
|||||||
platform: macos
|
platform: macos
|
||||||
preset: x-macos-ci-arm64
|
preset: x-macos-ci-arm64
|
||||||
artifact_name: macos-appleclang-arm64
|
artifact_name: macos-appleclang-arm64
|
||||||
# - name: AppleClang macOS x86_64
|
- name: AppleClang macOS x86_64
|
||||||
# platform: macos
|
platform: macos
|
||||||
# preset: x-macos-ci-x86_64
|
preset: x-macos-ci-x86_64
|
||||||
# artifact_name: macos-appleclang-x86_64
|
artifact_name: macos-appleclang-x86_64
|
||||||
# - name: AppleClang iOS arm64
|
- name: AppleClang iOS arm64
|
||||||
# platform: ios
|
platform: ios
|
||||||
# preset: x-ios-ci
|
preset: x-ios-ci
|
||||||
# artifact_name: ios-appleclang-arm64
|
artifact_name: ios-appleclang-arm64
|
||||||
# - name: AppleClang tvOS arm64
|
# - name: AppleClang tvOS arm64
|
||||||
# platform: tvos
|
# platform: tvos
|
||||||
# preset: x-tvos-ci
|
# preset: x-tvos-ci
|
||||||
@@ -106,7 +103,6 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: 'false'
|
|
||||||
run: brew install cmake ninja
|
run: brew install cmake ninja
|
||||||
|
|
||||||
- name: Install Rust iOS target
|
- name: Install Rust iOS target
|
||||||
@@ -128,7 +124,7 @@ jobs:
|
|||||||
rustup target add x86_64-apple-darwin
|
rustup target add x86_64-apple-darwin
|
||||||
|
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.10
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
run: cmake --preset ${{matrix.preset}}
|
run: cmake --preset ${{matrix.preset}}
|
||||||
@@ -137,14 +133,80 @@ jobs:
|
|||||||
run: cmake --build --preset ${{matrix.preset}}
|
run: cmake --build --preset ${{matrix.preset}}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
|
name: dusklight-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
|
||||||
path: |
|
path: |
|
||||||
build/install/Dusk.app
|
build/install/Dusklight.app
|
||||||
build/install/debug.tar.*
|
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:
|
build-windows:
|
||||||
name: Build Windows (${{matrix.name}})
|
name: Build Windows (${{matrix.name}})
|
||||||
runs-on: ${{matrix.runner}}
|
runs-on: ${{matrix.runner}}
|
||||||
@@ -154,7 +216,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: MSVC x86_64
|
- name: MSVC x86_64
|
||||||
runner: [self-hosted, Windows]
|
runner: windows-latest
|
||||||
preset: msvc
|
preset: msvc
|
||||||
msvc_arch: amd64
|
msvc_arch: amd64
|
||||||
vcpkg_arch: x64
|
vcpkg_arch: x64
|
||||||
@@ -191,7 +253,6 @@ jobs:
|
|||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: 'false' # disabled for self-hosted
|
|
||||||
run: |
|
run: |
|
||||||
choco install ninja
|
choco install ninja
|
||||||
vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static
|
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}}
|
run: cmake --build --preset x-windows-ci-${{matrix.preset}}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
name: dusklight-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
||||||
path: |
|
path: |
|
||||||
build/install/*.exe
|
build/install/*.exe
|
||||||
build/install/*.dll
|
build/install/*.dll
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ compile_commands.json
|
|||||||
# MacOS
|
# MacOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# direnv / nix
|
||||||
|
.direnv/
|
||||||
|
.envrc
|
||||||
|
|
||||||
# ISOs
|
# ISOs
|
||||||
*.iso
|
*.iso
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "(gdb) Launch Dusk MSVC",
|
"name": "(gdb) Launch Dusklight MSVC",
|
||||||
"type": "cppvsdbg",
|
"type": "cppvsdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${command:cmake.launchTargetPath}",
|
"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.generator": "Ninja",
|
||||||
"cmake.configureSettings": {
|
"cmake.configureSettings": {
|
||||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||||
|
|||||||
@@ -5,8 +5,19 @@ if (NOT CMAKE_BUILD_TYPE)
|
|||||||
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
|
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# obtain revision info from git
|
set(DUSK_VERSION_OVERRIDE "" CACHE STRING "Override version string (skips git detection and format validation)")
|
||||||
find_package(Git)
|
|
||||||
|
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)
|
if (GIT_FOUND)
|
||||||
# make sure version information gets re-run when the current Git HEAD changes
|
# 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
|
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")
|
message(STATUS "Unable to find git, commit information will not be available")
|
||||||
endif ()
|
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}")
|
set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
|
||||||
if (CMAKE_MATCH_5)
|
set(DUSK_VERSION_TWEAK "0")
|
||||||
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${CMAKE_MATCH_5}")
|
if (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+)(-dirty)?$")
|
||||||
else ()
|
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
|
||||||
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.0")
|
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 ()
|
endif ()
|
||||||
|
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${DUSK_VERSION_TWEAK}")
|
||||||
else ()
|
else ()
|
||||||
set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION")
|
set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION")
|
||||||
set(DUSK_VERSION_STRING "0.0.0.0")
|
set(DUSK_VERSION_STRING "0.0.0.0")
|
||||||
set(DUSK_SHORT_VERSION_STRING "0.0.0")
|
set(DUSK_SHORT_VERSION_STRING "0.0.0")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Add version information to CI environment variables
|
# Add version information to CI environment variables
|
||||||
if(DEFINED ENV{GITHUB_ENV})
|
if(DEFINED ENV{GITHUB_ENV})
|
||||||
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n")
|
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n")
|
||||||
endif()
|
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}")
|
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)
|
if (APPLE)
|
||||||
enable_language(OBJC)
|
enable_language(OBJC OBJCXX)
|
||||||
endif ()
|
endif ()
|
||||||
if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS)
|
if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS)
|
||||||
# ios.toolchain.cmake hack for SDL
|
# ios.toolchain.cmake hack for SDL
|
||||||
@@ -102,31 +117,34 @@ set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE)
|
|||||||
set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE)
|
set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE)
|
||||||
set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE)
|
set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE)
|
||||||
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
|
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
|
||||||
|
target_compile_definitions(aurora_mtx PRIVATE MTX_USE_PS=1)
|
||||||
|
|
||||||
add_subdirectory(libs/freeverb)
|
add_subdirectory(libs/freeverb)
|
||||||
|
|
||||||
option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)")
|
option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)")
|
||||||
option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.")
|
option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.")
|
||||||
option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON)
|
option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON)
|
||||||
|
option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON)
|
||||||
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_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
|
option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
|
||||||
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
|
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
|
||||||
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
|
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)
|
if (DUSK_MOVIE_SUPPORT)
|
||||||
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
|
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
|
||||||
if (libjpeg-turbo_FOUND)
|
if (libjpeg-turbo_FOUND)
|
||||||
message(STATUS "dusk: Using system libjpeg-turbo")
|
message(STATUS "dusklight: Using system libjpeg-turbo")
|
||||||
else ()
|
else ()
|
||||||
message(STATUS "dusk: Fetching libjpeg-turbo")
|
message(STATUS "dusklight: Fetching libjpeg-turbo")
|
||||||
include(ExternalProject)
|
include(ExternalProject)
|
||||||
set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install)
|
set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
@@ -145,11 +163,14 @@ if (DUSK_MOVIE_SUPPORT)
|
|||||||
list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file})
|
list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file})
|
||||||
endif ()
|
endif ()
|
||||||
set(_jpeg_passthrough_vars
|
set(_jpeg_passthrough_vars
|
||||||
|
ANDROID_ABI
|
||||||
|
ANDROID_PLATFORM
|
||||||
CMAKE_BUILD_TYPE
|
CMAKE_BUILD_TYPE
|
||||||
CMAKE_C_COMPILER
|
CMAKE_C_COMPILER
|
||||||
CMAKE_C_COMPILER_LAUNCHER
|
CMAKE_C_COMPILER_LAUNCHER
|
||||||
CMAKE_MAKE_PROGRAM
|
CMAKE_MAKE_PROGRAM
|
||||||
CMAKE_MSVC_RUNTIME_LIBRARY
|
CMAKE_MSVC_RUNTIME_LIBRARY
|
||||||
|
CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||||
CMAKE_OSX_ARCHITECTURES
|
CMAKE_OSX_ARCHITECTURES
|
||||||
DEPLOYMENT_TARGET
|
DEPLOYMENT_TARGET
|
||||||
ENABLE_ARC
|
ENABLE_ARC
|
||||||
@@ -215,13 +236,13 @@ endif ()
|
|||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
# Declare all dependencies first so CMake can download them in parallel
|
# Declare all dependencies first so CMake can download them in parallel
|
||||||
message(STATUS "dusk: Fetching cxxopts")
|
message(STATUS "dusklight: Fetching cxxopts")
|
||||||
FetchContent_Declare(cxxopts
|
FetchContent_Declare(cxxopts
|
||||||
URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz
|
URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz
|
||||||
URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072
|
URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
)
|
)
|
||||||
message(STATUS "dusk: Fetching nlohmann/json")
|
message(STATUS "dusklight: Fetching nlohmann/json")
|
||||||
FetchContent_Declare(json
|
FetchContent_Declare(json
|
||||||
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
|
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
|
||||||
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
|
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
|
||||||
@@ -230,7 +251,7 @@ FetchContent_Declare(json
|
|||||||
FetchContent_MakeAvailable(cxxopts json)
|
FetchContent_MakeAvailable(cxxopts json)
|
||||||
|
|
||||||
if (DUSK_ENABLE_SENTRY_NATIVE)
|
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_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||||
set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE)
|
set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
@@ -275,17 +296,17 @@ include(files.cmake)
|
|||||||
|
|
||||||
# TODO: version handling for res includes
|
# 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_BUNDLE_IDENTIFIER dev.twilitrealm.dusk)
|
||||||
set(DUSK_COMPANY_NAME "Twilit Realm")
|
set(DUSK_COMPANY_NAME "Twilit Realm")
|
||||||
set(DUSK_FILE_DESCRIPTION "Dusk")
|
set(DUSK_FILE_DESCRIPTION "Dusklight")
|
||||||
set(DUSK_PRODUCT_NAME "Dusk")
|
set(DUSK_PRODUCT_NAME "Dusklight")
|
||||||
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
|
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
|
||||||
|
|
||||||
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
|
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
|
||||||
source_group("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)
|
set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1)
|
||||||
|
|
||||||
set(GAME_INCLUDE_DIRS
|
set(GAME_INCLUDE_DIRS
|
||||||
include
|
include
|
||||||
@@ -313,6 +334,41 @@ if (WIN32)
|
|||||||
list(APPEND GAME_LIBS Ws2_32)
|
list(APPEND GAME_LIBS Ws2_32)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/no_backend.cpp)
|
||||||
|
if (DUSK_ENABLE_UPDATE_CHECKER)
|
||||||
|
list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_UPDATE_CHECKER=1)
|
||||||
|
if (WIN32)
|
||||||
|
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/winhttp.cpp)
|
||||||
|
list(APPEND GAME_LIBS winhttp)
|
||||||
|
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_WINHTTP=1)
|
||||||
|
message(STATUS "dusklight: Enabled update checker (WinHTTP)")
|
||||||
|
elseif (ANDROID)
|
||||||
|
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/android.cpp)
|
||||||
|
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_ANDROID=1)
|
||||||
|
message(STATUS "dusklight: Enabled update checker (Android)")
|
||||||
|
elseif (APPLE)
|
||||||
|
find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED)
|
||||||
|
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/url_session.mm)
|
||||||
|
set_source_files_properties(src/dusk/http/url_session.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||||
|
list(APPEND GAME_LIBS ${FOUNDATION_FRAMEWORK})
|
||||||
|
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_URLSESSION=1)
|
||||||
|
message(STATUS "dusklight: Enabled update checker (NSURLSession)")
|
||||||
|
elseif (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||||
|
find_package(CURL QUIET OPTIONAL_COMPONENTS HTTPS SSL)
|
||||||
|
if (CURL_FOUND AND CURL_HTTPS_FOUND AND CURL_SSL_FOUND)
|
||||||
|
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/curl.cpp)
|
||||||
|
list(APPEND GAME_LIBS CURL::libcurl)
|
||||||
|
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_LIBCURL=1)
|
||||||
|
message(STATUS "dusklight: Enabled update checker (libcurl)")
|
||||||
|
else ()
|
||||||
|
message(STATUS "dusklight: Disabled update checker (libcurl + HTTPS/SSL not found)")
|
||||||
|
endif ()
|
||||||
|
else ()
|
||||||
|
message(STATUS "dusklight: Disabled update checker (unsupported platform)")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE})
|
||||||
|
|
||||||
if (DUSK_MOVIE_SUPPORT)
|
if (DUSK_MOVIE_SUPPORT)
|
||||||
if (TARGET libjpeg-turbo::turbojpeg-static)
|
if (TARGET libjpeg-turbo::turbojpeg-static)
|
||||||
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
|
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
|
||||||
@@ -331,16 +387,6 @@ if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
|
|||||||
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
|
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
|
||||||
endif ()
|
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)
|
if(ANDROID)
|
||||||
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
|
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
|
||||||
endif ()
|
endif ()
|
||||||
@@ -396,31 +442,37 @@ endif ()
|
|||||||
|
|
||||||
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES})
|
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES})
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
add_library(dusk SHARED ${DUSK_FILES})
|
add_library(dusklight SHARED ${DUSK_FILES})
|
||||||
set_target_properties(dusk PROPERTIES OUTPUT_NAME main)
|
set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
|
||||||
else ()
|
else ()
|
||||||
add_executable(dusk ${DUSK_FILES})
|
add_executable(dusklight ${DUSK_FILES})
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
target_compile_definitions(dusk PRIVATE ${GAME_COMPILE_DEFS})
|
target_compile_definitions(dusklight PRIVATE ${GAME_COMPILE_DEFS})
|
||||||
target_include_directories(dusk PRIVATE ${GAME_INCLUDE_DIRS})
|
target_include_directories(dusklight PRIVATE ${GAME_INCLUDE_DIRS})
|
||||||
target_link_libraries(dusk PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
|
target_link_libraries(dusklight PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
|
||||||
target_precompile_headers(dusk PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
|
target_precompile_headers(dusklight PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
|
||||||
if (TARGET crashpad_handler)
|
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 ()
|
endif ()
|
||||||
|
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
# SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static
|
# 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.
|
# 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 ()
|
endif ()
|
||||||
|
|
||||||
if (NOT APPLE)
|
if (NOT APPLE)
|
||||||
add_custom_command(TARGET dusk POST_BUILD
|
add_custom_command(TARGET dusklight POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
"${CMAKE_SOURCE_DIR}/res"
|
"${CMAKE_SOURCE_DIR}/res"
|
||||||
"$<TARGET_FILE_DIR:dusk>/res"
|
"$<TARGET_FILE_DIR:dusklight>/res"
|
||||||
COMMENT "Copying resources"
|
COMMENT "Copying resources"
|
||||||
)
|
)
|
||||||
endif ()
|
endif ()
|
||||||
@@ -428,9 +480,9 @@ endif ()
|
|||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows)
|
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_PNG ${CMAKE_CURRENT_SOURCE_DIR}/res/icon.png)
|
||||||
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusk.ico)
|
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusklight.ico)
|
||||||
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusk.rc)
|
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusklight.rc)
|
||||||
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusk.manifest)
|
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusklight.manifest)
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${DUSK_WINDOWS_ICON_ICO}
|
OUTPUT ${DUSK_WINDOWS_ICON_ICO}
|
||||||
@@ -443,14 +495,14 @@ if (WIN32)
|
|||||||
COMMENT "Generating Windows icon"
|
COMMENT "Generating Windows icon"
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY)
|
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.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.rc.in ${DUSK_WINDOWS_RC} @ONLY)
|
||||||
|
|
||||||
target_sources(dusk PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
|
target_sources(dusklight PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
|
||||||
set_target_properties(dusk PROPERTIES WIN32_EXECUTABLE TRUE)
|
set_target_properties(dusklight PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
target_link_options(dusk PRIVATE /MANIFEST:NO)
|
target_link_options(dusklight PRIVATE /MANIFEST:NO)
|
||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
@@ -466,10 +518,10 @@ if (APPLE)
|
|||||||
file(GLOB_RECURSE DUSK_RESOURCE_FILES
|
file(GLOB_RECURSE DUSK_RESOURCE_FILES
|
||||||
"${DUSK_RESOURCE_DIR}/Assets.car"
|
"${DUSK_RESOURCE_DIR}/Assets.car"
|
||||||
"${DUSK_RESOURCE_DIR}/Base.lproj/*"
|
"${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/*")
|
file(GLOB_RECURSE DUSK_APP_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/*")
|
||||||
target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES})
|
target_sources(dusklight PRIVATE ${DUSK_RESOURCE_FILES})
|
||||||
target_sources(dusk PRIVATE ${DUSK_APP_RESOURCE_FILES})
|
target_sources(dusklight PRIVATE ${DUSK_APP_RESOURCE_FILES})
|
||||||
foreach (FILE ${DUSK_RESOURCE_FILES})
|
foreach (FILE ${DUSK_RESOURCE_FILES})
|
||||||
file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE})
|
file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE})
|
||||||
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
|
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
|
||||||
@@ -481,29 +533,36 @@ if (APPLE)
|
|||||||
set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}")
|
set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}")
|
||||||
endforeach ()
|
endforeach ()
|
||||||
set_target_properties(
|
set_target_properties(
|
||||||
dusk PROPERTIES
|
dusklight PROPERTIES
|
||||||
MACOSX_BUNDLE TRUE
|
MACOSX_BUNDLE TRUE
|
||||||
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
|
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
|
||||||
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
|
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
|
||||||
MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}
|
MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}
|
||||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}
|
MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}
|
||||||
MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}
|
MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}
|
||||||
OUTPUT_NAME Dusk
|
OUTPUT_NAME Dusklight
|
||||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
|
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
|
||||||
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES"
|
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES"
|
||||||
)
|
)
|
||||||
endif ()
|
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)
|
if (IOS)
|
||||||
find_library(UIKIT_FRAMEWORK UIKit REQUIRED)
|
find_library(UIKIT_FRAMEWORK UIKit REQUIRED)
|
||||||
find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers 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)
|
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 ()
|
endif ()
|
||||||
|
|
||||||
include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
|
include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
|
||||||
aurora_copy_runtime_dlls(dusk)
|
aurora_copy_runtime_dlls(dusklight)
|
||||||
|
|
||||||
if (DUSK_SELECTED_OPT)
|
if (DUSK_SELECTED_OPT)
|
||||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||||
@@ -541,13 +600,13 @@ function(get_target_prefix target result_var)
|
|||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
endfunction()
|
endfunction()
|
||||||
list(APPEND BINARY_TARGETS dusk)
|
list(APPEND BINARY_TARGETS dusklight)
|
||||||
set(EXTRA_TARGETS "")
|
set(EXTRA_TARGETS "")
|
||||||
if (TARGET crashpad_handler)
|
if (TARGET crashpad_handler)
|
||||||
list(APPEND EXTRA_TARGETS crashpad_handler)
|
list(APPEND EXTRA_TARGETS crashpad_handler)
|
||||||
endif ()
|
endif ()
|
||||||
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX})
|
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)
|
if (NOT APPLE)
|
||||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX})
|
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX})
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -249,22 +249,11 @@
|
|||||||
"type": "BOOL",
|
"type": "BOOL",
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"CMAKE_DISABLE_FIND_PACKAGE_BZip2": {
|
"CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": {
|
||||||
"type": "BOOL",
|
"type": "BOOL",
|
||||||
"value": true
|
"value": true
|
||||||
},
|
},
|
||||||
"CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": {
|
"CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew"
|
||||||
"type": "BOOL",
|
|
||||||
"value": true
|
|
||||||
},
|
|
||||||
"CMAKE_DISABLE_FIND_PACKAGE_zstd": {
|
|
||||||
"type": "BOOL",
|
|
||||||
"value": true
|
|
||||||
},
|
|
||||||
"CMAKE_DISABLE_FIND_PACKAGE_Freetype": {
|
|
||||||
"type": "BOOL",
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"vendor": {
|
"vendor": {
|
||||||
"microsoft.com/VisualStudioSettings/CMake/1.0": {
|
"microsoft.com/VisualStudioSettings/CMake/1.0": {
|
||||||
@@ -329,7 +318,11 @@
|
|||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
|
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
|
||||||
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_HOME}/ndk/$env{ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake",
|
"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"
|
"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",
|
"name": "x-linux-ci",
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
@@ -391,7 +409,7 @@
|
|||||||
},
|
},
|
||||||
"CMAKE_OSX_DEPLOYMENT_TARGET": "11.0",
|
"CMAKE_OSX_DEPLOYMENT_TARGET": "11.0",
|
||||||
"CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew",
|
"CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew",
|
||||||
"DUSK_MOVIE_SUPPORT": {
|
"BUILD_SHARED_LIBS": {
|
||||||
"type": "BOOL",
|
"type": "BOOL",
|
||||||
"value": false
|
"value": false
|
||||||
}
|
}
|
||||||
@@ -412,6 +430,7 @@
|
|||||||
"x-macos-ci"
|
"x-macos-ci"
|
||||||
],
|
],
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
|
"AURORA_DAWN_PROVIDER": "vendor",
|
||||||
"CMAKE_OSX_ARCHITECTURES": "x86_64",
|
"CMAKE_OSX_ARCHITECTURES": "x86_64",
|
||||||
"Rust_CARGO_TARGET": "x86_64-apple-darwin"
|
"Rust_CARGO_TARGET": "x86_64-apple-darwin"
|
||||||
}
|
}
|
||||||
@@ -423,11 +442,7 @@
|
|||||||
],
|
],
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
|
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
|
||||||
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
|
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
|
||||||
"DUSK_MOVIE_SUPPORT": {
|
|
||||||
"type": "BOOL",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -437,11 +452,7 @@
|
|||||||
],
|
],
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
|
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
|
||||||
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
|
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
|
||||||
"DUSK_MOVIE_SUPPORT": {
|
|
||||||
"type": "BOOL",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -525,7 +536,7 @@
|
|||||||
"description": "iOS release build with debug info",
|
"description": "iOS release build with debug info",
|
||||||
"displayName": "iOS RelWithDebInfo",
|
"displayName": "iOS RelWithDebInfo",
|
||||||
"targets": [
|
"targets": [
|
||||||
"dusk"
|
"dusklight"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -534,7 +545,7 @@
|
|||||||
"description": "tvOS release build with debug info",
|
"description": "tvOS release build with debug info",
|
||||||
"displayName": "tvOS RelWithDebInfo",
|
"displayName": "tvOS RelWithDebInfo",
|
||||||
"targets": [
|
"targets": [
|
||||||
"dusk"
|
"dusklight"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -543,7 +554,7 @@
|
|||||||
"description": "Android arm64-v8a release build with debug info",
|
"description": "Android arm64-v8a release build with debug info",
|
||||||
"displayName": "Android arm64-v8a RelWithDebInfo",
|
"displayName": "Android arm64-v8a RelWithDebInfo",
|
||||||
"targets": [
|
"targets": [
|
||||||
"dusk"
|
"dusklight"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -552,7 +563,16 @@
|
|||||||
"description": "Android x86_64 release build with debug info",
|
"description": "Android x86_64 release build with debug info",
|
||||||
"displayName": "Android x86_64 RelWithDebInfo",
|
"displayName": "Android x86_64 RelWithDebInfo",
|
||||||
"targets": [
|
"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">
|
<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">
|
<p align="center">
|
||||||
<a href="https://twilitrealm.dev">Official Website</a>
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# Overview
|
# 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.
|
It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!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
|
### 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 |
|
| Version | SHA-1 hash |
|
||||||
|--------------| ------------------------------------------ |
|
|--------------| ------------------------------------------ |
|
||||||
| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` |
|
| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` |
|
||||||
| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` |
|
| 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
|
### 3. Setup the game
|
||||||
|
**Windows / macOS / Linux**
|
||||||
- Extract the .zip file
|
- Extract the .zip file
|
||||||
- Launch Dusk
|
- Launch Dusklight
|
||||||
- Press **Select Disc Image** and provide the path to your supported game dump.
|
- 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**!
|
- Press **Play**!
|
||||||
|
|
||||||
# Building
|
# 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
|
# 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/>
|
<br/>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
#!/bin/bash -ex
|
#!/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
|
# Get linuxdeploy
|
||||||
cd "$RUNNER_WORKSPACE"
|
mkdir -p "$build_dir"
|
||||||
curl -fOL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage
|
curl -fL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage" -o "$linuxdeploy"
|
||||||
chmod +x linuxdeploy-$(uname -m).AppImage
|
chmod +x "$linuxdeploy"
|
||||||
|
|
||||||
# Build AppImage
|
# Build AppImage
|
||||||
cd "$GITHUB_WORKSPACE"
|
|
||||||
mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}}
|
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 -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
|
cd build/install
|
||||||
VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \
|
VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \
|
||||||
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage
|
-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"
|
sudo dnf groupinstall "Development Tools" "Development Libraries"
|
||||||
```
|
```
|
||||||
#### Setup
|
#### Setup
|
||||||
Clone and initialize the Dusk repository
|
Clone and initialize the Dusklight repository
|
||||||
```sh
|
```sh
|
||||||
git clone --recursive https://github.com/TwilitRealm/dusk.git
|
git clone --recursive https://github.com/TwilitRealm/dusklight.git
|
||||||
cd dusk
|
cd dusklight
|
||||||
git pull
|
git pull
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
```
|
```
|
||||||
@@ -93,6 +93,6 @@ Alternate presets available:
|
|||||||
#### Running
|
#### Running
|
||||||
Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
|
Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
|
||||||
```sh
|
```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
|
set(DUSK_FILES
|
||||||
|
include/dusk/action_bindings.h
|
||||||
include/dusk/endian_gx.hpp
|
include/dusk/endian_gx.hpp
|
||||||
include/dusk/config.hpp
|
include/dusk/config.hpp
|
||||||
include/dusk/dvd_asset.hpp
|
include/dusk/dvd_asset.hpp
|
||||||
@@ -1420,6 +1421,8 @@ set(DUSK_FILES
|
|||||||
src/dusk/asserts.cpp
|
src/dusk/asserts.cpp
|
||||||
src/dusk/config.cpp
|
src/dusk/config.cpp
|
||||||
src/dusk/crash_reporting.cpp
|
src/dusk/crash_reporting.cpp
|
||||||
|
src/dusk/data.cpp
|
||||||
|
src/dusk/data.hpp
|
||||||
src/dusk/endian.cpp
|
src/dusk/endian.cpp
|
||||||
src/dusk/extras.c
|
src/dusk/extras.c
|
||||||
src/dusk/file_select.cpp
|
src/dusk/file_select.cpp
|
||||||
@@ -1430,29 +1433,31 @@ set(DUSK_FILES
|
|||||||
src/dusk/gyro.cpp
|
src/dusk/gyro.cpp
|
||||||
src/dusk/gamepad_color.cpp
|
src/dusk/gamepad_color.cpp
|
||||||
src/dusk/autosave.cpp
|
src/dusk/autosave.cpp
|
||||||
|
src/dusk/http/http.hpp
|
||||||
src/dusk/io.cpp
|
src/dusk/io.cpp
|
||||||
src/dusk/layout.cpp
|
src/dusk/layout.cpp
|
||||||
src/dusk/logging.cpp
|
src/dusk/logging.cpp
|
||||||
src/dusk/settings.cpp
|
src/dusk/settings.cpp
|
||||||
|
src/dusk/speedrun.cpp
|
||||||
src/dusk/stubs.cpp
|
src/dusk/stubs.cpp
|
||||||
|
src/dusk/update_check.cpp
|
||||||
|
src/dusk/update_check.hpp
|
||||||
#src/dusk/m_Do_ext_dusk.cpp
|
#src/dusk/m_Do_ext_dusk.cpp
|
||||||
src/dusk/imgui/ImGuiConfig.hpp
|
src/dusk/imgui/ImGuiConfig.hpp
|
||||||
src/dusk/imgui/ImGuiConsole.hpp
|
src/dusk/imgui/ImGuiConsole.hpp
|
||||||
src/dusk/imgui/ImGuiConsole.cpp
|
src/dusk/imgui/ImGuiConsole.cpp
|
||||||
src/dusk/imgui/ImGuiEngine.cpp
|
src/dusk/imgui/ImGuiEngine.cpp
|
||||||
src/dusk/imgui/ImGuiEngine.hpp
|
src/dusk/imgui/ImGuiEngine.hpp
|
||||||
src/dusk/imgui/ImGuiMenuGame.cpp
|
|
||||||
src/dusk/imgui/ImGuiMenuGame.hpp
|
|
||||||
src/dusk/imgui/ImGuiBloomWindow.cpp
|
src/dusk/imgui/ImGuiBloomWindow.cpp
|
||||||
src/dusk/imgui/ImGuiBloomWindow.hpp
|
src/dusk/imgui/ImGuiBloomWindow.hpp
|
||||||
src/dusk/imgui/ImGuiMenuTools.cpp
|
src/dusk/imgui/ImGuiMenuTools.cpp
|
||||||
src/dusk/imgui/ImGuiMenuTools.hpp
|
src/dusk/imgui/ImGuiMenuTools.hpp
|
||||||
|
src/dusk/imgui/ImGuiActorSpawner.cpp
|
||||||
src/dusk/imgui/ImGuiProcessOverlay.cpp
|
src/dusk/imgui/ImGuiProcessOverlay.cpp
|
||||||
src/dusk/imgui/ImGuiCameraOverlay.cpp
|
src/dusk/imgui/ImGuiCameraOverlay.cpp
|
||||||
src/dusk/imgui/ImGuiHeapOverlay.cpp
|
src/dusk/imgui/ImGuiHeapOverlay.cpp
|
||||||
src/dusk/imgui/ImGuiControllerOverlay.cpp
|
src/dusk/imgui/ImGuiControllerOverlay.cpp
|
||||||
src/dusk/imgui/ImGuiStubLog.cpp
|
src/dusk/imgui/ImGuiStubLog.cpp
|
||||||
src/dusk/imgui/ImGuiMapLoader.cpp
|
|
||||||
src/dusk/imgui/ImGuiSaveEditor.cpp
|
src/dusk/imgui/ImGuiSaveEditor.cpp
|
||||||
src/dusk/imgui/ImGuiStateShare.hpp
|
src/dusk/imgui/ImGuiStateShare.hpp
|
||||||
src/dusk/imgui/ImGuiStateShare.cpp
|
src/dusk/imgui/ImGuiStateShare.cpp
|
||||||
@@ -1491,6 +1496,8 @@ set(DUSK_FILES
|
|||||||
src/dusk/ui/prelaunch.hpp
|
src/dusk/ui/prelaunch.hpp
|
||||||
src/dusk/ui/preset.cpp
|
src/dusk/ui/preset.cpp
|
||||||
src/dusk/ui/preset.hpp
|
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.cpp
|
||||||
src/dusk/ui/select_button.hpp
|
src/dusk/ui/select_button.hpp
|
||||||
src/dusk/ui/settings.cpp
|
src/dusk/ui/settings.cpp
|
||||||
@@ -1501,6 +1508,8 @@ set(DUSK_FILES
|
|||||||
src/dusk/ui/tab_bar.hpp
|
src/dusk/ui/tab_bar.hpp
|
||||||
src/dusk/ui/ui.cpp
|
src/dusk/ui/ui.cpp
|
||||||
src/dusk/ui/ui.hpp
|
src/dusk/ui/ui.hpp
|
||||||
|
src/dusk/ui/warp.cpp
|
||||||
|
src/dusk/ui/warp.hpp
|
||||||
src/dusk/ui/window.cpp
|
src/dusk/ui/window.cpp
|
||||||
src/dusk/ui/window.hpp
|
src/dusk/ui/window.hpp
|
||||||
src/dusk/achievements.cpp
|
src/dusk/achievements.cpp
|
||||||
@@ -1515,4 +1524,12 @@ set(DUSK_FILES
|
|||||||
src/dusk/discord.hpp
|
src/dusk/discord.hpp
|
||||||
src/dusk/discord_presence.cpp
|
src/dusk/discord_presence.cpp
|
||||||
src/dusk/version.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 }:
|
outputs = { self, nixpkgs }:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
supportedSystems = [
|
||||||
dusk = pkgs.stdenv.mkDerivation {
|
"x86_64-linux"
|
||||||
name = "dusk";
|
"aarch64-linux"
|
||||||
src = ./.;
|
"x86_64-darwin"
|
||||||
nativeBuildInputs = [
|
"aarch64-darwin"
|
||||||
pkgs.cmake
|
];
|
||||||
pkgs.pkg-config
|
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||||
pkgs.wayland
|
pkgsFor = system: import nixpkgs { inherit system; };
|
||||||
];
|
|
||||||
buildInputs = [
|
# Dependencies that are not packaged in nixpkgs (used by the Linux package build):
|
||||||
pkgs.libGL
|
buildSources = pkgs: {
|
||||||
pkgs.libX11
|
aurora-src = pkgs.fetchFromGitHub {
|
||||||
pkgs.libXcursor
|
owner = "encounter";
|
||||||
pkgs.libxi
|
repo = "aurora";
|
||||||
pkgs.libxcb
|
rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
|
||||||
pkgs.libxrandr
|
hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
|
||||||
pkgs.libxscrnsaver
|
};
|
||||||
pkgs.libxtst
|
dawn-src = pkgs.fetchzip {
|
||||||
pkgs.libjpeg8
|
url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
|
||||||
pkgs.libxkbcommon
|
hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
|
||||||
pkgs.libglvnd
|
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 {
|
in {
|
||||||
packages.x86_64-linux.default = dusk;
|
packages.x86_64-linux.default = mkDusklight (pkgsFor "x86_64-linux");
|
||||||
|
|
||||||
|
devShells = forAllSystems (system: {
|
||||||
|
default = mkDevShell (pkgsFor system);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -4552,6 +4552,18 @@ public:
|
|||||||
void handleWolfHowl();
|
void handleWolfHowl();
|
||||||
void handleQuickTransform();
|
void handleQuickTransform();
|
||||||
bool checkGyroAimContext();
|
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
|
#endif
|
||||||
}; // Size: 0x385C
|
}; // Size: 0x385C
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,12 @@ public:
|
|||||||
/* 0x125C */ u32 field_0x125c;
|
/* 0x125C */ u32 field_0x125c;
|
||||||
/* 0x1260 */ u8 field_0x1260[0x126C - 0x1260];
|
/* 0x1260 */ u8 field_0x1260[0x126C - 0x1260];
|
||||||
/* 0x126C */ u8 HIOInit;
|
/* 0x126C */ u8 HIOInit;
|
||||||
|
#if TARGET_PC
|
||||||
|
cXyz mStalkLineInterpPrev[12];
|
||||||
|
cXyz mStalkLineInterpCurr[12];
|
||||||
|
bool mStalkLineInterpPrevValid;
|
||||||
|
bool mStalkLineInterpCurrValid;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
STATIC_ASSERT(sizeof(e_db_class) == 0x1270);
|
STATIC_ASSERT(sizeof(e_db_class) == 0x1270);
|
||||||
|
|||||||
@@ -73,6 +73,12 @@ public:
|
|||||||
/* 0x124C */ f32 field_0x124c;
|
/* 0x124C */ f32 field_0x124c;
|
||||||
/* 0x1250 */ u8 field_0x1250[0x1264 - 0x1250];
|
/* 0x1250 */ u8 field_0x1250[0x1264 - 0x1250];
|
||||||
/* 0x1264 */ u8 HIOInit;
|
/* 0x1264 */ u8 HIOInit;
|
||||||
|
#if TARGET_PC
|
||||||
|
cXyz mStalkLineInterpPrev[12];
|
||||||
|
cXyz mStalkLineInterpCurr[12];
|
||||||
|
bool mStalkLineInterpPrevValid;
|
||||||
|
bool mStalkLineInterpCurrValid;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
STATIC_ASSERT(sizeof(e_hb_class) == 0x1268);
|
STATIC_ASSERT(sizeof(e_hb_class) == 0x1268);
|
||||||
|
|||||||
@@ -81,6 +81,15 @@ public:
|
|||||||
/* 0x306D */ u8 field_0x306D[0x307C - 0x306D];
|
/* 0x306D */ u8 field_0x306D[0x307C - 0x306D];
|
||||||
/* 0x307C */ u32 mBodyEffEmtrID;
|
/* 0x307C */ u32 mBodyEffEmtrID;
|
||||||
/* 0x3080 */ u8 mInitHIO;
|
/* 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);
|
STATIC_ASSERT(sizeof(e_s1_class) == 0x3084);
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ public:
|
|||||||
/* 0x1250 */ f32 field_0x1250;
|
/* 0x1250 */ f32 field_0x1250;
|
||||||
/* 0x1254 */ u8 field_0x1254[0x1268 - 0x1254];
|
/* 0x1254 */ u8 field_0x1254[0x1268 - 0x1254];
|
||||||
/* 0x1268 */ u8 field_0x1268;
|
/* 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);
|
STATIC_ASSERT(sizeof(e_yd_class) == 0x126c);
|
||||||
|
|||||||
@@ -63,6 +63,15 @@ public:
|
|||||||
/* 0x0BB4 */ yg_ke_s mYgKes[13];
|
/* 0x0BB4 */ yg_ke_s mYgKes[13];
|
||||||
/* 0x1880 */ mDoExt_3DlineMat0_c mLineMat;
|
/* 0x1880 */ mDoExt_3DlineMat0_c mLineMat;
|
||||||
/* 0x189C */ u8 mIsFirstSpawn;
|
/* 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);
|
STATIC_ASSERT(sizeof(e_yg_class) == 0x18a0);
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ public:
|
|||||||
/* 0x1260 */ u32 field_0x1260;
|
/* 0x1260 */ u32 field_0x1260;
|
||||||
/* 0x1260 */ u8 field_0x1264[0x1270 - 0x1264];
|
/* 0x1260 */ u8 field_0x1264[0x1270 - 0x1264];
|
||||||
/* 0x1270 */ bool mIsHIOOwner;
|
/* 0x1270 */ bool mIsHIOOwner;
|
||||||
|
#if TARGET_PC
|
||||||
|
cXyz mLineInterpPrev[12];
|
||||||
|
cXyz mLineInterpCurr[12];
|
||||||
|
bool mLineInterpPrevValid;
|
||||||
|
bool mLineInterpCurrValid;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
STATIC_ASSERT(sizeof(e_yh_class) == 0x1274);
|
STATIC_ASSERT(sizeof(e_yh_class) == 0x1274);
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ public:
|
|||||||
csXyz* getAngle() { return field_0x8a4; }
|
csXyz* getAngle() { return field_0x8a4; }
|
||||||
J3DModelData* getModelData() { return mModelData; }
|
J3DModelData* getModelData() { return mModelData; }
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
void onInterpCallback();
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* 0x568 */ request_of_phase_process_class mPhase;
|
/* 0x568 */ request_of_phase_process_class mPhase;
|
||||||
/* 0x570 */ J3DModelData* mModelData;
|
/* 0x570 */ J3DModelData* mModelData;
|
||||||
@@ -42,6 +46,14 @@ private:
|
|||||||
/* 0x694 */ cXyz field_0x694[22];
|
/* 0x694 */ cXyz field_0x694[22];
|
||||||
/* 0x79C */ cXyz field_0x79c[22];
|
/* 0x79C */ cXyz field_0x79c[22];
|
||||||
/* 0x8A4 */ csXyz field_0x8a4[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);
|
STATIC_ASSERT(sizeof(daObjFchain_c) == 0x928);
|
||||||
|
|||||||
@@ -1845,6 +1845,12 @@ inline void dComIfGs_addDeathCount() {
|
|||||||
g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().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() {
|
inline char* dComIfGs_getPlayerName() {
|
||||||
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getPlayerName();
|
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getPlayerName();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,11 @@ public:
|
|||||||
/* 0x108 */ int mSkipTimer;
|
/* 0x108 */ int mSkipTimer;
|
||||||
/* 0x10C */ int mSkipParameter;
|
/* 0x10C */ int mSkipParameter;
|
||||||
/* 0x110 */ BOOL mIsSkipFade;
|
/* 0x110 */ BOOL mIsSkipFade;
|
||||||
|
#if TARGET_PC
|
||||||
|
/* 0x114 */ char mSkipEventName[21];
|
||||||
|
#else
|
||||||
/* 0x114 */ char mSkipEventName[20];
|
/* 0x114 */ char mSkipEventName[20];
|
||||||
|
#endif
|
||||||
/* 0x128 */ u8 mCompulsory;
|
/* 0x128 */ u8 mCompulsory;
|
||||||
/* 0x129 */ bool mRoomInfoSet;
|
/* 0x129 */ bool mRoomInfoSet;
|
||||||
/* 0x12C */ int mRoomNo;
|
/* 0x12C */ int mRoomNo;
|
||||||
|
|||||||
@@ -223,6 +223,9 @@ private:
|
|||||||
/* 0x8F */ u8 field_0x8f;
|
/* 0x8F */ u8 field_0x8f;
|
||||||
/* 0x90 */ u8 field_0x90;
|
/* 0x90 */ u8 field_0x90;
|
||||||
/* 0x91 */ u8 field_0x91;
|
/* 0x91 */ u8 field_0x91;
|
||||||
|
#if TARGET_PC
|
||||||
|
bool previousMirror;
|
||||||
|
#endif
|
||||||
}; // Size: 0x94
|
}; // Size: 0x94
|
||||||
|
|
||||||
class dMap_HIO_list_c : public dMpath_HIO_n::hioList_c {
|
class dMap_HIO_list_c : public dMpath_HIO_n::hioList_c {
|
||||||
|
|||||||
@@ -486,6 +486,9 @@ public:
|
|||||||
mDeathCount++;
|
mDeathCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if TARGET_PC
|
||||||
|
u16 getDeathCount() const { return mDeathCount; }
|
||||||
|
#endif
|
||||||
char* getPlayerName() const { return const_cast<char*>(mPlayerName); }
|
char* getPlayerName() const { return const_cast<char*>(mPlayerName); }
|
||||||
void setPlayerName(const char* i_name) {
|
void setPlayerName(const char* i_name) {
|
||||||
#if AVOID_UB
|
#if AVOID_UB
|
||||||
|
|||||||
@@ -12,9 +12,8 @@
|
|||||||
namespace dusk {
|
namespace dusk {
|
||||||
|
|
||||||
enum class AchievementCategory : uint8_t {
|
enum class AchievementCategory : uint8_t {
|
||||||
Story,
|
|
||||||
Collection,
|
|
||||||
Challenge,
|
Challenge,
|
||||||
|
Collection,
|
||||||
Minigame,
|
Minigame,
|
||||||
Misc,
|
Misc,
|
||||||
Glitched
|
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!
|
* 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.
|
* \brief The internal organization name for the game.
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <dolphin/types.h>
|
#include <dolphin/types.h>
|
||||||
|
|
||||||
namespace dusk::audio {
|
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.
|
* Initialize the audio system and start playing audio.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ void enterAutoSave();
|
|||||||
void autoSaving();
|
void autoSaving();
|
||||||
void waitingForWrite();
|
void waitingForWrite();
|
||||||
void endAutoSave();
|
void endAutoSave();
|
||||||
|
void toggleAutoSave(bool enabled);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef DUSK_CONFIG_HPP
|
#ifndef DUSK_CONFIG_HPP
|
||||||
#define DUSK_CONFIG_HPP
|
#define DUSK_CONFIG_HPP
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include "config_var.hpp"
|
#include "config_var.hpp"
|
||||||
@@ -111,6 +112,18 @@ void Save();
|
|||||||
*/
|
*/
|
||||||
ConfigVarBase* GetConfigVar(std::string_view name);
|
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>
|
template <ConfigValue T>
|
||||||
const ConfigImplBase* GetConfigImpl() {
|
const ConfigImplBase* GetConfigImpl() {
|
||||||
static ConfigImpl<T> config;
|
static ConfigImpl<T> config;
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ enum class ConfigVarLayer : u8 {
|
|||||||
* Will not get saved to config.
|
* Will not get saved to config.
|
||||||
*/
|
*/
|
||||||
Override,
|
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;
|
class ConfigImplBase;
|
||||||
@@ -113,6 +120,12 @@ public:
|
|||||||
* This is necessary to make it legal to access.
|
* This is necessary to make it legal to access.
|
||||||
*/
|
*/
|
||||||
void markRegistered();
|
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>
|
template <typename T>
|
||||||
@@ -162,6 +175,7 @@ class ConfigVar : public ConfigVarBase {
|
|||||||
T defaultValue;
|
T defaultValue;
|
||||||
T value;
|
T value;
|
||||||
T overrideValue;
|
T overrideValue;
|
||||||
|
ConfigVarLayer priorLayer = ConfigVarLayer::Default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -189,6 +203,7 @@ public:
|
|||||||
case ConfigVarLayer::Value:
|
case ConfigVarLayer::Value:
|
||||||
return value;
|
return value;
|
||||||
case ConfigVarLayer::Override:
|
case ConfigVarLayer::Override:
|
||||||
|
case ConfigVarLayer::Speedrun:
|
||||||
return overrideValue;
|
return overrideValue;
|
||||||
default:
|
default:
|
||||||
abort();
|
abort();
|
||||||
@@ -239,8 +254,54 @@ public:
|
|||||||
overrideValue = std::move(newValue);
|
overrideValue = std::move(newValue);
|
||||||
layer = ConfigVarLayer::Override;
|
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
|
#endif // DUSK_CONFIG_VAR_HPP
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace dusk {
|
namespace dusk::crash_reporting {
|
||||||
|
|
||||||
void InitializeCrashReporting();
|
enum class Consent {
|
||||||
void ShutdownCrashReporting();
|
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;
|
extern bool s_sensor_keep_alive;
|
||||||
bool get_sensor_keep_alive();
|
bool get_sensor_keep_alive();
|
||||||
void set_sensor_keep_alive(bool value);
|
void set_sensor_keep_alive(bool value);
|
||||||
|
bool rollgoal_gyro_enabled();
|
||||||
} // namespace dusk::gyro
|
} // namespace dusk::gyro
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ constexpr const char* SHOW_DEBUG_OVERLAY = "F3";
|
|||||||
constexpr const char* SHOW_HEAP_VIEWER = "F4";
|
constexpr const char* SHOW_HEAP_VIEWER = "F4";
|
||||||
constexpr const char* SHOW_PLAYER_INFO = "F5";
|
constexpr const char* SHOW_PLAYER_INFO = "F5";
|
||||||
constexpr const char* SHOW_SAVE_EDITOR = "F6";
|
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_STATE_SHARE = "F8";
|
||||||
constexpr const char* SHOW_DEBUG_CAMERA = "F9";
|
constexpr const char* SHOW_DEBUG_CAMERA = "F9";
|
||||||
constexpr const char* SHOW_AUDIO_DEBUG = "F10";
|
constexpr const char* SHOW_AUDIO_DEBUG = "F10";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef DUSK_IO_HPP
|
#ifndef DUSK_IO_HPP
|
||||||
#define DUSK_IO_HPP
|
#define DUSK_IO_HPP
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// I can't believe it's 2026 and neither SDL (no error codes) nor
|
// 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.
|
* Methods on this class throw appropriate C++ exceptions when an error occurs.
|
||||||
*/
|
*/
|
||||||
class FileStream {
|
class FileStream {
|
||||||
void* file;
|
FILE* file;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileStream() noexcept;
|
FileStream() noexcept;
|
||||||
@@ -23,7 +24,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* \brief Take ownership of a FILE* handle.
|
* \brief Take ownership of a FILE* handle.
|
||||||
*/
|
*/
|
||||||
explicit FileStream(void* file);
|
explicit FileStream(FILE* file);
|
||||||
FileStream(const FileStream& other) = delete;
|
FileStream(const FileStream& other) = delete;
|
||||||
FileStream(FileStream&& other) noexcept;
|
FileStream(FileStream&& other) noexcept;
|
||||||
|
|
||||||
@@ -34,6 +35,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
static FileStream OpenRead(const char* utf8Path);
|
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.
|
* \brief Create a file for writing.
|
||||||
*
|
*
|
||||||
@@ -41,16 +47,33 @@ public:
|
|||||||
*/
|
*/
|
||||||
static FileStream Create(const char* utf8Path);
|
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.
|
* \brief Read the byte contents of a file directly into a vector.
|
||||||
*/
|
*/
|
||||||
static std::vector<u8> ReadAllBytes(const char* utf8Path);
|
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.
|
* \brief Read the byte contents of a file directly into a vector.
|
||||||
*/
|
*/
|
||||||
static void WriteAllText(const char* utf8Path, std::string_view text);
|
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.
|
* \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.
|
* Get direct access to the underlying FILE* handle.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] void* GetFileHandle() const noexcept {
|
[[nodiscard]] void* GetFileHandle() const noexcept { return file; }
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write data to the file.
|
* Write data to the file.
|
||||||
*/
|
*/
|
||||||
void Write(const char* data, size_t dataLen);
|
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
|
#endif // DUSK_IO_HPP
|
||||||
|
|||||||
@@ -1,28 +1,26 @@
|
|||||||
#ifndef DUSK_MAIN_H
|
#ifndef DUSK_MAIN_H
|
||||||
#define DUSK_MAIN_H
|
#define DUSK_MAIN_H
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
|
||||||
#include <TargetConditionals.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
namespace dusk {
|
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)
|
(defined(TARGET_OS_TV) && TARGET_OS_TV)
|
||||||
inline constexpr bool SupportsProcessRestart = false;
|
inline constexpr bool SupportsProcessRestart = false;
|
||||||
#else
|
#else
|
||||||
inline constexpr bool SupportsProcessRestart = true;
|
inline constexpr bool SupportsProcessRestart = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void RequestRestart() noexcept;
|
void RequestRestart() noexcept;
|
||||||
}
|
|
||||||
|
} // namespace dusk
|
||||||
|
|
||||||
#endif // DUSK_MAIN_H
|
#endif // DUSK_MAIN_H
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef DUSK_CONFIG_H
|
#ifndef DUSK_CONFIG_H
|
||||||
#define DUSK_CONFIG_H
|
#define DUSK_CONFIG_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include "dusk/config_var.hpp"
|
#include "dusk/config_var.hpp"
|
||||||
|
|
||||||
namespace dusk {
|
namespace dusk {
|
||||||
@@ -27,6 +29,11 @@ enum class DiscVerificationState : u8 {
|
|||||||
HashMismatch,
|
HashMismatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class GyroMode : u8 {
|
||||||
|
Sensor = 0,
|
||||||
|
Mouse = 1,
|
||||||
|
};
|
||||||
|
|
||||||
namespace config {
|
namespace config {
|
||||||
template <>
|
template <>
|
||||||
struct ConfigEnumRange<BloomMode> {
|
struct ConfigEnumRange<BloomMode> {
|
||||||
@@ -45,6 +52,12 @@ struct ConfigEnumRange<DiscVerificationState> {
|
|||||||
static constexpr auto min = DiscVerificationState::Unknown;
|
static constexpr auto min = DiscVerificationState::Unknown;
|
||||||
static constexpr auto max = DiscVerificationState::HashMismatch;
|
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
|
// Persistent user settings
|
||||||
@@ -57,6 +70,8 @@ struct UserSettings {
|
|||||||
ConfigVar<bool> enableFullscreen;
|
ConfigVar<bool> enableFullscreen;
|
||||||
ConfigVar<bool> enableVsync;
|
ConfigVar<bool> enableVsync;
|
||||||
ConfigVar<bool> lockAspectRatio;
|
ConfigVar<bool> lockAspectRatio;
|
||||||
|
ConfigVar<bool> enableFpsOverlay;
|
||||||
|
ConfigVar<int> fpsOverlayCorner;
|
||||||
} video;
|
} video;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -97,11 +112,12 @@ struct UserSettings {
|
|||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
ConfigVar<bool> enableMirrorMode;
|
ConfigVar<bool> enableMirrorMode;
|
||||||
ConfigVar<bool> disableMainHUD;
|
ConfigVar<bool> minimalHUD;
|
||||||
ConfigVar<bool> pauseOnFocusLost;
|
ConfigVar<bool> pauseOnFocusLost;
|
||||||
ConfigVar<bool> enableLinkDollRotation;
|
ConfigVar<bool> enableLinkDollRotation;
|
||||||
ConfigVar<bool> enableAchievementNotifications;
|
ConfigVar<bool> enableAchievementToasts;
|
||||||
|
ConfigVar<bool> enableControllerToasts;
|
||||||
|
ConfigVar<bool> enableDiscordPresence;
|
||||||
|
|
||||||
// Graphics
|
// Graphics
|
||||||
ConfigVar<BloomMode> bloomMode;
|
ConfigVar<BloomMode> bloomMode;
|
||||||
@@ -112,12 +128,14 @@ struct UserSettings {
|
|||||||
ConfigVar<int> shadowResolutionMultiplier;
|
ConfigVar<int> shadowResolutionMultiplier;
|
||||||
ConfigVar<bool> enableDepthOfField;
|
ConfigVar<bool> enableDepthOfField;
|
||||||
ConfigVar<bool> enableMapBackground;
|
ConfigVar<bool> enableMapBackground;
|
||||||
|
ConfigVar<bool> disableCutscenePillarboxing;
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
ConfigVar<bool> noLowHpSound;
|
ConfigVar<bool> noLowHpSound;
|
||||||
ConfigVar<bool> midnasLamentNonStop;
|
ConfigVar<bool> midnasLamentNonStop;
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
|
ConfigVar<GyroMode> gyroMode;
|
||||||
ConfigVar<bool> enableGyroAim;
|
ConfigVar<bool> enableGyroAim;
|
||||||
ConfigVar<bool> enableGyroRollgoal;
|
ConfigVar<bool> enableGyroRollgoal;
|
||||||
ConfigVar<float> gyroSensitivityX;
|
ConfigVar<float> gyroSensitivityX;
|
||||||
@@ -130,12 +148,17 @@ struct UserSettings {
|
|||||||
ConfigVar<bool> freeCamera;
|
ConfigVar<bool> freeCamera;
|
||||||
ConfigVar<bool> invertCameraXAxis;
|
ConfigVar<bool> invertCameraXAxis;
|
||||||
ConfigVar<bool> invertCameraYAxis;
|
ConfigVar<bool> invertCameraYAxis;
|
||||||
|
ConfigVar<bool> invertFirstPersonXAxis;
|
||||||
|
ConfigVar<bool> invertFirstPersonYAxis;
|
||||||
ConfigVar<float> freeCameraSensitivity;
|
ConfigVar<float> freeCameraSensitivity;
|
||||||
ConfigVar<bool> debugFlyCam;
|
ConfigVar<bool> debugFlyCam;
|
||||||
|
ConfigVar<bool> debugFlyCamLockEvents;
|
||||||
|
ConfigVar<bool> allowBackgroundInput;
|
||||||
|
|
||||||
// Cheats
|
// Cheats
|
||||||
ConfigVar<bool> infiniteHearts;
|
ConfigVar<bool> infiniteHearts;
|
||||||
ConfigVar<bool> infiniteArrows;
|
ConfigVar<bool> infiniteArrows;
|
||||||
|
ConfigVar<bool> infiniteSeeds;
|
||||||
ConfigVar<bool> infiniteBombs;
|
ConfigVar<bool> infiniteBombs;
|
||||||
ConfigVar<bool> infiniteOil;
|
ConfigVar<bool> infiniteOil;
|
||||||
ConfigVar<bool> infiniteOxygen;
|
ConfigVar<bool> infiniteOxygen;
|
||||||
@@ -146,18 +169,27 @@ struct UserSettings {
|
|||||||
ConfigVar<bool> alwaysGreatspin;
|
ConfigVar<bool> alwaysGreatspin;
|
||||||
ConfigVar<bool> enableFastIronBoots;
|
ConfigVar<bool> enableFastIronBoots;
|
||||||
ConfigVar<bool> canTransformAnywhere;
|
ConfigVar<bool> canTransformAnywhere;
|
||||||
|
ConfigVar<bool> fastRoll;
|
||||||
ConfigVar<bool> fastSpinner;
|
ConfigVar<bool> fastSpinner;
|
||||||
ConfigVar<bool> freeMagicArmor;
|
ConfigVar<bool> magicArmorNoDrain;
|
||||||
|
ConfigVar<bool> magicArmorNoDamageLoss;
|
||||||
|
ConfigVar<bool> magicArmorNoHeavy;
|
||||||
|
ConfigVar<bool> invincibleEnemies;
|
||||||
|
|
||||||
// Technical
|
// Technical
|
||||||
ConfigVar<bool> restoreWiiGlitches;
|
ConfigVar<bool> restoreWiiGlitches;
|
||||||
|
|
||||||
// Controls
|
// Controls
|
||||||
ConfigVar<bool> enableTurboKeybind;
|
ConfigVar<bool> enableTurboKeybind;
|
||||||
|
ConfigVar<bool> enableResetKeybind;
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
ConfigVar<bool> speedrunMode;
|
ConfigVar<bool> speedrunMode;
|
||||||
ConfigVar<bool> liveSplitEnabled;
|
ConfigVar<bool> liveSplitEnabled;
|
||||||
|
ConfigVar<bool> showSpeedrunRTATimer;
|
||||||
|
ConfigVar<bool> recordingMode;
|
||||||
|
ConfigVar<bool> showInputViewer;
|
||||||
|
ConfigVar<bool> showInputViewerGyro;
|
||||||
} game;
|
} game;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -167,9 +199,18 @@ struct UserSettings {
|
|||||||
ConfigVar<bool> skipPreLaunchUI;
|
ConfigVar<bool> skipPreLaunchUI;
|
||||||
ConfigVar<bool> showPipelineCompilation;
|
ConfigVar<bool> showPipelineCompilation;
|
||||||
ConfigVar<bool> wasPresetChosen;
|
ConfigVar<bool> wasPresetChosen;
|
||||||
ConfigVar<bool> enableCrashReporting;
|
ConfigVar<bool> checkForUpdates;
|
||||||
ConfigVar<int> cardFileType;
|
ConfigVar<int> cardFileType;
|
||||||
|
ConfigVar<bool> enableAdvancedSettings;
|
||||||
} backend;
|
} 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();
|
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/JUtility/JUTFader.h"
|
||||||
#include "JSystem/J2DGraph/J2DOrthoGraph.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)
|
JUTFader::JUTFader(int x, int y, int width, int height, JUtility::TColor pColor)
|
||||||
: mColor(pColor), mBox(x, y, x + width, y + height) {
|
: mColor(pColor), mBox(x, y, x + width, y + height) {
|
||||||
mStatus = None;
|
mStatus = None;
|
||||||
@@ -63,14 +67,24 @@ void JUTFader::advance() {
|
|||||||
|
|
||||||
void JUTFader::control() {
|
void JUTFader::control() {
|
||||||
advance();
|
advance();
|
||||||
#ifndef TARGET_PC
|
|
||||||
// FRAME INTERP NOTE: Draw is called by JFWDisplay when interpolation is active
|
|
||||||
draw();
|
draw();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void JUTFader::draw() {
|
void JUTFader::draw() {
|
||||||
if (mColor.a != 0) {
|
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;
|
J2DOrthoGraph orthograph;
|
||||||
orthograph.setColor(mColor);
|
orthograph.setColor(mColor);
|
||||||
orthograph.fillBox(mBox);
|
orthograph.fillBox(mBox);
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "os_report.h"
|
#include "os_report.h"
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
#include "dusk/action_bindings.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
u32 JUTGamePad::CRumble::sChannelMask[4] = {
|
u32 JUTGamePad::CRumble::sChannelMask[4] = {
|
||||||
PAD_CHAN0_BIT,
|
PAD_CHAN0_BIT,
|
||||||
PAD_CHAN1_BIT,
|
PAD_CHAN1_BIT,
|
||||||
@@ -85,6 +89,9 @@ u32 JUTGamePad::sRumbleSupported;
|
|||||||
|
|
||||||
u32 JUTGamePad::read() {
|
u32 JUTGamePad::read() {
|
||||||
sRumbleSupported = PADRead(mPadStatus);
|
sRumbleSupported = PADRead(mPadStatus);
|
||||||
|
#if TARGET_PC
|
||||||
|
dusk::updateActionBindings();
|
||||||
|
#endif
|
||||||
|
|
||||||
switch (sClampMode) {
|
switch (sClampMode) {
|
||||||
case EClampStick:
|
case EClampStick:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Android Shell
|
# 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
|
## Prerequisites
|
||||||
|
|
||||||
@@ -66,12 +66,11 @@ Output APK:
|
|||||||
You can pass command-line args through the activity intent:
|
You can pass command-line args through the activity intent:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb shell am start -n com.twilitrealm.dusk/.DuskActivity \
|
adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \
|
||||||
--es dusk_args "'/sdcard/Download/The Legend of Zelda: Twilight Princess (USA).iso'"
|
--es dusk_args "--backend vulkan"
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported extras:
|
Supported extras:
|
||||||
|
|
||||||
- `dusk_args`: single shell-like argument string
|
- `dusk_args`: single shell-like argument string
|
||||||
- `dusk_argv`: string-array argv
|
- `dusk_argv`: string-array argv
|
||||||
- `dusk_disc`: compatibility shortcut (single ISO path)
|
|
||||||
|
|||||||
@@ -2,12 +2,22 @@ plugins {
|
|||||||
id 'com.android.application'
|
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 {
|
android {
|
||||||
namespace 'com.twilitrealm.dusk'
|
namespace 'dev.twilitrealm.dusk'
|
||||||
compileSdk 36
|
compileSdk 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'com.twilitrealm.dusk'
|
applicationId 'dev.twilitrealm.dusk'
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 1
|
versionCode 1
|
||||||
@@ -27,7 +37,7 @@ android {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
jniLibs.srcDirs = ['src/main/jniLibs']
|
jniLibs.srcDirs = ['src/main/jniLibs']
|
||||||
assets.srcDirs = ['../../assets']
|
assets.srcDirs = [duskGeneratedAssetsDir]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,3 +58,10 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
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 SDL activity and related JNI bridge methods.
|
||||||
-keep class org.libsdl.app.** { *; }
|
-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-feature android:name="android.hardware.type.pc" android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -23,8 +24,19 @@
|
|||||||
<meta-data android:name="android.game_mode_config"
|
<meta-data android:name="android.game_mode_config"
|
||||||
android:resource="@xml/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
|
<activity
|
||||||
android:name="com.twilitrealm.dusk.DuskActivity"
|
android:name="dev.twilitrealm.dusk.DuskActivity"
|
||||||
android:alwaysRetainTaskState="true"
|
android:alwaysRetainTaskState="true"
|
||||||
android:configChanges="layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
android:configChanges="layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -1,18 +1,39 @@
|
|||||||
package com.twilitrealm.dusk;
|
package dev.twilitrealm.dusk;
|
||||||
|
|
||||||
import android.app.ActionBar;
|
import android.app.ActionBar;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.ClipData;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
|
import android.provider.OpenableColumns;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
import android.view.WindowInsetsController;
|
||||||
|
|
||||||
import org.libsdl.app.SDLActivity;
|
import org.libsdl.app.SDLActivity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DuskActivity extends SDLActivity {
|
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) {
|
private static String[] splitArgs(String raw) {
|
||||||
List<String> out = new ArrayList<>();
|
List<String> out = new ArrayList<>();
|
||||||
StringBuilder current = new StringBuilder();
|
StringBuilder current = new StringBuilder();
|
||||||
@@ -61,18 +82,46 @@ public class DuskActivity extends SDLActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
getWindow().getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
|
window.setDecorFitsSystemWindows(false);
|
||||||
}else {
|
WindowInsetsController ctrl = window.getDecorView().getWindowInsetsController();
|
||||||
View decorView = getWindow().getDecorView();
|
if (ctrl != null) {
|
||||||
// Hide the status bar.
|
ctrl.setSystemBarsBehavior(
|
||||||
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
|
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);
|
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 actionBar = getActionBar();
|
||||||
actionBar.hide();
|
if (actionBar != null) {
|
||||||
|
actionBar.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,12 +149,241 @@ public class DuskActivity extends SDLActivity {
|
|||||||
return splitArgs(trimmed);
|
return splitArgs(trimmed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String discPath = intent.getStringExtra("dusk_disc");
|
|
||||||
if (discPath != null && !discPath.isEmpty()) {
|
|
||||||
return new String[] { discPath };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return new String[0];
|
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
|
0x24c6, // PowerA
|
||||||
0x2c22, // Qanba
|
0x2c22, // Qanba
|
||||||
0x2dc8, // 8BitDo
|
0x2dc8, // 8BitDo
|
||||||
|
0x37d7, // Flydigi
|
||||||
0x9886, // ASTRO Gaming
|
0x9886, // ASTRO Gaming
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
|||||||
private static final String TAG = "SDL";
|
private static final String TAG = "SDL";
|
||||||
private static final int SDL_MAJOR_VERSION = 3;
|
private static final int SDL_MAJOR_VERSION = 3;
|
||||||
private static final int SDL_MINOR_VERSION = 4;
|
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
|
// Display InputType.SOURCE/CLASS of events and devices
|
||||||
//
|
//
|
||||||
@@ -2032,7 +2032,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
|||||||
try {
|
try {
|
||||||
ParcelFileDescriptor pfd = mSingleton.getContentResolver().openFileDescriptor(Uri.parse(uri), mode);
|
ParcelFileDescriptor pfd = mSingleton.getContentResolver().openFileDescriptor(Uri.parse(uri), mode);
|
||||||
return pfd != null ? pfd.detachFd() : -1;
|
return pfd != null ? pfd.detachFd() : -1;
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException | SecurityException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -2227,4 +2227,3 @@ class SDLClipboardHandler implements
|
|||||||
SDLActivity.onNativeClipboardChanged();
|
SDLActivity.onNativeClipboardChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,17 +65,15 @@ class SDLInputConnection extends BaseInputConnection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
|
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
|
||||||
// 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
|
||||||
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
|
if (beforeLength > 0 && afterLength == 0) {
|
||||||
if (beforeLength > 0 && afterLength == 0) {
|
// backspace(s)
|
||||||
// backspace(s)
|
while (beforeLength-- > 0) {
|
||||||
while (beforeLength-- > 0) {
|
nativeGenerateScancodeForUnichar('\b');
|
||||||
nativeGenerateScancodeForUnichar('\b');
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!super.deleteSurroundingText(beforeLength, afterLength)) {
|
if (!super.deleteSurroundingText(beforeLength, afterLength)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
|
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
|
||||||
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
|
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
|
||||||
|
|
||||||
|
private static native void auroraNativeSetSurfaceReady(boolean ready);
|
||||||
|
|
||||||
// Sensors
|
// Sensors
|
||||||
protected SensorManager mSensorManager;
|
protected SensorManager mSensorManager;
|
||||||
protected Display mDisplay;
|
protected Display mDisplay;
|
||||||
@@ -96,6 +98,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
@Override
|
@Override
|
||||||
public void surfaceCreated(SurfaceHolder holder) {
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
Log.v("SDL", "surfaceCreated()");
|
Log.v("SDL", "surfaceCreated()");
|
||||||
|
auroraNativeSetSurfaceReady(false);
|
||||||
SDLActivity.onNativeSurfaceCreated();
|
SDLActivity.onNativeSurfaceCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +106,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
@Override
|
@Override
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
Log.v("SDL", "surfaceDestroyed()");
|
Log.v("SDL", "surfaceDestroyed()");
|
||||||
|
auroraNativeSetSurfaceReady(false);
|
||||||
|
|
||||||
// Transition to pause, if needed
|
// Transition to pause, if needed
|
||||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||||
@@ -192,6 +196,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
/* Surface is ready */
|
/* Surface is ready */
|
||||||
mIsSurfaceReady = true;
|
mIsSurfaceReady = true;
|
||||||
|
auroraNativeSetSurfaceReady(true);
|
||||||
|
|
||||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||||
SDLActivity.handleNativeState();
|
SDLActivity.handleNativeState();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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>
|
</resources>
|
||||||
|
|||||||
@@ -14,9 +14,22 @@ if [[ -z "$ANDROID_NDK_VER" ]] && [[ -d "$ANDROID_HOME_DIR/ndk" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$ANDROID_NDK_VER" ]]; then
|
if [[ -n "$ANDROID_NDK_VER" ]]; then
|
||||||
TOOLCHAIN_BIN="$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
case "$(uname -s)" in
|
||||||
if [[ -x "$TOOLCHAIN_BIN/llvm-strip" ]]; then
|
Darwin) HOST_TAG="darwin-x86_64" ;;
|
||||||
STRIP_TOOL="$TOOLCHAIN_BIN/llvm-strip"
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -25,29 +38,35 @@ copy_lib() {
|
|||||||
local src="$2"
|
local src="$2"
|
||||||
local dst_dir="$APP_DIR/$abi"
|
local dst_dir="$APP_DIR/$abi"
|
||||||
local dst="$dst_dir/libmain.so"
|
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"
|
mkdir -p "$dst_dir"
|
||||||
cp -f "$src" "$dst"
|
cp -f "$src" "$tmp"
|
||||||
if [[ "$ANDROID_STAGE_STRIP" != "0" ]] && [[ -n "$STRIP_TOOL" ]]; then
|
if [[ "$ANDROID_STAGE_STRIP" != "0" ]] && [[ -n "$STRIP_TOOL" ]]; then
|
||||||
"$STRIP_TOOL" --strip-debug "$dst"
|
"$STRIP_TOOL" --strip-unneeded "$tmp"
|
||||||
echo "Staged and stripped $src -> $dst"
|
mv -f "$tmp" "$dst"
|
||||||
|
echo "Stripped and staged $src -> $dst"
|
||||||
else
|
else
|
||||||
|
mv -f "$tmp" "$dst"
|
||||||
echo "Staged $src -> $dst (strip disabled or strip tool unavailable)"
|
echo "Staged $src -> $dst (strip disabled or strip tool unavailable)"
|
||||||
fi
|
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.
|
# Drop any previously staged ABI directories to avoid stale APK contents.
|
||||||
rm -rf "$APP_DIR/x86" "$APP_DIR/arm64-v8a" "$APP_DIR/x86_64"
|
rm -rf "$APP_DIR/x86" "$APP_DIR/arm64-v8a" "$APP_DIR/x86_64"
|
||||||
|
|
||||||
for abi in $ANDROID_STAGE_ABIS; do
|
for abi in $ANDROID_STAGE_ABIS; do
|
||||||
src="${ABI_TO_LIB[$abi]:-}"
|
case "$abi" in
|
||||||
if [[ -z "$src" ]]; then
|
arm64-v8a) src="$ROOT_DIR/build/android-arm64/libmain.so" ;;
|
||||||
echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
|
x86_64) src="$ROOT_DIR/build/android-x86_64/libmain.so" ;;
|
||||||
exit 1
|
*)
|
||||||
fi
|
echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
copy_lib "$abi" "$src"
|
copy_lib "$abi" "$src"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -14,5 +14,5 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "dusk-android"
|
rootProject.name = "dusklight-android"
|
||||||
include ':app'
|
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/>
|
<true/>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIFileSharingEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSSupportsGameMode</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>Dusk</string>
|
<string>Dusklight</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
<string>Dusk</string>
|
<string>Dusk</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -28,5 +28,7 @@
|
|||||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>LSSupportsGameMode</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -45,5 +45,7 @@
|
|||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIUserInterfaceStyle</key>
|
<key>UIUserInterfaceStyle</key>
|
||||||
<string>Automatic</string>
|
<string>Automatic</string>
|
||||||
|
<key>LSSupportsGameMode</key>
|
||||||
|
<true />
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ BEGIN
|
|||||||
VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0"
|
VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0"
|
||||||
VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0"
|
VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0"
|
||||||
VALUE "FileVersion", "@DUSK_VERSION_STRING@\0"
|
VALUE "FileVersion", "@DUSK_VERSION_STRING@\0"
|
||||||
VALUE "InternalName", "dusk\0"
|
VALUE "InternalName", "dusklight\0"
|
||||||
VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0"
|
VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0"
|
||||||
VALUE "OriginalFilename", "dusk.exe\0"
|
VALUE "OriginalFilename", "dusklight.exe\0"
|
||||||
VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0"
|
VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0"
|
||||||
VALUE "ProductVersion", "@DUSK_VERSION_STRING@\0"
|
VALUE "ProductVersion", "@DUSK_VERSION_STRING@\0"
|
||||||
END
|
END
|
||||||
|
Before Width: | Height: | Size: 457 KiB |
|
Before Width: | Height: | Size: 340 KiB After Width: | Height: | Size: 642 KiB |
@@ -20,18 +20,22 @@ body {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fps,
|
||||||
toast {
|
toast {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
border: 1dp #92875B;
|
||||||
|
background-color: rgba(21, 22, 16, 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast {
|
||||||
top: 40dp;
|
top: 40dp;
|
||||||
right: 40dp;
|
right: 40dp;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
border-radius: 14dp;
|
border-radius: 14dp;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1dp #92875B;
|
|
||||||
backdrop-filter: blur(5dp);
|
backdrop-filter: blur(5dp);
|
||||||
box-shadow: 0 0 15dp 3dp;
|
box-shadow: 0 0 15dp 3dp;
|
||||||
background-color: rgba(21, 22, 16, 80%);
|
|
||||||
filter: opacity(0);
|
filter: opacity(0);
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
@@ -159,33 +163,99 @@ icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
icon.arrow-forward {
|
icon.arrow-forward {
|
||||||
width: 24dp;
|
width: 1.2em;
|
||||||
height: 24dp;
|
height: 1.2em;
|
||||||
font-size: 24dp;
|
font-size: 1.2em;
|
||||||
decorator: text("" center center);
|
decorator: text("" center center);
|
||||||
}
|
}
|
||||||
|
|
||||||
icon.trophy {
|
icon.trophy {
|
||||||
width: 24dp;
|
width: 1.2em;
|
||||||
height: 24dp;
|
height: 1.2em;
|
||||||
font-size: 24dp;
|
font-size: 1.2em;
|
||||||
decorator: text("" center center);
|
decorator: text("" center center);
|
||||||
}
|
}
|
||||||
|
|
||||||
icon.controller {
|
icon.controller {
|
||||||
width: 24dp;
|
width: 1.2em;
|
||||||
height: 24dp;
|
height: 1.2em;
|
||||||
font-size: 24dp;
|
font-size: 1.2em;
|
||||||
decorator: text("" center center);
|
decorator: text("" center center);
|
||||||
}
|
}
|
||||||
|
|
||||||
icon.warning {
|
icon.warning {
|
||||||
width: 24dp;
|
width: 1.2em;
|
||||||
height: 24dp;
|
height: 1.2em;
|
||||||
font-size: 24dp;
|
font-size: 1.2em;
|
||||||
decorator: text("" center center);
|
decorator: text("" center center);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fps {
|
||||||
|
display: none;
|
||||||
|
z-index: 99;
|
||||||
|
font-size: 18dp;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 9dp 12dp;
|
||||||
|
border-radius: 7dp;
|
||||||
|
pointer-events: none;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
fps[corner=tl] {
|
||||||
|
top: 12dp;
|
||||||
|
left: 12dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
fps[corner=tr] {
|
||||||
|
top: 12dp;
|
||||||
|
right: 12dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
fps[corner=bl] {
|
||||||
|
bottom: 12dp;
|
||||||
|
left: 12dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
fps[corner=br] {
|
||||||
|
bottom: 12dp;
|
||||||
|
right: 12dp;
|
||||||
|
}
|
||||||
|
|
||||||
logo {
|
logo {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100dp;
|
width: 100dp;
|
||||||
@@ -235,3 +305,18 @@ logo img.outer {
|
|||||||
transform: rotate(360deg);
|
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;
|
right: auto;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
/* Scale based on a reference screen width, 428/1216 */
|
/* Scale based on a reference screen width, 856/1216 */
|
||||||
width: 35.230264vw;
|
width: 70.394736vw;
|
||||||
min-width: 428dp;
|
min-width: 428dp;
|
||||||
max-width: 856dp;
|
max-width: 50vw;
|
||||||
height: auto;
|
height: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -83,9 +83,8 @@ body.mirrored menu {
|
|||||||
hero {
|
hero {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 8dp;
|
gap: 4dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.mirrored hero {
|
body.mirrored hero {
|
||||||
@@ -96,19 +95,19 @@ hero img {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eyebrow {
|
eyebrow {
|
||||||
font-family: "Alegreya SC";
|
font-family: "Alegreya SC";
|
||||||
font-size: 32dp;
|
font-size: 32dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1216dp) {
|
@media (min-width: 1216dp) {
|
||||||
.eyebrow {
|
eyebrow {
|
||||||
/* Same logic as .menu, 32/1216 */
|
/* Same logic as .menu, 32/1216 */
|
||||||
font-size: 2.631579vw;
|
font-size: 2.631579vw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.eyebrow span {
|
eyebrow span {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,20 +272,60 @@ body.mirrored version-info {
|
|||||||
font-size: 20dp;
|
font-size: 20dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Hidden until an actual update checker is introduced */
|
|
||||||
.update {
|
.update {
|
||||||
display: none;
|
display: none;
|
||||||
font-size: 16dp;
|
color: #A6A09B;
|
||||||
font-weight: bold;
|
align-items: center;
|
||||||
cursor: pointer;
|
justify-content: flex-end;
|
||||||
color: #D8F999;
|
gap: 8dp;
|
||||||
|
font-size: 20dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail,
|
.update[state=checking],
|
||||||
.update span {
|
.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;
|
color: #A6A09B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.mirrored .update {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
/* Startup animation */
|
/* Startup animation */
|
||||||
.intro-item {
|
.intro-item {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -322,3 +361,136 @@ body.animate-in .intro-item {
|
|||||||
.delay-5 {
|
.delay-5 {
|
||||||
transition: opacity transform 0.3s 0.6s cubic-in-out;
|
transition: opacity transform 0.3s 0.6s cubic-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile layout */
|
||||||
|
@media (max-height: 640dp) {
|
||||||
|
.gradient {
|
||||||
|
decorator: horizontal-gradient(#00000000 #000000FF);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mirrored .gradient {
|
||||||
|
decorator: horizontal-gradient(#000000FF #00000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu {
|
||||||
|
left: 32dp;
|
||||||
|
right: 32dp;
|
||||||
|
width: auto;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: none;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mirrored menu {
|
||||||
|
left: 32dp;
|
||||||
|
right: 32dp;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
hero {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 48%;
|
||||||
|
margin-left: 32dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mirrored hero {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
hero img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-list {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 52%;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-list button {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-list button:hover,
|
||||||
|
#menu-list button:focus-visible {
|
||||||
|
decorator: horizontal-gradient(#FEE68500 #FEE685FF);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mirrored #menu-list {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mirrored #menu-list button {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mirrored #menu-list button:hover,
|
||||||
|
body.mirrored #menu-list button:focus-visible {
|
||||||
|
decorator: horizontal-gradient(#FEE685FF #FEE68500);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
window {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1088dp;
|
max-width: 1088dp;
|
||||||
@@ -104,6 +105,12 @@ window content pane:last-of-type > div {
|
|||||||
line-height: 1.625;
|
line-height: 1.625;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-folder-current {
|
||||||
|
display: block;
|
||||||
|
font-size: 16dp;
|
||||||
|
color: rgba(224, 219, 200, 65%);
|
||||||
|
}
|
||||||
|
|
||||||
window content pane > spacer {
|
window content pane > spacer {
|
||||||
display: block;
|
display: block;
|
||||||
/* Completes the 24dp bottom inset after the pane's 8dp gap. */
|
/* 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;
|
box-shadow: #C2A42D 0 0 0 2dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
button.modal-btn {
|
button.modal-btn {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -287,6 +299,23 @@ icon.celebration {
|
|||||||
decorator: text("" center center);
|
decorator: text("" center center);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
.achievement-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -343,7 +372,7 @@ icon.celebration {
|
|||||||
color: rgba(224, 219, 200, 45%);
|
color: rgba(224, 219, 200, 45%);
|
||||||
}
|
}
|
||||||
|
|
||||||
progressbar {
|
progress {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 6dp;
|
height: 6dp;
|
||||||
@@ -352,12 +381,12 @@ progressbar {
|
|||||||
margin: 6dp 0 2dp 0;
|
margin: 6dp 0 2dp 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
progressbar.progress-done fill {
|
progress.progress-done fill {
|
||||||
background-color: #44aa22;
|
background-color: #44aa22;
|
||||||
border-radius: 3dp;
|
border-radius: 3dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
progressbar.progress-ongoing fill {
|
progress.progress-ongoing fill {
|
||||||
background-color: #2255bb;
|
background-color: #2255bb;
|
||||||
border-radius: 3dp;
|
border-radius: 3dp;
|
||||||
}
|
}
|
||||||
@@ -447,6 +476,11 @@ window.modal.danger .modal-header icon {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-body span.tip {
|
||||||
|
font-size: 14dp;
|
||||||
|
color: #92875B;
|
||||||
|
}
|
||||||
|
|
||||||
.verification-progress {
|
.verification-progress {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -460,7 +494,7 @@ window.modal.danger .modal-header icon {
|
|||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
progressbar.verification-progress-bar {
|
progress.verification-progress-bar {
|
||||||
height: 8dp;
|
height: 8dp;
|
||||||
margin: 2dp 0 0 0;
|
margin: 2dp 0 0 0;
|
||||||
}
|
}
|
||||||
@@ -475,7 +509,6 @@ progressbar.verification-progress-bar {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: stretch;
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 12dp;
|
gap: 12dp;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -51,9 +51,13 @@
|
|||||||
#include "d/actor/d_a_ni.h"
|
#include "d/actor/d_a_ni.h"
|
||||||
#include "d/d_s_play.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 "dusk/settings.h"
|
||||||
#include "res/Object/Alink.h"
|
#include "res/Object/Alink.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#endif
|
||||||
|
|
||||||
static int daAlink_Create(fopAc_ac_c* i_this);
|
static int daAlink_Create(fopAc_ac_c* i_this);
|
||||||
static int daAlink_Delete(daAlink_c* i_this);
|
static int daAlink_Delete(daAlink_c* i_this);
|
||||||
@@ -9362,6 +9366,12 @@ BOOL daAlink_c::spActionTrigger() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BOOL daAlink_c::midnaTalkTrigger() const {
|
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;
|
return mItemTrigger & BTN_Z;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12728,7 +12738,7 @@ void daAlink_c::setMagicArmorBrk(int i_status) {
|
|||||||
|
|
||||||
BOOL daAlink_c::checkMagicArmorHeavy() const {
|
BOOL daAlink_c::checkMagicArmorHeavy() const {
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
return checkMagicArmorWearAbility() && (dComIfGs_getRupee() == 0 && !dusk::getSettings().game.freeMagicArmor);
|
return checkMagicArmorWearAbility() && (dComIfGs_getRupee() == 0 && !dusk::getSettings().game.magicArmorNoHeavy);
|
||||||
#else
|
#else
|
||||||
return checkMagicArmorWearAbility() && dComIfGs_getRupee() == 0;
|
return checkMagicArmorWearAbility() && dComIfGs_getRupee() == 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -14787,6 +14797,10 @@ void daAlink_c::deleteEquipItem(BOOL i_isPlaySound, BOOL i_isDeleteKantera) {
|
|||||||
mIronBallChainPos = NULL;
|
mIronBallChainPos = NULL;
|
||||||
mIronBallChainAngle = NULL;
|
mIronBallChainAngle = NULL;
|
||||||
field_0x3848 = NULL;
|
field_0x3848 = NULL;
|
||||||
|
#if TARGET_PC
|
||||||
|
mIBChainInterpPrevValid = false;
|
||||||
|
mIBChainInterpCurrValid = false;
|
||||||
|
#endif
|
||||||
field_0x0774 = NULL;
|
field_0x0774 = NULL;
|
||||||
field_0x0778 = NULL;
|
field_0x0778 = NULL;
|
||||||
mpHookshotLinChk = NULL;
|
mpHookshotLinChk = NULL;
|
||||||
@@ -16109,6 +16123,9 @@ int daAlink_c::procSlideLand() {
|
|||||||
|
|
||||||
int daAlink_c::procFrontRollInit() {
|
int daAlink_c::procFrontRollInit() {
|
||||||
BOOL is_guard_anime = checkUpperGuardAnime();
|
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) {
|
if (mProcID == PROC_FRONT_ROLL && mDemo.getDemoMode() == daPy_demo_c::DEMO_FRONT_ROLL_e) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -16124,10 +16141,16 @@ int daAlink_c::procFrontRollInit() {
|
|||||||
roll_anm_speed = mpHIO->mFrontRoll.m.mRollAnm.mStartFrame;
|
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.mEndFrame,
|
||||||
mpHIO->mFrontRoll.m.mRollAnm.mInterpolation);
|
mpHIO->mFrontRoll.m.mRollAnm.mInterpolation);
|
||||||
|
|
||||||
mNormalSpeed = speedF * mpHIO->mFrontRoll.m.mSpeedRate + mpHIO->mFrontRoll.m.mInitSpeed;
|
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;
|
f32 max_speed = mpHIO->mFrontRoll.m.mInitSpeed + mpHIO->mMove.m.mMaxSpeed * mpHIO->mFrontRoll.m.mSpeedRate;
|
||||||
@@ -16140,11 +16163,20 @@ int daAlink_c::procFrontRollInit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
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)) {
|
} else if (checkHeavyStateOn(TRUE, TRUE)) {
|
||||||
mNormalSpeed *= mHeavySpeedMultiplier;
|
mNormalSpeed *= mHeavySpeedMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TARGET_PC
|
||||||
|
mNormalSpeed *= fastRollMultiplier;
|
||||||
|
#endif
|
||||||
|
|
||||||
current.angle.y = shape_angle.y;
|
current.angle.y = shape_angle.y;
|
||||||
voiceStart(Z2SE_AL_V_BACKTEN);
|
voiceStart(Z2SE_AL_V_BACKTEN);
|
||||||
mProcVar2.field_0x300c = 0;
|
mProcVar2.field_0x300c = 0;
|
||||||
@@ -16275,8 +16307,13 @@ int daAlink_c::procFrontRollCrashInit() {
|
|||||||
speed.y = mpHIO->mFrontRoll.m.mCrashSpeedV;
|
speed.y = mpHIO->mFrontRoll.m.mCrashSpeedV;
|
||||||
|
|
||||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
||||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
#if TARGET_PC
|
||||||
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY;
|
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);
|
ANGLE_ADD_2(current.angle.y, 0x8000);
|
||||||
@@ -16370,6 +16407,9 @@ int daAlink_c::procFrontRollSuccess() {
|
|||||||
|
|
||||||
int daAlink_c::procSideRollInit(int param_0) {
|
int daAlink_c::procSideRollInit(int param_0) {
|
||||||
BOOL is_prev_guardAnm = checkUpperGuardAnime();
|
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)) {
|
if (!commonProcInitNotSameProc(PROC_SIDE_ROLL)) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -16386,17 +16426,30 @@ int daAlink_c::procSideRollInit(int param_0) {
|
|||||||
current.angle.y = shape_angle.y + -0x4000;
|
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.mStartFrame,
|
||||||
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mEndFrame,
|
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mEndFrame,
|
||||||
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mInterpolation);
|
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mInterpolation);
|
||||||
mNormalSpeed = mpHIO->mGuard.mTurnMove.m.mSideRollSpeed;
|
mNormalSpeed = mpHIO->mGuard.mTurnMove.m.mSideRollSpeed;
|
||||||
|
|
||||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
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)) {
|
} else if (checkHeavyStateOn(TRUE, TRUE)) {
|
||||||
mNormalSpeed *= mHeavySpeedMultiplier;
|
mNormalSpeed *= mHeavySpeedMultiplier;
|
||||||
}
|
}
|
||||||
|
#ifdef TARGET_PC
|
||||||
|
mNormalSpeed *= fastRollMultiplier;
|
||||||
|
#endif
|
||||||
|
|
||||||
setFootEffectProcType(0);
|
setFootEffectProcType(0);
|
||||||
field_0x2f9d = 4;
|
field_0x2f9d = 4;
|
||||||
@@ -18658,7 +18711,7 @@ int daAlink_c::execute() {
|
|||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
// This handles rupee drain and transitions between rupees/no rupees
|
// This handles rupee drain and transitions between rupees/no rupees
|
||||||
// We can skip all of that if the magic armor doesn't use rupees
|
// We can skip all of that if the magic armor doesn't use rupees
|
||||||
if (!dusk::getSettings().game.freeMagicArmor && checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) {
|
if (!dusk::getSettings().game.magicArmorNoDrain && checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) {
|
||||||
#else
|
#else
|
||||||
if (checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) {
|
if (checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) {
|
||||||
#endif
|
#endif
|
||||||
@@ -18671,7 +18724,8 @@ int daAlink_c::execute() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dComIfGs_getRupee() == 0 && field_0x2fd7 != 0) {
|
if (dComIfGs_getRupee() == 0 && field_0x2fd7 != 0 IF_DUSK( && !dusk::getSettings().game.magicArmorNoHeavy))
|
||||||
|
{
|
||||||
setMagicArmorBrk(0);
|
setMagicArmorBrk(0);
|
||||||
seStartOnlyReverb(Z2SE_AL_M_ARMER_TURNOFF);
|
seStartOnlyReverb(Z2SE_AL_M_ARMER_TURNOFF);
|
||||||
mZ2Link.setLinkState(5);
|
mZ2Link.setLinkState(5);
|
||||||
@@ -19717,6 +19771,27 @@ int daAlink_c::draw() {
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
dComIfGd_getOpaListDark()->entryImm(mpHookChain, 0);
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ int daAlink_c::setDamagePoint(int i_dmgAmount, BOOL i_checkZoraMag, BOOL i_setDm
|
|||||||
|
|
||||||
if (checkMagicArmorNoDamage()) {
|
if (checkMagicArmorNoDamage()) {
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
if(dusk::getSettings().game.freeMagicArmor) {
|
if(dusk::getSettings().game.magicArmorNoDamageLoss) {
|
||||||
i_dmgAmount = 0;
|
i_dmgAmount = 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -205,6 +205,9 @@ int daAlink_c::setDamagePoint(int i_dmgAmount, BOOL i_checkZoraMag, BOOL i_setDm
|
|||||||
dComIfGp_setItemLifeCount(-i_dmgAmount, 0);
|
dComIfGp_setItemLifeCount(-i_dmgAmount, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
dusk::AchievementSystem::get().signal("player_damaged");
|
||||||
|
#endif
|
||||||
onResetFlg1(RFLG1_DAMAGE_IMPACT);
|
onResetFlg1(RFLG1_DAMAGE_IMPACT);
|
||||||
mSwordUpTimer = 0;
|
mSwordUpTimer = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||||
#include "dusk/settings.h"
|
#include "dusk/settings.h"
|
||||||
|
#include "dusk/speedrun.h"
|
||||||
|
|
||||||
BOOL daAlink_c::checkEventRun() const {
|
BOOL daAlink_c::checkEventRun() const {
|
||||||
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
|
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "d/actor/d_a_obj_swhang.h"
|
#include "d/actor/d_a_obj_swhang.h"
|
||||||
#include "d/actor/d_a_obj_chandelier.h"
|
#include "d/actor/d_a_obj_chandelier.h"
|
||||||
#include "JSystem/J3DGraphBase/J3DMaterial.h"
|
#include "JSystem/J3DGraphBase/J3DMaterial.h"
|
||||||
|
#include "dusk/frame_interpolation.h"
|
||||||
|
#include "dusk/settings.h"
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
HS_MODE_NONE_e,
|
HS_MODE_NONE_e,
|
||||||
@@ -17,11 +19,11 @@ enum {
|
|||||||
HS_MODE_RETURN_e = 6,
|
HS_MODE_RETURN_e = 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
void daAlink_c::hsChainShape_c::draw() {
|
#if TARGET_PC
|
||||||
if (dusk::getSettings().game.superClawshot) {
|
static const int HS_CHAIN_MAX_LINKS = 600;
|
||||||
return;
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
|
void daAlink_c::hsChainShape_c::draw() {
|
||||||
static const int dummy = 0;
|
static const int dummy = 0;
|
||||||
|
|
||||||
daAlink_c* alink = (daAlink_c*)getUserArea();
|
daAlink_c* alink = (daAlink_c*)getUserArea();
|
||||||
@@ -165,7 +167,11 @@ void daAlink_c::hsChainShape_c::draw() {
|
|||||||
}
|
}
|
||||||
(void)0;
|
(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);
|
temp_f27 = var_f28 * cM_fsin(sp34 * var_f30);
|
||||||
s16 spC = cM_atan2s(temp_f27 - var_f26, 5.0f);
|
s16 spC = cM_atan2s(temp_f27 - var_f26, 5.0f);
|
||||||
sp64.x = sp6C.x + spC;
|
sp64.x = sp6C.x + spC;
|
||||||
@@ -187,6 +193,10 @@ void daAlink_c::hsChainShape_c::draw() {
|
|||||||
|
|
||||||
var_f26 = temp_f27;
|
var_f26 = temp_f27;
|
||||||
var_f30 += fabsf(cM_scos(spC)) * 5.0f;
|
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;
|
sp98 = subChainTopPos;
|
||||||
sp6C.set(maxDistance.atan2sY_XZ(), maxDistance.atan2sX_Z(), 0);
|
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::copy(j3dSys.getViewMtx());
|
||||||
mDoMtx_stack_c::transM(sp98);
|
mDoMtx_stack_c::transM(sp98);
|
||||||
mDoMtx_stack_c::ZXYrotM(sp6C);
|
mDoMtx_stack_c::ZXYrotM(sp6C);
|
||||||
@@ -215,11 +229,39 @@ void daAlink_c::hsChainShape_c::draw() {
|
|||||||
sp98 += maxDistance * 5.0f;
|
sp98 += maxDistance * 5.0f;
|
||||||
ANGLE_ADD_2(sp6C.z, 0x3000);
|
ANGLE_ADD_2(sp6C.z, 0x3000);
|
||||||
var_f30 += 5.0f;
|
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,
|
void daAlink_c::hookshotAtHitCallBack(dCcD_GObjInf* i_atObjInf, fopAc_ac_c* i_tgActor,
|
||||||
dCcD_GObjInf* i_tgObjInf) {
|
dCcD_GObjInf* i_tgObjInf) {
|
||||||
if (i_tgActor != NULL && fopAcM_IsActor(i_tgActor) && !i_tgObjInf->ChkTgHookshotThrough()) {
|
if (i_tgActor != NULL && fopAcM_IsActor(i_tgActor) && !i_tgObjInf->ChkTgHookshotThrough()) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
#include "dusk/gyro.h"
|
#include "dusk/gyro.h"
|
||||||
|
#include "dusk/action_bindings.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool daAlink_c::checkNoSubjectModeCamera() {
|
bool daAlink_c::checkNoSubjectModeCamera() {
|
||||||
@@ -119,8 +120,8 @@ BOOL daAlink_c::setBodyAngleToCamera() {
|
|||||||
var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c);
|
var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c);
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(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));
|
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) {
|
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
|
||||||
sp8 = mBodyAngle.x;
|
sp8 = mBodyAngle.x;
|
||||||
@@ -192,7 +193,9 @@ BOOL daAlink_c::subjectCancelTrigger() {
|
|||||||
BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) {
|
BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) {
|
||||||
setDoStatus(BUTTON_STATUS_BACK);
|
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) {
|
if (i_isPlaySe) {
|
||||||
seStartSystem(Z2SE_SUBJ_VIEW_OUT);
|
seStartSystem(Z2SE_SUBJ_VIEW_OUT);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,12 @@ int daAlink_c::loadModelDVD() {
|
|||||||
mpWlMidnaHairModel = NULL;
|
mpWlMidnaHairModel = NULL;
|
||||||
|
|
||||||
if (!checkNoResetFlg2(FLG2_UNK_280000)) {
|
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);
|
cPhs_Reset(&mPhaseReq);
|
||||||
mpArcHeap->freeAll();
|
mpArcHeap->freeAll();
|
||||||
|
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ void daAlink_c::changeLink(int param_0) {
|
|||||||
initModel(static_cast<J3DModelData*>(dComIfG_getObjectRes(l_mArcName, "al_hands.bmd")), 0);
|
initModel(static_cast<J3DModelData*>(dComIfG_getObjectRes(l_mArcName, "al_hands.bmd")), 0);
|
||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.freeMagicArmor)
|
if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.magicArmorNoHeavy)
|
||||||
#else
|
#else
|
||||||
if (dComIfGs_getRupee() != 0)
|
if (dComIfGs_getRupee() != 0)
|
||||||
#endif
|
#endif
|
||||||
@@ -458,7 +458,7 @@ void daAlink_c::changeLink(int param_0) {
|
|||||||
field_0x06f0 = field_0x064C->getMaterialNodePointer(2)->getShape();
|
field_0x06f0 = field_0x064C->getMaterialNodePointer(2)->getShape();
|
||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.freeMagicArmor) {
|
if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.magicArmorNoHeavy) {
|
||||||
#else
|
#else
|
||||||
if (dComIfGs_getRupee() != 0) {
|
if (dComIfGs_getRupee() != 0) {
|
||||||
#endif
|
#endif
|
||||||
@@ -8723,6 +8723,12 @@ int daAlink_c::procWolfCargoCarry() {
|
|||||||
return checkNextActionWolf(0);
|
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::copy(((e_yc_class*)field_0x280c.getActor())->getLegR3Mtx());
|
||||||
mDoMtx_stack_c::transM(-9.0f, -7.0f, -30.0f);
|
mDoMtx_stack_c::transM(-9.0f, -7.0f, -30.0f);
|
||||||
mDoMtx_stack_c::multVecZero(¤t.pos);
|
mDoMtx_stack_c::multVecZero(¤t.pos);
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
#include "d/actor/d_a_e_pz.h"
|
#include "d/actor/d_a_e_pz.h"
|
||||||
#include "d/actor/d_a_horse.h"
|
#include "d/actor/d_a_horse.h"
|
||||||
#include "d/actor/d_a_hozelda.h"
|
#include "d/actor/d_a_hozelda.h"
|
||||||
|
#if TARGET_PC
|
||||||
|
#include "dusk/achievements.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
int daArrow_c::createHeap() {
|
int daArrow_c::createHeap() {
|
||||||
J3DModelData* model_data;
|
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) {
|
if (dist_to_hitpos < field_0x998) {
|
||||||
field_0x998 = dist_to_hitpos;
|
field_0x998 = dist_to_hitpos;
|
||||||
mHitAcID = fopAcM_GetID(i_tgActor);
|
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) {
|
if (mArrowType == 1) {
|
||||||
field_0x9a8 = *hit_pos_p;
|
field_0x9a8 = *hit_pos_p;
|
||||||
} else if (i_tgObjInf->ChkTgShield()) {
|
} else if (i_tgObjInf->ChkTgShield()) {
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
|
|
||||||
#include "dusk/frame_interpolation.h"
|
#include "dusk/frame_interpolation.h"
|
||||||
#include "dusk/settings.h"
|
#include "dusk/settings.h"
|
||||||
|
#if TARGET_PC
|
||||||
|
#include "dusk/achievements.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class daB_GND_HIO_c : public JORReflexible {
|
class daB_GND_HIO_c : public JORReflexible {
|
||||||
public:
|
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) {
|
if (i_this->mMoveMode < 5 && i_this->mPlayerDistXZ < 600.0f) {
|
||||||
i_this->mMoveMode = 5;
|
i_this->mMoveMode = 5;
|
||||||
i_this->field_0xc44[0] = 10;
|
i_this->field_0xc44[0] = 10;
|
||||||
|
#if TARGET_PC
|
||||||
|
dusk::AchievementSystem::get().signal("ganondorf_fishing_rod");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} else if (i_this->mMoveMode == 5) {
|
} else if (i_this->mMoveMode == 5) {
|
||||||
i_this->mMoveMode = 6;
|
i_this->mMoveMode = 6;
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
#include "f_op/f_op_kankyo_mng.h"
|
#include "f_op/f_op_kankyo_mng.h"
|
||||||
#include "f_op/f_op_actor_enemy.h"
|
#include "f_op/f_op_actor_enemy.h"
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
#include "dusk/frame_interpolation.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class daE_DB_HIO_c : public JORReflexible {
|
class daE_DB_HIO_c : public JORReflexible {
|
||||||
public:
|
public:
|
||||||
daE_DB_HIO_c();
|
daE_DB_HIO_c();
|
||||||
@@ -66,6 +70,22 @@ static BOOL leaf_anm_init(e_db_class* i_this, int i_anm, f32 i_morf, u8 i_mode,
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
static void daE_DB_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||||
|
e_db_class* i_this = (e_db_class*)pUserWork;
|
||||||
|
if (!i_this->mStalkLineInterpPrevValid || !i_this->mStalkLineInterpCurrValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||||
|
cXyz* dst = i_this->stalkLine.getPos(0);
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
const cXyz& p0 = i_this->mStalkLineInterpPrev[i];
|
||||||
|
const cXyz& p1 = i_this->mStalkLineInterpCurr[i];
|
||||||
|
dst[i] = p0 + (p1 - p0) * alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int daE_DB_Draw(e_db_class* i_this) {
|
static int daE_DB_Draw(e_db_class* i_this) {
|
||||||
fopAc_ac_c* actor = &i_this->enemy;
|
fopAc_ac_c* actor = &i_this->enemy;
|
||||||
|
|
||||||
@@ -95,6 +115,17 @@ static int daE_DB_Draw(e_db_class* i_this) {
|
|||||||
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
|
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
|
||||||
i_this->stalkLine.update(12, l_color, &actor->tevStr);
|
i_this->stalkLine.update(12, l_color, &actor->tevStr);
|
||||||
dComIfGd_set3DlineMat(&i_this->stalkLine);
|
dComIfGd_set3DlineMat(&i_this->stalkLine);
|
||||||
|
#if TARGET_PC
|
||||||
|
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||||
|
if (i_this->mStalkLineInterpCurrValid) {
|
||||||
|
memcpy(i_this->mStalkLineInterpPrev, i_this->mStalkLineInterpCurr, sizeof(i_this->mStalkLineInterpCurr));
|
||||||
|
i_this->mStalkLineInterpPrevValid = true;
|
||||||
|
}
|
||||||
|
memcpy(i_this->mStalkLineInterpCurr, i_this->stalkLine.getPos(0), 12 * sizeof(cXyz));
|
||||||
|
i_this->mStalkLineInterpCurrValid = true;
|
||||||
|
dusk::frame_interp::add_interpolation_callback(&daE_DB_interp_callback, i_this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
for (int i = 1; i < 11; i++) {
|
for (int i = 1; i < 11; i++) {
|
||||||
if (i_this->thornModel[i] != NULL) {
|
if (i_this->thornModel[i] != NULL) {
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
#include "d/actor/d_a_e_hb_leaf.h"
|
#include "d/actor/d_a_e_hb_leaf.h"
|
||||||
#include "f_op/f_op_actor_enemy.h"
|
#include "f_op/f_op_actor_enemy.h"
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
#include "dusk/frame_interpolation.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
enum daE_HB_ACTION {
|
enum daE_HB_ACTION {
|
||||||
ACTION_STAY,
|
ACTION_STAY,
|
||||||
ACTION_APPEAR,
|
ACTION_APPEAR,
|
||||||
@@ -64,6 +68,22 @@ static BOOL leaf_anm_init(e_hb_class* i_this, int i_anm, f32 i_morf, u8 i_mode,
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
static void daE_HB_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||||
|
e_hb_class* i_this = (e_hb_class*)pUserWork;
|
||||||
|
if (!i_this->mStalkLineInterpPrevValid || !i_this->mStalkLineInterpCurrValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||||
|
cXyz* dst = i_this->stalkLine.getPos(0);
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
const cXyz& p0 = i_this->mStalkLineInterpPrev[i];
|
||||||
|
const cXyz& p1 = i_this->mStalkLineInterpCurr[i];
|
||||||
|
dst[i] = p0 + (p1 - p0) * alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int daE_HB_Draw(e_hb_class* i_this) {
|
static int daE_HB_Draw(e_hb_class* i_this) {
|
||||||
fopAc_ac_c* actor = &i_this->enemy;
|
fopAc_ac_c* actor = &i_this->enemy;
|
||||||
|
|
||||||
@@ -82,6 +102,17 @@ static int daE_HB_Draw(e_hb_class* i_this) {
|
|||||||
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
|
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
|
||||||
i_this->stalkLine.update(12, l_color, &actor->tevStr);
|
i_this->stalkLine.update(12, l_color, &actor->tevStr);
|
||||||
dComIfGd_set3DlineMat(&i_this->stalkLine);
|
dComIfGd_set3DlineMat(&i_this->stalkLine);
|
||||||
|
#if TARGET_PC
|
||||||
|
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||||
|
if (i_this->mStalkLineInterpCurrValid) {
|
||||||
|
memcpy(i_this->mStalkLineInterpPrev, i_this->mStalkLineInterpCurr, sizeof(i_this->mStalkLineInterpCurr));
|
||||||
|
i_this->mStalkLineInterpPrevValid = true;
|
||||||
|
}
|
||||||
|
memcpy(i_this->mStalkLineInterpCurr, i_this->stalkLine.getPos(0), 12 * sizeof(cXyz));
|
||||||
|
i_this->mStalkLineInterpCurrValid = true;
|
||||||
|
dusk::frame_interp::add_interpolation_callback(&daE_HB_interp_callback, i_this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
for (int i = 1; i < 11; i++) {
|
for (int i = 1; i < 11; i++) {
|
||||||
if (i_this->thornModel[i] != NULL) {
|
if (i_this->thornModel[i] != NULL) {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
#include "d/d_s_play.h"
|
#include "d/d_s_play.h"
|
||||||
#include "f_op/f_op_actor_enemy.h"
|
#include "f_op/f_op_actor_enemy.h"
|
||||||
#include "f_op/f_op_camera_mng.h"
|
#include "f_op/f_op_camera_mng.h"
|
||||||
|
#include "dusk/frame_interpolation.h"
|
||||||
|
#include "dusk/settings.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
class daE_S1_HIO_c {
|
class daE_S1_HIO_c {
|
||||||
@@ -99,6 +101,25 @@ static void anm_init(e_s1_class* i_this, int i_resNo, f32 i_morf, u8 i_attr, f32
|
|||||||
i_this->mAnm = i_resNo;
|
i_this->mAnm = i_resNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
static void daE_S1_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||||
|
e_s1_class* i_this = (e_s1_class*)pUserWork;
|
||||||
|
if (!i_this->mHairInterpPrevValid || !i_this->mHairInterpCurrValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||||
|
for (int s = 0; s < e_s1_class::HAIR_STRAND_COUNT; s++) {
|
||||||
|
cXyz* dst = i_this->mLineMat.getPos(s);
|
||||||
|
for (int i = 0; i < e_s1_class::HAIR_SEGMENT_COUNT; i++) {
|
||||||
|
int idx = s * e_s1_class::HAIR_SEGMENT_COUNT + i;
|
||||||
|
const cXyz& p0 = i_this->mHairInterpPrev[idx];
|
||||||
|
const cXyz& p1 = i_this->mHairInterpCurr[idx];
|
||||||
|
dst[i] = p0 + (p1 - p0) * alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int daE_S1_Draw(e_s1_class* i_this) {
|
static int daE_S1_Draw(e_s1_class* i_this) {
|
||||||
if (i_this->field_0x306c != 0) {
|
if (i_this->field_0x306c != 0) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -132,6 +153,22 @@ static int daE_S1_Draw(e_s1_class* i_this) {
|
|||||||
i_this->mLineMat.update(16, line_color, &i_this->tevStr);
|
i_this->mLineMat.update(16, line_color, &i_this->tevStr);
|
||||||
dComIfGd_set3DlineMatDark(&i_this->mLineMat);
|
dComIfGd_set3DlineMatDark(&i_this->mLineMat);
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||||
|
if (i_this->mHairInterpCurrValid) {
|
||||||
|
memcpy(i_this->mHairInterpPrev, i_this->mHairInterpCurr, sizeof(i_this->mHairInterpCurr));
|
||||||
|
i_this->mHairInterpPrevValid = true;
|
||||||
|
}
|
||||||
|
for (int s = 0; s < e_s1_class::HAIR_STRAND_COUNT; s++) {
|
||||||
|
cXyz* src = i_this->mLineMat.getPos(s);
|
||||||
|
memcpy(&i_this->mHairInterpCurr[s * e_s1_class::HAIR_SEGMENT_COUNT], src,
|
||||||
|
e_s1_class::HAIR_SEGMENT_COUNT * sizeof(cXyz));
|
||||||
|
}
|
||||||
|
i_this->mHairInterpCurrValid = true;
|
||||||
|
dusk::frame_interp::add_interpolation_callback(&daE_S1_interp_callback, i_this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
dComIfGd_setList();
|
dComIfGd_setList();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -2149,6 +2186,11 @@ static int daE_S1_Create(fopAc_ac_c* i_this) {
|
|||||||
return cPhs_ERROR_e;
|
return cPhs_ERROR_e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
a_this->mHairInterpPrevValid = false;
|
||||||
|
a_this->mHairInterpCurrValid = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
OS_REPORT("//////////////E_S1 SET 2 !!\n");
|
OS_REPORT("//////////////E_S1 SET 2 !!\n");
|
||||||
|
|
||||||
if (path_no != 0xFF) {
|
if (path_no != 0xFF) {
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
#include "c/c_damagereaction.h"
|
#include "c/c_damagereaction.h"
|
||||||
#include "f_op/f_op_actor_enemy.h"
|
#include "f_op/f_op_actor_enemy.h"
|
||||||
#include "f_op/f_op_camera_mng.h"
|
#include "f_op/f_op_camera_mng.h"
|
||||||
|
#if TARGET_PC
|
||||||
|
#include "dusk/achievements.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class daE_TH_HIO_c : public JORReflexible {
|
class daE_TH_HIO_c : public JORReflexible {
|
||||||
public:
|
public:
|
||||||
@@ -542,6 +545,7 @@ static void damage_check(e_th_class* i_this) {
|
|||||||
if (i_this->field_0x6a4 == 0 && i_this->mAction != ACTION_SPIN) {
|
if (i_this->field_0x6a4 == 0 && i_this->mAction != ACTION_SPIN) {
|
||||||
daPy_py_c* player = (daPy_py_c*)dComIfGp_getPlayer(0);
|
daPy_py_c* player = (daPy_py_c*)dComIfGp_getPlayer(0);
|
||||||
OS_REPORT("E_th HP1 %d\n", i_this->health);
|
OS_REPORT("E_th HP1 %d\n", i_this->health);
|
||||||
|
s16 prevHealth = i_this->health;
|
||||||
cc_at_check(i_this, &i_this->mAtInfo);
|
cc_at_check(i_this, &i_this->mAtInfo);
|
||||||
OS_REPORT("E_th HP2 %d\n", i_this->health);
|
OS_REPORT("E_th HP2 %d\n", i_this->health);
|
||||||
|
|
||||||
@@ -554,6 +558,11 @@ static void damage_check(e_th_class* i_this) {
|
|||||||
dComIfGs_onOneZoneSwitch(3, -1);
|
dComIfGs_onOneZoneSwitch(3, -1);
|
||||||
|
|
||||||
if (i_this->health <= 0) {
|
if (i_this->health <= 0) {
|
||||||
|
#if TARGET_PC
|
||||||
|
if (prevHealth == i_this->field_0x560) {
|
||||||
|
dusk::AchievementSystem::get().signal("dark_hammer_one_hit");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
i_this->mAction = ACTION_END;
|
i_this->mAction = ACTION_END;
|
||||||
i_this->mMode = 0;
|
i_this->mMode = 0;
|
||||||
i_this->field_0x68a |= 4;
|
i_this->field_0x68a |= 4;
|
||||||
|
|||||||