Compare commits

...

89 Commits

Author SHA1 Message Date
MelonSpeedruns b6f1fbf074 Magic Armor Cheat Refactor 2026-05-13 13:53:05 -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
Luke Street 04b5861f29 Update CI (again x2) 2026-05-10 00:12:11 -06:00
Luke Street 453e958068 Update CI (again) 2026-05-10 00:11:05 -06:00
Luke Street e7d2fbcc0b Update CI 2026-05-10 00:08:42 -06:00
Krutonium 8f71c70d14 fix io.hpp to enable compiling on GCC15 (#790) 2026-05-09 23:41:15 -06:00
Luke Street df23edcb69 Add iOS UIFileSharingEnabled integration 2026-05-09 21:42:43 -06:00
Luke Street daff157027 Fix change notifications in Android DocumentsProvider 2026-05-09 21:42:32 -06:00
Luke Street 0c23bd4332 Add "Open Data Folder" to Interface menu 2026-05-09 20:57:34 -06:00
Luke Street 7562486449 Log disc verification status 2026-05-09 20:40:59 -06:00
Luke Street 5e08b810fc Update aurora 2026-05-09 20:26:37 -06:00
Pieter-Jan Briers c66cccf660 Fix handling of Unicode paths on Windows (#767)
I love C++
2026-05-09 20:24:50 -06:00
Luke Street 3b1118229b Add Android DocumentsProvider 2026-05-09 20:23:11 -06:00
TakaRikka 491da372a1 Merge pull request #764 from TwilitRealm/gyro-axis-fix
Fix Gyro Sensitivity Axes
2026-05-09 17:47:59 -07:00
SuperDude88 a2f463d146 Fix Gyro Sensitivity Axes
Fixes #759
2026-05-09 20:15:33 -04:00
TakaRikka 63b3ce4849 temp fix for speedrun timer controls 2026-05-09 15:16:54 -07:00
133 changed files with 7593 additions and 1136 deletions
+86 -26
View File
@@ -8,7 +8,7 @@ on:
pull_request: pull_request:
env: env:
# SCCACHE_GHA_ENABLED: "true" SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache" RUSTC_WRAPPER: "sccache"
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
@@ -22,7 +22,7 @@ jobs:
matrix: matrix:
include: include:
- name: GCC x86_64 - name: GCC x86_64
runner: [self-hosted, Linux] runner: ubuntu-latest
preset: gcc preset: gcc
artifact_arch: x86_64 artifact_arch: x86_64
# - name: GCC aarch64 # - name: GCC aarch64
@@ -41,7 +41,6 @@ jobs:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
if: 'false' # disabled for self-hosted
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \ sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \
@@ -51,8 +50,7 @@ jobs:
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
- name: Setup sccache - name: Setup sccache
if: 'false' # disabled for self-hosted uses: mozilla-actions/sccache-action@v0.0.10
uses: mozilla-actions/sccache-action@v0.0.9
- name: Print sccache stats - name: Print sccache stats
run: sccache --show-stats run: sccache --show-stats
@@ -67,17 +65,16 @@ jobs:
run: ci/build-appimage.sh run: ci/build-appimage.sh
- name: Upload artifacts - name: Upload artifacts
if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}} name: dusklight-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
path: | path: |
build/install/Dusk-*.AppImage build/install/Dusklight-*.AppImage
build/install/debug.tar.* build/install/debug.tar.*
build-apple: build-apple:
name: Build Apple (${{matrix.name}}) name: Build Apple (${{matrix.name}})
runs-on: [self-hosted, macOS] runs-on: macos-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -86,14 +83,14 @@ jobs:
platform: macos platform: macos
preset: x-macos-ci-arm64 preset: x-macos-ci-arm64
artifact_name: macos-appleclang-arm64 artifact_name: macos-appleclang-arm64
# - name: AppleClang macOS x86_64 - name: AppleClang macOS x86_64
# platform: macos platform: macos
# preset: x-macos-ci-x86_64 preset: x-macos-ci-x86_64
# artifact_name: macos-appleclang-x86_64 artifact_name: macos-appleclang-x86_64
# - name: AppleClang iOS arm64 - name: AppleClang iOS arm64
# platform: ios platform: ios
# preset: x-ios-ci preset: x-ios-ci
# artifact_name: ios-appleclang-arm64 artifact_name: ios-appleclang-arm64
# - name: AppleClang tvOS arm64 # - name: AppleClang tvOS arm64
# platform: tvos # platform: tvos
# preset: x-tvos-ci # preset: x-tvos-ci
@@ -106,7 +103,6 @@ jobs:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
if: 'false'
run: brew install cmake ninja run: brew install cmake ninja
- name: Install Rust iOS target - name: Install Rust iOS target
@@ -128,7 +124,7 @@ jobs:
rustup target add x86_64-apple-darwin rustup target add x86_64-apple-darwin
- name: Setup sccache - name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9 uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake - name: Configure CMake
run: cmake --preset ${{matrix.preset}} run: cmake --preset ${{matrix.preset}}
@@ -137,14 +133,80 @@ jobs:
run: cmake --build --preset ${{matrix.preset}} run: cmake --build --preset ${{matrix.preset}}
- name: Upload artifacts - name: Upload artifacts
if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}} name: dusklight-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
path: | path: |
build/install/Dusk.app build/install/Dusklight.app
build/install/debug.tar.* build/install/debug.tar.*
build-android:
name: Build Android (${{matrix.name}})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: Clang arm64-v8a
preset: x-android-ci-arm64
abi: arm64-v8a
artifact_arch: arm64
rust_target: aarch64-linux-android
env:
ANDROID_NDK_VERSION: "29.0.14206865"
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get -y install ninja-build
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17
- name: Setup Android SDK
uses: android-actions/setup-android@v4
- name: Install Android SDK packages
run: sdkmanager "platforms;android-36" "build-tools;36.1.0" "ndk;${ANDROID_NDK_VERSION}"
- name: Install Rust Android target
run: |
rustup toolchain install stable
rustup target add ${{matrix.rust_target}}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake
run: cmake --preset ${{matrix.preset}}
- name: Build native library
run: cmake --build --preset ${{matrix.preset}} --target dusklight
- name: Stage stripped JNI library
run: ANDROID_STAGE_ABIS="${{matrix.abi}}" platforms/android/scripts/stage-jni-libs.sh
- name: Build APK
working-directory: platforms/android
run: ./gradlew :app:assembleRelease --rerun-tasks
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: dusklight-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}}
path: platforms/android/app/build/outputs/apk/release/app-${{matrix.abi}}-release-unsigned.apk
build-windows: build-windows:
name: Build Windows (${{matrix.name}}) name: Build Windows (${{matrix.name}})
runs-on: ${{matrix.runner}} runs-on: ${{matrix.runner}}
@@ -154,7 +216,7 @@ jobs:
matrix: matrix:
include: include:
- name: MSVC x86_64 - name: MSVC x86_64
runner: [self-hosted, Windows] runner: windows-latest
preset: msvc preset: msvc
msvc_arch: amd64 msvc_arch: amd64
vcpkg_arch: x64 vcpkg_arch: x64
@@ -191,7 +253,6 @@ jobs:
uses: mozilla-actions/sccache-action@v0.0.9 uses: mozilla-actions/sccache-action@v0.0.9
- name: Install dependencies - name: Install dependencies
if: 'false' # disabled for self-hosted
run: | run: |
choco install ninja choco install ninja
vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static
@@ -203,10 +264,9 @@ jobs:
run: cmake --build --preset x-windows-ci-${{matrix.preset}} run: cmake --build --preset x-windows-ci-${{matrix.preset}}
- name: Upload artifacts - name: Upload artifacts
if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}} name: dusklight-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
path: | path: |
build/install/*.exe build/install/*.exe
build/install/*.dll build/install/*.dll
+4
View File
@@ -41,6 +41,10 @@ compile_commands.json
# MacOS # MacOS
.DS_Store .DS_Store
# direnv / nix
.direnv/
.envrc
# ISOs # ISOs
*.iso *.iso
+1 -1
View File
@@ -2,7 +2,7 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "(gdb) Launch Dusk MSVC", "name": "(gdb) Launch Dusklight MSVC",
"type": "cppvsdbg", "type": "cppvsdbg",
"request": "launch", "request": "launch",
"program": "${command:cmake.launchTargetPath}", "program": "${command:cmake.launchTargetPath}",
+1 -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.generator": "Ninja",
"cmake.configureSettings": { "cmake.configureSettings": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON" "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
+87 -63
View File
@@ -5,8 +5,19 @@ if (NOT CMAKE_BUILD_TYPE)
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE) "Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
endif () endif ()
# obtain revision info from git set(DUSK_VERSION_OVERRIDE "" CACHE STRING "Override version string (skips git detection and format validation)")
find_package(Git)
if (DUSK_VERSION_OVERRIDE)
set(DUSK_WC_DESCRIBE "${DUSK_VERSION_OVERRIDE}")
set(DUSK_VERSION_STRING "0.0.0.0")
set(DUSK_SHORT_VERSION_STRING "0.0.0")
set(DUSK_WC_REVISION "")
set(DUSK_WC_BRANCH "")
set(DUSK_WC_DATE "")
message(STATUS "Dusklight version overridden to ${DUSK_WC_DESCRIBE}")
else ()
# obtain revision info from git
find_package(Git)
if (GIT_FOUND) if (GIT_FOUND)
# make sure version information gets re-run when the current Git HEAD changes # make sure version information gets re-run when the current Git HEAD changes
execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD
@@ -63,13 +74,15 @@ else ()
set(DUSK_SHORT_VERSION_STRING "0.0.0") set(DUSK_SHORT_VERSION_STRING "0.0.0")
endif () endif ()
endif ()
# Add version information to CI environment variables # Add version information to CI environment variables
if(DEFINED ENV{GITHUB_ENV}) if(DEFINED ENV{GITHUB_ENV})
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n") file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n")
endif() endif()
message(STATUS "Dusk version set to ${DUSK_WC_DESCRIBE}") message(STATUS "Dusklight version set to ${DUSK_WC_DESCRIBE}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
project(dusk LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING}) project(dusklight LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
if (APPLE) if (APPLE)
enable_language(OBJC OBJCXX) enable_language(OBJC OBJCXX)
endif () endif ()
@@ -112,21 +125,26 @@ option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)")
option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.") option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.")
option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON) option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON)
option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON) option(DUSK_ENABLE_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) option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN") set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment") set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
# Edit & Continue
if (MSVC)
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
endif ()
if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
add_link_options("/INCREMENTAL")
endif ()
endif ()
if (DUSK_MOVIE_SUPPORT) if (DUSK_MOVIE_SUPPORT)
find_package(libjpeg-turbo 3.0 CONFIG QUIET) find_package(libjpeg-turbo 3.0 CONFIG QUIET)
if (libjpeg-turbo_FOUND) if (libjpeg-turbo_FOUND)
message(STATUS "dusk: Using system libjpeg-turbo") message(STATUS "dusklight: Using system libjpeg-turbo")
else () else ()
message(STATUS "dusk: Fetching libjpeg-turbo") message(STATUS "dusklight: Fetching libjpeg-turbo")
include(ExternalProject) include(ExternalProject)
set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install) set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install)
if (WIN32) if (WIN32)
@@ -145,11 +163,14 @@ if (DUSK_MOVIE_SUPPORT)
list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file}) list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file})
endif () endif ()
set(_jpeg_passthrough_vars set(_jpeg_passthrough_vars
ANDROID_ABI
ANDROID_PLATFORM
CMAKE_BUILD_TYPE CMAKE_BUILD_TYPE
CMAKE_C_COMPILER CMAKE_C_COMPILER
CMAKE_C_COMPILER_LAUNCHER CMAKE_C_COMPILER_LAUNCHER
CMAKE_MAKE_PROGRAM CMAKE_MAKE_PROGRAM
CMAKE_MSVC_RUNTIME_LIBRARY CMAKE_MSVC_RUNTIME_LIBRARY
CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
CMAKE_OSX_ARCHITECTURES CMAKE_OSX_ARCHITECTURES
DEPLOYMENT_TARGET DEPLOYMENT_TARGET
ENABLE_ARC ENABLE_ARC
@@ -215,13 +236,13 @@ endif ()
include(FetchContent) include(FetchContent)
# Declare all dependencies first so CMake can download them in parallel # Declare all dependencies first so CMake can download them in parallel
message(STATUS "dusk: Fetching cxxopts") message(STATUS "dusklight: Fetching cxxopts")
FetchContent_Declare(cxxopts FetchContent_Declare(cxxopts
URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz
URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072 URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072
DOWNLOAD_EXTRACT_TIMESTAMP TRUE DOWNLOAD_EXTRACT_TIMESTAMP TRUE
) )
message(STATUS "dusk: Fetching nlohmann/json") message(STATUS "dusklight: Fetching nlohmann/json")
FetchContent_Declare(json FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
@@ -230,7 +251,7 @@ FetchContent_Declare(json
FetchContent_MakeAvailable(cxxopts json) FetchContent_MakeAvailable(cxxopts json)
if (DUSK_ENABLE_SENTRY_NATIVE) if (DUSK_ENABLE_SENTRY_NATIVE)
message(STATUS "dusk: Fetching sentry-native") message(STATUS "dusklight: Fetching sentry-native")
set(SENTRY_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(SENTRY_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE) set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE)
if (WIN32) if (WIN32)
@@ -275,15 +296,15 @@ include(files.cmake)
# TODO: version handling for res includes # TODO: version handling for res includes
set(DUSK_BUNDLE_NAME Dusk) set(DUSK_BUNDLE_NAME Dusklight)
set(DUSK_BUNDLE_IDENTIFIER dev.twilitrealm.dusk) set(DUSK_BUNDLE_IDENTIFIER dev.twilitrealm.dusk)
set(DUSK_COMPANY_NAME "Twilit Realm") set(DUSK_COMPANY_NAME "Twilit Realm")
set(DUSK_FILE_DESCRIPTION "Dusk") set(DUSK_FILE_DESCRIPTION "Dusklight")
set(DUSK_PRODUCT_NAME "Dusk") set(DUSK_PRODUCT_NAME "Dusklight")
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors") set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES}) source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
source_group("dusk" FILES ${DUSK_FILES} ${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) set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1)
@@ -320,30 +341,30 @@ if (DUSK_ENABLE_UPDATE_CHECKER)
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/winhttp.cpp) set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/winhttp.cpp)
list(APPEND GAME_LIBS winhttp) list(APPEND GAME_LIBS winhttp)
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_WINHTTP=1) 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) elseif (ANDROID)
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/android.cpp) set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/android.cpp)
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_ANDROID=1) 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) elseif (APPLE)
find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED) find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED)
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/url_session.mm) 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) set_source_files_properties(src/dusk/http/url_session.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
list(APPEND GAME_LIBS ${FOUNDATION_FRAMEWORK}) list(APPEND GAME_LIBS ${FOUNDATION_FRAMEWORK})
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_URLSESSION=1) 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) elseif (CMAKE_SYSTEM_NAME STREQUAL Linux)
find_package(CURL QUIET OPTIONAL_COMPONENTS HTTPS SSL) find_package(CURL QUIET OPTIONAL_COMPONENTS HTTPS SSL)
if (CURL_FOUND AND CURL_HTTPS_FOUND AND CURL_SSL_FOUND) if (CURL_FOUND AND CURL_HTTPS_FOUND AND CURL_SSL_FOUND)
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/curl.cpp) set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/curl.cpp)
list(APPEND GAME_LIBS CURL::libcurl) list(APPEND GAME_LIBS CURL::libcurl)
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_LIBCURL=1) 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 () else ()
message(STATUS "dusk: Disabled update checker (libcurl + HTTPS/SSL not found)") message(STATUS "dusklight: Disabled update checker (libcurl + HTTPS/SSL not found)")
endif () endif ()
else () else ()
message(STATUS "dusk: Disabled update checker (unsupported platform)") message(STATUS "dusklight: Disabled update checker (unsupported platform)")
endif () endif ()
endif () endif ()
list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE}) list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE})
@@ -366,16 +387,6 @@ if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1) list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
endif () endif ()
# Edit & Continue
if (MSVC)
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
endif ()
if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
add_link_options("/INCREMENTAL")
endif ()
endif ()
if(ANDROID) if(ANDROID)
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1) list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
endif () endif ()
@@ -431,31 +442,37 @@ endif ()
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES}) set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES})
if(ANDROID) if(ANDROID)
add_library(dusk SHARED ${DUSK_FILES}) add_library(dusklight SHARED ${DUSK_FILES})
set_target_properties(dusk PROPERTIES OUTPUT_NAME main) set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
else () else ()
add_executable(dusk ${DUSK_FILES}) add_executable(dusklight ${DUSK_FILES})
endif () endif ()
target_compile_definitions(dusk PRIVATE ${GAME_COMPILE_DEFS}) target_compile_definitions(dusklight PRIVATE ${GAME_COMPILE_DEFS})
target_include_directories(dusk PRIVATE ${GAME_INCLUDE_DIRS}) target_include_directories(dusklight PRIVATE ${GAME_INCLUDE_DIRS})
target_link_libraries(dusk PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES}) target_link_libraries(dusklight PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
target_precompile_headers(dusk PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") target_precompile_headers(dusklight PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
if (TARGET crashpad_handler) if (TARGET crashpad_handler)
add_dependencies(dusk crashpad_handler) add_dependencies(dusklight crashpad_handler)
add_custom_command(TARGET dusklight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:crashpad_handler>"
"$<TARGET_FILE_DIR:dusklight>"
COMMENT "Copying crashpad handler"
)
endif () endif ()
if (ANDROID) if (ANDROID)
# SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static # SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static
# archive, force an undefined reference so the linker keeps the SDL_main object. # archive, force an undefined reference so the linker keeps the SDL_main object.
target_link_options(dusk PRIVATE "-Wl,-u,SDL_main") target_link_options(dusklight PRIVATE "-Wl,-u,SDL_main")
endif () endif ()
if (NOT APPLE) if (NOT APPLE)
add_custom_command(TARGET dusk POST_BUILD add_custom_command(TARGET dusklight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/res" "${CMAKE_SOURCE_DIR}/res"
"$<TARGET_FILE_DIR:dusk>/res" "$<TARGET_FILE_DIR:dusklight>/res"
COMMENT "Copying resources" COMMENT "Copying resources"
) )
endif () endif ()
@@ -463,9 +480,9 @@ endif ()
if (WIN32) if (WIN32)
set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows) set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows)
set(DUSK_WINDOWS_ICON_PNG ${CMAKE_CURRENT_SOURCE_DIR}/res/icon.png) set(DUSK_WINDOWS_ICON_PNG ${CMAKE_CURRENT_SOURCE_DIR}/res/icon.png)
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusk.ico) set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusklight.ico)
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusk.rc) set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusklight.rc)
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusk.manifest) set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusklight.manifest)
add_custom_command( add_custom_command(
OUTPUT ${DUSK_WINDOWS_ICON_ICO} OUTPUT ${DUSK_WINDOWS_ICON_ICO}
@@ -478,14 +495,14 @@ if (WIN32)
COMMENT "Generating Windows icon" COMMENT "Generating Windows icon"
) )
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY) configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.rc.in ${DUSK_WINDOWS_RC} @ONLY) configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.rc.in ${DUSK_WINDOWS_RC} @ONLY)
target_sources(dusk PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC}) target_sources(dusklight PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
set_target_properties(dusk PROPERTIES WIN32_EXECUTABLE TRUE) set_target_properties(dusklight PROPERTIES WIN32_EXECUTABLE TRUE)
if (MSVC) if (MSVC)
target_link_options(dusk PRIVATE /MANIFEST:NO) target_link_options(dusklight PRIVATE /MANIFEST:NO)
endif () endif ()
endif () endif ()
@@ -501,10 +518,10 @@ if (APPLE)
file(GLOB_RECURSE DUSK_RESOURCE_FILES file(GLOB_RECURSE DUSK_RESOURCE_FILES
"${DUSK_RESOURCE_DIR}/Assets.car" "${DUSK_RESOURCE_DIR}/Assets.car"
"${DUSK_RESOURCE_DIR}/Base.lproj/*" "${DUSK_RESOURCE_DIR}/Base.lproj/*"
"${DUSK_RESOURCE_DIR}/Dusk.icns") "${DUSK_RESOURCE_DIR}/Dusklight.icns")
file(GLOB_RECURSE DUSK_APP_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/*") file(GLOB_RECURSE DUSK_APP_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/*")
target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES}) target_sources(dusklight PRIVATE ${DUSK_RESOURCE_FILES})
target_sources(dusk PRIVATE ${DUSK_APP_RESOURCE_FILES}) target_sources(dusklight PRIVATE ${DUSK_APP_RESOURCE_FILES})
foreach (FILE ${DUSK_RESOURCE_FILES}) foreach (FILE ${DUSK_RESOURCE_FILES})
file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE}) file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE})
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
@@ -516,29 +533,36 @@ if (APPLE)
set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}")
endforeach () endforeach ()
set_target_properties( set_target_properties(
dusk PROPERTIES dusklight PROPERTIES
MACOSX_BUNDLE TRUE MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME} MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER} MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING} MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING} MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}
MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST} MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}
OUTPUT_NAME Dusk OUTPUT_NAME Dusklight
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES" XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES"
) )
endif () endif ()
if (APPLE AND NOT IOS AND NOT TVOS)
find_library(APPKIT_FRAMEWORK AppKit REQUIRED)
target_sources(dusklight PRIVATE src/dusk/file_select_macos.mm)
set_source_files_properties(src/dusk/file_select_macos.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
target_link_libraries(dusklight PRIVATE ${APPKIT_FRAMEWORK})
endif ()
if (IOS) if (IOS)
find_library(UIKIT_FRAMEWORK UIKit REQUIRED) find_library(UIKIT_FRAMEWORK UIKit REQUIRED)
find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers REQUIRED) find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers REQUIRED)
target_sources(dusk PRIVATE src/dusk/ios/FileSelectDialog.m) target_sources(dusklight PRIVATE src/dusk/ios/FileSelectDialog.m)
set_source_files_properties(src/dusk/ios/FileSelectDialog.m PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(src/dusk/ios/FileSelectDialog.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
target_link_libraries(dusk PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK}) target_link_libraries(dusklight PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
endif () endif ()
include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
aurora_copy_runtime_dlls(dusk) aurora_copy_runtime_dlls(dusklight)
if (DUSK_SELECTED_OPT) if (DUSK_SELECTED_OPT)
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
@@ -576,13 +600,13 @@ function(get_target_prefix target result_var)
endif () endif ()
endif () endif ()
endfunction() endfunction()
list(APPEND BINARY_TARGETS dusk) list(APPEND BINARY_TARGETS dusklight)
set(EXTRA_TARGETS "") set(EXTRA_TARGETS "")
if (TARGET crashpad_handler) if (TARGET crashpad_handler)
list(APPEND EXTRA_TARGETS crashpad_handler) list(APPEND EXTRA_TARGETS crashpad_handler)
endif () endif ()
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX}) install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX})
aurora_install_runtime_dlls(dusk ${CMAKE_INSTALL_PREFIX}) aurora_install_runtime_dlls(dusklight ${CMAKE_INSTALL_PREFIX})
if (NOT APPLE) if (NOT APPLE)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX}) install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX})
endif () endif ()
+50 -30
View File
@@ -30,7 +30,7 @@
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
"DUSK_ENABLE_SENTRY_NATIVE": { "DUSK_ENABLE_SENTRY_NATIVE": {
"type": "BOOL", "type": "BOOL",
"value": false "value": true
}, },
"DUSK_SENTRY_DSN": "$env{SENTRY_DSN}", "DUSK_SENTRY_DSN": "$env{SENTRY_DSN}",
"DUSK_SENTRY_ENVIRONMENT": "production" "DUSK_SENTRY_ENVIRONMENT": "production"
@@ -249,22 +249,11 @@
"type": "BOOL", "type": "BOOL",
"value": false "value": false
}, },
"CMAKE_DISABLE_FIND_PACKAGE_BZip2": { "CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": {
"type": "BOOL", "type": "BOOL",
"value": true "value": true
}, },
"CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": { "CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew"
"type": "BOOL",
"value": true
},
"CMAKE_DISABLE_FIND_PACKAGE_zstd": {
"type": "BOOL",
"value": true
},
"CMAKE_DISABLE_FIND_PACKAGE_Freetype": {
"type": "BOOL",
"value": true
}
}, },
"vendor": { "vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": { "microsoft.com/VisualStudioSettings/CMake/1.0": {
@@ -329,7 +318,11 @@
"cacheVariables": { "cacheVariables": {
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_HOME}/ndk/$env{ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake", "CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_HOME}/ndk/$env{ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake",
"ANDROID_PLATFORM": "android-28" "ANDROID_PLATFORM": "android-28",
"BUILD_SHARED_LIBS": {
"type": "BOOL",
"value": false
}
} }
}, },
{ {
@@ -352,6 +345,31 @@
"ANDROID_ABI": "x86_64" "ANDROID_ABI": "x86_64"
} }
}, },
{
"name": "x-android-ci",
"hidden": true,
"inherits": [
"android-base",
"ci"
],
"cacheVariables": {
"DUSK_ENABLE_SENTRY_NATIVE": {
"type": "BOOL",
"value": false
}
}
},
{
"name": "x-android-ci-arm64",
"binaryDir": "${sourceDir}/build/android-arm64",
"inherits": [
"x-android-ci"
],
"cacheVariables": {
"ANDROID_ABI": "arm64-v8a",
"Rust_CARGO_TARGET": "aarch64-linux-android"
}
},
{ {
"name": "x-linux-ci", "name": "x-linux-ci",
"hidden": true, "hidden": true,
@@ -391,7 +409,7 @@
}, },
"CMAKE_OSX_DEPLOYMENT_TARGET": "11.0", "CMAKE_OSX_DEPLOYMENT_TARGET": "11.0",
"CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew", "CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew",
"DUSK_MOVIE_SUPPORT": { "BUILD_SHARED_LIBS": {
"type": "BOOL", "type": "BOOL",
"value": false "value": false
} }
@@ -412,6 +430,7 @@
"x-macos-ci" "x-macos-ci"
], ],
"cacheVariables": { "cacheVariables": {
"AURORA_DAWN_PROVIDER": "vendor",
"CMAKE_OSX_ARCHITECTURES": "x86_64", "CMAKE_OSX_ARCHITECTURES": "x86_64",
"Rust_CARGO_TARGET": "x86_64-apple-darwin" "Rust_CARGO_TARGET": "x86_64-apple-darwin"
} }
@@ -423,11 +442,7 @@
], ],
"cacheVariables": { "cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_C_COMPILER_LAUNCHER": "sccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
"DUSK_MOVIE_SUPPORT": {
"type": "BOOL",
"value": false
}
} }
}, },
{ {
@@ -437,11 +452,7 @@
], ],
"cacheVariables": { "cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_C_COMPILER_LAUNCHER": "sccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
"DUSK_MOVIE_SUPPORT": {
"type": "BOOL",
"value": false
}
} }
}, },
{ {
@@ -525,7 +536,7 @@
"description": "iOS release build with debug info", "description": "iOS release build with debug info",
"displayName": "iOS RelWithDebInfo", "displayName": "iOS RelWithDebInfo",
"targets": [ "targets": [
"dusk" "dusklight"
] ]
}, },
{ {
@@ -534,7 +545,7 @@
"description": "tvOS release build with debug info", "description": "tvOS release build with debug info",
"displayName": "tvOS RelWithDebInfo", "displayName": "tvOS RelWithDebInfo",
"targets": [ "targets": [
"dusk" "dusklight"
] ]
}, },
{ {
@@ -543,7 +554,7 @@
"description": "Android arm64-v8a release build with debug info", "description": "Android arm64-v8a release build with debug info",
"displayName": "Android arm64-v8a RelWithDebInfo", "displayName": "Android arm64-v8a RelWithDebInfo",
"targets": [ "targets": [
"dusk" "dusklight"
] ]
}, },
{ {
@@ -552,7 +563,16 @@
"description": "Android x86_64 release build with debug info", "description": "Android x86_64 release build with debug info",
"displayName": "Android x86_64 RelWithDebInfo", "displayName": "Android x86_64 RelWithDebInfo",
"targets": [ "targets": [
"dusk" "dusklight"
]
},
{
"name": "x-android-ci-arm64",
"configurePreset": "x-android-ci-arm64",
"description": "(Internal) Android CI arm64-v8a",
"displayName": "(Internal) Android CI arm64-v8a",
"targets": [
"dusklight"
] ]
}, },
{ {
+15 -12
View File
@@ -1,27 +1,30 @@
<div align="center"> <div align="center">
<img src="res/logo-mascot.png" alt="Logo" width="640"> <img src="res/logo.png" alt="Logo" width="640">
<p align="center"> <p align="center">
<a href="https://twilitrealm.dev">Official Website</a> <a href="https://twilitrealm.dev">Official Website</a>
<a href="https://discord.gg/dusktp">Discord</a> <a href="https://discord.gg/6NpMhefCK9">Discord</a>
</p> </p>
</div> </div>
# Overview # Overview
Dusk is a reverse-engineered reimplementation of Twilight Princess. Dusklight is a reverse-engineered reimplementation of Twilight Princess.
It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience. It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
# Setup # Setup
> [!IMPORTANT] > [!IMPORTANT]
> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game. > Dusklight does *not* provide any copyrighted assets. You must provide your own copy of the original game.
> [!IMPORTANT]
> At a minimum, Dusklight requires a GPU with support for either D3D12, Vulkan, or Metal. Your experience with specific hardware, operating systems, and drivers may vary. In particular, older Intel iGPUs have a high likelihood of incompatibility. We are also aware of a number of issues on devices with Adreno GPUs and are working to resolve them.
### 1. Verify your dump ### 1. Verify your dump
First, make sure your dump of the game is clean and supported by Dusk. You can do this by checking the SHA-1 hash of your dump against this list of supported versions: First, make sure your dump of the game is clean and supported by Dusklight. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
| Version | SHA-1 hash | | Version | SHA-1 hash |
|--------------| ------------------------------------------ | |--------------| ------------------------------------------ |
@@ -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. *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 ### 3. Setup the game
**Windows / macOS / Linux** **Windows / macOS / Linux**
- Extract the .zip file - Extract the .zip file
- Launch Dusk - Launch Dusklight
- Press **Select Disc Image** and provide the path to your supported game dump - Press **Select Disc Image** and provide the path to your supported game dump
- Press **Play**! - 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) - Follow the [iOS setup guide](docs/ios-install-altstore.md)
**Android** **Android**
- Install the Dusk apk - Install the Dusklight APK
- Launch Dusk - Launch Dusklight
- Press **Select Disc Image** and provide the path to your supported game dump - Press **Select Disc Image** and provide the path to your supported game dump
- Press **Play**! - Press **Play**!
# Building # Building
If you'd like to build Dusk from source, please read the [build instructions](docs/building.md). If you'd like to build Dusklight from source, please read the [build instructions](docs/building.md).
Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much. Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much. Please also see the [code conventions](docs/code-conventions.md).
# Credits # Credits
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors). Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusklight/graphs/contributors).
<br/> <br/>
<div align="center"> <div align="center">
+17 -9
View File
@@ -1,18 +1,26 @@
#!/bin/bash -ex #!/bin/bash -ex
shopt -s extglob
if [[ -n "${GITHUB_WORKSPACE:-}" ]]; then
cd "$GITHUB_WORKSPACE"
fi
build_dir="$PWD/build"
linuxdeploy="$build_dir/linuxdeploy-$(uname -m).AppImage"
# Get linuxdeploy # Get linuxdeploy
cd "$RUNNER_WORKSPACE" mkdir -p "$build_dir"
curl -fOL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage curl -fL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage" -o "$linuxdeploy"
chmod +x linuxdeploy-$(uname -m).AppImage chmod +x "$linuxdeploy"
# Build AppImage # Build AppImage
cd "$GITHUB_WORKSPACE"
mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}} mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}}
cp -r build/install/!(*.*) build/appdir/usr/bin for install_path in build/install/*; do
[[ "$(basename "$install_path")" == *.* ]] && continue
cp -r "$install_path" build/appdir/usr/bin
done
cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor
cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications cp platforms/freedesktop/dusklight.desktop build/appdir/usr/share/applications
cd build/install cd build/install
VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \ VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage -l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$build_dir/appdir" --output appimage
+5 -5
View File
@@ -36,10 +36,10 @@
sudo dnf groupinstall "Development Tools" "Development Libraries" sudo dnf groupinstall "Development Tools" "Development Libraries"
``` ```
#### Setup #### Setup
Clone and initialize the Dusk repository Clone and initialize the Dusklight repository
```sh ```sh
git clone --recursive https://github.com/TwilitRealm/dusk.git git clone --recursive https://github.com/TwilitRealm/dusklight.git
cd dusk cd dusklight
git pull git pull
git submodule update --init --recursive git submodule update --init --recursive
``` ```
@@ -93,6 +93,6 @@ Alternate presets available:
#### Running #### Running
Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
```sh ```sh
build/{preset}/dusk /path/to/game.rvz build/{preset}/dusklight/path/to/game.rvz
``` ```
If no path is specified, Dusk defaults to `game.iso` in the current working directory. If no path is specified, Dusklight defaults to `game.iso` in the current working directory.
+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 ## Prerequisites
- Mac with Homebrew installed - Mac with Homebrew installed
- iPhone connected via USB - 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) - Game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL)
## 1. Install AltServer ## 1. Install AltServer
+1 -1
+10 -3
View File
@@ -1411,6 +1411,7 @@ set(DOLPHIN_FILES
) )
set(DUSK_FILES set(DUSK_FILES
include/dusk/action_bindings.h
include/dusk/endian_gx.hpp include/dusk/endian_gx.hpp
include/dusk/config.hpp include/dusk/config.hpp
include/dusk/dvd_asset.hpp include/dusk/dvd_asset.hpp
@@ -1420,6 +1421,8 @@ set(DUSK_FILES
src/dusk/asserts.cpp src/dusk/asserts.cpp
src/dusk/config.cpp src/dusk/config.cpp
src/dusk/crash_reporting.cpp src/dusk/crash_reporting.cpp
src/dusk/data.cpp
src/dusk/data.hpp
src/dusk/endian.cpp src/dusk/endian.cpp
src/dusk/extras.c src/dusk/extras.c
src/dusk/file_select.cpp src/dusk/file_select.cpp
@@ -1435,6 +1438,7 @@ set(DUSK_FILES
src/dusk/layout.cpp src/dusk/layout.cpp
src/dusk/logging.cpp src/dusk/logging.cpp
src/dusk/settings.cpp src/dusk/settings.cpp
src/dusk/speedrun.cpp
src/dusk/stubs.cpp src/dusk/stubs.cpp
src/dusk/update_check.cpp src/dusk/update_check.cpp
src/dusk/update_check.hpp src/dusk/update_check.hpp
@@ -1444,18 +1448,16 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiConsole.cpp src/dusk/imgui/ImGuiConsole.cpp
src/dusk/imgui/ImGuiEngine.cpp src/dusk/imgui/ImGuiEngine.cpp
src/dusk/imgui/ImGuiEngine.hpp src/dusk/imgui/ImGuiEngine.hpp
src/dusk/imgui/ImGuiMenuGame.cpp
src/dusk/imgui/ImGuiMenuGame.hpp
src/dusk/imgui/ImGuiBloomWindow.cpp src/dusk/imgui/ImGuiBloomWindow.cpp
src/dusk/imgui/ImGuiBloomWindow.hpp src/dusk/imgui/ImGuiBloomWindow.hpp
src/dusk/imgui/ImGuiMenuTools.cpp src/dusk/imgui/ImGuiMenuTools.cpp
src/dusk/imgui/ImGuiMenuTools.hpp src/dusk/imgui/ImGuiMenuTools.hpp
src/dusk/imgui/ImGuiActorSpawner.cpp
src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiProcessOverlay.cpp
src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp
src/dusk/imgui/ImGuiHeapOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp
src/dusk/imgui/ImGuiControllerOverlay.cpp src/dusk/imgui/ImGuiControllerOverlay.cpp
src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiStubLog.cpp
src/dusk/imgui/ImGuiMapLoader.cpp
src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiSaveEditor.cpp
src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.hpp
src/dusk/imgui/ImGuiStateShare.cpp src/dusk/imgui/ImGuiStateShare.cpp
@@ -1494,6 +1496,8 @@ set(DUSK_FILES
src/dusk/ui/prelaunch.hpp src/dusk/ui/prelaunch.hpp
src/dusk/ui/preset.cpp src/dusk/ui/preset.cpp
src/dusk/ui/preset.hpp src/dusk/ui/preset.hpp
src/dusk/ui/reporting.cpp
src/dusk/ui/reporting.hpp
src/dusk/ui/select_button.cpp src/dusk/ui/select_button.cpp
src/dusk/ui/select_button.hpp src/dusk/ui/select_button.hpp
src/dusk/ui/settings.cpp src/dusk/ui/settings.cpp
@@ -1504,6 +1508,8 @@ set(DUSK_FILES
src/dusk/ui/tab_bar.hpp src/dusk/ui/tab_bar.hpp
src/dusk/ui/ui.cpp src/dusk/ui/ui.cpp
src/dusk/ui/ui.hpp src/dusk/ui/ui.hpp
src/dusk/ui/warp.cpp
src/dusk/ui/warp.hpp
src/dusk/ui/window.cpp src/dusk/ui/window.cpp
src/dusk/ui/window.hpp src/dusk/ui/window.hpp
src/dusk/achievements.cpp src/dusk/achievements.cpp
@@ -1518,6 +1524,7 @@ set(DUSK_FILES
src/dusk/discord.hpp src/dusk/discord.hpp
src/dusk/discord_presence.cpp src/dusk/discord_presence.cpp
src/dusk/version.cpp src/dusk/version.cpp
src/dusk/action_bindings.cpp
) )
set(DUSK_HTTP_BACKEND_FILES set(DUSK_HTTP_BACKEND_FILES
+218 -23
View File
@@ -4,30 +4,225 @@
}; };
outputs = { self, nixpkgs }: outputs = { self, nixpkgs }:
let let
pkgs = import nixpkgs { system = "x86_64-linux"; }; supportedSystems = [
dusk = pkgs.stdenv.mkDerivation { "x86_64-linux"
name = "dusk"; "aarch64-linux"
src = ./.; "x86_64-darwin"
nativeBuildInputs = [ "aarch64-darwin"
pkgs.cmake ];
pkgs.pkg-config forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
pkgs.wayland pkgsFor = system: import nixpkgs { inherit system; };
];
buildInputs = [ # Dependencies that are not packaged in nixpkgs (used by the Linux package build):
pkgs.libGL buildSources = pkgs: {
pkgs.libX11 aurora-src = pkgs.fetchFromGitHub {
pkgs.libXcursor owner = "encounter";
pkgs.libxi repo = "aurora";
pkgs.libxcb rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
pkgs.libxrandr hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
pkgs.libxscrnsaver };
pkgs.libxtst dawn-src = pkgs.fetchzip {
pkgs.libjpeg8 url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
pkgs.libxkbcommon hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
pkgs.libglvnd stripRoot = false;
]; };
nod-src = pkgs.fetchzip {
url = "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.8/libnod-linux-x86_64.tar.gz";
hash = "sha256-mUqvLsbsqaZ+HAjMmHYPYO+MgtanGRTw7Gzn5uXR5rE=";
stripRoot = false;
};
# The version of imgui on nixpkgs does not map cleanly.
imgui-src = pkgs.fetchFromGitHub {
owner = "ocornut";
repo = "imgui";
rev = "v1.91.9b-docking";
hash = "sha256-mQOJ6jCN+7VopgZ61yzaCnt4R1QLrW7+47xxMhFRHLQ=";
};
sqlite-src = pkgs.fetchzip {
url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip";
hash = "sha256-pNMR8zxaaqfAzQ0AQBOXMct4usdjey1Q0Gnitg06UhM=";
};
rmlui-src = pkgs.fetchzip {
url = "https://github.com/mikke89/RmlUi/archive/f9b8c9e2935d5df2c7dff2c190d3968e99b0c3dc.tar.gz";
hash = "sha256-g4O/JZUrrcseOz8o2QJRt+2CeuiLnVeuDJc906xvuIg=";
};
}; };
# Dusklight Actual (Linux x86_64 only — relies on prebuilt dawn/nod binaries)
mkDusklight = pkgs:
let srcs = buildSources pkgs;
versionSuffix = if self ? shortRev && self.shortRev != null
then "nix-${self.shortRev}"
else "nix-dirty";
in
pkgs.stdenv.mkDerivation {
name = "dusklight";
src = ./.;
postUnpack = ''
mkdir -p $sourceRoot/extern/aurora
cp -r ${srcs.aurora-src}/. $sourceRoot/extern/aurora/
chmod -R u+w $sourceRoot/extern/aurora
sed -i '/add_subdirectory(tests)/d' $sourceRoot/extern/aurora/CMakeLists.txt
'';
# Remove last line to re-enable tests
cmakeFlags = [
"-DDUSK_VERSION_OVERRIDE=${versionSuffix}"
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
"-DFETCHCONTENT_SOURCE_DIR_CXXOPTS=${pkgs.cxxopts.src}"
"-DFETCHCONTENT_SOURCE_DIR_JSON=${pkgs.nlohmann_json.src}"
"-DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${srcs.dawn-src}"
"-DFETCHCONTENT_SOURCE_DIR_XXHASH=${pkgs.xxHash.src}"
"-DFETCHCONTENT_SOURCE_DIR_FMT=${pkgs.fmt.src}"
"-DFETCHCONTENT_SOURCE_DIR_TRACY=${pkgs.tracy.src}"
"-DAURORA_SDL3_PROVIDER=system"
"-DFETCHCONTENT_SOURCE_DIR_NOD_PREBUILT=${srcs.nod-src}"
"-DAURORA_NOD_PROVIDER=package"
"-DFETCHCONTENT_SOURCE_DIR_FREETYPE=${pkgs.freetype.src}"
"-DFETCHCONTENT_SOURCE_DIR_ZSTD=${pkgs.zstd.src}"
"-DFETCHCONTENT_SOURCE_DIR_SQLITE3=${srcs.sqlite-src}"
"-DFETCHCONTENT_SOURCE_DIR_IMGUI=${srcs.imgui-src}"
"-DFETCHCONTENT_SOURCE_DIR_RMLUI=${srcs.rmlui-src}"
"-DCMAKE_CROSSCOMPILING=ON" # Tests are not working as I didn't want to work through getting google's test suite working as well. This is the only guard I could find to disable it.
];
installPhase = ''
mkdir -p $out/bin
cp dusklight $out/bin/dusklight
cp -r ./res $out/bin/res
mkdir -p $out/share/applications
cp $src/platforms/freedesktop/dusklight.desktop $out/share/applications/dusklight.desktop
for size in 16 32 48 64 128 256 512 1024; do
install -Dm644 $src/platforms/freedesktop/''${size}x''${size}/apps/dusklight.png \
$out/share/icons/hicolor/''${size}x''${size}/apps/dusklight.png
done
'';
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
pkgs.wayland
];
buildInputs = [
pkgs.libGL
pkgs.libX11
pkgs.libXcursor
pkgs.libxi
pkgs.libxcb
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.fmt
pkgs.tracy
pkgs.freetype
pkgs.zstd
];
};
# Tooling common to every supported host (Linux and macOS).
commonDevTools = pkgs: [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
pkgs.git
pkgs.python3
pkgs.python3Packages.markupsafe
pkgs.rustc
pkgs.cargo
pkgs.sccache
];
# Linux-only system libraries — mirrors the apt deps from .github/workflows/build.yml
# so the cmake presets resolve the same set of headers as CI.
linuxDevDeps = pkgs: [
# Compilers / linkers
pkgs.clang
pkgs.lld
# C/C++ utilities
pkgs.curl
pkgs.openssl
pkgs.zlib
pkgs.libpng
pkgs.libjpeg_turbo
pkgs.freetype
pkgs.zstd
pkgs.fmt
pkgs.tracy
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.ncurses
pkgs.libunwind
pkgs.libusb1
pkgs.fuse
# Wayland / display server
pkgs.wayland
pkgs.wayland-protocols
pkgs.libxkbcommon
pkgs.libdecor
# OpenGL / Vulkan
pkgs.libGL
pkgs.libGLU
pkgs.libglvnd
pkgs.vulkan-headers
pkgs.vulkan-loader
# X11
pkgs.libX11
pkgs.libxcb
pkgs.libXcursor
pkgs.libxi
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libxinerama
# Audio
pkgs.alsa-lib
pkgs.libpulseaudio
pkgs.pipewire
# System integration
pkgs.dbus
pkgs.udev
pkgs.gtk3
];
# On macOS we deliberately avoid pulling Nix's cc-wrapper so CMake picks up
# Apple Clang and the Xcode SDK directly, matching the macOS CI workflow.
mkDarwinShell = pkgs:
pkgs.mkShellNoCC {
packages = commonDevTools pkgs;
shellHook = ''
echo "Dusklight dev shell (macOS)"
echo "Requires Xcode Command Line Tools for Apple Clang and the macOS SDK."
echo "Configure: cmake --preset macos-default-relwithdebinfo"
echo "Build: cmake --build --preset macos-default-relwithdebinfo"
'';
};
mkLinuxShell = pkgs:
pkgs.mkShell {
packages = (commonDevTools pkgs) ++ (linuxDevDeps pkgs);
shellHook = ''
echo "Dusklight dev shell (Linux)"
echo "Configure: cmake --preset linux-default-relwithdebinfo"
echo " cmake --preset linux-clang-relwithdebinfo"
echo "Build: cmake --build --preset <preset>"
'';
};
mkDevShell = pkgs:
if pkgs.stdenv.isDarwin
then mkDarwinShell pkgs
else mkLinuxShell pkgs;
in { in {
packages.x86_64-linux.default = dusk; packages.x86_64-linux.default = mkDusklight (pkgsFor "x86_64-linux");
devShells = forAllSystems (system: {
default = mkDevShell (pkgsFor system);
});
}; };
} }
+4
View File
@@ -196,7 +196,11 @@ public:
/* 0x108 */ int mSkipTimer; /* 0x108 */ int mSkipTimer;
/* 0x10C */ int mSkipParameter; /* 0x10C */ int mSkipParameter;
/* 0x110 */ BOOL mIsSkipFade; /* 0x110 */ BOOL mIsSkipFade;
#if TARGET_PC
/* 0x114 */ char mSkipEventName[21];
#else
/* 0x114 */ char mSkipEventName[20]; /* 0x114 */ char mSkipEventName[20];
#endif
/* 0x128 */ u8 mCompulsory; /* 0x128 */ u8 mCompulsory;
/* 0x129 */ bool mRoomInfoSet; /* 0x129 */ bool mRoomInfoSet;
/* 0x12C */ int mRoomNo; /* 0x12C */ int mRoomNo;
+3
View File
@@ -223,6 +223,9 @@ private:
/* 0x8F */ u8 field_0x8f; /* 0x8F */ u8 field_0x8f;
/* 0x90 */ u8 field_0x90; /* 0x90 */ u8 field_0x90;
/* 0x91 */ u8 field_0x91; /* 0x91 */ u8 field_0x91;
#if TARGET_PC
bool previousMirror;
#endif
}; // Size: 0x94 }; // Size: 0x94
class dMap_HIO_list_c : public dMpath_HIO_n::hioList_c { class dMap_HIO_list_c : public dMpath_HIO_n::hioList_c {
+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! * This gets used for file paths and such, and cannot be changed!
*/ */
constexpr auto AppName = "Dusk"; constexpr auto AppName = "Dusklight";
/**
* Previous AppName to migrate data from.
*/
constexpr auto LegacyAppName = "Dusk";
/** /**
* \brief The internal organization name for the game. * \brief The internal organization name for the game.
+11
View File
@@ -1,8 +1,19 @@
#pragma once #pragma once
#include <cmath>
#include <dolphin/types.h> #include <dolphin/types.h>
namespace dusk::audio { namespace dusk::audio {
// Converts a 0-1 volume to a linear amplitude multiplier.
// The curve is -4 dB per 10% step: 100% = 0 dB, 90% = -4 dB, ..., 0% = -inf dB
inline f32 MasterVolumeToLinear(f32 v) {
if (v <= 0.0f) {
return 0.0f;
}
return std::pow(10.0f, (v - 1.0f) * 2.0f);
}
/** /**
* Initialize the audio system and start playing audio. * Initialize the audio system and start playing audio.
*/ */
+1
View File
@@ -13,5 +13,6 @@ void enterAutoSave();
void autoSaving(); void autoSaving();
void waitingForWrite(); void waitingForWrite();
void endAutoSave(); void endAutoSave();
void toggleAutoSave(bool enabled);
#endif #endif
+13
View File
@@ -1,6 +1,7 @@
#ifndef DUSK_CONFIG_HPP #ifndef DUSK_CONFIG_HPP
#define DUSK_CONFIG_HPP #define DUSK_CONFIG_HPP
#include <functional>
#include <stdexcept> #include <stdexcept>
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include "config_var.hpp" #include "config_var.hpp"
@@ -111,6 +112,18 @@ void Save();
*/ */
ConfigVarBase* GetConfigVar(std::string_view name); ConfigVarBase* GetConfigVar(std::string_view name);
/**
* \brief Resets all custom action bindings for a specific port to nothing
*
* @param port The port to be cleared of action bindings
*/
void ClearAllActionBindings(int port);
/**
* \brief Call a function on every registered CVar.
*/
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback);
template <ConfigValue T> template <ConfigValue T>
const ConfigImplBase* GetConfigImpl() { const ConfigImplBase* GetConfigImpl() {
static ConfigImpl<T> config; static ConfigImpl<T> config;
+61
View File
@@ -48,6 +48,13 @@ enum class ConfigVarLayer : u8 {
* Will not get saved to config. * Will not get saved to config.
*/ */
Override, Override,
/**
* The CVar is temporarily overridden by speedrun mode.
* Will not get saved to config. Cleared when speedrun mode is disabled.
* Lower priority than Override, so launch args still win.
*/
Speedrun,
}; };
class ConfigImplBase; class ConfigImplBase;
@@ -113,6 +120,12 @@ public:
* This is necessary to make it legal to access. * This is necessary to make it legal to access.
*/ */
void markRegistered(); void markRegistered();
/**
* Clear a speedrun-mode override if one is active on this CVar.
* Safe to call on any CVar, no-op if not at the Speedrun layer.
*/
virtual void clearSpeedrunOverride() {}
}; };
template <typename T> template <typename T>
@@ -162,6 +175,7 @@ class ConfigVar : public ConfigVarBase {
T defaultValue; T defaultValue;
T value; T value;
T overrideValue; T overrideValue;
ConfigVarLayer priorLayer = ConfigVarLayer::Default;
public: public:
/** /**
@@ -189,6 +203,7 @@ public:
case ConfigVarLayer::Value: case ConfigVarLayer::Value:
return value; return value;
case ConfigVarLayer::Override: case ConfigVarLayer::Override:
case ConfigVarLayer::Speedrun:
return overrideValue; return overrideValue;
default: default:
abort(); abort();
@@ -239,8 +254,54 @@ public:
overrideValue = std::move(newValue); overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override; layer = ConfigVarLayer::Override;
} }
/**
* \brief Give a CVar a speedrun-mode override value.
*
* Lower priority than a launch-arg override. Cleared when speedrun mode is disabled.
* The overridden value will not get saved to config.
*
* @param newValue The new value the CVar will get.
*/
void setSpeedrunValue(T newValue) {
checkRegistered();
if (layer != ConfigVarLayer::Override) {
priorLayer = layer;
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Speedrun;
}
}
void clearOverride() {
checkRegistered();
if (layer == ConfigVarLayer::Override) {
overrideValue = {};
layer = ConfigVarLayer::Value;
}
}
void clearSpeedrunOverride() override {
checkRegistered();
if (layer == ConfigVarLayer::Speedrun) {
overrideValue = {};
layer = priorLayer;
}
}
/**
* \brief Get the user-persisted value, ignoring any temporary overrides.
*
* Used by Save() to write the correct value even when a speedrun override is active.
*/
[[nodiscard]] constexpr const T& getValueForSave() const noexcept {
checkRegistered();
const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer;
return effectiveLayer == ConfigVarLayer::Default ? defaultValue : value;
}
}; };
using ActionBindConfigVar = ConfigVar<int>;
} }
#endif // DUSK_CONFIG_VAR_HPP #endif // DUSK_CONFIG_VAR_HPP
+13 -4
View File
@@ -1,8 +1,17 @@
#pragma once #pragma once
namespace dusk { namespace dusk::crash_reporting {
void InitializeCrashReporting(); enum class Consent {
void ShutdownCrashReporting(); Unavailable,
Unknown,
Given,
Revoked,
};
} // namespace dusk void initialize();
void shutdown();
Consent get_consent();
void set_consent(bool enabled);
} // namespace dusk::crash_reporting
-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_HEAP_VIEWER = "F4";
constexpr const char* SHOW_PLAYER_INFO = "F5"; constexpr const char* SHOW_PLAYER_INFO = "F5";
constexpr const char* SHOW_SAVE_EDITOR = "F6"; constexpr const char* SHOW_SAVE_EDITOR = "F6";
constexpr const char* SHOW_MAP_LOADER = "F7";
constexpr const char* SHOW_STATE_SHARE = "F8"; constexpr const char* SHOW_STATE_SHARE = "F8";
constexpr const char* SHOW_DEBUG_CAMERA = "F9"; constexpr const char* SHOW_DEBUG_CAMERA = "F9";
constexpr const char* SHOW_AUDIO_DEBUG = "F10"; constexpr const char* SHOW_AUDIO_DEBUG = "F10";
+35 -5
View File
@@ -1,6 +1,7 @@
#ifndef DUSK_IO_HPP #ifndef DUSK_IO_HPP
#define DUSK_IO_HPP #define DUSK_IO_HPP
#include <filesystem>
#include <vector> #include <vector>
// I can't believe it's 2026 and neither SDL (no error codes) nor // I can't believe it's 2026 and neither SDL (no error codes) nor
@@ -15,7 +16,7 @@ namespace dusk::io {
* Methods on this class throw appropriate C++ exceptions when an error occurs. * Methods on this class throw appropriate C++ exceptions when an error occurs.
*/ */
class FileStream { class FileStream {
void* file; FILE* file;
public: public:
FileStream() noexcept; FileStream() noexcept;
@@ -23,7 +24,7 @@ public:
/** /**
* \brief Take ownership of a FILE* handle. * \brief Take ownership of a FILE* handle.
*/ */
explicit FileStream(void* file); explicit FileStream(FILE* file);
FileStream(const FileStream& other) = delete; FileStream(const FileStream& other) = delete;
FileStream(FileStream&& other) noexcept; FileStream(FileStream&& other) noexcept;
@@ -34,6 +35,11 @@ public:
*/ */
static FileStream OpenRead(const char* utf8Path); static FileStream OpenRead(const char* utf8Path);
/**
* \brief Open a file for reading at the given path.
*/
static FileStream OpenRead(const std::filesystem::path& path);
/** /**
* \brief Create a file for writing. * \brief Create a file for writing.
* *
@@ -41,16 +47,33 @@ public:
*/ */
static FileStream Create(const char* utf8Path); static FileStream Create(const char* utf8Path);
/**
* \brief Create a file for writing.
*
* If there is an existing file, its contents are demolished.
*/
static FileStream Create(const std::filesystem::path& path);
/** /**
* \brief Read the byte contents of a file directly into a vector. * \brief Read the byte contents of a file directly into a vector.
*/ */
static std::vector<u8> ReadAllBytes(const char* utf8Path); static std::vector<u8> ReadAllBytes(const char* utf8Path);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static std::vector<u8> ReadAllBytes(const std::filesystem::path& path);
/** /**
* \brief Read the byte contents of a file directly into a vector. * \brief Read the byte contents of a file directly into a vector.
*/ */
static void WriteAllText(const char* utf8Path, std::string_view text); static void WriteAllText(const char* utf8Path, std::string_view text);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static void WriteAllText(const std::filesystem::path& path, std::string_view text);
/** /**
* \brief Read the remaining contents of the file directly into a vector. * \brief Read the remaining contents of the file directly into a vector.
*/ */
@@ -59,17 +82,24 @@ public:
/** /**
* Get direct access to the underlying FILE* handle. * Get direct access to the underlying FILE* handle.
*/ */
[[nodiscard]] void* GetFileHandle() const noexcept { [[nodiscard]] void* GetFileHandle() const noexcept { return file; }
return file;
}
/** /**
* Write data to the file. * Write data to the file.
*/ */
void Write(const char* data, size_t dataLen); void Write(const char* data, size_t dataLen);
FILE* ToInner();
}; };
/**
* Converts a std::filesystem::path to a std::string, UTF-8, without exploding on Windows.
*/
inline std::string fs_path_to_string(const std::filesystem::path& path) {
const auto u8str = path.u8string();
return {reinterpret_cast<const char*>(u8str.c_str())};
} }
} // namespace dusk::io
#endif // DUSK_IO_HPP #endif // DUSK_IO_HPP
+13 -14
View File
@@ -1,27 +1,26 @@
#ifndef DUSK_MAIN_H #ifndef DUSK_MAIN_H
#define DUSK_MAIN_H #define DUSK_MAIN_H
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#include <filesystem> #include <filesystem>
namespace dusk { namespace dusk {
extern bool IsRunning;
extern bool IsShuttingDown;
extern bool IsGameLaunched;
extern bool RestartRequested;
extern std::filesystem::path ConfigPath;
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \ extern bool IsRunning;
extern bool IsShuttingDown;
extern bool IsGameLaunched;
extern bool RestartRequested;
extern std::filesystem::path ConfigPath;
extern std::filesystem::path CachePath;
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV) (defined(TARGET_OS_TV) && TARGET_OS_TV)
inline constexpr bool SupportsProcessRestart = false; inline constexpr bool SupportsProcessRestart = false;
#else #else
inline constexpr bool SupportsProcessRestart = true; inline constexpr bool SupportsProcessRestart = true;
#endif #endif
void RequestRestart() noexcept; void RequestRestart() noexcept;
}
} // namespace dusk
#endif // DUSK_MAIN_H #endif // DUSK_MAIN_H
+24 -2
View File
@@ -1,6 +1,8 @@
#ifndef DUSK_CONFIG_H #ifndef DUSK_CONFIG_H
#define DUSK_CONFIG_H #define DUSK_CONFIG_H
#include <array>
#include "dusk/config_var.hpp" #include "dusk/config_var.hpp"
namespace dusk { namespace dusk {
@@ -115,6 +117,7 @@ struct UserSettings {
ConfigVar<bool> enableLinkDollRotation; ConfigVar<bool> enableLinkDollRotation;
ConfigVar<bool> enableAchievementToasts; ConfigVar<bool> enableAchievementToasts;
ConfigVar<bool> enableControllerToasts; ConfigVar<bool> enableControllerToasts;
ConfigVar<bool> enableDiscordPresence;
// Graphics // Graphics
ConfigVar<BloomMode> bloomMode; ConfigVar<BloomMode> bloomMode;
@@ -125,6 +128,7 @@ struct UserSettings {
ConfigVar<int> shadowResolutionMultiplier; ConfigVar<int> shadowResolutionMultiplier;
ConfigVar<bool> enableDepthOfField; ConfigVar<bool> enableDepthOfField;
ConfigVar<bool> enableMapBackground; ConfigVar<bool> enableMapBackground;
ConfigVar<bool> disableCutscenePillarboxing;
// Audio // Audio
ConfigVar<bool> noLowHpSound; ConfigVar<bool> noLowHpSound;
@@ -144,6 +148,8 @@ struct UserSettings {
ConfigVar<bool> freeCamera; ConfigVar<bool> freeCamera;
ConfigVar<bool> invertCameraXAxis; ConfigVar<bool> invertCameraXAxis;
ConfigVar<bool> invertCameraYAxis; ConfigVar<bool> invertCameraYAxis;
ConfigVar<bool> invertFirstPersonXAxis;
ConfigVar<bool> invertFirstPersonYAxis;
ConfigVar<float> freeCameraSensitivity; ConfigVar<float> freeCameraSensitivity;
ConfigVar<bool> debugFlyCam; ConfigVar<bool> debugFlyCam;
ConfigVar<bool> debugFlyCamLockEvents; ConfigVar<bool> debugFlyCamLockEvents;
@@ -152,6 +158,7 @@ struct UserSettings {
// Cheats // Cheats
ConfigVar<bool> infiniteHearts; ConfigVar<bool> infiniteHearts;
ConfigVar<bool> infiniteArrows; ConfigVar<bool> infiniteArrows;
ConfigVar<bool> infiniteSeeds;
ConfigVar<bool> infiniteBombs; ConfigVar<bool> infiniteBombs;
ConfigVar<bool> infiniteOil; ConfigVar<bool> infiniteOil;
ConfigVar<bool> infiniteOxygen; ConfigVar<bool> infiniteOxygen;
@@ -162,19 +169,27 @@ struct UserSettings {
ConfigVar<bool> alwaysGreatspin; ConfigVar<bool> alwaysGreatspin;
ConfigVar<bool> enableFastIronBoots; ConfigVar<bool> enableFastIronBoots;
ConfigVar<bool> canTransformAnywhere; ConfigVar<bool> canTransformAnywhere;
ConfigVar<bool> fastRoll;
ConfigVar<bool> fastSpinner; ConfigVar<bool> fastSpinner;
ConfigVar<bool> freeMagicArmor; ConfigVar<bool> magicArmorNoDrain;
ConfigVar<bool> magicArmorNoDamageLoss;
ConfigVar<bool> magicArmorNoHeavy;
ConfigVar<bool> invincibleEnemies;
// Technical // Technical
ConfigVar<bool> restoreWiiGlitches; ConfigVar<bool> restoreWiiGlitches;
// Controls // Controls
ConfigVar<bool> enableTurboKeybind; ConfigVar<bool> enableTurboKeybind;
ConfigVar<bool> enableResetKeybind;
// Tools // Tools
ConfigVar<bool> speedrunMode; ConfigVar<bool> speedrunMode;
ConfigVar<bool> liveSplitEnabled; ConfigVar<bool> liveSplitEnabled;
ConfigVar<bool> showSpeedrunRTATimer;
ConfigVar<bool> recordingMode; ConfigVar<bool> recordingMode;
ConfigVar<bool> showInputViewer;
ConfigVar<bool> showInputViewerGyro;
} game; } game;
struct { struct {
@@ -184,11 +199,18 @@ struct UserSettings {
ConfigVar<bool> skipPreLaunchUI; ConfigVar<bool> skipPreLaunchUI;
ConfigVar<bool> showPipelineCompilation; ConfigVar<bool> showPipelineCompilation;
ConfigVar<bool> wasPresetChosen; ConfigVar<bool> wasPresetChosen;
ConfigVar<bool> enableCrashReporting;
ConfigVar<bool> checkForUpdates; ConfigVar<bool> checkForUpdates;
ConfigVar<int> cardFileType; ConfigVar<int> cardFileType;
ConfigVar<bool> enableAdvancedSettings; ConfigVar<bool> enableAdvancedSettings;
} backend; } backend;
// Arrays of size 4 for 4 ports
struct {
std::array<ActionBindConfigVar, 4> firstPersonCamera;
std::array<ActionBindConfigVar, 4> callMidna;
std::array<ActionBindConfigVar, 4> openDusklightMenu;
std::array<ActionBindConfigVar, 4> turboSpeedButton;
} actionBindings;
}; };
UserSettings& getSettings(); UserSettings& getSettings();
+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 <cmath>
#include "os_report.h" #include "os_report.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#endif
u32 JUTGamePad::CRumble::sChannelMask[4] = { u32 JUTGamePad::CRumble::sChannelMask[4] = {
PAD_CHAN0_BIT, PAD_CHAN0_BIT,
PAD_CHAN1_BIT, PAD_CHAN1_BIT,
@@ -85,6 +89,9 @@ u32 JUTGamePad::sRumbleSupported;
u32 JUTGamePad::read() { u32 JUTGamePad::read() {
sRumbleSupported = PADRead(mPadStatus); sRumbleSupported = PADRead(mPadStatus);
#if TARGET_PC
dusk::updateActionBindings();
#endif
switch (sClampMode) { switch (sClampMode) {
case EClampStick: case EClampStick:
+1 -1
View File
@@ -1,6 +1,6 @@
# Android Shell # Android Shell
This directory contains a minimal SDLActivity-based Android app wrapper for Dusk. This directory contains a minimal SDLActivity-based Android app wrapper for Dusklight.
## Prerequisites ## Prerequisites
+1 -1
View File
@@ -3,7 +3,7 @@ plugins {
} }
def duskRepoDir = rootProject.projectDir.parentFile.parentFile 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) { def syncDuskAssets = tasks.register('syncDuskAssets', Sync) {
from(new File(duskRepoDir, 'res')) { from(new File(duskRepoDir, 'res')) {
into 'res' into 'res'
@@ -24,6 +24,17 @@
<meta-data android:name="android.game_mode_config" <meta-data android:name="android.game_mode_config"
android:resource="@xml/game_mode_config" /> android:resource="@xml/game_mode_config" />
<provider
android:name="dev.twilitrealm.dusk.DuskDocumentsProvider"
android:authorities="dev.twilitrealm.dusk.documents"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<activity <activity
android:name="dev.twilitrealm.dusk.DuskActivity" android:name="dev.twilitrealm.dusk.DuskActivity"
android:alwaysRetainTaskState="true" android:alwaysRetainTaskState="true"
@@ -1,12 +1,16 @@
package dev.twilitrealm.dusk; package dev.twilitrealm.dusk;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ClipData; import android.content.ClipData;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
@@ -22,6 +26,13 @@ import java.util.List;
public class DuskActivity extends SDLActivity { public class DuskActivity extends SDLActivity {
private static final String TAG = "DuskActivity"; private static final String TAG = "DuskActivity";
private static final int FOLDER_DIALOG_REQUEST_CODE = 0x4455;
private static final String EXTERNAL_STORAGE_AUTHORITY =
"com.android.externalstorage.documents";
private long folderDialogUserdata = 0;
private static native void nativeFolderDialogResult(long userdata, String path, String error);
private static String[] splitArgs(String raw) { private static String[] splitArgs(String raw) {
List<String> out = new ArrayList<>(); List<String> out = new ArrayList<>();
@@ -147,9 +158,154 @@ public class DuskActivity extends SDLActivity {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
persistUriPermissions(data); persistUriPermissions(data);
} }
if (requestCode == FOLDER_DIALOG_REQUEST_CODE) {
finishFolderDialog(resultCode, data);
return;
}
super.onActivityResult(requestCode, resultCode, data); 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) { private void persistUriPermissions(Intent data) {
if (data == null) { if (data == null) {
return; return;
@@ -0,0 +1,467 @@
package dev.twilitrealm.dusk;
import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class DuskDocumentsProvider extends DocumentsProvider {
public static final String AUTHORITY = "dev.twilitrealm.dusk.documents";
private static final String ROOT_ID = "dusk";
private static final String ROOT_DOCUMENT_ID = "root";
private static final String LOCATION_DESCRIPTOR_NAME = "data_location.json";
private static final String DIRECTORY_MIME_TYPE = Document.MIME_TYPE_DIR;
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_FLAGS,
Root.COLUMN_TITLE,
Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_ICON,
Root.COLUMN_AVAILABLE_BYTES,
Root.COLUMN_SUMMARY
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_FLAGS,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_SIZE
};
@Override
public boolean onCreate() {
if (!isCustomDataPathEnabled()) {
ensureUserDirectories();
}
return true;
}
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
if (isCustomDataPathEnabled()) {
return result;
}
final File root = getRootDirectory();
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, ROOT_ID);
row.add(Root.COLUMN_FLAGS,
Root.FLAG_LOCAL_ONLY |
Root.FLAG_SUPPORTS_CREATE |
Root.FLAG_SUPPORTS_IS_CHILD);
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
row.add(Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID);
row.add(Root.COLUMN_ICON, R.mipmap.icon);
row.add(Root.COLUMN_AVAILABLE_BYTES, root.getFreeSpace());
row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.documents_provider_summary));
return result;
}
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
includeDocument(result, documentId, getFileForDocumentId(documentId));
return result;
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
throws FileNotFoundException
{
return queryChildDocumentsInternal(parentDocumentId, projection);
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, Bundle queryArgs)
throws FileNotFoundException
{
return queryChildDocumentsInternal(parentDocumentId, projection);
}
private Cursor queryChildDocumentsInternal(String parentDocumentId, String[] projection)
throws FileNotFoundException
{
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
final File parent = getFileForDocumentId(parentDocumentId);
final File[] files = parent.listFiles();
result.setNotificationUri(getContext().getContentResolver(), getChildDocumentsUri(parentDocumentId));
if (files == null) {
return result;
}
for (File file : files) {
includeDocument(result, getDocumentIdForFile(file), file);
}
return result;
}
@Override
public boolean isChildDocument(String parentDocumentId, String documentId) {
try {
final File parent = getFileForDocumentId(parentDocumentId);
final File child = getFileForDocumentId(documentId);
return isInside(parent, child);
} catch (FileNotFoundException e) {
return false;
}
}
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName)
throws FileNotFoundException
{
final File parent = getFileForDocumentId(parentDocumentId);
if (!parent.isDirectory()) {
throw new FileNotFoundException("Parent is not a directory: " + parentDocumentId);
}
final String safeDisplayName = sanitizeDisplayName(displayName);
final File file = buildUniqueFile(parent, safeDisplayName);
final boolean created;
if (DIRECTORY_MIME_TYPE.equals(mimeType)) {
created = file.mkdir();
} else {
try {
created = file.createNewFile();
} catch (IOException e) {
throw asFileNotFound("Unable to create document", e);
}
}
if (!created) {
throw new FileNotFoundException("Unable to create document: " + displayName);
}
notifyChildrenChanged(parentDocumentId);
return getDocumentIdForFile(file);
}
@Override
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
final File file = getFileForDocumentId(documentId);
if (ROOT_DOCUMENT_ID.equals(documentId)) {
throw new FileNotFoundException("Cannot rename root document");
}
final File target = buildUniqueFile(file.getParentFile(), sanitizeDisplayName(displayName));
final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
if (!file.renameTo(target)) {
throw new FileNotFoundException("Unable to rename document: " + documentId);
}
notifyDocumentChanged(documentId);
notifyDocumentChanged(getDocumentIdForFile(target));
notifyChildrenChanged(parentDocumentId);
return getDocumentIdForFile(target);
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
if (ROOT_DOCUMENT_ID.equals(documentId)) {
throw new FileNotFoundException("Cannot delete root document");
}
final File file = getFileForDocumentId(documentId);
final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
deleteRecursively(file);
notifyDocumentChanged(documentId);
notifyChildrenChanged(parentDocumentId);
}
@Override
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal)
throws FileNotFoundException
{
return ParcelFileDescriptor.open(getFileForDocumentId(documentId), modeToParcelMode(mode));
}
@Override
public AssetFileDescriptor openDocumentThumbnail(String documentId, android.graphics.Point sizeHint,
CancellationSignal signal) throws FileNotFoundException
{
throw new FileNotFoundException("Thumbnails are not supported");
}
private void includeDocument(MatrixCursor result, String documentId, File file) throws FileNotFoundException {
final MatrixCursor.RowBuilder row = result.newRow();
final boolean isDirectory = file.isDirectory();
final String displayName = ROOT_DOCUMENT_ID.equals(documentId)
? getContext().getString(R.string.documents_provider_root_name)
: file.getName();
int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
if (isDirectory) {
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
} else if (file.canWrite()) {
flags |= Document.FLAG_SUPPORTS_WRITE;
}
if (ROOT_DOCUMENT_ID.equals(documentId)) {
flags &= ~(Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME);
}
row.add(Document.COLUMN_DOCUMENT_ID, documentId);
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_MIME_TYPE, isDirectory ? DIRECTORY_MIME_TYPE : getMimeType(file));
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_SIZE, isDirectory ? null : file.length());
}
private File getRootDirectory() throws FileNotFoundException {
if (isCustomDataPathEnabled()) {
throw new FileNotFoundException(
"Dusk DocumentsProvider is disabled while a custom data path is configured");
}
final File root = getContext().getFilesDir();
if (root == null) {
throw new FileNotFoundException("Dusklight files directory is unavailable");
}
return root;
}
private File getFileForDocumentId(String documentId) throws FileNotFoundException {
final File root = getRootDirectory();
if (ROOT_DOCUMENT_ID.equals(documentId)) {
return root;
}
if (!documentId.startsWith(ROOT_DOCUMENT_ID + "/")) {
throw new FileNotFoundException("Invalid document id: " + documentId);
}
final String relativePath = documentId.substring(ROOT_DOCUMENT_ID.length() + 1);
final File file = new File(root, relativePath);
if (!isInside(root, file)) {
throw new FileNotFoundException("Document escapes Dusklight files directory: " + documentId);
}
if (!file.exists()) {
throw new FileNotFoundException("Document does not exist: " + documentId);
}
return file;
}
private String getDocumentIdForFile(File file) throws FileNotFoundException {
final File root = getRootDirectory();
if (sameFile(root, file)) {
return ROOT_DOCUMENT_ID;
}
if (!isInside(root, file)) {
throw new FileNotFoundException("File escapes Dusklight files directory: " + file);
}
final String rootPath = canonicalPath(root);
final String filePath = canonicalPath(file);
return ROOT_DOCUMENT_ID + "/" + filePath.substring(rootPath.length() + 1);
}
private void ensureUserDirectories() {
final File root = getContext().getFilesDir();
if (root == null) {
return;
}
new File(root, "texture_replacements").mkdirs();
new File(root, "USA/Card A").mkdirs();
new File(root, "EUR/Card A").mkdirs();
}
private boolean isCustomDataPathEnabled() {
if (getContext() == null) {
return false;
}
final File filesDir = getContext().getFilesDir();
if (filesDir == null) {
return false;
}
final File descriptor = new File(filesDir, LOCATION_DESCRIPTOR_NAME);
if (!descriptor.isFile()) {
return false;
}
try {
final JSONObject json = new JSONObject(readText(descriptor));
return "custom".equals(json.optString("mode", "default"));
} catch (IOException | JSONException e) {
return false;
}
}
private static String readText(File file) throws IOException {
try (FileInputStream input = new FileInputStream(file);
ByteArrayOutputStream output = new ByteArrayOutputStream())
{
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
return output.toString(StandardCharsets.UTF_8.name());
}
}
private static String[] resolveRootProjection(String[] projection) {
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
}
private static String[] resolveDocumentProjection(String[] projection) {
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
}
private static String sanitizeDisplayName(String displayName) throws FileNotFoundException {
if (displayName == null) {
throw new FileNotFoundException("Document name is empty");
}
final String sanitized = displayName.trim();
if (sanitized.isEmpty() || ".".equals(sanitized) || "..".equals(sanitized) ||
sanitized.contains("/") || sanitized.contains("\\"))
{
throw new FileNotFoundException("Invalid document name: " + displayName);
}
return sanitized;
}
private static File buildUniqueFile(File parent, String displayName) {
File file = new File(parent, displayName);
if (!file.exists()) {
return file;
}
final int dot = displayName.lastIndexOf('.');
final String baseName = dot > 0 ? displayName.substring(0, dot) : displayName;
final String extension = dot > 0 ? displayName.substring(dot) : "";
for (int i = 1; i < 100; ++i) {
file = new File(parent, baseName + " (" + i + ")" + extension);
if (!file.exists()) {
return file;
}
}
return new File(parent, baseName + " (" + System.currentTimeMillis() + ")" + extension);
}
private static int modeToParcelMode(String mode) {
if ("r".equals(mode)) {
return ParcelFileDescriptor.MODE_READ_ONLY;
}
if ("w".equals(mode) || "wt".equals(mode)) {
return ParcelFileDescriptor.MODE_WRITE_ONLY |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE;
}
if ("wa".equals(mode)) {
return ParcelFileDescriptor.MODE_WRITE_ONLY |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_APPEND;
}
if ("rw".equals(mode)) {
return ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE;
}
if ("rwt".equals(mode)) {
return ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE;
}
return ParcelFileDescriptor.MODE_READ_ONLY;
}
private static String getMimeType(File file) {
final int dot = file.getName().lastIndexOf('.');
if (dot >= 0) {
final String extension = file.getName().substring(dot + 1).toLowerCase();
final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mimeType != null) {
return mimeType;
}
}
return "application/octet-stream";
}
private Uri getChildDocumentsUri(String parentDocumentId) {
return DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId);
}
private void notifyChildrenChanged(String parentDocumentId) {
final ContentResolver resolver = getContext().getContentResolver();
resolver.notifyChange(getChildDocumentsUri(parentDocumentId), null, false);
}
private void notifyDocumentChanged(String documentId) {
final ContentResolver resolver = getContext().getContentResolver();
resolver.notifyChange(DocumentsContract.buildDocumentUri(AUTHORITY, documentId), null, false);
}
private static void deleteRecursively(File file) throws FileNotFoundException {
if (file.isDirectory()) {
final File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursively(child);
}
}
}
if (!file.delete()) {
throw new FileNotFoundException("Unable to delete document: " + file);
}
}
private static boolean isInside(File parent, File child) {
try {
final String parentPath = canonicalPath(parent);
final String childPath = canonicalPath(child);
return childPath.equals(parentPath) || childPath.startsWith(parentPath + File.separator);
} catch (FileNotFoundException e) {
return false;
}
}
private static boolean sameFile(File a, File b) {
try {
return canonicalPath(a).equals(canonicalPath(b));
} catch (FileNotFoundException e) {
return false;
}
}
private static String canonicalPath(File file) throws FileNotFoundException {
try {
return file.getCanonicalPath();
} catch (IOException e) {
throw asFileNotFound("Unable to resolve path", e);
}
}
private static FileNotFoundException asFileNotFound(String message, IOException cause) {
final FileNotFoundException exception = new FileNotFoundException(message + ": " + cause.getMessage());
exception.initCause(cause);
return exception;
}
}
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Dusk</string> <string name="app_name">Dusklight</string>
<string name="documents_provider_root_name">Dusklight Data</string>
<string name="documents_provider_summary">Saves, texture packs, settings, and logs</string>
</resources> </resources>
Vendored Regular → Executable
View File
+1 -1
View File
@@ -14,5 +14,5 @@ dependencyResolutionManagement {
} }
} }
rootProject.name = "dusk-android" rootProject.name = "dusklight-android"
include ':app' 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;
+6
View File
@@ -79,5 +79,11 @@
<true/> <true/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>LSSupportsGameMode</key>
<true/>
</dict> </dict>
</plist> </plist>
+3 -1
View File
@@ -13,7 +13,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>Dusk</string> <string>Dusklight</string>
<key>CFBundleIconName</key> <key>CFBundleIconName</key>
<string>Dusk</string> <string>Dusk</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@@ -28,5 +28,7 @@
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<true/> <true/>
<key>LSSupportsGameMode</key>
<true/>
</dict> </dict>
</plist> </plist>
+2
View File
@@ -45,5 +45,7 @@
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIUserInterfaceStyle</key> <key>UIUserInterfaceStyle</key>
<string>Automatic</string> <string>Automatic</string>
<key>LSSupportsGameMode</key>
<true />
</dict> </dict>
</plist> </plist>
@@ -24,9 +24,9 @@ BEGIN
VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0" VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0"
VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0" VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0"
VALUE "FileVersion", "@DUSK_VERSION_STRING@\0" VALUE "FileVersion", "@DUSK_VERSION_STRING@\0"
VALUE "InternalName", "dusk\0" VALUE "InternalName", "dusklight\0"
VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0" VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0"
VALUE "OriginalFilename", "dusk.exe\0" VALUE "OriginalFilename", "dusklight.exe\0"
VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0" VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0"
VALUE "ProductVersion", "@DUSK_VERSION_STRING@\0" VALUE "ProductVersion", "@DUSK_VERSION_STRING@\0"
END END
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; 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] { fps[open] {
display: block; display: block;
} }
+8 -9
View File
@@ -65,10 +65,10 @@ menu {
right: auto; right: auto;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
/* Scale based on a reference screen width, 428/1216 */ /* Scale based on a reference screen width, 856/1216 */
width: 35.230264vw; width: 70.394736vw;
min-width: 428dp; min-width: 428dp;
max-width: 856dp; max-width: 50vw;
height: auto; height: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -83,9 +83,8 @@ body.mirrored menu {
hero { hero {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
align-items: flex-start; align-items: flex-start;
gap: 8dp; gap: 4dp;
} }
body.mirrored hero { body.mirrored hero {
@@ -96,19 +95,19 @@ hero img {
width: 100%; width: 100%;
} }
.eyebrow { eyebrow {
font-family: "Alegreya SC"; font-family: "Alegreya SC";
font-size: 32dp; font-size: 32dp;
} }
@media (min-width: 1216dp) { @media (min-width: 1216dp) {
.eyebrow { eyebrow {
/* Same logic as .menu, 32/1216 */ /* Same logic as .menu, 32/1216 */
font-size: 2.631579vw; font-size: 2.631579vw;
} }
} }
.eyebrow span { eyebrow span {
font-weight: bold; font-weight: bold;
} }
@@ -437,7 +436,7 @@ body.animate-in .intro-item {
decorator: horizontal-gradient(#FEE685FF #FEE68500); decorator: horizontal-gradient(#FEE685FF #FEE68500);
} }
.eyebrow { eyebrow {
display: none; display: none;
} }
+11
View File
@@ -105,6 +105,12 @@ window content pane:last-of-type > div {
line-height: 1.625; line-height: 1.625;
} }
.data-folder-current {
display: block;
font-size: 16dp;
color: rgba(224, 219, 200, 65%);
}
window content pane > spacer { window content pane > spacer {
display: block; display: block;
/* Completes the 24dp bottom inset after the pane's 8dp gap. */ /* Completes the 24dp bottom inset after the pane's 8dp gap. */
@@ -199,6 +205,11 @@ button:not(:disabled):active {
box-shadow: #C2A42D 0 0 0 2dp; box-shadow: #C2A42D 0 0 0 2dp;
} }
button:disabled {
opacity: 0.35;
cursor: default;
}
button.modal-btn { button.modal-btn {
flex: 1 1 0; flex: 1 1 0;
text-align: center; text-align: center;
+58 -9
View File
@@ -51,10 +51,13 @@
#include "d/actor/d_a_ni.h" #include "d/actor/d_a_ni.h"
#include "d/d_s_play.h" #include "d/d_s_play.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#include "dusk/frame_interpolation.h" #include "dusk/frame_interpolation.h"
#include "dusk/settings.h" #include "dusk/settings.h"
#include "res/Object/Alink.h" #include "res/Object/Alink.h"
#include <cstring> #include <cstring>
#endif
static int daAlink_Create(fopAc_ac_c* i_this); static int daAlink_Create(fopAc_ac_c* i_this);
static int daAlink_Delete(daAlink_c* i_this); static int daAlink_Delete(daAlink_c* i_this);
@@ -9363,6 +9366,12 @@ BOOL daAlink_c::spActionTrigger() {
} }
BOOL daAlink_c::midnaTalkTrigger() const { BOOL daAlink_c::midnaTalkTrigger() const {
#if TARGET_PC
// If we have a custom bind for Midna, check that instead
if (dusk::isActionBound(dusk::ActionBinds::CALL_MIDNA, 0)) {
return dusk::getActionBindTrig(dusk::ActionBinds::CALL_MIDNA, 0);
}
#endif
return mItemTrigger & BTN_Z; return mItemTrigger & BTN_Z;
} }
@@ -12729,7 +12738,7 @@ void daAlink_c::setMagicArmorBrk(int i_status) {
BOOL daAlink_c::checkMagicArmorHeavy() const { BOOL daAlink_c::checkMagicArmorHeavy() const {
#if TARGET_PC #if TARGET_PC
return checkMagicArmorWearAbility() && (dComIfGs_getRupee() == 0 && !dusk::getSettings().game.freeMagicArmor); return checkMagicArmorWearAbility() && (dComIfGs_getRupee() == 0 && !dusk::getSettings().game.magicArmorNoHeavy);
#else #else
return checkMagicArmorWearAbility() && dComIfGs_getRupee() == 0; return checkMagicArmorWearAbility() && dComIfGs_getRupee() == 0;
#endif #endif
@@ -16114,6 +16123,9 @@ int daAlink_c::procSlideLand() {
int daAlink_c::procFrontRollInit() { int daAlink_c::procFrontRollInit() {
BOOL is_guard_anime = checkUpperGuardAnime(); BOOL is_guard_anime = checkUpperGuardAnime();
#ifdef TARGET_PC
const f32 fastRollMultiplier = dusk::getSettings().game.fastRoll ? 2.0f : 1.0f;
#endif
if (mProcID == PROC_FRONT_ROLL && mDemo.getDemoMode() == daPy_demo_c::DEMO_FRONT_ROLL_e) { if (mProcID == PROC_FRONT_ROLL && mDemo.getDemoMode() == daPy_demo_c::DEMO_FRONT_ROLL_e) {
return 0; return 0;
@@ -16129,7 +16141,13 @@ int daAlink_c::procFrontRollInit() {
roll_anm_speed = mpHIO->mFrontRoll.m.mRollAnm.mStartFrame; roll_anm_speed = mpHIO->mFrontRoll.m.mRollAnm.mStartFrame;
} }
setSingleAnime(ANM_FRONT_ROLL, mpHIO->mFrontRoll.m.mRollAnm.mSpeed, roll_anm_speed, setSingleAnime(ANM_FRONT_ROLL,
#ifdef TARGET_PC
mpHIO->mFrontRoll.m.mRollAnm.mSpeed * fastRollMultiplier,
#else
mpHIO->mFrontRoll.m.mRollAnm.mSpeed,
#endif
roll_anm_speed,
mpHIO->mFrontRoll.m.mRollAnm.mEndFrame, mpHIO->mFrontRoll.m.mRollAnm.mEndFrame,
mpHIO->mFrontRoll.m.mRollAnm.mInterpolation); mpHIO->mFrontRoll.m.mRollAnm.mInterpolation);
@@ -16145,11 +16163,20 @@ int daAlink_c::procFrontRollInit() {
} }
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) { if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX; #if TARGET_PC
if (!(dusk::getSettings().game.enableFastIronBoots))
#endif
{
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
}
} else if (checkHeavyStateOn(TRUE, TRUE)) { } else if (checkHeavyStateOn(TRUE, TRUE)) {
mNormalSpeed *= mHeavySpeedMultiplier; mNormalSpeed *= mHeavySpeedMultiplier;
} }
#ifdef TARGET_PC
mNormalSpeed *= fastRollMultiplier;
#endif
current.angle.y = shape_angle.y; current.angle.y = shape_angle.y;
voiceStart(Z2SE_AL_V_BACKTEN); voiceStart(Z2SE_AL_V_BACKTEN);
mProcVar2.field_0x300c = 0; mProcVar2.field_0x300c = 0;
@@ -16280,8 +16307,13 @@ int daAlink_c::procFrontRollCrashInit() {
speed.y = mpHIO->mFrontRoll.m.mCrashSpeedV; speed.y = mpHIO->mFrontRoll.m.mCrashSpeedV;
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) { if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX; #if TARGET_PC
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY; if (!(dusk::getSettings().game.enableFastIronBoots))
#endif
{
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY;
}
} }
ANGLE_ADD_2(current.angle.y, 0x8000); ANGLE_ADD_2(current.angle.y, 0x8000);
@@ -16375,6 +16407,9 @@ int daAlink_c::procFrontRollSuccess() {
int daAlink_c::procSideRollInit(int param_0) { int daAlink_c::procSideRollInit(int param_0) {
BOOL is_prev_guardAnm = checkUpperGuardAnime(); BOOL is_prev_guardAnm = checkUpperGuardAnime();
#ifdef TARGET_PC
const f32 fastRollMultiplier = dusk::getSettings().game.fastRoll ? 2.0f : 1.0f;
#endif
if (!commonProcInitNotSameProc(PROC_SIDE_ROLL)) { if (!commonProcInitNotSameProc(PROC_SIDE_ROLL)) {
return 0; return 0;
@@ -16391,17 +16426,30 @@ int daAlink_c::procSideRollInit(int param_0) {
current.angle.y = shape_angle.y + -0x4000; current.angle.y = shape_angle.y + -0x4000;
} }
setSingleAnime(anmID, mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed, setSingleAnime(anmID,
#ifdef TARGET_PC
mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed * fastRollMultiplier,
#else
mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed,
#endif
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mStartFrame, mpHIO->mGuard.mTurnMove.m.mTurnAnm.mStartFrame,
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mEndFrame, mpHIO->mGuard.mTurnMove.m.mTurnAnm.mEndFrame,
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mInterpolation); mpHIO->mGuard.mTurnMove.m.mTurnAnm.mInterpolation);
mNormalSpeed = mpHIO->mGuard.mTurnMove.m.mSideRollSpeed; mNormalSpeed = mpHIO->mGuard.mTurnMove.m.mSideRollSpeed;
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) { if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX; #if TARGET_PC
if (!(dusk::getSettings().game.enableFastIronBoots))
#endif
{
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
}
} else if (checkHeavyStateOn(TRUE, TRUE)) { } else if (checkHeavyStateOn(TRUE, TRUE)) {
mNormalSpeed *= mHeavySpeedMultiplier; mNormalSpeed *= mHeavySpeedMultiplier;
} }
#ifdef TARGET_PC
mNormalSpeed *= fastRollMultiplier;
#endif
setFootEffectProcType(0); setFootEffectProcType(0);
field_0x2f9d = 4; field_0x2f9d = 4;
@@ -18663,7 +18711,7 @@ int daAlink_c::execute() {
#if TARGET_PC #if TARGET_PC
// This handles rupee drain and transitions between rupees/no rupees // This handles rupee drain and transitions between rupees/no rupees
// We can skip all of that if the magic armor doesn't use rupees // We can skip all of that if the magic armor doesn't use rupees
if (!dusk::getSettings().game.freeMagicArmor && checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) { if (!dusk::getSettings().game.magicArmorNoDrain && checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) {
#else #else
if (checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) { if (checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) {
#endif #endif
@@ -18676,7 +18724,8 @@ int daAlink_c::execute() {
} }
} }
if (dComIfGs_getRupee() == 0 && field_0x2fd7 != 0) { if (dComIfGs_getRupee() == 0 && field_0x2fd7 != 0 IF_DUSK( && !dusk::getSettings().game.magicArmorNoHeavy))
{
setMagicArmorBrk(0); setMagicArmorBrk(0);
seStartOnlyReverb(Z2SE_AL_M_ARMER_TURNOFF); seStartOnlyReverb(Z2SE_AL_M_ARMER_TURNOFF);
mZ2Link.setLinkState(5); mZ2Link.setLinkState(5);
+1 -1
View File
@@ -192,7 +192,7 @@ int daAlink_c::setDamagePoint(int i_dmgAmount, BOOL i_checkZoraMag, BOOL i_setDm
if (checkMagicArmorNoDamage()) { if (checkMagicArmorNoDamage()) {
#if TARGET_PC #if TARGET_PC
if(dusk::getSettings().game.freeMagicArmor) { if(dusk::getSettings().game.magicArmorNoDamageLoss) {
i_dmgAmount = 0; i_dmgAmount = 0;
} }
#endif #endif
+1
View File
@@ -25,6 +25,7 @@
#include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h" #include "dusk/settings.h"
#include "dusk/speedrun.h"
BOOL daAlink_c::checkEventRun() const { BOOL daAlink_c::checkEventRun() const {
return dComIfGp_event_runCheck() || checkPlayerDemoMode(); return dComIfGp_event_runCheck() || checkPlayerDemoMode();
+6 -3
View File
@@ -12,6 +12,7 @@
#if TARGET_PC #if TARGET_PC
#include "dusk/gyro.h" #include "dusk/gyro.h"
#include "dusk/action_bindings.h"
#endif #endif
bool daAlink_c::checkNoSubjectModeCamera() { bool daAlink_c::checkNoSubjectModeCamera() {
@@ -119,8 +120,8 @@ BOOL daAlink_c::setBodyAngleToCamera() {
var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c); var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c);
} }
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle)); shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f)));
sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle)); sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f)));
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) { if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x; sp8 = mBodyAngle.x;
@@ -192,7 +193,9 @@ BOOL daAlink_c::subjectCancelTrigger() {
BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) { BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) {
setDoStatus(BUTTON_STATUS_BACK); setDoStatus(BUTTON_STATUS_BACK);
if (checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) { // Allow pressing the first person binding to also leave first person
if (IF_DUSK(dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, 0)) ||
checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
if (i_isPlaySe) { if (i_isPlaySe) {
seStartSystem(Z2SE_SUBJ_VIEW_OUT); seStartSystem(Z2SE_SUBJ_VIEW_OUT);
} }
+6 -1
View File
@@ -77,7 +77,12 @@ int daAlink_c::loadModelDVD() {
mpWlMidnaHairModel = NULL; mpWlMidnaHairModel = NULL;
if (!checkNoResetFlg2(FLG2_UNK_280000)) { if (!checkNoResetFlg2(FLG2_UNK_280000)) {
dComIfG_resDelete(&mPhaseReq, mArcName); if (!dComIfG_resDelete(&mPhaseReq, mArcName)) {
#if TARGET_PC
// resDelete no-ops if load was in-progress; force-unregister before freeAll
dComIfG_deleteObjectResMain(mArcName);
#endif
}
cPhs_Reset(&mPhaseReq); cPhs_Reset(&mPhaseReq);
mpArcHeap->freeAll(); mpArcHeap->freeAll();
+8 -2
View File
@@ -348,7 +348,7 @@ void daAlink_c::changeLink(int param_0) {
initModel(static_cast<J3DModelData*>(dComIfG_getObjectRes(l_mArcName, "al_hands.bmd")), 0); initModel(static_cast<J3DModelData*>(dComIfG_getObjectRes(l_mArcName, "al_hands.bmd")), 0);
#if TARGET_PC #if TARGET_PC
if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.freeMagicArmor) if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.magicArmorNoHeavy)
#else #else
if (dComIfGs_getRupee() != 0) if (dComIfGs_getRupee() != 0)
#endif #endif
@@ -458,7 +458,7 @@ void daAlink_c::changeLink(int param_0) {
field_0x06f0 = field_0x064C->getMaterialNodePointer(2)->getShape(); field_0x06f0 = field_0x064C->getMaterialNodePointer(2)->getShape();
#if TARGET_PC #if TARGET_PC
if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.freeMagicArmor) { if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.magicArmorNoHeavy) {
#else #else
if (dComIfGs_getRupee() != 0) { if (dComIfGs_getRupee() != 0) {
#endif #endif
@@ -8723,6 +8723,12 @@ int daAlink_c::procWolfCargoCarry() {
return checkNextActionWolf(0); return checkNextActionWolf(0);
} }
#if TARGET_PC
if (field_0x280c.getActor() == NULL) {
return checkNextActionWolf(0);
}
#endif
mDoMtx_stack_c::copy(((e_yc_class*)field_0x280c.getActor())->getLegR3Mtx()); mDoMtx_stack_c::copy(((e_yc_class*)field_0x280c.getActor())->getLegR3Mtx());
mDoMtx_stack_c::transM(-9.0f, -7.0f, -30.0f); mDoMtx_stack_c::transM(-9.0f, -7.0f, -30.0f);
mDoMtx_stack_c::multVecZero(&current.pos); mDoMtx_stack_c::multVecZero(&current.pos);
+5 -1
View File
@@ -11,7 +11,9 @@
#include "d/d_msg_string.h" #include "d/d_msg_string.h"
#include "dusk/livesplit.h" #include "dusk/livesplit.h"
#include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/speedrun.h"
#include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_controller_pad.h"
#include <dusk/autosave.h>
dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) { dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) {
mArchive = i_archive; mArchive = i_archive;
@@ -146,10 +148,12 @@ void dBrightCheck_c::modeMove() {
if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) { if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) {
// start a new run if a run isn't already in progress // start a new run if a run isn't already in progress
if (!dusk::m_speedrunInfo.m_isRunStarted) { if (!dusk::m_speedrunInfo.m_isRunStarted) {
dusk::ImGuiMenuGame::resetForSpeedrunMode(); dusk::resetForSpeedrunMode();
dusk::m_speedrunInfo.startRun(); dusk::m_speedrunInfo.startRun();
} }
} }
toggleAutoSave(true);
#endif #endif
mCompleteCheck = true; mCompleteCheck = true;
mMode = MODE_WAIT_e; mMode = MODE_WAIT_e;
+96 -59
View File
@@ -31,6 +31,7 @@
#if TARGET_PC #if TARGET_PC
#include "dusk/frame_interpolation.h" #include "dusk/frame_interpolation.h"
#include "dusk/logging.h" #include "dusk/logging.h"
#include "dusk/action_bindings.h"
#include "imgui.h" #include "imgui.h"
#endif #endif
@@ -838,6 +839,12 @@ void dCamera_c::updatePad() {
mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false; mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false;
#if TARGET_PC #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) { if (mCamParam.mManualMode) {
return; return;
} }
@@ -877,7 +884,8 @@ void dCamera_c::updatePad() {
if (mPadInfo.mCStick.mLastPosY < -mCamSetup.mCStick.SwTHH()) { if (mPadInfo.mCStick.mLastPosY < -mCamSetup.mCStick.SwTHH()) {
if (mCStickYState != -1) { 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; mGear = 0;
setComStat(0x2000); setComStat(0x2000);
} else if (mGear == 0 && sp6C) { } else if (mGear == 0 && sp6C) {
@@ -888,7 +896,8 @@ void dCamera_c::updatePad() {
mCStickYState = -1; mCStickYState = -1;
} else if (mPadInfo.mCStick.mLastPosY > mCamSetup.mCStick.SwTHH()) { } else if (mPadInfo.mCStick.mLastPosY > mCamSetup.mCStick.SwTHH()) {
if (mCStickYState != 1) { 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); setComStat(0x1000);
} else if (mGear == 1) { } else if (mGear == 1) {
mGear = 0; mGear = 0;
@@ -7648,7 +7657,11 @@ bool dCamera_c::freeCamera() {
cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f}; 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); 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; mCamParam.mManualMode = 1;
camMovement = camMovement.normalize(); camMovement = camMovement.normalize();
camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f; camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
@@ -11231,6 +11244,62 @@ cXyz dCamera_c::Center() {
return mCenter + mShake.field_0x24; 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) { static int camera_execute(camera_process_class* i_this) {
preparation(i_this); preparation(i_this);
@@ -11251,6 +11320,28 @@ static int camera_execute(camera_process_class* i_this) {
store(i_this); store(i_this);
#ifdef TARGET_PC #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 // record new camera for our sim frame
dusk::frame_interp::record_camera(i_this, get_camera_id(i_this)); 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 // 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; 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) { static int camera_draw(camera_process_class* i_this) {
camera_class* a_this = (camera_class*)i_this; camera_class* a_this = (camera_class*)i_this;
dCamera_c* body = &i_this->mCamera; dCamera_c* body = &i_this->mCamera;
@@ -11334,42 +11405,8 @@ static int camera_draw(camera_process_class* i_this) {
} }
#endif #endif
#if TARGET_PC #if !TARGET_PC
set_ar_corrected_trim(window, body->TrimHeight()); // trim handling moved to camera_execute for PC
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
int trim_height = body->TrimHeight(); int trim_height = body->TrimHeight();
window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f); 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" #include "f_op/f_op_actor_mng.h"
#if TARGET_PC #if TARGET_PC
#include "dusk/achievements.h" #include "dusk/achievements.h"
#include "dusk/settings.h"
#endif #endif
static int plCutLRC[58] = { 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) { if (i_AtInfo->mAttackPower != 0) {
i_enemy->health -= i_AtInfo->mAttackPower; i_enemy->health -= i_AtInfo->mAttackPower;
} }
+4
View File
@@ -3882,7 +3882,11 @@ bool dCamera_c::hintTalkEvCamera() {
cSAngle acStack_1fc(20.0f); cSAngle acStack_1fc(20.0f);
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
#if AVOID_UB
for (j = 0; j < 10; j++) {
#else
for (j = 0; j < 12; j++) { for (j = 0; j < 12; j++) {
#endif
cSAngle acStack_200(local_b0[j] * fVar22); cSAngle acStack_200(local_b0[j] * fVar22);
hintTalk->mDirection.U(acStack_1f8 + acStack_200); hintTalk->mDirection.U(acStack_1f8 + acStack_200);
hintTalk->mDirection.V(((hintTalk->field_0x28.V() * acStack_200.Cos()) * 0.2f) + acStack_1fc); hintTalk->mDirection.V(((hintTalk->field_0x28.V() * acStack_200.Cos()) * 0.2f) + acStack_1fc);
+10 -1
View File
@@ -36,6 +36,7 @@
#include "dusk/imgui/ImGuiBloomWindow.hpp" #include "dusk/imgui/ImGuiBloomWindow.hpp"
#include "dusk/settings.h" #include "dusk/settings.h"
#include "dusk/frame_interpolation.h" #include "dusk/frame_interpolation.h"
#include "dusk/game_clock.h"
#endif #endif
static void GxXFog_set(); 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); color_b_start_p->b, color_b_end_p->b, blend_ratio, add_col.b, scale);
} }
void dScnKy_env_light_c::setLight() { void dScnKy_env_light_c::setLight() {
f32 color_ratio; f32 color_ratio;
@@ -2513,7 +2515,14 @@ void dScnKy_env_light_c::setLight() {
static s16 S_fuwan_sin; static s16 S_fuwan_sin;
f32 sin = cM_ssin(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)); blure_size += (u8)(sin * (0.2f * blure_size));
} }
+26 -1
View File
@@ -152,7 +152,7 @@ u8 STControl::checkTrigger() {
field_0x22 = -field_0x24; field_0x22 = -field_0x24;
} }
} }
#if !TARGET_PC
if (!(mDirectionTrig & 3)) { if (!(mDirectionTrig & 3)) {
Xinit(); Xinit();
} }
@@ -160,10 +160,35 @@ u8 STControl::checkTrigger() {
if (!(mDirectionTrig & 0xC)) { if (!(mDirectionTrig & 0xC)) {
Yinit(); Yinit();
} }
#endif
} else { } else {
mDirectionTrig = 0; mDirectionTrig = 0;
#if !TARGET_PC
Xinit(); Xinit();
Yinit(); 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) { 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; field_0x91 = 0;
m_mySelfPointer = this; m_mySelfPointer = this;
#endif #endif
#if TARGET_PC
previousMirror = dusk::getSettings().game.enableMirrorMode;
#endif
m_res = JKR_NEW_ARGS (0x20) dMap_prm_res_s; m_res = JKR_NEW_ARGS (0x20) dMap_prm_res_s;
JUT_ASSERT(2559, m_res != NULL); 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) { 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) { if (mStayRoomNo == -1) {
mStayRoomNo = i_roomNo; mStayRoomNo = i_roomNo;
field_0x80 = mStayRoomNo; field_0x80 = mStayRoomNo;
+1 -1
View File
@@ -139,7 +139,7 @@ bool dMenu_Fishing_c::isSync() {
void dMenu_Fishing_c::init() { void dMenu_Fishing_c::init() {
#if TARGET_PC || VERSION == VERSION_GCN_PAL #if TARGET_PC || VERSION == VERSION_GCN_PAL
BOOL isEnglish = FALSE; 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; isEnglish = TRUE;
} }
#endif #endif
+5 -1
View File
@@ -11,6 +11,7 @@
#include "d/d_s_name.h" #include "d/d_s_name.h"
#include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/memory.h" #include "dusk/memory.h"
#include "dusk/speedrun.h"
#include "dusk/settings.h" #include "dusk/settings.h"
#include "f_op/f_op_overlap_mng.h" #include "f_op/f_op_overlap_mng.h"
#include "f_op/f_op_scene_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_machine.h"
#include "m_Do/m_Do_main.h" #include "m_Do/m_Do_main.h"
#include "m_Do/m_Do_mtx.h" #include "m_Do/m_Do_mtx.h"
#include <dusk/autosave.h>
#if TARGET_PC #if TARGET_PC
#define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen) #define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen)
@@ -418,10 +420,12 @@ void dScnName_c::changeGameScene() {
if (dusk::getSettings().game.speedrunMode && dusk::getSettings().game.hideTvSettingsScreen) { if (dusk::getSettings().game.speedrunMode && dusk::getSettings().game.hideTvSettingsScreen) {
// start a new run on file load if a run isn't already in progress // start a new run on file load if a run isn't already in progress
if (!dusk::m_speedrunInfo.m_isRunStarted) { if (!dusk::m_speedrunInfo.m_isRunStarted) {
dusk::ImGuiMenuGame::resetForSpeedrunMode(); dusk::resetForSpeedrunMode();
dusk::m_speedrunInfo.startRun(); dusk::m_speedrunInfo.startRun();
} }
} }
toggleAutoSave(true);
#endif #endif
} }
} }
+4
View File
@@ -1042,6 +1042,10 @@ static BOOL heapSizeCheck() {
bool dScnPly_c::resetGame() { bool dScnPly_c::resetGame() {
if (fpcM_GetName(this) == fpcNm_OPENING_SCENE_e) { if (fpcM_GetName(this) == fpcNm_OPENING_SCENE_e) {
#if TARGET_PC
toggleAutoSave(false);
#endif
if (!dStage_roomControl_c::resetArchiveBank(0)) { if (!dStage_roomControl_c::resetArchiveBank(0)) {
return false; return false;
} }
+5
View File
@@ -53,6 +53,11 @@ static std::string FormatToString(const char* msg, va_list list) {
size *= 2; size *= 2;
} }
} }
while (!str.empty() && str[str.size()-1] == '\n') {
str.pop_back();
}
return str; return str;
} }
+8 -2
View File
@@ -692,6 +692,12 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
return; return;
} }
// prevent stuff like https://github.com/TwilitRealm/dusklight/issues/949
if (link->getDemoMode() != 0) {
inJump = false;
return;
}
if (!inJump) { if (!inJump) {
if (link->mProcID == daAlink_c::PROC_CUT_JUMP) { if (link->mProcID == daAlink_c::PROC_CUT_JUMP) {
inJump = true; inJump = true;
@@ -1030,7 +1036,7 @@ void AchievementSystem::load() {
return; return;
} }
try { try {
auto data = io::FileStream::ReadAllBytes(filePath.string().c_str()); auto data = io::FileStream::ReadAllBytes(filePath);
auto j = json::parse(data); auto j = json::parse(data);
if (!j.is_object()) { if (!j.is_object()) {
return; return;
@@ -1067,7 +1073,7 @@ void AchievementSystem::save() {
} }
try { try {
io::FileStream::WriteAllText( io::FileStream::WriteAllText(
(dusk::ConfigPath / ACHIEVEMENTS_FILENAME).string().c_str(), dusk::ConfigPath / ACHIEVEMENTS_FILENAME,
j.dump(2) j.dump(2)
); );
} catch (const std::exception&) {} } catch (const std::exception&) {}
+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 "dusk/ui/ui.hpp"
#include "imgui/ImGuiConsole.hpp" #include "imgui/ImGuiConsole.hpp"
bool shouldAutoSave = false;
u8 mSaveBuffer[QUEST_LOG_SIZE * 3]; u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
u8 mAutoSaveProc = 0; u8 mAutoSaveProc = 0;
int autoSaveWriteState = 0; int autoSaveWriteState = 0;
@@ -14,7 +15,7 @@ static AutoSaveFuncs AutoSaveFuncsProc[] = {
void noAutoSave() {} void noAutoSave() {}
void triggerAutoSave() { void triggerAutoSave() {
if (dusk::getSettings().game.autoSave && mAutoSaveProc == 0 && if (dusk::getSettings().game.autoSave && shouldAutoSave && mAutoSaveProc == 0 &&
strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0) strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0)
{ {
mAutoSaveProc = 1; mAutoSaveProc = 1;
@@ -25,8 +26,12 @@ void updateAutoSave() {
(AutoSaveFuncsProc[mAutoSaveProc])(); (AutoSaveFuncsProc[mAutoSaveProc])();
} }
void writeAutoSave() { bool writeAutoSave() {
int stageNo = dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo()); stage_stag_info_class* stagInfo = dComIfGp_getStageStagInfo();
if (stagInfo == nullptr) {
return false;
}
int stageNo = dStage_stagInfo_GetSaveTbl(stagInfo);
dComIfGs_putSave(stageNo); dComIfGs_putSave(stageNo);
dComIfGs_setMemoryToCard(mSaveBuffer, dComIfGs_getDataNum()); dComIfGs_setMemoryToCard(mSaveBuffer, dComIfGs_getDataNum());
@@ -39,6 +44,7 @@ void writeAutoSave() {
} }
g_mDoMemCd_control.save(mSaveBuffer, sizeof(mSaveBuffer), 0); g_mDoMemCd_control.save(mSaveBuffer, sizeof(mSaveBuffer), 0);
return true;
} }
void autoSaving() { void autoSaving() {
@@ -47,8 +53,9 @@ void autoSaving() {
if (cardState == 2) { if (cardState == 2) {
mAutoSaveProc = 1; mAutoSaveProc = 1;
} else if (cardState == 1) { } else if (cardState == 1) {
writeAutoSave(); if (writeAutoSave()) {
mAutoSaveProc = 3; mAutoSaveProc = 3;
}
} }
} }
} }
@@ -90,3 +97,7 @@ void endAutoSave() {
}); });
mAutoSaveProc = 0; mAutoSaveProc = 0;
} }
void toggleAutoSave(bool enabled) {
shouldAutoSave = enabled;
}
+24 -7
View File
@@ -11,6 +11,7 @@
#include <string> #include <string>
#include "dusk/main.h" #include "dusk/main.h"
#include "dusk/action_bindings.h"
using namespace dusk::config; using namespace dusk::config;
@@ -23,8 +24,8 @@ aurora::Module DuskConfigLog("dusk::config");
static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars; static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars;
static bool RegistrationDone = false; static bool RegistrationDone = false;
static std::string GetConfigJsonPath() { static std::u8string GetConfigJsonPath() {
return (dusk::ConfigPath / ConfigFileName).string(); return (dusk::ConfigPath / ConfigFileName).u8string();
} }
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) { ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {
@@ -60,7 +61,7 @@ void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
template<ConfigValue T> template<ConfigValue T>
nlohmann::json ConfigImpl<T>::dumpToJson(const ConfigVar<T>& cVar) { 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> template<ConfigValue T> requires std::is_integral_v<T> && std::is_signed_v<T>
@@ -189,7 +190,7 @@ void dusk::config::LoadFromUserPreferences() {
if (configJsonPath.empty()) { if (configJsonPath.empty()) {
return; return;
} }
LoadFromFileName(configJsonPath.c_str()); LoadFromFileName(reinterpret_cast<const char*>(configJsonPath.c_str()));
} }
static void LoadFromPath(const char* path) { static void LoadFromPath(const char* path) {
@@ -241,17 +242,27 @@ void dusk::config::Save() {
return; return;
} }
DuskConfigLog.info("Saving config to '{}'", configJsonPath); DuskConfigLog.info(
"Saving config to '{}'",
reinterpret_cast<const char*>(configJsonPath.c_str()));
json j; json j;
for (const auto& pair : RegisteredConfigVars) { 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); j[pair.first] = pair.second->getImpl()->dumpToJson(*pair.second);
} }
} }
io::FileStream::WriteAllText(configJsonPath.c_str(), j.dump(4)); 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) { ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
@@ -262,3 +273,9 @@ ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
return nullptr; 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/dusk.h"
#include "dusk/logging.h" #include "dusk/logging.h"
#include "dusk/main.h" #include "dusk/main.h"
#include "dusk/settings.h"
#include "version.h" #include "version.h"
#include <cstdlib> #include <cstdlib>
@@ -13,114 +12,83 @@
#include <string_view> #include <string_view>
#include <system_error> #include <system_error>
#include "SDL3/SDL_filesystem.h"
#if DUSK_ENABLE_SENTRY_NATIVE #if DUSK_ENABLE_SENTRY_NATIVE
#include <sentry.h> #include <sentry.h>
#endif #endif
namespace dusk { namespace dusk::crash_reporting {
namespace { namespace {
#if DUSK_ENABLE_SENTRY_NATIVE #if DUSK_ENABLE_SENTRY_NATIVE
bool g_sentryInitialized = false; bool g_sentryInitialized = false;
bool IsTruthy(std::string_view value) { bool truthy(std::string_view value) {
return value == "1" || value == "true" || value == "TRUE" || value == "yes" return value == "1" || value == "true" || value == "TRUE" || value == "yes" || value == "YES" ||
|| value == "YES" || value == "on" || value == "ON"; 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)) { if (const char* value = std::getenv(name)) {
return value; return value;
} }
return {}; return {};
} }
bool GetEffectiveEnabled() { bool disabled_by_env() {
const std::string env = GetEnvOrEmpty("DUSK_SENTRY_ENABLED"); const std::string env = env_or_empty("DUSK_SENTRY_ENABLED");
if (!env.empty()) { return !env.empty() && !truthy(env);
return IsTruthy(env);
}
return getSettings().backend.enableCrashReporting;
} }
std::string GetEffectiveDsn() { std::string effective_dsn() {
const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DSN"); const std::string env = env_or_empty("DUSK_SENTRY_DSN");
if (!env.empty()) { if (!env.empty()) {
return env; return env;
} }
return DUSK_SENTRY_DSN; return DUSK_SENTRY_DSN;
} }
bool GetEffectiveDebug() { bool effective_debug() {
const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DEBUG"); const std::string env = env_or_empty("DUSK_SENTRY_DEBUG");
if (!env.empty()) { if (!env.empty()) {
return IsTruthy(env); return truthy(env);
} }
return false; return false;
} }
std::string GetReleaseName() { std::string release_name() {
return std::string(AppName) + "@" DUSK_WC_DESCRIBE; return std::string(AppName) + "@" DUSK_WC_DESCRIBE;
} }
std::filesystem::path GetSentryDatabasePath() { std::filesystem::path sentry_database_path() {
return dusk::ConfigPath / "sentry"; return dusk::CachePath / "sentry";
} }
std::filesystem::path GetLogAttachmentPath() { std::filesystem::path log_attachment_path() {
if (const char* logPath = GetLogFilePath()) { if (const char* logPath = GetLogFilePath()) {
return logPath; return logPath;
} }
return {}; return {};
} }
std::filesystem::path GetCrashpadHandlerPath() { void configure_path_options(sentry_options_t* options) {
const char* basePath = SDL_GetBasePath(); const auto databasePath = sentry_database_path();
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();
std::error_code ec; std::error_code ec;
std::filesystem::create_directories(databasePath, ec); std::filesystem::create_directories(databasePath, ec);
if (ec) { if (ec) {
DuskLog.warn("Unable to create Sentry database path '{}': {}", DuskLog.warn(
databasePath.string(), ec.message()); "Unable to create Sentry database path '{}': {}", databasePath.string(), ec.message());
} }
#if _WIN32 #if _WIN32
const std::wstring databasePathWide = databasePath.wstring(); const std::wstring databasePathWide = databasePath.wstring();
sentry_options_set_database_pathw(options, databasePathWide.c_str()); 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 #else
const std::string databasePathUtf8 = databasePath.string(); const std::string databasePathUtf8 = databasePath.string();
sentry_options_set_database_path(options, databasePathUtf8.c_str()); 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 #endif
const auto logPath = GetLogAttachmentPath(); const auto logPath = log_attachment_path();
if (!logPath.empty()) { if (!logPath.empty()) {
#if _WIN32 #if _WIN32
sentry_options_add_attachmentw(options, logPath.wstring().c_str()); sentry_options_add_attachmentw(options, logPath.wstring().c_str());
@@ -133,32 +101,29 @@ void ConfigurePathOptions(sentry_options_t* options) {
} // namespace } // namespace
void InitializeCrashReporting() { void initialize() {
#if DUSK_ENABLE_SENTRY_NATIVE #if DUSK_ENABLE_SENTRY_NATIVE
if (g_sentryInitialized) { if (g_sentryInitialized || disabled_by_env()) {
return; return;
} }
if (!GetEffectiveEnabled()) { const std::string dsn = effective_dsn();
return;
}
const std::string dsn = GetEffectiveDsn();
if (dsn.empty()) { if (dsn.empty()) {
DuskLog.warn("Crash reporting is enabled but no Sentry DSN is configured"); DuskLog.warn("Crash reporting is enabled but no Sentry DSN is configured");
return; return;
} }
const std::string release = GetReleaseName(); const std::string release = release_name();
sentry_options_t* options = sentry_options_new(); sentry_options_t* options = sentry_options_new();
sentry_options_set_dsn(options, dsn.c_str()); sentry_options_set_dsn(options, dsn.c_str());
sentry_options_set_release(options, release.c_str()); sentry_options_set_release(options, release.c_str());
sentry_options_set_environment(options, DUSK_SENTRY_ENVIRONMENT); 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_cache_keep(options, 1);
sentry_options_set_max_breadcrumbs(options, 100); sentry_options_set_max_breadcrumbs(options, 100);
ConfigurePathOptions(options); configure_path_options(options);
if (sentry_init(options) != 0) { if (sentry_init(options) != 0) {
DuskLog.warn("Failed to initialize Sentry crash reporting"); DuskLog.warn("Failed to initialize Sentry crash reporting");
@@ -173,7 +138,7 @@ void InitializeCrashReporting() {
#endif #endif
} }
void ShutdownCrashReporting() { void shutdown() {
#if DUSK_ENABLE_SENTRY_NATIVE #if DUSK_ENABLE_SENTRY_NATIVE
if (!g_sentryInitialized) { if (!g_sentryInitialized) {
return; return;
@@ -184,4 +149,40 @@ void ShutdownCrashReporting() {
#endif #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{}; rpc::Presence presence{};
presence.startTimestamp = g_startTime; presence.startTimestamp = g_startTime;
presence.largeImageKey = "icon"; presence.largeImageKey = "icon";
presence.largeImageText = "Dusk"; presence.largeImageText = "Dusklight";
if (IsGameLaunched) { if (IsGameLaunched) {
const char* stageName = dComIfGp_getLastPlayStageName(); const char* stageName = dComIfGp_getLastPlayStageName();
+124 -6
View File
@@ -5,6 +5,7 @@
#include <SDL3/SDL_dialog.h> #include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_error.h> #include <SDL3/SDL_error.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_stdinc.h> #include <SDL3/SDL_stdinc.h>
#if defined(__ANDROID__) || defined(ANDROID) #if defined(__ANDROID__) || defined(ANDROID)
@@ -16,6 +17,12 @@
#include <TargetConditionals.h> #include <TargetConditionals.h>
#endif #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 #if defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST
#define USE_IOS_DIALOG 1 #define USE_IOS_DIALOG 1
#include "ios/FileSelectDialog.h" #include "ios/FileSelectDialog.h"
@@ -23,6 +30,13 @@
#define USE_IOS_DIALOG 0 #define USE_IOS_DIALOG 0
#endif #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 dusk {
namespace { namespace {
@@ -32,6 +46,10 @@ std::string fallback_display_name(std::string_view path) {
} }
std::string pathString(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("/\\"); const std::size_t slash = pathString.find_last_of("/\\");
if (slash == std::string::npos || slash + 1 >= pathString.size()) { if (slash == std::string::npos || slash + 1 >= pathString.size()) {
return pathString; return pathString;
@@ -98,8 +116,7 @@ std::string android_display_name(std::string_view path) {
return {}; return {};
} }
auto* displayName = auto* displayName = static_cast<jstring>(env->CallObjectMethod(activity, getDisplayName, uri));
static_cast<jstring>(env->CallObjectMethod(activity, getDisplayName, uri));
env->DeleteLocalRef(uri); env->DeleteLocalRef(uri);
env->DeleteLocalRef(activity); env->DeleteLocalRef(activity);
if (displayName == nullptr || clear_pending_exception(env)) { if (displayName == nullptr || clear_pending_exception(env)) {
@@ -110,6 +127,76 @@ std::string android_display_name(std::string_view path) {
env->DeleteLocalRef(displayName); env->DeleteLocalRef(displayName);
return result; 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 #endif
#if USE_IOS_DIALOG #if USE_IOS_DIALOG
@@ -159,8 +246,8 @@ void onSDLDialogFinished(void* userdata, const char* const* filelist, [[maybe_un
} // namespace } // namespace
void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window, void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
const SDL_DialogFileFilter* filters, int nfilters, const char* default_location, const SDL_DialogFileFilter* filters, int nfilters, const char* default_location,
bool allow_many) { bool allow_many) {
if (callback == nullptr) { if (callback == nullptr) {
return; return;
} }
@@ -171,14 +258,45 @@ void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
state->userdata = userdata; state->userdata = userdata;
Dusk_iOS_ShowFileSelect(&onIOSDialogFinished, state.release(), window, filters, nfilters, Dusk_iOS_ShowFileSelect(&onIOSDialogFinished, state.release(), window, filters, nfilters,
default_location, allow_many); default_location, allow_many);
#else #else
auto state = std::make_unique<SDLDialogCallbackState>(); auto state = std::make_unique<SDLDialogCallbackState>();
state->callback = callback; state->callback = callback;
state->userdata = userdata; state->userdata = userdata;
SDL_ShowOpenFileDialog(&onSDLDialogFinished, state.release(), window, filters, nfilters, 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 #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, void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
const SDL_DialogFileFilter* filters, int nfilters, const char* default_location, const SDL_DialogFileFilter* filters, int nfilters, const char* default_location,
bool allow_many); 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); 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
+4 -4
View File
@@ -140,8 +140,8 @@ void read(float dt) {
float my_rel = 0.0f; float my_rel = 0.0f;
SDL_GetRelativeMouseState(&mx_rel, &my_rel); SDL_GetRelativeMouseState(&mx_rel, &my_rel);
// Convert pixels to radians // Convert pixels to radians
s_pitch_rad = my_rel * kMousePixelToRad * getSettings().game.gyroSensitivityX; s_pitch_rad = my_rel * kMousePixelToRad * getSettings().game.gyroSensitivityY;
s_yaw_rad = -mx_rel * kMousePixelToRad * getSettings().game.gyroSensitivityY; s_yaw_rad = -mx_rel * kMousePixelToRad * getSettings().game.gyroSensitivityX;
s_roll_rad = 0.0f; s_roll_rad = 0.0f;
s_pitch_rad = getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad; s_pitch_rad = getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad;
@@ -184,7 +184,7 @@ void read(float dt) {
const float yaw_rate = apply_deadband(s_smooth_gy, deadband); const float yaw_rate = apply_deadband(s_smooth_gy, deadband);
const float roll_rate = apply_deadband(s_smooth_gz, deadband); const float roll_rate = apply_deadband(s_smooth_gz, deadband);
s_pitch_rad = -pitch_rate * dt * getSettings().game.gyroSensitivityX; s_pitch_rad = -pitch_rate * dt * getSettings().game.gyroSensitivityY;
s_roll_rad = roll_rate * dt * getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X s_roll_rad = roll_rate * dt * getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X
float horizontal_rate = yaw_rate; float horizontal_rate = yaw_rate;
@@ -223,7 +223,7 @@ void read(float dt) {
} }
} }
s_yaw_rad = horizontal_rate * dt * getSettings().game.gyroSensitivityY; s_yaw_rad = horizontal_rate * dt * getSettings().game.gyroSensitivityX;
s_pitch_rad = getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad; s_pitch_rad = getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad;
s_yaw_rad = getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad; s_yaw_rad = getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad;
+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 "ImGuiEngine.hpp"
#include "JSystem/JUtility/JUTGamePad.h" #include "JSystem/JUtility/JUTGamePad.h"
#include "SDL3/SDL_mouse.h" #include "SDL3/SDL_mouse.h"
#include "dusk/action_bindings.h"
#include "dusk/audio/DuskAudioSystem.h" #include "dusk/audio/DuskAudioSystem.h"
#include "dusk/config.hpp" #include "dusk/config.hpp"
#include "dusk/data.hpp"
#include "dusk/dusk.h" #include "dusk/dusk.h"
#include "dusk/frame_interpolation.h" #include "dusk/frame_interpolation.h"
#include "dusk/livesplit.h" #include "dusk/livesplit.h"
@@ -238,7 +240,8 @@ namespace dusk {
} }
void ImGuiConsole::UpdateSettings() { 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)) { 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; getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive;
@@ -259,6 +262,12 @@ namespace dusk {
config::Save(); 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 (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
if (getSettings().backend.enableAdvancedSettings) { if (getSettings().backend.enableAdvancedSettings) {
m_isHidden = !m_isHidden; m_isHidden = !m_isHidden;
@@ -273,7 +282,6 @@ namespace dusk {
// so make the window bg fully transparent temporarily // so make the window bg fully transparent temporarily
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
if (showMenu && ImGui::BeginMainMenuBar()) { if (showMenu && ImGui::BeginMainMenuBar()) {
m_menuGame.draw();
m_menuTools.draw(); m_menuTools.draw();
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
@@ -282,7 +290,7 @@ namespace dusk {
if (dusk::IsGameLaunched && !m_isLaunchInitialized) { if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
m_isLaunchInitialized = true; m_isLaunchInitialized = true;
if (getSettings().game.liveSplitEnabled) { if (getSettings().game.speedrunMode && getSettings().game.liveSplitEnabled) {
dusk::speedrun::connectLiveSplit(); dusk::speedrun::connectLiveSplit();
} }
} }
@@ -309,11 +317,13 @@ namespace dusk {
ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize}); ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize});
} else { } else {
ImGui::PushFont(ImGuiEngine::fontExtraLarge); ImGui::PushFont(ImGuiEngine::fontExtraLarge);
ImGuiTextCenter("Dusk"); ImGuiTextCenter("Dusklight");
ImGui::PopFont(); ImGui::PopFont();
} }
ImGui::PushFont(ImGuiEngine::fontLarge); 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& style = ImGui::GetStyle();
const auto retrySize = ImGui::CalcTextSize("Retry (Auto backend)"); const auto retrySize = ImGui::CalcTextSize("Retry (Auto backend)");
const auto quitSize = ImGui::CalcTextSize("Quit"); const auto quitSize = ImGui::CalcTextSize("Quit");
@@ -339,7 +349,7 @@ namespace dusk {
} }
#if DUSK_CAN_OPEN_DATA_FOLDER #if DUSK_CAN_OPEN_DATA_FOLDER
if (ImGui::Button("Open Data Folder")) { if (ImGui::Button("Open Data Folder")) {
OpenDataFolder(); data::open_data_path();
} }
ImGui::SameLine(); ImGui::SameLine();
#endif #endif
@@ -351,15 +361,6 @@ namespace dusk {
} }
m_menuTools.ShowInputViewer(); 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) { if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) {
m_menuTools.ShowDebugOverlay(); m_menuTools.ShowDebugOverlay();
@@ -367,12 +368,12 @@ namespace dusk {
m_menuTools.ShowProcessManager(); m_menuTools.ShowProcessManager();
m_menuTools.ShowHeapOverlay(); m_menuTools.ShowHeapOverlay();
m_menuTools.ShowStubLog(); m_menuTools.ShowStubLog();
m_menuTools.ShowMapLoader();
m_menuTools.ShowBloomWindow(); m_menuTools.ShowBloomWindow();
m_menuTools.ShowPlayerInfo(); m_menuTools.ShowPlayerInfo();
m_menuTools.ShowAudioDebug(); m_menuTools.ShowAudioDebug();
m_menuTools.ShowSaveEditor(); m_menuTools.ShowSaveEditor();
m_menuTools.ShowStateShare(); m_menuTools.ShowStateShare();
m_menuTools.ShowActorSpawner();
} }
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
-25
View File
@@ -2,14 +2,11 @@
#define DUSK_IMGUI_HPP #define DUSK_IMGUI_HPP
#include <deque> #include <deque>
#include <filesystem>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <SDL3/SDL_misc.h>
#include <aurora/aurora.h> #include <aurora/aurora.h>
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuTools.hpp" #include "ImGuiMenuTools.hpp"
#include "dusk/main.h" #include "dusk/main.h"
#include "imgui.h" #include "imgui.h"
@@ -46,8 +43,6 @@ private:
ImVec2 m_dragScrollLastMousePos = {}; ImVec2 m_dragScrollLastMousePos = {};
std::deque<Toast> m_toasts; std::deque<Toast> m_toasts;
ImGuiMenuGame m_menuGame;
// Keep always last // Keep always last
ImGuiMenuTools m_menuTools; ImGuiMenuTools m_menuTools;
@@ -73,24 +68,4 @@ bool ImGuiButtonCenter(std::string_view text);
float ImGuiScale(); float ImGuiScale();
} // namespace dusk } // namespace dusk
#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
namespace fs = std::filesystem;
static void OpenDataFolder() {
const std::string path = fs::absolute(dusk::ConfigPath).generic_string();
#if defined(_WIN32)
const std::string url = std::string("file:///") + path;
#else
const std::string url = std::string("file://") + path;
#endif
(void)SDL_OpenURL(url.c_str());
}
#else
#define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif
#endif // DUSK_IMGUI_HPP #endif // DUSK_IMGUI_HPP
+5 -4
View File
@@ -3,12 +3,13 @@
#include "imgui.h" #include "imgui.h"
#include <imgui_internal.h> #include <imgui_internal.h>
#include "ImGuiConsole.hpp" #include "ImGuiConsole.hpp"
#include "dusk/settings.h"
#include <dolphin/pad.h> #include <dolphin/pad.h>
namespace dusk { namespace dusk {
void ImGuiMenuTools::ShowInputViewer() { void ImGuiMenuTools::ShowInputViewer() {
if (!m_showInputViewer) { if (!getSettings().game.showInputViewer) {
return; return;
} }
@@ -259,10 +260,10 @@ namespace dusk {
size.y = 130 * scale; size.y = 130 * scale;
ImGui::Dummy(size); ImGui::Dummy(size);
if (PADHasSensor(PAD_1, PAD_SENSOR_GYRO) == TRUE) { if (getSettings().game.showInputViewerGyro)
{
ImGui::Separator(); ImGui::Separator();
ImGui::Checkbox("Gyro Values", &m_showInputViewerGyro); {
if (m_showInputViewerGyro) {
ImGui::TextUnformatted("Gyro"); ImGui::TextUnformatted("Gyro");
constexpr float kBarScale = 4.0f; constexpr float kBarScale = 4.0f;
+1 -1
View File
@@ -219,7 +219,7 @@ void ImGuiEngine_AddTextures() {
ImGuiEngine::orgIcon = AddTexture("org-icon.png"); ImGuiEngine::orgIcon = AddTexture("org-icon.png");
} }
if (ImGuiEngine::duskLogo == 0) { if (ImGuiEngine::duskLogo == 0) {
ImGuiEngine::duskLogo = AddTexture("logo-mascot.png"); ImGuiEngine::duskLogo = AddTexture("logo.png");
} }
} }
} // namespace dusk } // 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
-100
View File
@@ -1,100 +0,0 @@
#include "fmt/format.h"
#include "imgui.h"
#include "ImGuiConsole.hpp"
#include "ImGuiConfig.hpp"
#include "dusk/main.h"
#include "m_Do/m_Do_main.h"
namespace dusk {
ImGuiMenuGame::ImGuiMenuGame() {}
void ImGuiMenuGame::draw() {}
static std::string GetFormattedTime(OSTime ticks) {
OSCalendarTime time;
OSTicksToCalendarTime(ticks, &time);
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", time.hour, time.min, time.sec, time.msec);
}
void ImGuiMenuGame::resetForSpeedrunMode() {
// reset settings that should be off for speedrun mode
mDoMain::developmentMode = -1;
getSettings().game.damageMultiplier.setValue(1);
getSettings().game.instantDeath.setValue(false);
getSettings().game.noHeartDrops.setValue(false);
getSettings().game.infiniteHearts.setValue(false);
getSettings().game.infiniteArrows.setValue(false);
getSettings().game.infiniteBombs.setValue(false);
getSettings().game.infiniteOil.setValue(false);
getSettings().game.infiniteOxygen.setValue(false);
getSettings().game.infiniteRupees.setValue(false);
getSettings().game.enableIndefiniteItemDrops.setValue(false);
getSettings().game.moonJump.setValue(false);
getSettings().game.superClawshot.setValue(false);
getSettings().game.alwaysGreatspin.setValue(false);
getSettings().game.enableFastIronBoots.setValue(false);
getSettings().game.canTransformAnywhere.setValue(false);
getSettings().game.fastSpinner.setValue(false);
getSettings().game.freeMagicArmor.setValue(false);
getSettings().game.enableTurboKeybind.setValue(false);
getSettings().game.debugFlyCam.setValue(false);
getSettings().game.autoSave.setValue(false);
}
SpeedrunInfo m_speedrunInfo;
void ImGuiMenuGame::drawSpeedrunTimerOverlay() {
if (!getSettings().game.speedrunMode) {
return;
}
// L+R+A+Start to reset timer
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigStart(PAD_1)) {
m_speedrunInfo.reset();
}
// L+R+A+Z to manually stop timer
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1)) {
if (m_speedrunInfo.m_isRunStarted) {
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
m_speedrunInfo.m_isRunStarted = false;
}
}
ImGui::SetNextWindowBgAlpha(0.65f);
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoDocking
| ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoScrollbar;
if (ImGui::Begin("##SpeedrunTimerWindow", nullptr, flags)) {
OSTime elapsedTime = 0;
if (m_speedrunInfo.m_isRunStarted) {
elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp;
} else if (m_speedrunInfo.m_endTimestamp != 0) {
elapsedTime = m_speedrunInfo.m_endTimestamp;
}
ImGui::Text("RTA");
ImGui::SameLine(60.0f);
ImGuiStringViewText(GetFormattedTime(elapsedTime));
if (!m_speedrunInfo.m_isPauseIGT) {
m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime;
}
ImGui::Text("IGT");
ImGui::SameLine(60.0f);
ImGuiStringViewText(GetFormattedTime(m_speedrunInfo.m_igtTimer));
}
ImGui::End();
}
}
-58
View File
@@ -1,58 +0,0 @@
#ifndef DUSK_IMGUI_MENUGAME_HPP
#define DUSK_IMGUI_MENUGAME_HPP
#include <aurora/aurora.h>
#include <pad.h>
#include <string>
#include "imgui.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;
class ImGuiMenuGame {
public:
ImGuiMenuGame();
void draw();
void drawSpeedrunTimerOverlay();
static void resetForSpeedrunMode();
private:
bool m_showTimerWindow = false;
};
}
#endif // DUSK_IMGUI_MENUGAME_HPP
+3 -5
View File
@@ -12,6 +12,7 @@
#include "d/actor/d_a_alink.h" #include "d/actor/d_a_alink.h"
#include "d/actor/d_a_horse.h" #include "d/actor/d_a_horse.h"
#include "d/d_com_inf_game.h" #include "d/d_com_inf_game.h"
#include "dusk/data.hpp"
#include "dusk/dusk.h" #include "dusk/dusk.h"
#include "dusk/main.h" #include "dusk/main.h"
#include "m_Do/m_Do_main.h" #include "m_Do/m_Do_main.h"
@@ -39,7 +40,6 @@ namespace dusk {
ImGui::BeginDisabled(getSettings().game.speedrunMode); ImGui::BeginDisabled(getSettings().game.speedrunMode);
ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor); ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor);
ImGui::MenuItem("Map Loader", hotkeys::SHOW_MAP_LOADER, &m_showMapLoader);
ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare); ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare);
ImGui::EndDisabled(); ImGui::EndDisabled();
@@ -48,13 +48,10 @@ namespace dusk {
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
ImGui::Separator();
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
#if DUSK_CAN_OPEN_DATA_FOLDER #if DUSK_CAN_OPEN_DATA_FOLDER
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("Open Data Folder")) { if (ImGui::MenuItem("Open Data Folder")) {
OpenDataFolder(); data::open_data_path();
} }
#endif #endif
@@ -107,6 +104,7 @@ namespace dusk {
ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug);
ImGui::MenuItem("Bloom", nullptr, &m_showBloomWindow); ImGui::MenuItem("Bloom", nullptr, &m_showBloomWindow);
ImGui::MenuItem("Stub Log", nullptr, &m_showStubLog); ImGui::MenuItem("Stub Log", nullptr, &m_showStubLog);
ImGui::MenuItem("Actor Spawner", nullptr, &m_showActorSpawner);
if (!dusk::IsGameLaunched) { if (!dusk::IsGameLaunched) {
ImGui::EndDisabled(); ImGui::EndDisabled();
+2 -16
View File
@@ -21,13 +21,13 @@ namespace dusk {
void ShowProcessManager(); void ShowProcessManager();
void ShowHeapOverlay(); void ShowHeapOverlay();
void ShowStubLog(); void ShowStubLog();
void ShowMapLoader();
void ShowBloomWindow(); void ShowBloomWindow();
void ShowPlayerInfo(); void ShowPlayerInfo();
void ShowAudioDebug(); void ShowAudioDebug();
void ShowSaveEditor(); void ShowSaveEditor();
void ShowStateShare(); void ShowStateShare();
void ShowInputViewer(); void ShowInputViewer();
void ShowActorSpawner();
private: private:
bool m_showDebugOverlay = false; bool m_showDebugOverlay = false;
@@ -42,22 +42,9 @@ namespace dusk {
bool m_showStubLog = false; bool m_showStubLog = false;
bool m_showMapLoader = false;
bool m_showBloomWindow = false; bool m_showBloomWindow = false;
bool m_showAudioDebug = false; bool m_showAudioDebug = false;
struct {
int mapIdx = -1;
int regionIdx = -1;
int roomNoIdx = 0;
int pointNoIdx = 0;
int roomNo = -1;
int pointNo = -1;
int spawnId = 0;
int layer = -1;
bool showInternalNames = false;
} m_mapLoaderInfo;
bool m_showPlayerInfo = false; bool m_showPlayerInfo = false;
int m_playerInfoOverlayCorner = 1; // top-right int m_playerInfoOverlayCorner = 1; // top-right
@@ -68,8 +55,7 @@ namespace dusk {
bool m_showStateShare = false; bool m_showStateShare = false;
ImGuiStateShare m_stateShare; ImGuiStateShare m_stateShare;
bool m_showInputViewer = false; bool m_showActorSpawner = false;
bool m_showInputViewerGyro = false;
int m_inputOverlayCorner = 3; int m_inputOverlayCorner = 3;
std::string m_controllerName; std::string m_controllerName;
}; };
+8 -6
View File
@@ -18,6 +18,7 @@
#include <unordered_set> #include <unordered_set>
#include <zstd.h> #include <zstd.h>
#include <dusk/autosave.h>
namespace dusk { namespace dusk {
@@ -48,19 +49,18 @@ void ImGuiStateShare::onMergeFileSelected(void* userdata, const char* path, cons
static std::string GetStatesFilePath() { static std::filesystem::path GetStatesFilePath() {
return (dusk::ConfigPath / STATES_FILENAME).string(); return ConfigPath / STATES_FILENAME;
} }
void ImGuiStateShare::loadStatesFile() { void ImGuiStateShare::loadStatesFile() {
m_loaded = true; m_loaded = true;
const std::filesystem::path filePath = dusk::ConfigPath / STATES_FILENAME; const std::filesystem::path filePath = GetStatesFilePath();
if (!std::filesystem::exists(filePath)) { if (!std::filesystem::exists(filePath)) {
return; return;
} }
try { try {
const std::string pathStr = filePath.string(); auto data = io::FileStream::ReadAllBytes(filePath);
auto data = io::FileStream::ReadAllBytes(pathStr.c_str());
auto j = json::parse(data); auto j = json::parse(data);
if (!j.is_array()) { if (!j.is_array()) {
return; return;
@@ -85,7 +85,7 @@ void ImGuiStateShare::saveStatesFile() {
j.push_back(json{{"name", s.name}, {"data", s.encoded}}); j.push_back(json{{"name", s.name}, {"data", s.encoded}});
} }
try { try {
io::FileStream::WriteAllText(GetStatesFilePath().c_str(), j.dump(2)); io::FileStream::WriteAllText(GetStatesFilePath(), j.dump(2));
} catch (const std::exception& e) { } catch (const std::exception& e) {
m_statusMsg = fmt::format("Failed to save states: {}", e.what()); m_statusMsg = fmt::format("Failed to save states: {}", e.what());
} }
@@ -136,6 +136,8 @@ bool ImGuiStateShare::applyEncodedState(const std::string& encoded, const std::s
return false; return false;
} }
toggleAutoSave(false);
StateSharePacket pkt; StateSharePacket pkt;
memcpy(&pkt, raw.data(), sizeof(pkt)); memcpy(&pkt, raw.data(), sizeof(pkt));
pkt.stageName[7] = '\0'; pkt.stageName[7] = '\0';
+37 -12
View File
@@ -1,7 +1,8 @@
#include "dusk/io.hpp"
#include <cstdio> #include <cstdio>
#include <filesystem> #include <filesystem>
#include "dusk/io.hpp"
using namespace dusk::io; using namespace dusk::io;
#if _WIN32 #if _WIN32
@@ -16,6 +17,8 @@ using namespace dusk::io;
#else #else
#define MODE(val) val #define MODE(val) val
#endif #endif
#define _SH_DENYNO 0
#define _SH_DENYWR 0
#endif #endif
static FILE* ThrowIfNotOpen(const FileStream& file) { static FILE* ThrowIfNotOpen(const FileStream& file) {
@@ -30,19 +33,19 @@ static FILE* ThrowIfNotOpen(const FileStream& file) {
throw std::system_error(std::make_error_code(static_cast<std::errc>(code))); throw std::system_error(std::make_error_code(static_cast<std::errc>(code)));
} }
static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode) { static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode, int shareFlag) {
FILE* file; FILE* file;
int err; int err;
errno = 0;
#if _WIN32 #if _WIN32
static_assert(std::is_same_v<std::filesystem::path::value_type, wchar_t>); static_assert(std::is_same_v<std::filesystem::path::value_type, wchar_t>);
err = _wfopen_s(&file, path.c_str(), mode); file = _wfsopen(path.c_str(), mode, shareFlag);
#else #else
errno = 0;
static_assert(std::is_same_v<std::filesystem::path::value_type, char>); static_assert(std::is_same_v<std::filesystem::path::value_type, char>);
file = fopen(path.c_str(), mode); file = fopen(path.c_str(), mode);
err = errno;
#endif #endif
err = errno;
if (!file) { if (!file) {
ThrowForError(err); ThrowForError(err);
@@ -51,14 +54,14 @@ static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode)
return file; return file;
} }
static FILE* OpenCore(const char* path, const MODE_TYPE* mode) { static FILE* OpenCore(const char* path, const MODE_TYPE* mode, int shareFlag) {
return OpenCore(reinterpret_cast<const char8_t*>(path), mode); return OpenCore(reinterpret_cast<const char8_t*>(path), mode, shareFlag);
} }
FileStream::FileStream() noexcept : file(nullptr) { FileStream::FileStream() noexcept : file(nullptr) {
} }
FileStream::FileStream(void* file) : file(file) { FileStream::FileStream(FILE* file) : file(file) {
if (!file) { if (!file) {
CRASH("Invalid file handle"); CRASH("Invalid file handle");
} }
@@ -75,11 +78,19 @@ FileStream::~FileStream() {
} }
FileStream FileStream::OpenRead(const char* utf8Path) { FileStream FileStream::OpenRead(const char* utf8Path) {
return FileStream(OpenCore(utf8Path, MODE("rb"))); return FileStream(OpenCore(utf8Path, MODE("rb"), _SH_DENYWR));
}
FileStream FileStream::OpenRead(const std::filesystem::path& path) {
return FileStream(OpenCore(path, MODE("rb"), _SH_DENYWR));
} }
FileStream FileStream::Create(const char* utf8Path) { FileStream FileStream::Create(const char* utf8Path) {
return FileStream(OpenCore(utf8Path, MODE("wb"))); return FileStream(OpenCore(utf8Path, MODE("wb"), _SH_DENYWR));
}
FileStream FileStream::Create(const std::filesystem::path& path) {
return FileStream(OpenCore(path, MODE("wb"), _SH_DENYWR));
} }
std::vector<u8> FileStream::ReadFull() { std::vector<u8> FileStream::ReadFull() {
@@ -128,7 +139,11 @@ std::vector<u8> FileStream::ReadFull() {
} }
std::vector<u8> FileStream::ReadAllBytes(const char* utf8Path) { std::vector<u8> FileStream::ReadAllBytes(const char* utf8Path) {
auto handle = OpenRead(utf8Path); return ReadAllBytes(reinterpret_cast<const char8_t*>(utf8Path));
}
std::vector<u8> FileStream::ReadAllBytes(const std::filesystem::path& path) {
auto handle = OpenRead(path);
return std::move(handle.ReadFull()); return std::move(handle.ReadFull());
} }
@@ -142,6 +157,16 @@ void FileStream::Write(const char* data, size_t dataLen) {
} }
void FileStream::WriteAllText(const char* utf8Path, const std::string_view text) { void FileStream::WriteAllText(const char* utf8Path, const std::string_view text) {
auto handle = Create(utf8Path); WriteAllText(reinterpret_cast<const char8_t*>(utf8Path), text);
}
void FileStream::WriteAllText(const std::filesystem::path& path, const std::string_view text) {
auto handle = Create(path);
handle.Write(text.data(), text.size()); handle.Write(text.data(), text.size());
} }
FILE* FileStream::ToInner() {
auto handle = file;
file = nullptr;
return handle;
}

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