Compare commits

...

90 Commits

Author SHA1 Message Date
MelonSpeedruns 957ab06fc2 fix autosave spam 2026-05-14 09:36:00 -04:00
MelonSpeedruns fe270d976d Merge remote-tracking branch 'origin/main' into rando/new-item 2026-05-14 09:20:46 -04:00
TakaRikka a913f1699f Merge pull request #1252 from TwilitRealm/erd
clear e_rd static boss ptr on delete
2026-05-13 19:31:35 -07:00
TakaRikka 01b4eaa2fa clear e_rd static boss ptr on delete 2026-05-13 19:27:39 -07:00
SuperDude88 9d2ba3eb49 Rename Settings Preset (#1233)
To match the bloom preset, since that name was updated in #1211
2026-05-13 20:26:30 -04:00
JaxonWasTaken 6026b4bb9b Apply rebrand to bloom setting value name (#1211)
The display name for BloomSetting::Dusk was unchanged in the rebranding
process, and still shows up as "Dusk" in the settings menu. Rename it to
"Dusklight" to bring it in line with the rebrand.
2026-05-13 16:34:14 -04:00
Luke Street 7a77d48954 Use legacy path if migration fails 2026-05-13 09:08:41 -06:00
qwertyquerty 4ee0d8ed4b 1.1.1 fixes (#1168)
* fix keyboard npe

* fix autosave NPE

* hintTalkEvCamera UB

* fix UB in f_pc_base logging

* fix NPE in karg carry logic

* fix link model dangling pointers

* exponential audio slider and better audio default

* fix speedrun mode defaullt layer restore issue
2026-05-13 08:56:16 -06:00
qwertyquerty ce554a107d speedrun mode hotfixes (#1160) 2026-05-13 01:10:25 -06:00
Luke Street 9ce1ab7d5a Migrate only user files 2026-05-13 00:47:29 -06:00
Luke Street 8830760d34 BUILD_SHARED_LIBS=OFF for Android 2026-05-12 23:04:13 -06:00
SuperDude88 9abe89f47f Oops (#1156)
Forgot one
2026-05-12 22:57:42 -06:00
Loïs 3e62c1e96e Add Invincible Enemies cheat (#1123) 2026-05-12 22:52:35 -06:00
Irastris d9bbea300d Add map loader to RmlUi (#1147)
* Add Warp to RmlUi

* Remove ImGui map loader
2026-05-12 22:52:02 -06:00
Luke Street 1a951511be Merge pull request #1153 from TwilitRealm/slingshot-ammo-cheat
Infinite Slingshot Seeds
2026-05-12 22:51:23 -06:00
Luke Street ab0efb7a3b CI fixes and enable mobile THP support 2026-05-12 22:33:12 -06:00
Luke Street 5fdc3c7a54 Merge pull request #875 from Krutonium/flake-desktop-icon 2026-05-12 22:10:53 -06:00
SuperDude88 2978ae145d Infinite Slingshot Seeds 2026-05-13 00:04:58 -04:00
Krutonium e93773757f Edit CMakeLists to add Nix Version 2026-05-12 23:35:54 -04:00
Krutonium 6be742b15f Merge branch 'main' into flake-desktop-icon 2026-05-12 23:34:10 -04:00
Luke Street 861efaa053 Update aurora 2026-05-12 20:33:01 -06:00
qwertyquerty 8e41e0195e Merge pull request #1142 from SailorSnoW/fix/lja-achievement-bugfix
Fix LJA achievement triggering from cutscene teleport
2026-05-12 19:00:51 -07:00
qwertyquerty e9359a92d7 Merge branch 'main' into fix/lja-achievement-bugfix 2026-05-12 19:00:08 -07:00
SailorSnoW 0c78376ba8 Fix LJA achievement triggering from cutscene teleport 2026-05-13 03:40:38 +02:00
Loïs 8c001f7968 Add Nix devshell for Linux and macOS development (#1044)
- Restructure flake to expose `devShells.<system>.default` across
  x86_64-linux, aarch64-linux, x86_64-darwin, and aarch64-darwin via
  `nixpkgs.lib.genAttrs`. The existing `packages.x86_64-linux.default`
  build is preserved (still tied to the linux-x86_64 dawn/nod prebuilts).
- Linux devshell (`mkShell`): gcc + clang/lld, cmake, ninja, pkg-config,
  python3 + markupsafe, rustc/cargo, sccache, plus the system libs
  mirrored from the Ubuntu apt list in .github/workflows/build.yml
  (X11/Wayland, Vulkan, GL, ALSA/PulseAudio/PipeWire, GTK3, freetype,
  zstd, ...).
- macOS devshell (`mkShellNoCC`): cmake, ninja, python3 + markupsafe,
  rustc/cargo, sccache. No cc-wrapper so CMake picks up Apple Clang and
  the Xcode SDK directly, matching the build-apple CI job.
- Ignore `.direnv/` and `.envrc` so local direnv state stays out of git.
2026-05-12 19:37:31 -06:00
gymnast86 ef43b94370 Add options for binding custom buttons to specific actions (#1141)
* custom action framework and first person custom action

* add bind for midna call

* custom binding for opening dusklight menu

* turbo speed button action

* text descriptions

* fix not stopping default GC controller menu combo

* more explanation text

* block bind actions when in the dusklight menu
2026-05-12 19:36:07 -06:00
Pieter-Jan Briers 76efa02beb Allow written files to be read by other applications (#1092)
* Allow written files to be read by other applications

Intended for the log file mainly. Hopefully fixes https://github.com/TwilitRealm/dusk/issues/966?

* Consistency
2026-05-12 19:23:49 -06:00
MelonSpeedruns aeeb1ccdd2 Auto Save Protection (#1102)
* Auto Save Protection

* added line behind target_pc define

---------

Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
Co-authored-by: TakaRikka <38417346+TakaRikka@users.noreply.github.com>
2026-05-12 19:23:28 -06:00
SuperDude88 93c7d0d64d Only Flip Stick Axes (#1132)
Some people asked about this, I think I've come around to their position (follow-up to #870 ).

I checked what TPHD does and its invert setting only changes the sticks
2026-05-12 19:21:28 -06:00
qwertyquerty 1b76b2650c input viewer options in rmlui and reset key option (#1136)
* input viewer options in rmlui

* reset key
2026-05-12 19:18:00 -06:00
Pieter-Jan Briers 45196886b0 Trim trailing newlines off OSReport (#1127) 2026-05-12 19:16:49 -06:00
Pieter-Jan Briers 80af15c95b Log build info on startup (#1117)
Co-authored-by: Luke Street <luke@street.dev>
2026-05-12 19:16:37 -06:00
Sulfrix 4c5e3b933e set sdl app metadata when initializing audio (#1137)
* set sdl app metadata when initializing audio

* move metadata to main
2026-05-12 19:15:33 -06:00
SuperDude88 5eddcb9653 Discord RPC Toggle (#1120)
* Discord RPC Toggle

* I learned my lesson (Formatting)

Took me long enough

* Fix Mobile Platforms

- ifdef the setting so it builds properly on platforms that don't have rpc
- More formatting I missed
2026-05-12 19:14:44 -06:00
tomlube 6dd50c955c fix minor typo (#1134) 2026-05-13 01:21:45 +02:00
TakaRikka 4db65b9845 Merge pull request #1071 from TwilitRealm/better-speedrun-mode
improved speedrun mode
2026-05-12 15:11:24 -07:00
madeline ede6827369 Merge branch 'main' of https://github.com/TakaRikka/dusk into better-speedrun-mode 2026-05-12 14:15:05 -07:00
Luke Street 2c9b20841d Update aurora 2026-05-12 14:00:38 -06:00
Luke Street 2b9ed729a3 Fix file logging on Android 2026-05-12 14:00:38 -06:00
Luke Street f39195c5e0 Fix duplicate "Pause on Focus Lost" option 2026-05-12 14:00:38 -06:00
Luke Street a0f42c0c80 Incorporate SDL_GameControllerDB 2026-05-12 14:00:38 -06:00
Joey Cato d1e9d5af2f Add Fast Roll cheat (#929)
* Add roll fast cheat

* Corrected case on cheat name

* Addressed PR feedback

* Fixed whitespace

* Renamed cheat to be more consistent with other options
2026-05-12 15:56:54 -04:00
Rib 61b2e6ce4d Allow menu navigation via D-Pad (#814) 2026-05-12 11:48:14 -04:00
BoLThompson e73244bca5 add deltatime to darkworld blur size oscillation (#957)
* add deltatime to darkworld blur size oscillation

* updated bloom oscillation to use game_clock
2026-05-12 11:47:14 -04:00
Nathan Mena a4f25ecb28 Fix map offset when toggling mirror mode (#938)
Co-authored-by: Nathan Mena <natemena153+git@gmail.com>
2026-05-12 14:12:38 +02:00
Flash Computer 39b546b81f Don't slow down underwater rolling if the fastIronBoots cheat is on. (#1085)
* Don't slow down underwater rolling if the fastIronBoots cheat is on.

* Removed the comment
2026-05-12 14:11:59 +02:00
madeline 1bd4585994 fix indent 2026-05-11 22:23:31 -07:00
madeline c896bb39ea improved speedrun mode 2026-05-11 22:20:53 -07:00
doop 3366613354 Pillarbox widezoom cutscenes instead of cropping (#1054)
Fixes #777.
2026-05-11 22:59:17 -06:00
Luke Street 79b1f4ab4d Customizable data directory & migration (#1059)
* Customizable data directory & migration

* Add file/dir rename fast-path

* Write data_location.json to base path on Windows; fix UTF-8 custom paths

* Build fix

* Another build fix

* Android data directory selection

* Fix CMake target ref
2026-05-11 22:57:59 -06:00
Irastris 157f4f9df2 Rebrand (#1064)
* Rebrand

* Revert Info.plist.in

* Think, Mark!
2026-05-11 22:06:58 -06:00
SuperDude88 9d5d8dd13a Fix Prelaunch Break-Out With Controller Config (#950)
* Fix Prelaunch Break-Out With Controller Config

Fixes #945

* Formatting
2026-05-11 21:49:24 -06:00
Krutonium b0f1fbee1c Fix Overflow/Off-by-one. Fixes #1036 and #1012 (#1042)
* Fix Overflow/Off-by-one. Fixes #1036 and #1012

* Guard behind TARGET_PC
2026-05-12 01:16:33 +02:00
Howard Luck 40e3f7d057 freedom units (#948) 2026-05-11 12:23:04 +02:00
Luke Street 764fc0b96f Fix "Pause on Focus Lost" preventing startup 2026-05-10 22:29:34 -06:00
SuperDude88 1b4a842eec Invert First Person Aiming (#870)
- Inverts first person aiming on either axis, for both stick and gyro controls

Could be separated from gyro if desirable, but I think it makes the most sense to have it apply to both
2026-05-10 19:00:20 -06:00
gymnast86 08c7261262 fix going into first person with orbital cam (#923) 2026-05-10 19:00:09 -06:00
Luke Street 07b440a1c9 Update aurora 2026-05-10 18:42:31 -06:00
SuperDude88 e7ab978a30 Crash Reporting Popup (#879)
* Initial Draft

- Add draft crash report window on startup

If you want to disable them before/during startup, there is a command line option to force it

* Fixes

- Update language to be more precise, consistent with settings menu
- Actually shut down reporting properly if you disable it
- Fix my silly syntax errors

* Update text & use Sentry consent

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-05-10 18:37:22 -06:00
qwertyquerty 3a02e129e7 Fix keyboard not binding maybe (idk i cant repro) (#901)
* fixkb

* restore comment
2026-05-10 17:35:29 -06:00
qwertyquerty 93c6106770 cherry pick actor spawner (#920) 2026-05-10 17:30:39 -06:00
Luke Street b08d994e32 Update aurora 2026-05-10 17:27:30 -06:00
Luke Street 0665a78c84 Update CMakePresets.json 2026-05-10 13:35:58 -06:00
Luke Street 4db8b51f24 Disable Sentry on Android 2026-05-10 13:22:26 -06:00
Luke Street 800da8dbff Update aurora 2026-05-10 12:48:49 -06:00
Krutonium d6cbc9b6d5 Set Version String to be NixOS-<short git hash> OR dirty 2026-05-10 14:21:03 -04:00
Luke Street a0ecdb1735 Re-enable Sentry for release builds 2026-05-10 12:10:32 -06:00
Krutonium b10211c4c2 Add Desktop Icon 2026-05-10 13:25:43 -04:00
Luke Street c948bffd32 Update aurora 2026-05-10 10:55:30 -06:00
Luke Street 4bcf4ca354 Improve build-appimage.sh 2026-05-10 10:54:43 -06:00
Krutonium bfd9917ca1 Fix Flake to Build Successfully (#791)
* Fix Flake to Build Successfully

* Fix typo; Res Folder is now correctly placed
2026-05-10 10:53:07 -06:00
Pieter-Jan Briers 7f6212f9b7 Add some basic code conventions to the README (#831) 2026-05-10 10:52:47 -06:00
Irastris 80245387f3 Update README & fallback ImGui error message (#857) 2026-05-10 10:52:34 -06:00
Markos Theocharis 0a1fea4bc7 Add LSSupportsGameMode to enable game mode (#859) 2026-05-10 10:51:45 -06:00
project516 4ec7b01213 update github actions in ci (#852) 2026-05-10 10:39:55 -06:00
Luke Street 5187fe90c3 Seed initial pipeline cache through SDL IO & UTF8 path fixes 2026-05-10 10:39:11 -06:00
Pieter-Jan Briers a86fa9c162 State share Unicode fix (#834) 2026-05-10 10:35:11 -06:00
MelonSpeedruns 4ec8c1aaee Fix recording mode muting music until next reboot (#832)
Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
2026-05-10 17:32:55 +02:00
Luke Street 97d032f8b5 Add Android CI 2026-05-10 00:51:34 -06:00
Luke Street 286532904a Update aurora 2026-05-10 00:31:49 -06:00
MelonSpeedruns 9e0966e069 don't change save file length and use unused slot for ghost lantern 2026-05-08 16:32:12 -04:00
MelonSpeedruns b51e672540 Merge remote-tracking branch 'origin/main' into rando/new-item 2026-05-08 15:38:02 -04:00
MelonSpeedruns c844eb2b4a Merge remote-tracking branch 'origin/main' into rando/new-item 2026-05-05 08:33:25 -04:00
MelonSpeedruns 416bfaa90b added light to lantern + proper blue flame particle 2026-05-01 10:17:29 -04:00
MelonSpeedruns 365a0a2188 fire particle + closed link hand 2026-04-28 13:02:31 -04:00
MelonSpeedruns 4ff41fce4e Merge branch 'main' into rando/new-item 2026-04-26 12:10:21 -04:00
MelonSpeedruns a470b73b1b fix weirdnesses with real lantern 2026-04-26 12:10:08 -04:00
MelonSpeedruns 120864a5c9 working lantern model but with hack atm 2026-04-25 18:18:02 -04:00
MelonSpeedruns ba7fc14e87 added poe lantern wip code 2026-04-25 17:17:12 -04:00
MelonSpeedruns c42d10a74b new item code 2026-04-24 16:01:15 -04:00
139 changed files with 7238 additions and 1182 deletions
+75 -8
View File
@@ -10,7 +10,7 @@ on:
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
# SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
jobs:
build-linux:
@@ -50,7 +50,7 @@ jobs:
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.10
- name: Print sccache stats
run: sccache --show-stats
@@ -67,9 +67,9 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
name: dusklight-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
path: |
build/install/Dusk-*.AppImage
build/install/Dusklight-*.AppImage
build/install/debug.tar.*
build-apple:
@@ -124,7 +124,7 @@ jobs:
rustup target add x86_64-apple-darwin
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake
run: cmake --preset ${{matrix.preset}}
@@ -135,11 +135,78 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
name: dusklight-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
path: |
build/install/Dusk.app
build/install/Dusklight.app
build/install/debug.tar.*
build-android:
name: Build Android (${{matrix.name}})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: Clang arm64-v8a
preset: x-android-ci-arm64
abi: arm64-v8a
artifact_arch: arm64
rust_target: aarch64-linux-android
env:
ANDROID_NDK_VERSION: "29.0.14206865"
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get -y install ninja-build
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17
- name: Setup Android SDK
uses: android-actions/setup-android@v4
- name: Install Android SDK packages
run: sdkmanager "platforms;android-36" "build-tools;36.1.0" "ndk;${ANDROID_NDK_VERSION}"
- name: Install Rust Android target
run: |
rustup toolchain install stable
rustup target add ${{matrix.rust_target}}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake
run: cmake --preset ${{matrix.preset}}
- name: Build native library
run: cmake --build --preset ${{matrix.preset}} --target dusklight
- name: Stage stripped JNI library
run: ANDROID_STAGE_ABIS="${{matrix.abi}}" platforms/android/scripts/stage-jni-libs.sh
- name: Build APK
working-directory: platforms/android
run: ./gradlew :app:assembleRelease --rerun-tasks
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: dusklight-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}}
path: platforms/android/app/build/outputs/apk/release/app-${{matrix.abi}}-release-unsigned.apk
build-windows:
name: Build Windows (${{matrix.name}})
runs-on: ${{matrix.runner}}
@@ -199,7 +266,7 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
name: dusklight-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
path: |
build/install/*.exe
build/install/*.dll
+4
View File
@@ -41,6 +41,10 @@ compile_commands.json
# MacOS
.DS_Store
# direnv / nix
.direnv/
.envrc
# ISOs
*.iso
+1 -1
View File
@@ -2,7 +2,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch Dusk MSVC",
"name": "(gdb) Launch Dusklight MSVC",
"type": "cppvsdbg",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"cmake.buildDirectory": "${workspaceFolder}/build/dusk/${buildType}/${variant:tp_version}",
"cmake.buildDirectory": "${workspaceFolder}/build/dusklight/${buildType}/${variant:tp_version}",
"cmake.generator": "Ninja",
"cmake.configureSettings": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
+76 -53
View File
@@ -5,8 +5,19 @@ if (NOT CMAKE_BUILD_TYPE)
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
endif ()
# obtain revision info from git
find_package(Git)
set(DUSK_VERSION_OVERRIDE "" CACHE STRING "Override version string (skips git detection and format validation)")
if (DUSK_VERSION_OVERRIDE)
set(DUSK_WC_DESCRIBE "${DUSK_VERSION_OVERRIDE}")
set(DUSK_VERSION_STRING "0.0.0.0")
set(DUSK_SHORT_VERSION_STRING "0.0.0")
set(DUSK_WC_REVISION "")
set(DUSK_WC_BRANCH "")
set(DUSK_WC_DATE "")
message(STATUS "Dusklight version overridden to ${DUSK_WC_DESCRIBE}")
else ()
# obtain revision info from git
find_package(Git)
if (GIT_FOUND)
# make sure version information gets re-run when the current Git HEAD changes
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD
@@ -63,13 +74,15 @@ else ()
set(DUSK_SHORT_VERSION_STRING "0.0.0")
endif ()
endif ()
# Add version information to CI environment variables
if(DEFINED ENV{GITHUB_ENV})
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n")
endif()
message(STATUS "Dusk version set to ${DUSK_WC_DESCRIBE}")
message(STATUS "Dusklight version set to ${DUSK_WC_DESCRIBE}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
project(dusk LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
project(dusklight LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
if (APPLE)
enable_language(OBJC OBJCXX)
endif ()
@@ -112,11 +125,6 @@ option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)")
option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.")
option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON)
option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON)
if(ANDROID)
set(DUSK_MOVIE_SUPPORT OFF)
endif ()
option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
@@ -134,9 +142,9 @@ endif ()
if (DUSK_MOVIE_SUPPORT)
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
if (libjpeg-turbo_FOUND)
message(STATUS "dusk: Using system libjpeg-turbo")
message(STATUS "dusklight: Using system libjpeg-turbo")
else ()
message(STATUS "dusk: Fetching libjpeg-turbo")
message(STATUS "dusklight: Fetching libjpeg-turbo")
include(ExternalProject)
set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install)
if (WIN32)
@@ -155,6 +163,8 @@ if (DUSK_MOVIE_SUPPORT)
list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file})
endif ()
set(_jpeg_passthrough_vars
ANDROID_ABI
ANDROID_PLATFORM
CMAKE_BUILD_TYPE
CMAKE_C_COMPILER
CMAKE_C_COMPILER_LAUNCHER
@@ -226,13 +236,13 @@ endif ()
include(FetchContent)
# Declare all dependencies first so CMake can download them in parallel
message(STATUS "dusk: Fetching cxxopts")
message(STATUS "dusklight: Fetching cxxopts")
FetchContent_Declare(cxxopts
URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz
URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
message(STATUS "dusk: Fetching nlohmann/json")
message(STATUS "dusklight: Fetching nlohmann/json")
FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
@@ -241,7 +251,7 @@ FetchContent_Declare(json
FetchContent_MakeAvailable(cxxopts json)
if (DUSK_ENABLE_SENTRY_NATIVE)
message(STATUS "dusk: Fetching sentry-native")
message(STATUS "dusklight: Fetching sentry-native")
set(SENTRY_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE)
if (WIN32)
@@ -286,15 +296,15 @@ include(files.cmake)
# TODO: version handling for res includes
set(DUSK_BUNDLE_NAME Dusk)
set(DUSK_BUNDLE_NAME Dusklight)
set(DUSK_BUNDLE_IDENTIFIER dev.twilitrealm.dusk)
set(DUSK_COMPANY_NAME "Twilit Realm")
set(DUSK_FILE_DESCRIPTION "Dusk")
set(DUSK_PRODUCT_NAME "Dusk")
set(DUSK_FILE_DESCRIPTION "Dusklight")
set(DUSK_PRODUCT_NAME "Dusklight")
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
source_group("dusk" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES})
source_group("dusklight" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES})
set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1)
@@ -331,30 +341,30 @@ if (DUSK_ENABLE_UPDATE_CHECKER)
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 "dusk: Enabled update checker (WinHTTP)")
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 "dusk: Enabled update checker (Android)")
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 "dusk: Enabled update checker (NSURLSession)")
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 "dusk: Enabled update checker (libcurl)")
message(STATUS "dusklight: Enabled update checker (libcurl)")
else ()
message(STATUS "dusk: Disabled update checker (libcurl + HTTPS/SSL not found)")
message(STATUS "dusklight: Disabled update checker (libcurl + HTTPS/SSL not found)")
endif ()
else ()
message(STATUS "dusk: Disabled update checker (unsupported platform)")
message(STATUS "dusklight: Disabled update checker (unsupported platform)")
endif ()
endif ()
list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE})
@@ -432,31 +442,37 @@ endif ()
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES})
if(ANDROID)
add_library(dusk SHARED ${DUSK_FILES})
set_target_properties(dusk PROPERTIES OUTPUT_NAME main)
add_library(dusklight SHARED ${DUSK_FILES})
set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
else ()
add_executable(dusk ${DUSK_FILES})
add_executable(dusklight ${DUSK_FILES})
endif ()
target_compile_definitions(dusk PRIVATE ${GAME_COMPILE_DEFS})
target_include_directories(dusk PRIVATE ${GAME_INCLUDE_DIRS})
target_link_libraries(dusk PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
target_precompile_headers(dusk PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
target_compile_definitions(dusklight PRIVATE ${GAME_COMPILE_DEFS})
target_include_directories(dusklight PRIVATE ${GAME_INCLUDE_DIRS})
target_link_libraries(dusklight PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
target_precompile_headers(dusklight PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
if (TARGET crashpad_handler)
add_dependencies(dusk crashpad_handler)
add_dependencies(dusklight crashpad_handler)
add_custom_command(TARGET dusklight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:crashpad_handler>"
"$<TARGET_FILE_DIR:dusklight>"
COMMENT "Copying crashpad handler"
)
endif ()
if (ANDROID)
# SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static
# archive, force an undefined reference so the linker keeps the SDL_main object.
target_link_options(dusk PRIVATE "-Wl,-u,SDL_main")
target_link_options(dusklight PRIVATE "-Wl,-u,SDL_main")
endif ()
if (NOT APPLE)
add_custom_command(TARGET dusk POST_BUILD
add_custom_command(TARGET dusklight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/res"
"$<TARGET_FILE_DIR:dusk>/res"
"$<TARGET_FILE_DIR:dusklight>/res"
COMMENT "Copying resources"
)
endif ()
@@ -464,9 +480,9 @@ endif ()
if (WIN32)
set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows)
set(DUSK_WINDOWS_ICON_PNG ${CMAKE_CURRENT_SOURCE_DIR}/res/icon.png)
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusk.ico)
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusk.rc)
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusk.manifest)
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusklight.ico)
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusklight.rc)
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusklight.manifest)
add_custom_command(
OUTPUT ${DUSK_WINDOWS_ICON_ICO}
@@ -479,14 +495,14 @@ if (WIN32)
COMMENT "Generating Windows icon"
)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.rc.in ${DUSK_WINDOWS_RC} @ONLY)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.rc.in ${DUSK_WINDOWS_RC} @ONLY)
target_sources(dusk PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
set_target_properties(dusk PROPERTIES WIN32_EXECUTABLE TRUE)
target_sources(dusklight PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
set_target_properties(dusklight PROPERTIES WIN32_EXECUTABLE TRUE)
if (MSVC)
target_link_options(dusk PRIVATE /MANIFEST:NO)
target_link_options(dusklight PRIVATE /MANIFEST:NO)
endif ()
endif ()
@@ -502,10 +518,10 @@ if (APPLE)
file(GLOB_RECURSE DUSK_RESOURCE_FILES
"${DUSK_RESOURCE_DIR}/Assets.car"
"${DUSK_RESOURCE_DIR}/Base.lproj/*"
"${DUSK_RESOURCE_DIR}/Dusk.icns")
"${DUSK_RESOURCE_DIR}/Dusklight.icns")
file(GLOB_RECURSE DUSK_APP_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/*")
target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES})
target_sources(dusk PRIVATE ${DUSK_APP_RESOURCE_FILES})
target_sources(dusklight PRIVATE ${DUSK_RESOURCE_FILES})
target_sources(dusklight PRIVATE ${DUSK_APP_RESOURCE_FILES})
foreach (FILE ${DUSK_RESOURCE_FILES})
file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE})
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
@@ -517,29 +533,36 @@ if (APPLE)
set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}")
endforeach ()
set_target_properties(
dusk PROPERTIES
dusklight PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}
MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}
OUTPUT_NAME Dusk
OUTPUT_NAME Dusklight
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES"
)
endif ()
if (APPLE AND NOT IOS AND NOT TVOS)
find_library(APPKIT_FRAMEWORK AppKit REQUIRED)
target_sources(dusklight PRIVATE src/dusk/file_select_macos.mm)
set_source_files_properties(src/dusk/file_select_macos.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
target_link_libraries(dusklight PRIVATE ${APPKIT_FRAMEWORK})
endif ()
if (IOS)
find_library(UIKIT_FRAMEWORK UIKit REQUIRED)
find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers REQUIRED)
target_sources(dusk PRIVATE src/dusk/ios/FileSelectDialog.m)
target_sources(dusklight PRIVATE src/dusk/ios/FileSelectDialog.m)
set_source_files_properties(src/dusk/ios/FileSelectDialog.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
target_link_libraries(dusk PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
target_link_libraries(dusklight PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
endif ()
include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
aurora_copy_runtime_dlls(dusk)
aurora_copy_runtime_dlls(dusklight)
if (DUSK_SELECTED_OPT)
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
@@ -577,13 +600,13 @@ function(get_target_prefix target result_var)
endif ()
endif ()
endfunction()
list(APPEND BINARY_TARGETS dusk)
list(APPEND BINARY_TARGETS dusklight)
set(EXTRA_TARGETS "")
if (TARGET crashpad_handler)
list(APPEND EXTRA_TARGETS crashpad_handler)
endif ()
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX})
aurora_install_runtime_dlls(dusk ${CMAKE_INSTALL_PREFIX})
aurora_install_runtime_dlls(dusklight ${CMAKE_INSTALL_PREFIX})
if (NOT APPLE)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX})
endif ()
+49 -30
View File
@@ -30,7 +30,7 @@
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
"DUSK_ENABLE_SENTRY_NATIVE": {
"type": "BOOL",
"value": false
"value": true
},
"DUSK_SENTRY_DSN": "$env{SENTRY_DSN}",
"DUSK_SENTRY_ENVIRONMENT": "production"
@@ -249,22 +249,11 @@
"type": "BOOL",
"value": false
},
"CMAKE_DISABLE_FIND_PACKAGE_BZip2": {
"CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": {
"type": "BOOL",
"value": true
},
"CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": {
"type": "BOOL",
"value": true
},
"CMAKE_DISABLE_FIND_PACKAGE_zstd": {
"type": "BOOL",
"value": true
},
"CMAKE_DISABLE_FIND_PACKAGE_Freetype": {
"type": "BOOL",
"value": true
}
"CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew"
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
@@ -329,7 +318,11 @@
"cacheVariables": {
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_HOME}/ndk/$env{ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake",
"ANDROID_PLATFORM": "android-28"
"ANDROID_PLATFORM": "android-28",
"BUILD_SHARED_LIBS": {
"type": "BOOL",
"value": false
}
}
},
{
@@ -352,6 +345,31 @@
"ANDROID_ABI": "x86_64"
}
},
{
"name": "x-android-ci",
"hidden": true,
"inherits": [
"android-base",
"ci"
],
"cacheVariables": {
"DUSK_ENABLE_SENTRY_NATIVE": {
"type": "BOOL",
"value": false
}
}
},
{
"name": "x-android-ci-arm64",
"binaryDir": "${sourceDir}/build/android-arm64",
"inherits": [
"x-android-ci"
],
"cacheVariables": {
"ANDROID_ABI": "arm64-v8a",
"Rust_CARGO_TARGET": "aarch64-linux-android"
}
},
{
"name": "x-linux-ci",
"hidden": true,
@@ -391,7 +409,7 @@
},
"CMAKE_OSX_DEPLOYMENT_TARGET": "11.0",
"CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew",
"DUSK_MOVIE_SUPPORT": {
"BUILD_SHARED_LIBS": {
"type": "BOOL",
"value": false
}
@@ -424,11 +442,7 @@
],
"cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
"DUSK_MOVIE_SUPPORT": {
"type": "BOOL",
"value": false
}
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
}
},
{
@@ -438,11 +452,7 @@
],
"cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
"DUSK_MOVIE_SUPPORT": {
"type": "BOOL",
"value": false
}
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
}
},
{
@@ -526,7 +536,7 @@
"description": "iOS release build with debug info",
"displayName": "iOS RelWithDebInfo",
"targets": [
"dusk"
"dusklight"
]
},
{
@@ -535,7 +545,7 @@
"description": "tvOS release build with debug info",
"displayName": "tvOS RelWithDebInfo",
"targets": [
"dusk"
"dusklight"
]
},
{
@@ -544,7 +554,7 @@
"description": "Android arm64-v8a release build with debug info",
"displayName": "Android arm64-v8a RelWithDebInfo",
"targets": [
"dusk"
"dusklight"
]
},
{
@@ -553,7 +563,16 @@
"description": "Android x86_64 release build with debug info",
"displayName": "Android x86_64 RelWithDebInfo",
"targets": [
"dusk"
"dusklight"
]
},
{
"name": "x-android-ci-arm64",
"configurePreset": "x-android-ci-arm64",
"description": "(Internal) Android CI arm64-v8a",
"displayName": "(Internal) Android CI arm64-v8a",
"targets": [
"dusklight"
]
},
{
+15 -12
View File
@@ -1,27 +1,30 @@
<div align="center">
<img src="res/logo-mascot.png" alt="Logo" width="640">
<img src="res/logo.png" alt="Logo" width="640">
<p align="center">
<a href="https://twilitrealm.dev">Official Website</a>
<a href="https://discord.gg/dusktp">Discord</a>
<a href="https://discord.gg/6NpMhefCK9">Discord</a>
</p>
</div>
# Overview
Dusk is a reverse-engineered reimplementation of Twilight Princess.
Dusklight is a reverse-engineered reimplementation of Twilight Princess.
It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
# Setup
> [!IMPORTANT]
> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game.
> Dusklight does *not* provide any copyrighted assets. You must provide your own copy of the original game.
> [!IMPORTANT]
> At a minimum, Dusklight requires a GPU with support for either D3D12, Vulkan, or Metal. Your experience with specific hardware, operating systems, and drivers may vary. In particular, older Intel iGPUs have a high likelihood of incompatibility. We are also aware of a number of issues on devices with Adreno GPUs and are working to resolve them.
### 1. Verify your dump
First, make sure your dump of the game is clean and supported by Dusk. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
First, make sure your dump of the game is clean and supported by Dusklight. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
| Version | SHA-1 hash |
|--------------| ------------------------------------------ |
@@ -30,12 +33,12 @@ First, make sure your dump of the game is clean and supported by Dusk. You can d
*Support for other versions of the game is planned in the future.
### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases)
### 2. Download [Dusklight](https://github.com/TwilitRealm/dusklight/releases)
### 3. Setup the game
**Windows / macOS / Linux**
- Extract the .zip file
- Launch Dusk
- Launch Dusklight
- Press **Select Disc Image** and provide the path to your supported game dump
- Press **Play**!
@@ -43,20 +46,20 @@ First, make sure your dump of the game is clean and supported by Dusk. You can d
- Follow the [iOS setup guide](docs/ios-install-altstore.md)
**Android**
- Install the Dusk apk
- Launch Dusk
- Install the Dusklight APK
- Launch Dusklight
- Press **Select Disc Image** and provide the path to your supported game dump
- Press **Play**!
# Building
If you'd like to build Dusk from source, please read the [build instructions](docs/building.md).
If you'd like to build Dusklight from source, please read the [build instructions](docs/building.md).
Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much.
Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much. Please also see the [code conventions](docs/code-conventions.md).
# Credits
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors).
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusklight/graphs/contributors).
<br/>
<div align="center">
+17 -9
View File
@@ -1,18 +1,26 @@
#!/bin/bash -ex
shopt -s extglob
if [[ -n "${GITHUB_WORKSPACE:-}" ]]; then
cd "$GITHUB_WORKSPACE"
fi
build_dir="$PWD/build"
linuxdeploy="$build_dir/linuxdeploy-$(uname -m).AppImage"
# Get linuxdeploy
cd "$RUNNER_WORKSPACE"
curl -fOL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage
chmod +x linuxdeploy-$(uname -m).AppImage
mkdir -p "$build_dir"
curl -fL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage" -o "$linuxdeploy"
chmod +x "$linuxdeploy"
# Build AppImage
cd "$GITHUB_WORKSPACE"
mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}}
cp -r build/install/!(*.*) build/appdir/usr/bin
for install_path in build/install/*; do
[[ "$(basename "$install_path")" == *.* ]] && continue
cp -r "$install_path" build/appdir/usr/bin
done
cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor
cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications
cp platforms/freedesktop/dusklight.desktop build/appdir/usr/share/applications
cd build/install
VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage
VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$build_dir/appdir" --output appimage
+5 -5
View File
@@ -36,10 +36,10 @@
sudo dnf groupinstall "Development Tools" "Development Libraries"
```
#### Setup
Clone and initialize the Dusk repository
Clone and initialize the Dusklight repository
```sh
git clone --recursive https://github.com/TwilitRealm/dusk.git
cd dusk
git clone --recursive https://github.com/TwilitRealm/dusklight.git
cd dusklight
git pull
git submodule update --init --recursive
```
@@ -93,6 +93,6 @@ Alternate presets available:
#### Running
Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
```sh
build/{preset}/dusk /path/to/game.rvz
build/{preset}/dusklight/path/to/game.rvz
```
If no path is specified, Dusk defaults to `game.iso` in the current working directory.
If no path is specified, Dusklight defaults to `game.iso` in the current working directory.
+13
View File
@@ -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.
+2 -2
View File
@@ -1,10 +1,10 @@
# Installing Dusk on iOS via AltStore
# Installing Dusklight on iOS via AltStore
## Prerequisites
- Mac with Homebrew installed
- iPhone connected via USB
- Dusk IPA file (download the latest `Dusk-vX.X.X-ios-arm64.ipa` from the [releases page](https://github.com/TwilitRealm/dusk/releases))
- 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
+1 -1
+10 -3
View File
@@ -1411,6 +1411,7 @@ set(DOLPHIN_FILES
)
set(DUSK_FILES
include/dusk/action_bindings.h
include/dusk/endian_gx.hpp
include/dusk/config.hpp
include/dusk/dvd_asset.hpp
@@ -1420,6 +1421,8 @@ set(DUSK_FILES
src/dusk/asserts.cpp
src/dusk/config.cpp
src/dusk/crash_reporting.cpp
src/dusk/data.cpp
src/dusk/data.hpp
src/dusk/endian.cpp
src/dusk/extras.c
src/dusk/file_select.cpp
@@ -1435,6 +1438,7 @@ set(DUSK_FILES
src/dusk/layout.cpp
src/dusk/logging.cpp
src/dusk/settings.cpp
src/dusk/speedrun.cpp
src/dusk/stubs.cpp
src/dusk/update_check.cpp
src/dusk/update_check.hpp
@@ -1444,18 +1448,16 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiConsole.cpp
src/dusk/imgui/ImGuiEngine.cpp
src/dusk/imgui/ImGuiEngine.hpp
src/dusk/imgui/ImGuiMenuGame.cpp
src/dusk/imgui/ImGuiMenuGame.hpp
src/dusk/imgui/ImGuiBloomWindow.cpp
src/dusk/imgui/ImGuiBloomWindow.hpp
src/dusk/imgui/ImGuiMenuTools.cpp
src/dusk/imgui/ImGuiMenuTools.hpp
src/dusk/imgui/ImGuiActorSpawner.cpp
src/dusk/imgui/ImGuiProcessOverlay.cpp
src/dusk/imgui/ImGuiCameraOverlay.cpp
src/dusk/imgui/ImGuiHeapOverlay.cpp
src/dusk/imgui/ImGuiControllerOverlay.cpp
src/dusk/imgui/ImGuiStubLog.cpp
src/dusk/imgui/ImGuiMapLoader.cpp
src/dusk/imgui/ImGuiSaveEditor.cpp
src/dusk/imgui/ImGuiStateShare.hpp
src/dusk/imgui/ImGuiStateShare.cpp
@@ -1494,6 +1496,8 @@ set(DUSK_FILES
src/dusk/ui/prelaunch.hpp
src/dusk/ui/preset.cpp
src/dusk/ui/preset.hpp
src/dusk/ui/reporting.cpp
src/dusk/ui/reporting.hpp
src/dusk/ui/select_button.cpp
src/dusk/ui/select_button.hpp
src/dusk/ui/settings.cpp
@@ -1504,6 +1508,8 @@ set(DUSK_FILES
src/dusk/ui/tab_bar.hpp
src/dusk/ui/ui.cpp
src/dusk/ui/ui.hpp
src/dusk/ui/warp.cpp
src/dusk/ui/warp.hpp
src/dusk/ui/window.cpp
src/dusk/ui/window.hpp
src/dusk/achievements.cpp
@@ -1518,6 +1524,7 @@ set(DUSK_FILES
src/dusk/discord.hpp
src/dusk/discord_presence.cpp
src/dusk/version.cpp
src/dusk/action_bindings.cpp
)
set(DUSK_HTTP_BACKEND_FILES
+219 -24
View File
@@ -4,30 +4,225 @@
};
outputs = { self, nixpkgs }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
dusk = pkgs.stdenv.mkDerivation {
name = "dusk";
src = ./.;
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
pkgs.wayland
];
buildInputs = [
pkgs.libGL
pkgs.libX11
pkgs.libXcursor
pkgs.libxi
pkgs.libxcb
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
];
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
pkgsFor = system: import nixpkgs { inherit system; };
# Dependencies that are not packaged in nixpkgs (used by the Linux package build):
buildSources = pkgs: {
aurora-src = pkgs.fetchFromGitHub {
owner = "encounter";
repo = "aurora";
rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
};
dawn-src = pkgs.fetchzip {
url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
stripRoot = false;
};
nod-src = pkgs.fetchzip {
url = "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.8/libnod-linux-x86_64.tar.gz";
hash = "sha256-mUqvLsbsqaZ+HAjMmHYPYO+MgtanGRTw7Gzn5uXR5rE=";
stripRoot = false;
};
# The version of imgui on nixpkgs does not map cleanly.
imgui-src = pkgs.fetchFromGitHub {
owner = "ocornut";
repo = "imgui";
rev = "v1.91.9b-docking";
hash = "sha256-mQOJ6jCN+7VopgZ61yzaCnt4R1QLrW7+47xxMhFRHLQ=";
};
sqlite-src = pkgs.fetchzip {
url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip";
hash = "sha256-pNMR8zxaaqfAzQ0AQBOXMct4usdjey1Q0Gnitg06UhM=";
};
rmlui-src = pkgs.fetchzip {
url = "https://github.com/mikke89/RmlUi/archive/f9b8c9e2935d5df2c7dff2c190d3968e99b0c3dc.tar.gz";
hash = "sha256-g4O/JZUrrcseOz8o2QJRt+2CeuiLnVeuDJc906xvuIg=";
};
};
# Dusklight Actual (Linux x86_64 only — relies on prebuilt dawn/nod binaries)
mkDusklight = pkgs:
let srcs = buildSources pkgs;
versionSuffix = if self ? shortRev && self.shortRev != null
then "nix-${self.shortRev}"
else "nix-dirty";
in
pkgs.stdenv.mkDerivation {
name = "dusklight";
src = ./.;
postUnpack = ''
mkdir -p $sourceRoot/extern/aurora
cp -r ${srcs.aurora-src}/. $sourceRoot/extern/aurora/
chmod -R u+w $sourceRoot/extern/aurora
sed -i '/add_subdirectory(tests)/d' $sourceRoot/extern/aurora/CMakeLists.txt
'';
# Remove last line to re-enable tests
cmakeFlags = [
"-DDUSK_VERSION_OVERRIDE=${versionSuffix}"
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
"-DFETCHCONTENT_SOURCE_DIR_CXXOPTS=${pkgs.cxxopts.src}"
"-DFETCHCONTENT_SOURCE_DIR_JSON=${pkgs.nlohmann_json.src}"
"-DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${srcs.dawn-src}"
"-DFETCHCONTENT_SOURCE_DIR_XXHASH=${pkgs.xxHash.src}"
"-DFETCHCONTENT_SOURCE_DIR_FMT=${pkgs.fmt.src}"
"-DFETCHCONTENT_SOURCE_DIR_TRACY=${pkgs.tracy.src}"
"-DAURORA_SDL3_PROVIDER=system"
"-DFETCHCONTENT_SOURCE_DIR_NOD_PREBUILT=${srcs.nod-src}"
"-DAURORA_NOD_PROVIDER=package"
"-DFETCHCONTENT_SOURCE_DIR_FREETYPE=${pkgs.freetype.src}"
"-DFETCHCONTENT_SOURCE_DIR_ZSTD=${pkgs.zstd.src}"
"-DFETCHCONTENT_SOURCE_DIR_SQLITE3=${srcs.sqlite-src}"
"-DFETCHCONTENT_SOURCE_DIR_IMGUI=${srcs.imgui-src}"
"-DFETCHCONTENT_SOURCE_DIR_RMLUI=${srcs.rmlui-src}"
"-DCMAKE_CROSSCOMPILING=ON" # Tests are not working as I didn't want to work through getting google's test suite working as well. This is the only guard I could find to disable it.
];
installPhase = ''
mkdir -p $out/bin
cp dusklight $out/bin/dusklight
cp -r ./res $out/bin/res
mkdir -p $out/share/applications
cp $src/platforms/freedesktop/dusklight.desktop $out/share/applications/dusklight.desktop
for size in 16 32 48 64 128 256 512 1024; do
install -Dm644 $src/platforms/freedesktop/''${size}x''${size}/apps/dusklight.png \
$out/share/icons/hicolor/''${size}x''${size}/apps/dusklight.png
done
'';
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
pkgs.wayland
];
buildInputs = [
pkgs.libGL
pkgs.libX11
pkgs.libXcursor
pkgs.libxi
pkgs.libxcb
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.fmt
pkgs.tracy
pkgs.freetype
pkgs.zstd
];
};
# Tooling common to every supported host (Linux and macOS).
commonDevTools = pkgs: [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
pkgs.git
pkgs.python3
pkgs.python3Packages.markupsafe
pkgs.rustc
pkgs.cargo
pkgs.sccache
];
# Linux-only system libraries — mirrors the apt deps from .github/workflows/build.yml
# so the cmake presets resolve the same set of headers as CI.
linuxDevDeps = pkgs: [
# Compilers / linkers
pkgs.clang
pkgs.lld
# C/C++ utilities
pkgs.curl
pkgs.openssl
pkgs.zlib
pkgs.libpng
pkgs.libjpeg_turbo
pkgs.freetype
pkgs.zstd
pkgs.fmt
pkgs.tracy
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.ncurses
pkgs.libunwind
pkgs.libusb1
pkgs.fuse
# Wayland / display server
pkgs.wayland
pkgs.wayland-protocols
pkgs.libxkbcommon
pkgs.libdecor
# OpenGL / Vulkan
pkgs.libGL
pkgs.libGLU
pkgs.libglvnd
pkgs.vulkan-headers
pkgs.vulkan-loader
# X11
pkgs.libX11
pkgs.libxcb
pkgs.libXcursor
pkgs.libxi
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libxinerama
# Audio
pkgs.alsa-lib
pkgs.libpulseaudio
pkgs.pipewire
# System integration
pkgs.dbus
pkgs.udev
pkgs.gtk3
];
# On macOS we deliberately avoid pulling Nix's cc-wrapper so CMake picks up
# Apple Clang and the Xcode SDK directly, matching the macOS CI workflow.
mkDarwinShell = pkgs:
pkgs.mkShellNoCC {
packages = commonDevTools pkgs;
shellHook = ''
echo "Dusklight dev shell (macOS)"
echo "Requires Xcode Command Line Tools for Apple Clang and the macOS SDK."
echo "Configure: cmake --preset macos-default-relwithdebinfo"
echo "Build: cmake --build --preset macos-default-relwithdebinfo"
'';
};
mkLinuxShell = pkgs:
pkgs.mkShell {
packages = (commonDevTools pkgs) ++ (linuxDevDeps pkgs);
shellHook = ''
echo "Dusklight dev shell (Linux)"
echo "Configure: cmake --preset linux-default-relwithdebinfo"
echo " cmake --preset linux-clang-relwithdebinfo"
echo "Build: cmake --build --preset <preset>"
'';
};
mkDevShell = pkgs:
if pkgs.stdenv.isDarwin
then mkDarwinShell pkgs
else mkLinuxShell pkgs;
in {
packages.x86_64-linux.default = dusk;
packages.x86_64-linux.default = mkDusklight (pkgsFor "x86_64-linux");
devShells = forAllSystems (system: {
default = mkDevShell (pkgsFor system);
});
};
}
}
+23
View File
@@ -1733,6 +1733,9 @@ public:
int checkNewItemChange(u8 i_selItemIdx);
void deleteEquipItem(BOOL i_isPlaySound, BOOL i_isDeleteKantera);
void setLight();
#if TARGET_PC
void setGhostLanternLight();
#endif
void setFrontRollCrashShock(u8);
void changeWarpMaterial(daAlink_c::daAlink_WARP_MAT_MODE i_matMode);
void commonProcInit(daAlink_c::daAlink_PROC i_procID);
@@ -2656,8 +2659,14 @@ public:
void checkWaterInKandelaar(f32);
void offKandelaarModel();
int kandelaarModelCallBack();
int ghostLanternModelCallBack();
BOOL checkKandelaarEquipAnime() const;
void preKandelaarDraw();
void preGhostLanternDraw();
static void* srchPoe(void*, void*);
#if TARGET_PC
void setGhostLanternModel();
#endif
void setKandelaarModel();
void resetOilBottleModel();
void commonKandelaarPourInit();
@@ -3977,6 +3986,9 @@ public:
/* 0x006FC */ J3DModel* mpKanteraModel;
/* 0x00700 */ J3DModel* mpKanteraGlowModel;
/* 0x00704 */ J3DAnmTextureSRTKey* mpKanteraGlowBtk;
/* 0x006FC */ J3DModel* mpGhostLanternModel;
/* 0x00700 */ J3DModel* mpGhostLanternGlowModel;
/* 0x00704 */ J3DAnmTextureSRTKey* mpGhostLanternGlowBtk;
/* 0x00708 */ J3DModel* mHeldItemModel;
/* 0x0070C */ J3DModel* mpHookTipModel; // related to held item
/* 0x00710 */ J3DModel* field_0x0710; // related to held item
@@ -4373,6 +4385,7 @@ public:
/* 0x03194 */ int field_0x3194;
/* 0x03198 */ int field_0x3198;
/* 0x0319C */ int field_0x319c;
/* 0x0319C */ s32 ghostLanternFlameEffect[2];
/* 0x031A0 */ u32 mModeFlg;
/* 0x031A4 */ int field_0x31a4;
/* 0x031A8 */ u32 field_0x31a8[2];
@@ -4442,6 +4455,9 @@ public:
/* 0x033F4 */ f32 field_0x33f4;
/* 0x033F8 */ f32 field_0x33f8;
/* 0x033FC */ f32 field_0x33fc;
#if TARGET_PC
/* 0x033FC */ f32 current_ghost_lantern_light_power;
#endif
/* 0x03400 */ f32 field_0x3400;
/* 0x03404 */ f32 field_0x3404;
/* 0x03408 */ f32 field_0x3408;
@@ -4509,9 +4525,13 @@ public:
/* 0x035F4 */ cXyz mMidnaAtnPos;
/* 0x03600 */ cXyz mMidnaHairAtnPos;
/* 0x0360C */ cXyz mKandelaarFlamePos;
/* 0x0360C */ cXyz mGhostLanternFlamePos;
/* 0x03618 */ cXyz field_0x3618;
/* 0x03624 */ cXyz field_0x3624;
/* 0x03630 */ cXyz field_0x3630;
/* 0x03618 */ cXyz ghost_lantern_0x3618;
/* 0x03624 */ cXyz ghost_lantern_0x3624;
/* 0x03630 */ cXyz ghost_lantern_0x3630;
/* 0x0363C */ cXyz field_0x363c[4];
/* 0x0366C */ cXyz field_0x366c[4];
/* 0x0369C */ cXyz field_0x369c;
@@ -6821,6 +6841,9 @@ public:
static daAlinkHIO_boom_c0 const mBoomerang;
static daAlinkHIO_bomb_c0 const mBomb;
static daAlinkHIO_huLight_c0 const mLanternPL;
#if TARGET_PC
static daAlinkHIO_huLight_c0 const mGhostLanternPL;
#endif
static daAlinkHIO_kandelaar_c0 const mLantern;
static daAlinkHIO_fmChain_c0 const mFmChain;
static daAlinkHIO_magneBoots_c0 const mIronBoots;
+4
View File
@@ -196,7 +196,11 @@ public:
/* 0x108 */ int mSkipTimer;
/* 0x10C */ int mSkipParameter;
/* 0x110 */ BOOL mIsSkipFade;
#if TARGET_PC
/* 0x114 */ char mSkipEventName[21];
#else
/* 0x114 */ char mSkipEventName[20];
#endif
/* 0x128 */ u8 mCompulsory;
/* 0x129 */ bool mRoomInfoSet;
/* 0x12C */ int mRoomNo;
+2
View File
@@ -71,6 +71,7 @@ void item_func_WOOD_STICK();
void item_func_BOOMERANG();
void item_func_SPINNER();
void item_func_IRONBALL();
void item_func_GHOST_LANTERN();
void item_func_BOW();
void item_func_HOOKSHOT();
void item_func_HVY_BOOTS();
@@ -260,6 +261,7 @@ int item_getcheck_func_WOOD_STICK();
int item_getcheck_func_BOOMERANG();
int item_getcheck_func_SPINNER();
int item_getcheck_func_IRONBALL();
int item_getcheck_func_GHOST_LANTERN();
int item_getcheck_func_BOW();
int item_getcheck_func_HOOKSHOT();
int item_getcheck_func_HVY_BOOTS();
+1 -1
View File
@@ -317,7 +317,7 @@ enum {
/* 0xDE */ dItemNo_NOENTRY_222_e,
/* 0xDF */ dItemNo_NOENTRY_223_e,
/* 0xE0 */ dItemNo_POU_SPIRIT_e,
/* 0xE1 */ dItemNo_NOENTRY_225_e,
/* 0xE1 */ dItemNo_GHOST_LANTERN_e,
/* 0xE2 */ dItemNo_NOENTRY_226_e,
/* 0xE3 */ dItemNo_NOENTRY_227_e,
/* 0xE4 */ dItemNo_NOENTRY_228_e,
+3
View File
@@ -223,6 +223,9 @@ private:
/* 0x8F */ u8 field_0x8f;
/* 0x90 */ u8 field_0x90;
/* 0x91 */ u8 field_0x91;
#if TARGET_PC
bool previousMirror;
#endif
}; // Size: 0x94
class dMap_HIO_list_c : public dMpath_HIO_n::hioList_c {
+2 -2
View File
@@ -86,8 +86,8 @@ public:
/* 0x047 */ u8 field_0x47;
/* 0x048 */ u8 mPoes;
/* 0x049 */ u8 mPoesCopy;
/* 0x04A */ u8 mItems[24];
/* 0x062 */ u8 mItemsCopy[24];
/* 0x04A */ u8 mItems[25];
/* 0x062 */ u8 mItemsCopy[25];
/* 0x07A */ u8 field_0x7a[24];
/* 0x092 */ u8 mSwords[4];
/* 0x096 */ u8 mShields[3];
+43
View File
@@ -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);
}
+6 -1
View File
@@ -7,7 +7,12 @@ namespace dusk {
*
* This gets used for file paths and such, and cannot be changed!
*/
constexpr auto AppName = "Dusk";
constexpr auto AppName = "Dusklight";
/**
* Previous AppName to migrate data from.
*/
constexpr auto LegacyAppName = "Dusk";
/**
* \brief The internal organization name for the game.
+11
View File
@@ -1,8 +1,19 @@
#pragma once
#include <cmath>
#include <dolphin/types.h>
namespace dusk::audio {
// Converts a 0-1 volume to a linear amplitude multiplier.
// The curve is -4 dB per 10% step: 100% = 0 dB, 90% = -4 dB, ..., 0% = -inf dB
inline f32 MasterVolumeToLinear(f32 v) {
if (v <= 0.0f) {
return 0.0f;
}
return std::pow(10.0f, (v - 1.0f) * 2.0f);
}
/**
* Initialize the audio system and start playing audio.
*/
+1
View File
@@ -13,5 +13,6 @@ void enterAutoSave();
void autoSaving();
void waitingForWrite();
void endAutoSave();
void toggleAutoSave(bool enabled);
#endif
+13
View File
@@ -1,6 +1,7 @@
#ifndef DUSK_CONFIG_HPP
#define DUSK_CONFIG_HPP
#include <functional>
#include <stdexcept>
#include "nlohmann/json.hpp"
#include "config_var.hpp"
@@ -111,6 +112,18 @@ void Save();
*/
ConfigVarBase* GetConfigVar(std::string_view name);
/**
* \brief Resets all custom action bindings for a specific port to nothing
*
* @param port The port to be cleared of action bindings
*/
void ClearAllActionBindings(int port);
/**
* \brief Call a function on every registered CVar.
*/
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback);
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl() {
static ConfigImpl<T> config;
+61
View File
@@ -48,6 +48,13 @@ enum class ConfigVarLayer : u8 {
* Will not get saved to config.
*/
Override,
/**
* The CVar is temporarily overridden by speedrun mode.
* Will not get saved to config. Cleared when speedrun mode is disabled.
* Lower priority than Override, so launch args still win.
*/
Speedrun,
};
class ConfigImplBase;
@@ -113,6 +120,12 @@ public:
* This is necessary to make it legal to access.
*/
void markRegistered();
/**
* Clear a speedrun-mode override if one is active on this CVar.
* Safe to call on any CVar, no-op if not at the Speedrun layer.
*/
virtual void clearSpeedrunOverride() {}
};
template <typename T>
@@ -162,6 +175,7 @@ class ConfigVar : public ConfigVarBase {
T defaultValue;
T value;
T overrideValue;
ConfigVarLayer priorLayer = ConfigVarLayer::Default;
public:
/**
@@ -189,6 +203,7 @@ public:
case ConfigVarLayer::Value:
return value;
case ConfigVarLayer::Override:
case ConfigVarLayer::Speedrun:
return overrideValue;
default:
abort();
@@ -239,8 +254,54 @@ public:
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override;
}
/**
* \brief Give a CVar a speedrun-mode override value.
*
* Lower priority than a launch-arg override. Cleared when speedrun mode is disabled.
* The overridden value will not get saved to config.
*
* @param newValue The new value the CVar will get.
*/
void setSpeedrunValue(T newValue) {
checkRegistered();
if (layer != ConfigVarLayer::Override) {
priorLayer = layer;
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Speedrun;
}
}
void clearOverride() {
checkRegistered();
if (layer == ConfigVarLayer::Override) {
overrideValue = {};
layer = ConfigVarLayer::Value;
}
}
void clearSpeedrunOverride() override {
checkRegistered();
if (layer == ConfigVarLayer::Speedrun) {
overrideValue = {};
layer = priorLayer;
}
}
/**
* \brief Get the user-persisted value, ignoring any temporary overrides.
*
* Used by Save() to write the correct value even when a speedrun override is active.
*/
[[nodiscard]] constexpr const T& getValueForSave() const noexcept {
checkRegistered();
const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer;
return effectiveLayer == ConfigVarLayer::Default ? defaultValue : value;
}
};
using ActionBindConfigVar = ConfigVar<int>;
}
#endif // DUSK_CONFIG_VAR_HPP
+13 -4
View File
@@ -1,8 +1,17 @@
#pragma once
namespace dusk {
namespace dusk::crash_reporting {
void InitializeCrashReporting();
void ShutdownCrashReporting();
enum class Consent {
Unavailable,
Unknown,
Given,
Revoked,
};
} // namespace dusk
void initialize();
void shutdown();
Consent get_consent();
void set_consent(bool enabled);
} // namespace dusk::crash_reporting
-1
View File
@@ -14,7 +14,6 @@ constexpr const char* SHOW_DEBUG_OVERLAY = "F3";
constexpr const char* SHOW_HEAP_VIEWER = "F4";
constexpr const char* SHOW_PLAYER_INFO = "F5";
constexpr const char* SHOW_SAVE_EDITOR = "F6";
constexpr const char* SHOW_MAP_LOADER = "F7";
constexpr const char* SHOW_STATE_SHARE = "F8";
constexpr const char* SHOW_DEBUG_CAMERA = "F9";
constexpr const char* SHOW_AUDIO_DEBUG = "F10";
+9 -5
View File
@@ -1,14 +1,13 @@
#ifndef DUSK_IO_HPP
#define DUSK_IO_HPP
#include <vector>
#include <filesystem>
#include <vector>
// I can't believe it's 2026 and neither SDL (no error codes) nor
// C++ (no error codes) have a file system API functional enough for me to use.
// Here you go, this one's inspired by C#. I only wrote the functions I need.
namespace dusk::io {
/**
@@ -83,9 +82,7 @@ public:
/**
* Get direct access to the underlying FILE* handle.
*/
[[nodiscard]] void* GetFileHandle() const noexcept {
return file;
}
[[nodiscard]] void* GetFileHandle() const noexcept { return file; }
/**
* Write data to the file.
@@ -95,7 +92,14 @@ public:
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
+13 -23
View File
@@ -1,36 +1,26 @@
#ifndef DUSK_MAIN_H
#define DUSK_MAIN_H
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#include <filesystem>
#if defined(_WIN32) || \
(defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST) || \
(defined(__linux__) && !defined(__ANDROID__))
#define DUSK_CAN_OPEN_DATA_FOLDER 1
#else
#define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif
namespace dusk {
extern bool IsRunning;
extern bool IsShuttingDown;
extern bool IsGameLaunched;
extern bool RestartRequested;
extern std::filesystem::path ConfigPath;
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
extern bool IsRunning;
extern bool IsShuttingDown;
extern bool IsGameLaunched;
extern bool RestartRequested;
extern std::filesystem::path ConfigPath;
extern std::filesystem::path CachePath;
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV)
inline constexpr bool SupportsProcessRestart = false;
inline constexpr bool SupportsProcessRestart = false;
#else
inline constexpr bool SupportsProcessRestart = true;
inline constexpr bool SupportsProcessRestart = true;
#endif
void RequestRestart() noexcept;
bool OpenDataFolder();
}
void RequestRestart() noexcept;
} // namespace dusk
#endif // DUSK_MAIN_H
+21 -1
View File
@@ -1,6 +1,8 @@
#ifndef DUSK_CONFIG_H
#define DUSK_CONFIG_H
#include <array>
#include "dusk/config_var.hpp"
namespace dusk {
@@ -115,6 +117,7 @@ struct UserSettings {
ConfigVar<bool> enableLinkDollRotation;
ConfigVar<bool> enableAchievementToasts;
ConfigVar<bool> enableControllerToasts;
ConfigVar<bool> enableDiscordPresence;
// Graphics
ConfigVar<BloomMode> bloomMode;
@@ -125,6 +128,7 @@ struct UserSettings {
ConfigVar<int> shadowResolutionMultiplier;
ConfigVar<bool> enableDepthOfField;
ConfigVar<bool> enableMapBackground;
ConfigVar<bool> disableCutscenePillarboxing;
// Audio
ConfigVar<bool> noLowHpSound;
@@ -144,6 +148,8 @@ struct UserSettings {
ConfigVar<bool> freeCamera;
ConfigVar<bool> invertCameraXAxis;
ConfigVar<bool> invertCameraYAxis;
ConfigVar<bool> invertFirstPersonXAxis;
ConfigVar<bool> invertFirstPersonYAxis;
ConfigVar<float> freeCameraSensitivity;
ConfigVar<bool> debugFlyCam;
ConfigVar<bool> debugFlyCamLockEvents;
@@ -152,6 +158,7 @@ struct UserSettings {
// Cheats
ConfigVar<bool> infiniteHearts;
ConfigVar<bool> infiniteArrows;
ConfigVar<bool> infiniteSeeds;
ConfigVar<bool> infiniteBombs;
ConfigVar<bool> infiniteOil;
ConfigVar<bool> infiniteOxygen;
@@ -162,19 +169,25 @@ struct UserSettings {
ConfigVar<bool> alwaysGreatspin;
ConfigVar<bool> enableFastIronBoots;
ConfigVar<bool> canTransformAnywhere;
ConfigVar<bool> fastRoll;
ConfigVar<bool> fastSpinner;
ConfigVar<bool> freeMagicArmor;
ConfigVar<bool> invincibleEnemies;
// Technical
ConfigVar<bool> restoreWiiGlitches;
// Controls
ConfigVar<bool> enableTurboKeybind;
ConfigVar<bool> enableResetKeybind;
// Tools
ConfigVar<bool> speedrunMode;
ConfigVar<bool> liveSplitEnabled;
ConfigVar<bool> showSpeedrunRTATimer;
ConfigVar<bool> recordingMode;
ConfigVar<bool> showInputViewer;
ConfigVar<bool> showInputViewerGyro;
} game;
struct {
@@ -184,11 +197,18 @@ struct UserSettings {
ConfigVar<bool> skipPreLaunchUI;
ConfigVar<bool> showPipelineCompilation;
ConfigVar<bool> wasPresetChosen;
ConfigVar<bool> enableCrashReporting;
ConfigVar<bool> checkForUpdates;
ConfigVar<int> cardFileType;
ConfigVar<bool> enableAdvancedSettings;
} backend;
// Arrays of size 4 for 4 ports
struct {
std::array<ActionBindConfigVar, 4> firstPersonCamera;
std::array<ActionBindConfigVar, 4> callMidna;
std::array<ActionBindConfigVar, 4> openDusklightMenu;
std::array<ActionBindConfigVar, 4> turboSpeedButton;
} actionBindings;
};
UserSettings& getSettings();
+41
View File
@@ -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
+7
View File
@@ -4,6 +4,10 @@
#include <cmath>
#include "os_report.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#endif
u32 JUTGamePad::CRumble::sChannelMask[4] = {
PAD_CHAN0_BIT,
PAD_CHAN1_BIT,
@@ -85,6 +89,9 @@ u32 JUTGamePad::sRumbleSupported;
u32 JUTGamePad::read() {
sRumbleSupported = PADRead(mPadStatus);
#if TARGET_PC
dusk::updateActionBindings();
#endif
switch (sClampMode) {
case EClampStick:
+1 -1
View File
@@ -1,6 +1,6 @@
# Android Shell
This directory contains a minimal SDLActivity-based Android app wrapper for Dusk.
This directory contains a minimal SDLActivity-based Android app wrapper for Dusklight.
## Prerequisites
+1 -1
View File
@@ -3,7 +3,7 @@ plugins {
}
def duskRepoDir = rootProject.projectDir.parentFile.parentFile
def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusk').get().asFile
def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusklight').get().asFile
def syncDuskAssets = tasks.register('syncDuskAssets', Sync) {
from(new File(duskRepoDir, 'res')) {
into 'res'
@@ -1,12 +1,16 @@
package dev.twilitrealm.dusk;
import android.app.ActionBar;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
import android.util.Log;
import android.view.View;
@@ -22,6 +26,13 @@ import java.util.List;
public class DuskActivity extends SDLActivity {
private static final String TAG = "DuskActivity";
private static final int FOLDER_DIALOG_REQUEST_CODE = 0x4455;
private static final String EXTERNAL_STORAGE_AUTHORITY =
"com.android.externalstorage.documents";
private long folderDialogUserdata = 0;
private static native void nativeFolderDialogResult(long userdata, String path, String error);
private static String[] splitArgs(String raw) {
List<String> out = new ArrayList<>();
@@ -147,9 +158,154 @@ public class DuskActivity extends SDLActivity {
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;
@@ -14,15 +14,22 @@ 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[] {
@@ -46,13 +53,19 @@ public class DuskDocumentsProvider extends DocumentsProvider {
@Override
public boolean onCreate() {
ensureUserDirectories();
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();
@@ -222,9 +235,14 @@ public class DuskDocumentsProvider extends DocumentsProvider {
}
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("Dusk files directory is unavailable");
throw new FileNotFoundException("Dusklight files directory is unavailable");
}
return root;
}
@@ -241,7 +259,7 @@ public class DuskDocumentsProvider extends DocumentsProvider {
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 Dusk files directory: " + documentId);
throw new FileNotFoundException("Document escapes Dusklight files directory: " + documentId);
}
if (!file.exists()) {
throw new FileNotFoundException("Document does not exist: " + documentId);
@@ -255,7 +273,7 @@ public class DuskDocumentsProvider extends DocumentsProvider {
return ROOT_DOCUMENT_ID;
}
if (!isInside(root, file)) {
throw new FileNotFoundException("File escapes Dusk files directory: " + file);
throw new FileNotFoundException("File escapes Dusklight files directory: " + file);
}
final String rootPath = canonicalPath(root);
@@ -273,6 +291,42 @@ public class DuskDocumentsProvider extends DocumentsProvider {
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;
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Dusk</string>
<string name="documents_provider_root_name">Dusk Data</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>
Vendored Regular → Executable
View File
+1 -1
View File
@@ -14,5 +14,5 @@ dependencyResolutionManagement {
}
}
rootProject.name = "dusk-android"
rootProject.name = "dusklight-android"
include ':app'

Before

Width:  |  Height:  |  Size: 928 KiB

After

Width:  |  Height:  |  Size: 928 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 1014 B

After

Width:  |  Height:  |  Size: 1014 B

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

-9
View File
@@ -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=Game;
+9
View File
@@ -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;
+2
View File
@@ -83,5 +83,7 @@
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>LSSupportsGameMode</key>
<true/>
</dict>
</plist>
+3 -1
View File
@@ -13,7 +13,7 @@
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>Dusk</string>
<string>Dusklight</string>
<key>CFBundleIconName</key>
<string>Dusk</string>
<key>CFBundleIdentifier</key>
@@ -28,5 +28,7 @@
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSSupportsGameMode</key>
<true/>
</dict>
</plist>
+2
View File
@@ -45,5 +45,7 @@
<string>LaunchScreen</string>
<key>UIUserInterfaceStyle</key>
<string>Automatic</string>
<key>LSSupportsGameMode</key>
<true />
</dict>
</plist>
@@ -24,9 +24,9 @@ BEGIN
VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0"
VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0"
VALUE "FileVersion", "@DUSK_VERSION_STRING@\0"
VALUE "InternalName", "dusk\0"
VALUE "InternalName", "dusklight\0"
VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0"
VALUE "OriginalFilename", "dusk.exe\0"
VALUE "OriginalFilename", "dusklight.exe\0"
VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0"
VALUE "ProductVersion", "@DUSK_VERSION_STRING@\0"
END
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 642 KiB

+31
View File
@@ -201,6 +201,37 @@ fps {
white-space: nowrap;
}
speedrun-timer {
display: none;
position: absolute;
bottom: 0;
right: 0;
z-index: 99;
background-color: rgba(0, 0, 0, 65%);
padding: 2dp 4dp;
pointer-events: none;
font-family: "Noto Mono";
font-size: 16dp;
color: #ffffff;
white-space: nowrap;
}
speedrun-timer[open] {
display: block;
}
speedrun-rta {
display: none;
}
speedrun-rta[open] {
display: block;
}
speedrun-igt {
display: block;
}
fps[open] {
display: block;
}
+8 -9
View File
@@ -65,10 +65,10 @@ menu {
right: auto;
top: 50%;
transform: translateY(-50%);
/* Scale based on a reference screen width, 428/1216 */
width: 35.230264vw;
/* Scale based on a reference screen width, 856/1216 */
width: 70.394736vw;
min-width: 428dp;
max-width: 856dp;
max-width: 50vw;
height: auto;
display: flex;
flex-direction: column;
@@ -83,9 +83,8 @@ body.mirrored menu {
hero {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 8dp;
gap: 4dp;
}
body.mirrored hero {
@@ -96,19 +95,19 @@ hero img {
width: 100%;
}
.eyebrow {
eyebrow {
font-family: "Alegreya SC";
font-size: 32dp;
}
@media (min-width: 1216dp) {
.eyebrow {
eyebrow {
/* Same logic as .menu, 32/1216 */
font-size: 2.631579vw;
}
}
.eyebrow span {
eyebrow span {
font-weight: bold;
}
@@ -437,7 +436,7 @@ body.animate-in .intro-item {
decorator: horizontal-gradient(#FEE685FF #FEE68500);
}
.eyebrow {
eyebrow {
display: none;
}
+11
View File
@@ -105,6 +105,12 @@ window content pane:last-of-type > div {
line-height: 1.625;
}
.data-folder-current {
display: block;
font-size: 16dp;
color: rgba(224, 219, 200, 65%);
}
window content pane > spacer {
display: block;
/* Completes the 24dp bottom inset after the pane's 8dp gap. */
@@ -199,6 +205,11 @@ button:not(:disabled):active {
box-shadow: #C2A42D 0 0 0 2dp;
}
button:disabled {
opacity: 0.35;
cursor: default;
}
button.modal-btn {
flex: 1 1 0;
text-align: center;
+170 -11
View File
@@ -51,10 +51,13 @@
#include "d/actor/d_a_ni.h"
#include "d/d_s_play.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#include "dusk/frame_interpolation.h"
#include "dusk/settings.h"
#include "res/Object/Alink.h"
#include <cstring>
#endif
static int daAlink_Create(fopAc_ac_c* i_this);
static int daAlink_Delete(daAlink_c* i_this);
@@ -6097,6 +6100,21 @@ void daAlink_c::setItemMatrix(int param_0) {
modelCalc(mpKanteraGlowModel);
}
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
simpleAnmPlay(mpGhostLanternGlowBtk);
mDoMtx_stack_c::copy(mpLinkModel->getAnmMtx(mLeftItemJntNo));
mDoMtx_stack_c::transM(-2.0f, -0.1f, -0.7f);
mDoMtx_stack_c::XYZrotM(cM_deg2s(100.0f), cM_deg2s(9.3f), cM_deg2s(183.0f));
mDoMtx_stack_c::scaleM(0.75f, 0.75f, 0.75f);
mpGhostLanternModel->setBaseTRMtx(mDoMtx_stack_c::get());
modelCalc(mpGhostLanternModel);
mDoMtx_stack_c::transS(mGhostLanternFlamePos);
mpGhostLanternGlowModel->setBaseTRMtx(mDoMtx_stack_c::get());
modelCalc(mpGhostLanternGlowModel);
}
setSwordPos();
simpleAnmPlay(m_mSwordBtk);
simpleAnmPlay(m_mSwordBrk);
@@ -6917,7 +6935,7 @@ const daAlink_BckData* daAlink_c::getMainBckData(daAlink_c::daAlink_ANM i_anmID)
{dRes_ID_ALANM_BCK_WAITHS_e, dRes_ID_ALANM_BCK_WAITHK_e},
};
if (mEquipItem == dItemNo_KANTERA_e) {
if (mEquipItem == dItemNo_KANTERA_e || mEquipItem == dItemNo_GHOST_LANTERN_e) {
if (i_anmID == ANM_WAIT) {
return &kandelaarAnm[0];
}
@@ -9363,6 +9381,12 @@ BOOL daAlink_c::spActionTrigger() {
}
BOOL daAlink_c::midnaTalkTrigger() const {
#if TARGET_PC
// If we have a custom bind for Midna, check that instead
if (dusk::isActionBound(dusk::ActionBinds::CALL_MIDNA, 0)) {
return dusk::getActionBindTrig(dusk::ActionBinds::CALL_MIDNA, 0);
}
#endif
return mItemTrigger & BTN_Z;
}
@@ -12058,7 +12082,8 @@ void daAlink_c::itemUnequip(u16 i_itemID, f32 i_playSpeed) {
u16 anm_id;
const daAlinkHIO_anm_c* anm_data;
if (i_itemID == dItemNo_BOOMERANG_e || i_itemID == dItemNo_COPY_ROD_e || i_itemID == dItemNo_KANTERA_e ||
if (i_itemID == dItemNo_BOOMERANG_e || i_itemID == dItemNo_COPY_ROD_e ||
i_itemID == dItemNo_KANTERA_e || i_itemID == dItemNo_GHOST_LANTERN_e ||
i_itemID == dItemNo_HOOKSHOT_e || checkBottleItem(i_itemID))
{
anm_id = dRes_ID_ALANM_BCK_TAKEL_e;
@@ -14148,6 +14173,11 @@ BOOL daAlink_c::setItemModel() {
}
}
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
setGhostLanternModel();
return 1;
}
if (mEquipItem == dItemNo_KANTERA_e) {
setKandelaarModel();
return 1;
@@ -14489,6 +14519,7 @@ enum daAlink_ItemProc {
/* 13 */ ITEM_PROC_BOTTLE_SWING,
/* 14 */ ITEM_PROC_NOT_USE_ITEM,
/* 15 */ ITEM_PROC_GRASS_WHISTLE,
/* 16 */ ITEM_PROC_GHOST_LANTERN,
};
int daAlink_c::changeItemTriggerKeepProc(u8 i_selItemIdx, int i_procType) {
@@ -14513,7 +14544,8 @@ int daAlink_c::changeItemTriggerKeepProc(u8 i_selItemIdx, int i_procType) {
} else {
procKandelaarPourInit();
}
} else if (i_procType == ITEM_PROC_FISHING_FOOD) {
}
else if (i_procType == ITEM_PROC_FISHING_FOOD) {
procFishingFoodInit();
} else if (i_procType == ITEM_PROC_BOOTS_EQUIP) {
procBootsEquipInit();
@@ -14818,6 +14850,43 @@ void daAlink_c::deleteEquipItem(BOOL i_isPlaySound, BOOL i_isDeleteKantera) {
}
}
#if TARGET_PC
void daAlink_c::setGhostLanternLight() {
const daAlinkHIO_huLight_c1* light_m = &mpHIO->mItem.mGhostLanternPL.m;
BOOL var_r28 = false;
f32 lightPower = 0.0f;
daE_HP_c* foundPoe = (daE_HP_c*)fopAcM_Search(srchPoe, this);
if (mEquipItem == dItemNo_GHOST_LANTERN_e && foundPoe != NULL) {
lightPower = 1.0f;
}
cLib_chaseF(&current_ghost_lantern_light_power, lightPower, 0.25f);
cXyz spB8;
f32 var_f27;
if (current_ghost_lantern_light_power > 0.0f) {
GXColor sp30 = {(u8)light_m->mColorR, (u8)light_m->mColorG, (u8)light_m->mColorB, 0xFF};
sp30.r = 120;
sp30.g = 150;
sp30.b = 150;
Vec sp5C = {0.0f, light_m->mYOffset, light_m->mZOffset};
f32 var_f26;
spB8 = mGhostLanternFlamePos;
var_f27 = 0.0f;
var_f26 = cM_sht2d(-shape_angle.y);
dKy_WolfEyeLight_set(&spB8, var_f27 + light_m->mXAngle, var_f26,
(light_m->mWidth * current_ghost_lantern_light_power) / light_m->mPower, &sp30, current_ghost_lantern_light_power,
light_m->mAngleAttenuationType,
light_m->mDistanceAttenuationType);
}
}
#endif
void daAlink_c::setLight() {
const daAlinkHIO_huLight_c1* light_m = &mpHIO->mItem.mLanternPL.m;
BOOL var_r28 = false;
@@ -16114,6 +16183,9 @@ int daAlink_c::procSlideLand() {
int daAlink_c::procFrontRollInit() {
BOOL is_guard_anime = checkUpperGuardAnime();
#ifdef TARGET_PC
const f32 fastRollMultiplier = dusk::getSettings().game.fastRoll ? 2.0f : 1.0f;
#endif
if (mProcID == PROC_FRONT_ROLL && mDemo.getDemoMode() == daPy_demo_c::DEMO_FRONT_ROLL_e) {
return 0;
@@ -16129,10 +16201,16 @@ int daAlink_c::procFrontRollInit() {
roll_anm_speed = mpHIO->mFrontRoll.m.mRollAnm.mStartFrame;
}
setSingleAnime(ANM_FRONT_ROLL, mpHIO->mFrontRoll.m.mRollAnm.mSpeed, roll_anm_speed,
setSingleAnime(ANM_FRONT_ROLL,
#ifdef TARGET_PC
mpHIO->mFrontRoll.m.mRollAnm.mSpeed * fastRollMultiplier,
#else
mpHIO->mFrontRoll.m.mRollAnm.mSpeed,
#endif
roll_anm_speed,
mpHIO->mFrontRoll.m.mRollAnm.mEndFrame,
mpHIO->mFrontRoll.m.mRollAnm.mInterpolation);
mNormalSpeed = speedF * mpHIO->mFrontRoll.m.mSpeedRate + mpHIO->mFrontRoll.m.mInitSpeed;
f32 max_speed = mpHIO->mFrontRoll.m.mInitSpeed + mpHIO->mMove.m.mMaxSpeed * mpHIO->mFrontRoll.m.mSpeedRate;
@@ -16145,11 +16223,20 @@ int daAlink_c::procFrontRollInit() {
}
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
#if TARGET_PC
if (!(dusk::getSettings().game.enableFastIronBoots))
#endif
{
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
}
} else if (checkHeavyStateOn(TRUE, TRUE)) {
mNormalSpeed *= mHeavySpeedMultiplier;
}
#ifdef TARGET_PC
mNormalSpeed *= fastRollMultiplier;
#endif
current.angle.y = shape_angle.y;
voiceStart(Z2SE_AL_V_BACKTEN);
mProcVar2.field_0x300c = 0;
@@ -16280,8 +16367,13 @@ int daAlink_c::procFrontRollCrashInit() {
speed.y = mpHIO->mFrontRoll.m.mCrashSpeedV;
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY;
#if TARGET_PC
if (!(dusk::getSettings().game.enableFastIronBoots))
#endif
{
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY;
}
}
ANGLE_ADD_2(current.angle.y, 0x8000);
@@ -16375,6 +16467,9 @@ int daAlink_c::procFrontRollSuccess() {
int daAlink_c::procSideRollInit(int param_0) {
BOOL is_prev_guardAnm = checkUpperGuardAnime();
#ifdef TARGET_PC
const f32 fastRollMultiplier = dusk::getSettings().game.fastRoll ? 2.0f : 1.0f;
#endif
if (!commonProcInitNotSameProc(PROC_SIDE_ROLL)) {
return 0;
@@ -16391,17 +16486,30 @@ int daAlink_c::procSideRollInit(int param_0) {
current.angle.y = shape_angle.y + -0x4000;
}
setSingleAnime(anmID, mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed,
setSingleAnime(anmID,
#ifdef TARGET_PC
mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed * fastRollMultiplier,
#else
mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed,
#endif
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mStartFrame,
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mEndFrame,
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mInterpolation);
mNormalSpeed = mpHIO->mGuard.mTurnMove.m.mSideRollSpeed;
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
#if TARGET_PC
if (!(dusk::getSettings().game.enableFastIronBoots))
#endif
{
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
}
} else if (checkHeavyStateOn(TRUE, TRUE)) {
mNormalSpeed *= mHeavySpeedMultiplier;
}
#ifdef TARGET_PC
mNormalSpeed *= fastRollMultiplier;
#endif
setFootEffectProcType(0);
field_0x2f9d = 4;
@@ -18217,7 +18325,8 @@ int daAlink_c::execute() {
resetUpperAnime(UPPER_2, 5.0f);
}
if (mEquipItem == dItemNo_KANTERA_e && checkNoUpperAnime() && !checkKandelaarEquipAnime() &&
if ((mEquipItem == dItemNo_KANTERA_e || mEquipItem == dItemNo_GHOST_LANTERN_e) &&
checkNoUpperAnime() && !checkKandelaarEquipAnime() &&
(checkModeFlg(MODE_UNK_1000) || mProcID == PROC_CROUCH))
{
if (checkReinRide()) {
@@ -18568,8 +18677,29 @@ int daAlink_c::execute() {
setCollision();
setAttentionPos();
setLight();
#if TARGET_PC
setGhostLanternLight();
#endif
setEffect();
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
cXyz effscale(0.75f, 0.75f, 0.75f);
static u16 particleNmaeDt[2] = {
0x8789,
0x878A,
};
for (s32 i = 0; i < 2; i++) {
ghostLanternFlameEffect[i] = dComIfGp_particle_set(ghostLanternFlameEffect[i], particleNmaeDt[i],
&mGhostLanternFlamePos, 0, &effscale);
}
} else {
for (s32 i = 0; i < 2; i++) {
stopDrawParticle(ghostLanternFlameEffect[i]);
}
}
if (mClothesChangeWaitTimer != 0) {
mDoMtx_stack_c::copy(mpLinkModel->getBaseTRMtx());
@@ -19271,6 +19401,10 @@ void daAlink_c::shadowDraw() {
dComIfGd_addRealShadow(shadowID, mpKanteraModel);
}
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
dComIfGd_addRealShadow(shadowID, mpGhostLanternModel);
}
if (checkEquipHeavyBoots()) {
for (int i = 0; i < 2; i++) {
dComIfGd_addRealShadow(shadowID, mpLinkBootModels[i]);
@@ -19431,6 +19565,22 @@ int daAlink_c::draw() {
}
}
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
daE_HP_c* foundPoe = (daE_HP_c*)fopAcM_Search(srchPoe, this);
for (s32 i = 0; i < 2; i++) {
JPABaseEmitter* emitter_gl = dComIfGp_particle_getEmitter(ghostLanternFlameEffect[i]);
if (emitter_gl != NULL) {
emitter_gl->setLocalTranslation({2.5f, -15.0f, 0.0f});
if (checkPlayerNoDraw() || foundPoe == NULL) {
emitter_gl->stopDrawParticle();
} else {
emitter_gl->playDrawParticle();
}
}
}
}
if (mClothesChangeWaitTimer != 0) {
if (mProcID == PROC_METAMORPHOSE) {
if (mProcVar3.field_0x300e > 0) {
@@ -19756,6 +19906,15 @@ int daAlink_c::draw() {
basicModelDraw(mpKanteraGlowModel);
}
if (mEquipItem == dItemNo_GHOST_LANTERN_e) {
daE_HP_c* foundPoe = (daE_HP_c*)fopAcM_Search(srchPoe, this);
modelDraw(mpGhostLanternModel, isPlayerNoDraw);
if (foundPoe != NULL) {
preGhostLanternDraw();
modelDraw(mpGhostLanternGlowModel, isPlayerNoDraw);
}
}
if (checkEquipHeavyBoots()) {
GXColorS10 color = tevStr.TevColor;
for (int i = 0; i < 2; i++) {
+2 -1
View File
@@ -25,6 +25,7 @@
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h"
#include "dusk/speedrun.h"
BOOL daAlink_c::checkEventRun() const {
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
@@ -2183,7 +2184,7 @@ void daAlink_c::setGetSubBgm(int i_itemNo) {
/* dItemNo_NOENTRY_222_e */ SETYPE_NONE,
/* dItemNo_NOENTRY_223_e */ SETYPE_NONE,
/* dItemNo_POU_SPIRIT_e */ SETYPE_ITEM_GET_POU,
/* dItemNo_NOENTRY_225_e */ SETYPE_NONE,
/* dItemNo_GHOST_LANTERN_e */ SETYPE_ITEM_GET,
/* dItemNo_NOENTRY_226_e */ SETYPE_NONE,
/* dItemNo_NOENTRY_227_e */ SETYPE_NONE,
/* dItemNo_NOENTRY_228_e */ SETYPE_NONE,
+121
View File
@@ -9,6 +9,7 @@
#include "d/actor/d_a_tag_ss_drink.h"
#include "m_Do/m_Do_lib.h"
#include "SSystem/SComponent/c_math.h"
#include "d/actor/d_a_e_hp.h"
static fopAc_ac_c* daAlink_searchTagKandelaar(fopAc_ac_c* i_actor, void* i_data) {
UNUSED(i_data);
@@ -138,6 +139,50 @@ int daAlink_c::kandelaarModelCallBack() {
return 1;
}
int daAlink_c::ghostLanternModelCallBack() {
cXyz sp44;
mDoMtx_multVecZero(J3DSys::mCurrentMtx, &sp44);
ghost_lantern_0x3630 = ghost_lantern_0x3624;
ghost_lantern_0x3624 = mGhostLanternFlamePos;
cXyz sp38 = (mGhostLanternFlamePos - sp44) + ghost_lantern_0x3618;
sp38.y -= 3.0f;
cXyz sp2C;
mDoMtx_multVec(J3DSys::mCurrentMtx, &cXyz::BaseZ, &sp2C);
s16 var_r29 = sp2C.atan2sX_Z();
mDoMtx_stack_c::YrotS(-var_r29);
mDoMtx_stack_c::multVec(&sp38, &sp38);
s16 var_r28 = cLib_minMaxLimit<s16>(cM_atan2s(-sp38.z, -sp38.y), -0x1800, 0x1800);
s16 var_r27 = cLib_minMaxLimit<s16>(cM_atan2s(sp38.x, JMAFastSqrt(SQUARE(sp38.y) + SQUARE(sp38.z))), -0x1800, 0x1800);
mDoMtx_stack_c::transS(sp44);
if (mProcID != PROC_OPEN_TREASURE && (mProcID != PROC_GET_ITEM || mProcVar4.field_0x3010 == 0)) {
mDoMtx_stack_c::ZXYrotM(var_r28, var_r29, var_r27);
}
static Vec const ghostLanternOffset = {0.0f, -20.0f, 0.0f};
mDoMtx_stack_c::multVec(&ghostLanternOffset, &mGhostLanternFlamePos);
ghost_lantern_0x3618 = (mGhostLanternFlamePos - ghost_lantern_0x3624) * 0.9f;
if (!checkEndResetFlg1(ERFLG1_UNK_8) && mpGhostLanternModel != nullptr) {
f32 scale = JMAFastSqrt(SQUARE(J3DSys::mCurrentMtx[0][0]) + SQUARE(J3DSys::mCurrentMtx[1][0]) + SQUARE(J3DSys::mCurrentMtx[2][0]));
mDoMtx_stack_c::transS(J3DSys::mCurrentMtx[0][3], J3DSys::mCurrentMtx[1][3], J3DSys::mCurrentMtx[2][3]);
mDoMtx_stack_c::ZXYrotM(var_r28, var_r29, var_r27);
mDoMtx_stack_c::ZXYrotM(0, 0, -16384);
mDoMtx_stack_c::scaleM(scale, scale, scale);
mpGhostLanternModel->setAnmMtx(1, mDoMtx_stack_c::get());
cMtx_copy(mDoMtx_stack_c::get(), J3DSys::mCurrentMtx);
}
return 1;
}
static int daAlink_kandelaarModelCallBack(J3DJoint* i_joint, int param_1) {
UNUSED(i_joint);
@@ -150,6 +195,18 @@ static int daAlink_kandelaarModelCallBack(J3DJoint* i_joint, int param_1) {
return 1;
}
static int daAlink_ghostLanternModelCallBack(J3DJoint* i_joint, int param_1) {
UNUSED(i_joint);
daAlink_c* player_p = (daAlink_c*)j3dSys.getModel()->getUserArea();
if (param_1 == 0) {
player_p->ghostLanternModelCallBack();
}
return 1;
}
cXyz* daAlink_c::getKandelaarFlamePos() {
if ((!checkNoResetFlg2(daPy_FLG2(FLG2_UNK_20000 | FLG2_UNK_1)) && !checkEndResetFlg1(ERFLG1_UNK_4)) ||
checkWolf() || !checkNoResetFlg1(FLG1_UNK_80))
@@ -164,6 +221,60 @@ BOOL daAlink_c::checkKandelaarEquipAnime() const {
return checkUpperAnime(dRes_ID_ALANM_BCK_WAITHK_e) || checkUpperAnime(dRes_ID_ALANM_BCK_WAITK_e);
}
void* daAlink_c::srchPoe(void* i_actor, void* i_this) {
if (i_actor != NULL && i_actor != i_this) {
if (fopAcM_IsExecuting(fopAcM_GetID(i_actor)) && fopAcM_GetName(i_actor) == fpcNm_E_HP_e) {
return i_actor;
}
}
return NULL;
}
void daAlink_c::preGhostLanternDraw() {
J3DMaterial* mat_p = mpGhostLanternGlowModel->getModelData()->getMaterialNodePointer(0);
J3DGXColorS10 color;
color.r = 120;
color.g = 150;
color.b = 150;
color.a = 255;
mat_p->setTevColor(1, &color);
color.r = 0;
color.g = 60;
color.b = 101;
mat_p->setTevColor(2, &color);
cXyz proj;
mDoLib_project(&mGhostLanternFlamePos, &proj);
camera_process_class* camera_p = dComIfGp_getCamera(0);
f32 trimHeight;
if (camera_p != NULL) {
trimHeight = camera_p->mCamera.TrimHeight();
} else {
trimHeight = 0.0f;
}
if (proj.x > 0.0f && proj.x < FB_WIDTH && proj.y > trimHeight && proj.y < FB_HEIGHT - trimHeight) {
dComIfGd_peekZ(proj.x, proj.y, &field_0x32c8);
} else {
field_0x32c8 = 0;
}
f32 near_ = dComIfGd_getView()->near_;
f32 far_ = dComIfGd_getView()->far_;
mDoLib_pos2camera(&mGhostLanternFlamePos, &proj);
proj.z += 30.0f;
if (proj.z > -0.01f) {
proj.z = -0.01f;
}
field_0x344c = ((near_ + (far_ * near_) / proj.z) / (far_ - near_) + 1.0f) * 1.6777215E7f;
}
void daAlink_c::preKandelaarDraw() {
J3DMaterial* mat_p = mpKanteraGlowModel->getModelData()->getMaterialNodePointer(0);
@@ -208,6 +319,16 @@ void daAlink_c::preKandelaarDraw() {
field_0x344c = ((near_ + (far_ * near_) / proj.z) / (far_ - near_) + 1.0f) * 1.6777215E7f;
}
void daAlink_c::setGhostLanternModel() {
field_0x2f94 = 0;
mDoMtx_multVecZero(mpLinkModel->getAnmMtx(mLeftItemJntNo), &mGhostLanternFlamePos);
ghost_lantern_0x3630 = ghost_lantern_0x3624;
ghost_lantern_0x3624 = mGhostLanternFlamePos;
ghost_lantern_0x3618 = cXyz::Zero;
}
void daAlink_c::setKandelaarModel() {
field_0x2f94 = 0;
+6 -3
View File
@@ -12,6 +12,7 @@
#if TARGET_PC
#include "dusk/gyro.h"
#include "dusk/action_bindings.h"
#endif
bool daAlink_c::checkNoSubjectModeCamera() {
@@ -119,8 +120,8 @@ BOOL daAlink_c::setBodyAngleToCamera() {
var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c);
}
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle));
sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle));
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f)));
sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f)));
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x;
@@ -192,7 +193,9 @@ BOOL daAlink_c::subjectCancelTrigger() {
BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) {
setDoStatus(BUTTON_STATUS_BACK);
if (checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
// Allow pressing the first person binding to also leave first person
if (IF_DUSK(dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, 0)) ||
checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
if (i_isPlaySe) {
seStartSystem(Z2SE_SUBJ_VIEW_OUT);
}
+6 -1
View File
@@ -77,7 +77,12 @@ int daAlink_c::loadModelDVD() {
mpWlMidnaHairModel = NULL;
if (!checkNoResetFlg2(FLG2_UNK_280000)) {
dComIfG_resDelete(&mPhaseReq, mArcName);
if (!dComIfG_resDelete(&mPhaseReq, mArcName)) {
#if TARGET_PC
// resDelete no-ops if load was in-progress; force-unregister before freeAll
dComIfG_deleteObjectResMain(mArcName);
#endif
}
cPhs_Reset(&mPhaseReq);
mpArcHeap->freeAll();
+20 -1
View File
@@ -300,6 +300,7 @@ void daAlink_c::changeWolf() {
mAtSph.SetR(40.0f);
mpKanteraModel = NULL;
mpGhostLanternModel = NULL;
mpLinkHatModel = NULL;
mpLinkFaceModel = NULL;
mpLinkHandModel = NULL;
@@ -406,12 +407,20 @@ void daAlink_c::changeLink(int param_0) {
modelData = static_cast<J3DModelData*>(dComIfG_getObjectRes(mArcName, "ef_ktGlow.bmd"));
mpKanteraGlowModel = initModel(modelData, 0x200);
mpKanteraGlowBtk = static_cast<J3DAnmTextureSRTKey*>(dComIfG_getObjectRes(mArcName, "ef_ktGlow.btk"));
mpKanteraGlowBtk->setFrame(0.0f);
mpKanteraGlowBtk->searchUpdateMaterialID(modelData);
modelData->entryTexMtxAnimator(mpKanteraGlowBtk);
mpGhostLanternModel = initModelEnv(static_cast<J3DModelData*>(dComIfG_getObjectRes("E_HP", "hp_ori.bmd")), 0);
modelData = static_cast<J3DModelData*>(dComIfG_getObjectRes(mArcName, "ef_ktGlow.bmd"));
mpGhostLanternGlowModel = initModel(modelData, 0x200);
mpGhostLanternGlowBtk = static_cast<J3DAnmTextureSRTKey*>(dComIfG_getObjectRes(mArcName, "ef_ktGlow.btk"));
mpGhostLanternGlowBtk->setFrame(0.0f);
mpGhostLanternGlowBtk->searchUpdateMaterialID(modelData);
modelData->entryTexMtxAnimator(mpGhostLanternGlowBtk);
mWoodSwordModel = initModel(static_cast<J3DModelData*>(dComIfG_getObjectRes(mArcName, "al_SWB.bmd")), 0);
mDoExt_setCurrentHeap(current_heap);
@@ -504,6 +513,10 @@ void daAlink_c::changeLink(int param_0) {
mpKanteraModel->setUserArea((uintptr_t)this);
mpKanteraModel->getModelData()->getJointNodePointer(1)->setCallBack(daAlink_kandelaarModelCallBack);
mpGhostLanternModel->setUserArea((uintptr_t)this);
mpGhostLanternModel->getModelData()->getJointNodePointer(1)->setCallBack(daAlink_ghostLanternModelCallBack);
mZ2Link.setLinkState(var_r27);
#if VERSION != VERSION_SHIELD_DEBUG
@@ -8723,6 +8736,12 @@ int daAlink_c::procWolfCargoCarry() {
return checkNextActionWolf(0);
}
#if TARGET_PC
if (field_0x280c.getActor() == NULL) {
return checkNextActionWolf(0);
}
#endif
mDoMtx_stack_c::copy(((e_yc_class*)field_0x280c.getActor())->getLegR3Mtx());
mDoMtx_stack_c::transM(-9.0f, -7.0f, -30.0f);
mDoMtx_stack_c::multVecZero(&current.pos);
+6
View File
@@ -7053,6 +7053,12 @@ static int daE_RD_IsDelete(e_rd_class*) {
}
static int daE_RD_Delete(e_rd_class* i_this) {
#if TARGET_PC
if (boss == i_this) {
boss = NULL;
}
#endif
fopEn_enemy_c* enemy = (fopEn_enemy_c*)&i_this->enemy;
fopAcM_RegisterDeleteID(i_this, "E_RD");
+5 -1
View File
@@ -11,7 +11,9 @@
#include "d/d_msg_string.h"
#include "dusk/livesplit.h"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/speedrun.h"
#include "m_Do/m_Do_controller_pad.h"
#include <dusk/autosave.h>
dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) {
mArchive = i_archive;
@@ -146,10 +148,12 @@ void dBrightCheck_c::modeMove() {
if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) {
// start a new run if a run isn't already in progress
if (!dusk::m_speedrunInfo.m_isRunStarted) {
dusk::ImGuiMenuGame::resetForSpeedrunMode();
dusk::resetForSpeedrunMode();
dusk::m_speedrunInfo.startRun();
}
}
toggleAutoSave(true);
#endif
mCompleteCheck = true;
mMode = MODE_WAIT_e;
+96 -59
View File
@@ -31,6 +31,7 @@
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#include "dusk/logging.h"
#include "dusk/action_bindings.h"
#include "imgui.h"
#endif
@@ -838,6 +839,12 @@ void dCamera_c::updatePad() {
mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false;
#if TARGET_PC
// If our custom action binding is triggered, and we're not already in first person, go into first person
if (dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID) && mGear != -1) {
setComStat(0x1000);
mGear = 0;
}
if (mCamParam.mManualMode) {
return;
}
@@ -877,7 +884,8 @@ void dCamera_c::updatePad() {
if (mPadInfo.mCStick.mLastPosY < -mCamSetup.mCStick.SwTHH()) {
if (mCStickYState != -1) {
if (mGear == -1 && mCurMode == 4) {
// Don't use regular first person trigger if custom mapping is set
if (mGear == -1 && mCurMode == 4 IF_DUSK(&& !dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID))) {
mGear = 0;
setComStat(0x2000);
} else if (mGear == 0 && sp6C) {
@@ -888,7 +896,8 @@ void dCamera_c::updatePad() {
mCStickYState = -1;
} else if (mPadInfo.mCStick.mLastPosY > mCamSetup.mCStick.SwTHH()) {
if (mCStickYState != 1) {
if (mGear == 0 && sp6B) {
// Don't use regular first person trigger if custom mapping is set
if (mGear == 0 && sp6B IF_DUSK(&& !dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID))) {
setComStat(0x1000);
} else if (mGear == 1) {
mGear = 0;
@@ -7648,7 +7657,11 @@ bool dCamera_c::freeCamera() {
cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f};
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) {
// If we aren't in manual cam mode, don't trigger it if the player tries to hit C-up
// for first person unless they have first person bound to a custom binding
if ((dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID) && mPadInfo.mCStick.mLastPosY != 0) ||
mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY < 0 || (mCamParam.mManualMode == 1 && mPadInfo.mCStick.mLastPosY != 0))
{
mCamParam.mManualMode = 1;
camMovement = camMovement.normalize();
camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
@@ -11231,6 +11244,62 @@ cXyz dCamera_c::Center() {
return mCenter + mShake.field_0x24;
}
#ifdef TARGET_PC
f32 get_target_trim_height(camera_process_class* i_this) {
const auto camera = &i_this->mCamera;
if (camera->mCurState != 2) {
switch (camera->mTrimSize) {
case 0:
case 4:
return 0.0f;
case 1:
return camera->mCamSetup.VistaTrimHeight();
case 2:
case 3:
return camera->mCamSetup.CinemaScopeTrimHeight();
default:
return camera->mTrimHeight;
}
}
return camera->mTrimHeight;
}
void widezoom_correction(camera_process_class* i_this, float trim_height) {
camera_class* camera = (camera_class*)i_this;
dDlst_window_c* window = get_window(camera);
view_port_class* viewport = window->getViewPort();
auto trim_width = 0.0f;
if (mDoGph_gInf_c::isWideZoom()) {
const auto target_ar = FB_WIDTH_BASE / (FB_HEIGHT_BASE - trim_height * 2.0f);
const auto target_ar_real =
FB_WIDTH_BASE / (FB_HEIGHT_BASE - get_target_trim_height(i_this) * 2.0f);
const auto current_ar = camera->view.aspect;
if (current_ar < target_ar) {
trim_height = FB_HEIGHT_BASE / 2.0f * (1.0f - current_ar / target_ar);
} else {
trim_height = 0.0f;
trim_width = FB_WIDTH_BASE / 2.0f * (1.0f - target_ar_real / current_ar);
}
if (dusk::frame_interp::is_sim_frame()) {
constexpr auto base_ar =
static_cast<f32>(FB_WIDTH_BASE) / static_cast<f32>(FB_HEIGHT_BASE);
const auto ar_corr = base_ar / std::min(current_ar, target_ar_real);
camera->view.fovy =
MTXRadToDeg(2.0f * atanf(tanf(MTXDegToRad(camera->view.fovy) * 0.5f) * ar_corr));
}
}
trim_width *= viewport->width / FB_WIDTH_BASE;
trim_height *= viewport->height / FB_HEIGHT_BASE;
window->setScissor(trim_width, trim_height, viewport->width - trim_width * 2.0f,
viewport->height - trim_height * 2.0f);
}
#endif
static int camera_execute(camera_process_class* i_this) {
preparation(i_this);
@@ -11251,6 +11320,28 @@ static int camera_execute(camera_process_class* i_this) {
store(i_this);
#ifdef TARGET_PC
widezoom_correction(i_this, i_this->mCamera.TrimHeight());
if (dusk::getSettings().game.enableFrameInterpolation) {
dusk::frame_interp::add_interpolation_callback([](bool _, void* pUserWork) {
const auto i_this = static_cast<camera_process_class*>(pUserWork);
const auto camera = &i_this->mCamera;
const auto trim_size = camera->mTrimSize;
if (camera->mCurState != 2 && trim_size >= 0 && trim_size <= 3) {
// derive trim height at previous tick using current camera state
const auto target = get_target_trim_height(i_this);
const auto step = dusk::frame_interp::get_interpolation_step();
const auto cur = camera->TrimHeight();
const auto prev = (4.0f * cur - target) / 3.0f;
const auto trim_height = prev + (cur - prev) * step;
widezoom_correction(i_this, trim_height);
}
}, i_this);
}
// record new camera for our sim frame
dusk::frame_interp::record_camera(i_this, get_camera_id(i_this));
// interpolate the view now so that this sim frame's view matrix matches what
@@ -11262,26 +11353,6 @@ static int camera_execute(camera_process_class* i_this) {
return 1;
}
#ifdef TARGET_PC
void set_ar_corrected_trim(dDlst_window_c* window, float trim_height) {
const auto viewport = window->getViewPort();
if (mDoGph_gInf_c::isWideZoom()) {
const auto target_ar = FB_WIDTH / (FB_HEIGHT - trim_height * 2.0f);
const auto current_ar = mDoGph_gInf_c::m_safeWidthF / mDoGph_gInf_c::m_safeHeightF;
if (current_ar < target_ar) {
trim_height = FB_HEIGHT / 2.0f * (1.0f - current_ar / target_ar);
} else {
trim_height = 0.0f;
}
}
trim_height *= viewport->height / FB_HEIGHT;
window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f);
}
#endif
static int camera_draw(camera_process_class* i_this) {
camera_class* a_this = (camera_class*)i_this;
dCamera_c* body = &i_this->mCamera;
@@ -11334,42 +11405,8 @@ static int camera_draw(camera_process_class* i_this) {
}
#endif
#if TARGET_PC
set_ar_corrected_trim(window, body->TrimHeight());
if (dusk::getSettings().game.enableFrameInterpolation) {
dusk::frame_interp::add_interpolation_callback([](bool _, void* pUserWork) {
const auto i_this = static_cast<camera_process_class*>(pUserWork);
const auto camera = &i_this->mCamera;
const auto trim_size = camera->mTrimSize;
if (camera->mCurState != 2 && trim_size >= 0 && trim_size <= 3) {
// derive trim height at previous tick using current camera state
f32 target;
switch (trim_size) {
case 0:
target = 0.0f;
break;
case 1:
target = camera->mCamSetup.VistaTrimHeight();
break;
case 2:
case 3:
target = camera->mCamSetup.CinemaScopeTrimHeight();
break;
}
const auto step = dusk::frame_interp::get_interpolation_step();
const auto cur = camera->TrimHeight();
const auto prev = (4.0f * cur - target) / 3.0f;
const auto trim_height = prev + (cur - prev) * step;
set_ar_corrected_trim(get_window((camera_class*)i_this), trim_height);
}
}, i_this);
}
#else
#if !TARGET_PC
// trim handling moved to camera_execute for PC
int trim_height = body->TrimHeight();
window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f);
+8
View File
@@ -15,6 +15,7 @@
#include "f_op/f_op_actor_mng.h"
#if TARGET_PC
#include "dusk/achievements.h"
#include "dusk/settings.h"
#endif
static int plCutLRC[58] = {
@@ -429,6 +430,13 @@ fopAc_ac_c* cc_at_check(fopAc_ac_c* i_enemy, dCcU_AtInfo* i_AtInfo) {
}
}
#if TARGET_PC
if (dusk::getSettings().game.invincibleEnemies &&
fopAcM_GetGroup(i_enemy) == fopAc_ENEMY_e) {
i_AtInfo->mAttackPower = 0;
}
#endif
if (i_AtInfo->mAttackPower != 0) {
i_enemy->health -= i_AtInfo->mAttackPower;
}
+4
View File
@@ -3882,7 +3882,11 @@ bool dCamera_c::hintTalkEvCamera() {
cSAngle acStack_1fc(20.0f);
for (i = 0; i < 2; i++) {
#if AVOID_UB
for (j = 0; j < 10; j++) {
#else
for (j = 0; j < 12; j++) {
#endif
cSAngle acStack_200(local_b0[j] * fVar22);
hintTalk->mDirection.U(acStack_1f8 + acStack_200);
hintTalk->mDirection.V(((hintTalk->field_0x28.V() * acStack_200.Cos()) * 0.2f) + acStack_1fc);
+10 -2
View File
@@ -236,7 +236,7 @@ static void (*item_func_ptr[256])() = {
item_func_noentry,
item_func_noentry,
item_func_POU_SPIRIT,
item_func_noentry,
item_func_GHOST_LANTERN,
item_func_noentry,
item_func_noentry,
item_func_noentry,
@@ -504,7 +504,7 @@ static int (*item_getcheck_func_ptr[256])() = {
item_getcheck_func_noentry,
item_getcheck_func_noentry,
item_getcheck_func_POU_SPIRIT,
item_getcheck_func_noentry,
item_getcheck_func_GHOST_LANTERN,
item_getcheck_func_noentry,
item_getcheck_func_noentry,
item_getcheck_func_noentry,
@@ -791,6 +791,10 @@ void item_func_IRONBALL() {
dComIfGs_setItem(SLOT_6, dItemNo_IRONBALL_e);
}
void item_func_GHOST_LANTERN() {
dComIfGs_setItem(SLOT_7, dItemNo_GHOST_LANTERN_e);
}
void item_func_BOW() {
dComIfGs_setItem(SLOT_4, dItemNo_BOW_e);
dComIfGs_setArrowNum(30);
@@ -1453,6 +1457,10 @@ int item_getcheck_func_IRONBALL() {
return dComIfGs_getItem(SLOT_6, true) == dItemNo_IRONBALL_e ? TRUE : FALSE;
}
int item_getcheck_func_GHOST_LANTERN() {
return dComIfGs_getItem(SLOT_7, true) == dItemNo_GHOST_LANTERN_e ? TRUE : FALSE;
}
int item_getcheck_func_BOW() {
return dComIfGs_getItem(SLOT_4, false) == dItemNo_BOW_e ? TRUE : FALSE;
}
+10 -1
View File
@@ -36,6 +36,7 @@
#include "dusk/imgui/ImGuiBloomWindow.hpp"
#include "dusk/settings.h"
#include "dusk/frame_interpolation.h"
#include "dusk/game_clock.h"
#endif
static void GxXFog_set();
@@ -2268,6 +2269,7 @@ void dKy_calc_color_set(GXColorS10* out_color_p, color_RGB_class* color_a_start_
color_b_start_p->b, color_b_end_p->b, blend_ratio, add_col.b, scale);
}
void dScnKy_env_light_c::setLight() {
f32 color_ratio;
@@ -2513,7 +2515,14 @@ void dScnKy_env_light_c::setLight() {
static s16 S_fuwan_sin;
f32 sin = cM_ssin(S_fuwan_sin);
S_fuwan_sin += (s16)cM_rndF(2000.0f) + 500;
#if TARGET_PC
const f32 deltaTime = dusk::game_clock::consume_interval(this);
const f32 timeScale = deltaTime / dusk::game_clock::period_for_original_frames(1.0f);
S_fuwan_sin += (s16)((cM_rndF(2000.0f) + 500) * timeScale);
#else
S_fuwan_sin += (s16)cM_rndF(2000.0f) + 500;
#endif
blure_size += (u8)(sin * (0.2f * blure_size));
}
+26 -1
View File
@@ -152,7 +152,7 @@ u8 STControl::checkTrigger() {
field_0x22 = -field_0x24;
}
}
#if !TARGET_PC
if (!(mDirectionTrig & 3)) {
Xinit();
}
@@ -160,10 +160,35 @@ u8 STControl::checkTrigger() {
if (!(mDirectionTrig & 0xC)) {
Yinit();
}
#endif
} else {
mDirectionTrig = 0;
#if !TARGET_PC
Xinit();
Yinit();
#endif
#if TARGET_PC
if (mDoCPd_c::getHoldLeft(PAD_1)) {
mDirectionTrig |= TRIG_LEFT;
}
if (mDoCPd_c::getHoldRight(PAD_1)) {
mDirectionTrig |= TRIG_RIGHT;
}
if (mDoCPd_c::getHoldUp(PAD_1)) {
mDirectionTrig |= TRIG_UP;
}
if (mDoCPd_c::getHoldDown(PAD_1)) {
mDirectionTrig |= TRIG_DOWN;
}
}
if (!(mDirectionTrig & 3)) {
Xinit();
}
if (!(mDirectionTrig & 0xC)) {
Yinit();
#endif
}
if ((field_0x0d & mDirectionTrig & 3) && field_0x0e > 0) {
+14
View File
@@ -1141,6 +1141,9 @@ dMap_c::dMap_c(int width, int height, int param_2, int param_3) {
field_0x91 = 0;
m_mySelfPointer = this;
#endif
#if TARGET_PC
previousMirror = dusk::getSettings().game.enableMirrorMode;
#endif
m_res = JKR_NEW_ARGS (0x20) dMap_prm_res_s;
JUT_ASSERT(2559, m_res != NULL);
@@ -1579,6 +1582,17 @@ bool dMap_c::isDrawRoomIcon(int param_0, int param_1) const {
}
void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
#if TARGET_PC
bool currentMirror = dusk::getSettings().game.enableMirrorMode;
if (currentMirror != previousMirror) {
previousMirror = currentMirror;
if (currentMirror) {
mCenterX -= 2.0f * mPackX;
} else {
mCenterX += 2.0f * mPackX;
}
}
#endif
if (mStayRoomNo == -1) {
mStayRoomNo = i_roomNo;
field_0x80 = mStayRoomNo;
+1 -1
View File
@@ -139,7 +139,7 @@ bool dMenu_Fishing_c::isSync() {
void dMenu_Fishing_c::init() {
#if TARGET_PC || VERSION == VERSION_GCN_PAL
BOOL isEnglish = FALSE;
if (dusk::version::isRegionUsa() || (dusk::version::isRegionPal() && dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH)) {
if (dusk::version::isRegionPal() && dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) {
isEnglish = TRUE;
}
#endif
+3
View File
@@ -1481,6 +1481,9 @@ void dScnLogo_c::dvdDataLoad() {
OS_REPORT("\x1b[32m%d archiveHeap->getTotalFreeSize %08x\n\x1b[m", 2421, archiveHeap->getTotalFreeSize());
archiveHeap->dump_sort();
rt = dComIfG_setObjectRes("E_HP", (u8)0, NULL);
JUT_ASSERT(2425, rt == 1);
rt = dComIfG_setObjectRes("Alink", (u8)0, NULL);
JUT_ASSERT(2429, rt == 1);
+5 -1
View File
@@ -11,6 +11,7 @@
#include "d/d_s_name.h"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/memory.h"
#include "dusk/speedrun.h"
#include "dusk/settings.h"
#include "f_op/f_op_overlap_mng.h"
#include "f_op/f_op_scene_mng.h"
@@ -19,6 +20,7 @@
#include "m_Do/m_Do_machine.h"
#include "m_Do/m_Do_main.h"
#include "m_Do/m_Do_mtx.h"
#include <dusk/autosave.h>
#if TARGET_PC
#define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen)
@@ -418,10 +420,12 @@ void dScnName_c::changeGameScene() {
if (dusk::getSettings().game.speedrunMode && dusk::getSettings().game.hideTvSettingsScreen) {
// start a new run on file load if a run isn't already in progress
if (!dusk::m_speedrunInfo.m_isRunStarted) {
dusk::ImGuiMenuGame::resetForSpeedrunMode();
dusk::resetForSpeedrunMode();
dusk::m_speedrunInfo.startRun();
}
}
toggleAutoSave(true);
#endif
}
}
+4
View File
@@ -1042,6 +1042,10 @@ static BOOL heapSizeCheck() {
bool dScnPly_c::resetGame() {
if (fpcM_GetName(this) == fpcNm_OPENING_SCENE_e) {
#if TARGET_PC
toggleAutoSave(false);
#endif
if (!dStage_roomControl_c::resetArchiveBank(0)) {
return false;
}
+5 -5
View File
@@ -432,11 +432,11 @@ u8 dSv_player_item_c::getItem(int i_slotNo, bool i_checkCombo) const {
}
void dSv_player_item_c::setLineUpItem() {
static u8 i_item_lst[23] = {
static u8 i_item_lst[24] = {
10, 8, 6, 2, 9, 4, 3,
0, 1, 23, 20, 5, 15, 16,
17, 11, 12, 13, 14, 19, 18,
22, 21
0, 1, 7, 23, 20, 5, 15,
16, 17, 11, 12, 13, 14, 19,
18, 22, 21
};
int slot_idx = 0;
@@ -445,7 +445,7 @@ void dSv_player_item_c::setLineUpItem() {
mItemSlots[i] = dItemNo_NONE_e;
}
for (int i = 0; i < 23; i++) {
for (int i = 0; i < 24; i++) {
u8 current = i_item_lst[i];
if (mItems[current] != dItemNo_NONE_e) {
mItemSlots[slot_idx] = current;
+5
View File
@@ -53,6 +53,11 @@ static std::string FormatToString(const char* msg, va_list list) {
size *= 2;
}
}
while (!str.empty() && str[str.size()-1] == '\n') {
str.pop_back();
}
return str;
}
+6
View File
@@ -692,6 +692,12 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
return;
}
// prevent stuff like https://github.com/TwilitRealm/dusklight/issues/949
if (link->getDemoMode() != 0) {
inJump = false;
return;
}
if (!inJump) {
if (link->mProcID == daAlink_c::PROC_CUT_JUMP) {
inJump = true;
+96
View File
@@ -0,0 +1,96 @@
#include "dusk/action_bindings.h"
#include "aurora/lib/input.hpp"
#include "dusk/settings.h"
#include "dusk/ui/ui.hpp"
namespace dusk {
static std::array<std::array<ActionBindPressData, static_cast<int>(ActionBinds::COUNT)>, PAD_CHANMAX> actionPressData{};
ActionBindsMap& getActionBinds() {
static ActionBindsMap actionBinds = {
{ActionBinds::FIRST_PERSON_CAMERA, {&getSettings().actionBindings.firstPersonCamera, "First Person Camera"}},
{ActionBinds::CALL_MIDNA, {&getSettings().actionBindings.callMidna, "Call Midna"}},
{ActionBinds::OPEN_DUSKLIGHT_MENU, {&getSettings().actionBindings.openDusklightMenu, "Open Dusklight Menu"}},
{ActionBinds::TURBO_SPEED_BUTTON, {&getSettings().actionBindings.turboSpeedButton, "Turbo Speed Button"}},
};
return actionBinds;
}
bool isActionBound(ActionBinds action, u32 port) {
auto& actionBinds = getActionBinds();
// Check to make sure action is properly bound
if (!actionBinds.contains(action)) {
return false;
}
return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID;
}
void updateActionBindings() {
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
// Move the current press to the previous frame
for (auto& pressData : actionPressData[port]) {
pressData.pressedPrevFrame = pressData.pressedCurFrame;
pressData.pressedCurFrame = false;
}
// Update current frame with whether action button is pressed
for (auto& [action, boundAction] : getActionBinds()) {
// If the action isn't bound, or if documents are visible and the action isn't
// opening the dusklight menu, don't update. Otherwise, we may accidentally
// perform actions while the dusklight menu is open.
if (!isActionBound(action, port) ||
(ui::any_document_visible() && action != ActionBinds::OPEN_DUSKLIGHT_MENU)) {
continue;
}
int button = boundAction.configVars->at(port);
// If keyboard is active for this port
u32 count = 0;
if (PADGetKeyButtonBindings(port, &count) != nullptr) {
int numKeys = 0;
const bool* kbState = SDL_GetKeyboardState(&numKeys);
if (kbState[button]) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
} else {
// If controller is active
auto controller = aurora::input::get_controller_for_player(port);
if (controller) {
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(button))) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
}
}
}
}
}
bool getActionBindTrig(ActionBinds action, u32 port) {
return isActionBound(action, port) &&
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
!actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
}
bool getActionBindHold(ActionBinds action, u32 port) {
return isActionBound(action, port) &&
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
}
bool getActionBindHoldAnyPort(ActionBinds action) {
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
if (getActionBindHold(action, port)) {
return true;
}
}
return false;
}
int getActionBindButton(ActionBinds action, u32 port) {
return (*getActionBinds()[action].configVars)[port];
}
}
+16 -5
View File
@@ -2,6 +2,7 @@
#include "dusk/ui/ui.hpp"
#include "imgui/ImGuiConsole.hpp"
bool shouldAutoSave = false;
u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
u8 mAutoSaveProc = 0;
int autoSaveWriteState = 0;
@@ -14,7 +15,7 @@ static AutoSaveFuncs AutoSaveFuncsProc[] = {
void noAutoSave() {}
void triggerAutoSave() {
if (dusk::getSettings().game.autoSave && mAutoSaveProc == 0 &&
if (dusk::getSettings().game.autoSave && shouldAutoSave && mAutoSaveProc == 0 &&
strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0)
{
mAutoSaveProc = 1;
@@ -25,8 +26,12 @@ void updateAutoSave() {
(AutoSaveFuncsProc[mAutoSaveProc])();
}
void writeAutoSave() {
int stageNo = dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo());
bool writeAutoSave() {
stage_stag_info_class* stagInfo = dComIfGp_getStageStagInfo();
if (stagInfo == nullptr) {
return false;
}
int stageNo = dStage_stagInfo_GetSaveTbl(stagInfo);
dComIfGs_putSave(stageNo);
dComIfGs_setMemoryToCard(mSaveBuffer, dComIfGs_getDataNum());
@@ -39,6 +44,7 @@ void writeAutoSave() {
}
g_mDoMemCd_control.save(mSaveBuffer, sizeof(mSaveBuffer), 0);
return true;
}
void autoSaving() {
@@ -47,8 +53,9 @@ void autoSaving() {
if (cardState == 2) {
mAutoSaveProc = 1;
} else if (cardState == 1) {
writeAutoSave();
mAutoSaveProc = 3;
if (writeAutoSave()) {
mAutoSaveProc = 3;
}
}
}
}
@@ -89,4 +96,8 @@ void endAutoSave() {
.duration = std::chrono::milliseconds(1500),
});
mAutoSaveProc = 0;
}
void toggleAutoSave(bool enabled) {
shouldAutoSave = enabled;
}
+17 -2
View File
@@ -11,6 +11,7 @@
#include <string>
#include "dusk/main.h"
#include "dusk/action_bindings.h"
using namespace dusk::config;
@@ -60,7 +61,7 @@ void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
template<ConfigValue T>
nlohmann::json ConfigImpl<T>::dumpToJson(const ConfigVar<T>& cVar) {
return cVar.getValue();
return cVar.getValueForSave();
}
template<ConfigValue T> requires std::is_integral_v<T> && std::is_signed_v<T>
@@ -248,7 +249,8 @@ void dusk::config::Save() {
json j;
for (const auto& pair : RegisteredConfigVars) {
if (pair.second->getLayer() == ConfigVarLayer::Value) {
const auto layer = pair.second->getLayer();
if (layer == ConfigVarLayer::Value || layer == ConfigVarLayer::Speedrun) {
j[pair.first] = pair.second->getImpl()->dumpToJson(*pair.second);
}
}
@@ -256,6 +258,13 @@ void dusk::config::Save() {
io::FileStream::WriteAllText(reinterpret_cast<const char*>(configJsonPath.c_str()), j.dump(4));
}
void dusk::config::ClearAllActionBindings(int port) {
for (auto& actionBinding : getActionBinds() | std::views::values) {
actionBinding.configVars->at(port).setValue(PAD_NATIVE_BUTTON_INVALID);
}
Save();
}
ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
const auto configVar = RegisteredConfigVars.find(name);
if (configVar != RegisteredConfigVars.end()) {
@@ -264,3 +273,9 @@ ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
return nullptr;
}
void dusk::config::EnumerateRegistered(std::function<void(ConfigVarBase&)> callback) {
for (auto& pair : RegisteredConfigVars) {
callback(*pair.second);
}
}
+67 -66
View File
@@ -4,7 +4,6 @@
#include "dusk/dusk.h"
#include "dusk/logging.h"
#include "dusk/main.h"
#include "dusk/settings.h"
#include "version.h"
#include <cstdlib>
@@ -13,114 +12,83 @@
#include <string_view>
#include <system_error>
#include "SDL3/SDL_filesystem.h"
#if DUSK_ENABLE_SENTRY_NATIVE
#include <sentry.h>
#endif
namespace dusk {
namespace dusk::crash_reporting {
namespace {
#if DUSK_ENABLE_SENTRY_NATIVE
bool g_sentryInitialized = false;
bool IsTruthy(std::string_view value) {
return value == "1" || value == "true" || value == "TRUE" || value == "yes"
|| value == "YES" || value == "on" || value == "ON";
bool truthy(std::string_view value) {
return value == "1" || value == "true" || value == "TRUE" || value == "yes" || value == "YES" ||
value == "on" || value == "ON";
}
std::string GetEnvOrEmpty(const char* name) {
std::string env_or_empty(const char* name) {
if (const char* value = std::getenv(name)) {
return value;
}
return {};
}
bool GetEffectiveEnabled() {
const std::string env = GetEnvOrEmpty("DUSK_SENTRY_ENABLED");
if (!env.empty()) {
return IsTruthy(env);
}
return getSettings().backend.enableCrashReporting;
bool disabled_by_env() {
const std::string env = env_or_empty("DUSK_SENTRY_ENABLED");
return !env.empty() && !truthy(env);
}
std::string GetEffectiveDsn() {
const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DSN");
std::string effective_dsn() {
const std::string env = env_or_empty("DUSK_SENTRY_DSN");
if (!env.empty()) {
return env;
}
return DUSK_SENTRY_DSN;
}
bool GetEffectiveDebug() {
const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DEBUG");
bool effective_debug() {
const std::string env = env_or_empty("DUSK_SENTRY_DEBUG");
if (!env.empty()) {
return IsTruthy(env);
return truthy(env);
}
return false;
}
std::string GetReleaseName() {
std::string release_name() {
return std::string(AppName) + "@" DUSK_WC_DESCRIBE;
}
std::filesystem::path GetSentryDatabasePath() {
return dusk::ConfigPath / "sentry";
std::filesystem::path sentry_database_path() {
return dusk::CachePath / "sentry";
}
std::filesystem::path GetLogAttachmentPath() {
std::filesystem::path log_attachment_path() {
if (const char* logPath = GetLogFilePath()) {
return logPath;
}
return {};
}
std::filesystem::path GetCrashpadHandlerPath() {
const char* basePath = SDL_GetBasePath();
if (!basePath) {
return {};
}
const std::filesystem::path handlerDir(basePath);
#if _WIN32
return handlerDir / "crashpad_handler.exe";
#else
return handlerDir / "crashpad_handler";
#endif
}
void ConfigurePathOptions(sentry_options_t* options) {
const auto databasePath = GetSentryDatabasePath();
void configure_path_options(sentry_options_t* options) {
const auto databasePath = sentry_database_path();
std::error_code ec;
std::filesystem::create_directories(databasePath, ec);
if (ec) {
DuskLog.warn("Unable to create Sentry database path '{}': {}",
databasePath.string(), ec.message());
DuskLog.warn(
"Unable to create Sentry database path '{}': {}", databasePath.string(), ec.message());
}
#if _WIN32
const std::wstring databasePathWide = databasePath.wstring();
sentry_options_set_database_pathw(options, databasePathWide.c_str());
const auto handlerPath = GetCrashpadHandlerPath();
if (!handlerPath.empty()) {
const std::wstring handlerPathWide = handlerPath.wstring();
sentry_options_set_handler_pathw(options, handlerPathWide.c_str());
}
#else
const std::string databasePathUtf8 = databasePath.string();
sentry_options_set_database_path(options, databasePathUtf8.c_str());
const auto handlerPath = GetCrashpadHandlerPath();
if (!handlerPath.empty()) {
const std::string handlerPathUtf8 = handlerPath.string();
sentry_options_set_handler_path(options, handlerPathUtf8.c_str());
}
#endif
const auto logPath = GetLogAttachmentPath();
const auto logPath = log_attachment_path();
if (!logPath.empty()) {
#if _WIN32
sentry_options_add_attachmentw(options, logPath.wstring().c_str());
@@ -133,32 +101,29 @@ void ConfigurePathOptions(sentry_options_t* options) {
} // namespace
void InitializeCrashReporting() {
void initialize() {
#if DUSK_ENABLE_SENTRY_NATIVE
if (g_sentryInitialized) {
if (g_sentryInitialized || disabled_by_env()) {
return;
}
if (!GetEffectiveEnabled()) {
return;
}
const std::string dsn = GetEffectiveDsn();
const std::string dsn = effective_dsn();
if (dsn.empty()) {
DuskLog.warn("Crash reporting is enabled but no Sentry DSN is configured");
return;
}
const std::string release = GetReleaseName();
const std::string release = release_name();
sentry_options_t* options = sentry_options_new();
sentry_options_set_dsn(options, dsn.c_str());
sentry_options_set_release(options, release.c_str());
sentry_options_set_environment(options, DUSK_SENTRY_ENVIRONMENT);
sentry_options_set_debug(options, GetEffectiveDebug() ? 1 : 0);
sentry_options_set_debug(options, effective_debug() ? 1 : 0);
sentry_options_set_require_user_consent(options, 1);
sentry_options_set_cache_keep(options, 1);
sentry_options_set_max_breadcrumbs(options, 100);
ConfigurePathOptions(options);
configure_path_options(options);
if (sentry_init(options) != 0) {
DuskLog.warn("Failed to initialize Sentry crash reporting");
@@ -173,7 +138,7 @@ void InitializeCrashReporting() {
#endif
}
void ShutdownCrashReporting() {
void shutdown() {
#if DUSK_ENABLE_SENTRY_NATIVE
if (!g_sentryInitialized) {
return;
@@ -184,4 +149,40 @@ void ShutdownCrashReporting() {
#endif
}
} // namespace dusk
Consent get_consent() {
#if DUSK_ENABLE_SENTRY_NATIVE
if (!g_sentryInitialized) {
return Consent::Unavailable;
}
switch (sentry_user_consent_get()) {
case SENTRY_USER_CONSENT_GIVEN:
return Consent::Given;
case SENTRY_USER_CONSENT_REVOKED:
return Consent::Revoked;
case SENTRY_USER_CONSENT_UNKNOWN:
default:
return Consent::Unknown;
}
#else
return Consent::Unavailable;
#endif
}
void set_consent(bool enabled) {
#if DUSK_ENABLE_SENTRY_NATIVE
if (!g_sentryInitialized) {
return;
}
if (enabled) {
sentry_user_consent_give();
} else {
sentry_user_consent_revoke();
}
#else
(void)enabled;
#endif
}
} // namespace dusk::crash_reporting
+1100
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <filesystem>
#include <string>
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if defined(_WIN32) || \
(defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST) || \
(defined(__linux__) && !defined(__ANDROID__))
#define DUSK_CAN_OPEN_DATA_FOLDER 1
#else
#define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif
#if (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || defined(__ANDROID__)
#define DUSK_CAN_CHANGE_DATA_FOLDER 0
#else
#define DUSK_CAN_CHANGE_DATA_FOLDER 1
#endif
namespace dusk::data {
struct Paths {
std::filesystem::path userPath;
std::filesystem::path cachePath;
};
Paths initialize_data();
std::filesystem::path configured_data_path();
std::filesystem::path cache_path();
bool open_data_path();
bool set_custom_data_path(const char* path, std::string* errorOut);
bool set_custom_data_path(const std::filesystem::path& path, std::string* errorOut);
bool set_portable_data_path();
bool reset_data_path();
bool is_default_data_path();
bool is_data_path_restart_pending();
} // namespace dusk::data
+1 -1
View File
@@ -81,7 +81,7 @@ void update_presence() {
rpc::Presence presence{};
presence.startTimestamp = g_startTime;
presence.largeImageKey = "icon";
presence.largeImageText = "Dusk";
presence.largeImageText = "Dusklight";
if (IsGameLaunched) {
const char* stageName = dComIfGp_getLastPlayStageName();
+124 -6
View File
@@ -5,6 +5,7 @@
#include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_stdinc.h>
#if defined(__ANDROID__) || defined(ANDROID)
@@ -16,6 +17,12 @@
#include <TargetConditionals.h>
#endif
#if defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST
#define USE_MACOS_FOLDER_DIALOG 1
#else
#define USE_MACOS_FOLDER_DIALOG 0
#endif
#if defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST
#define USE_IOS_DIALOG 1
#include "ios/FileSelectDialog.h"
@@ -23,6 +30,13 @@
#define USE_IOS_DIALOG 0
#endif
#if USE_MACOS_FOLDER_DIALOG
namespace dusk {
bool ShowMacOSFolderSelect(
FileCallback callback, void* userdata, SDL_Window* window, const char* default_location);
} // namespace dusk
#endif
namespace dusk {
namespace {
@@ -32,6 +46,10 @@ std::string fallback_display_name(std::string_view path) {
}
std::string pathString(path);
while (pathString.size() > 1 && (pathString.back() == '/' || pathString.back() == '\\')) {
pathString.pop_back();
}
const std::size_t slash = pathString.find_last_of("/\\");
if (slash == std::string::npos || slash + 1 >= pathString.size()) {
return pathString;
@@ -98,8 +116,7 @@ std::string android_display_name(std::string_view path) {
return {};
}
auto* displayName =
static_cast<jstring>(env->CallObjectMethod(activity, getDisplayName, uri));
auto* displayName = static_cast<jstring>(env->CallObjectMethod(activity, getDisplayName, uri));
env->DeleteLocalRef(uri);
env->DeleteLocalRef(activity);
if (displayName == nullptr || clear_pending_exception(env)) {
@@ -110,6 +127,76 @@ std::string android_display_name(std::string_view path) {
env->DeleteLocalRef(displayName);
return result;
}
struct AndroidFolderDialogState {
FileCallback callback;
void* userdata;
std::string path;
std::string error;
};
void onAndroidFolderDialogFinished(void* userdata) {
std::unique_ptr<AndroidFolderDialogState> state(
static_cast<AndroidFolderDialogState*>(userdata));
const char* path = state->path.empty() ? nullptr : state->path.c_str();
const char* error = state->error.empty() ? nullptr : state->error.c_str();
state->callback(state->userdata, path, error);
}
bool show_android_folder_select(AndroidFolderDialogState* state) {
auto* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
if (env == nullptr) {
return false;
}
jobject activity = static_cast<jobject>(SDL_GetAndroidActivity());
if (activity == nullptr || clear_pending_exception(env)) {
if (activity != nullptr) {
env->DeleteLocalRef(activity);
}
return false;
}
jclass activityClass = env->GetObjectClass(activity);
if (activityClass == nullptr || clear_pending_exception(env)) {
env->DeleteLocalRef(activity);
return false;
}
jmethodID showFolderDialog =
env->GetMethodID(activityClass, "showFolderDialog", "(J)Z");
env->DeleteLocalRef(activityClass);
if (showFolderDialog == nullptr || clear_pending_exception(env)) {
env->DeleteLocalRef(activity);
return false;
}
const jboolean shown = env->CallBooleanMethod(
activity, showFolderDialog, reinterpret_cast<jlong>(state));
env->DeleteLocalRef(activity);
if (clear_pending_exception(env)) {
return false;
}
return shown == JNI_TRUE;
}
extern "C" JNIEXPORT void JNICALL
Java_dev_twilitrealm_dusk_DuskActivity_nativeFolderDialogResult(
JNIEnv* env, jclass, jlong userdata, jstring path, jstring error) {
auto* state = reinterpret_cast<AndroidFolderDialogState*>(userdata);
if (state == nullptr) {
return;
}
state->path = to_string(env, path);
state->error = to_string(env, error);
if (!SDL_RunOnMainThread(&onAndroidFolderDialogFinished, state, false)) {
onAndroidFolderDialogFinished(state);
}
}
#endif
#if USE_IOS_DIALOG
@@ -159,8 +246,8 @@ void onSDLDialogFinished(void* userdata, const char* const* filelist, [[maybe_un
} // namespace
void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
const SDL_DialogFileFilter* filters, int nfilters, const char* default_location,
bool allow_many) {
const SDL_DialogFileFilter* filters, int nfilters, const char* default_location,
bool allow_many) {
if (callback == nullptr) {
return;
}
@@ -171,14 +258,45 @@ void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
state->userdata = userdata;
Dusk_iOS_ShowFileSelect(&onIOSDialogFinished, state.release(), window, filters, nfilters,
default_location, allow_many);
default_location, allow_many);
#else
auto state = std::make_unique<SDLDialogCallbackState>();
state->callback = callback;
state->userdata = userdata;
SDL_ShowOpenFileDialog(&onSDLDialogFinished, state.release(), window, filters, nfilters,
default_location, allow_many);
default_location, allow_many);
#endif
}
void ShowFolderSelect(
FileCallback callback, void* userdata, SDL_Window* window, const char* default_location) {
if (callback == nullptr) {
return;
}
#if USE_IOS_DIALOG
callback(userdata, nullptr, "Folder selection is not supported on this platform");
#elif USE_MACOS_FOLDER_DIALOG
ShowMacOSFolderSelect(callback, userdata, window, default_location);
#elif defined(__ANDROID__) || defined(ANDROID)
auto state = std::make_unique<AndroidFolderDialogState>();
state->callback = callback;
state->userdata = userdata;
if (show_android_folder_select(state.get())) {
state.release();
return;
}
callback(userdata, nullptr, "Folder selection is not supported on this platform");
#else
auto state = std::make_unique<SDLDialogCallbackState>();
state->callback = callback;
state->userdata = userdata;
SDL_ShowOpenFolderDialog(
&onSDLDialogFinished, state.release(), window, default_location, false);
#endif
}
+2
View File
@@ -14,6 +14,8 @@ using FileCallback = void (*)(void* userdata, const char* path, const char* erro
void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
const SDL_DialogFileFilter* filters, int nfilters, const char* default_location,
bool allow_many);
void ShowFolderSelect(
FileCallback callback, void* userdata, SDL_Window* window, const char* default_location);
std::string display_name_for_path(std::string_view path);
+102
View File
@@ -0,0 +1,102 @@
#include "file_select.hpp"
#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_video.h>
#import <AppKit/AppKit.h>
namespace dusk {
namespace {
struct MacOSFolderDialogState {
FileCallback callback;
void* userdata;
};
void finish_folder_dialog(MacOSFolderDialogState* state, NSURL* url, const char* error) {
if (state == nullptr) {
return;
}
if (error != nullptr) {
state->callback(state->userdata, nullptr, error);
delete state;
return;
}
if (url == nil) {
state->callback(state->userdata, nullptr, nullptr);
delete state;
return;
}
state->callback(state->userdata, [[url path] UTF8String], nullptr);
delete state;
}
void configure_default_location(NSOpenPanel* panel, const char* defaultLocation) {
if (panel == nil || defaultLocation == nullptr || defaultLocation[0] == '\0') {
return;
}
NSString* path = [NSString stringWithUTF8String:defaultLocation];
if (path == nil) {
return;
}
BOOL isDirectory = NO;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* url = [NSURL fileURLWithPath:path];
if ([fileManager fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory) {
[panel setDirectoryURL:url];
} else {
[panel setDirectoryURL:[url URLByDeletingLastPathComponent]];
}
}
NSWindow* window_for_sdl_window(SDL_Window* window) {
if (window == nullptr) {
return nil;
}
auto props = SDL_GetWindowProperties(window);
return (__bridge NSWindow*)SDL_GetPointerProperty(
props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
}
} // namespace
bool ShowMacOSFolderSelect(
FileCallback callback, void* userdata, SDL_Window* window, const char* defaultLocation) {
if (callback == nullptr) {
return false;
}
auto* state = new MacOSFolderDialogState{
.callback = callback,
.userdata = userdata,
};
NSOpenPanel* panel = [NSOpenPanel openPanel];
[panel setCanChooseFiles:NO];
[panel setCanChooseDirectories:YES];
[panel setAllowsMultipleSelection:NO];
[panel setCanCreateDirectories:YES];
configure_default_location(panel, defaultLocation);
NSWindow* modalWindow = window_for_sdl_window(window);
if (modalWindow != nil) {
[panel beginSheetModalForWindow:modalWindow
completionHandler:^(NSModalResponse result) {
finish_folder_dialog(
state, result == NSModalResponseOK ? [panel URL] : nil, nullptr);
}];
return true;
}
const NSModalResponse result = [panel runModal];
finish_folder_dialog(state, result == NSModalResponseOK ? [panel URL] : nil, nullptr);
return true;
}
} // namespace dusk
+142
View File
@@ -0,0 +1,142 @@
#include "imgui.h"
#include "ImGuiMenuTools.hpp"
#include "d/actor/d_a_alink.h"
#include "d/d_com_inf_game.h"
#include "f_op/f_op_actor_mng.h"
#include "SSystem/SComponent/c_sxyz.h"
#include "SSystem/SComponent/c_xyz.h"
namespace dusk {
namespace {
struct ActorSpawnerState {
int actorId = 0;
int params = -1;
int argument = -1;
int angleX = 0;
int angleY = 0;
int angleZ = 0;
float scaleX = 1.0f;
float scaleY = 1.0f;
float scaleZ = 1.0f;
bool usePlayerRoom = true;
int manualRoom = 0;
int spawnCount = 1;
bool hasResult = false;
unsigned int lastResult = 0;
int lastAttempted = 0;
};
ActorSpawnerState s_state;
} // namespace
void ImGuiMenuTools::ShowActorSpawner() {
if (!m_showActorSpawner) {
return;
}
if (!ImGui::Begin("Actor Spawner", &m_showActorSpawner)) {
ImGui::End();
return;
}
daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0);
ImGui::SeparatorText("Actor");
ImGui::InputInt("Actor ID", &s_state.actorId);
ImGui::InputInt("Params (hex)", &s_state.params, 0, 0, ImGuiInputTextFlags_CharsHexadecimal);
ImGui::InputInt("Argument", &s_state.argument);
s_state.argument = (s_state.argument < -128) ? -128 : (s_state.argument > 127) ? 127 : s_state.argument;
ImGui::SeparatorText("Angle");
ImGui::InputInt("Angle X", &s_state.angleX);
ImGui::InputInt("Angle Y", &s_state.angleY);
ImGui::InputInt("Angle Z", &s_state.angleZ);
ImGui::SeparatorText("Scale");
ImGui::InputFloat("Scale X", &s_state.scaleX, 0.1f, 1.0f);
ImGui::InputFloat("Scale Y", &s_state.scaleY, 0.1f, 1.0f);
ImGui::InputFloat("Scale Z", &s_state.scaleZ, 0.1f, 1.0f);
ImGui::SeparatorText("Spawn");
ImGui::InputInt("Count", &s_state.spawnCount);
if (s_state.spawnCount < 1) {
s_state.spawnCount = 1;
}
ImGui::SeparatorText("Position");
ImGui::Checkbox("Use player room", &s_state.usePlayerRoom);
if (!s_state.usePlayerRoom) {
ImGui::InputInt("Room No", &s_state.manualRoom);
}
if (player != nullptr) {
ImGui::Text("Spawn pos: %.2f, %.2f, %.2f",
player->current.pos.x, player->current.pos.y, player->current.pos.z);
} else {
ImGui::TextDisabled("Player not available");
}
ImGui::Separator();
bool canSpawn = player != nullptr;
if (!canSpawn) {
ImGui::BeginDisabled();
}
if (ImGui::Button("Spawn", ImVec2(-1, 0))) {
cXyz pos = player->current.pos;
csXyz angle;
angle.set((s16)s_state.angleX, (s16)s_state.angleY, (s16)s_state.angleZ);
cXyz scale(s_state.scaleX, s_state.scaleY, s_state.scaleZ);
int roomNo = s_state.usePlayerRoom ? player->current.roomNo : s_state.manualRoom;
layer_class* savedLayer = fpcLy_CurrentLayer();
base_process_class* playScene = fpcM_SearchByName(fpcNm_PLAY_SCENE_e);
if (playScene != nullptr) {
fpcLy_SetCurrentLayer(&((process_node_class*)playScene)->layer);
}
s_state.lastResult = 0;
s_state.lastAttempted = s_state.spawnCount;
for (int i = 0; i < s_state.spawnCount; ++i) {
unsigned int result = fopAcM_create(
(s16)s_state.actorId,
(u32)s_state.params,
&pos,
roomNo,
&angle,
&scale,
(s8)s_state.argument
);
if (result != 0) {
s_state.lastResult = result;
}
}
s_state.hasResult = true;
fpcLy_SetCurrentLayer(savedLayer);
}
if (!canSpawn) {
ImGui::EndDisabled();
}
if (s_state.hasResult) {
if (s_state.lastResult != 0) {
if (s_state.lastAttempted == 1) {
ImGui::Text("Spawned: proc ID %u", s_state.lastResult);
} else {
ImGui::Text("Spawned %d (last proc ID %u)", s_state.lastAttempted, s_state.lastResult);
}
} else {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "Spawn failed (returned 0)");
}
}
ImGui::End();
}
} // namespace dusk
+17 -16
View File
@@ -13,8 +13,10 @@
#include "ImGuiEngine.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "SDL3/SDL_mouse.h"
#include "dusk/action_bindings.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/config.hpp"
#include "dusk/data.hpp"
#include "dusk/dusk.h"
#include "dusk/frame_interpolation.h"
#include "dusk/livesplit.h"
@@ -238,7 +240,8 @@ namespace dusk {
}
void ImGuiConsole::UpdateSettings() {
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab);
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind &&
(ImGui::IsKeyDown(ImGuiKey_Tab) || getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON));
if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) {
getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive;
@@ -259,6 +262,12 @@ namespace dusk {
config::Save();
}
if (getSettings().game.enableResetKeybind && ImGui::GetIO().KeyCtrl &&
ImGui::IsKeyPressed(ImGuiKey_R) && !fpcM_SearchByName(fpcNm_LOGO_SCENE_e))
{
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
}
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
if (getSettings().backend.enableAdvancedSettings) {
m_isHidden = !m_isHidden;
@@ -273,7 +282,6 @@ namespace dusk {
// so make the window bg fully transparent temporarily
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
if (showMenu && ImGui::BeginMainMenuBar()) {
m_menuGame.draw();
m_menuTools.draw();
ImGui::EndMainMenuBar();
@@ -282,7 +290,7 @@ namespace dusk {
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
m_isLaunchInitialized = true;
if (getSettings().game.liveSplitEnabled) {
if (getSettings().game.speedrunMode && getSettings().game.liveSplitEnabled) {
dusk::speedrun::connectLiveSplit();
}
}
@@ -309,11 +317,13 @@ namespace dusk {
ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize});
} else {
ImGui::PushFont(ImGuiEngine::fontExtraLarge);
ImGuiTextCenter("Dusk");
ImGuiTextCenter("Dusklight");
ImGui::PopFont();
}
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGuiTextCenter("Failed to initialize any graphics backend");
ImGuiTextCenter("Failed to initialize any graphics backend.");
ImGuiTextCenter("\nYour system may be misconfigured, or your hardware may not support the required versions of any of the available backends.");
ImGuiTextCenter("\nA clean reinstall of Dusklight may help. For further assistance, please visit #tech-support on the Twilit Realm Discord server.");
const auto& style = ImGui::GetStyle();
const auto retrySize = ImGui::CalcTextSize("Retry (Auto backend)");
const auto quitSize = ImGui::CalcTextSize("Quit");
@@ -339,7 +349,7 @@ namespace dusk {
}
#if DUSK_CAN_OPEN_DATA_FOLDER
if (ImGui::Button("Open Data Folder")) {
OpenDataFolder();
data::open_data_path();
}
ImGui::SameLine();
#endif
@@ -351,15 +361,6 @@ namespace dusk {
}
m_menuTools.ShowInputViewer();
m_menuGame.drawSpeedrunTimerOverlay();
if (getSettings().game.liveSplitEnabled) {
dusk::speedrun::updateLiveSplit();
if (dusk::speedrun::consumeConnectedEvent())
AddToast("LiveSplit connected");
else if (dusk::speedrun::consumeDisconnectedEvent())
AddToast("LiveSplit disconnected");
}
if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) {
m_menuTools.ShowDebugOverlay();
@@ -367,12 +368,12 @@ namespace dusk {
m_menuTools.ShowProcessManager();
m_menuTools.ShowHeapOverlay();
m_menuTools.ShowStubLog();
m_menuTools.ShowMapLoader();
m_menuTools.ShowBloomWindow();
m_menuTools.ShowPlayerInfo();
m_menuTools.ShowAudioDebug();
m_menuTools.ShowSaveEditor();
m_menuTools.ShowStateShare();
m_menuTools.ShowActorSpawner();
}
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
-3
View File
@@ -7,7 +7,6 @@
#include <aurora/aurora.h>
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuTools.hpp"
#include "dusk/main.h"
#include "imgui.h"
@@ -44,8 +43,6 @@ private:
ImVec2 m_dragScrollLastMousePos = {};
std::deque<Toast> m_toasts;
ImGuiMenuGame m_menuGame;
// Keep always last
ImGuiMenuTools m_menuTools;
+5 -4
View File
@@ -3,12 +3,13 @@
#include "imgui.h"
#include <imgui_internal.h>
#include "ImGuiConsole.hpp"
#include "dusk/settings.h"
#include <dolphin/pad.h>
namespace dusk {
void ImGuiMenuTools::ShowInputViewer() {
if (!m_showInputViewer) {
if (!getSettings().game.showInputViewer) {
return;
}
@@ -259,10 +260,10 @@ namespace dusk {
size.y = 130 * scale;
ImGui::Dummy(size);
if (PADHasSensor(PAD_1, PAD_SENSOR_GYRO) == TRUE) {
if (getSettings().game.showInputViewerGyro)
{
ImGui::Separator();
ImGui::Checkbox("Gyro Values", &m_showInputViewerGyro);
if (m_showInputViewerGyro) {
{
ImGui::TextUnformatted("Gyro");
constexpr float kBarScale = 4.0f;
+1 -1
View File
@@ -219,7 +219,7 @@ void ImGuiEngine_AddTextures() {
ImGuiEngine::orgIcon = AddTexture("org-icon.png");
}
if (ImGuiEngine::duskLogo == 0) {
ImGuiEngine::duskLogo = AddTexture("logo-mascot.png");
ImGuiEngine::duskLogo = AddTexture("logo.png");
}
}
} // namespace dusk
-149
View File
@@ -1,149 +0,0 @@
#include "d/d_com_inf_game.h"
#include "imgui.h"
#include <imgui_internal.h>
#include "ImGuiConsole.hpp"
#include "ImGuiMenuTools.hpp"
#include "dusk/map_loader_definitions.h"
#include "fmt/format.h"
namespace dusk {
void ImGuiMenuTools::ShowMapLoader() {
if (!getSettings().backend.enableAdvancedSettings ||
!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F7, m_showMapLoader))
{
return;
}
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
// ImGui::SetNextWindowBgAlpha(0.65f);
if (!ImGui::Begin("Map Loader", &m_showMapLoader, windowFlags)) {
ImGui::End();
return;
}
ImGui::Checkbox("Show Internal Names", &m_mapLoaderInfo.showInternalNames);
const char* previewRegion = "None";
if (m_mapLoaderInfo.regionIdx != -1) {
previewRegion = gameRegions[m_mapLoaderInfo.regionIdx].regionName;
}
if (ImGui::BeginCombo("Select Region", previewRegion)) {
int idx = 0;
for (const auto& region : gameRegions) {
if (ImGui::Selectable(region.regionName)) {
if (m_mapLoaderInfo.regionIdx != idx) {
m_mapLoaderInfo.mapIdx = 0;
m_mapLoaderInfo.roomNoIdx = 0;
m_mapLoaderInfo.pointNoIdx = 0;
}
m_mapLoaderInfo.regionIdx = idx;
}
idx++;
}
ImGui::EndCombo();
}
if (m_mapLoaderInfo.regionIdx != -1) {
const auto& region = gameRegions[m_mapLoaderInfo.regionIdx];
std::string previewMap = "None";
if (m_mapLoaderInfo.mapIdx != -1) {
const auto& map = region.maps[m_mapLoaderInfo.mapIdx];
previewMap = m_mapLoaderInfo.showInternalNames ? fmt::format("{} ({})", map.mapName, map.mapFile) : map.mapName;
}
if (ImGui::BeginCombo("Select Map", previewMap.data())) {
int prevMapIdx = m_mapLoaderInfo.mapIdx;
for (int i = 0; i < region.maps.size(); ++i) {
const auto& map = region.maps[i];
std::string label = m_mapLoaderInfo.showInternalNames ? fmt::format("{} ({})", map.mapName, map.mapFile) : map.mapName;
if (ImGui::Selectable(label.data())) {
m_mapLoaderInfo.mapIdx = i;
}
}
ImGui::EndCombo();
if (m_mapLoaderInfo.mapIdx != prevMapIdx) {
m_mapLoaderInfo.roomNoIdx = 0;
m_mapLoaderInfo.pointNoIdx = 0;
}
}
} else {
ImGui::Text("No region selected.");
}
if (m_mapLoaderInfo.regionIdx != -1 && m_mapLoaderInfo.mapIdx != -1) {
const auto& region = gameRegions[m_mapLoaderInfo.regionIdx];
const auto& map = region.maps[m_mapLoaderInfo.mapIdx];
const auto& room = map.mapRooms[m_mapLoaderInfo.roomNoIdx];
if (map.mapRooms.size() > 1) {
ImGui::Text("Selected Room: %2d", room.roomNo);
ImGui::SameLine();
if (ImGui::Button("-###RoomNoIdxDec")) {
m_mapLoaderInfo.roomNoIdx--;
if (m_mapLoaderInfo.roomNoIdx < 0) {
m_mapLoaderInfo.roomNoIdx = map.mapRooms.size() - 1;
}
m_mapLoaderInfo.pointNoIdx = 0;
}
ImGui::SameLine();
if (ImGui::Button("+###RoomNoIdxInc")) {
m_mapLoaderInfo.roomNoIdx++;
if (m_mapLoaderInfo.roomNoIdx >= map.mapRooms.size()) {
m_mapLoaderInfo.roomNoIdx = 0;
}
m_mapLoaderInfo.pointNoIdx = 0;
}
}
constexpr int MAX_LAYER = 14;
ImGui::Text("Selected Layer: %3d", m_mapLoaderInfo.layer);
ImGui::SameLine();
if (ImGui::Button("-###layerDec")) {
m_mapLoaderInfo.layer--;
if (m_mapLoaderInfo.layer < -1) {
m_mapLoaderInfo.layer = MAX_LAYER;
}
}
ImGui::SameLine();
if (ImGui::Button("+###layerInc")) {
m_mapLoaderInfo.layer++;
if (m_mapLoaderInfo.layer > MAX_LAYER) {
m_mapLoaderInfo.layer = -1;
}
}
if (room.roomPoints.size() > 1) {
ImGui::Text("Selected Point: %3d", room.roomPoints[m_mapLoaderInfo.pointNoIdx]);
ImGui::SameLine();
if (ImGui::Button("-###PointNoIdxDec")) {
m_mapLoaderInfo.pointNoIdx--;
if (m_mapLoaderInfo.pointNoIdx < 0) {
m_mapLoaderInfo.pointNoIdx = room.roomPoints.size() - 1;
}
}
ImGui::SameLine();
if (ImGui::Button("+###PointNoIdxInc")) {
m_mapLoaderInfo.pointNoIdx++;
if (m_mapLoaderInfo.pointNoIdx >= room.roomPoints.size()) {
m_mapLoaderInfo.pointNoIdx = 0;
}
}
}
if (ImGui::Button("Warp")) {
dComIfGp_setNextStage(map.mapFile, room.roomPoints[m_mapLoaderInfo.pointNoIdx], room.roomNo, m_mapLoaderInfo.layer);
}
}
ImGui::End();
}
} // namespace dusk

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