Compare commits

...

45 Commits

Author SHA1 Message Date
crueter f0a4ac7359 [frontend] only set https on Android (#3733)
Otherwise it breaks the shit out of every other platform.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3733
2026-03-15 02:57:32 +01:00
Eden CI d604a9da7b [dist, android] Update translations from Transifex for Mar 14 (#3732)
Automatic translation update for Mar 14

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3732
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: Eden CI <ci@eden-emu.dev>
Co-committed-by: Eden CI <ci@eden-emu.dev>
2026-03-15 02:52:34 +01:00
lizzie 0ec60f21ff Revert "[updater] Explicit set https for api url (#3720)" (#3726)
#3720 was merged without proper revision.
AAAA
It's fine, if there are issues just merge the revert of.

This reverts commit 8de1dd151f.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3726
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-15 02:06:29 +01:00
crueter 9cf1e19d98 [externals] Update SDL2 (#3731)
https://github.com/libsdl-org/SDL/pull/15207

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3731
2026-03-15 01:45:11 +01:00
John 98a93561de Revert #3690 to fix regression. (#3728)
- Fixes Ender Magnolia and Demon Slayer freezing on Android.

May be related to this PR or other fixes on the master but:
- Using Cure in Ender Magnolia no longer crashes the game.
- Fixes Demon Slayer vertex explosion on Android.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3728
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: John <john@eden-emu.dev>
Co-committed-by: John <john@eden-emu.dev>
2026-03-14 19:37:24 +01:00
xbzk 4a60085a76 [android,settings] fix for *_combined (fastmem, gpu unswizzle) kotlin side only settings being unnecesarily requested to native (#3724)
Fixes jni reporting error when these settings change.
Combined settings are the grouping reference for settings like gpu unswizzle.
As far as i recall, fastmen only uses it coz it has a sub setting in the past.

[ 445.450666] Frontend <Error> main/jni/native_config.cpp:33:getSetting: [Android Native] Could not find setting - gpu_unswizzle_combined
[ 450.054470] Frontend <Error> main/jni/native_config.cpp:33:getSetting: [Android Native] Could not find setting - fastmem_combined

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3724
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-14 03:30:37 +01:00
PavelBARABANOV 47ed86d3e2 [vk] Partial return of the old buffer update logic (#3690)
Fixes shadows in Metroid Prime Remastered

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3690
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-03-13 19:59:11 +01:00
xbzk 2aea7f9584 [android,settings] addons: fix crash when launching Install addons from per game addons menu (#3727)
to fix navigation issues (PR3699), i've bounded all orphaned fragments to SettingsSubscreenActivity.
(IIRC that were tested, whatever) Somehow the launching from per-game settings (where addons fragment is bounded to MainActivity got broken).
This PR just made ContentTypeSelectionDialogFragment self-sufficient so it lives under both MainActivity and SettingsSubscreenActivity.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3727
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-13 19:46:35 +01:00
lizzie 59b0e66722 Revert "[core/memory] remove indirection handling for unaligned access (#3584)" (#3725)
This reverts commit 2d27359074.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3725
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-13 19:09:53 +01:00
DraVee 8de1dd151f [updater] Explicit set https for api url (#3720)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3720
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: DraVee <chimera@dravee.dev>
Co-committed-by: DraVee <chimera@dravee.dev>
2026-03-13 17:55:38 +01:00
DraVee 98604d369a [vulkan] Revert "[vulkan]fix vuid 02751 (#3573)" (#3721)
Attached backtrace on PR comments

This reverts commit cdf9b556b2.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3721
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: DraVee <chimera@dravee.dev>
Co-committed-by: DraVee <chimera@dravee.dev>
2026-03-13 02:47:47 +01:00
lizzie 4337135910 [common/logging] fix android stupid ctor() init order not initializing logging first (#3719)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3719
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-12 23:16:56 +01:00
lizzie 395613b01f [common/logging] Simplify logging logic and fix issues when logging before system is created (#3688)
- our logging code was bigger than spdlog itself, why???? just keep it simple
- fix issues when logging before logging system is even started
- removes the "initialized logging twice" issue
- removes uneeded indirection in file logging
- uses direct formatting instead of jumping hoopla-around the fmt::format() ressult
- code duplication and dead code removal as usual

I did explore dup2() but I think it's not worth the hassle
I did try `fwopen()` but it's better if things are just kept as-is.

there is a lot of noise because I removed a bunch of redundant files on logging and just put everything in one file

now normally this wouldn't be a good idea, however consider: the complexity of logging; it's less than 500 lines... does it really need a whole subsystem?!?!?! ITS JUST LOGGING

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3688
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-12 18:29:15 +01:00
xbzk 2896fa3835 [android,ui] chore: settings subscreens transition and other minor conformances (#3699)
- Fix black screen in transition animations

- Adjustments to about fragment
made about text more label and less button like, header transparency, spacing adjustments, word Contributors replaced by People in Contributors field for de-duplication.

- installable actions code de-duplication
Extracted install/update/import firmware/user data flows into InstallableActions.kt and reused it from MainActivity and InstallableFragment, reducing duplicated logic, ensuring single source of truth.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3699
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-11 22:47:16 +01:00
lizzie 0dad29698e [frontend] allow to specify input profile name for first player on command line (#3684)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3684
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-11 16:50:00 +01:00
lizzie 5a0780b826 [video_core] Properly disable/avoid building OpenGL when it's disabled (#3692)
- OpenGL symbols would still be included in builds without OpenGL, this pr fixes that
- Same goes for Vulkan, but now with `ENABLE_VULKAN`
- Add support to have OpenGL-only builds (why would you do this?)
- Add support for headless runs (yes you could just select NULL backend, but why not compile it headless? :)

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3692
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-11 16:49:29 +01:00
lizzie d35fc7b7ee [docs] testing guidelines, unify controller guide, gamemode (#3709)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3709
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-11 16:49:03 +01:00
crueter 8678cb06eb [meta] clang-format literally all of the Qt code (#3706)
I'm tired of dealing with this tbh

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3706
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-03-10 06:51:08 +01:00
crueter 769edbfea3 [video_core] Revert "Simplify TextureCache GC and remove redundant code" (#3652) (#3704)
regr. Steam Deck

Please, for the love of God, stop saying "YOLO good to merge" after
testers report performance regressions (and promptly get brushed to the
side). Seriously, what the hell?

This reverts commit f8ea09fa0f.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3704
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
2026-03-10 05:44:51 +01:00
crueter 0ff1d215c8 [desktop] Port some QtCommon changes from QML branch (#3703)
- Linker now resolves implementation differences
- Remove unneeded ifdefs
- Better abstractions overall

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3703
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-03-10 05:37:45 +01:00
crueter 07e3a2aa46 [settings] Disable fastmem on Linux systems with non-4kb page sizes (#3669)
Asahi, etc

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3669
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-03-10 05:36:12 +01:00
xbzk a1b50e9339 [android] patches bin button + version bug fixes (#3691)
This fixed the delete button enabled for external content (which is auto handled and the proper way to get rid of them is either by removing its folder from ext content list, or removing the file itself) by streaming patch source thru jni.

Along the way stumbled upon another bug: If you have an external content update installed (say latest version for example) and you NAND install a previous update (like in silksong's hard mode update), the newest update version string would leak to the previous one.

Did videos for both. Fixed both. Seems good to go.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3691
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-09 00:30:10 +01:00
crueter f5e2b1fb13 [dynarmic] Remove incorrect LICENSE (#3698)
Our dynarmic is GPLv3, not BSD.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3698
2026-03-08 23:22:51 +01:00
lizzie 6693b99ae4 [core] coalesce tracking entries for GPU (#3677)
I think I may have attempted this before, but I doubt it.

Anyways this should reduce virtual buffers from 3 to just 1, also improved access times :)

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3677
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-08 22:45:38 +01:00
MaranBr f8ea09fa0f [video_core] Simplify TextureCache GC and remove redundant code (#3652)
This enhances the garbage collection in TextureCache to make it more responsive and reliable during long gameplay sessions.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3652
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-03-08 22:45:35 +01:00
smiRaphi 361e6209b2 [settings] Add back & properly implement use_dev_keys (#3631)
Was removed recently but also wasn't really working before, this adds it to the debug UI (under the kiosk option) and also makes it properly reload the keys on launch & setting change.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3631
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: smiRaphi <neogt404@gmail.com>
Co-committed-by: smiRaphi <neogt404@gmail.com>
2026-03-08 22:37:20 +01:00
crueter 3d1a67af18 [externals] Update dependencies (#3664)
* zlib: 1.3.1.2 -> 1.3.2
* vulkan-validation-layers: 1.4.335.0 -> 1.4.341.0
* sirit: 1.0.3 -> 1.0.4
* httplib: 0.35.0 -> 0.37.0
* xbyak: 7.33.3 -> 7.35.2
* catch2: 3.12.0 -> 3.13.0
* vulkan-headers: 1.4.342 -> 1.4.345
* vulkan-utility-libraries: 1.4.342 -> 1.4.345

Also fixed a build error with newer xbyak.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3664
2026-03-08 22:33:51 +01:00
crueter c7b23f4a1a [time] fix: guard timezone out buffer logging (#3668)
Original text, per the emailed patch:

---------------

Hello,

I am submitting a small fix to prevent an unintended abort when _GLIBCXX_ASSERTIONS is enabled, caused by out-of-bounds access in debug logging.
Background / Issue

In the server-side implementations of ITimeZoneService::ToPosixTime and ToPosixTimeWithMyRule, the SCOPE_EXIT debug logging previously accessed out_times[0] and out_times[1] unconditionally.

However, out_times is an IPC-provided output buffer (OutArray, which inherits from std::span). Its length depends on the caller-provided buffer capacity. During debugging, I encountered a case where out_times.size() == 1.

Under _GLIBCXX_ASSERTIONS, accessing out_times[1] triggers a std::span::operator[] assertion failure (std::__glibcxx_assert_fail) and aborts the process, causing the service thread to crash. This results in an unintended crash caused solely by debug logging.
Change Description

In the SCOPE_EXIT logging blocks of both ToPosixTime and ToPosixTimeWithMyRule, I added bounds checks before accessing out_times[0] and out_times[1]:

    Access out_times[0] only if out_times.size() > 0

    Access out_times[1] only if out_times.size() > 1

    Print 0 when the corresponding element is unavailable

This change only affects debug log output. It does not modify IPC semantics or the time conversion logic itself.
Reproduction Context (for reference)

I encountered this issue while running 13 Sentinels: Aegis Rim (title ID: 01008D7016438000). During the “Load Game” flow, ToPosixTimeWithMyRule is invoked with an out_times buffer of length 1, which previously led to the out-of-bounds access in the logging code.

Thank you for your time and review.

Best regards,
darkpaper

Environment: Arch Linux / KDE / X11

This email and the accompanying patch were prepared with assistance from
an LLM.

Authored-by: darkpaper <lirunzhou2021@gamil.com>
Signed-off-by: crueter <crueter@eden-emu.dev>

Co-authored-by: darkpaper <lirunzhou2021@gamil.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3668
2026-03-08 20:53:37 +01:00
lizzie 38aa2bc5e1 [hle/services] use ankerl:: for Service's function handlers map, use const char* instead of std::string{} (#3671)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3671
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-08 20:50:29 +01:00
lizzie 1864160326 [qt] remove 'None' interface since net code already uses first avail interface anyways, disable changing ifaces at runtime (#3683)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3683
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-08 20:49:17 +01:00
lizzie 0a169dec4d [qt] fix crash when having an invalid graphics backend (#3693)
closely related to #3692, however the crash may also occur when sharing configs between macOS and a system that had OpenGL

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3693
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-08 20:48:47 +01:00
wildcard 80bafc8fe8 [vulkan] Fix incorrect offset application for Array2D textures (#3696)
Earlier we were taking the coords and adding them to coords instead of actually taking offsets and adding it to coord

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3696
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-08 20:48:19 +01:00
lizzie f2c46eadc1 [ports] build fixes for *BSD make (#3496)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3496
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-08 19:32:24 +01:00
xbzk 7a17fd8c71 [android,ui] chore: minor standardization layout (#3694)
Amost no visual differences, but tons of layout cleanups and fixes
Most notable change, Freedreno option only show on Adreno GPUs

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3694
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-08 16:16:23 +01:00
xbzk 9423a33fc2 [android,ui] fix addons list not refresh upon rotation (#3687)
Fixes a bug reported by Pavel, in which an RC as returning empty patchList after phone rotated.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3687
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-08 00:41:40 +01:00
lizzie afec66f598 core/hle/services/audio] Fix audioctl being started before audout/audin (which are required for ctor) (#3695)
`audctl` depends on `audout`, simply start `audout` and `audin` before!

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3695
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-07 23:44:58 +01:00
SchweGELBin a022560991 [desktop] tas: add option to disable file overwrite dialog (#3657)
Hello everybody, thank you for letting me participate in the development of the Eden emulator!

I've been playing around with the TAS functionality and didn't want to always click "Yes" in the dialog that askes if I want to "Overwrite file of player 1?" after recording the inputs.
So I can't record and play TAS files with keybinds only, because I'd still need to switching from my contoller to my keyboard and back.

So I added the option "Show recording dialog" into the configure_tas screen.
(The final naming and string can be changed of course.)
It's a checkbox that is enabled by default (so no changes if ignored), but can be unchecked to disable the popup.
The change has been tested on top of the current master branch.

I've also created a commit to add the relevant translation data, where german is translated and the rest unfinished.
I'm not sure how this would be handled as this project uses transifex for it localization, so I can remove this commit if preferred.

Have a great day!
- Michi

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3657
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: SchweGELBin <abramjannikmichael06@gmail.com>
Co-committed-by: SchweGELBin <abramjannikmichael06@gmail.com>
2026-03-07 20:08:47 +01:00
lizzie 11ad71b1e7 [docs] Obtanium, installing mods, external ES-DE config, section about CFW and settings (#3678)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3678
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-07 18:16:05 +01:00
xbzk ddac8c8eb5 [vk] fix crash introduced in 9a07bd0570 (#3685)
Fix for current crash on master.
Just reverted only the necessary stuff so that PresentManager can hold a reference to khr and resist death upon application hold/restore.
@Lizzie shall judge.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3685
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-06 19:52:17 +01:00
lizzie c062931c9b [qt] add translation table entry for debug_knobs,serial_battery and serial_unit (#3682)
trivial qt change

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3682
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 16:38:39 +01:00
crueter e4122dae1d [desktop] addons: open mod folder in rc menu (#3662)
also fixed the multiselection being absolutely horrendous

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3662
2026-03-06 16:38:21 +01:00
lizzie b75e81af5e [video_core/engines] implement stub NV01 timer, inline other channel engines (#3640)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3640
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:05:39 +01:00
lizzie 2ed1328c93 [vk] use static_vector instead of small_vector for TFB and other bindings (#3641)
MK8D is a big offender, taking up lots of time memcpy'ing and memmov'ing small_vector<> AND to add salt to the wound it doesn't even do heap allocations (no game does I think) - so basically useless waste of compute time in hot path for NO reason :^)

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3641
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:05:05 +01:00
lizzie c70b857c4f [video_core/engines] Macro HLE inline (#3653)
Should slightly boost perf on android, Desktop is mainly unaffected (for now)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3653
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:04:38 +01:00
MaranBr 23566a1f7d [prepo] Add support for missing PlayReport commands (#3674)
This fixes:

`[ 433.095195] Debug <Critical> core\hle\service\service.cpp:operator ():69: Assertion Failed!
Unknown / unimplemented function '10107': port='prepo:u' cmd_buf={[0]=0x110006, [1]=0x80000014, [2]=0x1, [3]=0x0, [4]=0x0, [5]=0x191080, [6]=0x5A7350F8, [7]=0x112, [8]=0x5A735158}`

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3674
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-03-06 15:02:59 +01:00
556 changed files with 56073 additions and 40417 deletions
+2 -2
View File
@@ -115,7 +115,7 @@ for file in $FILES; do
*.cmake|*.sh|*CMakeLists.txt)
begin="#"
;;
*.kt*|*.cpp|*.h)
*.kt*|*.cpp|*.h|*.qml)
begin="//"
;;
*)
@@ -193,7 +193,7 @@ if [ "$UPDATE" = "true" ]; then
begin="#"
shell=true
;;
*.kt*|*.cpp|*.h)
*)
begin="//"
shell="false"
;;
+89
View File
@@ -0,0 +1,89 @@
From 509be32bbfa6eb95014860f7c9ea6d45c8ddaa56 Mon Sep 17 00:00:00 2001
From: crueter <crueter@eden-emu.dev>
Date: Sun, 8 Mar 2026 15:11:12 -0400
Subject: [PATCH] [cmake] Simplify zstd find logic, and support pre-existing
zstd target
Some deduplication work on the zstd required/if-available logic. Also
adds support for pre-existing `zstd::libzstd` which is useful for
projects that bundle their own zstd in a way that doesn't get caught by
`CONFIG`
Signed-off-by: crueter <crueter@eden-emu.dev>
---
CMakeLists.txt | 46 ++++++++++++++++++++++++++--------------------
1 file changed, 26 insertions(+), 20 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1874e36be0..8d31198006 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -241,28 +241,34 @@ endif()
# NOTE:
# zstd < 1.5.6 does not provide the CMake imported target `zstd::libzstd`.
# Older versions must be consumed via their pkg-config file.
-if(HTTPLIB_REQUIRE_ZSTD)
- find_package(zstd 1.5.6 CONFIG)
- if(NOT zstd_FOUND)
- find_package(PkgConfig REQUIRED)
- pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd)
- add_library(zstd::libzstd ALIAS PkgConfig::zstd)
- endif()
- set(HTTPLIB_IS_USING_ZSTD TRUE)
-elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE)
- find_package(zstd 1.5.6 CONFIG QUIET)
- if(NOT zstd_FOUND)
- find_package(PkgConfig QUIET)
- if(PKG_CONFIG_FOUND)
- pkg_check_modules(zstd QUIET IMPORTED_TARGET libzstd)
-
- if(TARGET PkgConfig::zstd)
+if (HTTPLIB_REQUIRE_ZSTD)
+ set(HTTPLIB_ZSTD_REQUESTED ON)
+ set(HTTPLIB_ZSTD_REQUIRED REQUIRED)
+elseif (HTTPLIB_USE_ZSTD_IF_AVAILABLE)
+ set(HTTPLIB_ZSTD_REQUESTED ON)
+ set(HTTPLIB_ZSTD_REQUIRED QUIET)
+endif()
+
+if (HTTPLIB_ZSTD_REQUESTED)
+ if (TARGET zstd::libzstd)
+ set(HTTPLIB_IS_USING_ZSTD TRUE)
+ else()
+ find_package(zstd 1.5.6 CONFIG QUIET)
+
+ if (NOT zstd_FOUND)
+ find_package(PkgConfig ${HTTPLIB_ZSTD_REQUIRED})
+ pkg_check_modules(zstd ${HTTPLIB_ZSTD_REQUIRED} IMPORTED_TARGET libzstd)
+
+ if (TARGET PkgConfig::zstd)
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
endif()
endif()
+
+ # This will always be true if zstd is required.
+ # If zstd *isn't* found when zstd is set to required,
+ # CMake will error out earlier in this block.
+ set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND})
endif()
- # Both find_package and PkgConf set a XXX_FOUND var
- set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND})
endif()
# Used for default, common dirs that the end-user can change (if needed)
@@ -317,13 +323,13 @@ if(HTTPLIB_COMPILE)
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
)
-
+
# Add C++20 module support if requested
# Include from separate file to prevent parse errors on older CMake versions
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")
include(cmake/modules.cmake)
endif()
-
+
set_target_properties(${PROJECT_NAME}
PROPERTIES
VERSION ${${PROJECT_NAME}_VERSION}
+1 -1
View File
@@ -196,7 +196,7 @@ option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
# FreeBSD 15+ has libusb, versions below should disable it
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR PLATFORM_FREEBSD OR APPLE" OFF)
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT WIN32 OR NOT ARCHITECTURE_arm64" OFF)
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF)
mark_as_advanced(FORCE ENABLE_OPENGL)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
+7 -1
View File
@@ -61,6 +61,10 @@ See the [sign-up instructions](docs/SIGNUP.md) for information on registration.
Alternatively, if you wish to add translations, go to the [Eden project on Transifex](https://app.transifex.com/edenemu/eden-emulator) and review [the translations README](./dist/languages).
## Documentation
We have a user manual! See our [User Handbook](./docs/user/README.md).
## Building
See the [General Build Guide](docs/Build.md)
@@ -69,7 +73,9 @@ For information on provided development tooling, see the [Tools directory](./too
## Download
You can download the latest releases from [here](https://github.com/eden-emulator/Releases/releases).
You can download the latest releases from [here](https://git.eden-emu.dev/eden-emu/eden/releases).
Save us some bandwidth! We have [mirrors available](./docs/user/ThirdParty.md#mirrors) as well.
## Support
+4 -4
View File
@@ -46,9 +46,9 @@
"package": "ZLIB",
"repo": "madler/zlib",
"tag": "v%VERSION%",
"hash": "06eaa3a1eaaeb31f461a2283b03a91ed8eb2406e62cd97ea1c69836324909edeecd93edd03ff0bf593d9dde223e3376149134c5b1fe2e8688c258cadf8cd60ff",
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
"version": "1.2",
"git_version": "1.3.1.2",
"git_version": "1.3.2",
"options": [
"ZLIB_BUILD_SHARED OFF",
"ZLIB_INSTALL OFF"
@@ -98,9 +98,9 @@
"package": "VVL",
"repo": "KhronosGroup/Vulkan-ValidationLayers",
"tag": "vulkan-sdk-%VERSION%",
"git_version": "1.4.335.0",
"git_version": "1.4.341.0",
"artifact": "android-binaries-%VERSION%.zip",
"hash": "48167c4a17736301bd08f9290f41830443e1f18cce8ad867fc6f289b49e18b40e93c9850b377951af82f51b5b6d7313aa6a884fc5df79f5ce3df82696c1c1244"
"hash": "8812ae84cbe49e6a3418ade9c458d3be6d74a3dffd319d4502007b564d580998056e8190414368ec11b27bc83993c7a0dad713c31bcc3d9553b51243efee3753"
},
"quazip": {
"package": "QuaZip-Qt6",
+1742 -1242
View File
File diff suppressed because it is too large Load Diff
+1843 -1350
View File
File diff suppressed because it is too large Load Diff
+1693 -1202
View File
File diff suppressed because it is too large Load Diff
+1692 -1201
View File
File diff suppressed because it is too large Load Diff
+1827 -1328
View File
File diff suppressed because it is too large Load Diff
+1692 -1201
View File
File diff suppressed because it is too large Load Diff
+2299 -1768
View File
File diff suppressed because it is too large Load Diff
+1692 -1201
View File
File diff suppressed because it is too large Load Diff
+1698 -1208
View File
File diff suppressed because it is too large Load Diff
+1671 -1180
View File
File diff suppressed because it is too large Load Diff
+1692 -1201
View File
File diff suppressed because it is too large Load Diff
+1750 -1247
View File
File diff suppressed because it is too large Load Diff
+1734 -1240
View File
File diff suppressed because it is too large Load Diff
+1666 -1175
View File
File diff suppressed because it is too large Load Diff
+2230 -1738
View File
File diff suppressed because it is too large Load Diff
+1667 -1177
View File
File diff suppressed because it is too large Load Diff
+1675 -1185
View File
File diff suppressed because it is too large Load Diff
+1827 -1311
View File
File diff suppressed because it is too large Load Diff
+1671 -1180
View File
File diff suppressed because it is too large Load Diff
+2265 -1720
View File
File diff suppressed because it is too large Load Diff
+1685 -1184
View File
File diff suppressed because it is too large Load Diff
+1669 -1179
View File
File diff suppressed because it is too large Load Diff
+1724 -1223
View File
File diff suppressed because it is too large Load Diff
+1666 -1175
View File
File diff suppressed because it is too large Load Diff
+1666 -1175
View File
File diff suppressed because it is too large Load Diff
+1710 -1211
View File
File diff suppressed because it is too large Load Diff
+1672 -1182
View File
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -91,7 +91,7 @@ After configuration, you may need to modify `externals/ffmpeg/CMakeFiles/ffmpeg-
`-lc++-experimental` doesn't exist in OpenBSD but the LLVM driver still tries to link against it, to solve just symlink `ln -s /usr/lib/libc++.a /usr/lib/libc++experimental.a`. Builds are currently not working due to lack of `std::jthread` and such, either compile libc++ manually or wait for ports to catch up.
If clang has errors, try using `g++-11`.
If clang has errors, try using `g++11`.
## FreeBSD
@@ -107,6 +107,8 @@ hw.usb.usbhid.enable="0"
## NetBSD
2026-02-07: `vulkan-headers` must not be installed, since the version found in `pkgsrc` is older than required. Either wait for binary packages to update or build newer versions from source.
Install `pkgin` if not already `pkg_add pkgin`, see also the general [pkgsrc guide](https://www.netbsd.org/docs/pkgsrc/using.html). For NetBSD 10.1 provide `echo 'PKG_PATH="https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.1/All/"' >/etc/pkg_install.conf`. If `pkgin` is taking too much time consider adding the following to `/etc/rc.conf`:
```sh
@@ -116,7 +118,7 @@ ip6addrctl_policy=ipv4_prefer
System provides a default `g++-10` which doesn't support the current C++ codebase; install `clang-19` with `pkgin install clang-19`. Or install `gcc14` (or `gcc15` with current pkgsrc). Provided that, the following CMake commands may work:
- `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -Bbuild`
- `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -Bbuild` (Recommended)
- `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/pkg/gcc14/bin/gcc -DCMAKE_CXX_COMPILER=/usr/pkg/gcc14/bin/g++ -Bbuild`
- `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/pkg/gcc15/bin/gcc -DCMAKE_CXX_COMPILER=/usr/pkg/gcc15/bin/g++ -Bbuild`
@@ -138,8 +140,12 @@ cmake --install build
However, pkgsrc is highly recommended, see [getting pkgsrc](https://iso.us.netbsd.org/pub/pkgsrc/current/pkgsrc/doc/pkgsrc.html#getting). You must get `current` not the `2025Q2` version.
`QtCore` on NetBSD is included, but due to misconfigurations(!) we MUST include one of the standard headers that include `bits/c++config.h`, since source_location (required by `QtCore`) isn't properly configured to intake `bits/c++config.h` (none of the experimental library is). This is a bug with NetBSD packaging and not our fault, but alas.
## DragonFlyBSD
2026-02-07: `vulkan-headers` and `vulkan-utility-libraries` must NOT be uninstalled, since they're too old: `1.3.289`. Either wait for binary packages to update or build newer versions from source.
If `libstdc++.so.6` is not found (`GLIBCXX_3.4.30`) then attempt:
```sh
+2 -2
View File
@@ -70,8 +70,8 @@ These options control executables and build flavors.
The following options are desktop only.
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input frontend (HIGHLY RECOMMENDED)
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics frontend
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input backend (HIGHLY RECOMMENDED)
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics backend
- Unavailable on Windows/ARM64
- You probably shouldn't turn this off.
+2 -1
View File
@@ -1,5 +1,7 @@
# Eden Build Documentation
Are you just a casual user? Take a look at our [User Handbook](./user) then!
This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil), and more.
- **[General Build Instructions](Build.md)**
@@ -11,7 +13,6 @@ This contains documentation created by developers. This contains build instructi
- **[CPM - CMake Package Manager](./CPMUtil)**
- **[Platform-Specific Caveats](Caveats.md)**
- **[The NVIDIA SM86 (Maxwell) GPU](./NvidiaGpu.md)**
- **[User Handbook](./user)**
- **[Dynarmic](./dynarmic)**
- **[Cross compilation](./CrossCompile.md)**
- **[Driver Bugs](./DriverBugs.md)**
-100
View File
@@ -1,100 +0,0 @@
# Importing Games into Steam with Steam Rom Manager
Use this when you want to import your games inside Eden into Steam to launch with artwork from Steam Game Mode without needing to launch Eden first.
**Click [Here](https://evilperson1337.notion.site/Importing-Games-into-Steam-with-Steam-Rom-Manager-2b757c2edaf680d7a491c92b138f1fcc) for a version of this guide with images & visual elements.**
---
### Pre-Requisites
- Steam Deck Set up and Configured
- Eden set up and Configured
- Internet Access
---
## Steps
1. Press the **STEAM** button and then go to *Power → Switch to Desktop* to enter the Desktop mode.
1. Install ***Steam ROM Manager***, there are 2 ways you can accomplish this, either manually or through [*EmuDeck*](https://www.emudeck.com/#downloads).
---
### Manual Installation
1. Open the *Discover Store* and search for *Steam ROM Manager.*
2. Select the **Install** button to install the program.
---
### Installing Through *EmuDeck*
<aside>
***NOTE***: This assumes you have already set up EmuDeck, if not - just run through the guided installation and select *Steam ROM Manager* as one of the options.
</aside>
1. Open **EmuDeck**, then navigate to *Manage Emulators.*
2. Scroll down to the bottom of the page to the *Manage your Tools & Frontends* section. Click **Steam ROM Manager**.
3. Click the **Install** button on the right hand side to install it.
---
2. Open the Start Menu and Launch ***Steam ROM Manager***
1. The program will now launch and show you a window with parsers.
<aside>
***TIP***: Your layout may look different depending on how you installed *Steam ROM Manager*. You may need to go to **Settings → Theme** and change it to *Classic* to follow along.
</aside>
2. Switch off all Parsers by hitting the *Toggle Parsers* switch.
3. Scroll down the list on the left-hand side and look for a parser called *Nintendo Switch - Eden* and switch it on. This parser may not exist depending on how you installed *Steam ROM Manager* (EmuDeck creates it for you). Follow these steps to create it if it is missing.
---
### Creating the Eden Parser
1. Select Create Parser and in the *Community Presets* option look for **Nintendo Switch - Yuzu**.
2. Change the **Parser title** from *Nintendo Switch - Yuzu* to *Nintendo Switch - Eden.*
3. Hit the **Browse** option under the *ROMs directory* section. Select the directory containing your Switch ROMs.
4. Under *Steam collections*, you can add a Steam category name. This just organizes the games under a common category in your Steam Library, this is optional but recommended.
5. Scroll down slightly to the **Executable Configuration → Executable**, select **Browse** and select the Eden AppImage.
6. Leave everything else the same and hit **Save** to save the parser.
---
4. Click the Eden parser to view the options on the right, select **Test** at the bottom of the screen to ensure that *Steam ROM Manager* detects your games correctly.
1. *Steam ROM Manager* will start to scan the specified ROMs directory and match them to games. Look over the results to ensure they are accurate. If you do not see any entries - check your parsers ROMs directory field.
1. When you are happy with the results, click the **Add Games****Parse** to start the actual Parsing.
1. The program will now identify the games and pull artwork from [*SteamGridDB*](https://www.steamgriddb.com/).
2. Review the game matches and ensure everything is there.
---
### Correcting a Mismatch
If the game is not identified correctly, you may need to tell *Steam ROM Manager* what the game is manually.
1. Hover over the game card and click the magnifying glass icon.
2. Search for the game on the *Search SteamGridDB* section and scroll through the results, selecting the one you want.
3. Ensure the *Name* and *Game ID* update in the **Per-App Exceptions** and press **Save and close**. The game should now update.
---
### Excluding Matches
You may want to tell Steam ROM Manager to ignore some files (updates/DLC/etc.) that it finds in the directory. This is how you do so.
1. Hit the **Exclude Games** button in the bottom right.
2. Deselect the game you want to exclude, the poster artwork should go dim and the **Number Excluded** number should increment up. Repeat with any other exclusions you want to add.
3. Hit **Save Excludes** when you are happy with your selections.
---
3. When you are happy with the results, select **Save to Steam** to save the results.
1. The program will now start writing the entries into the Steam Library. You should get pop up notifications of the progress, but you can monitor the progress by selecting the **Log** on the left-hand side if needed.
2. Restart Steam to have the changes take effect. Check your library to ensure that your games are there, in a category if you defined one in the parser.
3. Try to launch a game and ensure everything is working. You are now good to go.
+3 -3
View File
@@ -1,6 +1,6 @@
# Setting a Custom Date/Time in Eden
Use this guide whenever you want to modify the Date or Time that Eden reports to games. This can be useful for modifying RNG elements, skipping wait times in games, etc.
Use this guide whenever you want to modify the Date or Time that Eden reports to games. This can be useful for modifying RNG elements, skipping wait times in games, etc.
**Click [Here](https://evilperson1337.notion.site/Setting-a-Custom-Date-Time-in-Eden-2b357c2edaf680acb8d4e63ccc126564) for a version of this guide with images & visual elements.**
@@ -16,5 +16,5 @@ Use this guide whenever you want to modify the Date or Time that Eden reports to
1. Navigate to *Emulation → Configure*.
2. Click on the **System** item on the left-hand side navigation, then check the *Custom RTC Date* box.
3. The Date/Time option now becomes editable. Set it to the value you want and hit **OK**.
4. GREAT SCOTT! We have time traveled! You can of course go forward or backward in time (as long as it is not before the year 1970) and your game should update accordingly (e.g. certain *Super Mario Odyssey* moons that take time for flowers to grow will now be fully grown.).
3. The Date/Time option now becomes editable. Set it to the value you want and hit **OK**.
4. GREAT SCOTT! We have time traveled! You can of course go forward or backward in time (as long as it is not before the year 1970) and your game should update accordingly (e.g. certain *Super Mario Odyssey* moons that take time for flowers to grow will now be fully grown.).
+9
View File
@@ -16,6 +16,15 @@ The CPU must support FMA for an optimal gameplay experience. The GPU needs to su
If your GPU doesn't support or is just behind by a minor version, see Mesa environment variables below (*nix only).
## Releases and versions
- Stable releases/Versioned releases: Has a version number and it's the versions we expect 3rd party repositories to host (package managers and such), these are, well, stable, have low amount of regressions (wrt. to master and nightlies) and generally focus on "keeping things without regressions", recommended for the average user.
- RC releases: Release candidate, generally "less stable but still stable" versions.
- Full release: "The stablest possible you could get".
- Nightly: Builds done around 2PM UTC (if there are any changes), generally stable, but not recommended for the average user. These contain daily updates and may contain critical fixes for some games.
- Master: Unstable builds, can lead from a game working exceptionally fine to absolute crashing in some systems because someone forgot to check if NixOS or Solaris worked. These contain straight from the oven fixes, please don't use them unless you plan to contribute something! They're very experimental! Still 95% of the time it will work just fine.
- PR builds: Highly experimental builds, testers may grab from these. The average user should treat them the same as master builds, except sometimes they straight up don't build/work.
## User configuration
### Configuration directories
+11
View File
@@ -0,0 +1,11 @@
# User Handbook - Custom Firmware (CFW)
At the moment of writing, we do not support CFW such as Atmosphere, due to:
- Lacking the required LLE emulation capabilities to properly emulate the full firmware.
- Lack of implementation on some of the key internals.
- Nobody has bothered to do it (PRs always welcome!)
We do however, maintain HLE compatibility with the former mentioned CFW, applications that require Atmosphere to run will run fine in the emulator without any adjustments.
If they don't run - then that's a bug!
+2
View File
@@ -7,6 +7,7 @@ There are two main applications, an SDL2 based app (`eden-cli`) and a Qt based a
- `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
- `-f`: Use fullscreen.
- `-u <number>`: Select the index of the user to load as.
- `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
- `-qlaunch`: Launch QLaunch.
- `-setup`: Launch setup applet.
@@ -20,3 +21,4 @@ There are two main applications, an SDL2 based app (`eden-cli`) and a Qt based a
- `--program/-p`: Specify the program arguments to pass (optional).
- `--user/-u`: Specify the user index.
- `--version/-v`: Display version and quit.
- `--input-profile/-i`: Specifies input profile name to use (for player #0 only).
-49
View File
@@ -1,49 +0,0 @@
# Configuring Controller Profiles
Use this guide for when you want to configure specific controller settings to be reused.
**Click [Here](https://evilperson1337.notion.site/Configuring-Controller-Profiles-2be57c2edaf680eabc3ac8c333ec75c4) for a version of this guide with images & visual elements.**
---
### Pre-Requisites
- Eden Set Up and Configured
---
### Steps
1. Launch Eden and wait for it to load.
2. Navigate to *Emulation > Configure…*
3. Select **Controls** from the left-hand menu and configure your controller for the way you want it to be in game.
4. Select **New** and enter a name for the profile in the box that appears. Press **OK** to save the profile settings.
5. Select **OK** to close the settings menu.
## Setting Controller Profiles By Game
Use this guide when you want to set up specific controller profiles for specific games. This can be useful for certain games like *Captain Toad Treasure Tracker* where a blue dot appears in the middle of the screen when you have docked mode enabled, but not handheld mode.
**Click [Here](https://evilperson1337.notion.site/Setting-Controller-Profiles-By-Game-2b057c2edaf681658a57f0c199cb6083) for a version of this guide with images & visual elements.**
---
### Pre-Requisites
- Eden Emulator set up and fully configured
- Controller Profile Created
- See [*Configuring Controller Profiles*](./ControllerProfiles.md) for instructions on how to do this if needed.
---
### Steps
1. *Right-Click* the game you want to apply the profile to in the main window and select **Properties.**
2. Navigate to the **Input Profiles** tab in the window that appears. Drop down on *Player 1 profile* (or whatever player profile you want to apply it to) and select the profile you want.
<aside>
***NOTE***: You may have to resize the window to see all tabs, or press the arrows by the tabs to see **Input Profiles**.
</aside>
1. Click **OK** to apply the profile mapping.
2. Launch the game and confirm that the profile is applied, regardless of what the global configuration is.
+65
View File
@@ -0,0 +1,65 @@
# User Handbook - Controllers
Most of the controls should work out of the box. If not, please use a joystick calibrator to ensure it's not an issue with your own controller, for example:
- https://github.com/dkosmari/calibrate-joystick
## Using external controllers on the Steamdeck
In desktop mode ignore your pro controller/xbox contoller external controller and use **Steam Virtual Gamepad 0 as Player 1**. If you have multiple external controllers set **Player 2 to Steam Virtual Gamepad 1**. Steam app must not be closed on desktop mode.
Here's the annoying part of it. When waking up the steam deck from sleep try not to touch any button on the Steamdeck and turn on your external controller. Then open the Eden.AppImage. If you're lucky you can get your external controller to be position 0 and also Steam Virtual Gamepad 0 in desktop mode. If not that is ok too unless you need to configure player 1 to have gyro. You might need to repeat this to get your external controller as Steam Virtual Gamepad 0 so you can config Player 1 having gyro. You might be able to config player 1 to have gyro with the Steamdeck itself. Or you can also config player 1, 2, 3, etc, to have gyro somehow. Make sure they are all using Virtual Gamepads though.
Turn off controller then go to gaming mode. Try not to touch any buttons on the physical Steamdeck. When in gaming mode turn on the external controller. If lucky it will be assigned as Steam Virtual Gamepad 0. If not just use steam Gamemode feature to rearrange controller positions order.
Basically the Steamdeck or the external controller is fighting for position 0 and it depends on what is touched first after waking from sleep.
## Configuring Controller Profiles
Use this guide for when you want to configure specific controller settings to be reused.
**Click [Here](https://evilperson1337.notion.site/Configuring-Controller-Profiles-2be57c2edaf680eabc3ac8c333ec75c4) for a version of this guide with images & visual elements.**
---
#### Pre-Requisites
- Eden Set Up and Configured
---
#### Steps
1. Launch Eden and wait for it to load.
2. Navigate to *Emulation > Configure…*
3. Select **Controls** from the left-hand menu and configure your controller for the way you want it to be in game.
4. Select **New** and enter a name for the profile in the box that appears. Press **OK** to save the profile settings.
5. Select **OK** to close the settings menu.
### Setting Controller Profiles By Game
Use this guide when you want to set up specific controller profiles for specific games. This can be useful for certain games like *Captain Toad Treasure Tracker* where a blue dot appears in the middle of the screen when you have docked mode enabled, but not handheld mode.
**Click [Here](https://evilperson1337.notion.site/Setting-Controller-Profiles-By-Game-2b057c2edaf681658a57f0c199cb6083) for a version of this guide with images & visual elements.**
---
#### Pre-Requisites
- Eden Emulator set up and fully configured
- Controller Profile Created
- See [*Configuring Controller Profiles*](./ControllerProfiles.md) for instructions on how to do this if needed.
---
#### Steps
1. *Right-Click* the game you want to apply the profile to in the main window and select **Properties.**
2. Navigate to the **Input Profiles** tab in the window that appears. Drop down on *Player 1 profile* (or whatever player profile you want to apply it to) and select the profile you want.
<aside>
***NOTE***: You may have to resize the window to see all tabs, or press the arrows by the tabs to see **Input Profiles**.
</aside>
1. Click **OK** to apply the profile mapping.
2. Launch the game and confirm that the profile is applied, regardless of what the global configuration is.
+3 -1
View File
@@ -1,5 +1,7 @@
# User Handbook - Graphics
Graphical enhancements and visual quality improvments. This doesn't cover texture mods.
## Visual Enhancements
### Anti-aliasing
@@ -89,7 +91,7 @@ The OpenGL backend would invoke behaviour that would result in swarst/LLVMpipe w
### HaikuOS compatibility
HaikuOS bundles a Mesa library that doesn't support full core OpenGL 4.6 (required by the emulator). This leads to HaikuOS being one of the few computer platforms where Vulkan is the only available option for users. If OpenGL is desired, Mesa has to be built manually from source. For debugging purpouses `lavapipe` is recommended over the GPU driver; there is in-kernel support for NVIDIA cards through.
HaikuOS bundles a Mesa library that doesn't support full core OpenGL 4.6 (required by the emulator). This leads to HaikuOS being one of the few computer platforms where Vulkan is the only available option for users. If OpenGL is desired, Mesa has to be built manually from source. For debugging purposes `lavapipe` is recommended over the GPU driver; there is in-kernel support for NVIDIA cards through.
### Fixes for Windows 10 and above having "Device loss"
+206
View File
@@ -0,0 +1,206 @@
# User Handbook - Installing Mods
## General Notes
**Note:** When installing a mod, always read the mod's installation instructions.
This is especially important if a mod uses a framework such as **ARCropolis**, **Skyline**, or **Atmosphere plugins**. In those cases, follow the framework's instructions instead of using Eden's normal mod folder.
For example, **Super Smash Bros. Ultimate** uses such a framework. See the related section below for details.
---
# Installing Mods for Most Games
1. Right click a game in the game list.
2. Click **"Open Mod Data Location"**.
3. Extract the mod into that folder.
Each mod should be placed inside **its own subfolder**.
---
# Enabling or Disabling Mods
1. Right click the game in the game list.
2. Click **Configure Game**.
3. In the **Add-Ons** tab, enable or disable mods, updates, and DLC by ticking or unticking their boxes.
---
# Important Note About SD Card Paths
Some mods are designed for real Nintendo Switch consoles and refer to the **SD card root**.
The emulated SD card is located at:
```
%AppData%\eden\sdmc
```
Example:
```
Switch instruction: sd:/ultimate/mods
Eden equivalent: sdmc/ultimate/mods
```
---
# Framework-Based Mods (Super Smash Bros. Ultimate)
Some games require external mod frameworks instead of the built-in mod loader.
The most common example is **Super Smash Bros. Ultimate**.
These mods are installed directly to the **emulated SD card**, not the normal Eden mod folder.
---
# Installing the ARCropolis Modding Framework
**Note:** Some mod packs bundle ARCropolis with their installer (for example, Smash Ult-S).
---
## 1. Download ARCropolis
Download the latest release:
https://github.com/Raytwo/ARCropolis/releases/
---
## 2. Install ARCropolis
Extract the **`atmosphere`** folder into:
```
%AppData%\eden\sdmc
```
This is the **emulated SD card directory**.
Verify installation by checking that the following file exists:
```
sdmc\atmosphere\contents\01006A800016E000\romfs\skyline\plugins\libarcropolis.nro
```
---
## 3. Download Skyline
Download the latest Skyline release:
https://github.com/skyline-dev/skyline/releases
Skyline used to be bundled with ARCropolis but is now distributed separately to avoid compatibility issues caused by outdated bundled versions.
---
## 4. Install Skyline
Extract the **`exefs`** folder into:
```
sdmc\atmosphere\contents\01006A800016E000
```
The `exefs` folder should be **next to the `romfs` folder**.
Verify installation by checking that the following file exists:
```
%AppData%\eden\sdmc\atmosphere\contents\01006A800016E000\exefs\subsdk9
```
---
## 5. Launch the Game Once
Start the game and make sure you see the **ARCropolis version text on the title screen**.
This will also create the folders required for installing mods.
---
## 6. Install Smash Ultimate Mods
Install mods inside:
```
sdmc\ultimate\mods
```
Each mod must be placed inside **its own subfolder**.
Example:
```
sdmc\ultimate\mods\ExampleMod
```
---
# Troubleshooting
## ARCropolis text does not appear on startup
Check the following:
- `libarcropolis.nro` exists in:
```
sdmc\atmosphere\contents\01006A800016E000\romfs\skyline\plugins
```
- `subsdk9` exists in:
```
sdmc\atmosphere\contents\01006A800016E000\exefs
```
- Files were extracted to:
```
%AppData%\eden\sdmc
```
---
## Mods are not loading
Make sure mods are installed inside:
```
sdmc\ultimate\mods
```
Each mod must have its **own subfolder**.
Correct example:
```
sdmc\ultimate\mods\ExampleMod
```
Incorrect example:
```
sdmc\ultimate\mods\ExampleMod\ExampleMod
```
---
## Installing mods in the wrong folder
ARCropolis mods **do not go in Eden's normal mod folder**.
Do **not** install Smash mods here:
```
user\load\01006A800016E000
```
That folder is only used for traditional **RomFS mods**, not ARCropolis.
+19 -6
View File
@@ -4,35 +4,48 @@ The "FAQ".
This handbook is primarily aimed at the end-user - baking useful knowledge for enhancing their emulation experience.
A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/README.md).
## Basics
- **[The Basics](Basics.md)**
- **[Quickstart](./QuickStart.md)**
- **[Run On macOS](./RunOnMacOS.md)**
- **[Settings](./Settings.md)**
- **[Controllers](./Controllers.md)**
- **[Controller profiles](./Controllers.md#configuring-controller-profiles)**
- **[Audio](Audio.md)**
- **[Graphics](Graphics.md)**
- **[Installing Mods](./Mods.md)**
- **[Run On macOS](./RunOnMacOS.md)**
- **[Data, Savefiles and Storage](Storage.md)**
- **[Orphaned Profiles](Orphaned.md)**
- **[Troubleshooting](./Troubleshoot.md)**
- **[Using Amiibo](./UsingAmiibo.md)**
- **[Using Cheats](./UsingCheats.md)**
- **[Importing Saves](./ImportingSaves.md)**
- **[Add Eden to Steam ROM Manager](./AddEdenToSRM.md)**
- **[Add Games to Steam ROM Manager](./AddGamesToSRM.md)**
- **[Installing Atmosphere Mods](./InstallingAtmosphereMods.md)**
- **[Installing Updates & DLCs](./InstallingUpdatesDLC.md)**
- **[Controller Profiles](./ControllerProfiles.md)**
- **[Alter Date & Time](./AlterDateTime.md)**
## 3rd-party Integration
- **[Configuring Steam ROM Manager](./SteamROM.md)**
- **[Server hosting](ServerHosting.md)**
- **[Syncthing Guide](./SyncthingGuide.md)**
- **[Third Party](./ThirdParty.md)**
- **[Obtainium](./ThirdParty.md#configuring-obtainium)**
- **[ES-DE](./ThirdParty.md#configuring-es-de)**
- **[Mirrors](./ThirdParty.md#mirrors)**
- **[GameMode](./ThirdParty.md#configuring-gamemode)**
## Advanced
- **[Custom Firmware](./CFW.md)**
- **[How To Access Logs](./HowToAccessLogs.md)**
- **[Gyro Controls](./GyroControls.md)**
- **[Platforms and Architectures](Architectures.md)**
- **[Server hosting](ServerHosting.md)**
- **[Command Line](CommandLine.md)**
- **[Native Application Development](Native.md)**
- **[Adding Boolean Settings Toggles](AddingBooleanToggles.md)**
- **[Adding Debug Knobs](./AddingDebugKnobs.md)**
- **[Syncthing Guide](./SyncthingGuide.md)**
- **[Testing](Testing.md)**
+12 -4
View File
@@ -1,4 +1,12 @@
# Allowing Eden to Run on MacOS
# User Handbook - Run on macOS
Current macOS support is still experimental and very reliant on MoltenVK developments, plans have shifted to properly provide support for KosmicKrisp and similar new GPU endeavours, but macOS users still are bound to MoltenVK itself.
Users of macOS may wish to use [Asahi Linux](https://wiki.gentoo.org/wiki/Project:Asahi/Guide) for the rising KosmicKrisp support.
As of writing, neither macOS nor Asahi has support for NCE; additionally Asahi has extraneous paging bugs with fastmem.
## Allowing Eden to Run on MacOS
Use this guide when you need to allow Eden to run on a Mac system, but are being blocked by Apple Security policy.
@@ -6,19 +14,19 @@ Use this guide when you need to allow Eden to run on a Mac system, but are being
---
### Pre-Requisites
#### Pre-Requisites
- Permissions to modify settings in MacOS
---
## Why am I Seeing This?
### Why am I Seeing This?
Recent versions of MacOS (Catalina & newer) introduced the **Gatekeeper** security functionality, requiring software to be signed by Apple or a trusted (aka - paying) developer. If the signature isnt on the list of trusted ones, it will stop the program from executing and display the message above.
---
## Steps
### Steps
1. Open the *System Settings* panel.
2. Navigate to *Privacy & Security*.
+53
View File
@@ -0,0 +1,53 @@
# User Handbook - Settings
As the emulator continues to grow, so does the number of settings that come and go.
Most of the development adds new settings that enhance performance/compatibility, only to be removed later in newer versions due to newfound discoveries or because they were "a hacky workaround".
As such, this guide will NOT mention those kind of settings, we'd rather mention settings which have a long shelf time (i.e won't get removed in future releases) and are likely to be unchanged.
Some of the options are self explainatory, and they do exactly what they say they do (i.e "Pause when not in focus"); such options will be also skipped due to triviality.
## Foreword
Before touching the settings, please see the game boots with stock options. We try our best to ensure users can boot any game using the default settings. If they don't work, then you may try fiddling with options - but please, first use stock options.
## General
- `General/Force X11 as Graphics Backend`: Wayland on *NIX has prominent issues that are unlikely to be resolved; the kind that are "not our fault, it's Wayland issue", this "temporary" hack forces X11 as the backend, regardless of the desktop manager's default.
- `General/Enable Gamemode`: This only does anything when you have Feral Interactive's Gamemode library installed somewhere, if you do, this will help boost FPS by telling the OS to explicitly prioritize *this* application for "gaming" - only for *NIX systems.
- `Hotkeys`: Deceptively to remove a hotkey you must right click and a menu will appear to remove that specific hotkey.
- `UI/Language`: Changes language *of the interface* NOT the emulated program!
- `Debug/Enable Auto Stub`: May help to "fix" some games by just lying and saying that everything they do returns "success" instead of outright crashing for any function/service that is NOT implemented.
- `Debug/Show log in console`: Does as said, note that the program may need to be reopened (Windows) for changes to take effect.
- `Debug/Flush log output`: Classically, every write to the log is "buffered", that is, changes aren't written to the disk UNTIL the program has decided it is time to write, until then it keeps data in a buffer which resides on RAM. If the program crashes, the OS will automatically discard said buffer (any RAM associated with a dead process is automatically discarded/reused for some other purpose); this means critical data may not be logged to the disk on time, which may lead to missing log lines. Use this if you're wanting to remove that factor when debugging, sometimes a hard crash may "eat" some of the log lines IF this option isn't enabled.
- `Debug/Disable Macro HLE:` The emulator has HLE emulation of macro programs for Maxwell, this means that some details are purpousefully skipped; this option forces all macro programs to be ran without skipping anything.
## System
- `System/RNG Seed`: Set to 0 (and uncheck) to disable ASLR systemwide (this makes mods like CTGP to stop working); by default it enables ASLR to replicate console behaviour.
- `Network/Enable Airplane Mode`: Enable this if a game is crashing before loading AND the logs mention anything related to "web" or "internet" services.
## CPU
- `CPU/Virtual table bouncing`: Some games have the tendency to crash on loading due to an indirect bad jump (Pokemon ZA being the worst offender); this option lies to the game and tells it to just pretend it never executed a given function. This is fine for most casual users, but developers of switch applications **must** disable this. This temporary "hack" should hopefully be gone in 6-7 months from now on.
- `Fastmem`, aka. `CPU/Enable Host MMU`: Enables "fastmem"; a detailed description of fastmem can be found [here](../dynarmic/Design.md#fast-memory-fastmem).
- `CPU/Unsafe FMA`: Enables deliberate innacurate FMA behaviour which may affect how FMA returns any given operation - this may introduce tiny floating point errors which can cascade in sensitive code (i.e FFmpeg).
- `CPU/Faster FRSQRTE and FRECPE`: Introduces accuracy errors on square root and reciprocals in exchange for less checks - this introduces inaccuracies with some cases but it's mostly safe.
- `CPU/Faster ASIMD Instructions`: Skips rounding mode checks for ARM ASIMD instructions - this means some code dpeending on these rounding modes may misbehave.
- `CPU/Disable address space checks`: Before each memory access, the emulator checks the address is in range, if not it faults; this option makes it so the emulator skips the check entirely (which may be expensive for a myriad of reasons). However at the same time this allows the guest program to "break out" of the emulation context by writing to arbitrary addresses.
- `CPU/Ignore global monitor`: This relies on a quirk present on x86 to avoid the ARM global monitor emulation, this may increase performance in mutex-heavy contexts (i.e games waiting for next frames or such); but also can cause deadlocks and fun to debug issues.
It is important to note the majority of precision-reducing instructions do not benefit cases where they are not used, which means the performance gains will vary per game.
# Graphics
See also [an extended breakdown of some options](./Graphics.md).
- `Extras/Extended Dynamic State` and `Extras/Vertex Input Dynamic State`: These Vulkan extensions essentially allow you to reuse the same pipeline but just change the state between calls (so called "dynamic state"); the "extended" levels signifies how much state can be placed on this "dynamic" range, for example the amount of depth culling to use can be placed on the dynamic state, avoiding costly reloads and flushes. While this by itself is a fine option, SOME vendors (notably PowerVR and Mali) have problems with anything related to EDS3. EDS3 contains EDS2, and EDS2 contains EDS1. Essentially this means more extended data the driver has to keep track of, at the benefit of avoiding costly flushes.
- `Advanced/Use persistent cache`: This saves compiled shaders onto the disk, independent of any driver's own disk saved shaders (yes, some drivers, notably NVIDIA, save a secondary shader cache onto disk) - disable this only if you're debugging or working on the GPU backend. This option is meant to massively help to reduce shader stutters (after playing for one session that compiles them).
- `Advanced/Use Vulkan pipeline cache`: This is NOT the same as `Use persistent cache`; it's a separate flag that tells the Vulkan backend to create pipeline caches, which are a detail that can be used to massively improve performance and remove pipeline creation overhead. This is a Vulkan feature.
## Controls
See [controllers](./Controllers.md).
@@ -1,4 +1,6 @@
# Importing Eden into Steam with Steam Rom Manager
# User Handbook - Configuring Steam ROM Manager
## Importing Eden into Steam with Steam Rom Manager
Use this when you want to import the Eden AppImage into your Steam Library along with artwork using *Steam ROM Manager.*
@@ -6,7 +8,7 @@ Use this when you want to import the Eden AppImage into your Steam Library along
---
### Pre-Requisites
#### Pre-Requisites
- Eden set up and configured
- Internet Connection
@@ -14,9 +16,9 @@ Use this when you want to import the Eden AppImage into your Steam Library along
---
## Steps
### Steps
### Initial Setup
#### Initial Setup
1. Press the **STEAM** button and then go to *Power → Switch to Desktop* to enter the Desktop mode.
@@ -24,14 +26,14 @@ Use this when you want to import the Eden AppImage into your Steam Library along
---
### Manual Installation
#### Manual Installation
1. Open the *Discover Store* and search for *Steam ROM Manager.*
2. Select the **Install** button to install the program.
---
### Installing Through *EmuDeck*
#### Installing Through *EmuDeck*
<aside>
@@ -45,9 +47,9 @@ Use this when you want to import the Eden AppImage into your Steam Library along
---
### Adding Eden into *Steam ROM Manager*
#### Adding Eden into *Steam ROM Manager*
### EmuDeck Users
#### EmuDeck Users
EmuDeck will automatically create an *Emulators - Emulators* parser for ***Steam ROM Manager*** that uses shell scripts to launch them. We will follow this convention.
@@ -87,7 +89,7 @@ EmuDeck will automatically create an *Emulators - Emulators* parser for ***Steam
---
### Non-EmuDeck Users
#### Non-EmuDeck Users
We will need to create a new parser for the Emulators. Unlike with the EmuDeck model, we will have the parser look for AppImages.
@@ -126,7 +128,7 @@ We will need to create a new parser for the Emulators. Unlike with the EmuDeck
---
### Adding Eden to Steam
#### Adding Eden to Steam
Now that we have the parser or shell script created, we can actually add it to Steam.
@@ -137,7 +139,7 @@ Now that we have the parser or shell script created, we can actually add it to S
---
### Correcting a Mismatch
#### Correcting a Mismatch
If the emulator is not identified correctly, you may need to tell *Steam ROM Manager* what the game is manually.
@@ -147,7 +149,7 @@ Now that we have the parser or shell script created, we can actually add it to S
---
### Excluding Matches
#### Excluding Matches
You may want to tell Steam ROM Manager to ignore some files that it finds in the directory. This is how you do so.
@@ -159,4 +161,105 @@ Now that we have the parser or shell script created, we can actually add it to S
5. The program will now start writing the entries into the Steam Library. You should get pop up notifications of the progress, but you can monitor the progress by selecting the **Log** on the left-hand side if needed.
6. Restart Steam to have the changes take effect. Check your library to ensure that your games are there, in a category if you defined one in the parser.
7. Try to launch the Emulator from Steam and ensure everything is working. You are now good to go.
7. Try to launch the Emulator from Steam and ensure everything is working. You are now good to go.
## Importing Games into Steam with Steam Rom Manager
Use this when you want to import your games inside Eden into Steam to launch with artwork from Steam Game Mode without needing to launch Eden first.
**Click [Here](https://evilperson1337.notion.site/Importing-Games-into-Steam-with-Steam-Rom-Manager-2b757c2edaf680d7a491c92b138f1fcc) for a version of this guide with images & visual elements.**
---
#### Pre-Requisites
- Steam Deck Set up and Configured
- Eden set up and Configured
- Internet Access
---
### Steps
1. Press the **STEAM** button and then go to *Power → Switch to Desktop* to enter the Desktop mode.
1. Install ***Steam ROM Manager***, there are 2 ways you can accomplish this, either manually or through [*EmuDeck*](https://www.emudeck.com/#downloads).
---
#### Manual Installation
1. Open the *Discover Store* and search for *Steam ROM Manager.*
2. Select the **Install** button to install the program.
---
#### Installing Through *EmuDeck*
<aside>
***NOTE***: This assumes you have already set up EmuDeck, if not - just run through the guided installation and select *Steam ROM Manager* as one of the options.
</aside>
1. Open **EmuDeck**, then navigate to *Manage Emulators.*
2. Scroll down to the bottom of the page to the *Manage your Tools & Frontends* section. Click **Steam ROM Manager**.
3. Click the **Install** button on the right hand side to install it.
---
2. Open the Start Menu and Launch ***Steam ROM Manager***
1. The program will now launch and show you a window with parsers.
<aside>
***TIP***: Your layout may look different depending on how you installed *Steam ROM Manager*. You may need to go to **Settings → Theme** and change it to *Classic* to follow along.
</aside>
2. Switch off all Parsers by hitting the *Toggle Parsers* switch.
3. Scroll down the list on the left-hand side and look for a parser called *Nintendo Switch - Eden* and switch it on. This parser may not exist depending on how you installed *Steam ROM Manager* (EmuDeck creates it for you). Follow these steps to create it if it is missing.
---
#### Creating the Eden Parser
1. Select Create Parser and in the *Community Presets* option look for **Nintendo Switch - Yuzu**.
2. Change the **Parser title** from *Nintendo Switch - Yuzu* to *Nintendo Switch - Eden.*
3. Hit the **Browse** option under the *ROMs directory* section. Select the directory containing your Switch ROMs.
4. Under *Steam collections*, you can add a Steam category name. This just organizes the games under a common category in your Steam Library, this is optional but recommended.
5. Scroll down slightly to the **Executable Configuration → Executable**, select **Browse** and select the Eden AppImage.
6. Leave everything else the same and hit **Save** to save the parser.
---
4. Click the Eden parser to view the options on the right, select **Test** at the bottom of the screen to ensure that *Steam ROM Manager* detects your games correctly.
1. *Steam ROM Manager* will start to scan the specified ROMs directory and match them to games. Look over the results to ensure they are accurate. If you do not see any entries - check your parsers ROMs directory field.
1. When you are happy with the results, click the **Add Games****Parse** to start the actual Parsing.
1. The program will now identify the games and pull artwork from [*SteamGridDB*](https://www.steamgriddb.com/).
2. Review the game matches and ensure everything is there.
---
#### Correcting a Mismatch
If the game is not identified correctly, you may need to tell *Steam ROM Manager* what the game is manually.
1. Hover over the game card and click the magnifying glass icon.
2. Search for the game on the *Search SteamGridDB* section and scroll through the results, selecting the one you want.
3. Ensure the *Name* and *Game ID* update in the **Per-App Exceptions** and press **Save and close**. The game should now update.
---
#### Excluding Matches
You may want to tell Steam ROM Manager to ignore some files (updates/DLC/etc.) that it finds in the directory. This is how you do so.
1. Hit the **Exclude Games** button in the bottom right.
2. Deselect the game you want to exclude, the poster artwork should go dim and the **Number Excluded** number should increment up. Repeat with any other exclusions you want to add.
3. Hit **Save Excludes** when you are happy with your selections.
---
3. When you are happy with the results, select **Save to Steam** to save the results.
1. The program will now start writing the entries into the Steam Library. You should get pop up notifications of the progress, but you can monitor the progress by selecting the **Log** on the left-hand side if needed.
2. Restart Steam to have the changes take effect. Check your library to ensure that your games are there, in a category if you defined one in the parser.
3. Try to launch a game and ensure everything is working. You are now good to go.
+80 -20
View File
@@ -1,39 +1,99 @@
# User Handbook - Testing
While this is mainly aimed for testers - normal users can benefit from these guidelines to make their life easier when trying to outline and/or report an issue.
## Getting logs
In order to get more information, you can find logs in the following location:
## How to Test a PR Against the Based Master When Issues Arise
# Testing
When you're testing a pull request (PR) and encounter unexpected behavior, it's important to determine whether the issue was introduced by the PR or if it already exists in the base code. To do this, compare the behavior against the based master branch.
Even before an issue occurs, it is best practice to keep the same settings and delete the shader cache. Using an already made shader cache can make the PR look like it is having a regression in some rare cases.
### What to Do When Something Seems Off
Try not to test PRs which are for documentation or extremely trivial changes (like a PR that changes the app icon), unless you really want to; generally avoid any PRs marked `[docs]`.
If a PR specifies it is for a given platform (i.e `linux`) then just test on Linux. If it says `NCE` then test on Android and Linux ARM64 (Raspberry Pi and such). macOS fixes may also affect Asahi, test that if you can too.
You may also build artifacts yourself, be aware that the resulting builds are NOT the same as those from CI, because of package versioning and build environment differences. One famous example is FFmpeg randomly breaking on many Arch distros due to packaging differences.
## Quickstart
Think of the source code as a "tree", with the "trunk" of that tree being our `master` branch, any other branches are PRs or separate development branches, only our stable releases pull from `master` - all other branches are considered unstable and aren't recommended to pull from unless you're testing multiple branches at once.
Here's some terminology you may want to familiarize yourself with:
- PR: Pull request, a change in the codebase; from which the author of said change (the programmer) requests a pull of that branch into master (make it so the new code makes it into a release basically).
- Bisect: Bilinear method of searching regressions, some regressions may be sporadic and can't be bisected, but the overwhelming majority are.
- WIP: Work-in-progress.
- Regression: A new bug/glitch caused by new code, i.e "Zelda broke in android after commit xyz".
- Master: The "root" branch, this is where all merged code goes to, traditionally called `main`, `trunk` or just `master`, it contains all the code that eventually make it to stable releases.
- `HEAD`: Latest commit in a given branch, `HEAD` of `master` is the latest commit on branch `master`.
- `origin`: The default "remote", basically the URL from where git is located at, for most of the time that location is https://git.eden-emu.dev/eden-emu/eden.
## Testing checklist
For regressions/bugs from PRs or commits:
- [ ] Occurs in master?
- If it occurs on master:
- [ ] Occurs on previous stable release? (before this particular PR).
- If it occurs on previous stable release:
- [ ] Occurs on previous-previous stable release?
- And so on and so forth... some bugs come from way before Eden was even conceived.
- Otherwise, try bisecting between the previous stable release AND the latest `HEAD` of master
- [ ] Occurs in given commit?
- [ ] Occurs in PR?
- If it occurs on PR:
- [ ] Bisected PR? (if it has commits)
- [ ] Found bisected commit?
If an issue sporadically appears, try to do multiple runs, try if possible, to count the number of times it has failed and the number of times it has "worked just fine"; say it worked 3 times but failed 1. then there is a 1/4th chance every run that the issue is replicated - so every bisect step would require 4 runs to ensure there is atleast a chance of triggering the bug.
## What to do when something seems off
If you notice something odd during testing:
- Reproduce the issue using the based master branch.
- Observe whether the same behavior occurs.
### Two Possible Outcomes
From there onwards there can be two possible outcomes:
- If the issue exists in the based master: This means the problem was already present before the PR. The PR most likely did not introduce the regression.
- If the issue does not exist in the based master: This suggests the PR most likely introduced the regression and needs further investigation.
### Report your findings
## Reporting Your Findings
When you report your results:
- Clearly state whether the behavior was observed in the based master.
- Indicate whether the result is good (expected behavior) or bad (unexpected or broken behavior). Without mentioning if your post/report/log is good or bad it may confuse the Developer of the PR.
- Example:
```
1. "Tested on based master — issue not present. Bad result for PR, likely regression introduced."
2. "Tested on based master issue already present. Good result for PR, not a regression."
```
- Indicate whether the result is good (expected behavior) or bad (unexpected or broken behavior). Without mentioning if your post/report/log is good or bad it may confuse the developer of the PR.
For example:
1. "Bad result for PR: Tested on based master - issue not present. Likely regression introduced."
2. "Good result for PR: Tested on based master - issue already present. Not a regression."
This approach helps maintain clarity and accountability in the testing process and ensures regressions are caught and addressed efficiently.
If the behavior seems normal for a certain game/feature then it may not be always required to check against the based master.
This approach helps maintain clarity and accountability in the testing process and ensures regressions are caught and addressed efficiently. If the behavior seems normal for a certain game/feature then it may not be always required to check against the based master.
If a master build for the PR' based master does not exist. It will be helpful to just test past and future builds nearby. That would help with gathering more information about the problem.
**Always include [debugging info](../Debug.md) as needed**.
**Always include [debugging info](../Debug.md) as needed**.
## Bisecting
One happy reminder, when testing, *know how to bisect!*
Say you're trying to find an issue between 1st of Jan and 8th of Jan, you can search by dividing "in half" the time between each commit:
- Check for 4th of Jan
- If 4th of Jan is "working" then the issue must be in the future
- So then check 6th of Jan
- If 6th of Jan isn't working then the issue must be in the past
- So then check 5th of Jan
- If 5th of Jan worked, then the issue starts at 6th of Jan
The faulty commit then, is 6th of Jan. This is called bisection https://git-scm.com/docs/git-bisect
## Notes
- PR's marked with **WIP** do NOT need to be tested unless explicitly asked (check the git in case)
- Sometimes license checks may fail, hover over the build icon to see if builds did succeed, as the CI will push builds even if license checks fail.
- All open PRs can be viewed [here](https://git.eden-emu.dev/eden-emu/eden/pulls/).
- If site is down use one of the [mirrors](./user/ThirdParty.md#mirrors).
+65 -1
View File
@@ -4,6 +4,70 @@ The Eden emulator by itself lacks some functionality - or otherwise requires ext
While most of the links mentioned in this guide are relatively "safe"; we urge users to use their due diligence and appropriatedly verify the integrity of all files downloaded and ensure they're not compromised.
- [Nightly Eden builds](https://github.com/pflyly/eden-nightly)
- [NixOS Eden Flake](https://github.com/Grantimatter/eden-flake)
- [ES-DE Frontend Support](https://github.com/GlazedBelmont/es-de-android-custom-systems)
## Mirrors
The main origin repository is always at https://git.eden-emu.dev/eden-emu/eden.
- https://github.com/eden-emulator/mirror
- https://git.crueter.xyz/mirror/eden
- https://collective.taymaerz.de/eden/eden
Other mirrors obviously exist on the internet, but we can't guarantee their reliability and/or availability.
If you're someone wanting to make a mirror, simply setup forgejo and automatically mirror from the origin repository. Or you could mirror a mirror to save us bandwidth... your choice!
## Configuring Obtainium
Very nice handy app, here's a quick rundown how to configure:
1. Copy the URL: https://git.eden-emu.dev/eden-emu/eden/ (or one of your favourite mirrors)
2. Open Obtainium and tap `Add App`.
3. Paste the URL into the `App Source URL` field.
4. Override Source: Look for the `Override Source` dropdown menu and select `Forgejo (Codeberg)`.
5. Click `Add:` Obtainium should now be able to parse the releases and find the APK files.
Note: Even though the site isn't Codeberg, it uses the same Forgejo/Gitea backend, and this setting tells Obtainium how to read the release data.
## Configuring ES-DE
### Method 1
1. Download ZIP from [here](https://github.com/GlazedBelmont/es-de-android-custom-systems)
2. Unzip the file and extract `es_systems.xml` and `es_find_rules.xml` to `\Odin2\Internal shared storage\ES-DE\custom_systems`.
3. Press `Start -> Other Settings -> Alternative Emulators` and set it to Eden (Standalone).
### Method 2
1. Navigate to `\Odin2\Internal shared storage\ES-DE\custom_systems`.
2. Add this to your `es_find_rules.xml`:
```xml
<!-- Standard aka. normal release -->
<emulator name="EDEN">
<rule type="androidpackage">
<entry>dev.eden.eden_emulator/org.yuzu.yuzu_emu.activities.EmulationActivity</entry>
</rule>
</emulator>
<!-- Optimized -->
<emulator name="EDEN">
<rule type="androidpackage">
<entry>com.miHoYo.Yuanshen/org.yuzu.yuzu_emu.activities.EmulationActivity</entry>
</rule>
</emulator>
```
3. Add this line of text to your `es_systems.xml` underneath where the rest of your switch system entries are:
```xml
<command label="Eden (Standalone)">%EMULATOR_EDEN% %ACTION%=android.nfc.action.TECH_DISCOVERED %DATA%=%ROMPROVIDER%</command>
```
## Configuring GameMode
There is a checkbox to enable gamemode automatically. The `libgamemode.so` library must be findable on the standard `LD_LIBRARY_PATH` otherwise it will not properly be enabled. If for whatever reason it doesn't work, see [Arch wiki: GameMode](https://wiki.archlinux.org/title/GameMode) for more info.
You may launch the emulator directly via the wrapper `gamemode <program>`, and things should work out of the box.
+14 -13
View File
@@ -9,7 +9,7 @@
},
"sirit": {
"repo": "eden-emulator/sirit",
"git_version": "1.0.3",
"git_version": "1.0.4",
"tag": "v%VERSION%",
"artifact": "sirit-source-%VERSION%.tar.zst",
"hash_suffix": "sha512sum",
@@ -28,11 +28,12 @@
"httplib": {
"repo": "yhirose/cpp-httplib",
"tag": "v%VERSION%",
"hash": "a229e24cca4afe78e5c0aa2e482f15108ac34101fd8dbd927365f15e8c37dec4de38c5277d635017d692a5b320e1b929f8bfcc076f52b8e4dcdab8fe53bfdf2e",
"git_version": "0.30.1",
"hash": "5efa8140aadffe105dcf39935b732476e95755f6c7473ada3d0b64df2bc02c557633ae3948a25b45e1cf67e89a3ff6329fb30362e4ac033b9a1d1e453aa2eded",
"git_version": "0.37.0",
"find_args": "MODULE GLOBAL",
"patches": [
"0001-mingw.patch"
"0001-mingw.patch",
"0002-fix-zstd.patch"
],
"options": [
"HTTPLIB_REQUIRE_OPENSSL ON"
@@ -55,8 +56,8 @@
"package": "xbyak",
"repo": "herumi/xbyak",
"tag": "v%VERSION%",
"hash": "ac333d7bea1d61865bebebb116201a58db431946aa2f11aa042ef5795c390ff30af4d6c90ed3b3d24443a1d430703b08f14fc13b2fa405c155a241456ed78a47",
"git_version": "7.33.2"
"hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a",
"git_version": "7.35.2"
},
"oaknut": {
"repo": "eden-emulator/oaknut",
@@ -139,16 +140,16 @@
"package": "SDL2",
"name": "SDL2",
"repo": "crueter-ci/SDL2",
"version": "2.32.10-cf5dabd6ea",
"version": "2.32.10-3c28e8ecc0",
"min_version": "2.26.4"
},
"catch2": {
"package": "Catch2",
"repo": "catchorg/Catch2",
"tag": "v%VERSION%",
"hash": "acb3f463a7404d6a3bce52e474075cdadf9bb241d93feaf147c182d756e5a2f8bd412f4658ca186d15ab8fed36fc587d79ec311f55642d8e4ded16df9e213656",
"hash": "7eea385d79d88a5690cde131fe7ccda97d5c54ea09d6f515000d7bf07c828809d61c1ac99912c1ee507cf933f61c1c47ecdcc45df7850ffa82714034b0fccf35",
"version": "3.0.1",
"git_version": "3.12.0",
"git_version": "3.13.0",
"patches": [
"0001-solaris-isnan-fix.patch"
]
@@ -256,15 +257,15 @@
"repo": "KhronosGroup/Vulkan-Headers",
"package": "VulkanHeaders",
"version": "1.4.317",
"hash": "26e0ad8fa34ab65a91ca62ddc54cc4410d209a94f64f2817dcdb8061dc621539a4262eab6387e9b9aa421db3dbf2cf8e2a4b041b696d0d03746bae1f25191272",
"git_version": "1.4.342",
"hash": "d2846ea228415772645eea4b52a9efd33e6a563043dd3de059e798be6391a8f0ca089f455ae420ff22574939ed0f48ed7c6ff3d5a9987d5231dbf3b3f89b484b",
"git_version": "1.4.345",
"tag": "v%VERSION%"
},
"vulkan-utility-libraries": {
"repo": "KhronosGroup/Vulkan-Utility-Libraries",
"package": "VulkanUtilityLibraries",
"hash": "8147370f964fd82c315d6bb89adeda30186098427bf3efaa641d36282d42a263f31e96e4586bfd7ae0410ff015379c19aa4512ba160630444d3d8553afd1ec14",
"git_version": "1.4.342",
"hash": "114f6b237a6dcba923ccc576befb5dea3f1c9b3a30de7dc741f234a831d1c2d52d8a224afb37dd57dffca67ac0df461eaaab6a5ab5e503b393f91c166680c3e1",
"git_version": "1.4.345",
"tag": "v%VERSION%"
},
"frozen": {
+1 -1
View File
@@ -1,5 +1,6 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
@@ -241,7 +242,6 @@ if (YUZU_CMD)
endif()
if (ENABLE_QT)
add_definitions(-DYUZU_QT_WIDGETS)
add_subdirectory(qt_common)
add_subdirectory(yuzu)
endif()
@@ -73,6 +73,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:theme="@style/Theme.Yuzu.Main"
android:label="@string/preferences_settings"/>
<activity
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreenActivity"
android:theme="@style/Theme.Yuzu.Main"
android:label="@string/preferences_settings"/>
<activity
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
android:theme="@style/Theme.Yuzu.Main"
@@ -40,11 +40,21 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
}
}
val deleteAction = {
addonViewModel.setAddonToDelete(model)
val canDelete = model.isRemovable
binding.deleteCard.isEnabled = canDelete
binding.buttonDelete.isEnabled = canDelete
binding.deleteCard.alpha = if (canDelete) 1f else 0.38f
if (canDelete) {
val deleteAction = {
addonViewModel.setAddonToDelete(model)
}
binding.deleteCard.setOnClickListener { deleteAction() }
binding.buttonDelete.setOnClickListener { deleteAction() }
} else {
binding.deleteCard.setOnClickListener(null)
binding.buttonDelete.setOnClickListener(null)
}
binding.deleteCard.setOnClickListener { deleteAction() }
binding.buttonDelete.setOnClickListener { deleteAction() }
}
}
}
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -6,10 +9,10 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.FragmentActivity
import androidx.navigation.findNavController
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -67,8 +70,13 @@ class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) :
title = YuzuApplication.appContext.getString(applet.titleId),
path = appletPath
)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
binding.root.findNavController().navigate(action)
binding.root.findNavController().navigate(
R.id.action_global_emulationActivity,
bundleOf(
"game" to appletGame,
"custom" to false
)
)
}
}
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -10,6 +10,8 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.button.MaterialButton
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.GameProperty
@@ -89,29 +91,33 @@ class GamePropertiesAdapter(
val hasVisibleActions = submenuProperty.secondaryActions?.any { it.isShown } == true
binding.layoutSecondaryActions.removeAllViews()
binding.dividerSecondaryActions.setVisible(false)
if (hasVisibleActions) {
binding.dividerSecondaryActions.setVisible(true)
binding.layoutSecondaryActions.setVisible(true)
submenuProperty.secondaryActions!!.forEach { secondaryAction ->
if (secondaryAction.isShown) {
val button = com.google.android.material.button.MaterialButton(
binding.root.context,
null,
com.google.android.material.R.attr.materialButtonOutlinedStyle
).apply {
setIconResource(secondaryAction.iconId)
iconSize = (18 * binding.root.context.resources.displayMetrics.density).toInt()
text = binding.root.context.getString(secondaryAction.descriptionId)
contentDescription = binding.root.context.getString(secondaryAction.descriptionId)
setOnClickListener { secondaryAction.action.invoke() }
}
binding.layoutSecondaryActions.addView(button)
val visibleActions = submenuProperty.secondaryActions!!.filter { it.isShown }
val inflater = LayoutInflater.from(binding.root.context)
visibleActions.forEachIndexed { index, secondaryAction ->
val button = inflater.inflate(
R.layout.item_secondary_action_button,
binding.layoutSecondaryActions,
false
) as MaterialButton
button.setIconResource(secondaryAction.iconId)
button.text = ""
button.contentDescription = binding.root.context
.getString(secondaryAction.descriptionId)
button.tooltipText = binding.root.context
.getString(secondaryAction.descriptionId)
if (index == visibleActions.lastIndex) {
(button.layoutParams as ViewGroup.MarginLayoutParams).marginEnd = 0
}
button.setOnClickListener { secondaryAction.action.invoke() }
binding.layoutSecondaryActions.addView(button)
}
} else {
binding.dividerSecondaryActions.setVisible(false)
binding.layoutSecondaryActions.setVisible(false)
}
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.dialogs
@@ -14,6 +14,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout
import androidx.core.content.getSystemService
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.DividerItemDecoration
@@ -58,6 +59,30 @@ class LobbyBrowser(context: Context) : BottomSheetDialog(context) {
setupSearchBar()
}
override fun onStart() {
super.onStart()
window?.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
val bottomSheet =
findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
if (bottomSheet != null) {
bottomSheet.layoutParams = bottomSheet.layoutParams.apply {
width = ViewGroup.LayoutParams.MATCH_PARENT
height = ViewGroup.LayoutParams.MATCH_PARENT
}
bottomSheet.requestLayout()
}
behavior.isFitToContents = false
behavior.expandedOffset = 0
behavior.skipCollapsed = true
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
private fun setupRecyclerView() {
adapter = LobbyRoomAdapter { room -> handleRoomSelection(room) }
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
@@ -27,7 +27,7 @@ object Settings {
SECTION_APP_SETTINGS(R.string.app_settings),
SECTION_CUSTOM_PATHS(R.string.preferences_custom_paths),
SECTION_DEBUG(R.string.preferences_debug),
SECTION_FREEDRENO(R.string.gpu_driver_settings),
SECTION_FREEDRENO(R.string.freedreno_settings_title),
SECTION_APPLETS(R.string.applets_menu);
}
@@ -27,6 +27,20 @@ class GpuUnswizzleSetting(
override val isSaveable = true
override val isRuntimeModifiable = true
override val isSwitchable = true
override val pairedSettingKey: String = ""
override var global: Boolean
get() {
return BooleanSetting.GPU_UNSWIZZLE_ENABLED.global &&
IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.global &&
IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.global &&
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.global
}
set(value) {
BooleanSetting.GPU_UNSWIZZLE_ENABLED.global = value
IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.global = value
IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.global = value
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.global = value
}
override fun getValueAsString(needsGlobal: Boolean): String = "combined"
override fun reset() {
BooleanSetting.GPU_UNSWIZZLE_ENABLED.reset()
@@ -72,4 +86,4 @@ class GpuUnswizzleSetting(
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.setInt(value)
fun reset() = setting.reset()
}
}
@@ -989,6 +989,7 @@ abstract class SettingsItem(
override val isRuntimeModifiable: Boolean = false
override val defaultValue: Boolean = true
override val isSwitchable: Boolean = true
override val pairedSettingKey: String = ""
override var global: Boolean
get() {
return BooleanSetting.FASTMEM.global &&
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -98,23 +98,8 @@ class SettingsFragment : Fragment() {
activity
)
binding.toolbarSettingsLayout.title = if (args.menuTag == Settings.MenuTag.SECTION_ROOT &&
args.game != null
) {
args.game!!.title
} else {
when (args.menuTag) {
Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> Settings.getPlayerString(1)
Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> Settings.getPlayerString(2)
Settings.MenuTag.SECTION_INPUT_PLAYER_THREE -> Settings.getPlayerString(3)
Settings.MenuTag.SECTION_INPUT_PLAYER_FOUR -> Settings.getPlayerString(4)
Settings.MenuTag.SECTION_INPUT_PLAYER_FIVE -> Settings.getPlayerString(5)
Settings.MenuTag.SECTION_INPUT_PLAYER_SIX -> Settings.getPlayerString(6)
Settings.MenuTag.SECTION_INPUT_PLAYER_SEVEN -> Settings.getPlayerString(7)
Settings.MenuTag.SECTION_INPUT_PLAYER_EIGHT -> Settings.getPlayerString(8)
else -> getString(args.menuTag.titleId)
}
}
val toolbarTitle = resolveToolbarTitle()
configureToolbar(toolbarTitle)
binding.listSettings.apply {
adapter = settingsAdapter
@@ -193,11 +178,9 @@ class SettingsFragment : Fragment() {
}
presenter.onViewCreated()
setInsets()
}
private fun getPlayerIndex(): Int =
private fun getPlayerIndex(): Int =
when (args.menuTag) {
Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> 0
Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> 1
@@ -210,6 +193,27 @@ class SettingsFragment : Fragment() {
else -> -1
}
private fun resolveToolbarTitle(): String {
if (args.menuTag == Settings.MenuTag.SECTION_ROOT && args.game != null) {
return args.game!!.title
}
return when (args.menuTag) {
Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> Settings.getPlayerString(1)
Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> Settings.getPlayerString(2)
Settings.MenuTag.SECTION_INPUT_PLAYER_THREE -> Settings.getPlayerString(3)
Settings.MenuTag.SECTION_INPUT_PLAYER_FOUR -> Settings.getPlayerString(4)
Settings.MenuTag.SECTION_INPUT_PLAYER_FIVE -> Settings.getPlayerString(5)
Settings.MenuTag.SECTION_INPUT_PLAYER_SIX -> Settings.getPlayerString(6)
Settings.MenuTag.SECTION_INPUT_PLAYER_SEVEN -> Settings.getPlayerString(7)
Settings.MenuTag.SECTION_INPUT_PLAYER_EIGHT -> Settings.getPlayerString(8)
else -> getString(args.menuTag.titleId)
}
}
private fun configureToolbar(title: String) {
binding.toolbarSettings.title = title
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.annotation.SuppressLint
import android.os.Build
import android.widget.Toast
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -27,11 +26,9 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import androidx.core.content.edit
import androidx.fragment.app.FragmentActivity
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -183,16 +180,6 @@ class SettingsFragmentPresenter(
menuKey = MenuTag.SECTION_DEBUG
)
)
if (GpuDriverHelper.isAdrenoGpu() && !NativeConfig.isPerGameConfigLoaded()) {
add(
SubmenuSetting(
titleId = R.string.gpu_driver_settings,
descriptionId = R.string.freedreno_settings_title,
iconId = R.drawable.ic_graphics,
menuKey = MenuTag.SECTION_FREEDRENO
)
)
}
add(
SubmenuSetting(
titleId = R.string.applets_menu,
@@ -1084,27 +1071,6 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.theme_and_color))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
add(
SingleChoiceSetting(
theme,
titleId = R.string.change_app_theme,
choicesId = R.array.themeEntriesA12,
valuesId = R.array.themeValuesA12
)
)
} else {
add(
SingleChoiceSetting(
theme,
titleId = R.string.change_app_theme,
choicesId = R.array.themeEntries,
valuesId = R.array.themeValues
)
)
}
val themeMode: AbstractIntSetting = object : AbstractIntSetting {
override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME_MODE.getInt()
override fun setInt(value: Int) {
@@ -1126,6 +1092,35 @@ class SettingsFragmentPresenter(
}
}
add(
SingleChoiceSetting(
themeMode,
titleId = R.string.change_theme_mode,
choicesId = R.array.themeModeEntries,
valuesId = R.array.themeModeValues
)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
add(
SingleChoiceSetting(
theme,
titleId = R.string.change_app_theme,
choicesId = R.array.themeEntriesA12,
valuesId = R.array.themeValuesA12
)
)
} else {
add(
SingleChoiceSetting(
theme,
titleId = R.string.change_app_theme,
choicesId = R.array.themeEntries,
valuesId = R.array.themeValues
)
)
}
val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting {
override fun getInt(needsGlobal: Boolean): Int =
IntSetting.STATIC_THEME_COLOR.getInt(needsGlobal)
@@ -1149,15 +1144,6 @@ class SettingsFragmentPresenter(
}
}
add(
SingleChoiceSetting(
themeMode,
titleId = R.string.change_theme_mode,
choicesId = R.array.themeModeEntries,
valuesId = R.array.themeModeValues
)
)
if (IntSetting.THEME.getInt() != 1) {
add(
SingleChoiceSetting(
@@ -0,0 +1,152 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.InsetsHelper
import org.yuzu.yuzu_emu.utils.ThemeHelper
enum class SettingsSubscreen {
PROFILE_MANAGER,
DRIVER_MANAGER,
DRIVER_FETCHER,
FREEDRENO_SETTINGS,
APPLET_LAUNCHER,
INSTALLABLE,
GAME_FOLDERS,
ABOUT,
LICENSES,
GAME_INFO,
ADDONS,
}
class SettingsSubscreenActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsBinding
private val args by navArgs<SettingsSubscreenActivityArgs>()
override fun attachBaseContext(base: Context) {
super.attachBaseContext(YuzuApplication.applyLanguage(base))
}
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
if (savedInstanceState == null) {
val navController = navHostFragment.navController
val navGraph = navController.navInflater.inflate(
R.navigation.settings_subscreen_navigation
)
navGraph.setStartDestination(resolveStartDestination())
navController.setGraph(navGraph, createStartDestinationArgs())
}
WindowCompat.setDecorFitsSystemWindows(window, false)
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
binding.navigationBarShade.setBackgroundColor(
ThemeHelper.getColorWithOpacity(
MaterialColors.getColor(
binding.navigationBarShade,
com.google.android.material.R.attr.colorSurface
),
ThemeHelper.SYSTEM_BAR_ALPHA
)
)
}
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() = navigateBack()
}
)
setInsets()
}
override fun onStart() {
super.onStart()
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
}
fun navigateBack() {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
if (!navHostFragment.navController.popBackStack()) {
finish()
}
}
private fun resolveStartDestination(): Int =
when (args.destination) {
SettingsSubscreen.PROFILE_MANAGER -> R.id.profileManagerFragment
SettingsSubscreen.DRIVER_MANAGER -> R.id.driverManagerFragment
SettingsSubscreen.DRIVER_FETCHER -> R.id.driverFetcherFragment
SettingsSubscreen.FREEDRENO_SETTINGS -> R.id.freedrenoSettingsFragment
SettingsSubscreen.APPLET_LAUNCHER -> R.id.appletLauncherFragment
SettingsSubscreen.INSTALLABLE -> R.id.installableFragment
SettingsSubscreen.GAME_FOLDERS -> R.id.gameFoldersFragment
SettingsSubscreen.ABOUT -> R.id.aboutFragment
SettingsSubscreen.LICENSES -> R.id.licensesFragment
SettingsSubscreen.GAME_INFO -> R.id.gameInfoFragment
SettingsSubscreen.ADDONS -> R.id.addonsFragment
}
private fun createStartDestinationArgs(): Bundle =
when (args.destination) {
SettingsSubscreen.DRIVER_MANAGER,
SettingsSubscreen.FREEDRENO_SETTINGS -> bundleOf("game" to args.game)
SettingsSubscreen.GAME_INFO,
SettingsSubscreen.ADDONS -> bundleOf(
"game" to requireNotNull(args.game) {
"Game is required for ${args.destination}"
}
)
else -> Bundle()
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
binding.navigationBarShade
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
mlpNavShade.height = barInsets.bottom
binding.navigationBarShade.layoutParams = mlpNavShade
windowInsets
}
}
}
@@ -1,7 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -24,9 +21,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.NativeLibrary
@@ -54,10 +52,10 @@ class AboutFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarAbout.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
binding.imageLogo.setOnLongClickListener {
@@ -75,8 +73,11 @@ class AboutFragment : Fragment() {
)
}
binding.buttonLicenses.setOnClickListener {
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.LICENSES,
null
)
binding.root.findNavController().navigate(action)
}
val buildName = getString(R.string.app_name_suffixed)
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -15,11 +15,9 @@ import androidx.core.view.updatePadding
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.AddonAdapter
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
@@ -42,7 +40,7 @@ class AddonsFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addonViewModel.onOpenAddons(args.game)
addonViewModel.onAddonsViewCreated(args.game)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
@@ -62,7 +60,7 @@ class AddonsFragment : Fragment() {
homeViewModel.setStatusBarShadeVisibility(false)
binding.toolbarAddons.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
binding.toolbarAddons.title = getString(R.string.addons_game, args.game.title)
@@ -122,12 +120,14 @@ class AddonsFragment : Fragment() {
override fun onResume() {
super.onResume()
addonViewModel.refreshAddons()
addonViewModel.onAddonsViewStarted(args.game)
}
override fun onDestroy() {
if (activity?.isChangingConfigurations != true) {
addonViewModel.onCloseAddons()
}
super.onDestroy()
addonViewModel.onCloseAddons()
}
val installAddon =
@@ -167,7 +167,7 @@ class AddonsFragment : Fragment() {
} catch (_: Exception) {
return@newInstance errorMessage
}
addonViewModel.refreshAddons()
addonViewModel.refreshAddons(force = true)
return@newInstance getString(R.string.addon_installed_successfully)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
} else {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -12,7 +12,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
@@ -50,7 +49,7 @@ class AppletLauncherFragment : Fragment() {
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarApplets.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
val applets = listOf(
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -5,15 +8,18 @@ package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.net.Uri
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.InstallableActions
class ContentTypeSelectionDialogFragment : DialogFragment() {
private val addonViewModel: AddonViewModel by activityViewModels()
@@ -23,6 +29,52 @@ class ContentTypeSelectionDialogFragment : DialogFragment() {
private var selectedItem = 0
private val installGameUpdateLauncher =
registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { documents ->
if (documents.isEmpty()) {
return@registerForActivityResult
}
val game = addonViewModel.game
if (game == null) {
installContent(documents)
return@registerForActivityResult
}
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.verifying_content,
false
) { _, _ ->
var updatesMatchProgram = true
for (document in documents) {
val valid = NativeLibrary.doesUpdateMatchProgram(
game.programId,
document.toString()
)
if (!valid) {
updatesMatchProgram = false
break
}
}
requireActivity().runOnUiThread {
if (updatesMatchProgram) {
installContent(documents)
} else {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.content_install_notice,
descriptionId = R.string.content_install_notice_description,
positiveAction = { installContent(documents) },
negativeAction = {}
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
}
return@newInstance Any()
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val launchOptions =
arrayOf(getString(R.string.updates_and_dlc), getString(R.string.mods_and_cheats))
@@ -31,12 +83,11 @@ class ContentTypeSelectionDialogFragment : DialogFragment() {
selectedItem = savedInstanceState.getInt(SELECTED_ITEM)
}
val mainActivity = requireActivity() as MainActivity
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.select_content_type)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (selectedItem) {
0 -> mainActivity.installGameUpdate.launch(arrayOf("*/*"))
0 -> installGameUpdateLauncher.launch(arrayOf("*/*"))
else -> {
if (!preferences.getBoolean(MOD_NOTICE_SEEN, false)) {
preferences.edit().putBoolean(MOD_NOTICE_SEEN, true).apply()
@@ -47,7 +98,7 @@ class ContentTypeSelectionDialogFragment : DialogFragment() {
}
}
}
.setSingleChoiceItems(launchOptions, 0) { _: DialogInterface, i: Int ->
.setSingleChoiceItems(launchOptions, selectedItem) { _: DialogInterface, i: Int ->
selectedItem = i
}
.setNegativeButton(android.R.string.cancel, null)
@@ -65,4 +116,13 @@ class ContentTypeSelectionDialogFragment : DialogFragment() {
private const val SELECTED_ITEM = "SelectedItem"
private const val MOD_NOTICE_SEEN = "ModNoticeSeen"
}
private fun installContent(documents: List<Uri>) {
InstallableActions.installContent(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
addonViewModel = addonViewModel,
documents = documents
)
}
}
@@ -13,7 +13,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
@@ -29,6 +28,7 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentDriverFetcherBinding
import org.yuzu.yuzu_emu.features.fetcher.DriverGroupAdapter
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import java.io.IOException
@@ -87,6 +87,7 @@ class DriverFetcherFragment : Fragment() {
private lateinit var driverGroupAdapter: DriverGroupAdapter
private val driverViewModel: DriverViewModel by activityViewModels()
private val homeViewModel: HomeViewModel by activityViewModels()
private fun parseAdrenoModel(): Int {
if (gpuModel == null) {
@@ -138,9 +139,9 @@ class DriverFetcherFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarDrivers.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
binding.listDrivers.layoutManager = LinearLayoutManager(context)
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -19,6 +19,7 @@ import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.HomeNavigationDirections
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -27,6 +28,7 @@ import org.yuzu.yuzu_emu.adapters.DriverAdapter
import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -105,7 +107,7 @@ class DriverManagerFragment : Fragment() {
}
binding.toolbarDrivers.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
binding.buttonInstall.setOnClickListener {
@@ -113,9 +115,11 @@ class DriverManagerFragment : Fragment() {
}
binding.buttonFetch.setOnClickListener {
binding.root.findNavController().navigate(
R.id.action_driverManagerFragment_to_driverFetcherFragment
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.DRIVER_FETCHER,
null
)
binding.root.findNavController().navigate(action)
}
binding.listDrivers.apply {
@@ -22,6 +22,7 @@ import org.yuzu.yuzu_emu.databinding.FragmentFreedrenoSettingsBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
import org.yuzu.yuzu_emu.utils.FreedrenoPresets
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
class FreedrenoSettingsFragment : Fragment() {
@@ -74,10 +75,15 @@ class FreedrenoSettingsFragment : Fragment() {
binding.toolbarFreedreno.setNavigationOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
if (isPerGameConfig) {
binding.toolbarFreedreno.title = getString(R.string.freedreno_per_game_title)
binding.toolbarFreedreno.subtitle = game!!.title
}
binding.toolbarFreedreno.title = getString(
if (isPerGameConfig) {
R.string.freedreno_per_game_title
} else {
R.string.freedreno_settings_title
}
)
binding.toolbarFreedreno.subtitle = null
}
private fun setupAdapters() {
@@ -175,17 +181,19 @@ class FreedrenoSettingsFragment : Fragment() {
private fun setupWindowInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
binding.root.updatePadding(
left = systemInsets.left,
right = systemInsets.right,
bottom = systemInsets.bottom
)
val barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = insets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
binding.appbarFreedreno.updateMargins(left = leftInsets, right = rightInsets)
binding.scrollFreedreno.updateMargins(left = leftInsets, right = rightInsets)
binding.scrollFreedreno.updatePadding(bottom = barInsets.bottom)
insets
}
}
private fun showSnackbar(message: String) {
private fun showSnackbar(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show()
}
@@ -4,20 +4,21 @@
package org.yuzu.yuzu_emu.fragments
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.FolderAdapter
import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
@@ -25,7 +26,6 @@ import org.yuzu.yuzu_emu.model.DirectoryType
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
@@ -36,6 +36,20 @@ class GameFoldersFragment : Fragment() {
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
private val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) {
processGamesDir(result)
}
}
private val getExternalContentDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) {
processExternalContentDir(result)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
@@ -59,7 +73,7 @@ class GameFoldersFragment : Fragment() {
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarFolders.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
binding.listFolders.apply {
@@ -74,7 +88,6 @@ class GameFoldersFragment : Fragment() {
(binding.listFolders.adapter as FolderAdapter).submitList(it)
}
val mainActivity = requireActivity() as MainActivity
binding.buttonAdd.setOnClickListener {
// Show a model to choose between Game and External Content
val options = arrayOf(
@@ -87,10 +100,10 @@ class GameFoldersFragment : Fragment() {
.setItems(options) { _, which ->
when (which) {
0 -> { // Game Folder
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
}
1 -> { // External Content Folder
mainActivity.getExternalContentDirectory.launch(null)
getExternalContentDirectory.launch(null)
}
}
}
@@ -105,6 +118,50 @@ class GameFoldersFragment : Fragment() {
gamesViewModel.onCloseGameFoldersFragment()
}
private fun processGamesDir(result: Uri) {
requireContext().contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
val uriString = result.toString()
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
if (folder != null) {
Toast.makeText(
requireContext().applicationContext,
R.string.folder_already_added,
Toast.LENGTH_SHORT
).show()
return
}
AddGameFolderDialogFragment.newInstance(uriString, calledFromGameFragment = false)
.show(parentFragmentManager, AddGameFolderDialogFragment.TAG)
}
private fun processExternalContentDir(result: Uri) {
requireContext().contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
val uriString = result.toString()
val folder = gamesViewModel.folders.value.firstOrNull {
it.uriString == uriString && it.type == DirectoryType.EXTERNAL_CONTENT
}
if (folder != null) {
Toast.makeText(
requireContext().applicationContext,
R.string.folder_already_added,
Toast.LENGTH_SHORT
).show()
return
}
val externalContentDir = GameDir(uriString, deepScan = false, DirectoryType.EXTERNAL_CONTENT)
gamesViewModel.addFolder(externalContentDir, savedFromGameFragment = false)
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -18,7 +18,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.NativeLibrary
@@ -64,7 +63,7 @@ class GameInfoFragment : Fragment() {
binding.apply {
toolbarInfo.title = args.game.title
toolbarInfo.setNavigationOnClickListener {
view.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
val pathString = Uri.parse(args.game.path).path ?: ""
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -35,6 +35,7 @@ import org.yuzu.yuzu_emu.adapters.GamePropertiesAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamePropertiesBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.GamesViewModel
@@ -250,8 +251,10 @@ class GamePropertiesFragment : Fragment() {
R.string.info_description,
R.drawable.ic_info_outline,
action = {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToGameInfoFragment(args.game)
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.GAME_INFO,
args.game
)
binding.root.findNavController().navigate(action)
}
)
@@ -310,6 +313,24 @@ class GamePropertiesFragment : Fragment() {
)
)
if (!args.game.isHomebrew) {
add(
SubmenuProperty(
R.string.add_ons,
R.string.add_ons_description,
R.drawable.ic_edit,
action = {
val action =
HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.ADDONS,
args.game
)
binding.root.findNavController().navigate(action)
}
)
)
}
if (GpuDriverHelper.supportsCustomDriverLoading()) {
add(
SubmenuProperty(
@@ -318,8 +339,11 @@ class GamePropertiesFragment : Fragment() {
R.drawable.ic_build,
detailsFlow = driverViewModel.selectedDriverTitle,
action = {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToDriverManagerFragment(args.game)
val action =
HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.DRIVER_MANAGER,
args.game
)
binding.root.findNavController().navigate(action)
}
)
@@ -332,8 +356,11 @@ class GamePropertiesFragment : Fragment() {
R.string.freedreno_per_game_description,
R.drawable.ic_graphics,
action = {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToFreedrenoSettingsFragment(args.game)
val action =
HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.FREEDRENO_SETTINGS,
args.game
)
binding.root.findNavController().navigate(action)
}
)
@@ -341,18 +368,6 @@ class GamePropertiesFragment : Fragment() {
}
if (!args.game.isHomebrew) {
add(
SubmenuProperty(
R.string.add_ons,
R.string.add_ons_description,
R.drawable.ic_edit,
action = {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToAddonsFragment(args.game)
binding.root.findNavController().navigate(action)
}
)
)
add(
InstallableProperty(
R.string.save_data,
@@ -1,9 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.Manifest
@@ -39,12 +36,15 @@ import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
class HomeSettingsFragment : Fragment() {
private var _binding: FragmentHomeSettingsBinding? = null
@@ -71,8 +71,12 @@ class HomeSettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setStatusBarShadeVisibility(visible = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
mainActivity = requireActivity() as MainActivity
binding.toolbarHomeSettings.setNavigationOnClickListener {
findNavController().popBackStack()
}
binding.toolbarHomeSettings.title = getString(R.string.preferences_settings)
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
add(
@@ -123,8 +127,11 @@ class HomeSettingsFragment : Fragment() {
R.string.profile_manager_description,
R.drawable.ic_account_circle,
{
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_profileManagerFragment)
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.PROFILE_MANAGER,
null
)
binding.root.findNavController().navigate(action)
}
)
)
@@ -134,8 +141,10 @@ class HomeSettingsFragment : Fragment() {
R.string.install_gpu_driver_description,
R.drawable.ic_build,
{
val action = HomeSettingsFragmentDirections
.actionHomeSettingsFragmentToDriverManagerFragment(null)
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.DRIVER_MANAGER,
null
)
binding.root.findNavController().navigate(action)
},
{ true },
@@ -144,6 +153,23 @@ class HomeSettingsFragment : Fragment() {
driverViewModel.selectedDriverTitle
)
)
if (GpuDriverHelper.isAdrenoGpu()) {
add(
HomeSetting(
R.string.freedreno_settings_title,
R.string.gpu_driver_settings,
R.drawable.ic_graphics,
{
val action =
HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.FREEDRENO_SETTINGS,
null
)
binding.root.findNavController().navigate(action)
}
)
)
}
add(
HomeSetting(
R.string.multiplayer,
@@ -160,8 +186,11 @@ class HomeSettingsFragment : Fragment() {
R.string.applets_description,
R.drawable.ic_applet,
{
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment)
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.APPLET_LAUNCHER,
null
)
binding.root.findNavController().navigate(action)
},
{ NativeLibrary.isFirmwareAvailable() },
R.string.applets_error_firmware,
@@ -174,8 +203,11 @@ class HomeSettingsFragment : Fragment() {
R.string.manage_yuzu_data_description,
R.drawable.ic_install,
{
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_installableFragment)
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.INSTALLABLE,
null
)
binding.root.findNavController().navigate(action)
}
)
)
@@ -185,8 +217,11 @@ class HomeSettingsFragment : Fragment() {
R.string.select_games_folder_description,
R.drawable.ic_add,
{
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment)
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.GAME_FOLDERS,
null
)
binding.root.findNavController().navigate(action)
}
)
)
@@ -269,9 +304,11 @@ class HomeSettingsFragment : Fragment() {
R.string.about_description,
R.drawable.ic_info_outline,
{
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
parentFragmentManager.primaryNavigationFragment?.findNavController()
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
SettingsSubscreen.ABOUT,
null
)
binding.root.findNavController().navigate(action)
}
)
)
@@ -465,19 +502,22 @@ class HomeSettingsFragment : Fragment() {
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
binding.appbarHomeSettings.updateMargins(
left = barInsets.left + cutoutInsets.left,
right = barInsets.right + cutoutInsets.right
)
binding.scrollViewSettings.updatePadding(
top = barInsets.top
bottom = barInsets.bottom
)
binding.homeSettingsList.updatePadding(
left = barInsets.left + cutoutInsets.left,
top = cutoutInsets.top,
right = barInsets.right + cutoutInsets.right,
bottom = barInsets.bottom
right = barInsets.right + cutoutInsets.right
)
windowInsets
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -14,23 +14,23 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.InstallableAdapter
import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.Installable
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.InstallableActions
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
@@ -45,6 +45,9 @@ class InstallableFragment : Fragment() {
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
private val addonViewModel: AddonViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -65,12 +68,10 @@ class InstallableFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val mainActivity = requireActivity() as MainActivity
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarInstallables.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
homeViewModel.openImportSaves.collect(viewLifecycleOwner) {
@@ -84,8 +85,8 @@ class InstallableFragment : Fragment() {
Installable(
R.string.user_data,
R.string.user_data_description,
install = { mainActivity.importUserData.launch(arrayOf("application/zip")) },
export = { mainActivity.exportUserData.launch("export.zip") }
install = { importUserDataLauncher.launch(arrayOf("application/zip")) },
export = { exportUserDataLauncher.launch("export.zip") }
),
Installable(
R.string.manage_save_data,
@@ -127,27 +128,33 @@ class InstallableFragment : Fragment() {
Installable(
R.string.install_game_content,
R.string.install_game_content_description,
install = { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
install = { installGameUpdateLauncher.launch(arrayOf("*/*")) }
),
Installable(
R.string.install_firmware,
R.string.install_firmware_description,
install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
install = { getFirmwareLauncher.launch(arrayOf("application/zip")) }
),
Installable(
R.string.uninstall_firmware,
R.string.uninstall_firmware_description,
install = { mainActivity.uninstallFirmware() }
install = {
InstallableActions.uninstallFirmware(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
homeViewModel = homeViewModel
)
}
),
Installable(
R.string.install_prod_keys,
R.string.install_prod_keys_description,
install = { mainActivity.getProdKey.launch(arrayOf("*/*")) }
install = { getProdKeyLauncher.launch(arrayOf("*/*")) }
),
Installable(
R.string.install_amiibo_keys,
R.string.install_amiibo_keys_description,
install = { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
install = { getAmiiboKeyLauncher.launch(arrayOf("*/*")) }
)
)
@@ -180,6 +187,132 @@ class InstallableFragment : Fragment() {
windowInsets
}
private val getProdKeyLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
InstallableActions.processKey(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
gamesViewModel = gamesViewModel,
result = result,
extension = "keys"
)
}
}
private val getAmiiboKeyLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
InstallableActions.processKey(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
gamesViewModel = gamesViewModel,
result = result,
extension = "bin"
)
}
}
private val getFirmwareLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
InstallableActions.processFirmware(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
homeViewModel = homeViewModel,
result = result
)
}
}
private val installGameUpdateLauncher =
registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { documents ->
if (documents.isEmpty()) {
return@registerForActivityResult
}
if (addonViewModel.game == null) {
InstallableActions.installContent(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
addonViewModel = addonViewModel,
documents = documents
)
return@registerForActivityResult
}
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.verifying_content,
false
) { _, _ ->
var updatesMatchProgram = true
for (document in documents) {
val valid = NativeLibrary.doesUpdateMatchProgram(
addonViewModel.game!!.programId,
document.toString()
)
if (!valid) {
updatesMatchProgram = false
break
}
}
if (updatesMatchProgram) {
requireActivity().runOnUiThread {
InstallableActions.installContent(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
addonViewModel = addonViewModel,
documents = documents
)
}
} else {
requireActivity().runOnUiThread {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.content_install_notice,
descriptionId = R.string.content_install_notice_description,
positiveAction = {
InstallableActions.installContent(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
addonViewModel = addonViewModel,
documents = documents
)
},
negativeAction = {}
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
}
return@newInstance Any()
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
private val importUserDataLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
InstallableActions.importUserData(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
gamesViewModel = gamesViewModel,
driverViewModel = driverViewModel,
result = result
)
}
}
private val exportUserDataLauncher =
registerForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { result ->
if (result != null) {
InstallableActions.exportUserData(
activity = requireActivity(),
fragmentManager = parentFragmentManager,
result = result
)
}
}
private val importSaves =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -13,7 +13,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
@@ -48,7 +47,7 @@ class LicensesFragment : Fragment() {
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarLicenses.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
val licenses = listOf(
@@ -51,7 +51,7 @@ class ProfileManagerFragment : Fragment() {
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarProfiles.setNavigationOnClickListener {
findNavController().popBackStack()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
setupRecyclerView()
@@ -18,7 +18,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
import java.util.concurrent.atomic.AtomicBoolean
class AddonViewModel : ViewModel() {
private val _patchList = MutableStateFlow(mutableListOf<Patch>())
private val _patchList = MutableStateFlow<List<Patch>>(emptyList())
val addonList get() = _patchList.asStateFlow()
private val _showModInstallPicker = MutableStateFlow(false)
@@ -31,34 +31,62 @@ class AddonViewModel : ViewModel() {
val addonToDelete = _addonToDelete.asStateFlow()
var game: Game? = null
private var loadedGameKey: String? = null
private val isRefreshing = AtomicBoolean(false)
private val pendingRefresh = AtomicBoolean(false)
fun onOpenAddons(game: Game) {
fun onAddonsViewCreated(game: Game) {
this.game = game
refreshAddons()
refreshAddons(commitEmpty = false)
}
fun refreshAddons() {
if (isRefreshing.get() || game == null) {
fun onAddonsViewStarted(game: Game) {
this.game = game
val hasLoadedCurrentGame = loadedGameKey == gameKey(game)
refreshAddons(force = !hasLoadedCurrentGame)
}
fun refreshAddons(force: Boolean = false, commitEmpty: Boolean = true) {
val currentGame = game ?: return
val currentGameKey = gameKey(currentGame)
if (!force && loadedGameKey == currentGameKey) {
return
}
isRefreshing.set(true)
if (!isRefreshing.compareAndSet(false, true)) {
if (force) {
pendingRefresh.set(true)
}
return
}
viewModelScope.launch {
withContext(Dispatchers.IO) {
val patchList = (
NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
?: emptyArray()
).toMutableList()
try {
val patches = withContext(Dispatchers.IO) {
NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId)
} ?: return@launch
val patchList = patches.toMutableList()
patchList.sortBy { it.name }
// Ensure only one update is enabled
ensureSingleUpdateEnabled(patchList)
removeDuplicates(patchList)
if (patchList.isEmpty() && !commitEmpty) {
return@launch
}
if (gameKey(game ?: return@launch) != currentGameKey) {
return@launch
}
_patchList.value = patchList
_patchList.value = patchList.toList()
loadedGameKey = currentGameKey
} finally {
isRefreshing.set(false)
if (pendingRefresh.compareAndSet(true, false)) {
refreshAddons(force = true)
}
}
}
}
@@ -119,17 +147,26 @@ class AddonViewModel : ViewModel() {
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
}
refreshAddons()
refreshAddons(force = true)
}
fun onCloseAddons() {
if (_patchList.value.isEmpty()) {
val currentGame = game ?: run {
_patchList.value = emptyList()
loadedGameKey = null
return
}
val currentList = _patchList.value
if (currentList.isEmpty()) {
_patchList.value = emptyList()
loadedGameKey = null
game = null
return
}
NativeConfig.setDisabledAddons(
game!!.programId,
_patchList.value.mapNotNull {
currentGame.programId,
currentList.mapNotNull {
if (it.enabled) {
null
} else {
@@ -148,7 +185,8 @@ class AddonViewModel : ViewModel() {
}.toTypedArray()
)
NativeConfig.saveGlobalConfig()
_patchList.value.clear()
_patchList.value = emptyList()
loadedGameKey = null
game = null
}
@@ -159,4 +197,8 @@ class AddonViewModel : ViewModel() {
fun showModNoticeDialog(show: Boolean) {
_showModNoticeDialog.value = show
}
private fun gameKey(game: Game): String {
return "${game.programId}|${game.path}"
}
}
@@ -16,5 +16,17 @@ data class Patch(
val type: Int,
val programId: String,
val titleId: String,
val numericVersion: Long = 0
)
val numericVersion: Long = 0,
val source: Int = 0
) {
companion object {
const val SOURCE_UNKNOWN = 0
const val SOURCE_NAND = 1
const val SOURCE_SDMC = 2
const val SOURCE_EXTERNAL = 3
const val SOURCE_PACKED = 4
}
val isRemovable: Boolean
get() = source != SOURCE_EXTERNAL && source != SOURCE_PACKED
}
@@ -26,7 +26,6 @@ import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.File
import java.io.FilenameFilter
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
@@ -39,16 +38,10 @@ import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.InstallResult
import android.os.Build
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import androidx.core.content.edit
import org.yuzu.yuzu_emu.activities.EmulationActivity
import kotlin.text.compareTo
@@ -453,35 +446,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
fun processKey(result: Uri, extension: String = "keys") {
contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
InstallableActions.processKey(
activity = this,
fragmentManager = supportFragmentManager,
gamesViewModel = gamesViewModel,
result = result,
extension = extension
)
val resultCode: Int = NativeLibrary.installKeys(result.toString(), extension)
if (resultCode == 0) {
// TODO(crueter): It may be worth it to switch some of these Toasts to snackbars,
// since most of it is foreground-only anyways.
Toast.makeText(
applicationContext,
R.string.keys_install_success,
Toast.LENGTH_SHORT
).show()
gamesViewModel.reloadGames(true)
return
}
val resultString: String =
resources.getStringArray(R.array.installKeysResults)[resultCode]
MessageDialogFragment.newInstance(
titleId = R.string.keys_failed,
descriptionString = resultString,
helpLinkId = R.string.keys_missing_help
).show(supportFragmentManager, MessageDialogFragment.TAG)
}
val getFirmware = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
@@ -491,75 +462,21 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
fun processFirmware(result: Uri, onComplete: (() -> Unit)? = null) {
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
val firmwarePath =
File(NativeConfig.getNandDir() + "/system/Contents/registered/")
val cacheFirmwareDir = File("${cacheDir.path}/registered/")
ProgressDialogFragment.newInstance(
this,
R.string.firmware_installing
) { progressCallback, _ ->
var messageToShow: Any
try {
FileUtil.unzipToInternalStorage(
result.toString(),
cacheFirmwareDir,
progressCallback
)
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
MessageDialogFragment.newInstance(
this,
titleId = R.string.firmware_installed_failure,
descriptionId = R.string.firmware_installed_failure_description
)
} else {
firmwarePath.deleteRecursively()
cacheFirmwareDir.copyRecursively(firmwarePath, true)
NativeLibrary.initializeSystem(true)
homeViewModel.setCheckKeys(true)
getString(R.string.save_file_imported_success)
}
} catch (e: Exception) {
Log.error("[MainActivity] Firmware install failed - ${e.message}")
messageToShow = getString(R.string.fatal_error)
} finally {
cacheFirmwareDir.deleteRecursively()
}
messageToShow
}.apply {
onDialogComplete = onComplete
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
InstallableActions.processFirmware(
activity = this,
fragmentManager = supportFragmentManager,
homeViewModel = homeViewModel,
result = result,
onComplete = onComplete
)
}
fun uninstallFirmware() {
val firmwarePath =
File(NativeConfig.getNandDir() + "/system/Contents/registered/")
ProgressDialogFragment.newInstance(
this,
R.string.firmware_uninstalling
) { progressCallback, _ ->
var messageToShow: Any
try {
// Ensure the firmware directory exists before attempting to delete
if (firmwarePath.exists()) {
firmwarePath.deleteRecursively()
// Optionally reinitialize the system or perform other necessary steps
NativeLibrary.initializeSystem(true)
homeViewModel.setCheckKeys(true)
messageToShow = getString(R.string.firmware_uninstalled_success)
} else {
messageToShow = getString(R.string.firmware_uninstalled_failure)
}
} catch (e: Exception) {
Log.error("[MainActivity] Firmware uninstall failed - ${e.message}")
messageToShow = getString(R.string.fatal_error)
}
messageToShow
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
InstallableActions.uninstallFirmware(
activity = this,
fragmentManager = supportFragmentManager,
homeViewModel = homeViewModel
)
}
val installGameUpdate = registerForActivityResult(
@@ -606,101 +523,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
private fun installContent(documents: List<Uri>) {
ProgressDialogFragment.newInstance(
this@MainActivity,
R.string.installing_game_content
) { progressCallback, messageCallback ->
var installSuccess = 0
var installOverwrite = 0
var errorBaseGame = 0
var error = 0
documents.forEach {
messageCallback.invoke(FileUtil.getFilename(it))
when (
InstallResult.from(
NativeLibrary.installFileToNand(
it.toString(),
progressCallback
)
)
) {
InstallResult.Success -> {
installSuccess += 1
}
InstallResult.Overwrite -> {
installOverwrite += 1
}
InstallResult.BaseInstallAttempted -> {
errorBaseGame += 1
}
InstallResult.Failure -> {
error += 1
}
}
}
addonViewModel.refreshAddons()
val separator = System.lineSeparator() ?: "\n"
val installResult = StringBuilder()
if (installSuccess > 0) {
installResult.append(
getString(
R.string.install_game_content_success_install,
installSuccess
)
)
installResult.append(separator)
}
if (installOverwrite > 0) {
installResult.append(
getString(
R.string.install_game_content_success_overwrite,
installOverwrite
)
)
installResult.append(separator)
}
val errorTotal: Int = errorBaseGame + error
if (errorTotal > 0) {
installResult.append(separator)
installResult.append(
getString(
R.string.install_game_content_failed_count,
errorTotal
)
)
installResult.append(separator)
if (errorBaseGame > 0) {
installResult.append(separator)
installResult.append(
getString(R.string.install_game_content_failure_base)
)
installResult.append(separator)
}
if (error > 0) {
installResult.append(
getString(R.string.install_game_content_failure_description)
)
installResult.append(separator)
}
return@newInstance MessageDialogFragment.newInstance(
this,
titleId = R.string.install_game_content_failure,
descriptionString = installResult.toString().trim(),
helpLinkId = R.string.install_game_content_help_link
)
} else {
return@newInstance MessageDialogFragment.newInstance(
this,
titleId = R.string.install_game_content_success,
descriptionString = installResult.toString().trim()
)
}
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
InstallableActions.installContent(
activity = this,
fragmentManager = supportFragmentManager,
addonViewModel = addonViewModel,
documents = documents
)
}
val exportUserData = registerForActivityResult(
@@ -709,25 +537,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (result == null) {
return@registerForActivityResult
}
ProgressDialogFragment.newInstance(
this,
R.string.exporting_user_data,
true
) { progressCallback, _ ->
val zipResult = FileUtil.zipFromInternalStorage(
File(DirectoryInitialization.userDirectory!!),
DirectoryInitialization.userDirectory!!,
BufferedOutputStream(contentResolver.openOutputStream(result)),
progressCallback,
compression = false
)
return@newInstance when (zipResult) {
TaskState.Completed -> getString(R.string.user_data_export_success)
TaskState.Failed -> R.string.export_failed
TaskState.Cancelled -> R.string.user_data_export_cancelled
}
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
InstallableActions.exportUserData(
activity = this,
fragmentManager = supportFragmentManager,
result = result
)
}
val importUserData =
@@ -735,58 +549,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (result == null) {
return@registerForActivityResult
}
ProgressDialogFragment.newInstance(
this,
R.string.importing_user_data
) { progressCallback, _ ->
val checkStream =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
var isYuzuBackup = false
checkStream.use { stream ->
var ze: ZipEntry? = null
while (stream.nextEntry?.also { ze = it } != null) {
val itemName = ze!!.name.trim()
if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
isYuzuBackup = true
return@use
}
}
}
if (!isYuzuBackup) {
return@newInstance MessageDialogFragment.newInstance(
this,
titleId = R.string.invalid_yuzu_backup,
descriptionId = R.string.user_data_import_failed_description
)
}
// Clear existing user data
NativeConfig.unloadGlobalConfig()
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
// Copy archive to internal storage
try {
FileUtil.unzipToInternalStorage(
result.toString(),
File(DirectoryInitialization.userDirectory!!),
progressCallback
)
} catch (e: Exception) {
return@newInstance MessageDialogFragment.newInstance(
this,
titleId = R.string.import_failed,
descriptionId = R.string.user_data_import_failed_description
)
}
// Reinitialize relevant data
NativeLibrary.initializeSystem(true)
NativeConfig.initializeGlobalConfig()
gamesViewModel.reloadGames(false)
driverViewModel.reloadDriverData()
return@newInstance getString(R.string.user_data_import_success)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
InstallableActions.importUserData(
activity = this,
fragmentManager = supportFragmentManager,
gamesViewModel = gamesViewModel,
driverViewModel = driverViewModel,
result = result
)
}
}
@@ -0,0 +1,327 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.InstallResult
import org.yuzu.yuzu_emu.model.TaskState
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.io.FilenameFilter
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
object InstallableActions {
fun processKey(
activity: FragmentActivity,
fragmentManager: FragmentManager,
gamesViewModel: GamesViewModel,
result: Uri,
extension: String = "keys"
) {
activity.contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
val resultCode = NativeLibrary.installKeys(result.toString(), extension)
if (resultCode == 0) {
Toast.makeText(
activity.applicationContext,
R.string.keys_install_success,
Toast.LENGTH_SHORT
).show()
gamesViewModel.reloadGames(true)
return
}
val resultString = activity.resources.getStringArray(R.array.installKeysResults)[resultCode]
MessageDialogFragment.newInstance(
titleId = R.string.keys_failed,
descriptionString = resultString,
helpLinkId = R.string.keys_missing_help
).show(fragmentManager, MessageDialogFragment.TAG)
}
fun processFirmware(
activity: FragmentActivity,
fragmentManager: FragmentManager,
homeViewModel: HomeViewModel,
result: Uri,
onComplete: (() -> Unit)? = null
) {
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
val firmwarePath = File(NativeConfig.getNandDir() + "/system/Contents/registered/")
val cacheFirmwareDir = File("${activity.cacheDir.path}/registered/")
ProgressDialogFragment.newInstance(
activity,
R.string.firmware_installing
) { progressCallback, _ ->
var messageToShow: Any
try {
FileUtil.unzipToInternalStorage(
result.toString(),
cacheFirmwareDir,
progressCallback
)
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
MessageDialogFragment.newInstance(
activity,
titleId = R.string.firmware_installed_failure,
descriptionId = R.string.firmware_installed_failure_description
)
} else {
firmwarePath.deleteRecursively()
cacheFirmwareDir.copyRecursively(firmwarePath, overwrite = true)
NativeLibrary.initializeSystem(true)
homeViewModel.setCheckKeys(true)
activity.getString(R.string.save_file_imported_success)
}
} catch (_: Exception) {
messageToShow = activity.getString(R.string.fatal_error)
} finally {
cacheFirmwareDir.deleteRecursively()
}
messageToShow
}.apply {
onDialogComplete = onComplete
}.show(fragmentManager, ProgressDialogFragment.TAG)
}
fun uninstallFirmware(
activity: FragmentActivity,
fragmentManager: FragmentManager,
homeViewModel: HomeViewModel
) {
val firmwarePath = File(NativeConfig.getNandDir() + "/system/Contents/registered/")
ProgressDialogFragment.newInstance(
activity,
R.string.firmware_uninstalling
) { _, _ ->
val messageToShow: Any = try {
if (firmwarePath.exists()) {
firmwarePath.deleteRecursively()
NativeLibrary.initializeSystem(true)
homeViewModel.setCheckKeys(true)
activity.getString(R.string.firmware_uninstalled_success)
} else {
activity.getString(R.string.firmware_uninstalled_failure)
}
} catch (_: Exception) {
activity.getString(R.string.fatal_error)
}
messageToShow
}.show(fragmentManager, ProgressDialogFragment.TAG)
}
fun installContent(
activity: FragmentActivity,
fragmentManager: FragmentManager,
addonViewModel: AddonViewModel,
documents: List<Uri>
) {
ProgressDialogFragment.newInstance(
activity,
R.string.installing_game_content
) { progressCallback, messageCallback ->
var installSuccess = 0
var installOverwrite = 0
var errorBaseGame = 0
var error = 0
documents.forEach {
messageCallback.invoke(FileUtil.getFilename(it))
when (
InstallResult.from(
NativeLibrary.installFileToNand(
it.toString(),
progressCallback
)
)
) {
InstallResult.Success -> installSuccess += 1
InstallResult.Overwrite -> installOverwrite += 1
InstallResult.BaseInstallAttempted -> errorBaseGame += 1
InstallResult.Failure -> error += 1
}
}
addonViewModel.refreshAddons(force = true)
val separator = System.lineSeparator() ?: "\n"
val installResult = StringBuilder()
if (installSuccess > 0) {
installResult.append(
activity.getString(
R.string.install_game_content_success_install,
installSuccess
)
)
installResult.append(separator)
}
if (installOverwrite > 0) {
installResult.append(
activity.getString(
R.string.install_game_content_success_overwrite,
installOverwrite
)
)
installResult.append(separator)
}
val errorTotal = errorBaseGame + error
if (errorTotal > 0) {
installResult.append(separator)
installResult.append(
activity.getString(
R.string.install_game_content_failed_count,
errorTotal
)
)
installResult.append(separator)
if (errorBaseGame > 0) {
installResult.append(separator)
installResult.append(activity.getString(R.string.install_game_content_failure_base))
installResult.append(separator)
}
if (error > 0) {
installResult.append(
activity.getString(R.string.install_game_content_failure_description)
)
installResult.append(separator)
}
return@newInstance MessageDialogFragment.newInstance(
activity,
titleId = R.string.install_game_content_failure,
descriptionString = installResult.toString().trim(),
helpLinkId = R.string.install_game_content_help_link
)
} else {
return@newInstance MessageDialogFragment.newInstance(
activity,
titleId = R.string.install_game_content_success,
descriptionString = installResult.toString().trim()
)
}
}.show(fragmentManager, ProgressDialogFragment.TAG)
}
fun exportUserData(
activity: FragmentActivity,
fragmentManager: FragmentManager,
result: Uri
) {
val userDirectory = DirectoryInitialization.userDirectory
if (userDirectory == null) {
Toast.makeText(
activity.applicationContext,
R.string.fatal_error,
Toast.LENGTH_SHORT
).show()
return
}
ProgressDialogFragment.newInstance(
activity,
R.string.exporting_user_data,
true
) { progressCallback, _ ->
val zipResult = FileUtil.zipFromInternalStorage(
File(userDirectory),
userDirectory,
BufferedOutputStream(activity.contentResolver.openOutputStream(result)),
progressCallback,
compression = false
)
return@newInstance when (zipResult) {
TaskState.Completed -> activity.getString(R.string.user_data_export_success)
TaskState.Failed -> R.string.export_failed
TaskState.Cancelled -> R.string.user_data_export_cancelled
}
}.show(fragmentManager, ProgressDialogFragment.TAG)
}
fun importUserData(
activity: FragmentActivity,
fragmentManager: FragmentManager,
gamesViewModel: GamesViewModel,
driverViewModel: DriverViewModel,
result: Uri
) {
val userDirectory = DirectoryInitialization.userDirectory
if (userDirectory == null) {
Toast.makeText(
activity.applicationContext,
R.string.fatal_error,
Toast.LENGTH_SHORT
).show()
return
}
ProgressDialogFragment.newInstance(
activity,
R.string.importing_user_data
) { progressCallback, _ ->
val checkStream = ZipInputStream(
BufferedInputStream(activity.contentResolver.openInputStream(result))
)
var isYuzuBackup = false
checkStream.use { stream ->
var ze: ZipEntry? = null
while (stream.nextEntry?.also { ze = it } != null) {
val itemName = ze!!.name.trim()
if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
isYuzuBackup = true
return@use
}
}
}
if (!isYuzuBackup) {
return@newInstance MessageDialogFragment.newInstance(
activity,
titleId = R.string.invalid_yuzu_backup,
descriptionId = R.string.user_data_import_failed_description
)
}
NativeConfig.unloadGlobalConfig()
File(userDirectory).deleteRecursively()
try {
FileUtil.unzipToInternalStorage(
result.toString(),
File(userDirectory),
progressCallback
)
} catch (_: Exception) {
return@newInstance MessageDialogFragment.newInstance(
activity,
titleId = R.string.import_failed,
descriptionId = R.string.user_data_import_failed_description
)
}
NativeLibrary.initializeSystem(true)
NativeConfig.initializeGlobalConfig()
gamesViewModel.reloadGames(false)
driverViewModel.reloadDriverData()
return@newInstance activity.getString(R.string.user_data_import_success)
}.show(fragmentManager, ProgressDialogFragment.TAG)
}
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <common/fs/path_util.h>
#include <common/logging/log.h>
#include <common/logging.h>
#include <common/settings.h>
#include <input_common/main.h>
#include "android_config.h"
@@ -7,7 +7,7 @@
#include <android/native_window_jni.h>
#include "common/android/id_cache.h"
#include "common/logging/log.h"
#include "common/logging.h"
#include "input_common/drivers/android.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/virtual_amiibo.h"
+2 -3
View File
@@ -42,8 +42,7 @@
#include "common/detached_tasks.h"
#include "common/dynamic_library.h"
#include "common/fs/path_util.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "common/logging.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/settings.h"
@@ -1407,7 +1406,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
Common::Android::ToJString(env, patch.version), static_cast<jint>(patch.type),
Common::Android::ToJString(env, std::to_string(patch.program_id)),
Common::Android::ToJString(env, std::to_string(patch.title_id)),
static_cast<jlong>(patch.numeric_version));
static_cast<jlong>(patch.numeric_version), static_cast<jint>(patch.source));
env->SetObjectArrayElement(jpatchArray, i, jpatch);
++i;
}
@@ -10,7 +10,7 @@
#include "common/android/android_common.h"
#include "common/android/id_cache.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/logging.h"
#include "common/settings.h"
#include "frontend_common/config.h"
#include "frontend_common/settings_generator.h"
@@ -23,7 +23,7 @@
#include <jni.h>
#include "common/android/android_common.h"
#include "common/logging/log.h"
#include "common/logging.h"
#include "native.h"
namespace {
+4 -1
View File
@@ -1,8 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/android/android_common.h>
#include <common/logging/log.h>
#include <common/logging.h>
#include <jni.h>
extern "C" {
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorSurface" />
<solid android:color="@android:color/transparent" />
<stroke
android:width="1dp"
android:color="?attr/colorOutline" />
@@ -0,0 +1,237 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="16dp"
android:paddingHorizontal="16dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="start|center_vertical"
android:text="@string/multiplayer_room_browser"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
android:textColor="?attr/colorOnSurface" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/search_background"
style="?attr/materialCardViewFilledStyle"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
app:cardBackgroundColor="?attr/colorSurfaceVariant"
app:cardCornerRadius="24dp">
<LinearLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:contentDescription="@string/home_search"
android:src="@drawable/ic_search"
app:tint="?attr/colorOnSurfaceVariant" />
<EditText
android:id="@+id/search_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:autofillHints=""
android:background="@android:color/transparent"
android:hint="@string/multiplayer_search_public_lobbies"
android:imeOptions="flagNoFullscreen"
android:inputType="text"
android:maxLines="1"
android:textColor="?attr/colorOnSurface" />
</LinearLayout>
<ImageView
android:id="@+id/clear_button"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="48dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/clear"
android:src="@drawable/ic_clear"
android:visibility="invisible"
app:tint="?attr/colorOnSurfaceVariant"
tools:visibility="visible" />
<ImageView
android:id="@+id/btn_submit"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/submit"
android:src="@drawable/ic_send"
app:tint="?attr/colorOnSurfaceVariant"
tools:visibility="visible" />
</com.google.android.material.card.MaterialCardView>
<FrameLayout
android:layout_width="48dp"
android:layout_height="48dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/refresh_button"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:text="@string/refresh"
app:icon="@drawable/ic_refresh" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleSmall"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
<HorizontalScrollView
android:id="@+id/horizontalScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:clipToPadding="false"
android:fadingEdge="horizontal"
android:paddingHorizontal="16dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false">
<com.google.android.material.chip.Chip
android:id="@+id/chip_hide_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:checkable="true"
android:checked="false"
android:paddingStart="8dp"
android:paddingTop="6dp"
android:paddingEnd="8dp"
android:paddingBottom="6dp"
android:text="@string/multiplayer_hide_empty_rooms"
app:chipCornerRadius="16dp"
app:chipIconSize="18dp"
app:chipIconTint="?attr/colorOnSurface" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_hide_full"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:checkable="true"
android:checked="false"
android:paddingStart="8dp"
android:paddingTop="6dp"
android:paddingEnd="8dp"
android:paddingBottom="6dp"
android:text="@string/multiplayer_hide_full_rooms"
app:chipCornerRadius="16dp"
app:chipIconSize="18dp"
app:chipIconTint="?attr/colorOnSurface" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/room_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:contentDescription="@string/room_list"
android:paddingBottom="16dp"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<LinearLayout
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="32dp"
android:visibility="gone">
<ImageView
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_marginBottom="16dp"
android:alpha="0.5"
android:contentDescription="@string/refresh"
android:src="@drawable/ic_refresh"
app:tint="?attr/colorOnSurface" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/multiplayer_no_rooms_found"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:textColor="?attr/colorOnSurface" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/multiplayer_tap_refresh_to_check_again"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
android:textColor="?attr/colorOnSurface" />
<com.google.android.material.button.MaterialButton
android:id="@+id/empty_refresh_button"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/refresh"
app:icon="@drawable/ic_refresh" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/theme_dialog_background"
android:orientation="vertical">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/drag_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="16dp">
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="20dp"
android:layout_weight="0.75">
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:contentDescription="@string/multiplayer"
android:src="@drawable/ic_network"
app:tint="?attr/colorPrimary" />
</FrameLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.25"
android:orientation="vertical">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/multiplayer"
android:textAppearance="?attr/textAppearanceHeadline6"
android:textColor="?attr/colorOnSurface" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_lobby_browser"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:minHeight="56dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:text="@string/multiplayer_public_room"
android:textColor="?attr/colorOnPrimary"
app:backgroundTint="?attr/colorPrimary"
app:cornerRadius="16dp"
app:icon="@drawable/ic_search"
app:iconPadding="12dp"
app:iconTint="?attr/colorOnPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_join"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="56dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:text="@string/multiplayer_join_room"
app:cornerRadius="16dp"
app:icon="@drawable/ic_install"
app:iconPadding="12dp" />
<Space
android:layout_width="16dp"
android:layout_height="match_parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_create"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="56dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:text="@string/multiplayer_create_room"
app:cornerRadius="16dp"
app:icon="@drawable/ic_add"
app:iconPadding="12dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
@@ -44,7 +44,12 @@
style="@style/EdenCard"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@android:color/transparent"
app:cardCornerRadius="24dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
android:padding="4dp"
>
@@ -103,7 +108,12 @@
style="@style/EdenCard"
android:layout_width="42dp"
android:layout_height="42dp"
android:background="@android:color/transparent"
app:cardCornerRadius="21dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
android:padding="8dp"
>
@@ -127,7 +137,12 @@
style="@style/EdenCard"
android:layout_width="42dp"
android:layout_height="42dp"
android:background="@android:color/transparent"
app:cardCornerRadius="21dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
android:padding="8dp"
>
@@ -151,7 +166,12 @@
style="@style/EdenCard"
android:layout_width="42dp"
android:layout_height="42dp"
android:background="@android:color/transparent"
app:cardCornerRadius="21dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
android:padding="8dp"
>
@@ -12,70 +12,83 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingVertical="16dp">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="20dp"
android:layout_gravity="center_vertical"
app:tint="?attr/colorOnSurface"
tools:src="@drawable/ic_settings" />
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:orientation="vertical">
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/user_data"
android:textAlignment="viewStart" />
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="20dp"
android:layout_gravity="center_vertical"
app:tint="?attr/colorOnSurface"
tools:src="@drawable/ic_settings" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/description"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/user_data_description"
android:textAlignment="viewStart" />
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/user_data"
android:textAlignment="viewStart" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/description"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/user_data_description"
android:textAlignment="viewStart" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/button_export"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="@string/export"
android:tooltipText="@string/export"
android:visibility="gone"
app:icon="@drawable/ic_export"
tools:visibility="visible" />
android:layout_marginTop="10dp"
android:gravity="end|center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/button_install"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="12dp"
android:contentDescription="@string/string_import"
android:tooltipText="@string/string_import"
android:visibility="gone"
app:icon="@drawable/ic_import"
tools:visibility="visible" />
<Button
android:id="@+id/button_install"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/string_import"
android:tooltipText="@string/string_import"
android:visibility="gone"
app:icon="@drawable/ic_import"
tools:visibility="visible" />
<Button
android:id="@+id/button_export"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:contentDescription="@string/export"
android:tooltipText="@string/export"
android:visibility="gone"
app:icon="@drawable/ic_export"
tools:visibility="visible" />
</LinearLayout>
</LinearLayout>
@@ -5,16 +5,16 @@
android:id="@+id/coordinator_about"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_about"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"
android:background="@android:color/transparent"
app:elevation="0dp">
android:touchscreenBlocksFocus="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_about"
@@ -40,15 +40,41 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="24dp">
android:paddingBottom="24dp"
android:paddingStart="24dp"
android:paddingTop="0dp"
android:paddingEnd="24dp">
<ImageView
android:id="@+id/image_logo"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_vertical"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:src="@drawable/ic_yuzu" />
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/image_logo"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_yuzu" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="220dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/about_app_description"
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
@@ -56,39 +82,6 @@
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="?attr/colorSurface"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/about"
android:textAlignment="viewStart" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/about_app_description"
android:textAlignment="viewStart" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/button_contributors"
android:layout_width="match_parent"
@@ -204,7 +197,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginTop="12dp"
android:gravity="start"
android:orientation="horizontal">
@@ -7,9 +7,13 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="12dp"
android:background="?attr/selectableItemBackground"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="true">
android:focusable="true"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
@@ -2,11 +2,15 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="12dp">
android:layout_marginVertical="12dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -27,10 +27,10 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="14dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingBottom="6dp">
android:paddingTop="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingBottom="2dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_game_screen"
@@ -48,11 +48,14 @@
style="@style/SynthwaveText.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginTop="2dp"
android:gravity="center"
android:requiresFadingEdge="horizontal"
android:textAlignment="center"
android:textSize="14sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
app:layout_constraintStart_toStartOf="@+id/image_game_screen"
app:layout_constraintTop_toBottomOf="@+id/image_game_screen"
@@ -27,10 +27,10 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="14dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingBottom="6dp">
android:paddingTop="3dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingBottom="3dp">
<FrameLayout
android:id="@+id/image_container"
@@ -11,7 +11,7 @@
android:transitionName="card_game"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/eden_card_background"
app:cardBackgroundColor="@android:color/transparent"
app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -27,6 +27,8 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Medium"
app:strokeColor="@color/eden_card_background"
app:strokeWidth="4dp"
tools:src="@drawable/default_icon" />
<com.google.android.material.textview.MaterialTextView
@@ -8,9 +8,14 @@
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginHorizontal="8dp"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp">
app:cardCornerRadius="16dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp">
<LinearLayout
android:id="@+id/option_layout"
@@ -60,6 +65,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textColor="?attr/colorPrimary"
android:textSize="14sp"
android:textStyle="bold"
android:singleLine="true"

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