Compare commits

...

41 Commits

Author SHA1 Message Date
xbzk b7f0f98519 [android] expose log filter setting (#4327)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Takes log filter to android and fixes qt "loads after setting, but ignore in next runs" issue

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4327
Reviewed-by: Lizzie and Samuel <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-31 04:22:15 +02:00
crueter 7fda6dde73 [common] Switch to boost::unordered_flat containers (#4326)
Replaces all instances of ankerl's unordered map/set with boost's
`unordered_flat_*` classes. This uses std::hash since boost::hash is
actually a lot slower.

Also adds an abstraction layer in `Common` so future changes are quicker
and easier.

Other implementation details:
- ankerl provided hash specializations for tuple and pair, so those were
  ported here
- std::erase_if doesn't work on boost, so just used the ADL'd erase_if

This should be about equal or superior performance as unordered_dense for everything except iteration.

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

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4326
Reviewed-by: Lizzie and Samuel <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-31 02:58:09 +02:00
lizzie 106a61c943 [core/file_sys] fix IPS not applying due to wrong NSObuild-id (#4323)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

NSO build id was being read wrongly... oops
also fixed some minor issues as well

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4323
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-30 20:08:40 +02:00
lizzie e5b656e372 [common/logging] Fix long logs, remove clutter on Android logcat (#4310)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

logs normally are like this:
```
08-27 02:58:48.188 12511  4743 W YuzuNative: [18534.062505] Shader <Warning> shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp:141:Read: (STUBBED) SR_WSCALEFACTOR_XY
08-27 02:58:48.188 12511  4743 W YuzuNative: [18534.062513] Shader <Warning> shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp:144:Read: (STUBBED) SR_WSCALEFACTOR_Z
08-27 02:58:48.188 12511  4743 W YuzuNative: [18534.062520] Shader <Warning> shader_recompiler/frontend/maxwell/translate/impl/vote.cpp:50:VOTE_vtg: (STUBBED) called
08-27 02:58:48.188 12511  4743 W YuzuNative: [18534.062523] Shader <Warning> shader_recompiler/frontend/ir/ir_emitter.cpp:267:GetFlowTest: (STUBBED) FCSM_TR
```
bunch of redundant info imo

instead they should just be
```
08-27 02:58:48.188 12511  4743 W YuzuNative: Shader shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp:141:Read: (STUBBED) SR_WSCALEFACTOR_XY
08-27 02:58:48.188 12511  4743 W YuzuNative: Shader shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp:144:Read: (STUBBED) SR_WSCALEFACTOR_Z
08-27 02:58:48.188 12511  4743 W YuzuNative: Shader shader_recompiler/frontend/maxwell/translate/impl/vote.cpp:50:VOTE_vtg: (STUBBED) called
08-27 02:58:48.188 12511  4743 W YuzuNative: Shader shader_recompiler/frontend/ir/ir_emitter.cpp:267:GetFlowTest: (STUBBED) FCSM_TR
```

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4310
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-30 08:24:00 +02:00
lizzie 297e797a32 [hle/kernel] remove flusher thread from OutputDebugString (#4291)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

Unneeded abstraction?
either way it seems this thread is not required
anyone thinking otherwise feel free to lmk
directly contradicts with #4290
and #3744

Remember that messages will be displayed out of order
this can make actual debugging much harder I'm afraid

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4291
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-08-30 08:18:50 +02:00
xbzk 48a95da874 [debug] debug knobs adjustments (#4317)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
minor changes to debug knobs:

-changed its category to System so it becomes per-game-able (believe me, it's useful)
-fixed kotlin field type to UShort
-changed get to Get for proper standard
-added spacings and more info to docs

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4317
Reviewed-by: Lizzie and Samuel <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-08-30 08:17:54 +02:00
rayanmargham 2fd1dc0ff9 [qt] fix closing software dialog color (#4305)
Signed-off-by: rayanmargham <rayanmargham4@gmail.com>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
This change fixes the closing software dialogue box having really grayish to white text on a white background making it impossible to read. This fixes default-dark giving it this look with the attached screenshot![image](/attachments/ad7dd13c-3808-4a0e-b29b-1a001a1106e0)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4305
Reviewed-by: Lizzie and Samuel <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-08-30 08:16:32 +02:00
Lizzie and Samuel 45b6ecff05 [vulkan, vk_pipeline_cache] PSO Optimizations (#4294)
PR/Commit Owner: CamilleLaVey

This PR prevents the recompilation of shaders when a new session has been started, based on data found on the recompiler, each new session does still recompile already cached shader cache, reducing the reaction and speed of games by at certain level, this prevent the warm-up with post sessions, only needed the initial warm-up and works globally for all games.

Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4294
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-08-30 08:15:29 +02:00
lizzie 672bcbae01 [file_sys] robust-er IPSwitch compiler (#3911)
this PR reworks IPS parser to be less stupid
what do i mean by this? well generally give it a bit of
love so it doesn't do a lot of unsound allocations
also simplify the logic greatly (and use memcmp() string idiom
instead of trusting the compiler so blindly...)

no this doesn't mean to uber optimize IPS to handle 999 gb/s
it's more so it doesn't outright crash with edge cases
as the previous codebase was quite spaghetty

also the major overhead is obviously the vector shenanigans
and the I/O -- but thats out of scope

Test that IPSwitch mods still properly work WITH ANY GAME
IF THERE IS ANY REGRESSION IN SOME GAME/MOD THEN
TELL ME

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3911
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-30 08:10:51 +02:00
Maufeat 28ab4a1a01 [kernel, service] Return ResultSessionClosed when a session is closed (#4318)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

I tried to run a homebrew game which tried to reply to a closed service. nnSdk expected ResultSessionClosed, we replied with ResultSuccess which mostly works but some have stricter handling and would terminate with abort.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4318
Reviewed-by: Lizzie and Samuel <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-08-30 08:00:18 +02:00
lizzie ccf3de02cf [dynarmic] optimise BlockOfCode to use less memory (#4303)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

removes unique_ptr indireciton with argCallback (lmao)
reorders jitState to be more cache friendly and yadda yadda
removes redundant BlockOfCode& ptr on constantPool
reorder blockOfCode to be btter
update dynarmic because it removes 3kb worth of useless data :)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4303
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-08-30 07:43:21 +02:00
PavelBARABANOV 1f03dee126 [fs] Add RenameDirectory support for same-parent directory renaming (#4312)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Fixes save corruption in Warhammer 40k: Mechanicus and other games
that rename directories within the same parent folder.

- Added RenameDirectory handler in IFileSystem (command 6)
- Implemented same-parent directory renaming using RenameDir

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4312
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Samuel <lizzie@eden-emu.dev>
2026-08-30 01:46:02 +02:00
xbzk 3df41c1e7a [android, ui] carousel: display-wise scaling, snapping fix, bottom insets rework (#4307)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Another batch of improvements and fixes for Carousel.

- Added display wise scaling factor for compatibility with 16:9 and 4:3 devices;
- Fixed snap to center feature being lost sometimes;
- Reworked entire bottom inset mechanism (made it event driven, and added use of getInsetsIgnoringVisibility) to avoid weird resizing on Recents (alt+tab) transitions.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4307
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-29 22:04:18 +02:00
lizzie f4a8f9421a [common/net] fix linking errors due to C++11 ABI typedef mishandle (#4260)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

basically bunch of linking errors due to gcc not liking
to compile HFA/whatever typedef stuffs
this is on non-x86_64 arch, ppc64

compiler where this occurs

```
gcc (Debian 14.2.0-19) 14.2.0
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4260
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-08-29 20:59:57 +02:00
xbzk c0a85d0e53 [service, nvhost] added machinery to allow microsleep between nvdec read requests to avoid guest panic in some games (#4316)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
This one deserves a long story, but imma try to resume:

During investigating Absolum 1.2 black screen of death upon loading intro video, i've discovered guest was aborting for failing to allocate room for the video.

By logging everything prior to crash and decoding guest side instructions managed to confirm its media allocator was reading data faster than it was updating free available bucket list.

Since the IStorage::Read was happening 247 times before the crash, i've decided to add a very small sleep there, and boom, not only Absolum but some other titles got the same issue fixed.

But i was unsatisfied with the sleep and kept tracking guest instructions upstream in order to find a sync point for the read worker and the memory allocation update. But unfortunately the media allocator helpers live in guest, accessing memory directly via MMU, so any sync signaling would need to come from some dynarmic hack.

It's been 6 days now, so i've decided to polish the sleep: Moved it upstream to where i could have access for proper predicate, and added machinery to service and nvhost to support that. Now the sleep is restricted only for nvdec istorage reads. Any other reads will flow normally.

TL;DR: currently our code is so blazing async that guest is capable to request reads before its very self refresh it have freed room to do so. The sleep accepted as broadly stable was 600 us (MICROseconds), and it affects ONLY nvdec chunk reading.
Reports confirm that now videos are smoother now.

Code was polished at my knowledge limits.
Mostly machinery to track when a request comes from a process with nvdec active, and is istorage read.
I can provide more details if it comes to be needed.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4316
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Samuel <lizzie@eden-emu.dev>
2026-08-29 14:22:09 +02:00
Maufeat 54cd5fb8eb [core, hle, video_core, memory] Improve multi-process, display layers, dynamic shader cache and rework overlay (#4238)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

- Dynamic shader cache reloading capability for qlaunch
- Multi-process improvements (thanks to @frank1734), instead of just using the main application we now respect caller process (also did it for HID devices while I was at it)
- Layer stack masks & shared buffer screenshot for video core, added different masks (screenshot, recording, etc.) this was discovered as an issue due to how in qlaunch the transition was not right between applications. May not be perfect but also fixes screenshots while using qlaunch
- Reworked overlay display management (input and visibility) - instead of random numbers as I've previously did, I decided to add an AppletZIndex enum for better readability. Also split capability of input by touch and gamepad.
- etc.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4238
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-29 01:04:02 +02:00
lizzie 119291dc77 [qt_common/discord] fix boxart not being used (#4275)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

issue was first reported here
https://github.com/eden-emulator/Issue-Reports/issues/607

tl;dr wrong boolean, now boxarts should work

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4275
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-28 03:46:36 +02:00
lizzie f7dbff2157 [core/hle/services/am] Fix MK8D crash (#4313)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

This fixes a random mk8d crash when connecting via LDN

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4313
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-28 03:44:45 +02:00
lizzie b859d7fdf4 [dynarmic/backend/exception_handler] normalize EH for arches, use LOG_ERROR instead of fmt::print (#4283)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

this should simplify a bit of code logic
akin to NetBSD's UC_CONTEXT_PC...
additionally use LOG_ERROR so the
"unhandled ..."
is logged unto a file
before sigsegv'ing

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4283
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-28 02:30:18 +02:00
lizzie f635827fa6 [common/stb] remove unused I/O funcs from non-LTO builds, use common/stb.h on ns service (#4282)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

- remove I/O functions that are unused from LTO builds
- use common/stb.h on ns
simple change really, not much to explain here

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4282
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-28 02:29:52 +02:00
lizzie 243453c172 [dynarmic, loong64] fix build error due to new term handlers (#4286)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

trivial fix

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4286
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-28 02:24:39 +02:00
lizzie 39158b67a7 [audio/sdl3] fix mismatches OS mixer settings for sdl3/cubeb audio backends (#4298)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

basically makes it so SDL3 and cubeb audio backends now share the same
identifier string so OS mixers (ok a bit of a misnormer, its like pulseaudio
pipewire and such, but yknow what i mean) -- dont conflate due to the
fact that "Eden" != "yuzu Latency Getter".
fix taken from pcsx2 https://github.com/PCSX2/pcsx2/pull/12312

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4298
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-28 01:50:55 +02:00
CamilleLaVey 50cb8fd1c9 [vulkan] Implementation for VK_EXT_shader_quad_control (#4168)
An experimental approach to introduce an smarter way to use and access QUAD's capabilities on shaders, suggested by @gidoly some months ago, finally came into something usable; current implementation checks GLASM, GLSL and Vulkan on their own way, stablishes proper emitters and receivers for Quads inside our recompiler, which are the introductions for future changes; checks for support and actual feature bit, OpCodes and mask were added within this PR. Meanwhile the expected behavior was to reduce graphical issues (on games with Quad shaders reliant), we encountered a very slight performance increase depending on what game and shader are actually compiled.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4168
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Samuel <lizzie@eden-emu.dev>
2026-08-28 01:21:22 +02:00
lizzie faaf1bac64 [common/logging] Fix logging overflow on logging settings (#4308)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

Apparently on FBSD we have plenty of stack space -- but not on Linux.
Just fixes a stack overflow thing.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4308
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-26 20:38:52 +02:00
CamilleLaVey 0295dc5fff [vulkan] Removal of QCOM sampler limiters + CustomBorderColor and ColorBorderSwizzle adjustments (#4301)
This PR removes the artificial limit added for QCOM drivers based on a sampler budget limit (based on #3280 work), removes the ban on CustomBorderColor/BorderColorSwizzle for also QCOM driver, reduce the amount of combination required to created a sampler with custom border color/ color border swizzle; adds cases on R16 formats not able to swizzle on BGR565 formats; degrades samplers and color combinations when there's no color border swizzle available; fixes the srgb (LUT) values from legacy and now all the color variations are cached in the same key, instead of having 7 different handlers for them. In resume, rather than performance (due to the reduced amount of process on duplicated/ synced code on pipeline/texture cache), it's a graphica accuracy work that will improve image quality at the cost of almost 0 performance hit, rather than certain hitch if the color combination wasn't cached before. This applies to all platform.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4301
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-25 20:03:18 +02:00
CamilleLaVey 60a474b8df [vulkan, qcom] Fix shader float controls on QCOM driver (#4297)
Finishes my torture to find the culprit behind the broken behavior with float controls, even if device does support flush denorm on fp32 it doesn't really support it and provokes bad rounding modes by not flushing correctly denorms; this also returns the other working rounding modes on QCOM drivers.

_Special Thanks_

1.- Smoly The Big (@Gidoly)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4297
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-24 17:57:12 +02:00
lizzie d9336ce6ce [common, settings] Fixes misaligned Aniso levels (#4295)
PR/Commit Owner: CamilleLaVey

Just quick fixes to ANISO bugged level selector, removes x32 and x64 from choices, also fixes the none value to actully use default.

Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4295
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-24 00:06:11 +02:00
PavelBARABANOV 4eb8dd1458 partial revert a41a98028a (#4292)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
This commit fix launch Assassin's creed 3

changes nvdrv:a - break launch with fw 20+
changes ldr:ro - break skip loading menu with all fw

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4292
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-08-24 00:01:21 +02:00
PavelBARABANOV 4a3cc9a3c2 [android] Fix Nvdec None (#4288)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Nvdec None is working now; I removed the unnecessary comments — the variable names make everything clear enough.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4288
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-23 23:58:50 +02:00
Samuel 86ba79b1ba [vulkan, sgsr] Adjustments to SGSR implementation (#4293)
PR/ Commit Owner: CamiileLaVey

This change has specified fixes on the SGSR initial implementation, it brought a wider sharpening level limits and better image quality.

Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4293
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-08-23 23:36:24 +02:00
lizzie b610b03d29 [common/logging] eliminate uneeded std::string{} allocations per each logging (#4273)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

the idea is simple, `std::string{}` adds pressure to the memory allocator
so whats the best next thing we can do? well of course use our stack
its plenty, but we shouldn't be greedy either, BUFSIZ should be a fair amount of
space for any would-be messages anyways

the main idea behind this PR is to remove std::string{} allocations
done with libfmt, that way we have 0-allocs per logging entry
this should 100% remove pressure uneeded from the memory allocator
we should only allocate things that are important, we can use our
trusty fast stack for any string manip we need to do
not await/global lock or do evil things with the memory allocator

obviously stack is thread local already sooo... this is even better
than having to deal with malloc()/free() in any capacity whatsoever

and no clang can't heap ellide this (how would you even?)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4273
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-23 22:37:17 +02:00
lizzie 421e0b834c Revert "[dynarmic] Remove Ignore Global Monitor from CPU Accuracy Auto (#3846)" (#4276)
This reverts commit 3a823de605.

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

kills performance and doesn't fix graphical glitches
(according to gido)
"It improved nothing but kills performance on default setting"

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4276
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-23 22:10:22 +02:00
John 55acf5d260 [Vulkan] Apply MK8D Buffer Fix to Linux (#4289)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

Credit: MaranBR
- Adds the Rainbow Graphics Bug Fix to Linux as the game needs MAX_STREAM_BUFFER_SIZE = 256_MiB

![image](/attachments/722d4014-9449-47a0-be47-730b99e0b464)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4289
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: Samuel <lizzie@eden-emu.dev>
2026-08-23 17:43:35 +02:00
crueter 3e07b466eb [vulkan] Adjustments on MSAA and BlitHelpers (#4287)
PR/Commit Owner: CamilleLaVey

This PR contains changes complementary to the MSAA refactor from some weeks ago, adds proper shader convert to depth, stencil for msaa and non msaa convertions; removes redundant helpers and unify paths on the resolve functions, just to make the readability and maintainability better; includes fixes for the blit operations on Nvidia, Intel/Windows (previously banned) and ensures Linux retain the fix without extra burden; fixes the QCOM driver bug on resolution upscaling on any games above x1 (includes turnip on A8xx series) and fixes crashes/ resolution upscaled bugs on certain games that used to have wrong viewporting effect on screen or directly device loss on Vulkan. Adds fixes for regressions caused by previous MSAA refactor on games as Fire Emblem: Engage.

Special Thanks

1.- Big Smolio (@Gidoly)

Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4287
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Samuel <lizzie@eden-emu.dev>
2026-08-23 17:31:09 +02:00
xbzk 5b86242313 [android, ui] remove the wash (white tint) of focused game cards (#4285)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

This removes the ugly wash from focused game cards, without removing the interaction's cool ripple effects.
I've stumbled upon a reddit complaining about it, and in fact i always hated it but hadn't realized yet.

It happens to any layout.
Joypad users notice more since navigation consists on focusing cards.
Carousel users notice even more coz cards are bigger and always focused.

Border is more than enough to highlight a card, and we want to cherish colors just like creator artists wanted it to be, right?

No chance of bugs. No chance of disagreement (¬¬).
Approve it already so Youtube videos with carousel thumbs get prettier earlier ^^.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4285
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-23 00:29:59 +02:00
xbzk d24e79c991 [android, ui] refresh game icons when updates are discovered (#4284)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

This small impl aims to fix an android old visual bug where it could cache/read a base game icon before its update was mounted, so games with update-specific icons kept showing the base icon like almost always (in a way that some players may never have seen the updated icons).

Fix: when scan detects an update matching an already found base game, reload that game’s metadata and refresh only its visible icon if the icon actually changed.

I believe it's polished to the max. No find/scans. When base is found it is tracked via hashmap, and when update is found and conditions met the refresh is triggered.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4284
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-23 00:25:55 +02:00
xbzk df05d3de23 [android, ui] lsfg: make "target frame rate" show/hide "frame multiplier" (#4274)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4274
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-22 07:52:16 +02:00
PavelBARABANOV c249ff462b [android] Restore the nvdec toggle (#4280)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4280
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-22 02:40:32 +02:00
crueter 056d11027c [internal_network] Fix WiFi scanner compilation on GCC 15.3.0 (#4279)
No idea why this didn't show up earlier.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4279
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Samuel <lizzie@eden-emu.dev>
2026-08-21 23:29:24 +02:00
xbzk 61ffb309a3 [android,ui] carousel view improvements (#4277)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

One major change to make on carousel, one yoozoo era bug fixed, and some other minor adjustments.

Commits squashed:

[android, ui] Fixed carousel card size scaling over bottom insets
[android, ui] Removed old shapingFunctions
[android, ui] Implemented carousel true elliptic movement model
[android, ui] Fixed overheighted flicker on cards after refresh
[android, ui] Fixed full opaque secondary cards after refresh
[android, ui] Fixed flickers due to doubled list redraw after refreshes

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4277
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Samuel <lizzie@eden-emu.dev>
2026-08-21 23:29:04 +02:00
crueter 65a3cfd2be [common, yuzu] Fix compilation on newer httplib versions (#4278)
Required for newer httplib versions to compile.

has_header is at least present on 0.38.0 on my local machine. Let's hope
it works on Trixie

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

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4278
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Samuel <lizzie@eden-emu.dev>
2026-08-21 23:17:46 +02:00
330 changed files with 5040 additions and 2435 deletions
@@ -1,26 +0,0 @@
From b3622608433c183ba868a1dc8dd9cf285eb3b916 Mon Sep 17 00:00:00 2001
From: Dario Petrillo <dario.pk1@gmail.com>
Date: Thu, 27 Nov 2025 23:12:38 +0100
Subject: [PATCH] avoid extra memset when clearing an empty table
---
include/ankerl/unordered_dense.h | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h
index 0835342..4938212 100644
--- a/include/ankerl/unordered_dense.h
+++ b/include/ankerl/unordered_dense.h
@@ -1490,8 +1490,10 @@ class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, bas
// modifiers //////////////////////////////////////////////////////////////
void clear() {
- m_values.clear();
- clear_buckets();
+ if (!empty()) {
+ m_values.clear();
+ clear_buckets();
+ }
}
auto insert(value_type const& value) -> std::pair<iterator, bool> {
-1
View File
@@ -540,7 +540,6 @@ add_subdirectory(externals)
# pass targets from externals # pass targets from externals
# TODO(crueter): CPMUtil Propagate func? # TODO(crueter): CPMUtil Propagate func?
find_package(enet) find_package(enet)
find_package(unordered_dense REQUIRED)
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
find_package(xbyak) find_package(xbyak)
+2 -13
View File
@@ -301,17 +301,6 @@
"repo": "eden-emu/tzdb_to_nx", "repo": "eden-emu/tzdb_to_nx",
"version": "230326" "version": "230326"
}, },
"unordered-dense": {
"bundled": true,
"find_args": "CONFIG",
"hash": "d2106f6640f6bfb81755e4b8bfb64982e46ec4a507cacdb38f940123212ccf35a20b43c70c6f01d7bfb8c246d1a16f7845d8052971949cea9def1475e3fa02c8",
"package": "unordered_dense",
"patches": [
"0001-avoid-memset-when-clearing-an-empty-table.patch"
],
"repo": "martinus/unordered_dense",
"version": "7b55cab841"
},
"vulkan-headers": { "vulkan-headers": {
"hash": "d2846ea228415772645eea4b52a9efd33e6a563043dd3de059e798be6391a8f0ca089f455ae420ff22574939ed0f48ed7c6ff3d5a9987d5231dbf3b3f89b484b", "hash": "d2846ea228415772645eea4b52a9efd33e6a563043dd3de059e798be6391a8f0ca089f455ae420ff22574939ed0f48ed7c6ff3d5a9987d5231dbf3b3f89b484b",
"min_version": "1.4.317", "min_version": "1.4.317",
@@ -340,10 +329,10 @@
"version": "vulkan-sdk-%NUMERIC_VERSION%" "version": "vulkan-sdk-%NUMERIC_VERSION%"
}, },
"xbyak": { "xbyak": {
"hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a", "hash": "e0aa0a603dd3ac1a39d82213df1e73c042831aec2d6b2fe382c899651eaae4ed7e8aeb6be9c41ea0097087c9d091961ab18f313cd6a9e10d621715ffd49bfe36",
"package": "xbyak", "package": "xbyak",
"repo": "herumi/xbyak", "repo": "herumi/xbyak",
"version": "v7.35.2" "version": "v7.40.1"
}, },
"zlib": { "zlib": {
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
+2
View File
@@ -509,6 +509,8 @@ QWidget#contentRichDialog QLabel#label_title_rich {
} }
QWidget#contentDialog QLabel#label_dialog { QWidget#contentDialog QLabel#label_dialog {
background: #2E2E2E;
padding: 20px 65px; padding: 20px 65px;
} }
+4 -5
View File
@@ -80,7 +80,6 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c
* [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on * [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on
* This package is known to be broken on the AUR. * This package is known to be broken on the AUR.
* [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on * [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on
* [unordered-dense](https://github.com/martinus/unordered_dense)
On amd64: On amd64:
@@ -119,7 +118,7 @@ Now, install all deps:
sudo emerge -a \ sudo emerge -a \
app-arch/lz4 app-arch/zstd app-arch/unzip \ app-arch/lz4 app-arch/zstd app-arch/unzip \
dev-libs/libfmt dev-libs/libusb dev-libs/mcl dev-libs/sirit \ dev-libs/libfmt dev-libs/libusb dev-libs/mcl dev-libs/sirit \
dev-libs/unordered_dense dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \ dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \ dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
dev-util/vulkan-utility-libraries dev-util/glslang \ dev-util/vulkan-utility-libraries dev-util/glslang \
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \ media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
@@ -260,7 +259,7 @@ brew install molten-vk
<details> <details>
<summary>FreeBSD</summary> <summary>FreeBSD</summary>
As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6` As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib vulkan-headers quazip-qt6`
If using FreeBSD 12 or prior, use `devel/pkg-config` instead. If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
@@ -294,7 +293,7 @@ pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gm
<summary>DragonFlyBSD</summary> <summary>DragonFlyBSD</summary>
```sh ```sh
pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl3 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl3 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 libva-vdpau-driver libva-utils libva-intel-driver
``` ```
[Caveats](./Caveats.md#dragonflybsd). [Caveats](./Caveats.md#dragonflybsd).
@@ -328,7 +327,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
```sh ```sh
BASE="git make autoconf libtool automake-wrapper jq patch" BASE="git make autoconf libtool automake-wrapper jq patch"
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL3" MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb openssl SDL3"
# Either x86_64 or clang-aarch64 (Windows on ARM) # Either x86_64 or clang-aarch64 (Windows on ARM)
packages="$BASE" packages="$BASE"
for pkg in $MINGW; do for pkg in $MINGW; do
+24 -8
View File
@@ -43,6 +43,7 @@ This guide will walk you through adding a new boolean toggle setting to Eden's c
Firstly add your desired toggle: Firstly add your desired toggle:
Example: `src/common/setting.h` Example: `src/common/setting.h`
```cpp ```cpp
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions}; SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
``` ```
@@ -67,6 +68,7 @@ Common Categories:
Add the toggle to the Qt UI, where you wish for it to appear and place it there. Add the toggle to the Qt UI, where you wish for it to appear and place it there.
Example: `src/qt_common/config/shared_translation.cpp` Example: `src/qt_common/config/shared_translation.cpp`
```cpp ```cpp
INSERT(Settings, INSERT(Settings,
your_setting_name, your_setting_name,
@@ -91,6 +93,7 @@ INSERT(Settings,
Add where it should be in the settings. Add where it should be in the settings.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
```kts ```kts
RENDERER_YOUR_SETTING_NAME("your_setting_name"), RENDERER_YOUR_SETTING_NAME("your_setting_name"),
``` ```
@@ -106,6 +109,7 @@ RENDERER_YOUR_SETTING_NAME("your_setting_name"),
Add the toggle to the Kotlin (Android) UI Add the toggle to the Kotlin (Android) UI
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
```kts ```kts
put( put(
SwitchSetting( SwitchSetting(
@@ -123,6 +127,7 @@ put(
Add your setting within the right category. Add your setting within the right category.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
```kts ```kts
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key) add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
``` ```
@@ -137,6 +142,7 @@ add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
Add your setting and description in the appropriate place. Add your setting and description in the appropriate place.
Example: `src/android/app/src/main/res/values/strings.xml` Example: `src/android/app/src/main/res/values/strings.xml`
```xml ```xml
<string name="your_setting_name">Your Setting Display Name</string> <string name="your_setting_name">Your Setting Display Name</string>
<string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string> <string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string>
@@ -150,6 +156,7 @@ Now the UI part is done find a place in the code for the toggle,
And use it to your heart's desire! And use it to your heart's desire!
Example: Example:
```cpp ```cpp
const bool your_value = Settings::values.your_setting_name.GetValue(); const bool your_value = Settings::values.your_setting_name.GetValue();
@@ -196,25 +203,31 @@ Common advantages recap:
#### Accessing Debug Knobs (dev side) #### Accessing Debug Knobs (dev side)
Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set: Use the `Settings::GetDebugKnobAt(u8 i)` function to check if a specific bit is set:
```cpp ```cpp
//cpp side //cpp side
#include "common/settings.h" #include "common/settings.h"
//To use it as a general purpose uint var:
unsigned int debug_knobs = Settings::values.debug_knobs.GetValue();
// Check if bit 0 is set // Check if bit 0 is set
bool feature_enabled = Settings::getDebugKnobAt(0); bool feature_enabled = Settings::GetDebugKnobAt(0);
// Check if bit 15 is set // Check if bit 15 is set
bool another_feature = Settings::getDebugKnobAt(15); bool another_feature = Settings::GetDebugKnobAt(15);
``` ```
```kts ```kts
//kotlin side //kotlin side
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
//To use it as a general purpose uint var
val debug_knobs: Int = UShortSetting.DEBUG_KNOBS.getInt()
// Check if bit x is set // Check if bit x is set
bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15 bool feature_enabled = Settings.GetDebugKnobAt(x); //x as integer from 0 to 15
``` ```
The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise. The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise.
@@ -247,6 +260,7 @@ There are two main confusions when talking about knobs:
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1). Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
Debug knobs are **zero-based**, which means: Debug knobs are **zero-based**, which means:
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise) * The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc. * You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
@@ -259,6 +273,7 @@ Whenever you're instructing tests or reporting results, be precise about whether
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once: ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
Examples: Examples:
- **knobs=0**: no knobs enabled - **knobs=0**: no knobs enabled
- **knobs=1**: knob0 enabled, others disabled - **knobs=1**: knob0 enabled, others disabled
- **knobs=2**: knob1 enabled, others disabled - **knobs=2**: knob1 enabled, others disabled
@@ -270,6 +285,7 @@ Examples:
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once: Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
Examples: Examples:
- **knob0**: knob 0 enabled, others disabled - **knob0**: knob 0 enabled, others disabled
- **knob1**: knob 1 enabled, others disabled - **knob1**: knob 1 enabled, others disabled
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled - **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
@@ -282,12 +298,12 @@ Examples:
```cpp ```cpp
void SomeFunction() { void SomeFunction() {
if (Settings::getDebugKnobAt(0)) { if (Settings::GetDebugKnobAt(0)) {
LOG_DEBUG(Common, "Debug feature 0 is enabled"); LOG_DEBUG(Common, "Debug feature 0 is enabled");
// Additional debug code here // Additional debug code here
} }
if (Settings::getDebugKnobAt(1)) { if (Settings::GetDebugKnobAt(1)) {
LOG_DEBUG(Common, "Debug feature 1 is enabled"); LOG_DEBUG(Common, "Debug feature 1 is enabled");
// Different debug behavior // Different debug behavior
} }
@@ -299,7 +315,7 @@ void SomeFunction() {
```cpp ```cpp
bool UseOptimizedPath() { bool UseOptimizedPath() {
// Skip optimization if debug bit 2 is set for testing // Skip optimization if debug bit 2 is set for testing
return !Settings::getDebugKnobAt(2); return !Settings::GetDebugKnobAt(2);
} }
``` ```
@@ -309,7 +325,7 @@ bool UseOptimizedPath() {
void ExperimentalFeature() { void ExperimentalFeature() {
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3; static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) { if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
// Fallback to stable implementation // Fallback to stable implementation
StableImplementation(); StableImplementation();
return; return;
-26
View File
@@ -308,32 +308,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
``` ```
### unordered_dense
```
MIT License
Copyright (c) 2022 Martin Leitner-Ankerl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
### xbyak ### xbyak
``` ```
-3
View File
@@ -58,9 +58,6 @@ if (WIN32 AND NOT TARGET LLVM::Demangle)
add_library(LLVM::Demangle ALIAS demangle) add_library(LLVM::Demangle ALIAS demangle)
endif() endif()
# unordered_dense
AddJsonPackage(unordered-dense)
# httplib # httplib
if (IOS) if (IOS)
set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF) set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF)
@@ -220,7 +220,7 @@ object NativeLibrary {
external fun refreshThreadPolicies() external fun refreshThreadPolicies()
external fun getDebugKnobAt(index: Int): Boolean external fun GetDebugKnobAt(index: Int): Boolean
/** /**
* Set the current speed limit to the configured turbo speed. * Set the current speed limit to the configured turbo speed.
@@ -35,8 +35,8 @@ object Settings {
fun getPlayerString(player: Int): String = fun getPlayerString(player: Int): String =
YuzuApplication.appContext.getString(R.string.preferences_player, player) YuzuApplication.appContext.getString(R.string.preferences_player, player)
fun getDebugKnobAt(index: Int): Boolean { fun GetDebugKnobAt(index: Int): Boolean {
return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index) return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index)
} }
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
@@ -11,8 +11,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(override val key: String) : AbstractShortSetting { enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit"), RENDERER_SPEED_LIMIT("speed_limit"),
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"), RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"), RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit")
DEBUG_KNOBS("debug_knobs")
; ;
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal) override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
@@ -11,6 +11,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class StringSetting(override val key: String) : AbstractStringSetting { enum class StringSetting(override val key: String) : AbstractStringSetting {
DRIVER_PATH("driver_path"), DRIVER_PATH("driver_path"),
DEVICE_NAME("device_name"), DEVICE_NAME("device_name"),
LOG_FILTER("log_filter"),
PROGRAM_ARGS("program_args"), PROGRAM_ARGS("program_args"),
WEB_TOKEN("eden_token"), WEB_TOKEN("eden_token"),
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class UShortSetting(override val key: String) : AbstractIntSetting {
DEBUG_KNOBS("debug_knobs")
;
override fun getInt(needsGlobal: Boolean): Int =
NativeConfig.getUnsignedShort(key, needsGlobal)
override fun setInt(value: Int) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setUnsignedShort(key, value)
}
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
override fun reset() = NativeConfig.setUnsignedShort(key, defaultValue)
}
@@ -20,6 +20,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting 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.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
import org.yuzu.yuzu_emu.network.NetDataValidators import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
@@ -624,6 +625,7 @@ abstract class SettingsItem(
IntSetting.FSR_SHARPENING_SLIDER, IntSetting.FSR_SHARPENING_SLIDER,
titleId = R.string.fsr_sharpness, titleId = R.string.fsr_sharpness,
descriptionId = R.string.fsr_sharpness_description, descriptionId = R.string.fsr_sharpness_description,
max = 200,
units = "%" units = "%"
) )
) )
@@ -1031,9 +1033,16 @@ abstract class SettingsItem(
descriptionId = R.string.use_auto_stub_description descriptionId = R.string.use_auto_stub_description
) )
) )
put(
StringInputSetting(
StringSetting.LOG_FILTER,
titleId = R.string.log_filter,
descriptionId = R.string.log_filter_description
)
)
put( put(
SpinBoxSetting( SpinBoxSetting(
ShortSetting.DEBUG_KNOBS, UShortSetting.DEBUG_KNOBS,
titleId = R.string.debug_knobs, titleId = R.string.debug_knobs,
descriptionId = R.string.debug_knobs_description, descriptionId = R.string.debug_knobs_description,
valueHint = R.string.debug_knobs_hint, valueHint = R.string.debug_knobs_hint,
@@ -25,6 +25,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag 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.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
@@ -99,7 +100,12 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_FRAME_GEN.key) add(BooleanSetting.RENDERER_FRAME_GEN.key)
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key) add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key) if (IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(
getNeedsGlobalForKey(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
) == 0
) {
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
}
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key) add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key) add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean( if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
@@ -307,8 +313,6 @@ class SettingsFragmentPresenter(
// TODO(crueter): sub-submenus? // TODO(crueter): sub-submenus?
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
sl.apply { sl.apply {
// add(IntSetting.RENDERER_NVDEC_EMULATION.key)
add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_RESOLUTION.key)
add(IntSetting.RENDERER_VSYNC.key) add(IntSetting.RENDERER_VSYNC.key)
add(IntSetting.RENDERER_SCALING_FILTER.key) add(IntSetting.RENDERER_SCALING_FILTER.key)
@@ -325,6 +329,7 @@ class SettingsFragmentPresenter(
add(IntSetting.MAX_ANISOTROPY.key) add(IntSetting.MAX_ANISOTROPY.key)
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key) add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key) add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
add(IntSetting.RENDERER_NVDEC_EMULATION.key)
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key) add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key) add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
@@ -1318,11 +1323,12 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.log)) add(HeaderSetting(R.string.log))
add(BooleanSetting.DEBUG_FLUSH_BY_LINE.key) add(BooleanSetting.DEBUG_FLUSH_BY_LINE.key)
add(StringSetting.LOG_FILTER.key)
} }
add(HeaderSetting(R.string.general)) add(HeaderSetting(R.string.general))
add(ShortSetting.DEBUG_KNOBS.key) add(UShortSetting.DEBUG_KNOBS.key)
add(StringSetting.PROGRAM_ARGS.key) add(StringSetting.PROGRAM_ARGS.key)
if (!NativeConfig.isPerGameConfigLoaded()) { if (!NativeConfig.isPerGameConfigLoaded()) {
@@ -1182,7 +1182,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
container, container,
IntSetting.FSR_SHARPENING_SLIDER, IntSetting.FSR_SHARPENING_SLIDER,
minValue = 0, minValue = 0,
maxValue = 100, maxValue = 200,
units = "%" units = "%"
) )
} }
@@ -18,6 +18,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.doOnPreDraw
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@@ -46,7 +47,6 @@ import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler import info.debatty.java.stringsimilarity.JaroWinkler
import java.util.Locale import java.util.Locale
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.view.doOnNextLayout
class GamesFragment : Fragment() { class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null private var _binding: FragmentGamesBinding? = null
@@ -58,7 +58,10 @@ class GamesFragment : Fragment() {
private var originalHeaderLeftMargin: Int? = null private var originalHeaderLeftMargin: Int? = null
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
private var fallbackBottomInset: Int = 0 private var pendingPostReloadListSettle = false
private var pendingPostReloadListSettleGeneration = 0
private var gameListSubmitGeneration = 0
private var committedGameListSubmitGeneration = 0
companion object { companion object {
private const val SEARCH_TEXT = "SearchText" private const val SEARCH_TEXT = "SearchText"
@@ -168,10 +171,9 @@ class GamesFragment : Fragment() {
gamesViewModel.shouldScrollAfterReload.collect(viewLifecycleOwner) { shouldScroll -> gamesViewModel.shouldScrollAfterReload.collect(viewLifecycleOwner) { shouldScroll ->
if (shouldScroll) { if (shouldScroll) {
binding.gridGames.post { pendingPostReloadListSettle = true
(binding.gridGames as? CarouselRecyclerView)?.pendingScrollAfterReload = true pendingPostReloadListSettleGeneration = gameListSubmitGeneration
gameAdapter.notifyDataSetChanged() schedulePostReloadListSettle()
}
gamesViewModel.setShouldScrollAfterReload(false) gamesViewModel.setShouldScrollAfterReload(false)
} }
} }
@@ -223,12 +225,7 @@ class GamesFragment : Fragment() {
} }
else -> throw IllegalArgumentException("Invalid view type: $savedViewType") else -> throw IllegalArgumentException("Invalid view type: $savedViewType")
} }
if (savedViewType == GameAdapter.VIEW_TYPE_CAROUSEL) { if (savedViewType != GameAdapter.VIEW_TYPE_CAROUSEL) {
(binding.gridGames as? View)?.let { it -> ViewCompat.requestApplyInsets(it)}
doOnNextLayout { //Carousel: important to avoid overlap issues
(this as? CarouselRecyclerView)?.notifyLaidOut(fallbackBottomInset)
}
} else {
(this as? CarouselRecyclerView)?.setupCarousel(false) (this as? CarouselRecyclerView)?.setupCarousel(false)
} }
adapter = gameAdapter adapter = gameAdapter
@@ -273,11 +270,42 @@ class GamesFragment : Fragment() {
lastSearchText = currentSearchText lastSearchText = currentSearchText
lastFilter = currentFilter lastFilter = currentFilter
} else { } else {
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(games) submitGameList(games)
gamesViewModel.setFilteredGames(games) gamesViewModel.setFilteredGames(games)
} }
} }
private fun submitGameList(games: List<Game>) {
val adapter = (binding.gridGames as? RecyclerView)?.adapter as? GameAdapter
if (adapter == null) {
schedulePostReloadListSettle()
return
}
val submitGeneration = ++gameListSubmitGeneration
adapter.submitList(games) {
if (committedGameListSubmitGeneration < submitGeneration) {
committedGameListSubmitGeneration = submitGeneration
}
schedulePostReloadListSettle()
}
}
private fun schedulePostReloadListSettle() {
if (!pendingPostReloadListSettle || _binding == null) return
binding.gridGames.doOnPreDraw {
if (!pendingPostReloadListSettle || _binding == null) return@doOnPreDraw
if (committedGameListSubmitGeneration < pendingPostReloadListSettleGeneration) {
schedulePostReloadListSettle()
return@doOnPreDraw
}
pendingPostReloadListSettle = false
(binding.gridGames as? CarouselRecyclerView)?.refreshView()
}
}
private fun setupTopView() { private fun setupTopView() {
binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int -> binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int ->
if (text.toString().isNotEmpty()) { if (text.toString().isNotEmpty()) {
@@ -414,9 +442,7 @@ class GamesFragment : Fragment() {
val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault()) val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
if (searchTerm.isEmpty()) { if (searchTerm.isEmpty()) {
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList( submitGameList(filteredList)
filteredList
)
gamesViewModel.setFilteredGames(filteredList) gamesViewModel.setFilteredGames(filteredList)
return return
} }
@@ -432,7 +458,7 @@ class GamesFragment : Fragment() {
} }
}.sortedByDescending { it.score }.map { it.item } }.sortedByDescending { it.score }.map { it.item }
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(sortedList) submitGameList(sortedList)
gamesViewModel.setFilteredGames(sortedList) gamesViewModel.setFilteredGames(sortedList)
} }
@@ -557,11 +583,6 @@ class GamesFragment : Fragment() {
qlaunchButton.layoutParams = mlpQLaunch qlaunchButton.layoutParams = mlpQLaunch
} }
val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom)
fallbackBottomInset = bottomInset
(binding.gridGames as? CarouselRecyclerView)?.notifyInsetsReady(bottomInset)
windowInsets windowInsets
} }
} }
@@ -31,6 +31,7 @@ object GameHelper {
fun getGames(): List<Game> { fun getGames(): List<Game> {
val games = mutableListOf<Game>() val games = mutableListOf<Game>()
val gamesByProgramId = mutableMapOf<String, Game>()
val context = YuzuApplication.appContext val context = YuzuApplication.appContext
preferences = PreferenceManager.getDefaultSharedPreferences(context) preferences = PreferenceManager.getDefaultSharedPreferences(context)
@@ -63,6 +64,7 @@ object GameHelper {
addGamesRecursive( addGamesRecursive(
games, games,
gamesByProgramId,
FileUtil.listFiles(gameDirUri), FileUtil.listFiles(gameDirUri),
scanDepth, scanDepth,
mountedContainerUris mountedContainerUris
@@ -136,6 +138,7 @@ object GameHelper {
private fun addGamesRecursive( private fun addGamesRecursive(
games: MutableList<Game>, games: MutableList<Game>,
gamesByProgramId: MutableMap<String, Game>,
files: Array<MinimalDocumentFile>, files: Array<MinimalDocumentFile>,
depth: Int, depth: Int,
mountedContainerUris: MutableSet<String> mountedContainerUris: MutableSet<String>
@@ -148,6 +151,7 @@ object GameHelper {
if (it.isDirectory) { if (it.isDirectory) {
addGamesRecursive( addGamesRecursive(
games, games,
gamesByProgramId,
FileUtil.listFiles(it.uri), FileUtil.listFiles(it.uri),
depth - 1, depth - 1,
mountedContainerUris mountedContainerUris
@@ -156,8 +160,9 @@ object GameHelper {
val extension = FileUtil.getExtension(it.uri).lowercase() val extension = FileUtil.getExtension(it.uri).lowercase()
val filePath = it.uri.toString() val filePath = it.uri.toString()
if (externalContentExtensions.contains(extension) && val mountedContainer = externalContentExtensions.contains(extension) &&
mountedContainerUris.add(filePath)) { mountedContainerUris.add(filePath)
if (mountedContainer) {
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath) NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
} }
@@ -165,6 +170,20 @@ object GameHelper {
val game = getGame(it.uri, true, false) val game = getGame(it.uri, true, false)
if (game != null) { if (game != null) {
games.add(game) games.add(game)
if (game.programId != "0") {
gamesByProgramId[game.programId] = game
}
} else if (mountedContainer) {
GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId ->
gamesByProgramId[(programId and 0x800L.inv()).toString()]
}?.let { existingGame ->
NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId)
existingGame.version = GameMetadata.getVersion(
existingGame.path,
true
)
GameIconUtils.refreshGameIcon(existingGame)
}
} }
} }
} }
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -24,6 +27,15 @@ import coil.request.Options
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import java.util.Collections
import java.util.WeakHashMap
private val gameIconHashes = Collections.synchronizedMap(mutableMapOf<String, Int>())
private val gameIconTargets = Collections.synchronizedMap(WeakHashMap<ImageView, GameIconTarget>())
private fun Game.iconCacheKey(): String = "$path|$version"
private data class GameIconTarget(val game: Game, var iconHash: Int? = null)
class GameIconFetcher( class GameIconFetcher(
private val game: Game, private val game: Game,
@@ -31,14 +43,15 @@ class GameIconFetcher(
) : Fetcher { ) : Fetcher {
override suspend fun fetch(): FetchResult { override suspend fun fetch(): FetchResult {
return DrawableResult( return DrawableResult(
drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources), drawable = decodeGameIcon(game)!!.toDrawable(options.context.resources),
isSampled = false, isSampled = false,
dataSource = DataSource.DISK dataSource = DataSource.DISK
) )
} }
private fun decodeGameIcon(uri: String): Bitmap? { private fun decodeGameIcon(game: Game): Bitmap? {
val data = GameMetadata.getIcon(uri) val data = GameMetadata.getIcon(game.path)
gameIconHashes[game.iconCacheKey()] = data.contentHashCode()
return BitmapFactory.decodeByteArray( return BitmapFactory.decodeByteArray(
data, data,
0, 0,
@@ -54,7 +67,7 @@ class GameIconFetcher(
} }
class GameIconKeyer : Keyer<Game> { class GameIconKeyer : Keyer<Game> {
override fun key(data: Game, options: Options): String = data.path override fun key(data: Game, options: Options): String = data.iconCacheKey()
} }
object GameIconUtils { object GameIconUtils {
@@ -71,14 +84,58 @@ object GameIconUtils {
.build() .build()
fun loadGameIcon(game: Game, imageView: ImageView) { fun loadGameIcon(game: Game, imageView: ImageView) {
gameIconTargets[imageView] = GameIconTarget(game)
val request = ImageRequest.Builder(YuzuApplication.appContext) val request = ImageRequest.Builder(YuzuApplication.appContext)
.data(game) .data(game)
.target(imageView) .target(imageView)
.error(R.drawable.default_icon) .error(R.drawable.default_icon)
.listener(
onSuccess = { _, _ ->
val target = gameIconTargets[imageView]
if (target?.game?.iconCacheKey() == game.iconCacheKey()) {
gameIconHashes[game.iconCacheKey()]?.let {
target.iconHash = it
}
}
},
onError = { _, _ ->
gameIconTargets[imageView]?.iconHash = null
}
)
.build() .build()
imageLoader.enqueue(request) imageLoader.enqueue(request)
} }
fun refreshGameIcon(game: Game) {
val targets = synchronized(gameIconTargets) {
gameIconTargets
.filterValues { it.game.path == game.path && it.game.programId == game.programId }
.keys
.toList()
}
if (targets.isEmpty()) {
return
}
val iconHash = GameMetadata.getIcon(game.path).contentHashCode()
val targetsToRefresh = targets.filter { gameIconTargets[it]?.iconHash != iconHash }
if (targetsToRefresh.isEmpty()) {
return
}
imageLoader.memoryCache?.remove(MemoryCache.Key(game.iconCacheKey()))
targetsToRefresh.forEach { imageView ->
imageView.post {
val target = gameIconTargets[imageView] ?: return@post
if (target.game.path == game.path && target.game.programId == game.programId) {
if (target.iconHash != iconHash) {
loadGameIcon(game, imageView)
}
}
}
}
}
suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap { suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap {
val request = ImageRequest.Builder(YuzuApplication.appContext) val request = ImageRequest.Builder(YuzuApplication.appContext)
.data(game) .data(game)
@@ -80,6 +80,12 @@ object NativeConfig {
@Synchronized @Synchronized
external fun setShort(key: String, value: Short) external fun setShort(key: String, value: Short)
@Synchronized
external fun getUnsignedShort(key: String, needsGlobal: Boolean): Int
@Synchronized
external fun setUnsignedShort(key: String, value: Int)
@Synchronized @Synchronized
external fun getInt(key: String, needsGlobal: Boolean): Int external fun getInt(key: String, needsGlobal: Boolean): Int
@@ -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-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.ui package org.yuzu.yuzu_emu.ui
@@ -11,12 +11,17 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter import org.yuzu.yuzu_emu.adapters.GameAdapter
import androidx.core.view.doOnNextLayout import androidx.core.view.doOnNextLayout
import androidx.core.view.ViewCompat
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import org.yuzu.yuzu_emu.utils.FullscreenHelper
/** /**
* CarouselRecyclerView encapsulates all carousel content for the games UI. * CarouselRecyclerView encapsulates all carousel content for the games UI.
* It manages overlapping cards, center snapping, custom drawing order, * It manages overlapping cards, center snapping, custom drawing order,
@@ -30,10 +35,13 @@ class CarouselRecyclerView @JvmOverloads constructor(
private var overlapFactor: Float = 0f private var overlapFactor: Float = 0f
private var overlapPx: Int = 0 private var overlapPx: Int = 0
private var bottomInset: Int = -1 private var bottomInset: Int = 0
private var latestWindowInsets: WindowInsetsCompat? = null
private var cardGeometryInitialized: Boolean = false
private var overlapDecoration: OverlappingDecoration? = null private var overlapDecoration: OverlappingDecoration? = null
private var pagerSnapHelper: PagerSnapHelper? = null private var pagerSnapHelper: PagerSnapHelper? = null
private var scalingScrollListener: OnScrollListener? = null private var scalingScrollListener: OnScrollListener? = null
private var savedItemAnimator: RecyclerView.ItemAnimator? = null
companion object { companion object {
private const val CAROUSEL_CARD_SIZE_FACTOR = "CarouselCardSizeMultiplier" private const val CAROUSEL_CARD_SIZE_FACTOR = "CarouselCardSizeMultiplier"
@@ -42,8 +50,13 @@ class CarouselRecyclerView @JvmOverloads constructor(
private const val CAROUSEL_OVERLAP_FACTOR = "CarouselOverlapFactor" private const val CAROUSEL_OVERLAP_FACTOR = "CarouselOverlapFactor"
private const val CAROUSEL_MAX_FLING_COUNT = "CarouselMaxFlingCount" private const val CAROUSEL_MAX_FLING_COUNT = "CarouselMaxFlingCount"
private const val CAROUSEL_FLING_MULTIPLIER = "CarouselFlingMultiplier" private const val CAROUSEL_FLING_MULTIPLIER = "CarouselFlingMultiplier"
private const val CAROUSEL_CARDS_SCALING_SHAPE = "CarouselCardsScalingShape" private const val CAROUSEL_ARC_ANGLE_STEP_DEGREES = 15.0
private const val CAROUSEL_CARDS_ALPHA_SHAPE = "CarouselCardsAlphaShape" private const val CAROUSEL_ARC_MAX_ANGLE_DEGREES = 165.0
private const val CAROUSEL_ARC_DEPTH_MAX_ANGLE_DEGREES = 85.0
private const val CAROUSEL_ARC_DEPTH_STRETCH = 5.0f
private const val CAROUSEL_ARC_X_DEPTH_FACTOR = 0.55f
private const val CAROUSEL_ARC_FADE_OUT_START_DEGREES = 60.0
private const val CAROUSEL_ARC_FADE_OUT_END_DEGREES = 95.0
const val CAROUSEL_LAST_SCROLL_POSITION = "CarouselLastScrollPosition" const val CAROUSEL_LAST_SCROLL_POSITION = "CarouselLastScrollPosition"
const val CAROUSEL_VIEW_TYPE_PORTRAIT = "GamesViewTypePortrait" const val CAROUSEL_VIEW_TYPE_PORTRAIT = "GamesViewTypePortrait"
const val CAROUSEL_VIEW_TYPE_LANDSCAPE = "GamesViewTypeLandscape" const val CAROUSEL_VIEW_TYPE_LANDSCAPE = "GamesViewTypeLandscape"
@@ -83,6 +96,38 @@ class CarouselRecyclerView @JvmOverloads constructor(
init { init {
setChildrenDrawingOrderEnabled(true) setChildrenDrawingOrderEnabled(true)
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
latestWindowInsets = insets
updateCardGeometry()
applyCarouselPadding()
insets
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
ViewCompat.requestApplyInsets(this)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (w != oldw || h != oldh) {
updateCardGeometry()
applyCarouselPadding()
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (isCarouselMode) updateChildScalesAndAlpha()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
ViewCompat.requestApplyInsets(this)
post { updateCardGeometry() }
}
} }
override fun setAdapter(adapter: Adapter<*>?) { override fun setAdapter(adapter: Adapter<*>?) {
@@ -95,6 +140,8 @@ class CarouselRecyclerView @JvmOverloads constructor(
super.setAdapter(adapter) super.setAdapter(adapter)
(adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver) (adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver)
updateCardGeometry()
applyCarouselPadding()
} }
private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int { private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int {
@@ -160,46 +207,52 @@ class CarouselRecyclerView @JvmOverloads constructor(
} }
} }
fun shapingFunction(x: Float, option: Int = 0): Float {
return when (option) {
0 -> 1f // Off
1 -> 1f - x // linear descending
2 -> (1f - x) * (1f - x) // Ease out
3 -> if (x < 0.05f) 1f else (1f - x) * 0.8f
4 -> kotlin.math.cos(x * Math.PI).toFloat() // Cosine
5 -> kotlin.math.cos((1.5f * x).coerceIn(0f, 1f) * Math.PI).toFloat() // Cosine 1.5x trimmed
else -> 1f // Default to Off
}
}
fun updateChildScaleAndAlphaForPosition(child: View) { fun updateChildScaleAndAlphaForPosition(child: View) {
val cardSize = (adapter as? GameAdapter ?: return).cardSize val cardSize = (adapter as? GameAdapter ?: return).cardSize
val position = getChildViewHolder(child).bindingAdapterPosition val position = getChildViewHolder(child).bindingAdapterPosition
if (position == RecyclerView.NO_POSITION || cardSize <= 0) { if (position == RecyclerView.NO_POSITION || cardSize <= 0) {
return // No valid position or card size return // No valid position or card size
} }
child.layoutParams.width = cardSize val layoutParams = child.layoutParams
child.layoutParams.height = cardSize if (layoutParams.width != cardSize || layoutParams.height != cardSize) {
child.layoutParams = layoutParams.apply {
width = cardSize
height = cardSize
}
}
val signedDistance = getChildDistanceToCenter(child)
val itemStep = (cardSize - overlapPx).toFloat().coerceAtLeast(1f)
val angleStep = Math.toRadians(CAROUSEL_ARC_ANGLE_STEP_DEGREES).toFloat()
val maxAngle = Math.toRadians(CAROUSEL_ARC_MAX_ANGLE_DEGREES).toFloat()
val depthMaxAngle = Math.toRadians(CAROUSEL_ARC_DEPTH_MAX_ANGLE_DEGREES).toFloat()
val fadeOutStartAngle = Math.toRadians(CAROUSEL_ARC_FADE_OUT_START_DEGREES).toFloat()
val fadeOutEndAngle = Math.toRadians(CAROUSEL_ARC_FADE_OUT_END_DEGREES).toFloat()
val angle = (signedDistance / itemStep * angleStep).coerceIn(-maxAngle, maxAngle)
val arcRadius = itemStep / angleStep
val arcX = sin(angle) * arcRadius
val absoluteAngle = abs(angle)
val rawDepthInput = ((1f - cos(absoluteAngle)) / (1f - cos(depthMaxAngle)))
.coerceIn(0f, 1f)
val easedDepthTail = Math.pow(
(1f - rawDepthInput).toDouble(),
CAROUSEL_ARC_DEPTH_STRETCH.toDouble()
).toFloat()
val depthInput = (1f - easedDepthTail).coerceIn(0f, 1f)
val projectedArcX = arcX * (1f - rawDepthInput * CAROUSEL_ARC_X_DEPTH_FACTOR)
child.animate().cancel()
child.translationX = projectedArcX - signedDistance
val center = getRecyclerViewCenter()
val distance = abs(getChildDistanceToCenter(child))
val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1) val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1)
val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn( val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn(
0f, 0f,
1f 1f
) )
val shapeInput = (distance / center).coerceIn(0f, 1f) val shapedScaling = 1f - depthInput
val internalShapeSetting = resources.getInteger(R.integer.carousel_cards_scaling_shape)
val scalingShapeSetting = preferences.getInt(
CAROUSEL_CARDS_SCALING_SHAPE,
internalShapeSetting
)
val shapedScaling = shapingFunction(shapeInput, scalingShapeSetting)
val scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f) val scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f)
val maxDistance = width / 2f
val alphaInput = (distance / maxDistance).coerceIn(0f, 1f)
val internalBordersAlpha = resources.getFraction( val internalBordersAlpha = resources.getFraction(
R.fraction.carousel_bordercards_alpha, R.fraction.carousel_bordercards_alpha,
1, 1,
@@ -209,15 +262,12 @@ class CarouselRecyclerView @JvmOverloads constructor(
0f, 0f,
1f 1f
) )
val internalAlphaShapeSetting = resources.getInteger(R.integer.carousel_cards_alpha_shape) val shapedAlpha = cos(depthInput * Math.PI).toFloat()
val alphaShapeSetting = preferences.getInt( val baseAlpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
CAROUSEL_CARDS_ALPHA_SHAPE, val rearPresence = (1f - (absoluteAngle - fadeOutStartAngle) /
internalAlphaShapeSetting (fadeOutEndAngle - fadeOutStartAngle)).coerceIn(0f, 1f)
) val alpha = (baseAlpha * rearPresence).coerceIn(0f, 1f)
val shapedAlpha = shapingFunction(alphaInput, alphaShapeSetting)
val alpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
child.animate().cancel()
child.alpha = alpha child.alpha = alpha
child.scaleX = scale child.scaleX = scale
child.scaleY = scale child.scaleY = scale
@@ -242,38 +292,71 @@ class CarouselRecyclerView @JvmOverloads constructor(
} }
} }
fun notifyInsetsReady(newBottomInset: Int) { private fun resolveBottomInset(windowInsets: WindowInsetsCompat): Int {
if (bottomInset != newBottomInset) { val navigationBottom = if (FullscreenHelper.isFullscreenEnabled(context)) {
bottomInset = newBottomInset 0
}
if (isCarouselMode) {
setupCarousel(true)
} else { } else {
setupCarousel(false) windowInsets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars()).bottom
} }
val gestureInsets = windowInsets.getInsetsIgnoringVisibility(
WindowInsetsCompat.Type.systemGestures()
)
val cutoutInsets = windowInsets.getInsetsIgnoringVisibility(
WindowInsetsCompat.Type.displayCutout()
)
return maxOf(navigationBottom, gestureInsets.bottom, cutoutInsets.bottom)
} }
fun notifyLaidOut(fallBackBottomInset: Int) { private fun updateCardGeometry() {
if (bottomInset < 0) bottomInset = fallBackBottomInset if (!isCarouselMode || height <= 0) return
var gameAdapter = adapter as? GameAdapter ?: return
var newCardSize = cardSize(bottomInset)
if (gameAdapter.cardSize != newCardSize) {
gameAdapter.setCardSize(newCardSize)
}
if (isCarouselMode) { val gameAdapter = adapter as? GameAdapter ?: return
setupCarousel(true) val windowInsets = latestWindowInsets ?: ViewCompat.getRootWindowInsets(this) ?: return
}
}
fun cardSize(bottomInset: Int): Int { if (cardGeometryInitialized && !hasWindowFocus()) return
val newBottomInset = resolveBottomInset(windowInsets).coerceIn(0, height)
val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1) val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1)
val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn( val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(
0f, 0f,
1f 1f
) )
return (userFactor * (height - bottomInset)).toInt() val screenWidth = resources.displayMetrics.widthPixels.toFloat()
val screenHeight = resources.displayMetrics.heightPixels.toFloat()
val aspectFactor = ((screenWidth / screenHeight) / (20f / 9f))
.pow(0.75f)
.coerceIn(0.5f, 1f)
val newCardSize = minOf(
(height * userFactor).toInt(),
height - newBottomInset,
(height * aspectFactor).toInt()
)
if (newCardSize <= 0) return
val insetChanged = bottomInset != newBottomInset
val cardSizeChanged = gameAdapter.cardSize != newCardSize
bottomInset = newBottomInset
cardGeometryInitialized = true
if (cardSizeChanged) gameAdapter.setCardSize(newCardSize)
if (insetChanged || cardSizeChanged) setupCarousel(true)
}
private fun applyCarouselPadding() {
if (!isCarouselMode) return
val gameAdapter = adapter as? GameAdapter ?: return
val cardSize = gameAdapter.cardSize
if (cardSize <= 0 || bottomInset < 0) return
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0)
val sidePadding = (width - cardSize) / 2
if (paddingLeft != sidePadding || paddingTop != topPadding ||
paddingRight != sidePadding || paddingBottom != 0
) {
setPadding(sidePadding, topPadding, sidePadding, 0)
}
clipToPadding = false
} }
fun setupCarousel(enabled: Boolean) { fun setupCarousel(enabled: Boolean) {
@@ -282,6 +365,13 @@ class CarouselRecyclerView @JvmOverloads constructor(
if (gameAdapter.cardSize == 0) return if (gameAdapter.cardSize == 0) return
if (bottomInset < 0) return if (bottomInset < 0) return
itemAnimator?.let {
if (savedItemAnimator == null) {
savedItemAnimator = it
}
itemAnimator = null
}
useCustomDrawingOrder = true useCustomDrawingOrder = true
val cardSize = gameAdapter.cardSize val cardSize = gameAdapter.cardSize
@@ -295,9 +385,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
internalFlingMultiplier internalFlingMultiplier
).coerceIn(1f, 5f) ).coerceIn(1f, 5f)
// Detach SnapHelper during setup
pagerSnapHelper?.attachToRecyclerView(null)
// Add overlap decoration if not present // Add overlap decoration if not present
if (overlapDecoration == null) { if (overlapDecoration == null) {
overlapDecoration = OverlappingDecoration(overlapPx) overlapDecoration = OverlappingDecoration(overlapPx)
@@ -315,12 +402,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
addOnScrollListener(scalingScrollListener!!) addOnScrollListener(scalingScrollListener!!)
} }
if (cardSize > 0) { applyCarouselPadding()
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0) // Center vertically
val sidePadding = (width - cardSize) / 2 // Center first/last card
setPadding(sidePadding, topPadding, sidePadding, 0)
clipToPadding = false
}
if (pagerSnapHelper == null) { if (pagerSnapHelper == null) {
pagerSnapHelper = CenterPagerSnapHelper() pagerSnapHelper = CenterPagerSnapHelper()
@@ -336,6 +418,13 @@ class CarouselRecyclerView @JvmOverloads constructor(
// Detach PagerSnapHelper // Detach PagerSnapHelper
pagerSnapHelper?.attachToRecyclerView(null) pagerSnapHelper?.attachToRecyclerView(null)
pagerSnapHelper = null pagerSnapHelper = null
savedItemAnimator?.let {
if (itemAnimator == null) {
itemAnimator = it
}
savedItemAnimator = null
}
cardGeometryInitialized = false
useCustomDrawingOrder = false useCustomDrawingOrder = false
// Reset padding and fling // Reset padding and fling
setPadding(0, 0, 0, 0) setPadding(0, 0, 0, 0)
@@ -344,6 +433,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
// Reset scaling // Reset scaling
for (i in 0 until childCount) { for (i in 0 until childCount) {
val child = getChildAt(i) val child = getChildAt(i)
child?.translationX = 0f
child?.scaleX = 1f child?.scaleX = 1f
child?.scaleY = 1f child?.scaleY = 1f
child?.alpha = 1f child?.alpha = 1f
@@ -20,7 +20,7 @@ struct RomMetadata {
std::vector<u8> icon; std::vector<u8> icon;
bool isHomebrew; bool isHomebrew;
}; };
static ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache; static ::Common::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
static RomMetadata CacheRomMetadata(const std::string& path) { static RomMetadata CacheRomMetadata(const std::string& path) {
auto& instance = EmulationSession::GetInstance(); auto& instance = EmulationSession::GetInstance();
+61 -6
View File
@@ -76,6 +76,7 @@ extern "C" {
#include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h" #include "core/frontend/applets/web_browser.h"
#include "common/android/applets/web_browser.h" #include "common/android/applets/web_browser.h"
#include "core/file_sys/common_funcs.h"
#include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
@@ -311,11 +312,23 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
ConfigureFilesystemProvider(filepath); ConfigureFilesystemProvider(filepath);
// Load the ROM. // Load the ROM.
const u64 previous_program_id =
program_index != 0 && m_next_program_id.load() >
static_cast<u64>(Service::AM::AppletProgramId::MaxProgramId)
? m_next_program_id.load()
: 0;
Service::AM::FrontendAppletParameters params{ Service::AM::FrontendAppletParameters params{
.program_id = previous_program_id,
.applet_id = static_cast<Service::AM::AppletId>(m_applet_id), .applet_id = static_cast<Service::AM::AppletId>(m_applet_id),
.launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated .launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated
: Service::AM::LaunchType::ApplicationInitiated, : Service::AM::LaunchType::ApplicationInitiated,
.program_index = static_cast<s32>(program_index), .program_index = static_cast<s32>(program_index),
.previous_program_index =
previous_program_id != 0
? static_cast<s32>(previous_program_id -
FileSys::GetBaseTitleID(previous_program_id))
: -1,
}; };
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params); m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params);
@@ -328,8 +341,12 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
m_system.GetCpuManager().OnGpuReady(); m_system.GetCpuManager().OnGpuReady();
m_system.RegisterExitCallback([&] { HaltEmulation(); }); m_system.RegisterExitCallback([&] { HaltEmulation(); });
m_system.RegisterApplicationChangedCallback(
[&](u64 changed_program_id) { RequestDiskShaderCacheReload(changed_program_id); });
// Register an ExecuteProgram callback such that Core can execute a sub-program // Register an ExecuteProgram callback such that Core can execute a sub-program
m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) { m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) {
m_next_program_id = m_system.GetApplicationProcessProgramID();
m_next_program_index = program_index_; m_next_program_index = program_index_;
EmulationSession::GetInstance().HaltEmulation(); EmulationSession::GetInstance().HaltEmulation();
}); });
@@ -407,20 +424,58 @@ void EmulationSession::RunEmulation() {
} }
while (true) { while (true) {
std::optional<u64> reload_title;
{ {
[[maybe_unused]] std::unique_lock lock(m_mutex); [[maybe_unused]] std::unique_lock lock(m_mutex);
if (m_cv.wait_for(lock, std::chrono::milliseconds(800), if (m_cv.wait_for(lock, std::chrono::milliseconds(800), [&]() {
[&]() { return !m_is_running; })) { return !m_is_running || m_pending_shader_cache_title.has_value();
// Emulation halted. })) {
break; if (!m_is_running) {
break;
}
reload_title = std::exchange(m_pending_shader_cache_title, std::nullopt);
} }
} }
if (reload_title.has_value())
ReloadDiskShaderCache(*reload_title);
} }
// Reset current applet ID. // Reset current applet ID.
m_applet_id = static_cast<int>(Service::AM::AppletId::Application); m_applet_id = static_cast<int>(Service::AM::AppletId::Application);
} }
void EmulationSession::RequestDiskShaderCacheReload(u64 program_id) {
{
std::scoped_lock lock(m_mutex);
m_pending_shader_cache_title = program_id;
}
m_cv.notify_one();
}
void EmulationSession::ReloadDiskShaderCache(u64 program_id) {
if (!Settings::values.use_disk_shader_cache.GetValue())
return;
LOG_INFO(Frontend, "Reloading disk shader cache for {:016X}", program_id);
const bool was_paused = m_is_paused;
m_system.Pause();
m_system.GPU().WaitForIdle();
m_system.GPU().ObtainContext();
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
m_system.Renderer().ReadRasterizer()->LoadDiskResources(program_id, std::stop_token{},
LoadDiskCacheProgress);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
m_system.GPU().ReleaseContext();
if (!was_paused)
m_system.Run();
}
Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() { Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
return m_software_keyboard; return m_software_keyboard;
} }
@@ -1245,8 +1300,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jo
Common::RefreshThreadPolicies(); Common::RefreshThreadPolicies();
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) { jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index))); return static_cast<jboolean>(Settings::GetDebugKnobAt(static_cast<u8>(index)));
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) { void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
+6
View File
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
#include <android/native_window_jni.h> #include <android/native_window_jni.h>
#include "common/android/applets/software_keyboard.h" #include "common/android/applets/software_keyboard.h"
#include "core/core.h" #include "core/core.h"
@@ -44,6 +46,7 @@ public:
void HaltEmulation(); void HaltEmulation();
void RunEmulation(); void RunEmulation();
void ShutdownEmulation(); void ShutdownEmulation();
void RequestDiskShaderCacheReload(u64 program_id);
const Core::PerfStatsResults& PerfStats(); const Core::PerfStatsResults& PerfStats();
int ShadersBuilding(); int ShadersBuilding();
@@ -65,6 +68,7 @@ private:
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max); static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
static void OnEmulationStopped(Core::SystemResultStatus result); static void OnEmulationStopped(Core::SystemResultStatus result);
static void ChangeProgram(std::size_t program_index); static void ChangeProgram(std::size_t program_index);
void ReloadDiskShaderCache(u64 program_id);
private: private:
// Window management // Window management
@@ -83,6 +87,7 @@ private:
Common::Android::SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; Common::Android::SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
int m_applet_id{1}; int m_applet_id{1};
std::optional<u64> m_pending_shader_cache_title;
// GPU driver parameters // GPU driver parameters
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
@@ -93,4 +98,5 @@ private:
// Program index for next boot // Program index for next boot
std::atomic<s32> m_next_program_index = -1; std::atomic<s32> m_next_program_index = -1;
std::atomic<u64> m_next_program_id = 0;
}; };
@@ -130,6 +130,25 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob
setting->SetValue(value); setting->SetValue(value);
} }
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey,
jboolean needGlobal) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return -1;
}
return static_cast<jint>(setting->GetValue(static_cast<bool>(needGlobal)));
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey, jint value) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetValue(static_cast<u16>(value));
}
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
jboolean needGlobal) { jboolean needGlobal) {
auto setting = getSetting<int>(env, jkey); auto setting = getSetting<int>(env, jkey);
@@ -21,7 +21,7 @@
#include "input_common/drivers/virtual_gamepad.h" #include "input_common/drivers/virtual_gamepad.h"
#include "native.h" #include "native.h"
ankerl::unordered_dense::map<std::string, std::unique_ptr<AndroidConfig>> map_profiles; ::Common::unordered_map<std::string, std::unique_ptr<AndroidConfig>> map_profiles;
bool IsHandheldOnly() { bool IsHandheldOnly() {
const auto npad_style_set = const auto npad_style_set =
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="?attr/colorControlHighlight" />
<item android:state_focused="true" android:color="@android:color/transparent" />
<item android:state_selected="true" android:color="@android:color/transparent" />
<item android:state_hovered="true" android:color="@android:color/transparent" />
<item android:color="@android:color/transparent" />
</selector>
@@ -10,6 +10,7 @@
android:clipChildren="true" android:clipChildren="true"
android:layout_margin="0dp" android:layout_margin="0dp"
app:cardBackgroundColor="@color/eden_card_background" app:cardBackgroundColor="@color/eden_card_background"
app:rippleColor="@color/game_card_ripple"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="@color/eden_border"> app:strokeColor="@color/eden_border">
@@ -11,6 +11,7 @@
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"
android:clipChildren="true" android:clipChildren="true"
app:rippleColor="@color/game_card_ripple"
android:layout_margin="4dp"> android:layout_margin="4dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@@ -22,6 +22,7 @@
android:focusable="true" android:focusable="true"
android:transitionName="card_game" android:transitionName="card_game"
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:rippleColor="@color/game_card_ripple"
android:foreground="@color/eden_border_gradient_start"> android:foreground="@color/eden_border_gradient_start">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@@ -22,6 +22,7 @@
android:focusable="true" android:focusable="true"
android:transitionName="card_game_compact" android:transitionName="card_game_compact"
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:rippleColor="@color/game_card_ripple"
android:foreground="@color/eden_border_gradient_start"> android:foreground="@color/eden_border_gradient_start">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@@ -12,6 +12,7 @@
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:cardElevation="0dp" app:cardElevation="0dp"
app:cardBackgroundColor="@android:color/transparent" app:cardBackgroundColor="@android:color/transparent"
app:rippleColor="@color/game_card_ripple"
app:strokeWidth="0dp"> app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@@ -1107,7 +1107,6 @@
<string name="theme_mode_light">فاتح</string> <string name="theme_mode_light">فاتح</string>
<string name="theme_mode_dark">داكن</string> <string name="theme_mode_dark">داكن</string>
<string name="multiplier_none">لا شيء</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">خلفيات سوداء</string> <string name="use_black_backgrounds">خلفيات سوداء</string>
@@ -991,7 +991,6 @@ Wirklich fortfahren?</string>
<string name="theme_mode_light">Hell</string> <string name="theme_mode_light">Hell</string>
<string name="theme_mode_dark">Dunkel</string> <string name="theme_mode_dark">Dunkel</string>
<string name="multiplier_none">Keine</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Schwarze Hintergründe</string> <string name="use_black_backgrounds">Schwarze Hintergründe</string>
@@ -1092,7 +1092,6 @@
<string name="theme_mode_light">Claro</string> <string name="theme_mode_light">Claro</string>
<string name="theme_mode_dark">Oscuro</string> <string name="theme_mode_dark">Oscuro</string>
<string name="multiplier_none">Nada</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Fondos oscuros</string> <string name="use_black_backgrounds">Fondos oscuros</string>
@@ -808,9 +808,6 @@
<string name="multiplier_x4">x4</string> <string name="multiplier_x4">x4</string>
<string name="multiplier_x8">x8</string> <string name="multiplier_x8">x8</string>
<string name="multiplier_x16">x16</string> <string name="multiplier_x16">x16</string>
<string name="multiplier_x32">x32</string>
<string name="multiplier_x64">x64</string>
<string name="multiplier_none">None</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">پس‌زمینه مشکی</string> <string name="use_black_backgrounds">پس‌زمینه مشکی</string>
@@ -1004,7 +1004,6 @@
<string name="theme_mode_light">Lumineux</string> <string name="theme_mode_light">Lumineux</string>
<string name="theme_mode_dark">Sombre</string> <string name="theme_mode_dark">Sombre</string>
<string name="multiplier_none">Aucun</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Arrière-plan noir</string> <string name="use_black_backgrounds">Arrière-plan noir</string>
@@ -935,7 +935,6 @@
<string name="theme_mode_light">Jasny</string> <string name="theme_mode_light">Jasny</string>
<string name="theme_mode_dark">Ciemny</string> <string name="theme_mode_dark">Ciemny</string>
<string name="multiplier_none">Brak</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Czarne tła</string> <string name="use_black_backgrounds">Czarne tła</string>
@@ -891,7 +891,6 @@
<string name="theme_mode_light">Claro</string> <string name="theme_mode_light">Claro</string>
<string name="theme_mode_dark">Escuro</string> <string name="theme_mode_dark">Escuro</string>
<string name="multiplier_none">Nenhum</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Planos de fundo pretos</string> <string name="use_black_backgrounds">Planos de fundo pretos</string>
@@ -1071,7 +1071,6 @@
<string name="theme_mode_light">Светлая</string> <string name="theme_mode_light">Светлая</string>
<string name="theme_mode_dark">Темная</string> <string name="theme_mode_dark">Темная</string>
<string name="multiplier_none">Отключено</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Чёрный фон</string> <string name="use_black_backgrounds">Чёрный фон</string>
@@ -1053,7 +1053,6 @@
<string name="theme_mode_light">Світла</string> <string name="theme_mode_light">Світла</string>
<string name="theme_mode_dark">Темна</string> <string name="theme_mode_dark">Темна</string>
<string name="multiplier_none">Жодного</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Чорний фон</string> <string name="use_black_backgrounds">Чорний фон</string>
@@ -1081,7 +1081,6 @@
<string name="theme_mode_light">浅色</string> <string name="theme_mode_light">浅色</string>
<string name="theme_mode_dark">深色</string> <string name="theme_mode_dark">深色</string>
<string name="multiplier_none"></string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">使用黑色背景</string> <string name="use_black_backgrounds">使用黑色背景</string>
@@ -1006,7 +1006,6 @@
<string name="theme_mode_light">淺色</string> <string name="theme_mode_light">淺色</string>
<string name="theme_mode_dark">深色</string> <string name="theme_mode_dark">深色</string>
<string name="multiplier_none"></string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">黑色背景</string> <string name="use_black_backgrounds">黑色背景</string>
+12 -23
View File
@@ -111,43 +111,38 @@
<item>1</item> <item>1</item>
</integer-array> </integer-array>
<!-- VRAM USAGE MODE CHOICES -->
<string-array name="vramUsageMethodNames"> <string-array name="vramUsageMethodNames">
<item>@string/vram_usage_conservative</item> <item>@string/vram_usage_conservative</item>
<item>@string/vram_usage_aggressive</item> <item>@string/vram_usage_aggressive</item>
</string-array> </string-array>
<!-- VRAM USAGE MODE VALUES -->
<integer-array name="vramUsageMethodValues"> <integer-array name="vramUsageMethodValues">
<item>0</item> <!-- Conservative --> <item>0</item>
<item>1</item> <!-- Aggressive --> <item>1</item>
</integer-array> </integer-array>
<!-- ASTC Decoding Method Choices -->
<string-array name="astcDecodingMethodNames"> <string-array name="astcDecodingMethodNames">
<item>@string/accelerate_astc_cpu</item> <item>@string/accelerate_astc_cpu</item>
<item>@string/accelerate_astc_gpu</item> <item>@string/accelerate_astc_gpu</item>
<item>@string/accelerate_astc_async</item> <item>@string/accelerate_astc_async</item>
</string-array> </string-array>
<!-- ASTC Decoding Method Values -->
<integer-array name="astcDecodingMethodValues"> <integer-array name="astcDecodingMethodValues">
<item>0</item> <!-- CPU --> <item>0</item>
<item>1</item> <!-- GPU --> <item>1</item>
<item>2</item> <!-- CPU Asynchronously --> <item>2</item>
</integer-array> </integer-array>
<!-- NVDEC Emulation Choices -->
<string-array name="rendererNvdecNames"> <string-array name="rendererNvdecNames">
<item>@string/nvdec_emulation_none</item> <!-- Off --> <item>@string/nvdec_emulation_none</item>
<item>@string/nvdec_emulation_cpu</item> <!-- Cpu --> <item>@string/nvdec_emulation_cpu</item>
<item>@string/nvdec_emulation_gpu</item> <!-- Gpu --> <item>@string/nvdec_emulation_gpu</item>
</string-array> </string-array>
<!-- NVDEC Emulation Values -->
<integer-array name="rendererNvdecValues"> <integer-array name="rendererNvdecValues">
<item>3</item> <!-- Off value --> <item>0</item>
<item>1</item> <!-- CPU value --> <item>1</item>
<item>2</item> <!-- GPU value --> <item>2</item>
</integer-array> </integer-array>
<string-array name="rendererResolutionNames"> <string-array name="rendererResolutionNames">
@@ -513,9 +508,6 @@
<item>@string/multiplier_x4</item> <item>@string/multiplier_x4</item>
<item>@string/multiplier_x8</item> <item>@string/multiplier_x8</item>
<item>@string/multiplier_x16</item> <item>@string/multiplier_x16</item>
<item>@string/multiplier_x32</item>
<item>@string/multiplier_x64</item>
<item>@string/multiplier_none</item>
</string-array> </string-array>
<integer-array name="anisoValues"> <integer-array name="anisoValues">
<item>0</item> <item>0</item>
@@ -524,9 +516,6 @@
<item>3</item> <item>3</item>
<item>4</item> <item>4</item>
<item>5</item> <item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
</integer-array> </integer-array>
<string-array name="verticalAlignmentEntries"> <string-array name="verticalAlignmentEntries">
@@ -5,8 +5,6 @@
<integer name="game_columns_grid">2</integer> <integer name="game_columns_grid">2</integer>
<integer name="carousel_max_fling_count">4</integer> <integer name="carousel_max_fling_count">4</integer>
<integer name="carousel_focus_search_repeat_threshold_ms">100</integer> <integer name="carousel_focus_search_repeat_threshold_ms">100</integer>
<integer name="carousel_cards_scaling_shape">1</integer>
<integer name="carousel_cards_alpha_shape">4</integer>
<!-- Default SWITCH landscape layout --> <!-- Default SWITCH landscape layout -->
<integer name="BUTTON_A_X">760</integer> <integer name="BUTTON_A_X">760</integer>
@@ -111,7 +111,7 @@
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC Emulation</string> <string name="nvdec_emulation">NVDEC Emulation</string>
<string name="nvdec_emulation_description">Select how video decoding (NVDEC) is handled during cutscenes and intros.</string> <string name="nvdec_emulation_description">Change to CPU if a crash occurs on cinematics.</string>
<string name="nvdec_emulation_cpu" translatable="false">CPU</string> <string name="nvdec_emulation_cpu" translatable="false">CPU</string>
<string name="nvdec_emulation_gpu" translatable="false">GPU</string> <string name="nvdec_emulation_gpu" translatable="false">GPU</string>
<string name="nvdec_emulation_none">None</string> <string name="nvdec_emulation_none">None</string>
@@ -636,6 +636,8 @@
<string name="log">Logging</string> <string name="log">Logging</string>
<string name="flush_by_line">Flush debug logs by line</string> <string name="flush_by_line">Flush debug logs by line</string>
<string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string> <string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string>
<string name="log_filter">Log filter</string>
<string name="log_filter_description">Controls Eden\'s log categories. Example: *:Info Service.LM:Debug</string>
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">GPU Logging</string> <string name="gpu_logging_header">GPU Logging</string>
@@ -1246,9 +1248,6 @@
<string name="multiplier_x4" translatable="false">x4</string> <string name="multiplier_x4" translatable="false">x4</string>
<string name="multiplier_x8" translatable="false">x8</string> <string name="multiplier_x8" translatable="false">x8</string>
<string name="multiplier_x16" translatable="false">x16</string> <string name="multiplier_x16" translatable="false">x16</string>
<string name="multiplier_x32" translatable="false">x32</string>
<string name="multiplier_x64" translatable="false">x64</string>
<string name="multiplier_none">None</string>
<!-- Black backgrounds theme --> <!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Black backgrounds</string> <string name="use_black_backgrounds">Black backgrounds</string>
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -7,13 +10,12 @@
namespace AudioCore::ADSP::OpusDecoder { namespace AudioCore::ADSP::OpusDecoder {
namespace { namespace {
bool IsValidChannelCount(u32 channel_count) { constexpr u32 OpusStreamCountMax = 255;
return channel_count == 1 || channel_count == 2;
}
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) { bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
return total_stream_count > 0 && static_cast<s32>(stereo_stream_count) >= 0 && return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax &&
stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count); static_cast<s32>(stereo_stream_count) >= 0 &&
stereo_stream_count <= total_stream_count;
} }
} // namespace } // namespace
+4 -1
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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -105,7 +108,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
shared_memory.host_send_data[4] = total_stream_count; shared_memory.host_send_data[4] = total_stream_count;
shared_memory.host_send_data[5] = stereo_stream_count; shared_memory.host_send_data[5] = stereo_stream_count;
ASSERT(channel_count <= MaxChannels); ASSERT(channel_count <= shared_memory.channel_mapping.size());
std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8)); std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8));
opus_decoder.Send(ADSP::Direction::DSP, opus_decoder.Send(ADSP::Direction::DSP,
+28 -27
View File
@@ -19,6 +19,23 @@
namespace AudioCore::Sink { namespace AudioCore::Sink {
namespace { namespace {
[[nodiscard]] bool InitializeAudio() {
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
// See https://github.com/PCSX2/pcsx2/pull/12312
// "SDL and cubeb backends previously resulted in different names for the output which
// caused them be identified as different applications by the OS."
//
// Keep in sync with cubeb_sink.cpp name.
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return false;
}
}
return true;
}
SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) { SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) {
int device_count = 0; int device_count = 0;
SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count) SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count)
@@ -204,20 +221,14 @@ private:
}; };
SDLSink::SDLSink(std::string_view target_device_name) { SDLSink::SDLSink(std::string_view target_device_name) {
if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (InitializeAudio()) {
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { if (target_device_name != auto_device_name && !target_device_name.empty()) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); output_device = target_device_name;
return; } else {
output_device.clear();
} }
device_channels = 2;
} }
if (target_device_name != auto_device_name && !target_device_name.empty()) {
output_device = target_device_name;
} else {
output_device.clear();
}
device_channels = 2;
} }
SDLSink::~SDLSink() = default; SDLSink::~SDLSink() = default;
@@ -265,15 +276,10 @@ void SDLSink::SetSystemVolume(f32 volume) {
} }
std::vector<std::string> ListSDLSinkDevices(bool capture) { std::vector<std::string> ListSDLSinkDevices(bool capture) {
if (!InitializeAudio())
return {}; //no devices
std::vector<std::string> device_list; std::vector<std::string> device_list;
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return {};
}
}
int device_count = 0; int device_count = 0;
SDL_AudioDeviceID* devices = SDL_AudioDeviceID* devices =
capture ? SDL_GetAudioRecordingDevices(&device_count) capture ? SDL_GetAudioRecordingDevices(&device_count)
@@ -304,13 +310,8 @@ bool IsSDLSuitable() {
return false; return false;
#else #else
// Check SDL can init // Check SDL can init
if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (!InitializeAudio()!
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { return false;
LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
SDL_GetError());
return false;
}
}
// We can set any latency frequency we want with SDL, so no need to check that. // We can set any latency frequency we want with SDL, so no need to check that.
+3 -2
View File
@@ -147,7 +147,8 @@ add_library(
cpu_features.cpp cpu_features.cpp
cpu_features.h cpu_features.h
httplib.h httplib.h
net/net.h net/net.cpp) net/net.h net/net.cpp
container/unordered_map.h container/unordered_set.h)
if(WIN32) if(WIN32)
target_sources(common PRIVATE windows/timer_resolution.cpp target_sources(common PRIVATE windows/timer_resolution.cpp
@@ -241,7 +242,7 @@ if (lz4_ADDED)
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
endif() endif()
target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads unordered_dense::unordered_dense) target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads)
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd) target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd)
# Please refer to src/common/demangle.cpp # Please refer to src/common/demangle.cpp
+16
View File
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/container_hash.h"
#include <boost/unordered/unordered_flat_map.hpp>
namespace Common {
template <class Key, class T, class Hash = std::hash<Key>, class Pred = std::equal_to<Key>,
class Allocator = std::allocator<std::pair<const Key, T>>>
using unordered_map = boost::unordered::unordered_flat_map<Key, T, Hash, Pred, Allocator>;
}
+16
View File
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/container_hash.h"
#include <boost/unordered/unordered_flat_set.hpp>
namespace Common {
template <class Key, class Hash = std::hash<Key>, class Pred = std::equal_to<Key>,
class Allocator = std::allocator<Key>>
using unordered_set = boost::unordered::unordered_flat_set<Key, Hash, Pred, Allocator>;
}
+33
View File
@@ -10,8 +10,11 @@
#include <array> #include <array>
#include <climits> #include <climits>
#include <cstdint> #include <cstdint>
#include <functional>
#include <limits> #include <limits>
#include <tuple>
#include <type_traits> #include <type_traits>
#include <utility>
#include <vector> #include <vector>
namespace Common { namespace Common {
@@ -69,10 +72,17 @@ struct HashCombineImpl<64> {
} // namespace detail } // namespace detail
template <typename T> template <typename T>
requires std::is_unsigned_v<T>
inline void HashCombine(std::size_t& seed, const T& v) { inline void HashCombine(std::size_t& seed, const T& v) {
seed = detail::HashCombineImpl<sizeof(std::size_t) * CHAR_BIT>::fn(seed, detail::HashValue(v)); seed = detail::HashCombineImpl<sizeof(std::size_t) * CHAR_BIT>::fn(seed, detail::HashValue(v));
} }
template <typename T>
requires (!std::is_unsigned_v<T>)
inline void HashCombine(std::size_t& seed, const T& v) {
seed = detail::HashCombineImpl<sizeof(std::size_t) * CHAR_BIT>::fn(seed, std::hash<T>{}(v));
}
template <typename It> template <typename It>
inline std::size_t HashRange(It first, It last) { inline std::size_t HashRange(It first, It last) {
std::size_t seed = 0; std::size_t seed = 0;
@@ -95,3 +105,26 @@ std::size_t HashValue(const std::vector<T, Allocator>& v) {
} }
} // namespace Common } // namespace Common
namespace std {
template <typename... Args>
struct hash<std::tuple<Args...>> {
std::size_t operator()(const std::tuple<Args...>& t) const noexcept {
std::size_t seed = 0;
std::apply([&seed](const Args&... args) { (Common::HashCombine(seed, args), ...); }, t);
return seed;
}
};
template <class A, class B>
struct hash<std::pair<A, B>> {
std::size_t operator()(const std::pair<A, B>& p) const noexcept {
std::size_t seed = 0;
Common::HashCombine(seed, p.first);
Common::HashCombine(seed, p.second);
return seed;
}
};
} // namespace std
+3 -3
View File
@@ -7,7 +7,7 @@
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/fs/fs.h" #include "common/fs/fs.h"
@@ -196,8 +196,8 @@ private:
SetLegacyPathImpl(legacy_path, new_path); SetLegacyPathImpl(legacy_path, new_path);
} }
ankerl::unordered_dense::map<EdenPath, fs::path> eden_paths; ::Common::unordered_map<EdenPath, fs::path> eden_paths;
ankerl::unordered_dense::map<EmuPath, fs::path> legacy_paths; ::Common::unordered_map<EmuPath, fs::path> legacy_paths;
}; };
bool ValidatePath(const fs::path& path) { bool ValidatePath(const fs::path& path) {
+2 -2
View File
@@ -7,7 +7,7 @@
#ifdef _WIN32 #ifdef _WIN32
#include <iterator> #include <iterator>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include <boost/icl/separate_interval_set.hpp> #include <boost/icl/separate_interval_set.hpp>
#include <windows.h> #include <windows.h>
#include "common/dynamic_library.h" #include "common/dynamic_library.h"
@@ -391,7 +391,7 @@ private:
std::mutex placeholder_mutex; ///< Mutex for placeholders std::mutex placeholder_mutex; ///< Mutex for placeholders
boost::icl::separate_interval_set<size_t> placeholders; ///< Mapped placeholders boost::icl::separate_interval_set<size_t> placeholders; ///< Mapped placeholders
ankerl::unordered_dense::map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset ::Common::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
}; };
#elif defined(__OPENORBIS__) || defined(__managarm__) #elif defined(__OPENORBIS__) || defined(__managarm__)
+2 -2
View File
@@ -9,7 +9,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "common/logging.h" #include "common/logging.h"
@@ -412,7 +412,7 @@ public:
namespace Impl { namespace Impl {
template <typename InputDeviceType> template <typename InputDeviceType>
using FactoryListType = ankerl::unordered_dense::map<std::string, std::shared_ptr<Factory<InputDeviceType>>>; using FactoryListType = ::Common::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
template <typename InputDeviceType> template <typename InputDeviceType>
struct FactoryList { struct FactoryList {
+79 -56
View File
@@ -39,6 +39,19 @@
namespace Common::Log { namespace Common::Log {
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
/// formatting on different frontends, as well as facilitating filtering and aggregation.
struct Entry {
char const* message = nullptr;
size_t message_len = 0;
std::chrono::microseconds timestamp;
Class log_class{};
Level log_level{};
const char* filename = nullptr;
const char* function = nullptr;
uint32_t line_num = 0;
};
namespace { namespace {
/// @brief Returns the name of the passed log class as a C-string. Subclasses are separated by periods /// @brief Returns the name of the passed log class as a C-string. Subclasses are separated by periods
@@ -70,8 +83,6 @@ const char* GetLevelName(Level log_level) {
} }
} }
}
// Some IDEs prefer <file>:<line> instead, so let's just do that :) // Some IDEs prefer <file>:<line> instead, so let's just do that :)
std::string FormatLogMessage(const Entry& entry) noexcept { std::string FormatLogMessage(const Entry& entry) noexcept {
if (!entry.filename) return ""; if (!entry.filename) return "";
@@ -79,10 +90,9 @@ std::string FormatLogMessage(const Entry& entry) noexcept {
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000); auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
auto const class_name = GetLogClassName(entry.log_class); auto const class_name = GetLogClassName(entry.log_class);
auto const level_name = GetLevelName(entry.log_level); auto const level_name = GetLevelName(entry.log_level);
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message); return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}\n", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message);
} }
namespace {
template <typename It> template <typename It>
Level GetLevelByName(const It begin, const It end) { Level GetLevelByName(const It begin, const It end) {
for (u32 i = 0; i < u32(Level::Count); ++i) { for (u32 i = 0; i < u32(Level::Count); ++i) {
@@ -127,25 +137,6 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
instance.SetClassLevel(log_class, level); instance.SetClassLevel(log_class, level);
return true; return true;
} }
} // Anonymous namespace
void Filter::ParseFilterString(std::string_view filter_view) {
auto clause_begin = filter_view.cbegin();
while (clause_begin != filter_view.cend()) {
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
// If clause isn't empty
if (clause_end != clause_begin) {
ParseFilterRule(*this, clause_begin, clause_end);
}
if (clause_end != filter_view.cend()) {
// Skip over the whitespace
++clause_end;
}
clause_begin = clause_end;
}
}
namespace {
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string /// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
/// do not be fooled this isn't generating new strings on .rodata :) /// do not be fooled this isn't generating new strings on .rodata :)
@@ -208,7 +199,7 @@ struct ColorConsoleBackend final : public Backend {
}()); }());
SetConsoleTextAttribute(console_handle, color); SetConsoleTextAttribute(console_handle, color);
auto const df = GetDirectFormatArgs(entry); auto const df = GetDirectFormatArgs(entry);
std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str()); std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
} }
} }
void Flush() noexcept override {} void Flush() noexcept override {}
@@ -220,22 +211,24 @@ struct ColorConsoleBackend final : public Backend {
~ColorConsoleBackend() noexcept override {} ~ColorConsoleBackend() noexcept override {}
void Write(const Entry& entry) noexcept override { void Write(const Entry& entry) noexcept override {
if (enabled) { if (enabled) {
#define ESC "\x1b"
auto const color_str = [&entry]() -> const char* { auto const color_str = [&entry]() -> const char* {
switch (entry.log_level) { switch (entry.log_level) {
#define CCB_MAKE_COLOR_FMT(X) ESC X CCB_PRINTF_FMT ESC "[0m\n" case Level::Debug: return "[0;36m"; // Cyan
case Level::Debug: return CCB_MAKE_COLOR_FMT("[0;36m"); // Cyan case Level::Info: return "[0;37m"; // Bright gray
case Level::Info: return CCB_MAKE_COLOR_FMT("[0;37m"); // Bright gray case Level::Warning: return "[1;33m"; // Bright yellow
case Level::Warning: return CCB_MAKE_COLOR_FMT("[1;33m"); // Bright yellow case Level::Error: return "[1;31m"; // Bright red
case Level::Error: return CCB_MAKE_COLOR_FMT("[1;31m"); // Bright red case Level::Critical: return "[1;35m"; // Bright magenta
case Level::Critical: return CCB_MAKE_COLOR_FMT("[1;35m"); // Bright magenta default: return "[1;30m"; // Grey
default: return CCB_MAKE_COLOR_FMT("[1;30m"); // Grey
#undef CCB_MAKE_COLOR_FMT
} }
}(); }();
auto const df = GetDirectFormatArgs(entry); auto const df = GetDirectFormatArgs(entry);
std::fprintf(stdout, color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str()); // more restrictive, because take for example this simple prelude:
#undef ESC // [ 50.872256] Config <Info> common/settings.cpp:142:LogSettings:
char buffer[256];
auto result = fmt::format_to_n(buffer, sizeof(buffer) - 1, "\x1b{}[{:4d}.{:06d}] {} <{}> {}:{}:{}: ", color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
std::fwrite(buffer, 1, (std::min)(sizeof(buffer) - 1, result.size), stdout);
std::fwrite(entry.message, 1, entry.message_len, stdout);
std::fwrite("\x1b[0m\n", 1, sizeof("\x1b[0m\n"), stdout);
} }
} }
void Flush() noexcept override {} void Flush() noexcept override {}
@@ -246,7 +239,7 @@ struct ColorConsoleBackend final : public Backend {
#ifndef __OPENORBIS__ #ifndef __OPENORBIS__
/// @brief Backend that writes to a file passed into the constructor /// @brief Backend that writes to a file passed into the constructor
struct FileBackend final : public Backend { struct FileBackend final : public Backend {
explicit FileBackend(const std::filesystem::path& filename) noexcept { explicit FileBackend(const std::filesystem::path filename) noexcept {
auto old_filename = filename; auto old_filename = filename;
old_filename += ".old.txt"; old_filename += ".old.txt";
// Existence checks are done within the functions themselves. // Existence checks are done within the functions themselves.
@@ -261,7 +254,7 @@ struct FileBackend final : public Backend {
if (!enabled) if (!enabled)
return; return;
auto message = FormatLogMessage(entry).append(1, '\n'); auto message = FormatLogMessage(entry);
#ifndef __ANDROID__ #ifndef __ANDROID__
if (Settings::values.censor_username.GetValue()) { if (Settings::values.censor_username.GetValue()) {
// This must be a static otherwise it would get checked on EVERY // This must be a static otherwise it would get checked on EVERY
@@ -269,8 +262,7 @@ struct FileBackend final : public Backend {
static std::string username = []() -> std::string { static std::string username = []() -> std::string {
// in order of precedence // in order of precedence
// LOGNAME usually works on UNIX, USERNAME on Windows // LOGNAME usually works on UNIX, USERNAME on Windows
// Some UNIX systems suck and don't use LOGNAME so we also // Some UNIX systems suck and don't use LOGNAME so we also need USER :(
// need USER :(
for (auto const var : { "LOGNAME", "USERNAME", "USER", }) for (auto const var : { "LOGNAME", "USERNAME", "USER", })
if (auto const s = ::getenv(var); s != nullptr) if (auto const s = ::getenv(var); s != nullptr)
return std::string{s}; return std::string{s};
@@ -280,7 +272,7 @@ struct FileBackend final : public Backend {
boost::replace_all(message, username, "user"); boost::replace_all(message, username, "user");
} }
#endif #endif
bytes_written += file->WriteString(message); bytes_written += file->WriteSpan(std::span<const char>{message.begin(), message.end()});
// Option to log each line rather than 4k buffers // Option to log each line rather than 4k buffers
if (Settings::values.log_flush_line.GetValue()) if (Settings::values.log_flush_line.GetValue())
@@ -308,14 +300,13 @@ private:
bool enabled = true; bool enabled = true;
}; };
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
/// @brief Backend that writes to Visual Studio's output window /// @brief Backend that writes to Visual Studio's output window
struct DebuggerBackend final : public Backend { struct DebuggerBackend final : public Backend {
explicit DebuggerBackend() noexcept = default; explicit DebuggerBackend() noexcept = default;
~DebuggerBackend() noexcept override = default; ~DebuggerBackend() noexcept override = default;
void Write(const Entry& entry) noexcept override { void Write(const Entry& entry) noexcept override {
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry)).c_str());
} }
void Flush() noexcept override {} void Flush() noexcept override {}
}; };
@@ -338,7 +329,7 @@ struct LogcatBackend : public Backend {
} }
}(); }();
auto const df = GetDirectFormatArgs(entry); auto const df = GetDirectFormatArgs(entry);
__android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str()); __android_log_print(android_log_priority, "YuzuNative", "%s %s:%u:%s: %s", df.class_name, entry.filename, entry.line_num, entry.function, entry.message);
} }
void Flush() noexcept override {} void Flush() noexcept override {}
}; };
@@ -377,7 +368,23 @@ struct Impl {
#endif #endif
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
}; };
} // namespace } // Anonymous namespace
void Filter::ParseFilterString(std::string_view filter_view) {
auto clause_begin = filter_view.cbegin();
while (clause_begin < filter_view.cend()) {
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
// If clause isn't empty
if (clause_end != clause_begin) {
ParseFilterRule(*this, clause_begin, clause_end);
}
if (clause_end != filter_view.cend()) {
// Skip over the whitespace
++clause_end;
}
clause_begin = clause_end;
}
}
// Constructor shall NOT depend upon Settings() or whatever // Constructor shall NOT depend upon Settings() or whatever
// it's ran at global static ctor() time... so BE CAREFUL MFER! // it's ran at global static ctor() time... so BE CAREFUL MFER!
@@ -419,19 +426,35 @@ void SetColorConsoleBackendEnabled(bool enabled) {
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) { void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) { if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) {
auto const flush = ::Settings::values.log_flush_line.GetValue(); auto const flush = ::Settings::values.log_flush_line.GetValue();
logging_instance->ForEachBackend([=](Backend& backend) { char buffer[BUFSIZ];
backend.Write(Entry{ auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args);
.message = fmt::vformat(format, args), Entry e{
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin), .message = nullptr,
.log_class = log_class, .message_len = 0,
.log_level = log_level, .timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
.filename = TrimSourcePath(filename), .log_class = log_class,
.function = function, .log_level = log_level,
.line_num = line_num, .filename = TrimSourcePath(filename),
.function = function,
.line_num = line_num,
};
if (result.size <= sizeof(buffer) - 1) {
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0';
e.message = buffer;
e.message_len = (std::min)(result.size, sizeof(buffer) - 1);
logging_instance->ForEachBackend([=](Backend& backend) {
backend.Write(e);
if (flush) backend.Flush();
}); });
if (flush) } else {
backend.Flush(); std::string s = fmt::vformat(format, args);
}); e.message = s.c_str();
e.message_len = s.size();
logging_instance->ForEachBackend([=](Backend& backend) {
backend.Write(e);
if (flush) backend.Flush();
});
}
} }
} }
} // namespace Common::Log } // namespace Common::Log
-21
View File
@@ -140,25 +140,4 @@ void Stop();
void SetGlobalFilter(const Filter& filter); void SetGlobalFilter(const Filter& filter);
void SetColorConsoleBackendEnabled(bool enabled); void SetColorConsoleBackendEnabled(bool enabled);
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
/// formatting on different frontends, as well as facilitating filtering and aggregation.
struct Entry {
std::string message;
std::chrono::microseconds timestamp;
Class log_class{};
Level log_level{};
const char* filename = nullptr;
const char* function = nullptr;
unsigned int line_num = 0;
};
/// Formats a log entry into the provided text buffer.
std::string FormatLogMessage(const Entry& entry) noexcept;
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
void PrintColoredMessage(const Entry& entry) noexcept;
/// Formats and prints a log entry to the android logcat.
void PrintMessageToLogcat(const Entry& entry) noexcept;
} // namespace Common::Log } // namespace Common::Log
+1 -1
View File
@@ -241,7 +241,7 @@ std::optional<std::string> MakeRequest(const std::string& url, const std::string
response.status); response.status);
return {}; return {};
} }
if (!response.headers.contains("content-type")) { if (!response.has_header("content-type")) {
LOG_ERROR(Common, "GET to {}{} returned no content", url, path); LOG_ERROR(Common, "GET to {}{} returned no content", url, path);
return {}; return {};
} }
+4 -4
View File
@@ -11,14 +11,14 @@
namespace Common::Net { namespace Common::Net {
typedef struct { struct Asset {
std::string name; std::string name;
std::string url; std::string url;
std::string path; std::string path;
std::string filename; std::string filename;
} Asset; };
typedef struct Release { struct Release {
std::string title; std::string title;
std::string body; std::string body;
std::string tag; std::string tag;
@@ -39,7 +39,7 @@ typedef struct Release {
static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo); static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo);
static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo); static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo);
static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo); static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo);
} Release; };
// Make a request via httplib, and return the response body if applicable. // Make a request via httplib, and return the response body if applicable.
std::optional<std::string> MakeRequest(const std::string &url, const std::string &path); std::optional<std::string> MakeRequest(const std::string &url, const std::string &path);
+2 -2
View File
@@ -8,14 +8,14 @@
#include <initializer_list> #include <initializer_list>
#include <string> #include <string>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
namespace Common { namespace Common {
/// A string-based key-value container supporting serializing to and deserializing from a string /// A string-based key-value container supporting serializing to and deserializing from a string
class ParamPackage { class ParamPackage {
public: public:
using DataType = ankerl::unordered_dense::map<std::string, std::string>; using DataType = ::Common::unordered_map<std::string, std::string>;
ParamPackage() = default; ParamPackage() = default;
explicit ParamPackage(const std::string& serialized); explicit ParamPackage(const std::string& serialized);
+5 -7
View File
@@ -126,17 +126,15 @@ void LogSettings() {
setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category), setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category),
setting->GetLabel()); setting->GetLabel());
if (is_default) if (is_default)
settings_list.push_back(fmt::format("{}: {}\n", name, setting->Canonicalize())); settings_list.push_back(fmt::format("{}: {}", name, setting->Canonicalize()));
else else
settings_list.push_front(fmt::format("{}: {}\n", name, setting->Canonicalize())); settings_list.push_front(fmt::format("{}: {}", name, setting->Canonicalize()));
} }
} }
} }
LOG_INFO(Config, "Eden Configuration:");
std::string settings_str{};
for (auto const& e : settings_list) for (auto const& e : settings_list)
settings_str += e; LOG_INFO(Config, "{}", e);
LOG_INFO(Config, "Eden Configuration:\n{}", settings_str);
#define LOG_PATH(NAME) \ #define LOG_PATH(NAME) \
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME))) LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
LOG_PATH(CacheDir); LOG_PATH(CacheDir);
@@ -148,7 +146,7 @@ void LogSettings() {
#undef LOG_PATH #undef LOG_PATH
} }
bool getDebugKnobAt(u8 i) { bool GetDebugKnobAt(u8 i) {
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0; return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
} }
+2 -2
View File
@@ -904,7 +904,7 @@ struct Values {
0, 0,
65535, 65535,
"debug_knobs", "debug_knobs",
Category::Debugging, Category::System,
Specialization::Countable, Specialization::Countable,
true, true,
true}; true};
@@ -947,7 +947,7 @@ constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
[[nodiscard]] size_t FrameGenMaxGenerations(); [[nodiscard]] size_t FrameGenMaxGenerations();
bool getDebugKnobAt(u8 i); bool GetDebugKnobAt(u8 i);
void UpdateGPUAccuracy(); void UpdateGPUAccuracy();
bool IsGPULevelHigh(); bool IsGPULevelHigh();
+1 -1
View File
@@ -128,7 +128,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt
GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica, GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt, Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu); Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None); ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16);
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous); ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3); ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120); ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
-2
View File
@@ -7,6 +7,4 @@
#define STB_IMAGE_IMPLEMENTATION 1 #define STB_IMAGE_IMPLEMENTATION 1
#define STB_IMAGE_RESIZE_IMPLEMENTATION 1 #define STB_IMAGE_RESIZE_IMPLEMENTATION 1
#define STB_IMAGE_WRITE_IMPLEMENTATION 1 #define STB_IMAGE_WRITE_IMPLEMENTATION 1
#define STBI_ONLY_JPEG 1
#include "common/stb.h" #include "common/stb.h"
+1
View File
@@ -7,6 +7,7 @@
#pragma once #pragma once
#define STBI_ONLY_JPEG 1 #define STBI_ONLY_JPEG 1
#define STBI_WRITE_NO_STDIO 1
#include <stb_image.h> #include <stb_image.h>
#include <stb_image_resize.h> #include <stb_image_resize.h>
#include <stb_image_write.h> #include <stb_image_write.h>
+4 -3
View File
@@ -22,10 +22,11 @@
#endif #endif
// You must ensure this matches with src/common/x64/xbyak.h on root dir // You must ensure this matches with src/common/x64/xbyak.h on root dir
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include "common/container/unordered_set.h"
#include <boost/unordered_map.hpp> #include <boost/unordered_map.hpp>
#define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set #define XBYAK_STD_UNORDERED_SET ::Common::unordered_set
#define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map #define XBYAK_STD_UNORDERED_MAP ::Common::unordered_map
#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap #define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap
#include <xbyak/xbyak.h> #include <xbyak/xbyak.h>
#include <xbyak/xbyak_util.h> #include <xbyak/xbyak_util.h>
@@ -286,6 +286,7 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
break; break;
// Paranoia mode for debugging optimizations // Paranoia mode for debugging optimizations
case Settings::CpuAccuracy::Paranoid: case Settings::CpuAccuracy::Paranoid:
@@ -338,6 +338,7 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
config.unsafe_optimizations = true; config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.fastmem_address_space_bits = 64; config.fastmem_address_space_bits = 64;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
break; break;
// Paranoia mode for debugging optimizations // Paranoia mode for debugging optimizations
case Settings::CpuAccuracy::Paranoid: case Settings::CpuAccuracy::Paranoid:
+1 -1
View File
@@ -8,7 +8,7 @@
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include <dynarmic/interface/A64/a64.h> #include <dynarmic/interface/A64/a64.h>
#include <dynarmic/interface/code_page.h> #include <dynarmic/interface/code_page.h>
+2 -2
View File
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <span> #include <span>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include <vector> #include <vector>
#include <oaknut/code_block.hpp> #include <oaknut/code_block.hpp>
#include <oaknut/oaknut.hpp> #include <oaknut/oaknut.hpp>
@@ -46,7 +46,7 @@ enum class PatchMode : u32 {
using ModuleTextAddress = u64; using ModuleTextAddress = u64;
using PatchTextAddress = u64; using PatchTextAddress = u64;
using EntryTrampolines = ankerl::unordered_dense::map<ModuleTextAddress, PatchTextAddress>; using EntryTrampolines = ::Common::unordered_map<ModuleTextAddress, PatchTextAddress>;
class Patcher { class Patcher {
public: public:
+77 -8
View File
@@ -4,6 +4,7 @@
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <unordered_map>
#include <utility> #include <utility>
#include "game_settings.h" #include "game_settings.h"
@@ -248,12 +249,30 @@ struct System::Impl {
} }
} }
void SetNVDECActive(bool is_nvdec_active) { void NotifyNVDECChannelOpen(u64 process_id) {
nvdec_active = is_nvdec_active; std::scoped_lock lock{nvdec_active_mutex};
++nvdec_active_channels[process_id];
}
void NotifyNVDECChannelClose(u64 process_id) {
std::scoped_lock lock{nvdec_active_mutex};
const auto it = nvdec_active_channels.find(process_id);
if (it == nvdec_active_channels.end()) {
return;
}
if (--it->second == 0) {
nvdec_active_channels.erase(it);
}
} }
bool GetNVDECActive() { bool GetNVDECActive() {
return nvdec_active; std::scoped_lock lock{nvdec_active_mutex};
return !nvdec_active_channels.empty();
}
bool IsNVDECActiveForProcess(u64 process_id) {
std::scoped_lock lock{nvdec_active_mutex};
return nvdec_active_channels.contains(process_id);
} }
void InitializeDebugger(System& system, u16 port) { void InitializeDebugger(System& system, u16 port) {
@@ -295,6 +314,10 @@ struct System::Impl {
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) { SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) {
InitializeKernel(system); InitializeKernel(system);
if (params.applet_type == Service::AM::AppletType::Application) {
current_application_filepath = filepath;
}
const auto file = GetGameFileFromPath(virtual_filesystem, filepath); const auto file = GetGameFileFromPath(virtual_filesystem, filepath);
// Create the application process // Create the application process
@@ -330,7 +353,7 @@ struct System::Impl {
LaunchTimestampCache::SaveLaunchTimestamp(params.program_id); LaunchTimestampCache::SaveLaunchTimestamp(params.program_id);
// Make the process created be the application // Make the process created be the application
kernel.MakeApplicationProcess(process->GetHandle()); kernel.SetApplicationProcess(process->GetHandle());
// Set up the rest of the system. // Set up the rest of the system.
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)}; SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
@@ -467,6 +490,7 @@ struct System::Impl {
Core::SpeedLimiter speed_limiter; Core::SpeedLimiter speed_limiter;
ExecuteProgramCallback execute_program_callback; ExecuteProgramCallback execute_program_callback;
ExitCallback exit_callback; ExitCallback exit_callback;
ApplicationChangedCallback application_changed_callback;
std::optional<Service::Services> services; std::optional<Service::Services> services;
std::optional<Core::Debugger> debugger; std::optional<Core::Debugger> debugger;
@@ -488,6 +512,8 @@ struct System::Impl {
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{}; std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<u8, 0x20> build_id{}; std::array<u8, 0x20> build_id{};
std::string current_application_filepath;
/// Service manager /// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager; std::shared_ptr<Service::SM::ServiceManager> service_manager;
/// ContentProviderUnion instance /// ContentProviderUnion instance
@@ -498,6 +524,8 @@ struct System::Impl {
mutable std::mutex suspend_guard; mutable std::mutex suspend_guard;
std::mutex general_channel_mutex; std::mutex general_channel_mutex;
std::mutex nvdec_active_mutex;
std::unordered_map<u64, u32> nvdec_active_channels;
std::atomic_bool is_paused{}; std::atomic_bool is_paused{};
std::atomic_bool is_shutting_down{}; std::atomic_bool is_shutting_down{};
std::atomic_bool is_powered_on{}; std::atomic_bool is_powered_on{};
@@ -505,7 +533,6 @@ struct System::Impl {
bool extended_memory_layout : 1 = false; bool extended_memory_layout : 1 = false;
bool exit_locked : 1 = false; bool exit_locked : 1 = false;
bool exit_requested : 1 = false; bool exit_requested : 1 = false;
bool nvdec_active : 1 = false;
void EnsureGeneralChannelInitialized(System& system) { void EnsureGeneralChannelInitialized(System& system) {
if (!general_channel_event) { if (!general_channel_event) {
@@ -569,14 +596,22 @@ void System::UnstallApplication() {
impl->UnstallApplication(); impl->UnstallApplication();
} }
void System::SetNVDECActive(bool is_nvdec_active) { void System::NotifyNVDECChannelOpen(u64 process_id) {
impl->SetNVDECActive(is_nvdec_active); impl->NotifyNVDECChannelOpen(process_id);
}
void System::NotifyNVDECChannelClose(u64 process_id) {
impl->NotifyNVDECChannelClose(process_id);
} }
bool System::GetNVDECActive() { bool System::GetNVDECActive() {
return impl->GetNVDECActive(); return impl->GetNVDECActive();
} }
bool System::IsNVDECActiveForProcess(u64 process_id) {
return impl->IsNVDECActiveForProcess(process_id);
}
void System::InitializeDebugger() { void System::InitializeDebugger() {
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue()); impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
} }
@@ -727,7 +762,25 @@ const Core::SpeedLimiter& System::SpeedLimiter() const {
} }
u64 System::GetApplicationProcessProgramID() const { u64 System::GetApplicationProcessProgramID() const {
return impl->kernel.ApplicationProcess()->GetProgramId(); const auto* const process = impl->kernel.ApplicationProcess();
return process != nullptr ? process->GetProgramId() : 0;
}
u64 System::GetProgramIdForProcessId(u64 process_id) const {
auto process = impl->kernel.GetProcessByProcessId(process_id);
return process.IsNull() ? 0 : process->GetProgramId();
}
u64 System::ResolveCallerProgramId(u64 process_id) const {
if (const auto program_id = this->GetProgramIdForProcessId(process_id); program_id != 0) {
return program_id;
}
const auto fallback = this->GetApplicationProcessProgramID();
LOG_WARNING(Core,
"Could not resolve caller process_id={}, falling back to application {:016X}",
process_id, fallback);
return fallback;
} }
Loader::ResultStatus System::GetGameName(std::string& out) const { Loader::ResultStatus System::GetGameName(std::string& out) const {
@@ -910,6 +963,10 @@ void System::ExecuteProgram(std::size_t program_index) {
} }
} }
const std::string& System::GetCurrentApplicationFilePath() const {
return impl->current_application_filepath;
}
/// @brief Gets a reference to the user channel stack. /// @brief Gets a reference to the user channel stack.
/// It is used to transfer data between programs. /// It is used to transfer data between programs.
std::vector<std::vector<u8>>& System::GetUserChannel() { std::vector<std::vector<u8>>& System::GetUserChannel() {
@@ -961,6 +1018,18 @@ void System::Exit() {
} }
} }
void System::RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback) {
impl->application_changed_callback = std::move(callback);
}
void System::NotifyApplicationChanged(u64 program_id) {
//LOG_DEBUG(Core, "Running application changed to {:016X}", program_id);
if (impl->application_changed_callback) {
impl->application_changed_callback(program_id);
}
}
void System::ApplySettings() { void System::ApplySettings() {
impl->RefreshTime(*this); impl->RefreshTime(*this);
+12 -1
View File
@@ -191,8 +191,10 @@ public:
std::unique_lock<std::mutex> StallApplication(); std::unique_lock<std::mutex> StallApplication();
void UnstallApplication(); void UnstallApplication();
void SetNVDECActive(bool is_nvdec_active); void NotifyNVDECChannelOpen(u64 process_id);
void NotifyNVDECChannelClose(u64 process_id);
[[nodiscard]] bool GetNVDECActive(); [[nodiscard]] bool GetNVDECActive();
[[nodiscard]] bool IsNVDECActiveForProcess(u64 process_id);
/** /**
* Initialize the debugger. * Initialize the debugger.
@@ -322,6 +324,10 @@ public:
[[nodiscard]] u64 GetApplicationProcessProgramID() const; [[nodiscard]] u64 GetApplicationProcessProgramID() const;
[[nodiscard]] u64 GetProgramIdForProcessId(u64 process_id) const;
[[nodiscard]] u64 ResolveCallerProgramId(u64 process_id) const;
/// Gets the name of the current game /// Gets the name of the current game
[[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const; [[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const;
@@ -422,6 +428,7 @@ public:
void PushGeneralChannelData(std::vector<u8>&& data); void PushGeneralChannelData(std::vector<u8>&& data);
bool TryPopGeneralChannel(std::vector<u8>& out_data); bool TryPopGeneralChannel(std::vector<u8>& out_data);
[[nodiscard]] Service::Event& GetGeneralChannelEvent(); [[nodiscard]] Service::Event& GetGeneralChannelEvent();
[[nodiscard]] const std::string& GetCurrentApplicationFilePath() const;
/// Type used for the frontend to designate a callback for System to exit the application. /// Type used for the frontend to designate a callback for System to exit the application.
using ExitCallback = std::function<void()>; using ExitCallback = std::function<void()>;
@@ -435,6 +442,10 @@ public:
/// Instructs the frontend to exit the application. /// Instructs the frontend to exit the application.
void Exit(); void Exit();
using ApplicationChangedCallback = std::function<void(u64 program_id)>;
void RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback);
void NotifyApplicationChanged(u64 program_id);
/// Applies any changes to settings to this core instance. /// Applies any changes to settings to this core instance.
void ApplySettings(); void ApplySettings();
+2 -2
View File
@@ -29,7 +29,7 @@ static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
return std::max<u8>(key_generation, 1) - 1; return std::max<u8>(key_generation, 1) - 1;
} }
NCA::NCA(VirtualFile file_, const NCA* base_nca) NCA::NCA(VirtualFile file_, const NCA* base_nca, bool allow_missing_base)
: file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) { if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile; status = Loader::ResultStatus::ErrorNullFile;
@@ -110,7 +110,7 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
} }
} }
if (is_update && base_nca == nullptr) { if (is_update && base_nca == nullptr && !allow_missing_base) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
} else { } else {
status = Loader::ResultStatus::Success; status = Loader::ResultStatus::Success;
+4 -1
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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -63,7 +66,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
// After construction, use GetStatus to determine if the file is valid and ready to be used. // After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory { class NCA : public ReadOnlyVfsDirectory {
public: public:
explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); explicit NCA(VirtualFile file, const NCA* base_nca = nullptr, bool allow_missing_base = false);
~NCA() override; ~NCA() override;
Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetStatus() const;
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging.h"
#include "core/file_sys/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_indirect_storage.h" #include "core/file_sys/fssystem/fssystem_indirect_storage.h"
@@ -99,6 +100,18 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s
R_SUCCEED(); R_SUCCEED();
} }
void IndirectStorage::ReportMissingOriginal(s64 offset, s64 size) {
if (m_reported_missing_original) {
return;
}
m_reported_missing_original = true;
LOG_ERROR(Common_Filesystem,
"Patch storage requests {:#x} bytes at {:#x} from a base storage that is not "
"present; this data will read as garbage",
size, offset);
}
size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const { size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Validate pre-conditions. // Validate pre-conditions.
ASSERT(this->IsInitialized()); ASSERT(this->IsInitialized());
@@ -85,6 +85,9 @@ public:
void SetStorage(s32 idx, VirtualFile storage) { void SetStorage(s32 idx, VirtualFile storage) {
ASSERT(0 <= idx && idx < StorageCount); ASSERT(0 <= idx && idx < StorageCount);
m_data_storage[idx] = storage; m_data_storage[idx] = storage;
if (idx == 0) {
m_original_missing = storage == nullptr || storage->GetSize() == 0;
}
} }
template <typename T> template <typename T>
@@ -130,6 +133,9 @@ protected:
template <bool ContinuousCheck, bool RangeCheck, typename F> template <bool ContinuousCheck, bool RangeCheck, typename F>
Result OperatePerEntry(s64 offset, s64 size, F func); Result OperatePerEntry(s64 offset, s64 size, F func);
// Launching another game makes the original storage inaccessable.
// This is a helper for multi-nca games.
void ReportMissingOriginal(s64 offset, s64 size);
private: private:
struct ContinuousReadingEntry { struct ContinuousReadingEntry {
@@ -154,6 +160,8 @@ private:
private: private:
mutable BucketTree m_table; mutable BucketTree m_table;
std::array<VirtualFile, StorageCount> m_data_storage; std::array<VirtualFile, StorageCount> m_data_storage;
bool m_original_missing{false};
bool m_reported_missing_original{false};
}; };
template <bool ContinuousCheck, bool RangeCheck, typename F> template <bool ContinuousCheck, bool RangeCheck, typename F>
@@ -272,6 +280,10 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
if (needs_operate) { if (needs_operate) {
const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
if (cur_entry.storage_index == 0 && m_original_missing) {
this->ReportMissingOriginal(cur_offset, cur_size);
}
if constexpr (RangeCheck) { if constexpr (RangeCheck) {
// Get the current data storage's size. // Get the current data storage's size.
s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize(); s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
+189 -236
View File
@@ -3,10 +3,12 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <map>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <utility> #include <utility>
#include <span>
#include <cctype>
#include "common/container/unordered_map.h"
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging.h" #include "common/logging.h"
@@ -22,61 +24,30 @@ enum class IPSFileType {
Error, Error,
}; };
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{ static IPSFileType IdentifyMagic(std::span<const u8> magic) {
{"\\a", "\a"}, if (magic.size() >= 5) {
{"\\b", "\b"}, if (std::memcmp(magic.data(), "PATCH", 5) == 0)
{"\\f", "\f"}, return IPSFileType::IPS;
{"\\n", "\n"}, if (std::memcmp(magic.data(), "IPS32", 5) == 0)
{"\\r", "\r"}, return IPSFileType::IPS32;
{"\\t", "\t"},
{"\\v", "\v"},
{"\\\\", "\\"},
{"\\\'", "\'"},
{"\\\"", "\""},
{"\\\?", "\?"},
}};
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
if (magic.size() != 5) {
return IPSFileType::Error;
} }
static constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
return IPSFileType::IPS;
}
static constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
return IPSFileType::IPS32;
}
return IPSFileType::Error; return IPSFileType::Error;
} }
static bool IsEOF(IPSFileType type, const std::vector<u8>& data) { static bool IsEOF(IPSFileType type, std::span<const u8> magic) {
static constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}}; return (type == IPSFileType::IPS && magic.size() > 3 && std::memcmp(magic.data(), "EOF", 3) == 0)
if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) { || (type == IPSFileType::IPS32 && magic.size() > 4 && std::memcmp(magic.data(), "EEOF", 4) == 0);
return true;
}
static constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
} }
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
if (in == nullptr || ips == nullptr) if (in == nullptr || ips == nullptr)
return nullptr; return nullptr;
const auto type = IdentifyMagic(ips->ReadBytes(0x5)); auto in_data = in->ReadAllBytes();
auto const type = IdentifyMagic(in_data);
if (type == IPSFileType::Error) if (type == IPSFileType::Error)
return nullptr; return nullptr;
auto in_data = in->ReadAllBytes();
if (in_data.size() == 0) {
return nullptr;
}
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4); std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
u64 offset = 5; // After header u64 offset = 5; // After header
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) { while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
@@ -85,12 +56,9 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
break; break;
} }
u32 real_offset{}; u32 real_offset = (type == IPSFileType::IPS32)
if (type == IPSFileType::IPS32) ? ((temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3])
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3]; : ((temp[0] << 16) | (temp[1] << 8) | temp[2]);
else
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
if (real_offset > in_data.size()) { if (real_offset > in_data.size()) {
return nullptr; return nullptr;
} }
@@ -113,34 +81,35 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
return nullptr; return nullptr;
if (real_offset + rle_size > in_data.size()) if (real_offset + rle_size > in_data.size())
rle_size = static_cast<u16>(in_data.size() - real_offset); rle_size = u16(in_data.size() - real_offset);
std::memset(in_data.data() + real_offset, *data, rle_size); std::memset(in_data.data() + real_offset, *data, rle_size);
} else { // Standard Patch } else { // Standard Patch
auto read = data_size; auto read = data_size;
if (real_offset + read > in_data.size()) if (real_offset + read > in_data.size())
read = static_cast<u16>(in_data.size() - real_offset); read = u16(in_data.size() - real_offset);
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size) if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
return nullptr; return nullptr;
offset += data_size; offset += data_size;
} }
} }
if (IsEOF(type, temp)) {
if (!IsEOF(type, temp)) { return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
return nullptr;
} }
return nullptr;
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
} }
struct IPSwitchRecord {
std::array<uint8_t, 256 - sizeof(size_t)> data;
size_t count;
};
struct IPSwitchCompiler::IPSwitchPatch { struct IPSwitchCompiler::IPSwitchPatch {
std::string name; ::Common::unordered_map<u32, IPSwitchRecord> records;
bool enabled; bool enabled;
std::map<u32, std::vector<u8>> records;
}; };
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
Parse(); Parse(patch_text->ReadAllBytes());
} }
IPSwitchCompiler::~IPSwitchCompiler() = default; IPSwitchCompiler::~IPSwitchCompiler() = default;
@@ -149,201 +118,185 @@ std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
return nso_build_id; return nso_build_id;
} }
bool IPSwitchCompiler::IsValid() const { static IPSwitchRecord EscapeStringSequences(std::string_view sv) {
return valid; IPSwitchRecord r{};
} for (auto it = sv.cbegin(); it != sv.cend(); ) {
if (*it == '\\' && it + 1 < sv.cend()) {
static bool StartsWith(std::string_view base, std::string_view check) { switch (it[1]) {
return base.size() >= check.size() && base.substr(0, check.size()) == check; case 'a': r.data[r.count] = '\a'; break;
} case 'b': r.data[r.count] = '\b'; break;
case 'e': r.data[r.count] = '\e'; break;
static std::string EscapeStringSequences(std::string in) { case 'f': r.data[r.count] = '\f'; break;
for (const auto& seq : ESCAPE_CHARACTER_MAP) { case 'n': r.data[r.count] = '\n'; break;
for (auto index = in.find(seq.first); index != std::string::npos; case 'r': r.data[r.count] = '\r'; break;
index = in.find(seq.first, index)) { case 't': r.data[r.count] = '\t'; break;
in.replace(index, std::strlen(seq.first), seq.second); case 'v': r.data[r.count] = '\v'; break;
index += std::strlen(seq.second); case '?': r.data[r.count] = '\?'; break;
default: r.data[r.count] = it[1]; break;
}
++r.count;
it += 2;
} else {
++r.count;
++it;
} }
} }
return r;
return in;
} }
void IPSwitchCompiler::ParseFlag(const std::string& line) { void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
if (StartsWith(line, "@flag offset_shift ")) { LOG_INFO(Loader, "IPSwitchCompiler: '{}'", patch_text->GetName());
// Offset Shift Flag bool is_little_endian = true;
offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0); s64 offset_shift = 0;
} else if (StartsWith(line, "@little-endian")) { //bool print_values = false;
// Set values to read as little endian auto const parse_line = [&](std::string_view const line) {
is_little_endian = true; // Keep in mind lines have trimmed spaces (at the end & start)!
} else if (StartsWith(line, "@big-endian")) { LOG_INFO(Loader, "<{}>", line);
// Set values to read as big endian // IPSwitch is case insensitive
is_little_endian = false; // Yes this is how the logic goes for the main reference parsers!
} else if (StartsWith(line, "@flag print_values")) { if (line.size() > 2 && line[0] == '@') {
// Force printing of applied values switch (line[1]) {
print_values = true; // yes, @nsobid too -- NSO Build ID Specifier
} case 'n':
} case 'N':
nso_build_id = Common::HexStringToArray<0x20>(fmt::format("{:0<64}", line.substr(8)));
break;
// @stop
case 's':
case 'S':
return false;
// @enabled
case 'e':
case 'E':
patches.push_back({{}, true});
break;
// @disabled
case 'd':
case 'D':
patches.push_back({{}, false});
break;
// @flag
case 'f':
case 'F': {
if (line.starts_with("@flag offset_shift")) {
offset_shift = std::strtoll(line.data() + 19, nullptr, 0); // Offset Shift Flag
} else if (line.starts_with("@flag print_values")) {
//print_values = true; // Force printing of applied values
}
break;
}
case 'l':
case 'L':
is_little_endian = true;
break;
// IPS parsers dont support big endian no more, we do due to backcompat
case 'b':
case 'B':
is_little_endian = false;
break;
default:
LOG_WARNING(Loader, "Unknown flag {}", line);
break;
}
} else {
size_t offset = size_t(std::strtoul(line.data(), nullptr, 16));
offset += size_t(offset_shift);
if (auto const first_quote = line.find_first_of("\"\'"); first_quote != std::string::npos) {
// string replacement
char quote = line[first_quote];
auto const start = line.cbegin() + first_quote + 1;
auto end = start;
for (; end < line.cend() && *end != quote; )
end += (*end == '\\') ? 2 : 1;
if (start <= line.cend() && end <= line.cend()) {
LOG_INFO(Loader, "[S] value @ {:#08X} ", offset);
patches.back().records.insert_or_assign(u32(offset), EscapeStringSequences({start, end}));
} else {
LOG_WARNING(Loader, "invalid string");
}
} else if (auto const first_space = line.find_last_of(" /\t\r\n"); first_space != std::string::npos) {
IPSwitchRecord r{}; // hex replacement
auto const start = line.cbegin() + first_space + 1;
auto const end = line.cend();
if (start <= line.cend() && end <= line.cend()) {
// Actually IPS wants ordering from {lsb, ..., msb} -- so LE and BE are inverted, fun!
auto const hs = Common::HexStringToVector({start, end}, is_little_endian);
std::memcpy(r.data.data(), hs.data(), hs.size());
r.count = hs.size();
LOG_INFO(Loader, "[H] value @ {:#08X}", offset);
patches.back().records.insert_or_assign(u32(offset), std::move(r));
} else {
LOG_WARNING(Loader, "invalid line");
}
} else {
LOG_WARNING(Loader, "unhandled line!");
}
}
return true; //continue
};
void IPSwitchCompiler::Parse() { for (auto it = bytes.begin(); it < bytes.end(); ) {
const auto bytes = patch_text->ReadAllBytes(); auto const start = it;
std::stringstream s; auto end = start;
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size()); for (; end < bytes.end() && *end != '\n' && *end != '\r'; ++end)
;
std::vector<std::string> lines; it = end + 1; //prepare for next line
std::string stream_line; std::string_view const sline{
while (std::getline(s, stream_line)) { reinterpret_cast<const char*>(bytes.data() + std::distance(bytes.begin(), start)),
// Remove a trailing \r size_t(std::distance(start, end))
if (!stream_line.empty() && stream_line.back() == '\r') };
stream_line.pop_back(); if (sline.size() > 0) {
lines.push_back(std::move(stream_line)); auto p = sline.cbegin();
} // skip space off line
for (; p < sline.cend() && std::isspace(*p); ++p)
for (std::size_t i = 0; i < lines.size(); ++i) { ;
auto line = lines[i]; // now make a nominal preprocessed line: remove comments
char quote = '\0';
// Remove midline comments auto const sline_start = p;
std::size_t comment_index = std::string::npos; for (; p < sline.cend(); ) {
bool within_string = false; // we dont check for "//", IPS checks for '/' only...
for (std::size_t k = 0; k < line.size(); ++k) { if ((!quote && p[0] == '/')
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) { || (!quote && p[0] == '#')) {
within_string = !within_string; break;
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) { } else if (p[0] == '\"' || p[0] == '\'') {
comment_index = k; quote = (p[0] == quote) ? '\0' : p[0];
++p;
} else if (p + 1 < sline.cend() && p[0] == '\\') {
p += 2;
} else {
++p;
}
}
// now we have the preprocessed string ;)
std::string_view pp_str(sline_start, p);
if (pp_str.size() > 0 && !parse_line(pp_str)) {
break; break;
} }
} }
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
last_comment = line.substr(comment_index + 2);
line = line.substr(0, comment_index);
}
if (StartsWith(line, "@stop")) {
// Force stop
break;
} else if (StartsWith(line, "@nsobid-")) {
// NSO Build ID Specifier
const auto raw_build_id = fmt::format("{:0<64}", line.substr(8));
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
} else if (StartsWith(line, "#")) {
// Mandatory Comment
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
patch_text->GetName(), line.substr(1));
} else if (StartsWith(line, "//")) {
// Normal Comment
last_comment = line.substr(2);
if (last_comment.find_first_not_of(' ') == std::string::npos)
continue;
if (last_comment.find_first_not_of(' ') != 0)
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
// Start of patch
const auto enabled = StartsWith(line, "@enabled");
if (i == 0)
return;
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
patch_text->GetName(), last_comment, line.substr(1));
IPSwitchPatch patch{last_comment, enabled, {}};
// Read rest of patch
while (true) {
if (i + 1 >= lines.size()) {
break;
}
const auto& patch_line = lines[++i];
// Patch line may contain comments
if (StartsWith(patch_line, "//") || StartsWith(patch_line, "#")) {
continue;
}
// Start of new patch
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
--i;
break;
}
// Check for a flag
if (StartsWith(patch_line, "@")) {
ParseFlag(patch_line);
continue;
}
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
if (patch_line.length() < 11)
break;
auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
offset += static_cast<unsigned long>(offset_shift);
std::vector<u8> replace;
// 9 - first char of replacement val
if (patch_line[9] == '\"') {
// string replacement
auto end_index = patch_line.find('\"', 10);
if (end_index == std::string::npos || end_index < 10)
return;
while (patch_line[end_index - 1] == '\\') {
end_index = patch_line.find('\"', end_index + 1);
if (end_index == std::string::npos || end_index < 10)
return;
}
auto value = patch_line.substr(10, end_index - 10);
value = EscapeStringSequences(value);
replace.reserve(value.size());
std::copy(value.begin(), value.end(), std::back_inserter(replace));
} else {
// hex replacement
const auto value =
patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9);
replace = Common::HexStringToVector(value, is_little_endian);
}
if (print_values) {
LOG_INFO(Loader,
"[IPSwitchCompiler ('{}')] - Patching value at offset {:#08x} "
"with byte string '{}'",
patch_text->GetName(), offset, Common::HexToString(replace));
}
patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace));
}
patches.push_back(std::move(patch));
} else if (StartsWith(line, "@")) {
ParseFlag(line);
}
} }
valid = true;
} }
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const { VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
if (in == nullptr || !valid) if (in == nullptr)
return nullptr; return nullptr;
auto in_data = in->ReadAllBytes(); auto in_data = in->ReadAllBytes();
for (const auto& patch : patches) { for (const auto& patch : patches) {
if (!patch.enabled) if (patch.enabled) {
continue; for (const auto& record : patch.records) {
if (record.first < in_data.size()) {
for (const auto& record : patch.records) { auto replace_size = record.second.count;
if (record.first >= in_data.size()) if (record.first + replace_size > in_data.size())
continue; replace_size = in_data.size() - record.first;
auto replace_size = record.second.size(); std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size);
if (record.first + replace_size > in_data.size()) } else {
replace_size = in_data.size() - record.first; LOG_WARNING(Loader, "record offs={:x},size={:x}", record.first, record.second.data.size());
for (std::size_t i = 0; i < replace_size; ++i) }
in_data[i + record.first] = record.second[i]; }
} }
} }
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
} }
} // namespace FileSys } // namespace FileSys
+5 -9
View File
@@ -1,11 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <array> #include <array>
#include <memory>
#include <vector> #include <vector>
#include <span>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/file_sys/vfs/vfs.h" #include "core/file_sys/vfs/vfs.h"
@@ -20,24 +23,17 @@ public:
~IPSwitchCompiler(); ~IPSwitchCompiler();
std::array<u8, 0x20> GetBuildID() const; std::array<u8, 0x20> GetBuildID() const;
bool IsValid() const;
VirtualFile Apply(const VirtualFile& in) const; VirtualFile Apply(const VirtualFile& in) const;
private: private:
struct IPSwitchPatch; struct IPSwitchPatch;
void ParseFlag(const std::string& flag); void ParseFlag(const std::string& flag);
void Parse(); void Parse(std::span<u8 const> bytes);
bool valid = false;
VirtualFile patch_text; VirtualFile patch_text;
std::vector<IPSwitchPatch> patches; std::vector<IPSwitchPatch> patches;
std::array<u8, 0x20> nso_build_id{}; std::array<u8, 0x20> nso_build_id{};
bool is_little_endian = false;
s64 offset_shift = 0;
bool print_values = false;
std::string last_comment = "";
}; };
} // namespace FileSys } // namespace FileSys
+2 -2
View File
@@ -48,11 +48,11 @@ struct ContentRecord {
std::array<u8, 0x10> nca_id; std::array<u8, 0x10> nca_id;
std::array<u8, 0x6> size; std::array<u8, 0x6> size;
ContentRecordType type; ContentRecordType type;
INSERT_PADDING_BYTES(1); u8 id_offset;
}; };
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size."); static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}}; constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, 0};
struct MetaRecord { struct MetaRecord {
u64_le title_id; u64_le title_id;
+2 -9
View File
@@ -345,8 +345,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs; return exefs;
} }
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, const std::string& build_id) const {
const std::string& build_id) const {
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[title_id];
const auto nso_build_id = fmt::format("{:0<64}", build_id); const auto nso_build_id = fmt::format("{:0<64}", build_id);
@@ -361,16 +360,11 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
for (const auto& file : exefs_dir->GetFiles()) { for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") { if (file->GetExtension() == "ips") {
auto name = file->GetName(); auto name = file->GetName();
const auto this_build_id = fmt::format("{:0<64}", name.substr(0, name.find('.')));
const auto this_build_id =
fmt::format("{:0<64}", name.substr(0, name.find('.')));
if (nso_build_id == this_build_id) if (nso_build_id == this_build_id)
out.push_back(file); out.push_back(file);
} else if (file->GetExtension() == "pchtxt") { } else if (file->GetExtension() == "pchtxt") {
IPSwitchCompiler compiler{file}; IPSwitchCompiler compiler{file};
if (!compiler.IsValid())
continue;
const auto this_build_id = Common::HexToString(compiler.GetBuildID()); const auto this_build_id = Common::HexToString(compiler.GetBuildID());
if (nso_build_id == this_build_id) if (nso_build_id == this_build_id)
out.push_back(file); out.push_back(file);
@@ -378,7 +372,6 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
} }
} }
} }
return out; return out;
} }
+31 -6
View File
@@ -566,21 +566,46 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
return file; return file;
} }
static std::optional<NcaID> CheckMapForContentRecord(const ankerl::unordered_dense::map<u64, CNMT>& map, u64 title_id, ContentRecordType type) { static std::optional<NcaID> CheckMapForContentRecord(const ::Common::unordered_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
const auto cmnt_iter = map.find(title_id); auto cmnt_iter = map.find(title_id);
u8 id_offset = 0;
if (cmnt_iter == map.cend()) { if (cmnt_iter == map.cend()) {
return std::nullopt; const auto program_index = title_id & AOC_TITLE_ID_MASK;
if (program_index == 0) {
return std::nullopt;
}
cmnt_iter = map.find(title_id & ~AOC_TITLE_ID_MASK);
if (cmnt_iter == map.cend()) {
return std::nullopt;
}
id_offset = static_cast<u8>(program_index);
} }
const auto& cnmt = cmnt_iter->second; const auto& cnmt = cmnt_iter->second;
const auto& content_records = cnmt.GetContentRecords(); const auto& content_records = cnmt.GetContentRecords();
const auto iter = std::find_if(content_records.cbegin(), content_records.cend(), const auto iter = std::find_if(content_records.cbegin(), content_records.cend(),
[type](const ContentRecord& rec) { return rec.type == type; }); [type, id_offset](const ContentRecord& rec) {
if (iter == content_records.cend()) { return rec.type == type && rec.id_offset == id_offset;
});
if (iter != content_records.cend()) {
return std::make_optional(iter->nca_id);
}
if (id_offset != 0) {
return std::nullopt; return std::nullopt;
} }
return std::make_optional(iter->nca_id); const auto fallback_iter =
std::find_if(content_records.cbegin(), content_records.cend(),
[type](const ContentRecord& rec) { return rec.type == type; });
if (fallback_iter == content_records.cend()) {
return std::nullopt;
}
return std::make_optional(fallback_iter->nca_id);
} }
std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id, std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
+6 -6
View File
@@ -12,7 +12,7 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include <boost/container/flat_map.hpp> #include <boost/container/flat_map.hpp>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
@@ -209,11 +209,11 @@ private:
ContentProviderParsingFunction parser; ContentProviderParsingFunction parser;
// maps tid -> NcaID of meta // maps tid -> NcaID of meta
ankerl::unordered_dense::map<u64, NcaID> meta_id; ::Common::unordered_map<u64, NcaID> meta_id;
// maps tid -> meta // maps tid -> meta
ankerl::unordered_dense::map<u64, CNMT> meta; ::Common::unordered_map<u64, CNMT> meta;
// maps tid -> meta for CNMT in yuzu_meta // maps tid -> meta for CNMT in yuzu_meta
ankerl::unordered_dense::map<u64, CNMT> yuzu_meta; ::Common::unordered_map<u64, CNMT> yuzu_meta;
}; };
enum class ContentProviderUnionSlot { enum class ContentProviderUnionSlot {
@@ -313,8 +313,8 @@ private:
void ProcessXCI(const VirtualFile& file); void ProcessXCI(const VirtualFile& file);
std::vector<VirtualDir> load_dirs; std::vector<VirtualDir> load_dirs;
ankerl::unordered_dense::map<std::tuple<u64, ContentRecordType, TitleType>, VirtualFile> entries; ::Common::unordered_map<std::tuple<u64, ContentRecordType, TitleType>, VirtualFile> entries;
ankerl::unordered_dense::map<u64, u32> versions; ::Common::unordered_map<u64, u32> versions;
std::vector<ExternalUpdateEntry> multi_version_entries; std::vector<ExternalUpdateEntry> multi_version_entries;
}; };
+8 -6
View File
@@ -12,6 +12,7 @@
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging.h" #include "common/logging.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h" #include "core/file_sys/partition_filesystem.h"
@@ -65,10 +66,12 @@ u64 NSP::GetProgramTitleID() const {
} }
auto program_id = expected_program_id; auto program_id = expected_program_id;
if (program_id == 0) { if (program_id == 0 && !program_status.empty()) {
if (!program_status.empty()) { program_id = std::min_element(program_status.cbegin(), program_status.cend(),
program_id = program_status.begin()->first; [](const auto& lhs, const auto& rhs) {
} return lhs.first < rhs.first;
})
->first;
} }
program_id = program_id + program_index; program_id = program_id + program_index;
@@ -273,8 +276,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
// If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and // If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and
// 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular // 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular
// update NCA. // update NCA.
if ((next_nca->GetTitleId() & 0x7FF) != 0 && if ((next_nca->GetTitleId() & AOC_TITLE_ID_MASK) != 0) {
(next_nca->GetTitleId() & 0x800) == 0) {
ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
std::move(next_nca); std::move(next_nca);
} else { } else {
+3 -3
View File
@@ -6,7 +6,7 @@
#include <algorithm> #include <algorithm>
#include <set> #include <set>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_set.h"
#include <utility> #include <utility>
#include "core/file_sys/vfs/vfs_layered.h" #include "core/file_sys/vfs/vfs_layered.h"
@@ -63,7 +63,7 @@ std::string LayeredVfsDirectory::GetFullPath() const {
std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const { std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualFile> out; std::vector<VirtualFile> out;
ankerl::unordered_dense::set<std::string> out_names; ::Common::unordered_set<std::string> out_names;
for (const auto& layer : dirs) { for (const auto& layer : dirs) {
for (auto& file : layer->GetFiles()) { for (auto& file : layer->GetFiles()) {
@@ -79,7 +79,7 @@ std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const { std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
std::vector<VirtualDir> out; std::vector<VirtualDir> out;
ankerl::unordered_dense::set<std::string> out_names; ::Common::unordered_set<std::string> out_names;
for (const auto& layer : dirs) { for (const auto& layer : dirs) {
for (const auto& sd : layer->GetSubdirectories()) { for (const auto& sd : layer->GetSubdirectories()) {
+2 -2
View File
@@ -78,7 +78,7 @@ private:
std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{}; std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
std::map<KProcessAddress, u64> m_debug_page_refcounts{}; std::map<KProcessAddress, u64> m_debug_page_refcounts{};
#ifdef HAS_NCE #ifdef HAS_NCE
ankerl::unordered_dense::map<u64, u64> m_post_handlers{}; ::Common::unordered_map<u64, u64> m_post_handlers{};
#endif #endif
std::unique_ptr<Core::ExclusiveMonitor> m_exclusive_monitor; std::unique_ptr<Core::ExclusiveMonitor> m_exclusive_monitor;
Core::Memory::Memory m_memory; Core::Memory::Memory m_memory;
@@ -494,7 +494,7 @@ public:
static void Switch(KernelCore& kernel, KProcess* cur_process, KProcess* next_process); static void Switch(KernelCore& kernel, KProcess* cur_process, KProcess* next_process);
#ifdef HAS_NCE #ifdef HAS_NCE
ankerl::unordered_dense::map<u64, u64>& GetPostHandlers() noexcept { ::Common::unordered_map<u64, u64>& GetPostHandlers() noexcept {
return m_post_handlers; return m_post_handlers;
} }
#endif #endif
+2 -2
View File
@@ -1216,7 +1216,7 @@ Result KServerSession::ReceiveRequest(KernelCore& kernel, uintptr_t server_messa
} }
Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, bool is_hle) { KPhysicalAddress server_message_paddr, bool is_hle, bool session_closed) {
// Lock the session. // Lock the session.
KScopedLightLock lk{m_lock}; KScopedLightLock lk{m_lock};
@@ -1248,7 +1248,7 @@ Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, u
KEvent* event = request->GetEvent(); KEvent* event = request->GetEvent();
// Check whether we're closed. // Check whether we're closed.
const bool closed = (client_thread == nullptr || m_parent->IsClientClosed()); const bool closed = (client_thread == nullptr || m_parent->IsClientClosed() || session_closed);
Result result = ResultSuccess; Result result = ResultSuccess;
if (!closed) { if (!closed) {
+3 -3
View File
@@ -54,14 +54,14 @@ public:
Result OnRequest(KernelCore& kernel, KSessionRequest* request); Result OnRequest(KernelCore& kernel, KSessionRequest* request);
Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, bool is_hle = false); KPhysicalAddress server_message_paddr, bool is_hle = false, bool session_closed = false);
Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, KPhysicalAddress server_message_paddr,
std::shared_ptr<Service::HLERequestContext>* out_context = nullptr, std::shared_ptr<Service::HLERequestContext>* out_context = nullptr,
std::weak_ptr<Service::SessionRequestManager> manager = {}); std::weak_ptr<Service::SessionRequestManager> manager = {});
Result SendReplyHLE(KernelCore& kernel) { Result SendReplyHLE(KernelCore& kernel, bool session_closed = false) {
R_RETURN(this->SendReply(kernel, 0, 0, 0, true)); R_RETURN(this->SendReply(kernel, 0, 0, 0, true, session_closed));
} }
Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr<Service::HLERequestContext>* out_context, Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr<Service::HLERequestContext>* out_context,
+25 -7
View File
@@ -10,7 +10,8 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include "common/container/unordered_set.h"
#include <utility> #include <utility>
#include "common/assert.h" #include "common/assert.h"
@@ -349,9 +350,18 @@ struct KernelCore::Impl {
object_name_global_data.emplace(kernel); object_name_global_data.emplace(kernel);
} }
void MakeApplicationProcess(KernelCore& kernel, KProcess* process) { void SetApplicationProcess(KernelCore& kernel, KProcess* process) {
if (application_process == process)
return;
KProcess* const previous = application_process;
application_process = process; application_process = process;
application_process->Open(kernel);
if (application_process != nullptr)
application_process->Open(kernel);
if (previous != nullptr)
previous->Close(kernel);
} }
/// Sets the host thread ID for the caller. /// Sets the host thread ID for the caller.
@@ -783,8 +793,8 @@ struct KernelCore::Impl {
std::optional<KObjectNameGlobalData> object_name_global_data; std::optional<KObjectNameGlobalData> object_name_global_data;
ankerl::unordered_dense::set<KAutoObject*> registered_objects; ::Common::unordered_set<KAutoObject*> registered_objects;
ankerl::unordered_dense::set<KAutoObject*> registered_in_use_objects; ::Common::unordered_set<KAutoObject*> registered_in_use_objects;
std::mutex server_lock; std::mutex server_lock;
std::vector<std::unique_ptr<Service::ServerManager>> server_managers; std::vector<std::unique_ptr<Service::ServerManager>> server_managers;
@@ -879,8 +889,8 @@ void KernelCore::RemoveProcess(KProcess* process) {
} }
} }
void KernelCore::MakeApplicationProcess(KProcess* process) { void KernelCore::SetApplicationProcess(KProcess* process) {
impl->MakeApplicationProcess(*this, process); impl->SetApplicationProcess(*this, process);
} }
KProcess* KernelCore::ApplicationProcess() { KProcess* KernelCore::ApplicationProcess() {
@@ -891,6 +901,14 @@ const KProcess* KernelCore::ApplicationProcess() const {
return impl->application_process; return impl->application_process;
} }
KScopedAutoObject<KProcess> KernelCore::GetProcessByProcessId(u64 process_id) {
std::scoped_lock lk{impl->process_list_lock};
for (auto* const process : impl->process_list)
if (process != nullptr && process->GetProcessId() == process_id)
return {*this, process};
return {*this, nullptr};
}
std::list<KScopedAutoObject<KProcess>> KernelCore::GetProcessList() { std::list<KScopedAutoObject<KProcess>> KernelCore::GetProcessList() {
std::list<KScopedAutoObject<KProcess>> processes; std::list<KScopedAutoObject<KProcess>> processes;
std::scoped_lock lk{impl->process_list_lock}; std::scoped_lock lk{impl->process_list_lock};
+6 -3
View File
@@ -11,7 +11,7 @@
#include <list> #include <list>
#include <memory> #include <memory>
#include <string> #include <string>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include <vector> #include <vector>
#include "common/polyfill_thread.h" #include "common/polyfill_thread.h"
@@ -124,8 +124,8 @@ public:
void AppendNewProcess(KProcess* process); void AppendNewProcess(KProcess* process);
void RemoveProcess(KProcess* process); void RemoveProcess(KProcess* process);
/// Makes the given process the new application process. /// Makes the given process the current application process.
void MakeApplicationProcess(KProcess* process); void SetApplicationProcess(KProcess* process);
/// Retrieves a pointer to the application process. /// Retrieves a pointer to the application process.
KProcess* ApplicationProcess(); KProcess* ApplicationProcess();
@@ -133,6 +133,9 @@ public:
/// Retrieves a const pointer to the application process. /// Retrieves a const pointer to the application process.
const KProcess* ApplicationProcess() const; const KProcess* ApplicationProcess() const;
/// Retrieves the process with the given process ID, or a null object.
KScopedAutoObject<KProcess> GetProcessByProcessId(u64 process_id);
/// Retrieves the list of processes. /// Retrieves the list of processes.
std::list<KScopedAutoObject<KProcess>> GetProcessList(); std::list<KScopedAutoObject<KProcess>> GetProcessList();
+3 -47
View File
@@ -17,56 +17,12 @@
namespace Kernel::Svc { namespace Kernel::Svc {
constexpr auto MAX_MSG_TIME = std::chrono::milliseconds(250);
const auto MAX_MSG_SIZE = 0x1000;
/// Used to output a message on a debug hardware unit - does nothing on a retail unit /// Used to output a message on a debug hardware unit - does nothing on a retail unit
Result OutputDebugString(Core::System& system, u64 address, u64 len) { Result OutputDebugString(Core::System& system, u64 address, u64 len) {
static struct DebugFlusher {
std::string msg_buffer;
std::mutex msg_mutex;
std::condition_variable msg_cv;
std::chrono::steady_clock::time_point last_msg_time;
std::optional<std::jthread> thread;
} flusher_data;
R_SUCCEED_IF(len == 0); R_SUCCEED_IF(len == 0);
// Only start the thread the very first time this function is called std::string msg_buffer(len, 0);
if (!flusher_data.thread) { GetCurrentMemory(system.Kernel()).ReadBlock(address, msg_buffer.data(), len);
flusher_data.thread.emplace([](std::stop_token stop_token) { LOG_INFO(Debug_Emulated, "{}", msg_buffer);
while (!stop_token.stop_requested()) {
std::unique_lock lock(flusher_data.msg_mutex);
flusher_data.msg_cv.wait(lock, [&stop_token] {
return !flusher_data.msg_buffer.empty() || stop_token.stop_requested();
});
if (stop_token.stop_requested() && flusher_data.msg_buffer.empty())
break;
auto timeout = flusher_data.last_msg_time + MAX_MSG_TIME;
bool woke_early = flusher_data.msg_cv.wait_until(lock, timeout, [&stop_token] {
return flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested();
});
if (!woke_early || flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested()) {
if (!flusher_data.msg_buffer.empty()) {
// Remove trailing newline as LOG_INFO adds that anyways
if (flusher_data.msg_buffer.back() == '\n')
flusher_data.msg_buffer.pop_back();
LOG_INFO(Debug_Emulated, "\n{}", flusher_data.msg_buffer);
flusher_data.msg_buffer.clear();
}
if (stop_token.stop_requested()) break;
}
}
flusher_data.msg_cv.notify_all();
});
}
{
std::lock_guard lock(flusher_data.msg_mutex);
const auto old_size = flusher_data.msg_buffer.size();
flusher_data.msg_buffer.resize(old_size + len);
GetCurrentMemory(system.Kernel()).ReadBlock(address, flusher_data.msg_buffer.data() + old_size, len);
flusher_data.last_msg_time = std::chrono::steady_clock::now();
}
flusher_data.msg_cv.notify_one();
R_SUCCEED(); R_SUCCEED();
} }
+4
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -10,6 +13,7 @@ namespace Service::AM {
constexpr Result ResultNoDataInChannel{ErrorModule::AM, 2}; constexpr Result ResultNoDataInChannel{ErrorModule::AM, 2};
constexpr Result ResultNoMessages{ErrorModule::AM, 3}; constexpr Result ResultNoMessages{ErrorModule::AM, 3};
constexpr Result ResultLibraryAppletTerminated{ErrorModule::AM, 22}; constexpr Result ResultLibraryAppletTerminated{ErrorModule::AM, 22};
constexpr Result ResultApplicationRecordNotFound{ErrorModule::AM, 37};
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
constexpr Result ResultInvalidStorageType{ErrorModule::AM, 511}; constexpr Result ResultInvalidStorageType{ErrorModule::AM, 511};
constexpr Result ResultFatalSectionCountImbalance{ErrorModule::AM, 512}; constexpr Result ResultFatalSectionCountImbalance{ErrorModule::AM, 512};
+9 -1
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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -201,6 +201,14 @@ enum class ProgramSpecifyKind : u32 {
RestartProgram = 2, RestartProgram = 2,
}; };
// Maufeat: Use enums for zindex instead of using random zindex numbers
enum AppletZIndex : s32 {
Background = 0,
Foreground = 1,
ForegroundVisible = 2,
Overlay = 3,
};
struct CommonArguments { struct CommonArguments {
CommonArgumentVersion arguments_version; CommonArgumentVersion arguments_version;
CommonArgumentSize size; CommonArgumentSize size;
+9 -9
View File
@@ -56,22 +56,22 @@ void Applet::UpdateSuspensionStateLocked(bool force_message) {
} }
} }
void Applet::SetInteractibleLocked(bool interactible) { void Applet::SetInteractibleLocked(bool pad_interactible, bool touch_interactible) {
if (is_interactible == interactible) { if (is_pad_interactible == pad_interactible && is_touch_interactible == touch_interactible) {
return; return;
} }
is_interactible = interactible; is_pad_interactible = pad_interactible;
is_touch_interactible = touch_interactible;
const bool exit_requested = lifecycle_manager.GetExitRequested(); const bool exit_requested = lifecycle_manager.GetExitRequested();
const bool input_enabled = interactible && !exit_requested; const bool pad_enabled = pad_interactible && !exit_requested;
const bool touch_enabled = touch_interactible && !exit_requested;
if (applet_id == AppletId::OverlayDisplay || applet_id == AppletId::Application) { LOG_DEBUG(Service_AM, "applet={} pad={} touch={} exit_requested={}",
LOG_DEBUG(Service_AM, "called, applet={} interactible={} exit_requested={} input_enabled={} overlay_in_foreground={}", static_cast<u32>(applet_id), pad_enabled, touch_enabled, exit_requested);
static_cast<u32>(applet_id), interactible, exit_requested, input_enabled, overlay_in_foreground);
}
hid_registration.EnableAppletToGetInput(input_enabled); hid_registration.EnableAppletToGetInput(pad_enabled, touch_enabled);
} }
void Applet::OnProcessTerminatedLocked() { void Applet::OnProcessTerminatedLocked() {
+5 -3
View File
@@ -125,9 +125,11 @@ struct Applet {
bool album_image_taken_notification_enabled{}; bool album_image_taken_notification_enabled{};
bool record_volume_muted{}; bool record_volume_muted{};
bool is_activity_runnable{}; bool is_activity_runnable{};
bool is_interactible{true}; bool is_pad_interactible{true};
bool is_touch_interactible{true};
bool window_visible{true}; bool window_visible{true};
bool overlay_in_foreground{false}; bool overlay_watching_short_home_button{false};
bool overlay_handling_touch_input{false};
// Events // Events
Event overlay_event; Event overlay_event;
@@ -148,7 +150,7 @@ struct Applet {
// Process state management // Process state management
void UpdateSuspensionStateLocked(bool force_message); void UpdateSuspensionStateLocked(bool force_message);
void SetInteractibleLocked(bool interactible); void SetInteractibleLocked(bool pad_interactible, bool touch_interactible);
void OnProcessTerminatedLocked(); void OnProcessTerminatedLocked();
}; };
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -6,6 +6,7 @@
#include "core/core.h" #include "core/core.h"
#include "core/hle/service/am/display_layer_manager.h" #include "core/hle/service/am/display_layer_manager.h"
#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/sm/sm.h" #include "core/hle/service/sm/sm.h"
#include "core/hle/service/vi/application_display_service.h" #include "core/hle/service/vi/application_display_service.h"
#include "core/hle/service/vi/container.h" #include "core/hle/service/vi/container.h"
@@ -33,6 +34,7 @@ void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* pro
m_system_shared_buffer_id = 0; m_system_shared_buffer_id = 0;
m_system_shared_layer_id = 0; m_system_shared_layer_id = 0;
m_applet_id = applet_id; m_applet_id = applet_id;
m_library_applet_mode = mode;
m_buffer_sharing_enabled = false; m_buffer_sharing_enabled = false;
m_blending_enabled = mode == LibraryAppletMode::PartialForeground || m_blending_enabled = mode == LibraryAppletMode::PartialForeground ||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay; mode == LibraryAppletMode::PartialForegroundIndirectDisplay;
@@ -72,14 +74,16 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()})); out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id); m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerStackMask(*out_layer_id,
this->GetLayerStackMask());
if (m_applet_id != AppletId::Application) { if (m_applet_id != AppletId::Application) {
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id); (void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
if (m_applet_id == AppletId::OverlayDisplay) { if (m_applet_id == AppletId::OverlayDisplay) {
(void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id); (void)m_manager_display_service->SetLayerZIndex(Overlay, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true); (void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
} else { } else {
(void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id); (void)m_manager_display_service->SetLayerZIndex(Foreground, *out_layer_id);
} }
} }
@@ -122,10 +126,12 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
// Ensure the overlay layer is visible // Ensure the overlay layer is visible
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id); m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
(void)m_display_service->GetContainer()->SetLayerStackMask(m_system_shared_layer_id,
this->GetLayerStackMask());
m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id); m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
s32 initial_z = 1; s32 initial_z = Foreground;
if (m_applet_id == AppletId::OverlayDisplay) { if (m_applet_id == AppletId::OverlayDisplay) {
initial_z = -1; initial_z = Overlay;
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true); (void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
} }
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id); m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
@@ -142,6 +148,36 @@ Result DisplayLayerManager::GetSystemSharedLayerHandle(u64* out_system_shared_bu
R_SUCCEED(); R_SUCCEED();
} }
u32 DisplayLayerManager::GetLayerStackMask() const {
using Nvnflinger::LayerStackBit;
using Nvnflinger::LayerStackId;
constexpr u32 Displayed = LayerStackBit(LayerStackId::Default);
constexpr u32 Screenshot = LayerStackBit(LayerStackId::Screenshot);
constexpr u32 Recording = LayerStackBit(LayerStackId::Recording);
constexpr u32 LastFrame = LayerStackBit(LayerStackId::LastFrame);
constexpr u32 Debug = LayerStackBit(LayerStackId::ApplicationForDebug);
switch (m_applet_id) {
case AppletId::Application:
return Displayed | Screenshot | Recording | LastFrame | Debug;
case AppletId::OverlayDisplay:
return Displayed;
case AppletId::QLaunch:
return Displayed;
default:
break;
}
switch (m_library_applet_mode) {
case LibraryAppletMode::AllForeground:
case LibraryAppletMode::AllForegroundInitiallyHidden:
return Displayed | Screenshot | LastFrame;
default:
return Displayed | Screenshot;
}
}
void DisplayLayerManager::SetWindowVisibility(bool visible) { void DisplayLayerManager::SetWindowVisibility(bool visible) {
if (m_visible == visible) { if (m_visible == visible) {
return; return;
@@ -185,10 +221,17 @@ void DisplayLayerManager::SetOverlayZIndex(s32 z_index) {
} }
Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written, Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written,
s32* out_fbshare_layer_index) { s32* out_fbshare_layer_index,
VI::CaptureKind kind) {
R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied); R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);
R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->WriteAppletCaptureBuffer( R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->WriteAppletCaptureBuffer(
out_was_written, out_fbshare_layer_index)); out_was_written, out_fbshare_layer_index, kind));
}
Result DisplayLayerManager::ClearAppletCaptureBuffer(s32 fbshare_layer_index, u32 color) {
R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);
R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->ClearAppletCaptureBuffer(
fbshare_layer_index, color));
} }
} // namespace Service::AM } // namespace Service::AM
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -23,6 +23,7 @@ class KProcess;
namespace Service::VI { namespace Service::VI {
class IApplicationDisplayService; class IApplicationDisplayService;
class IManagerDisplayService; class IManagerDisplayService;
enum class CaptureKind : u32;
} // namespace Service::VI } // namespace Service::VI
namespace Service::AM { namespace Service::AM {
@@ -48,9 +49,13 @@ public:
void SetOverlayZIndex(s32 z_index); void SetOverlayZIndex(s32 z_index);
Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index); Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index,
VI::CaptureKind kind);
Result ClearAppletCaptureBuffer(s32 fbshare_layer_index, u32 color);
private: private:
u32 GetLayerStackMask() const;
Kernel::KProcess* m_process{}; Kernel::KProcess* m_process{};
std::shared_ptr<VI::IApplicationDisplayService> m_display_service{}; std::shared_ptr<VI::IApplicationDisplayService> m_display_service{};
std::shared_ptr<VI::IManagerDisplayService> m_manager_display_service{}; std::shared_ptr<VI::IManagerDisplayService> m_manager_display_service{};
@@ -59,6 +64,7 @@ private:
u64 m_system_shared_buffer_id{}; u64 m_system_shared_buffer_id{};
u64 m_system_shared_layer_id{}; u64 m_system_shared_layer_id{};
AppletId m_applet_id{}; AppletId m_applet_id{};
LibraryAppletMode m_library_applet_mode{};
bool m_buffer_sharing_enabled{}; bool m_buffer_sharing_enabled{};
bool m_blending_enabled{}; bool m_blending_enabled{};
bool m_visible{true}; bool m_visible{true};
@@ -7,7 +7,7 @@
#pragma once #pragma once
#include <array> #include <array>
#include <ankerl/unordered_dense.h> #include "common/container/unordered_map.h"
#include <vector> #include <vector>
#include "common/common_funcs.h" #include "common/common_funcs.h"
@@ -176,6 +176,6 @@ struct WebCommonReturnValue {
}; };
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
using WebArgInputTLVMap = ankerl::unordered_dense::map<WebArgInputTLVType, std::vector<u8>>; using WebArgInputTLVMap = ::Common::unordered_map<WebArgInputTLVType, std::vector<u8>>;
} // namespace Service::AM::Frontend } // namespace Service::AM::Frontend
+11 -6
View File
@@ -36,12 +36,17 @@ HidRegistration::~HidRegistration() {
} }
} }
void HidRegistration::EnableAppletToGetInput(bool enable) { void HidRegistration::EnableAppletToGetInput(bool enable_pad, bool enable_touch) {
if (m_process.IsInitialized()) { if (!m_process.IsInitialized())
m_hid_server->GetResourceManager()->SetAruidValidForVibration(m_process.GetProcessId(), return;
enable);
m_hid_server->GetResourceManager()->EnableInput(m_process.GetProcessId(), enable); const auto resource_manager = m_hid_server->GetResourceManager();
} const u64 aruid = m_process.GetProcessId();
resource_manager->EnablePadInput(aruid, enable_pad);
resource_manager->EnableTouchScreen(aruid, enable_touch);
resource_manager->SetAruidValidForVibration(aruid, enable_pad);
} }
} // namespace Service::AM } // namespace Service::AM
+1 -1
View File
@@ -28,7 +28,7 @@ public:
~HidRegistration(); ~HidRegistration();
void RegisterCurrentProcess(); void RegisterCurrentProcess();
void EnableAppletToGetInput(bool enable); void EnableAppletToGetInput(bool enable_pad, bool enable_touch);
private: private:
Process& m_process; Process& m_process;

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