Compare commits

...

38 commits

Author SHA1 Message Date
lizzie
48d1158204 fx 2026-03-10 09:29:59 +01:00
lizzie
35ef37ad03 [fs] use recursive_directory_iterator since MSVC CRT no longer should bug out
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-03-10 09:29:59 +01:00
crueter
8678cb06eb
[meta] clang-format literally all of the Qt code (#3706)
Some checks failed
tx-src / sources (push) Has been cancelled
Check Strings / check-strings (push) Has been cancelled
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)
Some checks failed
tx-src / sources (push) Has been cancelled
Check Strings / check-strings (push) Has been cancelled
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)
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run
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)
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run
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)
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run
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)
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run
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
xbzk
529b069499
[android,ui] fixed top disalignment between buttons of each column in settings fragment (#3675)
this silly little thing tickles obsessive compulsive disturbed fellas a lot hu3
was shipped along PR 3660, which was rediscussed for other reason, hence this tiny lonely PR.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3675
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-05 13:58:46 +01:00
lizzie
9a07bd0570
[vk] unify VkSurfaceKHR with Android and the rest of platforms; remove technically incorrect nullptr() ctor for handles (#2971)
Removes some odd #ifdef-ing that just can use a shrimple opaque type.

Also removes nullptr() ctor'ing for vulkan handles and such; it's not incorrect per se like how `void *p = 0;` isn't incorrect, just that, y'know, any static analyzer will go "woah". Also there isn't any guarantee that handles `sizeof(Handle) == sizeof(void*)` so may as well :)

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2971
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
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-05 07:32:18 +01:00
xbzk
05f6942bef
[android, ui] fixed setting reset to defaults infinite loop (#3659)
fixed a bug discovered by Pavel in which the settings' "reset to defaults" dialog would get stuck in a infinite loop, due to a recall prior to cleaning state.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3659
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
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-05 00:58:49 +01:00
nekle
70c1e9abed
[android] Try and fix SD Card storage mount points for external paths (#3436)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3436
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: nekle <nekle@protonmail.com>
Co-committed-by: nekle <nekle@protonmail.com>
2026-03-05 00:56:25 +01:00
wildcard
69e47bd2c0
[vulkan] Fix wrong stage index in prepare_stage render area check (#3672)
The OpenGL correctly uses const auto& info{stage_infos[stage]};

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3672
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-05 00:54:48 +01:00
wildcard
cdf9b556b2
[vulkan]fix vuid 02751 (#3573)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3573
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-05 00:54:26 +01:00
xbzk
7f5de6bcd6
[android/fs] external content loader + nca/xci patches (#3596)
Foreword: WHY DON'T EVERYBODY USE ONE FOLDER FOR EACH GAME+CONTENTS? AIN'T THIS THE FORMAT GAMES COME WHEN YOU BUE THEM? DO YOU LIVE WITH ALL YOUR FRIENDS AND HAVE A 2ND HOUSE FOR ALL THE CHILDREN?

Nice, i feel better now.

This feat extends Maufeat's work on external content loading. It harmonically additions:
"...also, if in each game folder X, you find a folder Y, and in this folder Y you detect ONLY a single game, then mount all external content for that game found in that folder Y and its subfolders."
Permanent (not toggleable). External Content folders are supported equally.

Also:
-Reworked several routines for preserving single source of truth between android and other systems;
-Fixed the annoying unknown format error for content files, by providing proper format detection.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3596
Reviewed-by: MaranBr <maranbr@eden-emu.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-04 22:51:35 +01:00
wildcard
c682306788
[vulkan] Implement push descriptors for compute pipelines (#3666)
Implements push descriptor for compute pipelines along with a bug fix, the increment logic was, offset += sizeof(DescriptorUpdateEntry);
This only advances the byte offset by a single descriptor slot, regardless of the array's size (descriptorCount).Now suppose if a shader utilized an array of descriptors (eg, layout(binding = 0) uniform sampler2D textures[4]) and if this happened to fit within the MaxPushDescriptors limit, the template would consume 4 * sizeof(DescriptorUpdateEntry) bytes, but the offset for the next binding would only advance by 1 slot.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3666
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-04 22:46:55 +01:00
259 changed files with 5173 additions and 4254 deletions

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"
;;

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}

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

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",

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

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)**

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.

11
docs/user/CFW.md Normal file
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!

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
docs/user/Mods.md Normal file
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.

View file

@ -4,10 +4,14 @@ 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)**
- **[Settings](./Settings.md)**
- **[Installing Mods](./Mods.md)**
- **[Run On macOS](./RunOnMacOS.md)**
- **[Audio](Audio.md)**
- **[Graphics](Graphics.md)**
@ -17,22 +21,29 @@ This handbook is primarily aimed at the end-user - baking useful knowledge for e
- **[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)**
## 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)**

54
docs/user/Settings.md Normal file
View file

@ -0,0 +1,54 @@
# 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
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

View file

@ -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.

View file

@ -7,3 +7,62 @@ While most of the links mentioned in this guide are relatively "safe"; we urge u
- [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>
```

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",
@ -146,9 +147,9 @@
"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": {

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()

View file

@ -607,6 +607,12 @@ object NativeLibrary {
*/
external fun addFileToFilesystemProvider(path: String)
/**
* Adds a game-folder file to the manual filesystem provider, respecting the internal gate for
* game-folder external-content mounting.
*/
external fun addGameFolderFileToFilesystemProvider(path: String)
/**
* Clears all files added to the manual filesystem provider in our EmulationSession instance
*/

View file

@ -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() }
}
}
}

View file

@ -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)
}
}

View file

@ -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) }

View file

@ -1,10 +1,11 @@
// 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.fetcher
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
@ -15,8 +16,20 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat
state: RecyclerView.State
) {
outRect.bottom = spacing
if (parent.getChildAdapterPosition(view) == 0) {
val position = parent.getChildAdapterPosition(view)
if (position == RecyclerView.NO_POSITION) return
if (position == 0) {
outRect.top = spacing
return
}
// If the item is in the first row, but NOT in first column add top spacing as well
val layoutManager = parent.layoutManager
if (layoutManager is GridLayoutManager && layoutManager.spanSizeLookup.getSpanGroupIndex(position, layoutManager.spanCount) == 0) {
outRect.top = spacing
return
}
}
}

View file

@ -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);
}

View file

@ -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
@ -111,10 +111,18 @@ class SettingsActivity : AppCompatActivity() {
if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
navHostFragment.navController.popBackStack()
} else {
finish()
finishWithFragmentLikeAnimation()
}
}
private fun finishWithFragmentLikeAnimation() {
finish()
overridePendingTransition(
androidx.navigation.ui.R.anim.nav_default_pop_enter_anim,
androidx.navigation.ui.R.anim.nav_default_pop_exit_anim
)
}
override fun onStart() {
super.onStart()
if (!DirectoryInitialization.areDirectoriesReady) {
@ -170,7 +178,7 @@ class SettingsActivity : AppCompatActivity() {
getString(R.string.settings_reset),
Toast.LENGTH_LONG
).show()
finish()
finishWithFragmentLikeAnimation()
}
private fun setInsets() {

View file

@ -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
@ -68,7 +68,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (val item = settingsViewModel.clickedItem) {
val item = settingsViewModel.clickedItem ?: return@setPositiveButton
clearDialogState()
when (item) {
is AnalogInputSetting -> {
val stickParam = NativeInput.getStickParam(
item.playerIndex,
@ -107,12 +109,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
}
else -> {
settingsViewModel.clickedItem!!.setting.reset()
item.setting.reset()
settingsViewModel.setAdapterItemChanged(position)
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
clearDialogState()
}
.setOnCancelListener {
clearDialogState()
}
.create()
}
@ -186,27 +193,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
updateButtonState(isValid)
}
/*
* xbzk: these two events, along with attachRepeat feature,
* were causing spinbox buttons to respond twice per press
* cutting these out to retain accelerated press functionality
* TODO: clean this out later if no issues arise
*
spinboxBinding.buttonDecrement.setOnClickListener {
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
val newValue = current - 1
spinboxBinding.editValue.setText(newValue.toString())
updateValidity(newValue)
}
spinboxBinding.buttonIncrement.setOnClickListener {
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
val newValue = current + 1
spinboxBinding.editValue.setText(newValue.toString())
updateValidity(newValue)
}
*/
fun attachRepeat(button: View, delta: Int) {
val handler = Handler(Looper.getMainLooper())
var runnable: Runnable? = null
@ -439,9 +425,13 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
private fun closeDialog() {
settingsViewModel.setAdapterItemChanged(position)
clearDialogState()
dismiss()
}
private fun clearDialogState() {
settingsViewModel.clickedItem = null
settingsViewModel.setSliderProgress(-1f)
dismiss()
}
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {

View file

@ -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

View file

@ -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,
@ -1066,7 +1053,10 @@ class SettingsFragmentPresenter(
IntSetting.THEME.getValueAsString()
override val defaultValue: Int = IntSetting.THEME.defaultValue
override fun reset() = IntSetting.THEME.setInt(defaultValue)
override fun reset() {
IntSetting.THEME.setInt(defaultValue)
settingsViewModel.setShouldRecreate(true)
}
}
add(HeaderSetting(R.string.app_settings))
@ -1081,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) {
@ -1123,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)
@ -1146,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(

View file

@ -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
@ -54,8 +51,8 @@ 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()
}

View file

@ -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
@ -19,7 +19,6 @@ 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 +41,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)
@ -122,12 +121,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 +168,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 {

View file

@ -29,6 +29,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 +88,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,7 +140,7 @@ 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()
}

View file

@ -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()
}

View file

@ -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
@ -310,6 +310,21 @@ 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)
}
)
)
}
if (GpuDriverHelper.supportsCustomDriverLoading()) {
add(
SubmenuProperty(
@ -341,18 +356,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,

View file

@ -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
@ -44,7 +41,9 @@ 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 +70,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(
@ -144,6 +147,18 @@ class HomeSettingsFragment : Fragment() {
driverViewModel.selectedDriverTitle
)
)
if (GpuDriverHelper.isAdrenoGpu()) {
add(
HomeSetting(
R.string.freedreno_settings_title,
R.string.gpu_driver_settings,
R.drawable.ic_graphics,
{
binding.root.findNavController().navigate(R.id.freedrenoSettingsFragment)
}
)
)
}
add(
HomeSetting(
R.string.multiplayer,
@ -465,19 +480,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

View file

@ -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,28 +147,33 @@ 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
}
// Check if there are multiple update versions
val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update }
val hasMultipleUpdates = updates.size > 1
NativeConfig.setDisabledAddons(
game!!.programId,
_patchList.value.mapNotNull {
currentGame.programId,
currentList.mapNotNull {
if (it.enabled) {
null
} else {
if (PatchType.from(it.type) == PatchType.Update) {
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
it.name
} else if (hasMultipleUpdates) {
} else if (it.numericVersion != 0L) {
"Update@${it.numericVersion}"
} else {
it.name
@ -152,7 +185,8 @@ class AddonViewModel : ViewModel() {
}.toTypedArray()
)
NativeConfig.saveGlobalConfig()
_patchList.value.clear()
_patchList.value = emptyList()
loadedGameKey = null
game = null
}
@ -163,4 +197,8 @@ class AddonViewModel : ViewModel() {
fun showModNoticeDialog(show: Boolean) {
_showModNoticeDialog.value = show
}
private fun gameKey(game: Game): String {
return "${game.programId}|${game.path}"
}
}

View file

@ -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
}

View file

@ -424,7 +424,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
val uriString = result.toString()
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
val folder = gamesViewModel.folders.value.firstOrNull {
it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT
}
if (folder != null) {
Toast.makeText(
applicationContext,
@ -640,7 +642,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
addonViewModel.refreshAddons()
addonViewModel.refreshAddons(force = true)
val separator = System.lineSeparator() ?: "\n"
val installResult = StringBuilder()

View file

@ -51,11 +51,24 @@ object GameHelper {
// Scan External Content directories and register all NSP/XCI files
val externalContentDirs = NativeConfig.getExternalContentDirs()
for (externalDir in externalContentDirs) {
val uniqueExternalContentDirs = linkedSetOf<String>()
externalContentDirs.forEach { externalDir ->
if (externalDir.isNotEmpty()) {
uniqueExternalContentDirs.add(externalDir)
}
}
val mountedContainerUris = mutableSetOf<String>()
for (externalDir in uniqueExternalContentDirs) {
if (externalDir.isNotEmpty()) {
val externalDirUri = externalDir.toUri()
if (FileUtil.isTreeUriValid(externalDirUri)) {
scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3)
scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
val containerUri = it.uri.toString()
if (mountedContainerUris.add(containerUri)) {
NativeLibrary.addFileToFilesystemProvider(containerUri)
}
}
}
}
}
@ -65,10 +78,13 @@ object GameHelper {
val gameDirUri = gameDir.uriString.toUri()
val isValid = FileUtil.isTreeUriValid(gameDirUri)
if (isValid) {
val scanDepth = if (gameDir.deepScan) 3 else 1
addGamesRecursive(
games,
FileUtil.listFiles(gameDirUri),
if (gameDir.deepScan) 3 else 1
scanDepth,
mountedContainerUris
)
} else {
badDirs.add(index)
@ -103,9 +119,10 @@ object GameHelper {
// be done better imo.
private val externalContentExtensions = setOf("nsp", "xci")
private fun scanExternalContentRecursive(
private fun scanContentContainersRecursive(
files: Array<MinimalDocumentFile>,
depth: Int
depth: Int,
onContainerFound: (MinimalDocumentFile) -> Unit
) {
if (depth <= 0) {
return
@ -113,14 +130,15 @@ object GameHelper {
files.forEach {
if (it.isDirectory) {
scanExternalContentRecursive(
scanContentContainersRecursive(
FileUtil.listFiles(it.uri),
depth - 1
depth - 1,
onContainerFound
)
} else {
val extension = FileUtil.getExtension(it.uri).lowercase()
if (externalContentExtensions.contains(extension)) {
NativeLibrary.addFileToFilesystemProvider(it.uri.toString())
onContainerFound(it)
}
}
}
@ -129,7 +147,8 @@ object GameHelper {
private fun addGamesRecursive(
games: MutableList<Game>,
files: Array<MinimalDocumentFile>,
depth: Int
depth: Int,
mountedContainerUris: MutableSet<String>
) {
if (depth <= 0) {
return
@ -140,11 +159,20 @@ object GameHelper {
addGamesRecursive(
games,
FileUtil.listFiles(it.uri),
depth - 1
depth - 1,
mountedContainerUris
)
} else {
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
val game = getGame(it.uri, true)
val extension = FileUtil.getExtension(it.uri).lowercase()
val filePath = it.uri.toString()
if (externalContentExtensions.contains(extension) &&
mountedContainerUris.add(filePath)) {
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
}
if (Game.extensions.contains(extension)) {
val game = getGame(it.uri, true, false)
if (game != null) {
games.add(game)
}
@ -153,14 +181,20 @@ object GameHelper {
}
}
fun getGame(uri: Uri, addedToLibrary: Boolean): Game? {
fun getGame(
uri: Uri,
addedToLibrary: Boolean,
registerFilesystemProvider: Boolean = true
): Game? {
val filePath = uri.toString()
if (!GameMetadata.getIsValid(filePath)) {
return null
}
// Needed to update installed content information
NativeLibrary.addFileToFilesystemProvider(filePath)
if (registerFilesystemProvider) {
// Needed to update installed content information
NativeLibrary.addFileToFilesystemProvider(filePath)
}
var name = GameMetadata.getTitle(filePath)

View file

@ -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.utils
@ -80,16 +80,14 @@ object PathUtil {
}
}
// This really shouldn't be necessary, but the Android API seemingly
// doesn't have a way of doing this?
// Apparently, on certain devices the mount location can vary, so add
// extra cases here if we discover any new ones.
fun getRemovableStoragePath(idString: String): String? {
var pathFile: File
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
pathFile = File("/mnt/media_rw/$idString");
if (pathFile.exists()) {
return pathFile.absolutePath
for (mountPath in possibleMountPaths) {
val pathFile = File(mountPath);
if (pathFile.exists()) {
return pathFile.absolutePath
}
}
return null

View file

@ -33,6 +33,12 @@ void AndroidConfig::ReadAndroidValues() {
if (global) {
ReadAndroidUIValues();
ReadUIValues();
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
std::string("ext_content_from_game_dirs"),
std::make_optional(
Settings::values.ext_content_from_game_dirs.GetDefault()));
EndGroup();
ReadOverlayValues();
}
ReadDriverValues();

View file

@ -96,6 +96,11 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
return false;
}
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
!Loader::IsBootableGameContainer(file, file_type)) {
return false;
}
u64 program_id = 0;
Loader::ResultStatus res = loader->ReadProgramId(program_id);
if (res != Loader::ResultStatus::Success) {

View file

@ -217,107 +217,8 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
return;
}
const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1));
if (extension == "nsp") {
auto nsp = std::make_shared<FileSys::NSP>(file);
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
std::map<u64, u32> nsp_versions;
std::map<u64, std::string> nsp_version_strings;
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == FileSys::ContentRecordType::Meta) {
const auto meta_nca = std::make_shared<FileSys::NCA>(nca->GetBaseFile());
if (meta_nca->GetStatus() == Loader::ResultStatus::Success) {
const auto section0 = meta_nca->GetSubdirectories();
if (!section0.empty()) {
for (const auto& meta_file : section0[0]->GetFiles()) {
if (meta_file->GetExtension() == "cnmt") {
FileSys::CNMT cnmt(meta_file);
nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
}
}
}
}
}
if (content_type == FileSys::ContentRecordType::Control &&
title_type == FileSys::TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = FileSys::ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
FileSys::NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
nsp_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type == FileSys::TitleType::Update) {
u32 version = 0;
auto ver_it = nsp_versions.find(title_id);
if (ver_it != nsp_versions.end()) {
version = ver_it->second;
}
std::string version_string;
auto str_it = nsp_version_strings.find(title_id);
if (str_it != nsp_version_strings.end()) {
version_string = str_it->second;
}
m_manual_provider->AddEntryWithVersion(
title_type, content_type, title_id, version, version_string,
nca->GetBaseFile());
LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}",
title_id, version, version_string);
} else {
// Use regular AddEntry for non-updates
m_manual_provider->AddEntry(title_type, content_type, title_id,
nca->GetBaseFile());
LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}",
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
}
}
}
return;
}
}
// Handle XCI files
if (extension == "xci") {
FileSys::XCI xci{file};
if (xci.GetStatus() == Loader::ResultStatus::Success) {
const auto nsp = xci.GetSecurePartitionNSP();
if (nsp) {
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
return;
}
if (m_manual_provider->AddEntriesFromContainer(file)) {
return;
}
auto loader = Loader::GetLoader(m_system, file);
@ -339,6 +240,13 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
}
}
void EmulationSession::ConfigureFilesystemProviderFromGameFolder(const std::string& filepath) {
if (!Settings::values.ext_content_from_game_dirs.GetValue()) {
return;
}
ConfigureFilesystemProvider(filepath);
}
void EmulationSession::InitializeSystem(bool reload) {
if (!reload) {
// Initialize logging system
@ -1499,7 +1407,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;
}
@ -1609,6 +1517,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
Common::Android::GetJString(env, jpath));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_addGameFolderFileToFilesystemProvider(
JNIEnv* env, jobject jobj, jstring jpath) {
EmulationSession::GetInstance().ConfigureFilesystemProviderFromGameFolder(
Common::Android::GetJString(env, jpath));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -46,6 +49,7 @@ public:
const Core::PerfStatsResults& PerfStats();
int ShadersBuilding();
void ConfigureFilesystemProvider(const std::string& filepath);
void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath);
void InitializeSystem(bool reload);
void SetAppletId(int applet_id);
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,

View file

@ -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" />

View file

@ -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>

View file

@ -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>

View file

@ -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"
>

View file

@ -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>

View file

@ -5,6 +5,7 @@
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

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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="@style/EdenCard"
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">
<LinearLayout
android:layout_width="match_parent"
@ -42,30 +46,37 @@
</LinearLayout>
<Button
android:id="@+id/button_export"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
<LinearLayout
android:layout_width="wrap_content"
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:gravity="center_horizontal"
android:orientation="vertical">
<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_export"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/export"
android:tooltipText="@string/export"
android:visibility="gone"
app:icon="@drawable/ic_export"
tools:visibility="visible" />
<Button
android:id="@+id/button_install"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:contentDescription="@string/string_import"
android:tooltipText="@string/string_import"
android:visibility="gone"
app:icon="@drawable/ic_import"
tools:visibility="visible" />
</LinearLayout>
</LinearLayout>

View file

@ -6,63 +6,75 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="12dp">
android:layout_marginVertical="12dp"
android:background="@android:color/transparent"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp">
<LinearLayout
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>
<LinearLayout
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
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"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="@string/string_import"
android:tooltipText="@string/string_import"
android:visibility="gone"
@ -71,11 +83,10 @@
<Button
android:id="@+id/button_export"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="8dp"
android:layout_marginStart="12dp"
android:contentDescription="@string/export"
android:tooltipText="@string/export"
android:visibility="gone"

View file

@ -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"
@ -81,14 +85,15 @@
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.chip.ChipGroup
<LinearLayout
android:id="@+id/layoutSecondaryActions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end|center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:paddingVertical="8dp"
android:visibility="gone"
app:singleLine="false"
tools:visibility="visible" />
</LinearLayout>

View file

@ -8,16 +8,14 @@
<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"
android:background="?attr/colorSurface"
android:elevation="0dp">
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_scrollFlags="scroll|enterAlways|snap">
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"

View file

@ -59,8 +59,11 @@
android:layout_marginBottom="16dp"
android:text="@string/multiplayer_public_room"
app:cornerRadius="16dp"
app:backgroundTint="?attr/colorPrimary"
app:icon="@drawable/ic_search"
app:iconPadding="12dp" />
app:iconPadding="12dp"
app:iconTint="?attr/colorOnPrimary"
android:textColor="?attr/colorOnPrimary" />
<Space
android:layout_width="20dp"

View file

@ -5,6 +5,7 @@
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
@ -51,12 +52,15 @@
android:src="@drawable/ic_yuzu" />
<com.google.android.material.card.MaterialCardView
style="@style/EdenCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
app:cardCornerRadius="16dp">
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -84,14 +88,17 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/button_contributors"
style="@style/EdenCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="12dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp">
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -119,14 +126,17 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/button_licenses"
style="@style/EdenCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="12dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp">
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -161,7 +171,7 @@
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="?attr/colorSurface"
app:cardBackgroundColor="@android:color/transparent"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:cardElevation="0dp">

View file

@ -8,6 +8,7 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_addons"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
@ -18,6 +19,7 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_addons"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"

View file

@ -8,6 +8,7 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_applets"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
@ -15,6 +16,7 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_applets"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"

View file

@ -7,19 +7,22 @@
android:layout_height="match_parent"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_drivers"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:liftOnScrollTargetViewId="@id/list_drivers">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_drivers"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"

View file

@ -13,14 +13,15 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_drivers"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"
app:liftOnScrollTargetViewId="@id/list_drivers">
android:touchscreenBlocksFocus="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_drivers"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"

View file

@ -12,14 +12,15 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_folders"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"
app:liftOnScrollTargetViewId="@id/list_folders">
android:touchscreenBlocksFocus="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_folders"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"

View file

@ -8,35 +8,24 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_freedreno"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"
app:elevation="0dp">
android:touchscreenBlocksFocus="false">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_freedreno_layout"
style="?attr/collapsingToolbarLayoutMediumStyle"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_freedreno"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:contentScrim="?attr/colorSurface"
app:scrimVisibleHeightTrigger="100dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_freedreno"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/ic_back"
app:title="@string/gpu_driver_settings" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"
app:navigationIcon="@drawable/ic_back" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_freedreno"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
@ -63,8 +52,12 @@
android:id="@+id/list_freedreno_presets"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:scrollbars="horizontal"
android:scrollbarStyle="outsideInset"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<!-- Current Settings Section -->
@ -135,22 +128,23 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp"
android:spacing="8dp">
android:orientation="horizontal">
<Button
android:id="@+id/button_clear_all"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_weight="1"
android:text="@string/freedreno_clear_all"
style="?attr/materialButtonOutlinedStyle" />
style="?attr/materialButtonOutlinedStyle"
android:text="@string/freedreno_clear_all" />
<Button
android:id="@+id/button_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:text="@string/save" />
@ -161,6 +155,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline">

View file

@ -33,9 +33,12 @@
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@android:color/transparent"
app:icon="@drawable/ic_back"
app:iconSize="24dp"
app:iconTint="?attr/colorOnSurface"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -44,9 +47,12 @@
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@android:color/transparent"
app:icon="@drawable/ic_shortcut"
app:iconSize="24dp"
app:iconTint="?attr/colorOnSurface"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -37,7 +37,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"
>
@ -61,7 +66,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"
>
@ -85,7 +95,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"
>
@ -117,7 +132,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"
>

View file

@ -1,38 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scroll_view_settings"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:clipToPadding="false"
android:background="?attr/colorSurface"
android:defaultFocusHighlightEnabled="false">
android:background="?attr/colorSurface">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/linear_layout_settings"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_home_settings"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="16dp">
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/logo_image"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginTop="48dp"
android:layout_marginBottom="24dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_yuzu" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_home_settings"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"
app:navigationIcon="@drawable/ic_back"
app:title="@string/preferences_settings" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_settings_list"
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view_settings"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/transparent"
android:clipToPadding="false"
android:defaultFocusHighlightEnabled="false"
android:fadeScrollbars="false"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar_home_settings">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/linear_layout_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:nestedScrollingEnabled="false" />
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingTop="16dp">
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_settings_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:nestedScrollingEnabled="false" />
</androidx.core.widget.NestedScrollView>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -8,6 +8,7 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_installables"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
@ -15,6 +16,7 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_installables"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"

View file

@ -10,12 +10,14 @@
<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"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_profiles"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/profile_manager"

View file

@ -8,30 +8,19 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_settings"
style="@style/Widget.Eden.TransparentTopAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"
app:elevation="0dp">
android:touchscreenBlocksFocus="false">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_settings_layout"
style="?attr/collapsingToolbarLayoutMediumStyle"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_settings"
style="@style/Widget.Eden.TransparentTopToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:contentScrim="?attr/colorSurface"
app:scrimVisibleHeightTrigger="100dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_settings"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/ic_back" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"
app:navigationIcon="@drawable/ic_back" />
</com.google.android.material.appbar.AppBarLayout>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp" />

View file

@ -11,8 +11,11 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
@ -78,8 +81,11 @@
android:layout_width="48dp"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="10dp"
app:cardElevation="2dp"
app:cardElevation="0dp"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
@ -100,7 +106,7 @@
android:layout_gravity="center"
android:background="@null"
android:src="@drawable/ic_delete"
app:tint="@color/eden_border_gradient_end"
app:tint="?attr/colorPrimary"
android:contentDescription="@string/delete" />
</FrameLayout>

View file

@ -40,6 +40,10 @@
<action
android:id="@+id/action_global_settingsActivity"
app:destination="@id/settingsActivity" />
app:destination="@id/settingsActivity"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</navigation>

View file

@ -101,7 +101,11 @@
<action
android:id="@+id/action_global_settingsActivity"
app:destination="@id/settingsActivity" />
app:destination="@id/settingsActivity"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<fragment
android:id="@+id/installableFragment"
android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"

View file

@ -191,6 +191,22 @@
<item name="android:textColor">@color/eden_primary</item>
<item name="rippleColor">@color/eden_glow_pink</item>
</style>
<style name="Widget.Eden.TransparentTopAppBarLayout" parent="">
<item name="android:background">@android:color/transparent</item>
<item name="android:elevation">0dp</item>
<item name="android:stateListAnimator">@null</item>
<item name="elevation">0dp</item>
<item name="liftOnScroll">false</item>
<item name="statusBarForeground">@android:color/transparent</item>
</style>
<style name="Widget.Eden.TransparentTopToolbar" parent="">
<item name="android:background">@android:color/transparent</item>
<item name="android:elevation">0dp</item>
<item name="backgroundTint">@android:color/transparent</item>
</style>
<style name="EdenPopupMenu" parent="Widget.Material3.PopupMenu">
<item name="android:popupBackground">@drawable/popup_menu_background</item>
</style>

View file

@ -516,7 +516,7 @@ namespace Common::Android {
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
s_patch_constructor = env->GetMethodID(
patch_class, "<init>",
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;J)V");
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V");
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -434,16 +437,8 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
break;
}
if (True(filter & DirEntryFilter::File) &&
entry.status().type() == fs::file_type::regular) {
if (!callback(entry)) {
callback_error = true;
break;
}
}
if (True(filter & DirEntryFilter::Directory) &&
entry.status().type() == fs::file_type::directory) {
if ((True(filter & DirEntryFilter::File) && entry.status().type() == fs::file_type::regular)
|| (True(filter & DirEntryFilter::Directory) && entry.status().type() == fs::file_type::directory)) {
if (!callback(entry)) {
callback_error = true;
break;
@ -485,44 +480,24 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
std::error_code ec;
// TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC.
for (const auto& entry : fs::directory_iterator(path, ec)) {
// MSVC should now be fixed... right... right?!?!?!
for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
if (ec) {
break;
}
if (True(filter & DirEntryFilter::File) &&
entry.status().type() == fs::file_type::regular) {
if ((True(filter & DirEntryFilter::File) && entry.status().type() == fs::file_type::regular)
|| (True(filter & DirEntryFilter::Directory) && entry.status().type() == fs::file_type::directory)) {
if (!callback(entry)) {
callback_error = true;
break;
}
}
if (True(filter & DirEntryFilter::Directory) &&
entry.status().type() == fs::file_type::directory) {
if (!callback(entry)) {
callback_error = true;
break;
}
}
// TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator.
// recursive_directory_iterator throws an exception despite passing in a std::error_code.
if (entry.status().type() == fs::file_type::directory) {
IterateDirEntriesRecursively(entry.path(), callback, filter);
}
}
if (callback_error || ec) {
LOG_ERROR(Common_Filesystem,
"Failed to visit all the directory entries of path={}, ec_message={}",
PathToUTF8String(path), ec.message());
LOG_ERROR(Common_Filesystem, "Failed to visit all the directory entries of path={}, ec_message={}", PathToUTF8String(path), ec.message());
return;
}
LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}",
PathToUTF8String(path));
LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", PathToUTF8String(path));
}
// Generic Filesystem Operations

View file

@ -27,6 +27,10 @@
#include "common/settings.h"
#include "common/time_zone.h"
#if defined(__linux__ ) && defined(ARCHITECTURE_arm64)
#include <unistd.h>
#endif
namespace Settings {
// Clang 14 and earlier have errors when explicitly instantiating these classes
@ -178,7 +182,11 @@ bool IsFastmemEnabled() {
return bool(values.cpuopt_fastmem);
else if (values.cpu_accuracy.GetValue() == CpuAccuracy::Unsafe)
return bool(values.cpuopt_unsafe_host_mmu);
#if !defined(__APPLE__) && !defined(__linux__) && !defined(__ANDROID__) && !defined(_WIN32)
#if defined(__linux__) && defined(ARCHITECTURE_arm64)
// Only 4kb systems support host MMU right now
// TODO: Support this
return getpagesize() == 4096;
#elif !defined(__APPLE__) && !defined(__ANDROID__) && !defined(_WIN32) && !defined(__linux__)
return false;
#else
return true;

View file

@ -706,6 +706,7 @@ struct Values {
Setting<bool> pause_tas_on_load{linkage, true, "pause_tas_on_load", Category::Controls};
Setting<bool> tas_enable{linkage, false, "tas_enable", Category::Controls};
Setting<bool> tas_loop{linkage, false, "tas_loop", Category::Controls};
Setting<bool> tas_show_recording_dialog{linkage, true, "tas_show_recording_dialog", Category::Controls};
Setting<bool> mouse_panning{
linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false};
@ -756,6 +757,8 @@ struct Values {
Category::DataStorage};
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
Category::DataStorage};
Setting<bool> ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs",
Category::DataStorage};
std::vector<std::string> external_content_dirs;
// Debugging
@ -774,6 +777,7 @@ struct Values {
Setting<bool> reporting_services{
linkage, false, "reporting_services", Category::Debugging, Specialization::Default, false};
Setting<bool> quest_flag{linkage, false, "quest_flag", Category::Debugging};
Setting<bool> use_dev_keys{linkage, false, "use_dev_keys", Category::Debugging};
Setting<bool> disable_macro_jit{linkage, false, "disable_macro_jit",
Category::DebuggingGraphics};
Setting<bool> disable_macro_hle{linkage, false, "disable_macro_hle",

View file

@ -19,10 +19,12 @@
#include <windows.h>
#include "common/string_util.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#if defined(__FreeBSD__)
#include <sys/cpuset.h>
#include <sys/_cpuset.h>
#include <pthread_np.h>
#elif defined(__DragonFly__) || defined(__OpenBSD__) || defined(__Bitrig__)
#include <pthread_np.h>
#endif
#include <pthread.h>
#include <sched.h>

View file

@ -1220,7 +1220,7 @@ target_link_libraries(core PRIVATE
RenderDoc::API
ZLIB::ZLIB)
target_link_libraries(core PRIVATE httplib::httplib)
target_link_libraries(core PUBLIC httplib::httplib zstd::zstd)
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PUBLIC ENABLE_WEB_SERVICE)

View file

@ -21,6 +21,7 @@
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
@ -642,8 +643,15 @@ void KeyManager::ReloadKeys() {
const auto keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir);
if (!Common::FS::CreateDir(keys_dir))
LOG_ERROR(Core, "Failed to create the keys directory.");
LoadFromFile(keys_dir / "prod.keys_autogenerated", false);
LoadFromFile(keys_dir / "prod.keys", false);
if (Settings::values.use_dev_keys.GetValue()) {
dev_mode = true;
LoadFromFile(keys_dir / "dev.keys_autogenerated", false);
LoadFromFile(keys_dir / "dev.keys", false);
} else {
dev_mode = false;
LoadFromFile(keys_dir / "prod.keys_autogenerated", false);
LoadFromFile(keys_dir / "prod.keys", false);
}
LoadFromFile(keys_dir / "title.keys_autogenerated", true);
LoadFromFile(keys_dir / "title.keys", true);
LoadFromFile(keys_dir / "console.keys_autogenerated", false);
@ -838,7 +846,7 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
std::string filename = "title.keys_autogenerated";
if (category == KeyCategory::Standard) {
filename = "prod.keys_autogenerated";
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
} else if (category == KeyCategory::Console) {
filename = "console.keys_autogenerated";
}
@ -936,6 +944,8 @@ bool KeyManager::KeyFileExists(bool title) {
const auto keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir);
if (title)
return Common::FS::Exists(keys_dir / "title.keys");
if (Settings::values.use_dev_keys.GetValue())
return Common::FS::Exists(keys_dir / "dev.keys");
return Common::FS::Exists(keys_dir / "prod.keys");
}

View file

@ -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: Copyright 2018 yuzu Emulator Project
@ -314,6 +314,7 @@ private:
std::array<u8, 576> eticket_extended_kek{};
RSAKeyPair<2048> eticket_rsa_keypair{};
bool dev_mode;
void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys);
template <size_t Size>

View file

@ -76,16 +76,16 @@ public:
template <typename Func>
void ApplyOpOnPAddr(PAddr address, Common::ScratchBuffer<u32>& buffer, Func&& operation) {
DAddr subbits = static_cast<DAddr>(address & page_mask);
DAddr subbits = DAddr(address & page_mask);
const u32 base = compressed_device_addr[(address >> page_bits)];
if ((base >> MULTI_FLAG_BITS) == 0) [[likely]] {
const DAddr d_address = (static_cast<DAddr>(base) << page_bits) + subbits;
const DAddr d_address = (DAddr(base) << page_bits) + subbits;
operation(d_address);
return;
}
InnerGatherDeviceAddresses(buffer, address);
for (u32 value : buffer) {
operation((static_cast<DAddr>(value) << page_bits) + subbits);
operation((DAddr(value) << page_bits) + subbits);
}
}
@ -96,12 +96,12 @@ public:
}
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
PAddr subbits = static_cast<PAddr>(address & page_mask);
auto paddr = compressed_physical_ptr[(address >> page_bits)];
PAddr subbits = PAddr(address & page_mask);
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
if (paddr == 0) {
return 0;
}
return (static_cast<PAddr>(paddr - 1) << page_bits) + subbits;
return (PAddr(paddr - 1) << page_bits) + subbits;
}
template <typename T>
@ -172,9 +172,14 @@ private:
const uintptr_t physical_base;
DeviceInterface* device_inter;
Common::VirtualBuffer<u32> compressed_physical_ptr;
struct TrackedEntry {
VAddr cpu_backing_address;
u32 continuity_tracker;
u32 compressed_physical_ptr;
};
Common::VirtualBuffer<u32> compressed_device_addr;
Common::VirtualBuffer<u32> continuity_tracker;
Common::VirtualBuffer<TrackedEntry> tracked_entries;
// Process memory interfaces
@ -189,17 +194,16 @@ private:
static constexpr size_t asid_start_bit = guest_max_as_bits;
std::pair<Asid, VAddr> ExtractCPUBacking(size_t page_index) {
auto content = cpu_backing_address[page_index];
auto content = tracked_entries[page_index].cpu_backing_address;
const VAddr address = content & guest_mask;
const Asid asid{static_cast<size_t>(content >> asid_start_bit)};
return std::make_pair(asid, address);
}
void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) {
cpu_backing_address[page_index] = address | (asid.id << asid_start_bit);
tracked_entries[page_index].cpu_backing_address = address | (asid.id << asid_start_bit);
}
Common::VirtualBuffer<VAddr> cpu_backing_address;
std::array<TranslationEntry, 4> t_slot{};
u32 cache_cursor = 0;
using CounterType = u8;

View file

@ -166,29 +166,21 @@ struct DeviceMemoryManagerAllocator {
template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{reinterpret_cast<const uintptr_t>(device_memory_.buffer.BackingBasePointer())},
device_inter{nullptr}, compressed_physical_ptr(device_as_size >> Memory::YUZU_PAGEBITS),
compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() ==
Settings::MemoryLayout::Memory_4Gb
? physical_min_bits
: physical_max_bits) -
Memory::YUZU_PAGEBITS)),
continuity_tracker(device_as_size >> Memory::YUZU_PAGEBITS),
cpu_backing_address(device_as_size >> Memory::YUZU_PAGEBITS) {
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
, device_inter{nullptr}
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
{
impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>();
cached_pages = std::make_unique<CachedPages>();
const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS;
for (size_t i = 0; i < total_virtual; i++) {
compressed_physical_ptr[i] = 0;
continuity_tracker[i] = 1;
cpu_backing_address[i] = 0;
tracked_entries[i].compressed_physical_ptr = 0;
tracked_entries[i].continuity_tracker = 1;
tracked_entries[i].cpu_backing_address = 0;
}
const size_t total_phys = 1ULL << ((Settings::values.memory_layout_mode.GetValue() ==
Settings::MemoryLayout::Memory_4Gb
? physical_min_bits
: physical_max_bits) -
Memory::YUZU_PAGEBITS);
const size_t total_phys = 1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS);
for (size_t i = 0; i < total_phys; i++) {
compressed_device_addr[i] = 0;
}
@ -228,11 +220,11 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size
const VAddr new_vaddress = virtual_address + i * Memory::YUZU_PAGESIZE;
auto* ptr = process_memory->GetPointerSilent(Common::ProcessAddress(new_vaddress));
if (ptr == nullptr) [[unlikely]] {
compressed_physical_ptr[start_page_d + i] = 0;
tracked_entries[start_page_d + i].compressed_physical_ptr = 0;
continue;
}
auto phys_addr = static_cast<u32>(GetRawPhysicalAddr(ptr) >> Memory::YUZU_PAGEBITS) + 1U;
compressed_physical_ptr[start_page_d + i] = phys_addr;
tracked_entries[start_page_d + i].compressed_physical_ptr = phys_addr;
InsertCPUBacking(start_page_d + i, new_vaddress, asid);
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
const u32 new_dev = static_cast<u32>(start_page_d + i);
@ -260,9 +252,9 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) {
device_inter->InvalidateRegion(address, size);
std::scoped_lock lk(mapping_guard);
for (size_t i = 0; i < num_pages; i++) {
auto phys_addr = compressed_physical_ptr[start_page_d + i];
compressed_physical_ptr[start_page_d + i] = 0;
cpu_backing_address[start_page_d + i] = 0;
auto phys_addr = tracked_entries[start_page_d + i].compressed_physical_ptr;
tracked_entries[start_page_d + i].compressed_physical_ptr = 0;
tracked_entries[start_page_d + i].cpu_backing_address = 0;
if (phys_addr != 0) [[likely]] {
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
if ((base_dev >> MULTI_FLAG_BITS) == 0) [[likely]] {
@ -300,14 +292,14 @@ void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtu
page_count = 1;
}
last_ptr = new_ptr;
continuity_tracker[start_page_d + index] = static_cast<u32>(page_count);
tracked_entries[start_page_d + index].continuity_tracker = static_cast<u32>(page_count);
}
}
template <typename Traits>
u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) {
size_t page_index = src_addr >> page_bits;
size_t subbits = src_addr & page_mask;
if ((static_cast<size_t>(continuity_tracker[page_index]) << page_bits) >= size + subbits) {
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker) << page_bits) >= size + subbits) {
return GetPointer<u8>(src_addr);
}
return nullptr;
@ -317,7 +309,7 @@ template <typename Traits>
const u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) const {
size_t page_index = src_addr >> page_bits;
size_t subbits = src_addr & page_mask;
if ((static_cast<size_t>(continuity_tracker[page_index]) << page_bits) >= size + subbits) {
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker) << page_bits) >= size + subbits) {
return GetPointer<u8>(src_addr);
}
return nullptr;
@ -342,12 +334,10 @@ template <typename T>
T* DeviceMemoryManager<Traits>::GetPointer(DAddr address) {
const size_t index = address >> Memory::YUZU_PAGEBITS;
const size_t offset = address & Memory::YUZU_PAGEMASK;
auto phys_addr = compressed_physical_ptr[index];
if (phys_addr == 0) [[unlikely]] {
auto phys_addr = tracked_entries[index].compressed_physical_ptr;
if (phys_addr == 0) [[unlikely]]
return nullptr;
}
return GetPointerFromRaw<T>((static_cast<PAddr>(phys_addr - 1) << Memory::YUZU_PAGEBITS) +
offset);
return GetPointerFromRaw<T>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS) + offset);
}
template <typename Traits>
@ -355,12 +345,10 @@ template <typename T>
const T* DeviceMemoryManager<Traits>::GetPointer(DAddr address) const {
const size_t index = address >> Memory::YUZU_PAGEBITS;
const size_t offset = address & Memory::YUZU_PAGEMASK;
auto phys_addr = compressed_physical_ptr[index];
if (phys_addr == 0) [[unlikely]] {
auto phys_addr = tracked_entries[index].compressed_physical_ptr;
if (phys_addr == 0)
return nullptr;
}
return GetPointerFromRaw<T>((static_cast<PAddr>(phys_addr - 1) << Memory::YUZU_PAGEBITS) +
offset);
return GetPointerFromRaw<T>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS) + offset);
}
template <typename Traits>
@ -386,18 +374,14 @@ T DeviceMemoryManager<Traits>::Read(DAddr address) const {
}
template <typename Traits>
void DeviceMemoryManager<Traits>::WalkBlock(DAddr addr, std::size_t size, auto on_unmapped,
auto on_memory, auto increment) {
void DeviceMemoryManager<Traits>::WalkBlock(DAddr addr, std::size_t size, auto on_unmapped, auto on_memory, auto increment) {
std::size_t remaining_size = size;
std::size_t page_index = addr >> Memory::YUZU_PAGEBITS;
std::size_t page_offset = addr & Memory::YUZU_PAGEMASK;
while (remaining_size) {
const size_t next_pages = static_cast<std::size_t>(continuity_tracker[page_index]);
const std::size_t copy_amount =
(std::min)((next_pages << Memory::YUZU_PAGEBITS) - page_offset, remaining_size);
const auto current_vaddr =
static_cast<u64>((page_index << Memory::YUZU_PAGEBITS) + page_offset);
const size_t next_pages = std::size_t(tracked_entries[page_index].continuity_tracker);
const std::size_t copy_amount = (std::min)((next_pages << Memory::YUZU_PAGEBITS) - page_offset, remaining_size);
const auto current_vaddr = u64((page_index << Memory::YUZU_PAGEBITS) + page_offset);
SCOPE_EXIT{
page_index += next_pages;
page_offset = 0;
@ -405,13 +389,12 @@ void DeviceMemoryManager<Traits>::WalkBlock(DAddr addr, std::size_t size, auto o
remaining_size -= copy_amount;
};
auto phys_addr = compressed_physical_ptr[page_index];
auto phys_addr = tracked_entries[page_index].compressed_physical_ptr;
if (phys_addr == 0) {
on_unmapped(copy_amount, current_vaddr);
continue;
}
auto* mem_ptr = GetPointerFromRaw<u8>(
(static_cast<PAddr>(phys_addr - 1) << Memory::YUZU_PAGEBITS) + page_offset);
auto* mem_ptr = GetPointerFromRaw<u8>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS) + page_offset);
on_memory(copy_amount, mem_ptr);
}
}
@ -430,7 +413,7 @@ void DeviceMemoryManager<Traits>::ReadBlock(DAddr address, void* dest_pointer, s
}
const std::size_t page_index = address >> Memory::YUZU_PAGEBITS;
const auto phys_addr = compressed_physical_ptr[page_index];
const auto phys_addr = tracked_entries[page_index].compressed_physical_ptr;
if (phys_addr != 0) {
auto* const mem_ptr = GetPointerFromRaw<u8>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS));
t_slot[cache_cursor % t_slot.size()] = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr};
@ -488,7 +471,7 @@ void DeviceMemoryManager<Traits>::ReadBlockUnsafe(DAddr address, void* dest_poin
}
const std::size_t page_index = address >> Memory::YUZU_PAGEBITS;
const auto phys_addr = compressed_physical_ptr[page_index];
const auto phys_addr = tracked_entries[page_index].compressed_physical_ptr;
if (phys_addr != 0) {
auto* const mem_ptr = GetPointerFromRaw<u8>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS));
t_slot[cache_cursor % t_slot.size()] = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr};

View file

@ -117,6 +117,45 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
bool IsVersionedExternalUpdateDisabled(const std::vector<std::string>& disabled, u32 version) {
const std::string disabled_key = fmt::format("Update@{}", version);
return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() ||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
}
std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64 update_tid) {
if (provider == nullptr) {
return {};
}
auto control_nca = provider->GetEntry(update_tid, ContentRecordType::Control);
if (control_nca == nullptr ||
control_nca->GetStatus() != Loader::ResultStatus::Success) {
return {};
}
const auto romfs = control_nca->GetRomFS();
if (romfs == nullptr) {
return {};
}
const auto extracted = ExtractRomFS(romfs);
if (extracted == nullptr) {
return {};
}
auto nacp_file = extracted->GetFile("control.nacp");
if (nacp_file == nullptr) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file == nullptr) {
return {};
}
NACP nacp{nacp_file};
return nacp.GetVersionString();
}
} // Anonymous namespace
PatchManager::PatchManager(u64 title_id_,
@ -155,8 +194,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
break;
@ -175,8 +213,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
break;
@ -580,8 +617,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
@ -600,8 +636,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
@ -704,9 +739,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
version_str = FormatTitleVersion(update_entry.version);
}
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
@ -732,9 +766,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
version_str = FormatTitleVersion(update_entry.version);
}
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled,
@ -771,7 +804,9 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
for (const auto& [slot, entry] : all_updates) {
if (slot == ContentProviderUnionSlot::External) {
(void)entry;
if (slot == ContentProviderUnionSlot::External ||
slot == ContentProviderUnionSlot::FrontendManual) {
continue;
}
@ -785,7 +820,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
source_suffix = " (NAND)";
break;
case ContentProviderUnionSlot::SDMC:
source_type = PatchSource::NAND;
source_type = PatchSource::SDMC;
source_suffix = " (SDMC)";
break;
default:
@ -794,19 +829,16 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::string version_str;
u32 numeric_ver = 0;
PatchManager update{update_tid, fs_controller, content_provider};
const auto metadata = update.GetControlMetadata();
const auto& nacp = metadata.first;
const auto* slot_provider = content_union->GetSlotProvider(slot);
version_str = GetUpdateVersionStringFromSlot(slot_provider, update_tid);
if (nacp != nullptr) {
version_str = nacp->GetVersionString();
}
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.has_value()) {
numeric_ver = *meta_ver;
if (version_str.empty() && numeric_ver != 0) {
version_str = FormatTitleVersion(numeric_ver);
if (slot_provider != nullptr) {
const auto slot_ver = slot_provider->GetEntryVersion(update_tid);
if (slot_ver.has_value()) {
numeric_ver = *slot_ver;
if (version_str.empty() && numeric_ver != 0) {
version_str = FormatTitleVersion(numeric_ver);
}
}
}
@ -955,37 +987,60 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
}
// DLC
const auto dlc_entries =
content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
std::vector<ContentProviderEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
[this](const ContentProviderEntry& entry) {
const auto base_tid = GetBaseTitleID(entry.title_id);
const bool matches_base = base_tid == title_id;
bool has_external_dlc = false;
bool has_nand_dlc = false;
bool has_sdmc_dlc = false;
bool has_other_dlc = false;
const auto dlc_entries_with_origin =
content_union->ListEntriesFilterOrigin(std::nullopt, TitleType::AOC, ContentRecordType::Data);
if (!matches_base) {
LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}",
entry.title_id, base_tid, title_id);
return false;
}
dlc_match.reserve(dlc_entries_with_origin.size());
for (const auto& [slot, entry] : dlc_entries_with_origin) {
const auto base_tid = GetBaseTitleID(entry.title_id);
const bool matches_base = base_tid == title_id;
if (!matches_base) {
LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}",
entry.title_id, base_tid, title_id);
continue;
}
auto nca = content_provider.GetEntry(entry);
if (!nca) {
LOG_DEBUG(Loader, "Failed to get NCA for DLC {:016X}", entry.title_id);
return false;
}
const auto* slot_provider = content_union->GetSlotProvider(slot);
if (slot_provider == nullptr) {
continue;
}
const auto status = nca->GetStatus();
if (status != Loader::ResultStatus::Success) {
LOG_DEBUG(Loader, "DLC {:016X} NCA has status {}",
entry.title_id, static_cast<int>(status));
return false;
}
auto nca = slot_provider->GetEntry(entry);
if (!nca) {
LOG_DEBUG(Loader, "Failed to get NCA for DLC {:016X}", entry.title_id);
continue;
}
return true;
});
const auto status = nca->GetStatus();
if (status != Loader::ResultStatus::Success) {
LOG_DEBUG(Loader, "DLC {:016X} NCA has status {}", entry.title_id,
static_cast<int>(status));
continue;
}
switch (slot) {
case ContentProviderUnionSlot::External:
case ContentProviderUnionSlot::FrontendManual:
has_external_dlc = true;
break;
case ContentProviderUnionSlot::UserNAND:
case ContentProviderUnionSlot::SysNAND:
has_nand_dlc = true;
break;
case ContentProviderUnionSlot::SDMC:
has_sdmc_dlc = true;
break;
default:
has_other_dlc = true;
break;
}
dlc_match.push_back(entry);
}
if (!dlc_match.empty()) {
// Ensure sorted so DLC IDs show in order.
@ -999,13 +1054,22 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
const auto dlc_disabled =
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
PatchSource dlc_source = PatchSource::Unknown;
if (has_external_dlc && !has_nand_dlc && !has_sdmc_dlc && !has_other_dlc) {
dlc_source = PatchSource::External;
} else if (has_nand_dlc && !has_external_dlc && !has_sdmc_dlc && !has_other_dlc) {
dlc_source = PatchSource::NAND;
} else if (has_sdmc_dlc && !has_external_dlc && !has_nand_dlc && !has_other_dlc) {
dlc_source = PatchSource::SDMC;
}
out.push_back({.enabled = !dlc_disabled,
.name = "DLC",
.version = std::move(list),
.type = PatchType::DLC,
.program_id = title_id,
.title_id = dlc_match.back().title_id,
.source = PatchSource::Unknown});
.source = dlc_source});
}
return out;

View file

@ -34,6 +34,7 @@ enum class PatchType { Update, DLC, Mod };
enum class PatchSource {
Unknown,
NAND,
SDMC,
External,
Packed,
};

View file

@ -104,6 +104,206 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
}
static std::shared_ptr<NSP> OpenContainerAsNsp(const VirtualFile& file, Loader::FileType type) {
if (!file) {
return nullptr;
}
if (type == Loader::FileType::Unknown || type == Loader::FileType::Error) {
type = Loader::IdentifyFile(file);
if (type == Loader::FileType::Unknown) {
type = Loader::GuessFromFilename(file->GetName());
}
}
if (type == Loader::FileType::NSP) {
auto nsp = std::make_shared<NSP>(file);
return nsp->GetStatus() == Loader::ResultStatus::Success ? nsp : nullptr;
}
if (type == Loader::FileType::XCI) {
XCI xci(file);
if (xci.GetStatus() != Loader::ResultStatus::Success) {
return nullptr;
}
auto secure_partition = xci.GetSecurePartitionNSP();
if (secure_partition == nullptr) {
return nullptr;
}
return secure_partition;
}
// SAF-backed files can occasionally fail type-guessing despite being valid NSP/XCI.
// As a last resort, probe both container parsers directly.
{
auto nsp = std::make_shared<NSP>(file);
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
return nsp;
}
}
{
XCI xci(file);
if (xci.GetStatus() == Loader::ResultStatus::Success) {
auto secure_partition = xci.GetSecurePartitionNSP();
if (secure_partition != nullptr) {
return secure_partition;
}
}
}
return nullptr;
}
template <typename Callback>
bool ForEachContainerEntry(const std::shared_ptr<NSP>& nsp, bool only_content,
std::optional<u64> base_program_id, Callback&& on_entry) {
if (!nsp) {
return false;
}
const auto& ncas = nsp->GetNCAs();
if (ncas.empty()) {
return false;
}
std::map<u64, u32> versions;
std::map<u64, std::string> version_strings;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
if (!nca) {
continue;
}
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
for (const auto& inner_file : subdirs[0]->GetFiles()) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
break;
}
}
}
}
if (title_type == TitleType::Update && content_type == ContentRecordType::Control) {
const auto romfs = nca->GetRomFS();
if (!romfs) {
continue;
}
const auto extracted = ExtractRomFS(romfs);
if (!extracted) {
continue;
}
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (!nacp_file) {
continue;
}
const NACP nacp(nacp_file);
auto version_string = nacp.GetVersionString();
if (!version_string.empty()) {
version_strings[title_id] = std::move(version_string);
}
}
}
}
bool added_entries = false;
for (const auto& [title_id, nca_map] : ncas) {
if (base_program_id.has_value() && GetBaseTitleID(title_id) != *base_program_id) {
continue;
}
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (only_content && title_type != TitleType::Update && title_type != TitleType::AOC) {
continue;
}
auto entry_file = nca ? nca->GetBaseFile() : nullptr;
if (!entry_file) {
continue;
}
u32 version = 0;
std::string version_string;
if (title_type == TitleType::Update) {
if (const auto version_it = versions.find(title_id); version_it != versions.end()) {
version = version_it->second;
}
if (const auto version_str_it = version_strings.find(title_id);
version_str_it != version_strings.end()) {
version_string = version_str_it->second;
}
}
on_entry(title_type, content_type, title_id, entry_file, version, version_string);
added_entries = true;
}
}
return added_entries;
}
static void UpsertExternalVersionEntry(std::vector<ExternalUpdateEntry>& multi_version_entries,
u64 title_id, u32 version,
const std::string& version_string,
ContentRecordType content_type, const VirtualFile& file) {
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(),
[title_id, version](const ExternalUpdateEntry& entry) {
return entry.title_id == title_id && entry.version == version;
});
if (it == multi_version_entries.end()) {
ExternalUpdateEntry update_entry;
update_entry.title_id = title_id;
update_entry.version = version;
update_entry.version_string = version_string;
update_entry.files[static_cast<std::size_t>(content_type)] = file;
multi_version_entries.push_back(std::move(update_entry));
return;
}
it->files[static_cast<std::size_t>(content_type)] = file;
if (it->version_string.empty() && !version_string.empty()) {
it->version_string = version_string;
}
}
template <typename EntryMap, typename VersionMap>
static bool AddExternalEntriesFromContainer(const std::shared_ptr<NSP>& nsp, EntryMap& entries,
VersionMap& versions,
std::vector<ExternalUpdateEntry>& multi_version_entries) {
return ForEachContainerEntry(
nsp, true, std::nullopt,
[&entries, &versions,
&multi_version_entries](TitleType title_type, ContentRecordType content_type, u64 title_id,
const VirtualFile& file, u32 version,
const std::string& version_string) {
entries[{title_id, content_type, title_type}] = file;
if (title_type == TitleType::Update) {
versions[title_id] = version;
UpsertExternalVersionEntry(multi_version_entries, title_id, version, version_string,
content_type, file);
}
});
}
ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
switch (type) {
case NCAContentType::Program:
@ -1008,6 +1208,26 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec
}
}
bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_content,
std::optional<u64> base_program_id) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::Unknown);
if (!nsp) {
return false;
}
return ForEachContainerEntry(
nsp, only_content, base_program_id,
[this](TitleType title_type, ContentRecordType content_type, u64 title_id,
const VirtualFile& entry_file, u32 version, const std::string& version_string) {
if (title_type == TitleType::Update) {
AddEntryWithVersion(title_type, content_type, title_id, version, version_string,
entry_file);
} else {
AddEntry(title_type, content_type, title_id, entry_file);
}
});
}
void ManualContentProvider::ClearAllEntries() {
entries.clear();
multi_version_entries.clear();
@ -1091,14 +1311,6 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor
return nullptr;
}
bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.files[size_t(type)])
++count;
return count > 0;
}
ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
: load_dirs(std::move(load_directories)) {
ExternalContentProvider::Refresh();
@ -1159,247 +1371,22 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
}
void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
auto nsp = NSP(file);
if (nsp.GetStatus() != Loader::ResultStatus::Success) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP);
if (!nsp) {
return;
}
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName());
const auto ncas = nsp.GetNCAs();
std::map<u64, u32> nsp_versions;
std::map<u64, std::string> nsp_version_strings; // title_id -> NACP version string
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
const auto section0 = subdirs[0];
const auto files = section0->GetFiles();
for (const auto& inner_file : files) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
const auto cnmt_title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
nsp_versions[cnmt_title_id] = version;
versions[cnmt_title_id] = version;
break;
}
}
}
}
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
nsp_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
continue;
}
auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type);
if (nca_file != nullptr) {
entries[{title_id, content_type, title_type}] = nca_file;
if (title_type == TitleType::Update) {
u32 version = 0;
auto ver_it = nsp_versions.find(title_id);
if (ver_it != nsp_versions.end()) {
version = ver_it->second;
}
version_files[{title_id, version}][size_t(content_type)] = nca_file;
}
LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}",
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
}
}
}
for (const auto& [key, files_map] : version_files) {
const auto& [title_id, version] = key;
std::string ver_str;
auto str_it = nsp_version_strings.find(title_id);
if (str_it != nsp_version_strings.end()) {
ver_str = str_it->second;
}
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
existing.files = files_map;
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
version_exists = true;
break;
}
}
if (!version_exists && !files_map.empty()) {
ExternalUpdateEntry update_entry{
.title_id = title_id,
.version = version,
.version_string = ver_str,
.files = files_map
};
multi_version_entries.push_back(update_entry);
LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
title_id, version, ver_str, files_map.size());
}
}
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
}
void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
auto xci = XCI(file);
if (xci.GetStatus() != Loader::ResultStatus::Success) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI);
if (!nsp) {
return;
}
auto nsp = xci.GetSecurePartitionNSP();
if (nsp == nullptr) {
return;
}
const auto ncas = nsp->GetNCAs();
std::map<u64, u32> xci_versions;
std::map<u64, std::string> xci_version_strings;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
const auto section0 = subdirs[0];
const auto files = section0->GetFiles();
for (const auto& inner_file : files) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
const auto cnmt_title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
xci_versions[cnmt_title_id] = version;
versions[cnmt_title_id] = version;
break;
}
}
}
}
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
xci_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
continue;
}
auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type);
if (nca_file != nullptr) {
entries[{title_id, content_type, title_type}] = nca_file;
if (title_type == TitleType::Update) {
u32 version = 0;
auto ver_it = xci_versions.find(title_id);
if (ver_it != xci_versions.end()) {
version = ver_it->second;
}
version_files[{title_id, version}][size_t(content_type)] = nca_file;
}
}
}
}
for (const auto& [key, files_map] : version_files) {
const auto& [title_id, version] = key;
std::string ver_str;
auto str_it = xci_version_strings.find(title_id);
if (str_it != xci_version_strings.end()) {
ver_str = str_it->second;
}
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
existing.files = files_map;
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
version_exists = true;
break;
}
}
if (!version_exists && !files_map.empty()) {
ExternalUpdateEntry update_entry{
.title_id = title_id,
.version = version,
.version_string = ver_str,
.files = files_map
};
multi_version_entries.push_back(update_entry);
LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
title_id, version, ver_str, files_map.size());
}
}
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
}
bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
@ -1491,12 +1478,4 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec
return nullptr;
}
bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.files[size_t(type)])
++count;
return count > 1;
}
} // namespace FileSys

View file

@ -9,6 +9,7 @@
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <ankerl/unordered_dense.h>
@ -262,6 +263,8 @@ public:
VirtualFile file);
void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id,
u32 version, const std::string& version_string, VirtualFile file);
bool AddEntriesFromContainer(VirtualFile file, bool only_content = false,
std::optional<u64> base_program_id = std::nullopt);
void ClearAllEntries();
void Refresh() override;
@ -276,7 +279,6 @@ public:
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private:
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
@ -303,7 +305,6 @@ public:
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private:
void ScanDirectory(const VirtualDir& dir);

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -18,17 +21,14 @@ namespace Service::Audio {
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
server_manager->RegisterNamedService("audin:u", std::make_shared<IAudioInManager>(system));
server_manager->RegisterNamedService("audout:u", std::make_shared<IAudioOutManager>(system));
server_manager->RegisterNamedService(
"audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system));
server_manager->RegisterNamedService("audrec:u",
std::make_shared<IFinalOutputRecorderManager>(system));
server_manager->RegisterNamedService("audren:u",
std::make_shared<IAudioRendererManager>(system));
server_manager->RegisterNamedService("hwopus",
std::make_shared<IHardwareOpusDecoderManager>(system));
// Depends on audout:u and audin:u on ctor!
server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
server_manager->RegisterNamedService("audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system));
server_manager->RegisterNamedService("audrec:u", std::make_shared<IFinalOutputRecorderManager>(system));
server_manager->RegisterNamedService("audren:u", std::make_shared<IAudioRendererManager>(system));
server_manager->RegisterNamedService("hwopus", std::make_shared<IHardwareOpusDecoderManager>(system));
ServerManager::RunServer(std::move(server_manager));
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -104,7 +107,8 @@ Result TimeZoneService::LoadLocationNameList(
OutArray<Service::PSC::Time::LocationName, BufferAttr_HipcMapAlias> out_names, u32 index) {
SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. index={} out_count={} out_names[0]={} out_names[1]={}",
index, *out_count, out_names[0], out_names[1]);
index, *out_count, out_names.size() > 0 ? out_names[0] : Service::PSC::Time::LocationName{},
out_names.size() > 1 ? out_names[1] : Service::PSC::Time::LocationName{});
};
std::scoped_lock l{m_mutex};
@ -208,7 +212,8 @@ Result TimeZoneService::ToPosixTime(Out<u32> out_count,
SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. calendar_time={} out_count={} out_times[0]={} out_times[1]={}",
calendar_time, *out_count, out_times[0], out_times[1]);
calendar_time, *out_count, out_times.size() > 0 ? out_times[0] : s64{0},
out_times.size() > 1 ? out_times[1] : s64{0});
};
R_RETURN(m_wrapped_service->ToPosixTime(out_count, out_times, calendar_time, rule));
@ -220,7 +225,8 @@ Result TimeZoneService::ToPosixTimeWithMyRule(
SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. calendar_time={} out_count={} out_times[0]={} out_times[1]={}",
calendar_time, *out_count, out_times[0], out_times[1]);
calendar_time, *out_count, out_times.size() > 0 ? out_times[0] : s64{0},
out_times.size() > 1 ? out_times[1] : s64{0});
};
R_RETURN(m_wrapped_service->ToPosixTimeWithMyRule(out_count, out_times, calendar_time));

View file

@ -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: Copyright 2018 yuzu Emulator Project
@ -28,8 +28,10 @@ public:
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
{10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"},
{10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"},
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old3>, "SaveReportOld3"},
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old3>, "SaveReportWithUserOld3"},
{10106, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
{10107, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
{10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"},
{10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"},
{10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"},

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -148,7 +151,8 @@ Result TimeZoneService::ToPosixTime(Out<u32> out_count,
SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. calendar_time={} out_count={} out_times[0]={} out_times[1]={} ",
calendar_time, *out_count, out_times[0], out_times[1]);
calendar_time, *out_count, out_times.size() > 0 ? out_times[0] : s64{0},
out_times.size() > 1 ? out_times[1] : s64{0});
};
R_RETURN(
@ -161,7 +165,8 @@ Result TimeZoneService::ToPosixTimeWithMyRule(Out<u32> out_count,
SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. calendar_time={} out_count={} out_times[0]={} out_times[1]={} ",
calendar_time, *out_count, out_times[0], out_times[1]);
calendar_time, *out_count, out_times.size() > 0 ? out_times[0] : s64{0},
out_times.size() > 1 ? out_times[1] : s64{0});
};
R_RETURN(

View file

@ -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: Copyright 2018 yuzu Emulator Project
@ -29,10 +29,13 @@ namespace Service {
return function_string;
}
ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_,
u32 max_sessions_, InvokerFn* handler_invoker_)
: SessionRequestHandler(system_.Kernel(), service_name_), system{system_},
service_name{service_name_}, handler_invoker{handler_invoker_}, max_sessions{max_sessions_} {}
ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_, u32 max_sessions_, InvokerFn* handler_invoker_)
: SessionRequestHandler(system_.Kernel(), service_name_)
, system{system_}
, service_name{service_name_}
, handler_invoker{handler_invoker_}
, max_sessions{max_sessions_}
{}
ServiceFrameworkBase::~ServiceFrameworkBase() {
// Wait for other threads to release access before destroying
@ -50,8 +53,7 @@ void ServiceFrameworkBase::RegisterHandlersBaseTipc(const FunctionInfoBase* func
// Usually this array is sorted by id already, so hint to insert at the end
handlers_tipc.reserve(handlers_tipc.size() + n);
for (std::size_t i = 0; i < n; ++i)
handlers_tipc.emplace_hint(handlers_tipc.cend(), functions[i].expected_header,
functions[i]);
handlers_tipc.emplace_hint(handlers_tipc.cend(), functions[i].expected_header, functions[i]);
}
void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx,

View file

@ -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: Copyright 2018 yuzu Emulator Project
@ -8,8 +8,7 @@
#include <cstddef>
#include <mutex>
#include <string>
#include <boost/container/flat_map.hpp>
#include <ankerl/unordered_dense.h>
#include "common/common_types.h"
#include "core/hle/service/hle_ipc.h"
@ -78,13 +77,6 @@ protected:
[[nodiscard]] virtual std::unique_lock<std::mutex> LockService() noexcept {
return std::unique_lock{lock_service};
}
/// System context that the service operates under.
Core::System& system;
/// Identifier string used to connect to the service.
std::string service_name;
private:
template <typename T>
friend class ServiceFramework;
@ -106,16 +98,19 @@ private:
void RegisterHandlersBaseTipc(const FunctionInfoBase* functions, std::size_t n);
void ReportUnimplementedFunction(HLERequestContext& ctx, const FunctionInfoBase* info);
boost::container::flat_map<u32, FunctionInfoBase> handlers;
boost::container::flat_map<u32, FunctionInfoBase> handlers_tipc;
protected:
ankerl::unordered_dense::map<u32, FunctionInfoBase> handlers;
ankerl::unordered_dense::map<u32, FunctionInfoBase> handlers_tipc;
/// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
std::mutex lock_service;
/// System context that the service operates under.
Core::System& system;
/// Identifier string used to connect to the service.
const char* service_name;
/// Function used to safely up-cast pointers to the derived class before invoking a handler.
InvokerFn* handler_invoker;
/// Maximum number of concurrent sessions that this service can handle.
u32 max_sessions;
/// Flag to store if a port was already create/installed to detect multiple install attempts,
/// which is not supported.
bool service_registered = false;
@ -159,8 +154,7 @@ protected:
* @param max_sessions_ Maximum number of sessions that can be connected to this service at the
* same time.
*/
explicit ServiceFramework(Core::System& system_, const char* service_name_,
u32 max_sessions_ = ServerSessionCountMax)
explicit ServiceFramework(Core::System& system_, const char* service_name_, u32 max_sessions_ = ServerSessionCountMax)
: ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {}
/// Registers handlers in the service.
@ -219,7 +213,7 @@ private:
static void Invoker(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member,
HLERequestContext& ctx) {
// Cast back up to our original types and call the member function
(static_cast<Self*>(object)->*static_cast<HandlerFnP<Self>>(member))(ctx);
(static_cast<Self*>(object)->*HandlerFnP<Self>(member))(ctx);
}
};

View file

@ -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: Copyright 2018 yuzu Emulator Project
@ -9,11 +9,15 @@
#include <ostream>
#include <string>
#include <concepts>
#include <algorithm>
#include "common/concepts.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/k_process.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/kip.h"
@ -37,6 +41,49 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
return std::nullopt;
}
std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, FileType type,
u64 program_id = 0,
std::size_t program_index = 0) {
if (!file) {
return nullptr;
}
if (type == FileType::NSP) {
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
}
if (type == FileType::XCI) {
FileSys::XCI xci{file, program_id, program_index};
if (xci.GetStatus() != ResultStatus::Success) {
return nullptr;
}
auto secure_nsp = xci.GetSecurePartitionNSP();
if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
return nullptr;
}
return secure_nsp;
}
return nullptr;
}
bool HasApplicationProgramContent(const std::shared_ptr<FileSys::NSP>& nsp) {
if (!nsp) {
return false;
}
const auto& ncas = nsp->GetNCAs();
return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) {
const auto& nca_map = title_entry.second;
return nca_map.find(
{FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) !=
nca_map.end();
});
}
} // namespace
FileType IdentifyFile(FileSys::VirtualFile file) {
@ -62,6 +109,27 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
}
}
bool IsContainerType(FileType type) {
return type == FileType::NSP || type == FileType::XCI;
}
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
std::size_t program_index) {
if (!file) {
return false;
}
if (type == FileType::Unknown) {
type = IdentifyFile(file);
}
if (!IsContainerType(type)) {
return false;
}
return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index));
}
FileType GuessFromFilename(const std::string& name) {
if (name == "main")
return FileType::DeconstructedRomDirectory;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -46,12 +49,29 @@ enum class FileType {
};
/**
* Identifies the type of a bootable file based on the magic value in its header.
* Identifies the type of a supported file/container based on its structure.
* @param file open file
* @return FileType of file
*/
FileType IdentifyFile(FileSys::VirtualFile file);
/**
* Returns whether the file type represents a container format that can bundle multiple titles
* (currently NSP/XCI).
*/
bool IsContainerType(FileType type);
/**
* Returns whether a container file is bootable as a game (has Application/Program content).
*
* @param file open file
* @param type optional file type; if Unknown it is auto-detected.
* @param program_id optional program id hint for multi-program containers.
* @param program_index optional program index hint for multi-program containers.
*/
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown,
u64 program_id = 0, std::size_t program_index = 0);
/**
* Guess the type of a bootable file from its name
* @param name String name of bootable file

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -55,19 +58,30 @@ AppLoader_NSP::~AppLoader_NSP() = default;
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
const FileSys::NSP nsp(nsp_file);
if (nsp.GetStatus() == ResultStatus::Success) {
// Extracted Type case
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
return FileType::NSP;
if (nsp.GetStatus() != ResultStatus::Success) {
return FileType::Error;
}
// Extracted Type case
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
return FileType::NSP;
}
// Non-extracted NSPs can legitimately contain only update/DLC content.
// Identify the container format itself; bootability is validated by Load().
if (!nsp.GetNCAs().empty()) {
return FileType::NSP;
}
// Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs.
for (const auto& entry : nsp.GetFiles()) {
if (entry == nullptr) {
continue;
}
// Non-Extracted Type case
const auto program_id = nsp.GetProgramTitleID();
if (!nsp.IsExtractedType() &&
nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr &&
AppLoader_NCA::IdentifyType(
nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) {
const auto& name = entry->GetName();
if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") {
return FileType::NSP;
}
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -44,10 +47,13 @@ AppLoader_XCI::~AppLoader_XCI() = default;
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
const FileSys::XCI xci(xci_file);
if (xci.GetStatus() == ResultStatus::Success &&
xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
FileType::NCA) {
if (xci.GetStatus() != ResultStatus::Success) {
return FileType::Error;
}
// Identify XCI as a valid container even when it does not include a bootable Program NCA.
// Bootability is handled by AppLoader_XCI::Load().
if (xci.GetSecurePartitionNSP() != nullptr) {
return FileType::XCI;
}

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