Compare commits

...

240 Commits

Author SHA1 Message Date
madeline d204de47bf Merge branch 'main' of https://github.com/TakaRikka/dusk into phong 2026-05-12 20:37:35 -07:00
madeline 23dc0cdbf0 Merge branch 'main' into phong 2026-05-12 19:35:31 -07: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
Luke Street a0ecdb1735 Re-enable Sentry for release builds 2026-05-10 12:10:32 -06: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
MelonSpeedruns 8280ac00a0 Always disable cursor if Gyro is set to Mouse mode and the menu is not open (#736)
Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
2026-05-09 15:06:58 -06:00
Luke Street 45ef0d72b1 Fix Android backgrounding & re-hide bars after swipe 2026-05-09 15:03:21 -06:00
SuperDude88 ad9c460ec9 Navigate Carousel Without Focus (#741)
* Navigate Carousel Without Focus

- Allow left/right inputs to change the setting value without having the arrow explicitly clicked

* Formatting
2026-05-09 14:23:59 -06:00
Phillip Stephens 23dc9bc39a Disable setting default mappings for now (#742) 2026-05-09 14:14:45 -06:00
SuperDude88 bf23d44389 Fix Syntax Warning (#743)
- Remove invalid property value
2026-05-09 14:14:36 -06:00
SuperDude88 d0b9b6d10f Fix Prelaunch Break-out (#738)
- Prevent users from breaking out of the prelaunch menu through the GraphicsTuner pages
2026-05-09 13:37:14 -06:00
Luke Street 2f83753260 Fix Android release gradle build 2026-05-09 10:13:59 -06:00
Luke Street eeb0ad77a4 Missed a spot 2026-05-09 10:03:05 -06:00
Luke Street 594cadcf7d Update Android app ID to dev.twilitrealm.dusk 2026-05-09 09:55:26 -06:00
TakaRikka 4290726691 update setup instructions 2026-05-09 07:38:19 -07:00
TakaRikka 80dd5ff278 revert mirror mode message override for now 2026-05-09 07:32:29 -07:00
TakaRikka 08efb9a3cf Merge pull request #735 from TwilitRealm/mute-streams
Recording Mode - Mute streams & Fix 1 note playing
2026-05-09 06:38:33 -07:00
MelonSpeedruns d8a1dd1da4 Recording Mode - Mute streams & Fix 1 note playing 2026-05-09 09:15:10 -04:00
SuperDude88 13dd3c3932 Clamp LOD For Cutscene Midna (#728)
- Sets max eye LOD level to 0 for `demo00_Midna_cut00_FC_tongue` and `demo00_Midna_cut00_BD_tmp`
2026-05-09 06:30:46 -06:00
TakaRikka dd7885da9c Merge pull request #734 from TwilitRealm/number-button-fix
Number Button Fix
2026-05-09 04:40:55 -07:00
TakaRikka 5a05433a2b Merge pull request #733 from TwilitRealm/fix/e_s1_e_yg
Frame interp: Fix e_s1 & e_yg
2026-05-09 04:35:25 -07:00
TakaRikka c3ff3884d7 Merge pull request #730 from TwilitRealm/fix/fchain
Frame interp: Fix obj_fchain
2026-05-09 04:34:34 -07:00
SuperDude88 e42c4d3174 Number Button Fix
- Add `is_editing` helper to BaseStringButton
- Block left/right input from changing number while typing

Resolves #706
2026-05-08 23:19:48 -04:00
Pheenoh 06c77a6818 frame interp: e_s1 & e_yg 2026-05-08 21:16:57 -06:00
Pheenoh 4d4a80891f frame interp: fix obj_fchain 2026-05-08 20:21:37 -06:00
doop 71c892368d Use float vertex positions for trim (#729)
Fixes #726 and looks much smoother.
2026-05-08 21:31:46 -04:00
Irastris d2a1dda523 Add interp callbacks to the stalks of four Baba variants 2026-05-08 21:00:16 -04:00
roeming 78179eb93f comment out flags tab in editor (#727)
Co-authored-by: roeming <roeming@users.noreply.github.com>
2026-05-08 18:56:38 -06:00
Luke Street 34e10d3844 Show "3-finger tap" for menu notif on mobile 2026-05-08 17:55:39 -06:00
Howard Luck 65e8577253 frame interp: fix b&c chain links (#724) 2026-05-08 17:26:33 -06:00
Luke Street a4fcc10f5f Make version logic tolerate prerelease semver 2026-05-08 17:25:09 -06:00
Luke Street a2c2988666 Disable Sentry in CI builds 2026-05-08 17:12:58 -06:00
Irastris abec043249 Add mouse as a gyro input source (#720)
* Add mouse as a gyro input source

* Revisions

* Grammar
2026-05-08 17:11:13 -06:00
SuperDude88 699d069b0a Fix Letter Menu Page Numbers (#722)
This bug comes from `getBounds()` returning a reference to a static variable -- the compiler can load the float right away or later (after the second call), so the value it is referencing may have changed. If it waits to load the first operand, the subtraction results in 0. Further discussion at https://discord.com/channels/1446645861529550869/1446648842387722411/1502415404386091118.

Resolves #719
2026-05-08 17:10:37 -06:00
Tom Lube 4d67033ff8 Remove reset via CTRL + R 2026-05-08 17:08:03 -06:00
Irastris 84ffd67622 Refactor notification settings, allow disabling controller toasts (#721)
* Refactor notification settings, allow disabling controller toasts

* "Toasts" to "Notifications"
2026-05-08 17:01:19 -06:00
Luke Street 1c85ee63eb Many mobile fixes, Android update check, "Background Input" & "Pause On Focus Lost" options 2026-05-08 16:57:33 -06:00
qwertyquerty 81c7213993 Merge pull request #599 from TwilitRealm/50_achievements
More Achievements
2026-05-08 12:27:25 -07:00
madeline be82e606b2 45 achievements 2026-05-08 11:20:17 -07:00
MelonSpeedruns 44da1a9f7d Added sounds to Reset & Quit modals (#718)
Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
2026-05-08 11:51:13 -06:00
doop d0f8ea56f9 Interpolate more things (#699)
* Interpolate trim height

* Interpolate (some) fades
2026-05-08 11:50:44 -06:00
Howard Luck e472b36cef Merge pull request #717 from TwilitRealm/fix/super_clawshot 2026-05-08 10:26:46 -06:00
MelonSpeedruns 3934e09c8f remove mention of chains not rendering 2026-05-08 12:07:00 -04:00
MelonSpeedruns 3136816ce9 Merge remote-tracking branch 'origin/main' into fix/super_clawshot 2026-05-08 12:06:47 -04:00
MelonSpeedruns 6fd3762ffc optimized code 2026-05-08 12:06:38 -04:00
Pheenoh 97a1190713 set max chain links to 600 2026-05-08 08:59:13 -06:00
MelonSpeedruns 73a3bd9ae8 super clawshot warning about chains (#716)
Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
2026-05-08 08:53:01 -06:00
Luke Street fc533dbdc7 UI: Add update checks (#715) 2026-05-08 08:52:36 -06:00
madeline 6217e071d2 Merge branch '50_achievements' of https://github.com/TakaRikka/dusk into 50_achievements 2026-05-08 05:17:48 -07:00
Luke Street 673ca7f686 Update Linux icons & .desktop
Resolves #714
2026-05-08 01:23:53 -06:00
CraftyBoss 29a1cff7ea update aurora, add comment explaining king bulblin 1 strange behavior
the game saves a flag when the player hits king bulblin at least once, which makes subsequent loads (including king bulblin 2) have 1 less "health" as lap_num for the boar is set to 1 during creation, as it checks the flag.
2026-05-08 00:05:16 -07:00
Luke Street 3c5152a67b Disable autosave in speedrun mode 2026-05-08 00:46:02 -06:00
MelonSpeedruns 1c1a849095 Recording Mode & Enable Autosave in Dusk preset (#685)
* branch for recording without a HUD

* no more black bars thanks maddie

* Backslash hotkey

* mute music

* dont check for null twice

* renamed option to recording mode, and added it to the rmlui menu

* Move recording mode to Interface

* re-added & renamed minimal hud option

* Mute all BGM if recording mode is on

* Update Interface section

* Un-experimentalize Autosave & enable in Dusk preset

---------

Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
Co-authored-by: Irastris <irastris15@gmail.com>
Co-authored-by: Luke Street <luke@street.dev>
2026-05-07 23:26:22 -06:00
Phillip Stephens 89d393e57a Add missing climits include 2026-05-07 22:24:31 -07:00
Phillip Stephens 2f27687d80 Fix OSReport strings corruption with non-trivial format (#709)
* Fix OSReport strings corruption with non-trivial format

* Limit format attempts to 3
2026-05-07 23:10:23 -06:00
SuperDude88 167a50c01d Deselect Sub-Menu On Focus Change (#710)
- Unselect any previous items when focusing a new one

This might be way over-inclusive so it should be scrutinized harshly (but fixes #672)
2026-05-07 23:09:25 -06:00
SuperDude88 313f03f5e5 Dim Carousel Arrows
- Dim carousel arrows if there are no further options in that direction (closes #708)

This sets the color property constantly, not sure if that is a concern for perf (would imagine not since it already sets the rml to mValueElem every frame also?)
2026-05-07 23:05:08 -06:00
Howard Luck 6cfdc3d8a3 add keyboard controls + roll support to fly cam (#705) 2026-05-07 23:04:29 -06:00
Luke Street 9c24a0bc4b Disable Development Mode & move Input Viewer
Resolves #712
2026-05-07 23:03:26 -06:00
TakaRikka b99ad920c4 Merge pull request #704 from TwilitRealm/ira/rmlui-confirmation
Add confirmations modals to Reset and Quit
2026-05-07 13:57:03 -07:00
TakaRikka 912b18eca1 Merge pull request #703 from TwilitRealm/ui/advanced-settings
Advanced Settings
2026-05-07 13:55:13 -07:00
TakaRikka 9e651a51db Merge pull request #700 from TwilitRealm/ira/rmlui-fps
Add FPS counter to RmlUi
2026-05-07 13:51:51 -07:00
TakaRikka c8b6e997a7 Merge pull request #702 from TwilitRealm/ira/rmlui-inputconfig
Add remaining controller config options to RmlUi
2026-05-07 13:45:35 -07:00
Irastris 928e187524 Add mobile layout for prelaunch 2026-05-07 15:57:52 -04:00
Irastris cf12d19860 Resolve RmlUi deprecation warning, add L1/R1 tabbing to MenuBar 2026-05-07 13:48:12 -04:00
Irastris e3a3ac56fb Add confirmations modals to Reset and Quit 2026-05-07 13:32:59 -04:00
MelonSpeedruns d15ed81cd5 Only show Editor if Advanced Settings is enabled 2026-05-07 12:39:37 -04:00
TakaRikka ff054f6f47 update aurora and enable MTX_USE_PS 2026-05-07 09:36:32 -07:00
MelonSpeedruns 667cf70fa0 Add Advanced Settings option & Disable debug hotkeys if it's off 2026-05-07 12:01:32 -04:00
Irastris 73eb401c93 Add remaining controller config options, remove ImGui config 2026-05-07 12:00:35 -04:00
Irastris b11f3add06 Don't immediately dismiss prelaunch if skip config is toggled
Prevents a situation where memcard is not init until restarting after the deferral change
2026-05-07 09:26:41 -04:00
Irastris 92f888a152 Remove blur and shadow from counter, consolidate options 2026-05-07 03:28:30 -04:00
Irastris 78ed5cc716 Add FPS counter 2026-05-07 03:13:08 -04:00
Luke Street 3968b67c5d Disc verification cleanup 2026-05-07 00:35:00 -06:00
Luke Street 119830c979 Merge pull request #695 from TwilitRealm/hash-verif
Hash verification for disc images
2026-05-06 23:41:57 -06:00
Luke Street 647394c875 Don't hash on startup & cleanup 2026-05-06 23:33:55 -06:00
Luke Street c50d777309 Progress modal & persist validation state 2026-05-06 23:21:43 -06:00
Luke Street 4954685cca Merge branch 'main' into hash-verif 2026-05-06 22:09:20 -06:00
qwertyquerty 39d43a8d8f Keyboard mouse binding controls (#682)
* kbm controls

* submodule

* files.cmake

* a

* Fixes

* include <cstring> in J3DStruct.h

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-05-06 22:07:28 -06:00
TakaRikka b4761cc54e fix build 2026-05-06 20:36:21 -07:00
qwertyquerty 9aee2e9cfe PS math accuracy (#654)
* math accuracy

* submodule

* Update aurora

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-05-06 21:22:59 -06:00
Luke Street 7c7c8b228e Crafty's crafty Dungeon door hack 2026-05-06 19:53:18 -06:00
doop ca7714dafd Merge pull request #697 from TwilitRealm/include-numbers
Try to fix Windows/macOS build errors
2026-05-06 21:09:53 -04:00
doop 1ad5ab8632 Try to fix Windows/macOS build errors 2026-05-07 01:02:43 +00:00
Irastris c4e7838089 Swap shield labels in RmlUi editor 2026-05-06 21:02:15 -04:00
roeming 7313712263 update area flags to precalculate correct offsets/masks (#694)
Co-authored-by: roeming <roeming@users.noreply.github.com>
2026-05-06 18:40:10 -06:00
doop aa48d95f24 Procedurally-generate HQ minimap textures (#692)
* Procedurally-generate HQ minimap textures

* Don't leak old minimap textures
2026-05-06 18:40:04 -06:00
Thomas c5baabbe9c assets: Remove objdiff screenshot (#691)
Leftover from the decomp.
2026-05-06 18:38:01 -06:00
Irastris 1d2716139f Redesign PresetWindow 2026-05-06 19:49:57 -04:00
MelonSpeedruns 742ad2c150 Increase Link's heap size for mods 2026-05-06 19:48:43 -04:00
Luke Street 18eb0692f0 UI: "No controller" & menu notifications
Resolves #629
Resolves #678
2026-05-06 17:34:08 -06:00
Irastris b5f98f69db Redesign Modal 2026-05-06 19:19:38 -04:00
Irastris 47593d0eb4 Allow hash-mismatched discs to be loaded with user confirmation 2026-05-06 19:19:38 -04:00
Irastris 4404fce369 Do hash-based verification of disc images 2026-05-06 19:19:38 -04:00
Irastris 72c20f4dd0 Add stylesheet rules 2026-05-06 19:19:38 -04:00
doop 3240885bfd Preserved doop work
* Disc verification

* Add platform and region info to known discs map

* Use array over map

* Use std::to_array
2026-05-06 19:19:38 -04:00
Luke Street 7f0955f022 Add ImGui screen for Aurora Null backend
Resolves #628
2026-05-06 16:39:28 -06:00
Luke Street c21bce0093 UI: Adjust battery icon thresholds 2026-05-06 11:49:24 -06:00
Luke Street 4e23472ed5 UI: Controller connect/disconnect toasts 2026-05-06 11:14:12 -06:00
doop cfe1f2304b Increase line-height for right pane descriptions 2026-05-06 13:15:30 +00:00
TakaRikka de6568d750 allow suns song / quick transform in ToT past 2026-05-06 04:25:42 -07:00
TakaRikka 3aafe7fa16 more mirror mode map fixes 2026-05-06 04:19:00 -07:00
Luke Street c1e65d19e7 Autosave: Rotate inner ring too 2026-05-05 23:57:20 -06:00
Luke Street 5fdf954994 Reduce autosave spinner opacity 2026-05-05 23:25:06 -06:00
Luke Street 230868af3c Add autosave spinner & make RmlUi respect 4:3
Resolves #627
2026-05-05 23:18:11 -06:00
doop 66154d9de8 Remove post-trim scissor (#684)
* Remove post-trim scissor

Fixes #679

* Add comment
2026-05-05 19:41:59 -06:00
Luke Street 93ec3c7dbd New UI: Achievement toasts (#690)
* Rename Overlay -> GraphicsTuner, Popup -> MenuBar

* Update GraphicsTuner CSS

* WIP Overlay document & toasts

* Achievement toasts

* Cleanup
2026-05-05 19:29:42 -06:00
MelonSpeedruns 945ce3e4bc Alternate RMLUI Menu Sounds (#686)
* Alternate RMLUI Menu Sounds

Those fit more the game I feel like.

* swapped tab sounds

* pressing A on tab button plays the OK sound

* Fix tab sound + Added menu sounds to prelaunch menu

* Centralize UI sound definitions

* Improvements

* Add "Play" button sound

* Use kSoundItemFocus in prelaunch

* Oops

* Update play/enable/disable sounds

---------

Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
Co-authored-by: Luke Street <luke@street.dev>
2026-05-05 16:44:11 -06:00
TakaRikka 782a8573e9 Merge pull request #570 from TwilitRealm/mapmirror
fix main map for mirror mode
2026-05-05 14:22:25 -07:00
Irastris 4e0ca51159 Remove restart notice from cardFileType's rightPane 2026-05-05 17:03:04 -04:00
Irastris 3cd160e1b2 Defer mDoMemCd_ThdInit if necessary, removing restart requirement
Thanks, Melon
2026-05-05 16:56:41 -04:00
Luke Street 93a236a9d2 Prelaunch settings: actually save config 2026-05-05 14:47:53 -06:00
Irastris eaf3bc2f40 Remove skipWarningScreen config option, always skip logos 2026-05-05 16:27:41 -04:00
Luke Street 5ca0a2ba06 UI: Fix focus reconciliation for context root 2026-05-05 14:17:57 -06:00
Luke Street ccd2bdbaac Add full Settings window to prelaunch 2026-05-05 14:12:53 -06:00
Irastris 08321699cd Display the real title screen behind the prelaunch menu (#638)
* Start game execution as soon as a disk image is available

* Do not update dDemo_c if prelaunch document is visible

* Prevent intro music until prelaunch has popped

* Replace "Start Game" references with "Play"

* Make prelaunch layout respect mirror mode

* Add drop shadow to prelaunch disk-status and version-info

* Remove ImGui prelaunch

* Add "Change Disk Image" button to prelaunch options

* Actually validate discs and make prelaunch very betterer :)

* Check your build before pushing dumbass, and go to sleep

* "Disc" consistency, adjust restart notice logic

* Better LanguageSelect logic

* Add restart notice to SaveTypeSelect

* Added wind sounds to the pre-launch menu

* Add Modal document, use it for disc verification

* Consolidate Modal and PresetWindow

* Squash various bugs, rearrange document flow

* Allow Window inheritors to opt-out of being toggleable

* Tweak focus behavior/syntax

* Implement "Restart Now" option

* Tweaks

* Remove a bunch of dynamic_cast

* Update README.md

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-05-05 12:18:25 -06:00
Luke Street 7300c0e0f5 Update aurora 2026-05-05 12:15:01 -06:00
Luke Street 7e562824fe Add a <br> 2026-05-05 11:30:49 -06:00
Luke Street ed8b5c96b9 Update aurora & add logo to README.md 2026-05-05 11:28:05 -06:00
TakaRikka 14bccdffa6 fix mirror map portal selection 2026-05-05 06:25:46 -07:00
TakaRikka 39e465bcab fix mirror map region selection 2026-05-05 06:22:49 -07:00
TakaRikka e53bb3a12d Merge branch 'main' of github.com:TwilitRealm/dusk into mapmirror 2026-05-05 04:59:25 -07:00
TakaRikka 50fccd393f fix mirror mode text / rodeo direction 2026-05-05 04:44:14 -07:00
TakaRikka 7993740ac8 fix kargorok mirror mode controls 2026-05-05 01:16:01 -07:00
TakaRikka 8e5bb8ae59 Merge pull request #676 from TwilitRealm/instant-text-tweaks
Instant text tweaks
2026-05-04 23:43:43 -07:00
TakaRikka 8fefdd4114 Merge pull request #669 from TwilitRealm/remove-discord-rpc
Remove discord-rpc/rapidjson; roll our own
2026-05-04 23:43:24 -07:00
gymnast86 64c8cee21b revert to checking for Hold B at the start of do_isReady 2026-05-04 23:08:01 -07:00
gymnast86 25fe686573 add message ids for not being able to buy bombs from barnes 2026-05-04 23:07:23 -07:00
Luke Street 1c1ea98fdd Update aurora 2026-05-04 23:35:21 -06:00
TakaRikka e098104f8f Merge pull request #642 from TwilitRealm/debug-fly-cam
Add fly mode option to debug cam
2026-05-04 19:25:19 -07:00
Pheenoh 3c5ade5565 don't allow activation if paused or in event 2026-05-04 20:15:02 -06:00
Pheenoh b2ad75027e Merge branch 'main' into debug-fly-cam 2026-05-04 19:26:31 -06:00
TakaRikka fdfbf83b88 Merge pull request #660 from TwilitRealm/fix/fix-ub
Fix a UB in Morpheel
2026-05-04 18:24:16 -07:00
SuperDude88 1b9ca0949e Clear Description on More Hovers (#673)
- Properly clear the previous option description when you hover over "toggle fullscreen" or "restore default window size"

Fixes #671
2026-05-04 19:17:39 -06:00
Luke Street 827037f0fa Update aurora for RmlUi base path fix 2026-05-04 18:27:50 -06:00
Luke Street d84c5790f5 NOMINMAX 2026-05-04 16:27:12 -06:00
Luke Street 49eb2282af Forgor to add 2026-05-04 16:20:30 -06:00
Luke Street 741f9ecfab Remove discord-rpc/rapidjson; roll our own 2026-05-04 16:14:57 -06:00
Luke Street 37b8122962 UI: Refactor pane control to allow hover for help text 2026-05-04 14:16:35 -06:00
doop 5f2cf68e80 Minor UI nits 2026-05-04 19:38:11 +00:00
Luke Street 74f2c58b29 UI: Preset tweaks 2026-05-04 13:08:20 -06:00
madeline 208433047a Merge branch 'main' of https://github.com/TakaRikka/dusk 2026-05-04 12:06:28 -07:00
madeline 2c01430035 fix stereo audio with mirror mode fixes #666 2026-05-04 12:06:24 -07:00
Luke Street 5121437bcf UI: Avoid looping at end of window content 2026-05-04 11:49:12 -06:00
Luke Street f61bd3e5ad UI: Reorder settings tabs 2026-05-04 11:31:09 -06:00
Luke Street 835e409b32 UI: Fix prelaunch options close anim 2026-05-04 11:27:31 -06:00
Luke Street 010bdb7e25 UI: Fix left/right nav from content to change tabs 2026-05-04 11:17:48 -06:00
Luke Street 7ba22b7714 UI: Refocus top document automatically 2026-05-04 11:14:40 -06:00
Luke Street efcb19a3d0 Revert hide change to popup menu 2026-05-04 10:49:57 -06:00
Luke Street 55455bb1b5 Adjust presets & default settings 2026-05-04 10:43:42 -06:00
qwertyquerty e49be12297 RmlUi improvements (#663)
* rmlui audio

* fix menu select sound

* Fixes #662

* fix reset logic and fix popup getting stuck closed

* fix X button on menu popup

* rmlui achievements, and fix open/close bug

* presets, achievements css, and menu sounds toggle

* forgor

* fix b button causing audio when menu not visible
2026-05-04 10:25:13 -06:00
MelonSpeedruns 75f4940f5e set field_0xbaf variable like all the other conditions 2026-05-04 11:29:29 -04:00
MelonSpeedruns 8047330952 oops i suck 2026-05-04 09:55:01 -04:00
MelonSpeedruns 9105dcb078 remove empty line 2026-05-04 09:54:24 -04:00
MelonSpeedruns b8e38e03e2 Apply PJB request 2026-05-04 09:54:00 -04:00
MelonSpeedruns 331352878e Fix a UB in morpheel 2026-05-04 08:41:19 -04:00
Luke Street 62a88f1e9a UI: Add warning icons to experimental settings 2026-05-03 23:44:38 -06:00
Luke Street 43b603e70b UI: 3-finger tap to toggle menu on mobile 2026-05-03 22:39:31 -06:00
doop 95e6ac54cf Random UI and README tweaks (#653) 2026-05-03 22:02:09 -06:00
Luke Street c4b2e2e501 Make prelaunch fade out smoother 2026-05-03 22:01:02 -06:00
Luke Street dccba23980 Update aurora 2026-05-03 20:47:14 -06:00
Luke Street bf27d10519 Revert "Convert UI fonts to WOFF2 (#647)" (#648)
This reverts commit 6c27011e32.
2026-05-03 20:41:24 -06:00
doop 6c27011e32 Convert UI fonts to WOFF2 (#647)
* Convert UI fonts to WOFF2

* ...except for the ones we use for ImGui
2026-05-03 20:36:55 -06:00
Luke Street 6220990dc5 UI: Use decorator: text for close button 2026-05-03 19:01:44 -06:00
Pheenoh 901ce2ee4c add debug fly cam option 2026-05-03 15:23:59 -06:00
madeline 44f3828f68 fix morpheel logic 2026-04-30 02:48:30 -07:00
madeline 3f560b060c 40 achievements 2026-04-30 01:48:03 -07:00
TakaRikka eaae8b6137 Merge branch 'main' of github.com:TwilitRealm/dusk into mapmirror 2026-04-26 17:37:30 -07:00
madeline 24b468f2a2 phong 2026-04-26 01:08:58 -07:00
TakaRikka d9a0ef760f fix icon positions 2026-04-16 19:46:02 -07:00
TakaRikka 95470b830f wip map mirror 2026-04-16 05:55:46 -07:00
272 changed files with 21193 additions and 7320 deletions
+86 -26
View File
@@ -8,7 +8,7 @@ on:
pull_request:
env:
# SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
@@ -22,7 +22,7 @@ jobs:
matrix:
include:
- name: GCC x86_64
runner: [self-hosted, Linux]
runner: ubuntu-latest
preset: gcc
artifact_arch: x86_64
# - name: GCC aarch64
@@ -41,7 +41,6 @@ jobs:
submodules: recursive
- name: Install dependencies
if: 'false' # disabled for self-hosted
run: |
sudo apt-get update
sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \
@@ -51,8 +50,7 @@ jobs:
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
- name: Setup sccache
if: 'false' # disabled for self-hosted
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.10
- name: Print sccache stats
run: sccache --show-stats
@@ -67,17 +65,16 @@ jobs:
run: ci/build-appimage.sh
- name: Upload artifacts
if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
name: dusklight-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
path: |
build/install/Dusk-*.AppImage
build/install/Dusklight-*.AppImage
build/install/debug.tar.*
build-apple:
name: Build Apple (${{matrix.name}})
runs-on: [self-hosted, macOS]
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
@@ -86,14 +83,14 @@ jobs:
platform: macos
preset: x-macos-ci-arm64
artifact_name: macos-appleclang-arm64
# - name: AppleClang macOS x86_64
# platform: macos
# preset: x-macos-ci-x86_64
# artifact_name: macos-appleclang-x86_64
# - name: AppleClang iOS arm64
# platform: ios
# preset: x-ios-ci
# artifact_name: ios-appleclang-arm64
- name: AppleClang macOS x86_64
platform: macos
preset: x-macos-ci-x86_64
artifact_name: macos-appleclang-x86_64
- name: AppleClang iOS arm64
platform: ios
preset: x-ios-ci
artifact_name: ios-appleclang-arm64
# - name: AppleClang tvOS arm64
# platform: tvos
# preset: x-tvos-ci
@@ -106,7 +103,6 @@ jobs:
submodules: recursive
- name: Install dependencies
if: 'false'
run: brew install cmake ninja
- name: Install Rust iOS target
@@ -128,7 +124,7 @@ jobs:
rustup target add x86_64-apple-darwin
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake
run: cmake --preset ${{matrix.preset}}
@@ -137,14 +133,80 @@ jobs:
run: cmake --build --preset ${{matrix.preset}}
- name: Upload artifacts
if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
name: dusklight-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
path: |
build/install/Dusk.app
build/install/Dusklight.app
build/install/debug.tar.*
build-android:
name: Build Android (${{matrix.name}})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: Clang arm64-v8a
preset: x-android-ci-arm64
abi: arm64-v8a
artifact_arch: arm64
rust_target: aarch64-linux-android
env:
ANDROID_NDK_VERSION: "29.0.14206865"
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get -y install ninja-build
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17
- name: Setup Android SDK
uses: android-actions/setup-android@v4
- name: Install Android SDK packages
run: sdkmanager "platforms;android-36" "build-tools;36.1.0" "ndk;${ANDROID_NDK_VERSION}"
- name: Install Rust Android target
run: |
rustup toolchain install stable
rustup target add ${{matrix.rust_target}}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake
run: cmake --preset ${{matrix.preset}}
- name: Build native library
run: cmake --build --preset ${{matrix.preset}} --target dusklight
- name: Stage stripped JNI library
run: ANDROID_STAGE_ABIS="${{matrix.abi}}" platforms/android/scripts/stage-jni-libs.sh
- name: Build APK
working-directory: platforms/android
run: ./gradlew :app:assembleRelease --rerun-tasks
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: dusklight-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}}
path: platforms/android/app/build/outputs/apk/release/app-${{matrix.abi}}-release-unsigned.apk
build-windows:
name: Build Windows (${{matrix.name}})
runs-on: ${{matrix.runner}}
@@ -154,7 +216,7 @@ jobs:
matrix:
include:
- name: MSVC x86_64
runner: [self-hosted, Windows]
runner: windows-latest
preset: msvc
msvc_arch: amd64
vcpkg_arch: x64
@@ -191,7 +253,6 @@ jobs:
uses: mozilla-actions/sccache-action@v0.0.9
- name: Install dependencies
if: 'false' # disabled for self-hosted
run: |
choco install ninja
vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static
@@ -203,10 +264,9 @@ jobs:
run: cmake --build --preset x-windows-ci-${{matrix.preset}}
- name: Upload artifacts
if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
name: dusklight-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
path: |
build/install/*.exe
build/install/*.dll
+4
View File
@@ -41,6 +41,10 @@ compile_commands.json
# MacOS
.DS_Store
# direnv / nix
.direnv/
.envrc
# ISOs
*.iso
+1 -1
View File
@@ -2,7 +2,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch Dusk MSVC",
"name": "(gdb) Launch Dusklight MSVC",
"type": "cppvsdbg",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"cmake.buildDirectory": "${workspaceFolder}/build/dusk/${buildType}/${variant:tp_version}",
"cmake.buildDirectory": "${workspaceFolder}/build/dusklight/${buildType}/${variant:tp_version}",
"cmake.generator": "Ninja",
"cmake.configureSettings": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
+119 -101
View File
@@ -48,13 +48,15 @@ else ()
message(STATUS "Unable to find git, commit information will not be available")
endif ()
if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9]+).*)?$")
if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)([-+].*)?$")
set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
if (CMAKE_MATCH_5)
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${CMAKE_MATCH_5}")
else ()
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.0")
set(DUSK_VERSION_TWEAK "0")
if (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+)(-dirty)?$")
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
elseif (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-[0-9A-Za-z.-]+-([0-9]+)(-dirty)?$")
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
endif ()
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${DUSK_VERSION_TWEAK}")
else ()
set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION")
set(DUSK_VERSION_STRING "0.0.0.0")
@@ -65,11 +67,11 @@ endif ()
if(DEFINED ENV{GITHUB_ENV})
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n")
endif()
message(STATUS "Dusk version set to ${DUSK_WC_DESCRIBE}")
message(STATUS "Dusklight version set to ${DUSK_WC_DESCRIBE}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
project(dusk LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
project(dusklight LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
if (APPLE)
enable_language(OBJC)
enable_language(OBJC OBJCXX)
endif ()
if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS)
# ios.toolchain.cmake hack for SDL
@@ -102,31 +104,39 @@ set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE)
set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE)
set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE)
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
target_compile_definitions(aurora_mtx PRIVATE MTX_USE_PS=1)
add_subdirectory(libs/freeverb)
option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)")
option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.")
option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON)
option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON)
if(ANDROID)
set(DUSK_MOVIE_SUPPORT OFF)
set(NOD_COMPRESS_BZIP2 OFF CACHE BOOL "" FORCE)
set(NOD_COMPRESS_LZMA OFF CACHE BOOL "" FORCE)
set(NOD_COMPRESS_ZLIB OFF CACHE BOOL "" FORCE)
set(NOD_COMPRESS_ZSTD OFF CACHE BOOL "" FORCE)
endif ()
option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
# Edit & Continue
if (MSVC)
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
endif ()
if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
add_link_options("/INCREMENTAL")
endif ()
endif ()
if (DUSK_MOVIE_SUPPORT)
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
if (libjpeg-turbo_FOUND)
message(STATUS "dusk: Using system libjpeg-turbo")
message(STATUS "dusklight: Using system libjpeg-turbo")
else ()
message(STATUS "dusk: Fetching libjpeg-turbo")
message(STATUS "dusklight: Fetching libjpeg-turbo")
include(ExternalProject)
set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install)
if (WIN32)
@@ -150,6 +160,7 @@ if (DUSK_MOVIE_SUPPORT)
CMAKE_C_COMPILER_LAUNCHER
CMAKE_MAKE_PROGRAM
CMAKE_MSVC_RUNTIME_LIBRARY
CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
CMAKE_OSX_ARCHITECTURES
DEPLOYMENT_TARGET
ENABLE_ARC
@@ -215,13 +226,13 @@ endif ()
include(FetchContent)
# Declare all dependencies first so CMake can download them in parallel
message(STATUS "dusk: Fetching cxxopts")
message(STATUS "dusklight: Fetching cxxopts")
FetchContent_Declare(cxxopts
URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz
URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
message(STATUS "dusk: Fetching nlohmann/json")
message(STATUS "dusklight: Fetching nlohmann/json")
FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
@@ -230,7 +241,7 @@ FetchContent_Declare(json
FetchContent_MakeAvailable(cxxopts json)
if (DUSK_ENABLE_SENTRY_NATIVE)
message(STATUS "dusk: Fetching sentry-native")
message(STATUS "dusklight: Fetching sentry-native")
set(SENTRY_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE)
if (WIN32)
@@ -275,17 +286,17 @@ include(files.cmake)
# TODO: version handling for res includes
set(DUSK_BUNDLE_NAME Dusk)
set(DUSK_BUNDLE_NAME Dusklight)
set(DUSK_BUNDLE_IDENTIFIER dev.twilitrealm.dusk)
set(DUSK_COMPANY_NAME "Twilit Realm")
set(DUSK_FILE_DESCRIPTION "Dusk")
set(DUSK_PRODUCT_NAME "Dusk")
set(DUSK_FILE_DESCRIPTION "Dusklight")
set(DUSK_PRODUCT_NAME "Dusklight")
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
source_group("dusk" FILES ${DUSK_FILES})
source_group("dusklight" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES})
set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0)
set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1)
set(GAME_INCLUDE_DIRS
include
@@ -297,8 +308,10 @@ set(GAME_INCLUDE_DIRS
extern
${CMAKE_BINARY_DIR})
find_package(Threads REQUIRED)
set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd
aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt)
aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt
Threads::Threads)
list(APPEND GAME_LIBS libzstd_static)
@@ -311,6 +324,41 @@ if (WIN32)
list(APPEND GAME_LIBS Ws2_32)
endif ()
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/no_backend.cpp)
if (DUSK_ENABLE_UPDATE_CHECKER)
list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_UPDATE_CHECKER=1)
if (WIN32)
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/winhttp.cpp)
list(APPEND GAME_LIBS winhttp)
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_WINHTTP=1)
message(STATUS "dusklight: Enabled update checker (WinHTTP)")
elseif (ANDROID)
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/android.cpp)
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_ANDROID=1)
message(STATUS "dusklight: Enabled update checker (Android)")
elseif (APPLE)
find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED)
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/url_session.mm)
set_source_files_properties(src/dusk/http/url_session.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
list(APPEND GAME_LIBS ${FOUNDATION_FRAMEWORK})
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_URLSESSION=1)
message(STATUS "dusklight: Enabled update checker (NSURLSession)")
elseif (CMAKE_SYSTEM_NAME STREQUAL Linux)
find_package(CURL QUIET OPTIONAL_COMPONENTS HTTPS SSL)
if (CURL_FOUND AND CURL_HTTPS_FOUND AND CURL_SSL_FOUND)
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/curl.cpp)
list(APPEND GAME_LIBS CURL::libcurl)
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_LIBCURL=1)
message(STATUS "dusklight: Enabled update checker (libcurl)")
else ()
message(STATUS "dusklight: Disabled update checker (libcurl + HTTPS/SSL not found)")
endif ()
else ()
message(STATUS "dusklight: Disabled update checker (unsupported platform)")
endif ()
endif ()
list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE})
if (DUSK_MOVIE_SUPPORT)
if (TARGET libjpeg-turbo::turbojpeg-static)
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
@@ -320,56 +368,13 @@ if (DUSK_MOVIE_SUPPORT)
list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1)
endif ()
option(DUSK_ENABLE_DISCORD_RPC "Enable Discord Rich Presence support" ON)
if (DUSK_ENABLE_DISCORD_RPC AND NOT ANDROID AND NOT IOS AND NOT TVOS)
FetchContent_Populate(discord_rpc
URL https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.tar.gz
URL_HASH SHA256=e13427019027acd187352dacba6c65953af66fdf3c35fcf38fc40b454a9d7855
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
# RapidJSON is a git submodule absent from the discord-rpc tarball; fetch separately.
FetchContent_Populate(rapidjson
URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz
URL_HASH SHA256=bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
if (NOT TARGET discord-rpc)
set(_drpc ${discord_rpc_SOURCE_DIR}/src)
set(_drpc_src
${_drpc}/discord_rpc.cpp
${_drpc}/rpc_connection.cpp
${_drpc}/serialization.cpp
)
if (WIN32)
list(APPEND _drpc_src ${_drpc}/connection_win.cpp ${_drpc}/discord_register_win.cpp)
elseif (APPLE)
list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_osx.m)
else ()
list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_linux.cpp)
endif ()
add_library(discord-rpc STATIC ${_drpc_src})
target_include_directories(discord-rpc PUBLIC
${discord_rpc_SOURCE_DIR}/include
${rapidjson_SOURCE_DIR}/include
)
if (UNIX)
target_link_libraries(discord-rpc PUBLIC pthread)
endif ()
endif ()
list(APPEND GAME_LIBS discord-rpc)
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD_RPC=1)
set(DUSK_ENABLE_DISCORD_DEFAULT ON)
if (DEFINED DUSK_ENABLE_DISCORD_RPC AND NOT DEFINED DUSK_ENABLE_DISCORD)
set(DUSK_ENABLE_DISCORD_DEFAULT ${DUSK_ENABLE_DISCORD_RPC})
endif ()
# 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 ()
option(DUSK_ENABLE_DISCORD "Enable Discord Rich Presence support" ${DUSK_ENABLE_DISCORD_DEFAULT})
if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
endif ()
if(ANDROID)
@@ -427,31 +432,37 @@ endif ()
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES})
if(ANDROID)
add_library(dusk SHARED ${DUSK_FILES})
set_target_properties(dusk PROPERTIES OUTPUT_NAME main)
add_library(dusklight SHARED ${DUSK_FILES})
set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
else ()
add_executable(dusk ${DUSK_FILES})
add_executable(dusklight ${DUSK_FILES})
endif ()
target_compile_definitions(dusk PRIVATE ${GAME_COMPILE_DEFS})
target_include_directories(dusk PRIVATE ${GAME_INCLUDE_DIRS})
target_link_libraries(dusk PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
target_precompile_headers(dusk PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
target_compile_definitions(dusklight PRIVATE ${GAME_COMPILE_DEFS})
target_include_directories(dusklight PRIVATE ${GAME_INCLUDE_DIRS})
target_link_libraries(dusklight PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES})
target_precompile_headers(dusklight PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>")
if (TARGET crashpad_handler)
add_dependencies(dusk crashpad_handler)
add_dependencies(dusklight crashpad_handler)
add_custom_command(TARGET dusklight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:crashpad_handler>"
"$<TARGET_FILE_DIR:dusklight>"
COMMENT "Copying crashpad handler"
)
endif ()
if (ANDROID)
# SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static
# archive, force an undefined reference so the linker keeps the SDL_main object.
target_link_options(dusk PRIVATE "-Wl,-u,SDL_main")
target_link_options(dusklight PRIVATE "-Wl,-u,SDL_main")
endif ()
if (NOT APPLE)
add_custom_command(TARGET dusk POST_BUILD
add_custom_command(TARGET dusklight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/res"
"$<TARGET_FILE_DIR:dusk>/res"
"$<TARGET_FILE_DIR:dusklight>/res"
COMMENT "Copying resources"
)
endif ()
@@ -459,9 +470,9 @@ endif ()
if (WIN32)
set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows)
set(DUSK_WINDOWS_ICON_PNG ${CMAKE_CURRENT_SOURCE_DIR}/res/icon.png)
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusk.ico)
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusk.rc)
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusk.manifest)
set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusklight.ico)
set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusklight.rc)
set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusklight.manifest)
add_custom_command(
OUTPUT ${DUSK_WINDOWS_ICON_ICO}
@@ -474,14 +485,14 @@ if (WIN32)
COMMENT "Generating Windows icon"
)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.rc.in ${DUSK_WINDOWS_RC} @ONLY)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY)
configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusklight.rc.in ${DUSK_WINDOWS_RC} @ONLY)
target_sources(dusk PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
set_target_properties(dusk PROPERTIES WIN32_EXECUTABLE TRUE)
target_sources(dusklight PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC})
set_target_properties(dusklight PROPERTIES WIN32_EXECUTABLE TRUE)
if (MSVC)
target_link_options(dusk PRIVATE /MANIFEST:NO)
target_link_options(dusklight PRIVATE /MANIFEST:NO)
endif ()
endif ()
@@ -497,10 +508,10 @@ if (APPLE)
file(GLOB_RECURSE DUSK_RESOURCE_FILES
"${DUSK_RESOURCE_DIR}/Assets.car"
"${DUSK_RESOURCE_DIR}/Base.lproj/*"
"${DUSK_RESOURCE_DIR}/Dusk.icns")
"${DUSK_RESOURCE_DIR}/Dusklight.icns")
file(GLOB_RECURSE DUSK_APP_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/*")
target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES})
target_sources(dusk PRIVATE ${DUSK_APP_RESOURCE_FILES})
target_sources(dusklight PRIVATE ${DUSK_RESOURCE_FILES})
target_sources(dusklight PRIVATE ${DUSK_APP_RESOURCE_FILES})
foreach (FILE ${DUSK_RESOURCE_FILES})
file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE})
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
@@ -512,29 +523,36 @@ if (APPLE)
set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}")
endforeach ()
set_target_properties(
dusk PROPERTIES
dusklight PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}
MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}
OUTPUT_NAME Dusk
OUTPUT_NAME Dusklight
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES"
)
endif ()
if (APPLE AND NOT IOS AND NOT TVOS)
find_library(APPKIT_FRAMEWORK AppKit REQUIRED)
target_sources(dusklight PRIVATE src/dusk/file_select_macos.mm)
set_source_files_properties(src/dusk/file_select_macos.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
target_link_libraries(dusklight PRIVATE ${APPKIT_FRAMEWORK})
endif ()
if (IOS)
find_library(UIKIT_FRAMEWORK UIKit REQUIRED)
find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers REQUIRED)
target_sources(dusk PRIVATE src/dusk/ios/FileSelectDialog.m)
target_sources(dusklight PRIVATE src/dusk/ios/FileSelectDialog.m)
set_source_files_properties(src/dusk/ios/FileSelectDialog.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
target_link_libraries(dusk PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
target_link_libraries(dusklight PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
endif ()
include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
aurora_copy_runtime_dlls(dusk)
aurora_copy_runtime_dlls(dusklight)
if (DUSK_SELECTED_OPT)
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
@@ -572,13 +590,13 @@ function(get_target_prefix target result_var)
endif ()
endif ()
endfunction()
list(APPEND BINARY_TARGETS dusk)
list(APPEND BINARY_TARGETS dusklight)
set(EXTRA_TARGETS "")
if (TARGET crashpad_handler)
list(APPEND EXTRA_TARGETS crashpad_handler)
endif ()
install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX})
aurora_install_runtime_dlls(dusk ${CMAKE_INSTALL_PREFIX})
aurora_install_runtime_dlls(dusklight ${CMAKE_INSTALL_PREFIX})
if (NOT APPLE)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX})
endif ()
+39 -4
View File
@@ -352,6 +352,31 @@
"ANDROID_ABI": "x86_64"
}
},
{
"name": "x-android-ci",
"hidden": true,
"inherits": [
"android-base",
"ci"
],
"cacheVariables": {
"DUSK_ENABLE_SENTRY_NATIVE": {
"type": "BOOL",
"value": false
}
}
},
{
"name": "x-android-ci-arm64",
"binaryDir": "${sourceDir}/build/android-arm64",
"inherits": [
"x-android-ci"
],
"cacheVariables": {
"ANDROID_ABI": "arm64-v8a",
"Rust_CARGO_TARGET": "aarch64-linux-android"
}
},
{
"name": "x-linux-ci",
"hidden": true,
@@ -412,6 +437,7 @@
"x-macos-ci"
],
"cacheVariables": {
"AURORA_DAWN_PROVIDER": "vendor",
"CMAKE_OSX_ARCHITECTURES": "x86_64",
"Rust_CARGO_TARGET": "x86_64-apple-darwin"
}
@@ -525,7 +551,7 @@
"description": "iOS release build with debug info",
"displayName": "iOS RelWithDebInfo",
"targets": [
"dusk"
"dusklight"
]
},
{
@@ -534,7 +560,7 @@
"description": "tvOS release build with debug info",
"displayName": "tvOS RelWithDebInfo",
"targets": [
"dusk"
"dusklight"
]
},
{
@@ -543,7 +569,7 @@
"description": "Android arm64-v8a release build with debug info",
"displayName": "Android arm64-v8a RelWithDebInfo",
"targets": [
"dusk"
"dusklight"
]
},
{
@@ -552,7 +578,16 @@
"description": "Android x86_64 release build with debug info",
"displayName": "Android x86_64 RelWithDebInfo",
"targets": [
"dusk"
"dusklight"
]
},
{
"name": "x-android-ci-arm64",
"configurePreset": "x-android-ci-arm64",
"description": "(Internal) Android CI arm64-v8a",
"displayName": "(Internal) Android CI arm64-v8a",
"targets": [
"dusklight"
]
},
{
+52 -20
View File
@@ -1,37 +1,69 @@
![DuskLogo](res/logo-mascot.png)
<div align="center">
<img src="res/logo.png" alt="Logo" width="640">
- ### **[Official Website](https://twilitrealm.dev)**
- ### **[Discord](https://discord.gg/QACynxeyna)**
<p align="center">
<a href="https://twilitrealm.dev">Official Website</a>
<a href="https://discord.gg/6NpMhefCK9">Discord</a>
</p>
</div>
# Overview
Dusk is a reverse-engineered reimplementation of Twilight Princess.
Dusklight is a reverse-engineered reimplementation of Twilight Princess.
It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
# Setup
**⚠️ Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.**
### 1. Verify your ROM dump
First make sure your dump of the game is clean and supported by Dusk. You can do this by checking the sha1 hash of your dump against this list of supported versions.
> [!IMPORTANT]
> Dusklight does *not* provide any copyrighted assets. You must provide your own copy of the original game.
| Version | sha1 hash |
|--------------| ---------------------------------------- |
| GameCube USA | 75edd3ddff41f125d1b4ce1a40378f1b565519e7 |
| GameCube PAL | 2601822a488eeb86fb89db16ca8f29c2c953e1ca |
> [!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.
### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases)
### 1. Verify your dump
First, make sure your dump of the game is clean and supported by Dusklight. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
| Version | SHA-1 hash |
|--------------| ------------------------------------------ |
| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` |
| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` |
*Support for other versions of the game is planned in the future.
### 2. Download [Dusklight](https://github.com/TwilitRealm/dusklight/releases)
### 3. Setup the game
- Extract the zip folder
- Launch Dusk
- Select Options, then set the ISO Path to your supported game dump
- Press Start Game to play!
**Windows / macOS / Linux**
- Extract the .zip file
- Launch Dusklight
- Press **Select Disc Image** and provide the path to your supported game dump
- Press **Play**!
![Dusk options](assets/dusk_options.png)
**iOS**
- Follow the [iOS setup guide](docs/ios-install-altstore.md)
**Android**
- Install the Dusklight APK
- Launch Dusklight
- Press **Select Disc Image** and provide the path to your supported game dump
- Press **Play**!
# Building
If you'd like to build Dusk from source, please read the [build instructions](docs/building.md).
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.
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. Please also see the [code conventions](docs/code-conventions.md).
# Credits
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors).
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusklight/graphs/contributors).
<br/>
<div align="center">
<a href="https://github.com/encounter/aurora">
<img src="assets/aurora-powered.png" alt="Powered by Aurora" width="800">
</a>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

+66
View File
@@ -0,0 +1,66 @@
<svg width="600" height="600" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
<circle cx="150" cy="150" r="105" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="95" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="60" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="75" fill="none" stroke="white" stroke-width="4"/>
<defs>
<line id="ray" x1="150" y1="55" x2="150" y2="45"/>
<clipPath id="zigzag-clip">
<circle cx="150" cy="150" r="75"/>
</clipPath>
</defs>
<g stroke="white" stroke-width="3">
<use href="#ray"/>
<use href="#ray" transform="rotate(18 150 150)"/>
<use href="#ray" transform="rotate(36 150 150)"/>
<use href="#ray" transform="rotate(54 150 150)"/>
<use href="#ray" transform="rotate(72 150 150)"/>
<use href="#ray" transform="rotate(90 150 150)"/>
<use href="#ray" transform="rotate(108 150 150)"/>
<use href="#ray" transform="rotate(126 150 150)"/>
<use href="#ray" transform="rotate(144 150 150)"/>
<use href="#ray" transform="rotate(162 150 150)"/>
<use href="#ray" transform="rotate(180 150 150)"/>
<use href="#ray" transform="rotate(198 150 150)"/>
<use href="#ray" transform="rotate(216 150 150)"/>
<use href="#ray" transform="rotate(234 150 150)"/>
<use href="#ray" transform="rotate(252 150 150)"/>
<use href="#ray" transform="rotate(270 150 150)"/>
<use href="#ray" transform="rotate(288 150 150)"/>
<use href="#ray" transform="rotate(306 150 150)"/>
<use href="#ray" transform="rotate(324 150 150)"/>
<use href="#ray" transform="rotate(342 150 150)"/>
</g>
<polygon fill="none" stroke="white" stroke-width="4" opacity="1" clip-path="url(#zigzag-clip)"
points="
126.82,78.67
150,90
173.18,78.67
185.27,101.46
210.68,105.92
207.06,131.46
225,150
207.06,168.54
210.68,194.08
185.27,198.54
173.18,221.33
150,210
126.82,221.33
114.73,198.54
89.32,194.08
92.94,168.54
75,150
92.94,131.46
89.32,105.92
114.73,101.46
"/>
<g fill="none" stroke="white" stroke-width="4">
<polygon points="150,105 130,140 170,140"/>
<polygon points="130,140 110,175 150,175"/>
<polygon points="170,140 150,175 190,175"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+17 -9
View File
@@ -1,18 +1,26 @@
#!/bin/bash -ex
shopt -s extglob
if [[ -n "${GITHUB_WORKSPACE:-}" ]]; then
cd "$GITHUB_WORKSPACE"
fi
build_dir="$PWD/build"
linuxdeploy="$build_dir/linuxdeploy-$(uname -m).AppImage"
# Get linuxdeploy
cd "$RUNNER_WORKSPACE"
curl -fOL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage
chmod +x linuxdeploy-$(uname -m).AppImage
mkdir -p "$build_dir"
curl -fL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage" -o "$linuxdeploy"
chmod +x "$linuxdeploy"
# Build AppImage
cd "$GITHUB_WORKSPACE"
mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}}
cp -r build/install/!(*.*) build/appdir/usr/bin
for install_path in build/install/*; do
[[ "$(basename "$install_path")" == *.* ]] && continue
cp -r "$install_path" build/appdir/usr/bin
done
cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor
cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications
cp platforms/freedesktop/dusklight.desktop build/appdir/usr/share/applications
cd build/install
VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage
VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$build_dir/appdir" --output appimage
+5 -5
View File
@@ -36,10 +36,10 @@
sudo dnf groupinstall "Development Tools" "Development Libraries"
```
#### Setup
Clone and initialize the Dusk repository
Clone and initialize the Dusklight repository
```sh
git clone --recursive https://github.com/TwilitRealm/dusk.git
cd dusk
git clone --recursive https://github.com/TwilitRealm/dusklight.git
cd dusklight
git pull
git submodule update --init --recursive
```
@@ -93,6 +93,6 @@ Alternate presets available:
#### Running
Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
```sh
build/{preset}/dusk /path/to/game.rvz
build/{preset}/dusklight/path/to/game.rvz
```
If no path is specified, Dusk defaults to `game.iso` in the current working directory.
If no path is specified, Dusklight defaults to `game.iso` in the current working directory.
+13
View File
@@ -0,0 +1,13 @@
# Code conventions for Dusk
## Upstream when appropriate
Bug fixes, documentation improvements, code cleanup, etc that also apply to the [original decompilation project](https://github.com/zeldaret/tp) should preferably be PR'd there.
## Properly indicate Dusk-modified code
When modifying original game code (i.e. in decomp) for Dusk's purposes, please clearly delineate such code as being Dusk-specific. Generally, this can be done by using `#if TARGET_PC` and keeping the original code in place. Use `#if AVOID_UB` for Undefined Behavior fixes to the original codebase.
## Miscellaneous things
* The original codebase makes heavy use of global `operator new` and similar overloads to allocate into a strict tree of heaps. This would cause many linkage headaches for us, so effectively all uses of `new` or `delete` in the original game code have been replaced with `JKR_NEW`, `JKR_DELETE`, or similar macros. See `JKRHeap.h` for the full list.
+46
View File
@@ -0,0 +1,46 @@
# Installing Dusklight on iOS via AltStore
## Prerequisites
- Mac with Homebrew installed
- iPhone connected via USB
- Dusklight IPA file (download the latest `Dusklight-vX.X.X-ios-arm64.ipa` from the [releases page](https://github.com/TwilitRealm/dusk/releases))
- Game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL)
## 1. Install AltServer
```sh
brew install altserver
open -a AltServer
```
AltServer will appear in your menu bar.
## 2. Enable Developer Mode (iOS 16+)
- On your iPhone, go to **Settings > Privacy & Security > Developer Mode**
- Toggle it on and restart when prompted
## 3. Install AltStore on Your iPhone
- Click AltServer in the menu bar
- Click **Install AltStore > [Your iPhone]**
- Enter your Apple ID credentials when prompted
- On your iPhone, go to **Settings > General > VPN & Device Management**
- Tap your Apple ID under "Developer App" and tap **Trust**
## 4. Copy Files to Your iPhone
Transfer the IPA and game disc to your iPhone so they're accessible in the Files app. A few ways to do this:
- **AirDrop** - Right-click the files on your Mac and choose Share > AirDrop
- **iCloud Drive** - Place files in iCloud Drive on your Mac and they'll sync to Files on your iPhone
- **USB transfer** - Connect your iPhone and drag files via Finder's sidebar
- **Cloud storage** - Upload to Google Drive, Dropbox, etc. and download on your iPhone
## 5. Install via AltStore
- Open **AltStore** on your iPhone
- Go to the **My Apps** tab
- Tap the **+** button (top left)
- Open the **Files** app and select the `.ipa` file
+1 -1
+32 -13
View File
@@ -1411,6 +1411,7 @@ set(DOLPHIN_FILES
)
set(DUSK_FILES
include/dusk/action_bindings.h
include/dusk/endian_gx.hpp
include/dusk/config.hpp
include/dusk/dvd_asset.hpp
@@ -1420,6 +1421,8 @@ set(DUSK_FILES
src/dusk/asserts.cpp
src/dusk/config.cpp
src/dusk/crash_reporting.cpp
src/dusk/data.cpp
src/dusk/data.hpp
src/dusk/endian.cpp
src/dusk/extras.c
src/dusk/file_select.cpp
@@ -1430,39 +1433,39 @@ set(DUSK_FILES
src/dusk/gyro.cpp
src/dusk/gamepad_color.cpp
src/dusk/autosave.cpp
src/dusk/http/http.hpp
src/dusk/io.cpp
src/dusk/layout.cpp
src/dusk/logging.cpp
src/dusk/settings.cpp
src/dusk/speedrun.cpp
src/dusk/stubs.cpp
src/dusk/update_check.cpp
src/dusk/update_check.hpp
#src/dusk/m_Do_ext_dusk.cpp
src/dusk/imgui/ImGuiConfig.hpp
src/dusk/imgui/ImGuiConsole.hpp
src/dusk/imgui/ImGuiConsole.cpp
src/dusk/imgui/ImGuiEngine.cpp
src/dusk/imgui/ImGuiEngine.hpp
src/dusk/imgui/ImGuiMenuGame.cpp
src/dusk/imgui/ImGuiMenuGame.hpp
src/dusk/imgui/ImGuiBloomWindow.cpp
src/dusk/imgui/ImGuiBloomWindow.hpp
src/dusk/imgui/ImGuiEnhancedLightingWindow.cpp
src/dusk/imgui/ImGuiEnhancedLightingWindow.hpp
src/dusk/imgui/ImGuiMenuTools.cpp
src/dusk/imgui/ImGuiMenuTools.hpp
src/dusk/imgui/ImGuiPreLaunchWindow.cpp
src/dusk/imgui/ImGuiPreLaunchWindow.hpp
src/dusk/imgui/ImGuiFirstRunPreset.hpp
src/dusk/imgui/ImGuiFirstRunPreset.cpp
src/dusk/imgui/ImGuiActorSpawner.cpp
src/dusk/imgui/ImGuiProcessOverlay.cpp
src/dusk/imgui/ImGuiCameraOverlay.cpp
src/dusk/imgui/ImGuiHeapOverlay.cpp
src/dusk/imgui/ImGuiDebugPad.cpp
src/dusk/imgui/ImGuiControllerOverlay.cpp
src/dusk/imgui/ImGuiStubLog.cpp
src/dusk/imgui/ImGuiMapLoader.cpp
src/dusk/imgui/ImGuiSaveEditor.cpp
src/dusk/imgui/ImGuiStateShare.hpp
src/dusk/imgui/ImGuiStateShare.cpp
src/dusk/imgui/ImGuiAchievements.hpp
src/dusk/imgui/ImGuiAchievements.cpp
src/dusk/ui/achievements.cpp
src/dusk/ui/achievements.hpp
src/dusk/ui/bool_button.cpp
src/dusk/ui/bool_button.hpp
src/dusk/ui/button.cpp
@@ -1477,8 +1480,12 @@ set(DUSK_FILES
src/dusk/ui/editor.hpp
src/dusk/ui/event.cpp
src/dusk/ui/event.hpp
src/dusk/ui/graphics_tuner.cpp
src/dusk/ui/graphics_tuner.hpp
src/dusk/ui/input.cpp
src/dusk/ui/input.hpp
src/dusk/ui/modal.cpp
src/dusk/ui/modal.hpp
src/dusk/ui/nav_types.hpp
src/dusk/ui/number_button.cpp
src/dusk/ui/number_button.hpp
@@ -1486,12 +1493,14 @@ set(DUSK_FILES
src/dusk/ui/overlay.hpp
src/dusk/ui/pane.cpp
src/dusk/ui/pane.hpp
src/dusk/ui/popup.cpp
src/dusk/ui/popup.hpp
src/dusk/ui/menu_bar.cpp
src/dusk/ui/menu_bar.hpp
src/dusk/ui/prelaunch.cpp
src/dusk/ui/prelaunch.hpp
src/dusk/ui/prelaunch_options.cpp
src/dusk/ui/prelaunch_options.hpp
src/dusk/ui/preset.cpp
src/dusk/ui/preset.hpp
src/dusk/ui/reporting.cpp
src/dusk/ui/reporting.hpp
src/dusk/ui/select_button.cpp
src/dusk/ui/select_button.hpp
src/dusk/ui/settings.cpp
@@ -1512,6 +1521,16 @@ set(DUSK_FILES
src/dusk/OSReport.cpp
src/dusk/OSThread.cpp
src/dusk/OSMutex.cpp
src/dusk/discord.cpp
src/dusk/discord.hpp
src/dusk/discord_presence.cpp
src/dusk/version.cpp
src/dusk/action_bindings.cpp
)
set(DUSK_HTTP_BACKEND_FILES
src/dusk/http/no_backend.cpp
src/dusk/http/curl.cpp
src/dusk/http/winhttp.cpp
src/dusk/http/url_session.mm
)
+205 -23
View File
@@ -4,30 +4,212 @@
};
outputs = { self, nixpkgs }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
dusk = pkgs.stdenv.mkDerivation {
name = "dusk";
src = ./.;
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
pkgs.wayland
];
buildInputs = [
pkgs.libGL
pkgs.libX11
pkgs.libXcursor
pkgs.libxi
pkgs.libxcb
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
];
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
pkgsFor = system: import nixpkgs { inherit system; };
# Dependencies that are not packaged in nixpkgs (used by the Linux package build):
buildSources = pkgs: {
aurora-src = pkgs.fetchFromGitHub {
owner = "encounter";
repo = "aurora";
rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
};
dawn-src = pkgs.fetchzip {
url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
stripRoot = false;
};
nod-src = pkgs.fetchzip {
url = "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.8/libnod-linux-x86_64.tar.gz";
hash = "sha256-mUqvLsbsqaZ+HAjMmHYPYO+MgtanGRTw7Gzn5uXR5rE=";
stripRoot = false;
};
# The version of imgui on nixpkgs does not map cleanly.
imgui-src = pkgs.fetchFromGitHub {
owner = "ocornut";
repo = "imgui";
rev = "v1.91.9b-docking";
hash = "sha256-mQOJ6jCN+7VopgZ61yzaCnt4R1QLrW7+47xxMhFRHLQ=";
};
sqlite-src = pkgs.fetchzip {
url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip";
hash = "sha256-pNMR8zxaaqfAzQ0AQBOXMct4usdjey1Q0Gnitg06UhM=";
};
rmlui-src = pkgs.fetchzip {
url = "https://github.com/mikke89/RmlUi/archive/f9b8c9e2935d5df2c7dff2c190d3968e99b0c3dc.tar.gz";
hash = "sha256-g4O/JZUrrcseOz8o2QJRt+2CeuiLnVeuDJc906xvuIg=";
};
};
# Dusklight Actual (Linux x86_64 only — relies on prebuilt dawn/nod binaries)
mkDusklight = pkgs:
let srcs = buildSources pkgs; 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 = [
"-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
'';
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
pkgs.wayland
];
buildInputs = [
pkgs.libGL
pkgs.libX11
pkgs.libXcursor
pkgs.libxi
pkgs.libxcb
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.fmt
pkgs.tracy
pkgs.freetype
pkgs.zstd
];
};
# Tooling common to every supported host (Linux and macOS).
commonDevTools = pkgs: [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
pkgs.git
pkgs.python3
pkgs.python3Packages.markupsafe
pkgs.rustc
pkgs.cargo
pkgs.sccache
];
# Linux-only system libraries — mirrors the apt deps from .github/workflows/build.yml
# so the cmake presets resolve the same set of headers as CI.
linuxDevDeps = pkgs: [
# Compilers / linkers
pkgs.clang
pkgs.lld
# C/C++ utilities
pkgs.curl
pkgs.openssl
pkgs.zlib
pkgs.libpng
pkgs.libjpeg_turbo
pkgs.freetype
pkgs.zstd
pkgs.fmt
pkgs.tracy
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.ncurses
pkgs.libunwind
pkgs.libusb1
pkgs.fuse
# Wayland / display server
pkgs.wayland
pkgs.wayland-protocols
pkgs.libxkbcommon
pkgs.libdecor
# OpenGL / Vulkan
pkgs.libGL
pkgs.libGLU
pkgs.libglvnd
pkgs.vulkan-headers
pkgs.vulkan-loader
# X11
pkgs.libX11
pkgs.libxcb
pkgs.libXcursor
pkgs.libxi
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libxinerama
# Audio
pkgs.alsa-lib
pkgs.libpulseaudio
pkgs.pipewire
# System integration
pkgs.dbus
pkgs.udev
pkgs.gtk3
];
# On macOS we deliberately avoid pulling Nix's cc-wrapper so CMake picks up
# Apple Clang and the Xcode SDK directly, matching the macOS CI workflow.
mkDarwinShell = pkgs:
pkgs.mkShellNoCC {
packages = commonDevTools pkgs;
shellHook = ''
echo "Dusklight dev shell (macOS)"
echo "Requires Xcode Command Line Tools for Apple Clang and the macOS SDK."
echo "Configure: cmake --preset macos-default-relwithdebinfo"
echo "Build: cmake --build --preset macos-default-relwithdebinfo"
'';
};
mkLinuxShell = pkgs:
pkgs.mkShell {
packages = (commonDevTools pkgs) ++ (linuxDevDeps pkgs);
shellHook = ''
echo "Dusklight dev shell (Linux)"
echo "Configure: cmake --preset linux-default-relwithdebinfo"
echo " cmake --preset linux-clang-relwithdebinfo"
echo "Build: cmake --build --preset <preset>"
'';
};
mkDevShell = pkgs:
if pkgs.stdenv.isDarwin
then mkDarwinShell pkgs
else mkLinuxShell pkgs;
in {
packages.x86_64-linux.default = dusk;
packages.x86_64-linux.default = mkDusklight (pkgsFor "x86_64-linux");
devShells = forAllSystems (system: {
default = mkDevShell (pkgsFor system);
});
};
}
+12
View File
@@ -4552,6 +4552,18 @@ public:
void handleWolfHowl();
void handleQuickTransform();
bool checkGyroAimContext();
void onIronBallChainInterpCallback();
static const int IRON_BALL_CHAIN_COUNT = 102;
cXyz mIBChainInterpPrevPos[IRON_BALL_CHAIN_COUNT];
cXyz mIBChainInterpCurrPos[IRON_BALL_CHAIN_COUNT];
csXyz mIBChainInterpPrevAngle[IRON_BALL_CHAIN_COUNT];
csXyz mIBChainInterpCurrAngle[IRON_BALL_CHAIN_COUNT];
cXyz mIBChainInterpPrevHandRoot;
cXyz mIBChainInterpCurrHandRoot;
bool mIBChainInterpPrevValid;
bool mIBChainInterpCurrValid;
#endif
}; // Size: 0x385C
+6
View File
@@ -80,6 +80,12 @@ public:
/* 0x125C */ u32 field_0x125c;
/* 0x1260 */ u8 field_0x1260[0x126C - 0x1260];
/* 0x126C */ u8 HIOInit;
#if TARGET_PC
cXyz mStalkLineInterpPrev[12];
cXyz mStalkLineInterpCurr[12];
bool mStalkLineInterpPrevValid;
bool mStalkLineInterpCurrValid;
#endif
};
STATIC_ASSERT(sizeof(e_db_class) == 0x1270);
+6
View File
@@ -73,6 +73,12 @@ public:
/* 0x124C */ f32 field_0x124c;
/* 0x1250 */ u8 field_0x1250[0x1264 - 0x1250];
/* 0x1264 */ u8 HIOInit;
#if TARGET_PC
cXyz mStalkLineInterpPrev[12];
cXyz mStalkLineInterpCurr[12];
bool mStalkLineInterpPrevValid;
bool mStalkLineInterpCurrValid;
#endif
};
STATIC_ASSERT(sizeof(e_hb_class) == 0x1268);
+9
View File
@@ -81,6 +81,15 @@ public:
/* 0x306D */ u8 field_0x306D[0x307C - 0x306D];
/* 0x307C */ u32 mBodyEffEmtrID;
/* 0x3080 */ u8 mInitHIO;
#if TARGET_PC
static const int HAIR_STRAND_COUNT = 22;
static const int HAIR_SEGMENT_COUNT = 16;
cXyz mHairInterpPrev[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT];
cXyz mHairInterpCurr[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT];
bool mHairInterpPrevValid;
bool mHairInterpCurrValid;
#endif
};
STATIC_ASSERT(sizeof(e_s1_class) == 0x3084);
+6
View File
@@ -74,6 +74,12 @@ public:
/* 0x1250 */ f32 field_0x1250;
/* 0x1254 */ u8 field_0x1254[0x1268 - 0x1254];
/* 0x1268 */ u8 field_0x1268;
#if TARGET_PC
cXyz mLineMatInterpPrev[12];
cXyz mLineMatInterpCurr[12];
bool mLineMatInterpPrevValid;
bool mLineMatInterpCurrValid;
#endif
};
STATIC_ASSERT(sizeof(e_yd_class) == 0x126c);
+9
View File
@@ -63,6 +63,15 @@ public:
/* 0x0BB4 */ yg_ke_s mYgKes[13];
/* 0x1880 */ mDoExt_3DlineMat0_c mLineMat;
/* 0x189C */ u8 mIsFirstSpawn;
#if TARGET_PC
static const int TENTACLE_STRAND_COUNT = 13;
static const int TENTACLE_SEGMENT_COUNT = 10;
cXyz mTentacleInterpPrev[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT];
cXyz mTentacleInterpCurr[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT];
bool mTentacleInterpPrevValid;
bool mTentacleInterpCurrValid;
#endif
};
STATIC_ASSERT(sizeof(e_yg_class) == 0x18a0);
+6
View File
@@ -77,6 +77,12 @@ public:
/* 0x1260 */ u32 field_0x1260;
/* 0x1260 */ u8 field_0x1264[0x1270 - 0x1264];
/* 0x1270 */ bool mIsHIOOwner;
#if TARGET_PC
cXyz mLineInterpPrev[12];
cXyz mLineInterpCurr[12];
bool mLineInterpPrevValid;
bool mLineInterpCurrValid;
#endif
};
STATIC_ASSERT(sizeof(e_yh_class) == 0x1274);
+12
View File
@@ -31,6 +31,10 @@ public:
csXyz* getAngle() { return field_0x8a4; }
J3DModelData* getModelData() { return mModelData; }
#if TARGET_PC
void onInterpCallback();
#endif
private:
/* 0x568 */ request_of_phase_process_class mPhase;
/* 0x570 */ J3DModelData* mModelData;
@@ -42,6 +46,14 @@ private:
/* 0x694 */ cXyz field_0x694[22];
/* 0x79C */ cXyz field_0x79c[22];
/* 0x8A4 */ csXyz field_0x8a4[22];
#if TARGET_PC
static const int CHAIN_COUNT = 22;
cXyz mChainInterpPrev[CHAIN_COUNT];
cXyz mChainInterpCurr[CHAIN_COUNT];
bool mChainInterpPrevValid;
bool mChainInterpCurrValid;
#endif
};
STATIC_ASSERT(sizeof(daObjFchain_c) == 0x928);
+18
View File
@@ -118,6 +118,18 @@ class camera_class;
class dCamera_c;
typedef bool (dCamera_c::*engine_fn)(s32);
#if TARGET_PC
struct DebugFlyCam {
bool initialized;
f32 pitch;
f32 yaw;
cXyz savedCenter;
cXyz savedEye;
f32 savedFovy;
cSAngle savedBank;
};
#endif
class dCamera_c {
public:
class dCamInfo_c {
@@ -1028,6 +1040,8 @@ public:
bool test2Camera(s32);
#if TARGET_PC
bool freeCamera();
bool executeDebugFlyCam();
void deactivateDebugFlyCam();
#endif
bool towerCamera(s32);
bool hookshotCamera(s32);
@@ -1376,6 +1390,10 @@ public:
/* 0x970 */ dCamSetup_c mCamSetup;
/* 0xAEC */ dCamParam_c mCamParam;
/* 0xB0C */ u8 field_0xb0c;
#if TARGET_PC
DebugFlyCam mDebugFlyCam;
#endif
}; // Size: 0xB10
dCamera_c* dCam_getBody();
+6
View File
@@ -1845,6 +1845,12 @@ inline void dComIfGs_addDeathCount() {
g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().addDeathCount();
}
#if TARGET_PC
inline u16 dComIfGs_getDeathCount() {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getDeathCount();
}
#endif
inline char* dComIfGs_getPlayerName() {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getPlayerName();
}
+4
View File
@@ -196,7 +196,11 @@ public:
/* 0x108 */ int mSkipTimer;
/* 0x10C */ int mSkipParameter;
/* 0x110 */ BOOL mIsSkipFade;
#if TARGET_PC
/* 0x114 */ char mSkipEventName[21];
#else
/* 0x114 */ char mSkipEventName[20];
#endif
/* 0x128 */ u8 mCompulsory;
/* 0x129 */ bool mRoomInfoSet;
/* 0x12C */ int mRoomNo;
+3
View File
@@ -223,6 +223,9 @@ private:
/* 0x8F */ u8 field_0x8f;
/* 0x90 */ u8 field_0x90;
/* 0x91 */ u8 field_0x91;
#if TARGET_PC
bool previousMirror;
#endif
}; // Size: 0x94
class dMap_HIO_list_c : public dMpath_HIO_n::hioList_c {
+6
View File
@@ -169,6 +169,12 @@ public:
void mapBlink() {}
#if PLATFORM_WII || TARGET_PC
f32 getMirrorPosX(f32 param_0, f32 param_1) {
return (field_0x11dc * 2.0f - (param_0 + param_1)) - param_1;
}
#endif
// Unknown name
struct RegionTexData {
/* 0x00 */ float mMinX;
+10
View File
@@ -66,6 +66,16 @@ public:
_c90 = param_2;
}
#if PLATFORM_WII || TARGET_PC
f32 getMirrorCenterPosX(f32 param_0, f32 param_1) {
if (_c90) {
return (mCenterPosX * 2.0f - (param_0 + param_1)) - param_1;
}
return param_0;
}
#endif
struct Stage_c {
// Incomplete class
+3
View File
@@ -486,6 +486,9 @@ public:
mDeathCount++;
}
}
#if TARGET_PC
u16 getDeathCount() const { return mDeathCount; }
#endif
char* getPlayerName() const { return const_cast<char*>(mPlayerName); }
void setPlayerName(const char* i_name) {
#if AVOID_UB
+1 -5
View File
@@ -12,9 +12,8 @@
namespace dusk {
enum class AchievementCategory : uint8_t {
Story,
Collection,
Challenge,
Collection,
Minigame,
Misc,
Glitched
@@ -50,8 +49,6 @@ public:
bool hasSignal(const char* key) const;
std::vector<Achievement> getAchievements() const;
bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); }
std::string consumePendingUnlock();
private:
struct Entry {
@@ -68,7 +65,6 @@ private:
std::unordered_set<std::string_view> m_signals;
bool m_loaded = false;
bool m_dirty = false;
std::queue<std::string> m_pendingUnlocks;
};
} // namespace dusk
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <unordered_map>
#include "dusk/config_var.hpp"
namespace dusk {
enum class ActionBinds {
FIRST_PERSON_CAMERA,
CALL_MIDNA,
OPEN_DUSKLIGHT_MENU,
TURBO_SPEED_BUTTON,
COUNT,
};
struct ActionBindData {
std::array<config::ActionBindConfigVar, 4>* configVars{};
std::string actionName{};
};
struct ActionBindPressData {
bool pressedCurFrame{false};
bool pressedPrevFrame{false};
};
using ActionBindsMap = std::unordered_map<ActionBinds, ActionBindData>;
ActionBindsMap& getActionBinds();
bool isActionBound(ActionBinds action, u32 port);
void updateActionBindings();
bool getActionBindTrig(ActionBinds action, u32 port);
bool getActionBindHold(ActionBinds action, u32 port);
bool getActionBindHoldAnyPort(ActionBinds action);
int getActionBindButton(ActionBinds action, u32 port);
}
+6 -1
View File
@@ -7,7 +7,12 @@ namespace dusk {
*
* This gets used for file paths and such, and cannot be changed!
*/
constexpr auto AppName = "Dusk";
constexpr auto AppName = "Dusklight";
/**
* Previous AppName to migrate data from.
*/
constexpr auto LegacyAppName = "Dusk";
/**
* \brief The internal organization name for the game.
+1
View File
@@ -13,5 +13,6 @@ void enterAutoSave();
void autoSaving();
void waitingForWrite();
void endAutoSave();
void toggleAutoSave(bool enabled);
#endif
+13
View File
@@ -1,6 +1,7 @@
#ifndef DUSK_CONFIG_HPP
#define DUSK_CONFIG_HPP
#include <functional>
#include <stdexcept>
#include "nlohmann/json.hpp"
#include "config_var.hpp"
@@ -111,6 +112,18 @@ void Save();
*/
ConfigVarBase* GetConfigVar(std::string_view name);
/**
* \brief Resets all custom action bindings for a specific port to nothing
*
* @param port The port to be cleared of action bindings
*/
void ClearAllActionBindings(int port);
/**
* \brief Call a function on every registered CVar.
*/
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback);
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl() {
static ConfigImpl<T> config;
+48
View File
@@ -48,6 +48,13 @@ enum class ConfigVarLayer : u8 {
* Will not get saved to config.
*/
Override,
/**
* The CVar is temporarily overridden by speedrun mode.
* Will not get saved to config. Cleared when speedrun mode is disabled.
* Lower priority than Override, so launch args still win.
*/
Speedrun,
};
class ConfigImplBase;
@@ -113,6 +120,12 @@ public:
* This is necessary to make it legal to access.
*/
void markRegistered();
/**
* Clear a speedrun-mode override if one is active on this CVar.
* Safe to call on any CVar, no-op if not at the Speedrun layer.
*/
virtual void clearSpeedrunOverride() {}
};
template <typename T>
@@ -189,6 +202,7 @@ public:
case ConfigVarLayer::Value:
return value;
case ConfigVarLayer::Override:
case ConfigVarLayer::Speedrun:
return overrideValue;
default:
abort();
@@ -239,8 +253,42 @@ public:
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override;
}
/**
* \brief Give a CVar a speedrun-mode override value.
*
* Lower priority than a launch-arg override. Cleared when speedrun mode is disabled.
* The overridden value will not get saved to config.
*
* @param newValue The new value the CVar will get.
*/
void setSpeedrunValue(T newValue) {
checkRegistered();
if (layer != ConfigVarLayer::Override) {
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 = ConfigVarLayer::Value;
}
}
};
using ActionBindConfigVar = ConfigVar<int>;
}
#endif // DUSK_CONFIG_VAR_HPP
+13 -4
View File
@@ -1,8 +1,17 @@
#pragma once
namespace dusk {
namespace dusk::crash_reporting {
void InitializeCrashReporting();
void ShutdownCrashReporting();
enum class Consent {
Unavailable,
Unknown,
Given,
Revoked,
};
} // namespace dusk
void initialize();
void shutdown();
Consent get_consent();
void set_consent(bool enabled);
} // namespace dusk::crash_reporting
+8 -12
View File
@@ -1,18 +1,14 @@
#pragma once
#ifdef DUSK_DISCORD_RPC
#ifdef DUSK_DISCORD
namespace dusk {
namespace discord {
namespace dusk::discord {
void Initialize();
void initialize();
void run_callbacks();
void update_presence();
void shutdown();
void RunCallbacks();
} // namespace dusk::discord
void UpdatePresence();
void Shutdown();
}
}
#endif // DUSK_DISCORD_RPC
#endif // DUSK_DISCORD
+1
View File
@@ -12,6 +12,7 @@ void rollgoalTableOffset(s16& out_ax, s16& out_az);
extern bool s_sensor_keep_alive;
bool get_sensor_keep_alive();
void set_sensor_keep_alive(bool value);
bool rollgoal_gyro_enabled();
} // namespace dusk::gyro
#endif
+35 -5
View File
@@ -1,6 +1,7 @@
#ifndef DUSK_IO_HPP
#define DUSK_IO_HPP
#include <filesystem>
#include <vector>
// I can't believe it's 2026 and neither SDL (no error codes) nor
@@ -15,7 +16,7 @@ namespace dusk::io {
* Methods on this class throw appropriate C++ exceptions when an error occurs.
*/
class FileStream {
void* file;
FILE* file;
public:
FileStream() noexcept;
@@ -23,7 +24,7 @@ public:
/**
* \brief Take ownership of a FILE* handle.
*/
explicit FileStream(void* file);
explicit FileStream(FILE* file);
FileStream(const FileStream& other) = delete;
FileStream(FileStream&& other) noexcept;
@@ -34,6 +35,11 @@ public:
*/
static FileStream OpenRead(const char* utf8Path);
/**
* \brief Open a file for reading at the given path.
*/
static FileStream OpenRead(const std::filesystem::path& path);
/**
* \brief Create a file for writing.
*
@@ -41,16 +47,33 @@ public:
*/
static FileStream Create(const char* utf8Path);
/**
* \brief Create a file for writing.
*
* If there is an existing file, its contents are demolished.
*/
static FileStream Create(const std::filesystem::path& path);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static std::vector<u8> ReadAllBytes(const char* utf8Path);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static std::vector<u8> ReadAllBytes(const std::filesystem::path& path);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static void WriteAllText(const char* utf8Path, std::string_view text);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static void WriteAllText(const std::filesystem::path& path, std::string_view text);
/**
* \brief Read the remaining contents of the file directly into a vector.
*/
@@ -59,17 +82,24 @@ public:
/**
* Get direct access to the underlying FILE* handle.
*/
[[nodiscard]] void* GetFileHandle() const noexcept {
return file;
}
[[nodiscard]] void* GetFileHandle() const noexcept { return file; }
/**
* Write data to the file.
*/
void Write(const char* data, size_t dataLen);
FILE* ToInner();
};
/**
* Converts a std::filesystem::path to a std::string, UTF-8, without exploding on Windows.
*/
inline std::string fs_path_to_string(const std::filesystem::path& path) {
const auto u8str = path.u8string();
return {reinterpret_cast<const char*>(u8str.c_str())};
}
} // namespace dusk::io
#endif // DUSK_IO_HPP
+17 -6
View File
@@ -4,11 +4,22 @@
#include <filesystem>
namespace dusk {
extern bool IsRunning;
extern bool IsShuttingDown;
extern bool IsGameLaunched;
extern bool IsFocusPaused;
extern std::filesystem::path ConfigPath;
}
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) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV)
inline constexpr bool SupportsProcessRestart = false;
#else
inline constexpr bool SupportsProcessRestart = true;
#endif
void RequestRestart() noexcept;
} // namespace dusk
#endif // DUSK_MAIN_H
+2
View File
@@ -1,5 +1,7 @@
#pragma once
#include <array>
struct RoomEntry {
u8 roomNo;
std::vector<s16> roomPoints = {};
+1 -137
View File
@@ -2,9 +2,6 @@
#define _SRC_DUSK_MATH_H_
#include <cmath>
#include <array>
#include <limits>
#include <bit>
#ifndef M_PI
#define M_PI 3.14159265358979323846f
@@ -19,139 +16,6 @@ inline float i_cosf(float x) { return cos(x); }
inline float i_tanf(float x) { return tan(x); }
inline float i_acosf(float x) { return acos(x); }
// frsqrte matching courtesy of Geotale, with reference to https://achurch.org/cpu-tests/ppc750cl.s
struct BaseAndDec32 {
uint32_t base;
int32_t dec;
};
struct BaseAndDec64 {
uint64_t base;
int64_t dec;
};
union c32 {
constexpr c32(const float p) {
f = p;
}
constexpr c32(const uint32_t p) {
u = p;
}
uint32_t u;
float f;
};
union c64 {
constexpr c64(const double p) {
f = p;
}
constexpr c64(const uint64_t p) {
u = p;
}
uint64_t u;
double f;
};
static constexpr uint64_t EXPONENT_SHIFT_F64 = 52;
static constexpr uint64_t MANTISSA_MASK_F64 = 0x000fffffffffffffULL;
static constexpr uint64_t EXPONENT_MASK_F64 = 0x7ff0000000000000ULL;
static constexpr uint64_t SIGN_MASK_F64 = 0x8000000000000000ULL;
static constexpr std::array<BaseAndDec64, 32> RSQRTE_TABLE = {{
{0x69fa000000000ULL, -0x15a0000000LL},
{0x5f2e000000000ULL, -0x13cc000000LL},
{0x554a000000000ULL, -0x1234000000LL},
{0x4c30000000000ULL, -0x10d4000000LL},
{0x43c8000000000ULL, -0x0f9c000000LL},
{0x3bfc000000000ULL, -0x0e88000000LL},
{0x34b8000000000ULL, -0x0d94000000LL},
{0x2df0000000000ULL, -0x0cb8000000LL},
{0x2794000000000ULL, -0x0bf0000000LL},
{0x219c000000000ULL, -0x0b40000000LL},
{0x1bfc000000000ULL, -0x0aa0000000LL},
{0x16ae000000000ULL, -0x0a0c000000LL},
{0x11a8000000000ULL, -0x0984000000LL},
{0x0ce6000000000ULL, -0x090c000000LL},
{0x0862000000000ULL, -0x0898000000LL},
{0x0416000000000ULL, -0x082c000000LL},
{0xffe8000000000ULL, -0x1e90000000LL},
{0xf0a4000000000ULL, -0x1c00000000LL},
{0xe2a8000000000ULL, -0x19c0000000LL},
{0xd5c8000000000ULL, -0x17c8000000LL},
{0xc9e4000000000ULL, -0x1610000000LL},
{0xbedc000000000ULL, -0x1490000000LL},
{0xb498000000000ULL, -0x1330000000LL},
{0xab00000000000ULL, -0x11f8000000LL},
{0xa204000000000ULL, -0x10e8000000LL},
{0x9994000000000ULL, -0x0fe8000000LL},
{0x91a0000000000ULL, -0x0f08000000LL},
{0x8a1c000000000ULL, -0x0e38000000LL},
{0x8304000000000ULL, -0x0d78000000LL},
{0x7c48000000000ULL, -0x0cc8000000LL},
{0x75e4000000000ULL, -0x0c28000000LL},
{0x6fd0000000000ULL, -0x0b98000000LL},
}};
[[nodiscard]] static inline double frsqrte(const double val) {
c64 bits(val);
uint64_t mantissa = bits.u & MANTISSA_MASK_F64;
int64_t exponent = bits.u & EXPONENT_MASK_F64;
bool sign = (bits.u & SIGN_MASK_F64) != 0;
// Handle 0 case
if (mantissa == 0 && exponent == 0) {
return std::copysign(std::numeric_limits<double>::infinity(), bits.f);
}
// Handle NaN-like
if (exponent == EXPONENT_MASK_F64) {
if (mantissa == 0) {
return sign ? std::numeric_limits<double>::quiet_NaN() : 0.0;
}
return val;
}
// Handle negative inputs
if (sign) {
return std::numeric_limits<double>::quiet_NaN();
}
if (exponent == 0) {
// Shift so one bit goes to where the exponent would be,
// then clear that bit to mimic a not-subnormal number!
// Aka, if there are 12 leading zeroes, shift left once
uint32_t shift = std::countl_zero(mantissa) - static_cast<uint32_t>(63 - EXPONENT_SHIFT_F64);
mantissa <<= shift;
mantissa &= MANTISSA_MASK_F64;
// The shift is subtracted by 1 because denormals by default
// are offset by 1 (exponent 0 doesn't have implied 1 bit)
exponent -= static_cast<int64_t>(shift - 1) << EXPONENT_SHIFT_F64;
}
// In reality this doesn't get the full exponent -- Only the least significant bit
// Only that's needed because square roots of higher exponent bits simply multiply the
// result by 2!!
uint32_t key = static_cast<uint32_t>((static_cast<uint64_t>(exponent) | mantissa) >> 37);
uint64_t new_exp =
(static_cast<uint64_t>((0xbfcLL << EXPONENT_SHIFT_F64) - exponent) >> 1) & EXPONENT_MASK_F64;
// Remove the bits relating to anything higher than the LSB of the exponent
const auto &entry = RSQRTE_TABLE[0x1f & (key >> 11)];
// The result is given by an estimate then an adjustment based on the original
// key that was computed
uint64_t new_mantissa = static_cast<uint64_t>(entry.base + entry.dec * static_cast<int64_t>(key & 0x7ff));
return c64(new_exp | new_mantissa).f;
}
#include <dolphin/ppc_math.h>
#endif // _SRC_DUSK_MATH_H_
+63 -6
View File
@@ -1,6 +1,8 @@
#ifndef DUSK_CONFIG_H
#define DUSK_CONFIG_H
#include <array>
#include "dusk/config_var.hpp"
namespace dusk {
@@ -21,6 +23,17 @@ enum class GameLanguage : u8 {
Italian = OS_LANGUAGE_ITALIAN,
};
enum class DiscVerificationState : u8 {
Unknown = 0,
Success,
HashMismatch,
};
enum class GyroMode : u8 {
Sensor = 0,
Mouse = 1,
};
namespace config {
template <>
struct ConfigEnumRange<BloomMode> {
@@ -33,6 +46,18 @@ struct ConfigEnumRange<GameLanguage> {
static constexpr auto min = GameLanguage::English;
static constexpr auto max = GameLanguage::Italian;
};
template <>
struct ConfigEnumRange<DiscVerificationState> {
static constexpr auto min = DiscVerificationState::Unknown;
static constexpr auto max = DiscVerificationState::HashMismatch;
};
template <>
struct ConfigEnumRange<GyroMode> {
static constexpr auto min = GyroMode::Sensor;
static constexpr auto max = GyroMode::Mouse;
};
}
// Persistent user settings
@@ -45,6 +70,8 @@ struct UserSettings {
ConfigVar<bool> enableFullscreen;
ConfigVar<bool> enableVsync;
ConfigVar<bool> lockAspectRatio;
ConfigVar<bool> enableFpsOverlay;
ConfigVar<int> fpsOverlayCorner;
} video;
struct {
@@ -56,6 +83,7 @@ struct UserSettings {
ConfigVar<int> fanfareVolume;
ConfigVar<bool> enableReverb;
ConfigVar<bool> enableHrtf;
ConfigVar<bool> menuSounds;
} audio;
// Game settings
@@ -66,7 +94,6 @@ struct UserSettings {
// QoL
ConfigVar<bool> enableQuickTransform;
ConfigVar<bool> hideTvSettingsScreen;
ConfigVar<bool> skipWarningScreen;
ConfigVar<bool> biggerWallets;
ConfigVar<bool> noReturnRupees;
ConfigVar<bool> disableRupeeCutscenes;
@@ -85,13 +112,21 @@ struct UserSettings {
// Preferences
ConfigVar<bool> enableMirrorMode;
ConfigVar<bool> disableMainHUD;
ConfigVar<bool> minimalHUD;
ConfigVar<bool> pauseOnFocusLost;
ConfigVar<bool> enableLinkDollRotation;
ConfigVar<bool> enableAchievementNotifications;
ConfigVar<bool> enableAchievementToasts;
ConfigVar<bool> enableControllerToasts;
ConfigVar<bool> enableDiscordPresence;
// Graphics
ConfigVar<bool> enhancedLighting;
ConfigVar<bool> enableSpecularLighting;
ConfigVar<bool> enableRimLighting;
ConfigVar<float> specularIntensity;
ConfigVar<float> rimIntensity;
ConfigVar<float> ambientLightMultiplier;
ConfigVar<float> diffuseLightMultiplier;
ConfigVar<BloomMode> bloomMode;
ConfigVar<float> bloomMultiplier;
ConfigVar<bool> disableWaterRefraction;
@@ -100,12 +135,14 @@ struct UserSettings {
ConfigVar<int> shadowResolutionMultiplier;
ConfigVar<bool> enableDepthOfField;
ConfigVar<bool> enableMapBackground;
ConfigVar<bool> disableCutscenePillarboxing;
// Audio
ConfigVar<bool> noLowHpSound;
ConfigVar<bool> midnasLamentNonStop;
// Input
ConfigVar<GyroMode> gyroMode;
ConfigVar<bool> enableGyroAim;
ConfigVar<bool> enableGyroRollgoal;
ConfigVar<float> gyroSensitivityX;
@@ -118,7 +155,12 @@ struct UserSettings {
ConfigVar<bool> freeCamera;
ConfigVar<bool> invertCameraXAxis;
ConfigVar<bool> invertCameraYAxis;
ConfigVar<bool> invertFirstPersonXAxis;
ConfigVar<bool> invertFirstPersonYAxis;
ConfigVar<float> freeCameraSensitivity;
ConfigVar<bool> debugFlyCam;
ConfigVar<bool> debugFlyCamLockEvents;
ConfigVar<bool> allowBackgroundInput;
// Cheats
ConfigVar<bool> infiniteHearts;
@@ -133,6 +175,7 @@ struct UserSettings {
ConfigVar<bool> alwaysGreatspin;
ConfigVar<bool> enableFastIronBoots;
ConfigVar<bool> canTransformAnywhere;
ConfigVar<bool> fastRoll;
ConfigVar<bool> fastSpinner;
ConfigVar<bool> freeMagicArmor;
@@ -141,22 +184,36 @@ struct UserSettings {
// Controls
ConfigVar<bool> enableTurboKeybind;
ConfigVar<bool> enableResetKeybind;
// Tools
ConfigVar<bool> speedrunMode;
ConfigVar<bool> liveSplitEnabled;
ConfigVar<bool> showSpeedrunRTATimer;
ConfigVar<bool> recordingMode;
ConfigVar<bool> showInputViewer;
ConfigVar<bool> showInputViewerGyro;
} game;
struct {
ConfigVar<std::string> isoPath;
ConfigVar<DiscVerificationState> isoVerification;
ConfigVar<std::string> graphicsBackend;
ConfigVar<bool> skipPreLaunchUI;
ConfigVar<bool> showPipelineCompilation;
ConfigVar<bool> wasPresetChosen;
ConfigVar<bool> enableCrashReporting;
ConfigVar<bool> duskMenuOpen;
ConfigVar<bool> checkForUpdates;
ConfigVar<int> cardFileType;
ConfigVar<bool> enableAdvancedSettings;
} backend;
// Arrays of size 4 for 4 ports
struct {
std::array<ActionBindConfigVar, 4> firstPersonCamera;
std::array<ActionBindConfigVar, 4> callMidna;
std::array<ActionBindConfigVar, 4> openDusklightMenu;
std::array<ActionBindConfigVar, 4> turboSpeedButton;
} actionBindings;
};
UserSettings& getSettings();
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <aurora/aurora.h>
namespace dusk {
struct SpeedrunInfo {
void startRun() {
m_isRunStarted = true;
m_startTimestamp = OSGetTime();
}
void stopRun() {
m_isRunStarted = false;
m_endTimestamp = OSGetTime() - m_startTimestamp;
}
void reset() {
m_isRunStarted = false;
m_startTimestamp = 0;
m_endTimestamp = 0;
m_isPauseIGT = false;
m_loadStartTimestamp = 0;
m_totalLoadTime = 0;
m_igtTimer = 0;
}
bool m_isRunStarted = false;
OSTime m_startTimestamp = 0;
OSTime m_endTimestamp = 0;
bool m_isPauseIGT = false;
OSTime m_loadStartTimestamp = 0;
OSTime m_totalLoadTime = 0;
OSTime m_igtTimer = 0;
};
extern SpeedrunInfo m_speedrunInfo;
void resetForSpeedrunMode();
} // namespace dusk
+13
View File
@@ -5,6 +5,7 @@
#include "Z2AudioLib/Z2EnvSeMgr.h"
#include "Z2AudioLib/Z2LinkMgr.h"
#include "dusk/audio.h"
#include "dusk/settings.h"
class mDoAud_zelAudio_c : public Z2AudioMgr {
public:
@@ -132,6 +133,18 @@ inline void mDoAud_seStart(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_re
-1.0f, -1.0f, 0);
}
#if TARGET_PC
inline void mDoAud_seStartMenu(u32 i_sfxID) {
if (!mDoAud_zelAudio_c::isInitFlag()) {
return;
}
if (!dusk::getSettings().audio.menuSounds.getValue()) {
return;
}
mDoAud_seStart(i_sfxID, nullptr, 0, 0);
}
#endif
inline void mDoAud_seStartLevel(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_reverb) {
DUSK_AUDIO_SKIP()
Z2AudioMgr::getInterface()->seStartLevel(i_sfxID, i_sePos, param_2, i_reverb, 1.0f, 1.0f,
@@ -1,9 +1,9 @@
#ifndef J3DSTRUCT_H
#define J3DSTRUCT_H
#include <cstring>
#include <gx.h>
#include <mtx.h>
#include <mtx.h>
#include "global.h"
#include "JSystem/JMath/JMath.h"
@@ -11,7 +11,7 @@
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DLightInfo {
bool operator==(J3DLightInfo& other) const;
@@ -28,7 +28,7 @@ struct J3DLightInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DTextureSRTInfo {
// NOTE: Big endian when loaded from file!
@@ -79,7 +79,7 @@ enum J3DTexMtxMode {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DTexMtxInfo {
bool operator==(J3DTexMtxInfo& other) const;
@@ -97,7 +97,7 @@ struct J3DTexMtxInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DIndTexMtxInfo {
J3DIndTexMtxInfo& operator=(J3DIndTexMtxInfo const&);
@@ -107,7 +107,7 @@ struct J3DIndTexMtxInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DFogInfo {
bool operator==(J3DFogInfo&) const;
@@ -126,7 +126,7 @@ struct J3DFogInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DNBTScaleInfo {
bool operator==(const J3DNBTScaleInfo& other) const;
@@ -153,7 +153,7 @@ struct J3DIndTexOrderInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DTevSwapModeInfo {
/* 0x0 */ u8 mRasSel;
@@ -164,7 +164,7 @@ struct J3DTevSwapModeInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DTevSwapModeTableInfo {
/* 0x0 */ u8 field_0x0;
@@ -175,7 +175,7 @@ struct J3DTevSwapModeTableInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DTevStageInfo {
/* 0x0 */ u8 field_0x0;
@@ -202,7 +202,7 @@ struct J3DTevStageInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DIndTevStageInfo {
/* 0x0 */ u8 mIndStage;
@@ -219,7 +219,7 @@ struct J3DIndTevStageInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DTexCoordInfo {
/* 0x0 */ u8 mTexGenType;
@@ -265,7 +265,7 @@ struct J3DBlendInfo {
/**
* @ingroup jsystem-j3d
*
*
*/
struct J3DTevOrderInfo {
void operator=(const J3DTevOrderInfo& other) {
@@ -556,8 +556,8 @@ void J3DModelLoader::readVertexData(const J3DVertexBlock& block, J3DVertexData&
if (attr == GX_VA_POS) {
// can be a little off due to 0x20 alignment, account for that
u32 expect = ((data.mVtxNum * vertStride) + 0x1F) & ~0x1F;
JUT_ASSERT(1234, expect == addrDiff);
// u32 expect = ((data.mVtxNum * vertStride) + 0x1F) & ~0x1F;
// JUT_ASSERT(1234, expect == addrDiff);
} else if (attr == GX_VA_NRM) {
data.mNrmNum = num;
} else if (attr == GX_VA_CLR0) {
+17 -3
View File
@@ -8,6 +8,10 @@
#include "JSystem/JUtility/JUTFader.h"
#include "JSystem/J2DGraph/J2DOrthoGraph.h"
#ifdef TARGET_PC
#include <algorithm>
#endif
JUTFader::JUTFader(int x, int y, int width, int height, JUtility::TColor pColor)
: mColor(pColor), mBox(x, y, x + width, y + height) {
mStatus = None;
@@ -63,14 +67,24 @@ void JUTFader::advance() {
void JUTFader::control() {
advance();
#ifndef TARGET_PC
// FRAME INTERP NOTE: Draw is called by JFWDisplay when interpolation is active
draw();
#endif
}
void JUTFader::draw() {
if (mColor.a != 0) {
#ifdef TARGET_PC
if (dusk::frame_interp::is_enabled() && mDuration != 0) {
const auto step = dusk::frame_interp::get_interpolation_step();
const auto progress = static_cast<f32>(mTimer) / static_cast<f32>(mDuration);
const auto timer = mTimer - 1 + step + progress;
auto alpha = timer / mDuration;
if (mStatus == FadeIn) {
alpha = 1.0f - alpha;
}
alpha = std::clamp(alpha, 0.0f, 1.0f);
mColor.a = static_cast<u8>(alpha * 255.0f);
}
#endif
J2DOrthoGraph orthograph;
orthograph.setColor(mColor);
orthograph.fillBox(mBox);
+7
View File
@@ -4,6 +4,10 @@
#include <cmath>
#include "os_report.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#endif
u32 JUTGamePad::CRumble::sChannelMask[4] = {
PAD_CHAN0_BIT,
PAD_CHAN1_BIT,
@@ -85,6 +89,9 @@ u32 JUTGamePad::sRumbleSupported;
u32 JUTGamePad::read() {
sRumbleSupported = PADRead(mPadStatus);
#if TARGET_PC
dusk::updateActionBindings();
#endif
switch (sClampMode) {
case EClampStick:
+3 -4
View File
@@ -1,6 +1,6 @@
# Android Shell
This directory contains a minimal SDLActivity-based Android app wrapper for Dusk.
This directory contains a minimal SDLActivity-based Android app wrapper for Dusklight.
## Prerequisites
@@ -66,12 +66,11 @@ Output APK:
You can pass command-line args through the activity intent:
```bash
adb shell am start -n com.twilitrealm.dusk/.DuskActivity \
--es dusk_args "'/sdcard/Download/The Legend of Zelda: Twilight Princess (USA).iso'"
adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \
--es dusk_args "--backend vulkan"
```
Supported extras:
- `dusk_args`: single shell-like argument string
- `dusk_argv`: string-array argv
- `dusk_disc`: compatibility shortcut (single ISO path)
+20 -3
View File
@@ -2,12 +2,22 @@ plugins {
id 'com.android.application'
}
def duskRepoDir = rootProject.projectDir.parentFile.parentFile
def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusklight').get().asFile
def syncDuskAssets = tasks.register('syncDuskAssets', Sync) {
from(new File(duskRepoDir, 'res')) {
into 'res'
exclude '**/.DS_Store'
}
into duskGeneratedAssetsDir
}
android {
namespace 'com.twilitrealm.dusk'
namespace 'dev.twilitrealm.dusk'
compileSdk 36
defaultConfig {
applicationId 'com.twilitrealm.dusk'
applicationId 'dev.twilitrealm.dusk'
minSdk 26
targetSdk 36
versionCode 1
@@ -27,7 +37,7 @@ android {
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
assets.srcDirs = ['../../assets']
assets.srcDirs = [duskGeneratedAssetsDir]
}
}
@@ -48,3 +58,10 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
tasks.configureEach { task ->
if ((task.name.startsWith('merge') && task.name.endsWith('Assets')) ||
task.name.toLowerCase().contains('lint')) {
task.dependsOn(syncDuskAssets)
}
}
+2
View File
@@ -1,2 +1,4 @@
# Keep SDL activity and related JNI bridge methods.
-keep class org.libsdl.app.** { *; }
-keep class dev.twilitrealm.dusk.DuskHttpClient { *; }
-keep class dev.twilitrealm.dusk.DuskHttpClient$Response { *; }
@@ -10,6 +10,7 @@
<uses-feature android:name="android.hardware.type.pc" android:required="false" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
@@ -23,8 +24,19 @@
<meta-data android:name="android.game_mode_config"
android:resource="@xml/game_mode_config" />
<provider
android:name="dev.twilitrealm.dusk.DuskDocumentsProvider"
android:authorities="dev.twilitrealm.dusk.documents"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<activity
android:name="com.twilitrealm.dusk.DuskActivity"
android:name="dev.twilitrealm.dusk.DuskActivity"
android:alwaysRetainTaskState="true"
android:configChanges="layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:exported="true"
@@ -1,18 +1,39 @@
package com.twilitrealm.dusk;
package dev.twilitrealm.dusk;
import android.app.ActionBar;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import org.libsdl.app.SDLActivity;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class DuskActivity extends SDLActivity {
private static final String TAG = "DuskActivity";
private static final int FOLDER_DIALOG_REQUEST_CODE = 0x4455;
private static final String EXTERNAL_STORAGE_AUTHORITY =
"com.android.externalstorage.documents";
private long folderDialogUserdata = 0;
private static native void nativeFolderDialogResult(long userdata, String path, String error);
private static String[] splitArgs(String raw) {
List<String> out = new ArrayList<>();
StringBuilder current = new StringBuilder();
@@ -61,18 +82,46 @@ public class DuskActivity extends SDLActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hideSystemBars();
}
@Override
protected void onResume() {
super.onResume();
hideSystemBars();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
hideSystemBars();
}
}
private void hideSystemBars() {
Window window = getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
getWindow().getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
}else {
View decorView = getWindow().getDecorView();
// Hide the status bar.
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
window.setDecorFitsSystemWindows(false);
WindowInsetsController ctrl = window.getDecorView().getWindowInsetsController();
if (ctrl != null) {
ctrl.setSystemBarsBehavior(
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
ctrl.hide(WindowInsets.Type.systemBars());
}
} else {
View decorView = window.getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(uiOptions);
// Remember that you should never show the action bar if the
// status bar is hidden, so hide that too if necessary.
ActionBar actionBar = getActionBar();
actionBar.hide();
if (actionBar != null) {
actionBar.hide();
}
}
}
@@ -100,12 +149,241 @@ public class DuskActivity extends SDLActivity {
return splitArgs(trimmed);
}
}
String discPath = intent.getStringExtra("dusk_disc");
if (discPath != null && !discPath.isEmpty()) {
return new String[] { discPath };
}
}
return new String[0];
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
persistUriPermissions(data);
}
if (requestCode == FOLDER_DIALOG_REQUEST_CODE) {
finishFolderDialog(resultCode, data);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
public boolean showFolderDialog(long userdata) {
if (userdata == 0 || folderDialogUserdata != 0) {
return false;
}
folderDialogUserdata = userdata;
runOnUiThread(() -> {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
try {
startActivityForResult(intent, FOLDER_DIALOG_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Unable to open folder dialog.", e);
finishFolderDialog(Activity.RESULT_CANCELED, null);
}
});
return true;
}
private void finishFolderDialog(int resultCode, Intent data) {
long userdata = folderDialogUserdata;
folderDialogUserdata = 0;
if (userdata == 0) {
return;
}
if (resultCode == RESULT_OK && data != null && data.getData() != null) {
String path = getRealPathForUri(data.getData());
if (path != null && !path.isEmpty()) {
nativeFolderDialogResult(userdata, path, null);
} else {
nativeFolderDialogResult(
userdata, null, "Selected folder is not available as a filesystem path");
}
return;
}
nativeFolderDialogResult(userdata, null, null);
}
private String getRealPathForUri(Uri uri) {
if (uri == null) {
return null;
}
String scheme = uri.getScheme();
if ("file".equals(scheme)) {
return uri.getPath();
}
if (!"content".equals(scheme) ||
!EXTERNAL_STORAGE_AUTHORITY.equals(uri.getAuthority()) ||
Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
{
return null;
}
try {
return getExternalStoragePathForDocumentId(getExternalStorageDocumentId(uri));
} catch (IllegalArgumentException e) {
Log.w(TAG, "Unable to resolve URI: " + uri, e);
return null;
}
}
private static String getExternalStorageDocumentId(Uri uri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTreeDocumentUri(uri)) {
return DocumentsContract.getTreeDocumentId(uri);
}
return DocumentsContract.getDocumentId(uri);
}
private static boolean isTreeDocumentUri(Uri uri) {
List<String> segments = uri.getPathSegments();
return segments.size() >= 2 && "tree".equals(segments.get(0));
}
private String getExternalStoragePathForDocumentId(String documentId) {
if (documentId == null || documentId.isEmpty()) {
return null;
}
if (documentId.startsWith("raw:")) {
return documentId.substring("raw:".length());
}
String[] parts = documentId.split(":", 2);
String volumeId = parts[0];
String relativePath = parts.length > 1 ? parts[1] : "";
File root = getExternalStorageRoot(volumeId);
if (root == null) {
return null;
}
return relativePath.isEmpty()
? root.getAbsolutePath()
: new File(root, relativePath).getAbsolutePath();
}
private File getExternalStorageRoot(String volumeId) {
if ("primary".equalsIgnoreCase(volumeId)) {
return Environment.getExternalStorageDirectory();
}
if ("home".equalsIgnoreCase(volumeId)) {
return new File(
Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOCUMENTS);
}
File[] externalFilesDirs = getExternalFilesDirs(null);
if (externalFilesDirs != null) {
for (File externalFilesDir : externalFilesDirs) {
File root = getStorageRootForExternalFilesDir(externalFilesDir);
if (root != null && volumeId.equalsIgnoreCase(root.getName())) {
return root;
}
}
}
File fallback = new File("/storage", volumeId);
return fallback.exists() ? fallback : null;
}
private File getStorageRootForExternalFilesDir(File externalFilesDir) {
if (externalFilesDir == null) {
return null;
}
String path = externalFilesDir.getAbsolutePath();
int androidDir = path.indexOf("/Android/");
if (androidDir <= 0) {
return null;
}
return new File(path.substring(0, androidDir));
}
private void persistUriPermissions(Intent data) {
if (data == null) {
return;
}
int permissionFlags =
data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (permissionFlags == 0) {
return;
}
Uri uri = data.getData();
if (uri != null) {
persistUriPermission(uri, permissionFlags);
}
ClipData clipData = data.getClipData();
if (clipData == null) {
return;
}
for (int i = 0; i < clipData.getItemCount(); ++i) {
Uri itemUri = clipData.getItemAt(i).getUri();
if (itemUri != null) {
persistUriPermission(itemUri, permissionFlags);
}
}
}
private void persistUriPermission(Uri uri, int permissionFlags) {
if ((permissionFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
persistUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, "read");
}
if ((permissionFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
persistUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, "write");
}
}
private void persistUriPermission(Uri uri, int permissionFlag, String permissionName) {
try {
getContentResolver().takePersistableUriPermission(uri, permissionFlag);
} catch (SecurityException | IllegalArgumentException e) {
Log.w(TAG, "Unable to persist " + permissionName + " URI permission for " + uri, e);
}
}
public String getDisplayNameForUri(String uriString) {
if (uriString == null || uriString.isEmpty()) {
return "";
}
Uri uri = Uri.parse(uriString);
if ("content".equals(uri.getScheme())) {
try (Cursor cursor = getContentResolver().query(
uri, new String[] { OpenableColumns.DISPLAY_NAME }, null, null, null))
{
if (cursor != null && cursor.moveToFirst()) {
int displayNameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (displayNameColumn >= 0) {
String displayName = cursor.getString(displayNameColumn);
if (displayName != null && !displayName.isEmpty()) {
return displayName;
}
}
}
} catch (SecurityException | IllegalArgumentException e) {
Log.w(TAG, "Unable to query display name for " + uri, e);
}
} else if ("file".equals(uri.getScheme())) {
String path = uri.getPath();
if (path != null && !path.isEmpty()) {
String name = new File(path).getName();
if (!name.isEmpty()) {
return name;
}
}
}
String lastSegment = uri.getLastPathSegment();
return lastSegment != null ? lastSegment : "";
}
}
@@ -0,0 +1,467 @@
package dev.twilitrealm.dusk;
import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class DuskDocumentsProvider extends DocumentsProvider {
public static final String AUTHORITY = "dev.twilitrealm.dusk.documents";
private static final String ROOT_ID = "dusk";
private static final String ROOT_DOCUMENT_ID = "root";
private static final String LOCATION_DESCRIPTOR_NAME = "data_location.json";
private static final String DIRECTORY_MIME_TYPE = Document.MIME_TYPE_DIR;
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_FLAGS,
Root.COLUMN_TITLE,
Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_ICON,
Root.COLUMN_AVAILABLE_BYTES,
Root.COLUMN_SUMMARY
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_FLAGS,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_SIZE
};
@Override
public boolean onCreate() {
if (!isCustomDataPathEnabled()) {
ensureUserDirectories();
}
return true;
}
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
if (isCustomDataPathEnabled()) {
return result;
}
final File root = getRootDirectory();
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, ROOT_ID);
row.add(Root.COLUMN_FLAGS,
Root.FLAG_LOCAL_ONLY |
Root.FLAG_SUPPORTS_CREATE |
Root.FLAG_SUPPORTS_IS_CHILD);
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
row.add(Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID);
row.add(Root.COLUMN_ICON, R.mipmap.icon);
row.add(Root.COLUMN_AVAILABLE_BYTES, root.getFreeSpace());
row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.documents_provider_summary));
return result;
}
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
includeDocument(result, documentId, getFileForDocumentId(documentId));
return result;
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
throws FileNotFoundException
{
return queryChildDocumentsInternal(parentDocumentId, projection);
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, Bundle queryArgs)
throws FileNotFoundException
{
return queryChildDocumentsInternal(parentDocumentId, projection);
}
private Cursor queryChildDocumentsInternal(String parentDocumentId, String[] projection)
throws FileNotFoundException
{
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
final File parent = getFileForDocumentId(parentDocumentId);
final File[] files = parent.listFiles();
result.setNotificationUri(getContext().getContentResolver(), getChildDocumentsUri(parentDocumentId));
if (files == null) {
return result;
}
for (File file : files) {
includeDocument(result, getDocumentIdForFile(file), file);
}
return result;
}
@Override
public boolean isChildDocument(String parentDocumentId, String documentId) {
try {
final File parent = getFileForDocumentId(parentDocumentId);
final File child = getFileForDocumentId(documentId);
return isInside(parent, child);
} catch (FileNotFoundException e) {
return false;
}
}
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName)
throws FileNotFoundException
{
final File parent = getFileForDocumentId(parentDocumentId);
if (!parent.isDirectory()) {
throw new FileNotFoundException("Parent is not a directory: " + parentDocumentId);
}
final String safeDisplayName = sanitizeDisplayName(displayName);
final File file = buildUniqueFile(parent, safeDisplayName);
final boolean created;
if (DIRECTORY_MIME_TYPE.equals(mimeType)) {
created = file.mkdir();
} else {
try {
created = file.createNewFile();
} catch (IOException e) {
throw asFileNotFound("Unable to create document", e);
}
}
if (!created) {
throw new FileNotFoundException("Unable to create document: " + displayName);
}
notifyChildrenChanged(parentDocumentId);
return getDocumentIdForFile(file);
}
@Override
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
final File file = getFileForDocumentId(documentId);
if (ROOT_DOCUMENT_ID.equals(documentId)) {
throw new FileNotFoundException("Cannot rename root document");
}
final File target = buildUniqueFile(file.getParentFile(), sanitizeDisplayName(displayName));
final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
if (!file.renameTo(target)) {
throw new FileNotFoundException("Unable to rename document: " + documentId);
}
notifyDocumentChanged(documentId);
notifyDocumentChanged(getDocumentIdForFile(target));
notifyChildrenChanged(parentDocumentId);
return getDocumentIdForFile(target);
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
if (ROOT_DOCUMENT_ID.equals(documentId)) {
throw new FileNotFoundException("Cannot delete root document");
}
final File file = getFileForDocumentId(documentId);
final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
deleteRecursively(file);
notifyDocumentChanged(documentId);
notifyChildrenChanged(parentDocumentId);
}
@Override
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal)
throws FileNotFoundException
{
return ParcelFileDescriptor.open(getFileForDocumentId(documentId), modeToParcelMode(mode));
}
@Override
public AssetFileDescriptor openDocumentThumbnail(String documentId, android.graphics.Point sizeHint,
CancellationSignal signal) throws FileNotFoundException
{
throw new FileNotFoundException("Thumbnails are not supported");
}
private void includeDocument(MatrixCursor result, String documentId, File file) throws FileNotFoundException {
final MatrixCursor.RowBuilder row = result.newRow();
final boolean isDirectory = file.isDirectory();
final String displayName = ROOT_DOCUMENT_ID.equals(documentId)
? getContext().getString(R.string.documents_provider_root_name)
: file.getName();
int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
if (isDirectory) {
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
} else if (file.canWrite()) {
flags |= Document.FLAG_SUPPORTS_WRITE;
}
if (ROOT_DOCUMENT_ID.equals(documentId)) {
flags &= ~(Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME);
}
row.add(Document.COLUMN_DOCUMENT_ID, documentId);
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_MIME_TYPE, isDirectory ? DIRECTORY_MIME_TYPE : getMimeType(file));
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_SIZE, isDirectory ? null : file.length());
}
private File getRootDirectory() throws FileNotFoundException {
if (isCustomDataPathEnabled()) {
throw new FileNotFoundException(
"Dusk DocumentsProvider is disabled while a custom data path is configured");
}
final File root = getContext().getFilesDir();
if (root == null) {
throw new FileNotFoundException("Dusklight files directory is unavailable");
}
return root;
}
private File getFileForDocumentId(String documentId) throws FileNotFoundException {
final File root = getRootDirectory();
if (ROOT_DOCUMENT_ID.equals(documentId)) {
return root;
}
if (!documentId.startsWith(ROOT_DOCUMENT_ID + "/")) {
throw new FileNotFoundException("Invalid document id: " + documentId);
}
final String relativePath = documentId.substring(ROOT_DOCUMENT_ID.length() + 1);
final File file = new File(root, relativePath);
if (!isInside(root, file)) {
throw new FileNotFoundException("Document escapes Dusklight files directory: " + documentId);
}
if (!file.exists()) {
throw new FileNotFoundException("Document does not exist: " + documentId);
}
return file;
}
private String getDocumentIdForFile(File file) throws FileNotFoundException {
final File root = getRootDirectory();
if (sameFile(root, file)) {
return ROOT_DOCUMENT_ID;
}
if (!isInside(root, file)) {
throw new FileNotFoundException("File escapes Dusklight files directory: " + file);
}
final String rootPath = canonicalPath(root);
final String filePath = canonicalPath(file);
return ROOT_DOCUMENT_ID + "/" + filePath.substring(rootPath.length() + 1);
}
private void ensureUserDirectories() {
final File root = getContext().getFilesDir();
if (root == null) {
return;
}
new File(root, "texture_replacements").mkdirs();
new File(root, "USA/Card A").mkdirs();
new File(root, "EUR/Card A").mkdirs();
}
private boolean isCustomDataPathEnabled() {
if (getContext() == null) {
return false;
}
final File filesDir = getContext().getFilesDir();
if (filesDir == null) {
return false;
}
final File descriptor = new File(filesDir, LOCATION_DESCRIPTOR_NAME);
if (!descriptor.isFile()) {
return false;
}
try {
final JSONObject json = new JSONObject(readText(descriptor));
return "custom".equals(json.optString("mode", "default"));
} catch (IOException | JSONException e) {
return false;
}
}
private static String readText(File file) throws IOException {
try (FileInputStream input = new FileInputStream(file);
ByteArrayOutputStream output = new ByteArrayOutputStream())
{
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
return output.toString(StandardCharsets.UTF_8.name());
}
}
private static String[] resolveRootProjection(String[] projection) {
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
}
private static String[] resolveDocumentProjection(String[] projection) {
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
}
private static String sanitizeDisplayName(String displayName) throws FileNotFoundException {
if (displayName == null) {
throw new FileNotFoundException("Document name is empty");
}
final String sanitized = displayName.trim();
if (sanitized.isEmpty() || ".".equals(sanitized) || "..".equals(sanitized) ||
sanitized.contains("/") || sanitized.contains("\\"))
{
throw new FileNotFoundException("Invalid document name: " + displayName);
}
return sanitized;
}
private static File buildUniqueFile(File parent, String displayName) {
File file = new File(parent, displayName);
if (!file.exists()) {
return file;
}
final int dot = displayName.lastIndexOf('.');
final String baseName = dot > 0 ? displayName.substring(0, dot) : displayName;
final String extension = dot > 0 ? displayName.substring(dot) : "";
for (int i = 1; i < 100; ++i) {
file = new File(parent, baseName + " (" + i + ")" + extension);
if (!file.exists()) {
return file;
}
}
return new File(parent, baseName + " (" + System.currentTimeMillis() + ")" + extension);
}
private static int modeToParcelMode(String mode) {
if ("r".equals(mode)) {
return ParcelFileDescriptor.MODE_READ_ONLY;
}
if ("w".equals(mode) || "wt".equals(mode)) {
return ParcelFileDescriptor.MODE_WRITE_ONLY |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE;
}
if ("wa".equals(mode)) {
return ParcelFileDescriptor.MODE_WRITE_ONLY |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_APPEND;
}
if ("rw".equals(mode)) {
return ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE;
}
if ("rwt".equals(mode)) {
return ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE;
}
return ParcelFileDescriptor.MODE_READ_ONLY;
}
private static String getMimeType(File file) {
final int dot = file.getName().lastIndexOf('.');
if (dot >= 0) {
final String extension = file.getName().substring(dot + 1).toLowerCase();
final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mimeType != null) {
return mimeType;
}
}
return "application/octet-stream";
}
private Uri getChildDocumentsUri(String parentDocumentId) {
return DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId);
}
private void notifyChildrenChanged(String parentDocumentId) {
final ContentResolver resolver = getContext().getContentResolver();
resolver.notifyChange(getChildDocumentsUri(parentDocumentId), null, false);
}
private void notifyDocumentChanged(String documentId) {
final ContentResolver resolver = getContext().getContentResolver();
resolver.notifyChange(DocumentsContract.buildDocumentUri(AUTHORITY, documentId), null, false);
}
private static void deleteRecursively(File file) throws FileNotFoundException {
if (file.isDirectory()) {
final File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursively(child);
}
}
}
if (!file.delete()) {
throw new FileNotFoundException("Unable to delete document: " + file);
}
}
private static boolean isInside(File parent, File child) {
try {
final String parentPath = canonicalPath(parent);
final String childPath = canonicalPath(child);
return childPath.equals(parentPath) || childPath.startsWith(parentPath + File.separator);
} catch (FileNotFoundException e) {
return false;
}
}
private static boolean sameFile(File a, File b) {
try {
return canonicalPath(a).equals(canonicalPath(b));
} catch (FileNotFoundException e) {
return false;
}
}
private static String canonicalPath(File file) throws FileNotFoundException {
try {
return file.getCanonicalPath();
} catch (IOException e) {
throw asFileNotFound("Unable to resolve path", e);
}
}
private static FileNotFoundException asFileNotFound(String message, IOException cause) {
final FileNotFoundException exception = new FileNotFoundException(message + ": " + cause.getMessage());
exception.initCause(cause);
return exception;
}
}
@@ -0,0 +1,237 @@
package dev.twilitrealm.dusk;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
public final class DuskHttpClient {
public static final int ERROR_NONE = 0;
public static final int ERROR_INVALID_URL = 1;
public static final int ERROR_UNSUPPORTED_SCHEME = 2;
public static final int ERROR_TIMEOUT = 3;
public static final int ERROR_TOO_LARGE = 4;
public static final int ERROR_NETWORK = 5;
private static final int MAX_REDIRECTS = 5;
public static final class Response {
public int error;
public String message;
public int statusCode;
public String[] headerNames;
public String[] headerValues;
public byte[] body;
Response(int error, String message, int statusCode, String[] headerNames,
String[] headerValues, byte[] body) {
this.error = error;
this.message = message;
this.statusCode = statusCode;
this.headerNames = headerNames != null ? headerNames : new String[0];
this.headerValues = headerValues != null ? headerValues : new String[0];
this.body = body != null ? body : new byte[0];
}
}
private DuskHttpClient() {
}
public static Response get(String url, String[] headerNames, String[] headerValues,
int timeoutMs, long maxBodyBytes) {
if (url == null || url.isEmpty()) {
return fail(ERROR_INVALID_URL, "URL is empty");
}
try {
URL currentUrl = new URL(url);
if (!isHttps(currentUrl)) {
return fail(ERROR_UNSUPPORTED_SCHEME, "Only https:// URLs are supported");
}
for (int redirect = 0; redirect <= MAX_REDIRECTS; ++redirect) {
HttpsURLConnection connection =
(HttpsURLConnection) currentUrl.openConnection();
try {
connection.setRequestMethod("GET");
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setInstanceFollowRedirects(false);
applyHeaders(connection, headerNames, headerValues);
int statusCode = connection.getResponseCode();
if (isRedirect(statusCode)) {
String location = connection.getHeaderField("Location");
if (location == null || location.isEmpty()) {
return fail(ERROR_NETWORK, "Redirect response did not include Location",
statusCode, connection, new byte[0]);
}
URL nextUrl = new URL(currentUrl, location);
if (!isHttps(nextUrl)) {
return fail(ERROR_UNSUPPORTED_SCHEME,
"Only https:// redirects are supported", statusCode,
connection, new byte[0]);
}
currentUrl = nextUrl;
continue;
}
byte[] body = readBody(connection, statusCode, maxBodyBytes);
return success(statusCode, connection, body);
} catch (ResponseTooLargeException e) {
return fail(ERROR_TOO_LARGE, "Response body exceeded the configured limit",
safeStatusCode(connection), connection, e.partialBody);
} finally {
connection.disconnect();
}
}
return fail(ERROR_NETWORK, "Too many redirects");
} catch (MalformedURLException e) {
return fail(ERROR_INVALID_URL, "Failed to parse URL");
} catch (SocketTimeoutException e) {
return fail(ERROR_TIMEOUT, "Request timed out");
} catch (IOException e) {
String message = e.getMessage();
return fail(ERROR_NETWORK, message != null ? message : e.toString());
} catch (ClassCastException e) {
return fail(ERROR_UNSUPPORTED_SCHEME, "Only https:// URLs are supported");
}
}
private static void applyHeaders(HttpsURLConnection connection, String[] names,
String[] values) {
if (names == null || values == null) {
return;
}
int count = Math.min(names.length, values.length);
for (int i = 0; i < count; ++i) {
if (names[i] != null && values[i] != null) {
connection.setRequestProperty(names[i], values[i]);
}
}
}
private static boolean isHttps(URL url) {
return "https".equalsIgnoreCase(url.getProtocol());
}
private static boolean isRedirect(int statusCode) {
return statusCode == HttpURLConnection.HTTP_MOVED_PERM ||
statusCode == HttpURLConnection.HTTP_MOVED_TEMP ||
statusCode == HttpURLConnection.HTTP_SEE_OTHER ||
statusCode == 307 ||
statusCode == 308;
}
private static byte[] readBody(HttpsURLConnection connection, int statusCode,
long maxBodyBytes) throws IOException,
ResponseTooLargeException {
InputStream stream = statusCode >= HttpURLConnection.HTTP_BAD_REQUEST ?
connection.getErrorStream() : connection.getInputStream();
if (stream == null) {
return new byte[0];
}
try (InputStream bodyStream = stream;
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[8192];
long total = 0;
while (true) {
int read = bodyStream.read(buffer);
if (read < 0) {
return out.toByteArray();
}
if (read == 0) {
continue;
}
if (read > maxBodyBytes || total > maxBodyBytes - read) {
throw new ResponseTooLargeException(out.toByteArray());
}
out.write(buffer, 0, read);
total += read;
}
}
}
private static int safeStatusCode(HttpsURLConnection connection) {
try {
return connection.getResponseCode();
} catch (IOException e) {
return 0;
}
}
private static Response success(int statusCode, HttpsURLConnection connection, byte[] body) {
HeaderLists headers = readHeaders(connection);
return new Response(ERROR_NONE, "", statusCode, headers.names, headers.values, body);
}
private static Response fail(int error, String message) {
return new Response(error, message, 0, null, null, null);
}
private static Response fail(int error, String message, int statusCode,
HttpsURLConnection connection, byte[] body) {
HeaderLists headers = readHeaders(connection);
return new Response(error, message, statusCode, headers.names, headers.values, body);
}
private static HeaderLists readHeaders(HttpsURLConnection connection) {
List<String> names = new ArrayList<>();
List<String> values = new ArrayList<>();
Map<String, List<String>> headerFields = connection.getHeaderFields();
if (headerFields == null) {
return new HeaderLists(new String[0], new String[0]);
}
for (Map.Entry<String, List<String>> entry : headerFields.entrySet()) {
String name = entry.getKey();
if (name == null) {
continue;
}
List<String> entryValues = entry.getValue();
if (entryValues == null || entryValues.isEmpty()) {
names.add(name);
values.add("");
continue;
}
for (String value : entryValues) {
names.add(name);
values.add(value != null ? value : "");
}
}
return new HeaderLists(names.toArray(new String[0]), values.toArray(new String[0]));
}
private static final class HeaderLists {
final String[] names;
final String[] values;
HeaderLists(String[] names, String[] values) {
this.names = names;
this.values = values;
}
}
private static final class ResponseTooLargeException extends Exception {
final byte[] partialBody;
ResponseTooLargeException(byte[] partialBody) {
this.partialBody = partialBody;
}
}
}
@@ -256,6 +256,7 @@ public class HIDDeviceManager {
0x24c6, // PowerA
0x2c22, // Qanba
0x2dc8, // 8BitDo
0x37d7, // Flydigi
0x9886, // ASTRO Gaming
};
@@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 3;
private static final int SDL_MINOR_VERSION = 4;
private static final int SDL_MICRO_VERSION = 2;
private static final int SDL_MICRO_VERSION = 4;
/*
// Display InputType.SOURCE/CLASS of events and devices
//
@@ -2032,7 +2032,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
try {
ParcelFileDescriptor pfd = mSingleton.getContentResolver().openFileDescriptor(Uri.parse(uri), mode);
return pfd != null ? pfd.detachFd() : -1;
} catch (FileNotFoundException e) {
} catch (FileNotFoundException | SecurityException e) {
e.printStackTrace();
return -1;
}
@@ -2227,4 +2227,3 @@ class SDLClipboardHandler implements
SDLActivity.onNativeClipboardChanged();
}
}
@@ -65,17 +65,15 @@ class SDLInputConnection extends BaseInputConnection
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
if (beforeLength > 0 && afterLength == 0) {
// backspace(s)
while (beforeLength-- > 0) {
nativeGenerateScancodeForUnichar('\b');
}
return true;
}
}
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
if (beforeLength > 0 && afterLength == 0) {
// backspace(s)
while (beforeLength-- > 0) {
nativeGenerateScancodeForUnichar('\b');
}
return true;
}
if (!super.deleteSurroundingText(beforeLength, afterLength)) {
return false;
@@ -35,6 +35,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
private static native void auroraNativeSetSurfaceReady(boolean ready);
// Sensors
protected SensorManager mSensorManager;
protected Display mDisplay;
@@ -96,6 +98,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
auroraNativeSetSurfaceReady(false);
SDLActivity.onNativeSurfaceCreated();
}
@@ -103,6 +106,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
auroraNativeSetSurfaceReady(false);
// Transition to pause, if needed
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
@@ -192,6 +196,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
/* Surface is ready */
mIsSurfaceReady = true;
auroraNativeSetSurfaceReady(true);
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
SDLActivity.handleNativeState();
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Dusk</string>
<string name="app_name">Dusklight</string>
<string name="documents_provider_root_name">Dusklight Data</string>
<string name="documents_provider_summary">Saves, texture packs, settings, and logs</string>
</resources>
Vendored Regular → Executable
View File
+35 -16
View File
@@ -14,9 +14,22 @@ if [[ -z "$ANDROID_NDK_VER" ]] && [[ -d "$ANDROID_HOME_DIR/ndk" ]]; then
fi
if [[ -n "$ANDROID_NDK_VER" ]]; then
TOOLCHAIN_BIN="$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt/linux-x86_64/bin"
if [[ -x "$TOOLCHAIN_BIN/llvm-strip" ]]; then
STRIP_TOOL="$TOOLCHAIN_BIN/llvm-strip"
case "$(uname -s)" in
Darwin) HOST_TAG="darwin-x86_64" ;;
Linux) HOST_TAG="linux-x86_64" ;;
*) HOST_TAG="" ;;
esac
PREBUILT_DIR="$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt"
if [[ -n "$HOST_TAG" && -x "$PREBUILT_DIR/$HOST_TAG/bin/llvm-strip" ]]; then
STRIP_TOOL="$PREBUILT_DIR/$HOST_TAG/bin/llvm-strip"
else
for candidate in "$PREBUILT_DIR"/*/bin/llvm-strip; do
if [[ -x "$candidate" ]]; then
STRIP_TOOL="$candidate"
break
fi
done
fi
fi
@@ -25,29 +38,35 @@ copy_lib() {
local src="$2"
local dst_dir="$APP_DIR/$abi"
local dst="$dst_dir/libmain.so"
local tmp="$dst_dir/.libmain.so.$$"
if [[ ! -f "$src" ]]; then
echo "Missing native library for $abi: $src" >&2
exit 1
fi
mkdir -p "$dst_dir"
cp -f "$src" "$dst"
cp -f "$src" "$tmp"
if [[ "$ANDROID_STAGE_STRIP" != "0" ]] && [[ -n "$STRIP_TOOL" ]]; then
"$STRIP_TOOL" --strip-debug "$dst"
echo "Staged and stripped $src -> $dst"
"$STRIP_TOOL" --strip-unneeded "$tmp"
mv -f "$tmp" "$dst"
echo "Stripped and staged $src -> $dst"
else
mv -f "$tmp" "$dst"
echo "Staged $src -> $dst (strip disabled or strip tool unavailable)"
fi
}
declare -A ABI_TO_LIB=(
["arm64-v8a"]="$ROOT_DIR/build/android-arm64/libmain.so"
["x86_64"]="$ROOT_DIR/build/android-x86_64/libmain.so"
)
# Drop any previously staged ABI directories to avoid stale APK contents.
rm -rf "$APP_DIR/x86" "$APP_DIR/arm64-v8a" "$APP_DIR/x86_64"
for abi in $ANDROID_STAGE_ABIS; do
src="${ABI_TO_LIB[$abi]:-}"
if [[ -z "$src" ]]; then
echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
exit 1
fi
case "$abi" in
arm64-v8a) src="$ROOT_DIR/build/android-arm64/libmain.so" ;;
x86_64) src="$ROOT_DIR/build/android-x86_64/libmain.so" ;;
*)
echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
exit 1
;;
esac
copy_lib "$abi" "$src"
done
+1 -1
View File
@@ -14,5 +14,5 @@ dependencyResolutionManagement {
}
}
rootProject.name = "dusk-android"
rootProject.name = "dusklight-android"
include ':app'
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

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

Before

Width:  |  Height:  |  Size: 457 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 642 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 48 KiB

+296 -121
View File
@@ -8,140 +8,315 @@ body {
height: 100%;
margin: 0;
padding: 0;
font-family: "Fira Sans Condensed";
font-size: 24dp;
color: #FFFFFF;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
}
.overlay-root {
width: 100%;
min-height: 45%;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
decorator: vertical-gradient(#00000000 #151610F2);
padding: 48dp 0 40dp 0;
filter: opacity(0);
transition: filter 0.2s linear-in-out;
}
.overlay-root[open] {
filter: opacity(1);
}
.overlay {
width: 100%;
max-width: 1216dp;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
gap: 24dp;
padding: 0 32dp;
}
@media (max-height: 800dp) {
.overlay-root {
min-height: 38%;
padding: 32dp 0 28dp 0;
}
.overlay {
gap: 16dp;
padding: 0 24dp;
}
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
.carousel-container {
flex: 1 1 auto;
display: flex;
justify-content: flex-end;
min-width: 0;
}
.description {
font-size: 18dp;
line-height: 22dp;
color: rgba(255, 255, 255, 50%);
}
.divider {
margin: 1dp 0;
border-top: 1dp rgba(217, 217, 217, 50%);
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
footer-button {
display: block;
width: 100%;
max-width: 220dp;
border: 0;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
font-family: "Fira Sans";
font-weight: normal;
font-size: 20dp;
line-height: 24dp;
text-transform: uppercase;
color: #FFFFFF;
opacity: 1;
color: #E0DBC8;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
z-index: 1;
pointer-events: none;
}
fps,
toast {
position: absolute;
border: 1dp #92875B;
background-color: rgba(21, 22, 16, 80%);
}
toast {
top: 40dp;
right: 40dp;
display: flex;
flex-flow: column;
border-radius: 14dp;
overflow: hidden;
backdrop-filter: blur(5dp);
box-shadow: 0 0 15dp 3dp;
filter: opacity(0);
transform: scale(0.9);
transform-origin: center;
transition: filter transform 0.2s cubic-in-out;
padding: 18dp 24dp;
gap: 8dp;
}
toast[open] {
filter: opacity(1);
transform: scale(1);
}
/*toast:hover {
cursor: pointer;
background-color: rgba(61, 59, 36, 80%);
}
footer-button.return {
text-align: left;
toast:active {
background-color: rgba(45, 43, 26, 80%);
}*/
toast heading {
display: flex;
gap: 18dp;
align-items: center;
font-family: "Fira Sans Condensed";
font-size: 18dp;
font-weight: bold;
text-transform: uppercase;
color: #92875B;
}
footer-button.reset {
text-align: right;
toast heading > span {
flex: 1 0 auto;
}
.stepped-carousel {
toast heading > row {
flex: 1 0 auto;
display: flex;
align-items: center;
justify-content: center;
gap: 16dp;
width: auto;
min-width: 246dp;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
gap: 4dp;
}
.stepped-carousel-value {
line-height: 29dp;
min-width: 166dp;
toast message {
display: flex;
flex-flow: column;
gap: 8dp;
}
toast message row {
display: flex;
}
toast message row.muted {
opacity: 0.5;
}
toast progress {
height: 4dp;
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
toast progress fill {
background-color: rgba(194, 164, 45, 80%);
}
toast.achievement {
border: 1dp #C2A42D;
}
toast.achievement heading {
color: #C2A42D;
}
toast.controller-warning {
top: auto;
right: auto;
bottom: 40dp;
left: 50%;
width: 440dp;
max-width: 90%;
transform: translateX(-50%) scale(0.9);
}
toast.controller-warning[open] {
transform: translateX(-50%) scale(1);
}
toast.controller-warning heading {
color: #C2A42D;
}
toast.menu-notification {
top: 40dp;
right: auto;
bottom: auto;
left: 50%;
max-width: 90%;
transform: translateX(-50%) scale(0.9);
}
toast.menu-notification[open] {
transform: translateX(-50%) scale(1);
}
toast.menu-notification message {
align-items: center;
text-align: center;
white-space: nowrap;
opacity: 0.9;
}
.stepped-carousel-arrow {
width: 24dp;
height: 24dp;
min-width: 24dp;
padding: 0;
border: 0;
background-color: transparent;
opacity: 1;
cursor: pointer;
toast.menu-notification message row {
align-items: center;
gap: 6dp;
}
icon {
font-family: "Material Symbols Rounded";
font-weight: normal;
display: inline-block;
vertical-align: middle;
}
icon.arrow-forward {
width: 1.2em;
height: 1.2em;
font-size: 1.2em;
decorator: text("&#xe5c8;" center center);
}
icon.trophy {
width: 1.2em;
height: 1.2em;
font-size: 1.2em;
decorator: text("&#xe71a;" center center);
}
icon.controller {
width: 1.2em;
height: 1.2em;
font-size: 1.2em;
decorator: text("&#xf135;" center center);
}
icon.warning {
width: 1.2em;
height: 1.2em;
font-size: 1.2em;
decorator: text("&#xe002;" center center);
}
fps {
display: none;
z-index: 99;
font-size: 18dp;
font-weight: bold;
padding: 9dp 12dp;
border-radius: 7dp;
pointer-events: none;
white-space: nowrap;
}
speedrun-timer {
display: none;
position: absolute;
bottom: 0;
right: 0;
z-index: 99;
background-color: rgba(0, 0, 0, 65%);
padding: 2dp 4dp;
pointer-events: none;
font-family: "Noto Mono";
font-size: 16dp;
color: #ffffff;
white-space: nowrap;
}
speedrun-timer[open] {
display: block;
}
speedrun-rta {
display: none;
}
speedrun-rta[open] {
display: block;
}
speedrun-igt {
display: block;
}
fps[open] {
display: block;
}
fps[corner=tl] {
top: 12dp;
left: 12dp;
}
fps[corner=tr] {
top: 12dp;
right: 12dp;
}
fps[corner=bl] {
bottom: 12dp;
left: 12dp;
}
fps[corner=br] {
bottom: 12dp;
right: 12dp;
}
logo {
position: absolute;
width: 100dp;
height: 100dp;
bottom: 40dp;
left: 40dp;
opacity: 0;
transition: opacity 0.5s linear-in-out;
}
logo[open] {
opacity: 0.65;
}
logo img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
filter: drop-shadow(#0008 0 0 14dp);
transform-origin: center;
}
logo img.inner {
animation: 24s linear-in-out infinite logo-inner-spin;
}
logo img.outer {
animation: 8s linear-in-out infinite logo-outer-spin;
}
@keyframes logo-inner-spin {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
@keyframes logo-outer-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (max-height: 640dp) {
toast {
top: 20dp;
right: 20dp;
}
toast.controller-warning {
bottom: 20dp;
}
toast.menu-notification {
top: 20dp;
}
}

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