Compare commits

...

29 Commits

Author SHA1 Message Date
lizzie 0c553826c2 fx 2026-03-06 21:12:23 +00:00
lizzie 0b4393e49e [android] allow to change docked mode while running
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 19:43:07 +00:00
xbzk 529b069499 [android,ui] fixed top disalignment between buttons of each column in settings fragment (#3675)
this silly little thing tickles obsessive compulsive disturbed fellas a lot hu3
was shipped along PR 3660, which was rediscussed for other reason, hence this tiny lonely PR.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3675
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-05 13:58:46 +01:00
lizzie 9a07bd0570 [vk] unify VkSurfaceKHR with Android and the rest of platforms; remove technically incorrect nullptr() ctor for handles (#2971)
Removes some odd #ifdef-ing that just can use a shrimple opaque type.

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

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

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

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

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

Nice, i feel better now.

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

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

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3666
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-04 22:46:55 +01:00
PavelBARABANOV 9d2341eaea [vk] Disable float16 math on non-MESA AMD drivers as 2026+ versions are broken (#3661)
thanks MaranBR

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3661
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-03-03 14:27:47 +01:00
tarako d720a7b4b4 [vulkan] Fixed dual-source blending to correctly map shader outputs. (#3637)
Fixes Skyward Sword HD eye gitch and a related MoltenVK crash due to the incorrect output mapping. Verified working on mac and android.

The test in vk_pipeline_cache.cpp is a bit ugly, but it didn't seem worth it to go lambda/macro just to make it look cleaner. Could change if necessary.

Co-authored-by: tarako <none@none.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3637
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: tarako <r76036296@gmail.com>
Co-committed-by: tarako <r76036296@gmail.com>
2026-03-03 01:54:57 +01:00
crueter 464212393e [gitignore] add *.tar.zst (#3663)
the amount of times I've accidentally committed a tar.zst to this
repository...

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3663
2026-03-02 05:28:26 +01:00
MaranBr ea209e6dab [vulkan] Fine-tuning of frame pacing logic (#3628)
It makes fine adjustments to the frame pacing, ensuring better stability and precision.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3628
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-03-02 02:53:01 +01:00
MaranBr b5f9f8b743 [vulkan] Fix Vulkan rendering, image layout, and synchronization issues (#3511)
This fixes a visual corruption issue that occurred intermittently after loading screens, where some games would start the scene with vertex explosions, artifacts or with all colors blown out, resembling neon.

Among the known games affected by this bug are Mario Kart 8 Deluxe, The Legend of Zelda: Breath of the Wild, The Legend of Zelda: Tears of the Kingdom, Kirby and the Forgotten Land, Luigi's Mansion 3, Xenoblade Chronicles 3 and possibly others as well.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3511
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-03-02 02:51:50 +01:00
xbzk a8fc994c0b pr3655 gradientbordercardview fix (#3658)
This complements PR3655, where a change in theme setting caused gradientbordercardview theme color setting mismatch.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3658
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-01 23:21:26 +01:00
lizzie 2991bd18ef [vk] Enable VK_IMG_filter_cubic on powervr (#3643)
in powervr, the enum for cubic filtering is an alias for an ext, however in true powervr fashion they want you to use their stupid VK_IMG

and knowing powevr it probably bugs out if you dont

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3643
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-01 01:53:15 +01:00
lizzie 281d7a468e [ports] Remove ENABLE_OPENSSL and mbedtls nixos remnants (#3638)
shell.nix still had references to mbedtls

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3638
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-01 01:53:04 +01:00
PavelBARABANOV 12f89745be [android] add IntSetting for static theme and set green as default (#3655)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3655
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-02-28 19:34:55 +01:00
xXJSONDeruloXx 7de5eb6884 [android] fix persist manual game pause after android sleep/wake (#3651)
if user invokes the "pause game" option from the menu while in game, as expected this suspends the process till user manually hits resume.. except for one case: Android sleep/wake lifecycle.

If user manually pauses a running game, then sleeps their device, then wakes their device; the game will self-resume without user pressing "resume game".

Expected behavior IMO is that if user left the game process in manually paused state, app should respect this and persist the pause on system wake, so that user may manually press "resume game" to unfreeze the process.

Simple fix is to have a few params for user initiated pause and resume, and update the pause and run methods to handle as described above.

Please let me know if there is a cleaner way to implement!

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3651
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: xXJSONDeruloXx <danielhimebauch@gmail.com>
Co-committed-by: xXJSONDeruloXx <danielhimebauch@gmail.com>
2026-02-28 16:05:06 +01:00
John ac181b756f [Config] Update Rescale Toggle Description to Include Intel GPUs (#3650)
The toggle hack has benefits for Intel GPU users in Luigi Mansion 3 as they also suffer from line artifacts.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3650
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: John <john@eden-emu.dev>
Co-committed-by: John <john@eden-emu.dev>
2026-02-28 01:07:40 +01:00
smiRaphi 4a833e7206 [desktop] port IR Camera to Qt6 & fix camera saving on windows (#3630)
The camera was previously saved without escaping the name which made the values unusable after a settings load, for whatever reason replacing the backslashes when saving with / doesn't work but replacing them with | does.

Also note that the OBS virtual cam (and any other cameras that only have directshow drivers) won't work because Qt6 dropped support for that and the ffmpeg backend doesn't seem to support it either.

Closes #3468

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3630
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: smiRaphi <neogt404@gmail.com>
Co-committed-by: smiRaphi <neogt404@gmail.com>
2026-02-28 01:06:59 +01:00
lizzie 06a08de68a [core/loader] prevent program_image reallocations in NSO+KIP loading methods (#3639)
also changes some methods to std::span<> as well, but mainly std::vector<> in the NSO/KIP loading stuff is not needed to be memcpy'ed and memmove'd around
this should save a marginal amount of loading time (RDR1)

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3639
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-28 01:06:33 +01:00
JPikachu f8712e50e6 [audio] stub SetAudioDeviceOutputVolumeAutoTuneEnabled and IsAudioDeviceOutputVolumeAutoTuneEnabled (#3654)
For Pokémon LeafGreen Version and Pokémon FireRed Version

Co-authored-by: JPikachu <jpikachu.eden@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3654
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: JPikachu <jpikachu@eden-emu.dev>
Co-committed-by: JPikachu <jpikachu@eden-emu.dev>
2026-02-28 01:06:22 +01:00
crueter fc63de39dd [docs] Bring back the Options page, and clean up LLM policy (#3649)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3649
2026-02-27 03:33:36 +01:00
crueter bf25058dbd [docs] SIGNUP: Add spam/junk notice, email patch info, and clarify some things (#3648)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3648
2026-02-27 03:33:29 +01:00
lizzie 75104b6103 [hle] Implement IApplicationManagerInterface::GetTotalSpaceSize (#3642)
oops

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3642
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-26 16:48:04 +01:00
John 0d950195e9 [Settings] Add "Enable Legacy Rescale Pass" Toggle (#3582)
This PR introduces an optional Legacy Rescale Compatibility Mode that restores the previous rescale‑pass behavior for titles that rely on its quirks. While the new rescale logic is generally more correct, some games exhibit visual issues that the legacy behavior incidentally avoids.
Enabling this mode can mitigate line artifacts on AMD GPUs and reduce grey‑texture flickering on Nvidia GPUs in Luigi’s Mansion 3. This is a compatibility workaround rather than a full fix, and should only be used for titles affected by these rare edge‑case rendering problems.

Original Logic from MaranBR

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3582
Co-authored-by: John <john@eden-emu.dev>
Co-committed-by: John <john@eden-emu.dev>
2026-02-26 03:57:25 +01:00
PavelBARABANOV 04e88ab82c [qt] Remove QEvent::Polish from titlebar filter to fix Applets tab (#3644)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3644
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-02-26 03:15:32 +01:00
107 changed files with 1439 additions and 1173 deletions
+1
View File
@@ -63,3 +63,4 @@ artifacts
*.AppImage*
/install*
vulkansdk*.exe
*.tar.zst
+7 -7
View File
@@ -70,7 +70,7 @@ endif()
option(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF)
# option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
cmake_dependent_option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
cmake_dependent_option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries")
cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
@@ -577,9 +577,9 @@ if (ENABLE_QT)
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent)
# if (YUZU_USE_QT_MULTIMEDIA)
# find_package(Qt6 REQUIRED COMPONENTS Multimedia)
# endif()
if (YUZU_USE_QT_MULTIMEDIA)
find_package(Qt6 REQUIRED COMPONENTS Multimedia)
endif()
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
# yes Qt, we get it
@@ -618,9 +618,9 @@ if (ENABLE_QT)
if (PLATFORM_LINUX)
list(APPEND YUZU_QT_COMPONENTS DBus)
endif()
# if (YUZU_USE_QT_MULTIMEDIA)
# list(APPEND YUZU_QT_COMPONENTS Multimedia)
# endif()
if (YUZU_USE_QT_MULTIMEDIA)
list(APPEND YUZU_QT_COMPONENTS Multimedia)
endif()
if (YUZU_USE_QT_WEB_ENGINE)
list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets)
endif()
+100
View File
@@ -0,0 +1,100 @@
# CMake Options
To change these options, add `-DOPTION_NAME=NEWVALUE` to the command line.
- On Qt Creator, go to Project -> Current Configuration
Notes:
- Defaults are marked per-platform.
- "Non-UNIX" just means Windows/MSVC and Android (yes, macOS is UNIX
- Android generally doesn't need to change anything; if you do, go to `src/android/app/build.gradle.kts`
- To set a boolean variable to on, use `ON` for the value; to turn it off, use `OFF`
- If a variable is mentioned as being e.g. "ON" for a specific platform(s), that means it is defaulted to OFF on others
- TYPE is always boolean unless otherwise specified
- Format:
- `OPTION_NAME` (TYPE DEFAULT) DESCRIPTION
## Options
### Dependencies
These options control dependencies.
- `YUZU_USE_BUNDLED_FFMPEG` (ON for non-UNIX) Download a pre-built and configured FFmpeg
- `YUZU_USE_EXTERNAL_FFMPEG` (ON for Solaris) Build FFmpeg from source
- `YUZU_DOWNLOAD_ANDROID_VVL` (ON) Download validation layer binary for Android
- `YUZU_DOWNLOAD_TIME_ZONE_DATA` (ON) Always download time zone binaries
- Currently, build fails without this
- `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS and Gentoo)
- `YUZU_USE_BUNDLED_MOLTENVK` (ON, macOS only) Download bundled MoltenVK lib
- `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC, Android, Solaris, and OpenBSD) Download bundled OpenSSL build
- `YUZU_USE_EXTERNAL_SDL2` (OFF) Compiles SDL2 from source
- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2
### Miscellaneous
- `ENABLE_WEB_SERVICE` (ON) Enable multiplayer service
- `ENABLE_WIFI_SCAN` (OFF) Enable WiFi scanning (requires iw on Linux) - experimental
- `ENABLE_CUBEB` (ON) Enables the cubeb audio backend
- This option is subject for removal.
- `YUZU_TESTS` (ON) Compile tests - requires Catch2
- `ENABLE_LTO` (OFF) Enable link-time optimization
- Not recommended on Windows
- UNIX may be better off appending `-flto=thin` to compiler args
- `USE_FASTER_LINKER` (OFF) Check if a faster linker is available
- Not recommended outside of Linux
### Flavors
These options control executables and build flavors.
- `YUZU_LEGACY` (OFF): Apply patches to improve compatibility on some older GPUs at the cost of performance
- `NIGHTLY_BUILD` (OFF): This is only used by CI. Do not use this unless you're making your own distribution and know what you're doing.
- `YUZU_STATIC_BUILD` (OFF) Attempt to build using static libraries if possible
- Not supported on Linux
- Automatically set if `YUZU_USE_BUNDLED_QT` is on for non-Linux
- `ENABLE_UPDATE_CHECKER` (OFF) Enable update checking functionality
- `YUZU_DISABLE_LLVM` (OFF) Do not attempt to link to the LLVM demangler
- Really only useful for CI or distribution builds
**Desktop only**:
- `YUZU_CMD` (ON) Compile the SDL2 frontend (eden-cli)
- `YUZU_ROOM` (OFF) Compile dedicated room functionality into the main executable
- `YUZU_ROOM_STANDALONE` (OFF) Compile a separate executable for room functionality
- `YUZU_STATIC_ROOM` (OFF) Compile the room executable *only* as a static, portable executable
- This is only usable on Alpine Linux.
### Desktop
The following options are desktop only.
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input frontend (HIGHLY RECOMMENDED)
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics frontend
- Unavailable on Windows/ARM64
- You probably shouldn't turn this off.
### Qt
Also desktop-only, but apply strictly to Qt
- `ENABLE_QT` (ON) Enable the Qt frontend (recommended)
- `ENABLE_QT_TRANSLATION` (OFF) Enable translations for the Qt frontend
- `YUZU_USE_BUNDLED_QT` (ON for MSVC) Download bundled Qt binaries
- Not recommended on Linux. For Windows and macOS, the provided build is statically linked.
- `YUZU_QT_MIRROR` (string) What mirror to use for downloading the bundled Qt libraries
- `YUZU_USE_QT_MULTIMEDIA` (OFF) Use QtMultimedia for camera support
- `YUZU_USE_QT_WEB_ENGINE` (OFF) Use QtWebEngine for web applet implementation (requires the huge QtWebEngine dependency; not recommended)
- `USE_DISCORD_PRESENCE` (OFF) Enables Discord Rich Presence (Qt frontend only)
### Retired Options
The following options were a part of Eden at one point, but have since been retired.
- `ENABLE_OPENSSL` - MbedTLS was fully replaced with OpenSSL in [#3606](https://git.eden-emu.dev/eden-emu/eden/pulls/3606), because OpenSSL straight-up performs better.
- `ENABLE_SDL2` - While technically possible to *not* use SDL2 on desktop, this is **NOT** a supported configuration under any means, and adding this matrix to our build system was not worth the effort.
- `YUZU_USE_CPM` - This option once had a purpose, but that purpose has long since passed us by. *All* builds use CPMUtil to manage dependencies now.
- If you want to *force* the usage of system dependencies, use `-DCPMUTIL_FORCE_SYSTEM=ON`.
See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed
+1
View File
@@ -3,6 +3,7 @@
This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil), and more.
- **[General Build Instructions](Build.md)**
- **[CMake Options](Options.md)**
- **[Cross Compiling](CrossCompile.md)**
- **[Development Guidelines](Development.md)**
- **[Dependencies](Deps.md)**
+23 -4
View File
@@ -18,7 +18,7 @@ First of all, you MUST have a valid reason to sign up for our Git. Valid reasons
The following are not valid reasons to sign up:
- I want to contribute to Eden.
* Be at least somewhat specific!
* Be at least somewhat specific! We always welcome contributors and developers, but generic "I want to contribute" messages don't give us enough information.
- I want to support Eden.
* If you wish to support us through development, be more specific; otherwise, to support us, check out our [donations page](https://eden-emu.dev/donations).
- I want to report issues.
@@ -41,17 +41,36 @@ Email: <Your Desired Email>
I wish to sign up because... <your reason here>
```
Email notifications are disabled for the time being, so you don't have to use a real email. If you wish to remain anonymous, either send a separate email asking for access to a shared anonymous account, *or* create a fake username and email.
Email notifications are disabled for the time being, so you don't have to use a real email. If you wish to remain anonymous, either send a separate email asking for access to a shared anonymous account, *or* create a fake username and email. Do note that the email you sign up with is used to accredit commits on the web UI, and *must* match your configured GPG key.
## Patches
In general, PRs are the preferred method of tracking patches, as they allow us to go through our standard triage, CI, and testing process without having to deal with the minutiae of incremental patches. However, we also understand that many people prefer to use raw patches, and that's totally okay! While we currently don't have a mailing list, we do accept email patches. To do so:
1. Make your changes on a clean copy of the master branch
2. Commit your changes with a descriptive, well-formed message (see the [commit message docs](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/Development.md#pull-requests)), and a proper description thoroughly explaining your changes.
* Note that we don't need to know all the individual details about your code. A description explaining the motivation and general implementation of your changes is enough, alongside caveats and any potential blockers.
3. Format your patch with `git format-patch -1 HEAD`.
4. Email us with the subject `[Eden] [PATCH] <brief patch description...>`, with a brief description of your patch, and the previously-formatted patch file as an attachment.
* If you don't include the first two bracketed parts, your email may be lost!
The following emails are currently set up to receive and process patches:
- [eden@eden-emu.dev](mailto:eden@eden-emu.dev]
- [crueter@eden-emu.dev](mailto:eden@eden-emu.dev)
## Instructions
If you have read everything above and affirm that you will not abuse your access, click the summary below to get the email to send your request to.
<details>
<summary>I affirm that I have read ALL of the information above, and will not abuse my access to Eden, nor will I send unnecessary spam requests or the following email.</summary>
<summary>I affirm that I have read ALL of the information above, and will not abuse my access to Eden, nor will I send unnecessary spam to the following email.</summary>
Email [crueter@crueter.xyz](mailto:crueter@crueter.xyz) with the format above.
Once your request is processed, you should receive a confirmation email from crueter with your password alongside a link to a repository containing instructions on SSH, etc. Note that you are required to change your password. If your request is rejected, you will receive a notice as such, asking for clarification if needed. If you do not receive a response in 48 hours, you may send another email.
</details>
> [!WARNING]
> Some email providers may place the response email in your spam/junk folder; notable offenders include Gmail and Outlook. *Always* ensure to check your Spam/Junk folder, until Google/Microsoft finally end their vendetta against the great evil of my `.xyz` domain.
</details>
+7 -2
View File
@@ -17,7 +17,9 @@ AI is notorious for hallucinating facts out of thin air and sometimes outright l
- Code that works, but is extraordinarily verbose or not nearly as efficient as it can be
- Code that works well and is written well, but solves a different problem than was intended, or solves the same problem but in a completely incorrect way that will break other things horribly.
Human-written code will, without exception, always be of infinitely higher quality when properly researched and implemented by someone both familiar with the surrounding code and the programming language in use. LLMs may produce a "good enough" result, but this result is often subpar. Keep in mind: all code is held under a standard of excellence. If your code sucks, it will be rejected. AI-generated code just so happens to be a particularly sucky genre of code, and will thus be held to this same standard.
Human-written code will, without exception, always be of infinitely higher quality when properly researched and implemented by someone familiar with *both* the surrounding code and the programming language in use. LLMs may produce a "good enough" result, but this result is often subpar.
**All code is held under a STRICT STANDARD OF EXCELLENCE**. AI code is no different, and since it often produces subpar or outright terrible code, it will often fail to meet this excellence standard.
On a lesser-known note, LLM outputs often contain unicode symbols such as emojis or the arrow symbol. Please don't put Unicode symbols in your code. It messes with many an IDE, and the three people viewing your code on Lynx will be very unhappy.
@@ -25,7 +27,8 @@ On a lesser-known note, LLM outputs often contain unicode symbols such as emojis
## Acceptable Use
- As stated previously, AI is good in a few *very specific* cases. In these cases, it's usually fine to use AI, as long as you **explicitly provide notice that it was used**.
As stated previously, AI is good in a few *very specific* cases. In these cases, it's usually fine to use AI, as long as you **explicitly provide notice that it was used**.
- Anything directly outside of the realm of the code written in your PR or patch is none of our business.
- This primarily covers research.
- However, we *still* strongly discourage this for the reasons mentioned above.
@@ -104,4 +107,6 @@ This consolidates profile removal behavior, fixes potential race conditions in t
This has all of the same problems as the other one. Needlessly verbose, doesn't address *what* it actually fixes ("consolidates profile removal behavior"... okay, why? What does it fix?), etc. It even has the bonus of totally hallucinating the addition of a method!
On a more "philosophical" note, LLMs tend to be geared towards *corporate language*, as that's what they're trained on. This is why AI-generated commit messages feel like "word salad", and typically pad out the commit message to make it *look* like a lot of things were changed (trust me, it's like that in the corporate world). They typically also drift towards unneeded buzzwords and useless implementation details.
**Don't use AI for commit messages**.
+1 -1
View File
@@ -9,7 +9,7 @@ pkgs.mkShellNoCC {
# libraries
openssl boost fmt nlohmann_json lz4 zlib zstd
enet libopus vulkan-headers vulkan-utility-libraries
spirv-tools spirv-headers vulkan-loader unzip mbedtls
spirv-tools spirv-headers vulkan-loader unzip
glslang python3 httplib cpp-jwt ffmpeg-headless
libusb1 cubeb
# eden
-1
View File
@@ -80,7 +80,6 @@ android {
listOf(
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_WEB_SERVICE=1", // Enable web service
"-DENABLE_OPENSSL=ON",
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DYUZU_USE_CPM=ON",
"-DCPMUTIL_FORCE_BUNDLED=ON",
@@ -152,6 +152,10 @@ object NativeLibrary {
external fun surfaceDestroyed()
external fun getAppletCaptureBuffer(): ByteArray
external fun getAppletCaptureWidth(): Int
external fun getAppletCaptureHeight(): Int
/**
* Unpauses emulation from a paused state.
*/
@@ -603,6 +607,12 @@ object NativeLibrary {
*/
external fun addFileToFilesystemProvider(path: String)
/**
* Adds a game-folder file to the manual filesystem provider, respecting the internal gate for
* game-folder external-content mounting.
*/
external fun addGameFolderFileToFilesystemProvider(path: String)
/**
* Clears all files added to the manual filesystem provider in our EmulationSession instance
*/
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -204,9 +204,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
}
override fun onPause() {
super.onPause()
nfcReader.stopScanning()
stopMotionSensorListener()
super.onPause()
}
override fun onDestroy() {
@@ -339,6 +339,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
}
override fun onSensorChanged(event: SensorEvent) {
if (!NativeLibrary.isRunning() || NativeLibrary.isPaused()) {
return
}
val rotation = this.display?.rotation
if (rotation == Surface.ROTATION_90) {
flipMotionOrientation = true
@@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.fetcher
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
@@ -15,8 +16,20 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat
state: RecyclerView.State
) {
outRect.bottom = spacing
if (parent.getChildAdapterPosition(view) == 0) {
val position = parent.getChildAdapterPosition(view)
if (position == RecyclerView.NO_POSITION) return
if (position == 0) {
outRect.top = spacing
return
}
// If the item is in the first row, but NOT in first column add top spacing as well
val layoutManager = parent.layoutManager
if (layoutManager is GridLayoutManager && layoutManager.spanSizeLookup.getSpanGroupIndex(position, layoutManager.spanCount) == 0) {
outRect.top = spacing
return
}
}
}
@@ -34,6 +34,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
MAX_ANISOTROPY("max_anisotropy"),
THEME("theme"),
THEME_MODE("theme_mode"),
STATIC_THEME_COLOR("static_theme_color"),
APP_LANGUAGE("app_language"),
OVERLAY_SCALE("control_scale"),
OVERLAY_OPACITY("control_opacity"),
@@ -55,11 +55,6 @@ abstract class SettingsItem(
val isEditable: Boolean
get() {
// Can't change docked mode toggle when using handheld mode
if (setting.key == BooleanSetting.USE_DOCKED_MODE.key) {
return NativeInput.getStyleIndex(0) != NpadStyleIndex.Handheld
}
// Can't edit enable_qlaunch_button if firmware is not available
if (setting.key == BooleanSetting.ENABLE_QLAUNCH_BUTTON.key) {
return NativeLibrary.isFirmwareAvailable()
@@ -231,30 +226,9 @@ abstract class SettingsItem(
)
)
val dockedModeSetting = object : AbstractBooleanSetting {
override val key = BooleanSetting.USE_DOCKED_MODE.key
override fun getBoolean(needsGlobal: Boolean): Boolean {
if (NativeInput.getStyleIndex(0) == NpadStyleIndex.Handheld) {
return false
}
return BooleanSetting.USE_DOCKED_MODE.getBoolean(needsGlobal)
}
override fun setBoolean(value: Boolean) =
BooleanSetting.USE_DOCKED_MODE.setBoolean(value)
override val defaultValue = BooleanSetting.USE_DOCKED_MODE.defaultValue
override fun getValueAsString(needsGlobal: Boolean): String =
BooleanSetting.USE_DOCKED_MODE.getValueAsString(needsGlobal)
override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset()
}
put(
SwitchSetting(
dockedModeSetting,
BooleanSetting.USE_DOCKED_MODE,
titleId = R.string.use_docked_mode,
descriptionId = R.string.use_docked_mode_description
)
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -68,7 +68,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (val item = settingsViewModel.clickedItem) {
val item = settingsViewModel.clickedItem ?: return@setPositiveButton
clearDialogState()
when (item) {
is AnalogInputSetting -> {
val stickParam = NativeInput.getStickParam(
item.playerIndex,
@@ -107,12 +109,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
}
else -> {
settingsViewModel.clickedItem!!.setting.reset()
item.setting.reset()
settingsViewModel.setAdapterItemChanged(position)
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
clearDialogState()
}
.setOnCancelListener {
clearDialogState()
}
.create()
}
@@ -186,27 +193,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
updateButtonState(isValid)
}
/*
* xbzk: these two events, along with attachRepeat feature,
* were causing spinbox buttons to respond twice per press
* cutting these out to retain accelerated press functionality
* TODO: clean this out later if no issues arise
*
spinboxBinding.buttonDecrement.setOnClickListener {
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
val newValue = current - 1
spinboxBinding.editValue.setText(newValue.toString())
updateValidity(newValue)
}
spinboxBinding.buttonIncrement.setOnClickListener {
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
val newValue = current + 1
spinboxBinding.editValue.setText(newValue.toString())
updateValidity(newValue)
}
*/
fun attachRepeat(button: View, delta: Int) {
val handler = Handler(Looper.getMainLooper())
var runnable: Runnable? = null
@@ -439,9 +425,13 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
private fun closeDialog() {
settingsViewModel.setAdapterItemChanged(position)
clearDialogState()
dismiss()
}
private fun clearDialogState() {
settingsViewModel.clickedItem = null
settingsViewModel.setSliderProgress(-1f)
dismiss()
}
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
@@ -1066,7 +1066,10 @@ class SettingsFragmentPresenter(
IntSetting.THEME.getValueAsString()
override val defaultValue: Int = IntSetting.THEME.defaultValue
override fun reset() = IntSetting.THEME.setInt(defaultValue)
override fun reset() {
IntSetting.THEME.setInt(defaultValue)
settingsViewModel.setShouldRecreate(true)
}
}
add(HeaderSetting(R.string.app_settings))
@@ -1124,23 +1127,24 @@ class SettingsFragmentPresenter(
}
val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting {
val preferences = PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
override fun getInt(needsGlobal: Boolean): Int =
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
IntSetting.STATIC_THEME_COLOR.getInt(needsGlobal)
override fun setInt(value: Int) {
preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, value) }
IntSetting.STATIC_THEME_COLOR.setInt(value)
settingsViewModel.setShouldRecreate(true)
}
override val key: String = Settings.PREF_STATIC_THEME_COLOR
override val key: String = IntSetting.STATIC_THEME_COLOR.key
override val isRuntimeModifiable: Boolean = true
override fun getValueAsString(needsGlobal: Boolean): String =
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0).toString()
override val defaultValue: Any = 0
IntSetting.STATIC_THEME_COLOR.getValueAsString(needsGlobal)
override val defaultValue: Any = IntSetting.STATIC_THEME_COLOR.defaultValue
override fun reset() {
preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, 0) }
IntSetting.STATIC_THEME_COLOR.reset()
settingsViewModel.setShouldRecreate(true)
}
}
@@ -15,6 +15,7 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Bitmap
import android.net.Uri
import android.os.BatteryManager
import android.os.BatteryManager.*
@@ -97,6 +98,7 @@ import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.ByteBuffer
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.or
@@ -141,6 +143,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var wasInputOverlayAutoHidden = false
private var overlayTouchActive = false
private var pausedFrameBitmap: Bitmap? = null
var shouldUseCustom = false
private var isQuickSettingsMenuOpen = false
@@ -703,6 +706,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
binding.pausedIcon.setOnClickListener {
if (this::emulationState.isInitialized && emulationState.isPaused) {
resumeEmulationFromUi()
}
}
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
val lockMode = IntSetting.LOCK_DRAWER.getInt()
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
@@ -728,11 +737,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
when (it.itemId) {
R.id.menu_pause_emulation -> {
if (emulationState.isPaused) {
emulationState.run(false)
updatePauseMenuEntry(false)
resumeEmulationFromUi()
} else {
emulationState.pause()
updatePauseMenuEntry(true)
pauseEmulationAndCaptureFrame()
}
binding.inGameMenu.requestFocus()
true
@@ -826,6 +833,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
R.id.menu_exit -> {
clearPausedFrame()
emulationState.stop()
NativeConfig.reloadGlobalConfig()
emulationViewModel.setIsEmulationStopping(true)
@@ -1197,6 +1205,71 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
private fun pauseEmulationAndCaptureFrame() {
emulationState.pause()
updatePauseMenuEntry(true)
capturePausedFrameFromCore()
updatePausedFrameVisibility()
}
private fun capturePausedFrameFromCore() {
lifecycleScope.launch(Dispatchers.Default) {
val frameData = NativeLibrary.getAppletCaptureBuffer()
val width = NativeLibrary.getAppletCaptureWidth()
val height = NativeLibrary.getAppletCaptureHeight()
if (frameData.isEmpty() || width <= 0 || height <= 0) {
Log.warning(
"[EmulationFragment] Paused frame capture returned empty/invalid data. " +
"size=${frameData.size}, width=$width, height=$height"
)
return@launch
}
val expectedSize = width * height * 4
if (frameData.size < expectedSize) {
Log.warning(
"[EmulationFragment] Paused frame buffer smaller than expected. " +
"size=${frameData.size}, expected=$expectedSize"
)
return@launch
}
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(frameData, 0, expectedSize))
withContext(Dispatchers.Main) {
pausedFrameBitmap?.recycle()
pausedFrameBitmap = bitmap
updatePausedFrameVisibility()
}
}
}
private fun updatePausedFrameVisibility() {
val b = _binding ?: return
val showPausedUi = this::emulationState.isInitialized && emulationState.isPaused
b.pausedIcon.setVisible(showPausedUi)
val bitmap = if (showPausedUi) pausedFrameBitmap else null
b.pausedFrameImage.setImageBitmap(bitmap)
b.pausedFrameImage.setVisible(bitmap != null)
}
private fun resumeEmulationFromUi() {
clearPausedFrame()
emulationState.resume()
updatePauseMenuEntry(emulationState.isPaused)
updatePausedFrameVisibility()
}
private fun clearPausedFrame() {
val b = _binding
b?.pausedFrameImage?.setVisible(false)
b?.pausedFrameImage?.setImageDrawable(null)
pausedFrameBitmap?.recycle()
pausedFrameBitmap = null
}
private fun handleLoadAmiiboSelection(): Boolean {
val binding = _binding ?: return true
@@ -1290,8 +1363,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onPause() {
if (this::emulationState.isInitialized) {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
updatePauseMenuEntry(true)
pauseEmulationAndCaptureFrame()
} else {
updatePausedFrameVisibility()
}
}
super.onPause()
@@ -1301,6 +1375,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onDestroyView()
amiiboLoadJob?.cancel()
amiiboLoadJob = null
clearPausedFrame()
_binding?.surfaceInputOverlay?.touchEventListener = null
_binding = null
isAmiiboPickerOpen = false
@@ -1321,6 +1396,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
b.inGameMenu.post {
if (!this::emulationState.isInitialized || _binding == null) return@post
updatePauseMenuEntry(emulationState.isPaused)
updatePausedFrameVisibility()
}
}
@@ -1760,6 +1836,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// Only update surface reference, don't trigger state changes
emulationState.updateSurfaceReference(holder.surface)
}
updatePausedFrameVisibility()
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
@@ -2090,6 +2167,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
@Synchronized
fun resume() {
if (state != State.PAUSED) {
Log.warning("[EmulationFragment] Resume called while emulation is not paused.")
return
}
if (!emulationCanStart.invoke()) {
Log.warning("[EmulationFragment] Resume blocked by emulationCanStart check.")
return
}
val currentSurface = surface
if (currentSurface == null || !currentSurface.isValid) {
Log.debug("[EmulationFragment] Resume requested with invalid surface.")
return
}
NativeLibrary.surfaceChanged(currentSurface)
Log.debug("[EmulationFragment] Resuming emulation.")
NativeLibrary.unpauseEmulation()
NativeLibrary.playTimeManagerStart()
state = State.RUNNING
}
@Synchronized
fun changeProgram(programIndex: Int) {
emulationThread.join()
@@ -2111,7 +2211,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@Synchronized
fun updateSurface() {
if (surface != null) {
if (surface != null && state == State.RUNNING) {
NativeLibrary.surfaceChanged(surface)
}
}
@@ -2127,20 +2227,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@Synchronized
fun clearSurface() {
if (surface == null) {
Log.warning("[EmulationFragment] clearSurface called, but surface already null.")
Log.debug("[EmulationFragment] clearSurface called, but surface already null.")
} else {
if (state == State.RUNNING) {
pause()
}
NativeLibrary.surfaceDestroyed()
surface = null
Log.debug("[EmulationFragment] Surface destroyed.")
when (state) {
State.RUNNING -> {
state = State.PAUSED
}
State.PAUSED -> Log.warning(
State.PAUSED -> Log.debug(
"[EmulationFragment] Surface cleared while emulation paused."
)
else -> Log.warning(
else -> Log.debug(
"[EmulationFragment] Surface cleared while emulation stopped."
)
}
@@ -2148,29 +2248,35 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun runWithValidSurface(programIndex: Int = 0) {
NativeLibrary.surfaceChanged(surface)
if (!emulationCanStart.invoke()) {
return
}
val currentSurface = surface
if (currentSurface == null || !currentSurface.isValid) {
Log.debug("[EmulationFragment] runWithValidSurface called with invalid surface.")
return
}
when (state) {
State.STOPPED -> {
NativeLibrary.surfaceChanged(currentSurface)
emulationThread = Thread({
Log.debug("[EmulationFragment] Starting emulation thread.")
NativeLibrary.run(gamePath, programIndex, true)
}, "NativeEmulation")
emulationThread.start()
state = State.RUNNING
}
State.PAUSED -> {
Log.debug("[EmulationFragment] Resuming emulation.")
NativeLibrary.unpauseEmulation()
NativeLibrary.playTimeManagerStart()
Log.debug(
"[EmulationFragment] Surface restored while emulation paused; " +
"waiting for explicit resume."
)
}
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
}
state = State.RUNNING
}
private enum class State {
@@ -127,10 +127,6 @@ class AddonViewModel : ViewModel() {
return
}
// Check if there are multiple update versions
val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update }
val hasMultipleUpdates = updates.size > 1
NativeConfig.setDisabledAddons(
game!!.programId,
_patchList.value.mapNotNull {
@@ -140,7 +136,7 @@ class AddonViewModel : ViewModel() {
if (PatchType.from(it.type) == PatchType.Update) {
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
it.name
} else if (hasMultipleUpdates) {
} else if (it.numericVersion != 0L) {
"Update@${it.numericVersion}"
} else {
it.name
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
@@ -20,7 +20,6 @@ import android.os.Looper
import android.util.AttributeSet
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.SurfaceView
import android.view.View
import android.view.View.OnTouchListener
import android.view.WindowInsets
@@ -42,10 +41,10 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* Draws the interactive input overlay on top of the
* [SurfaceView] that is rendering emulation.
* emulation rendering surface.
*/
class InputOverlay(context: Context, attrs: AttributeSet?) :
SurfaceView(context, attrs),
View(context, attrs),
OnTouchListener {
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
@@ -424,7 +424,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
val uriString = result.toString()
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
val folder = gamesViewModel.folders.value.firstOrNull {
it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT
}
if (folder != null) {
Toast.makeText(
applicationContext,
@@ -61,6 +61,12 @@ object DirectoryInitialization {
saveConfig = true
}
val staticThemeColor = preferences.migratePreference<Int>(Settings.PREF_STATIC_THEME_COLOR)
if (staticThemeColor != null) {
IntSetting.STATIC_THEME_COLOR.setInt(staticThemeColor)
saveConfig = true
}
val blackBackgrounds =
preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
if (blackBackgrounds != null) {
@@ -51,11 +51,24 @@ object GameHelper {
// Scan External Content directories and register all NSP/XCI files
val externalContentDirs = NativeConfig.getExternalContentDirs()
for (externalDir in externalContentDirs) {
val uniqueExternalContentDirs = linkedSetOf<String>()
externalContentDirs.forEach { externalDir ->
if (externalDir.isNotEmpty()) {
uniqueExternalContentDirs.add(externalDir)
}
}
val mountedContainerUris = mutableSetOf<String>()
for (externalDir in uniqueExternalContentDirs) {
if (externalDir.isNotEmpty()) {
val externalDirUri = externalDir.toUri()
if (FileUtil.isTreeUriValid(externalDirUri)) {
scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3)
scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
val containerUri = it.uri.toString()
if (mountedContainerUris.add(containerUri)) {
NativeLibrary.addFileToFilesystemProvider(containerUri)
}
}
}
}
}
@@ -65,10 +78,13 @@ object GameHelper {
val gameDirUri = gameDir.uriString.toUri()
val isValid = FileUtil.isTreeUriValid(gameDirUri)
if (isValid) {
val scanDepth = if (gameDir.deepScan) 3 else 1
addGamesRecursive(
games,
FileUtil.listFiles(gameDirUri),
if (gameDir.deepScan) 3 else 1
scanDepth,
mountedContainerUris
)
} else {
badDirs.add(index)
@@ -103,9 +119,10 @@ object GameHelper {
// be done better imo.
private val externalContentExtensions = setOf("nsp", "xci")
private fun scanExternalContentRecursive(
private fun scanContentContainersRecursive(
files: Array<MinimalDocumentFile>,
depth: Int
depth: Int,
onContainerFound: (MinimalDocumentFile) -> Unit
) {
if (depth <= 0) {
return
@@ -113,14 +130,15 @@ object GameHelper {
files.forEach {
if (it.isDirectory) {
scanExternalContentRecursive(
scanContentContainersRecursive(
FileUtil.listFiles(it.uri),
depth - 1
depth - 1,
onContainerFound
)
} else {
val extension = FileUtil.getExtension(it.uri).lowercase()
if (externalContentExtensions.contains(extension)) {
NativeLibrary.addFileToFilesystemProvider(it.uri.toString())
onContainerFound(it)
}
}
}
@@ -129,7 +147,8 @@ object GameHelper {
private fun addGamesRecursive(
games: MutableList<Game>,
files: Array<MinimalDocumentFile>,
depth: Int
depth: Int,
mountedContainerUris: MutableSet<String>
) {
if (depth <= 0) {
return
@@ -140,11 +159,20 @@ object GameHelper {
addGamesRecursive(
games,
FileUtil.listFiles(it.uri),
depth - 1
depth - 1,
mountedContainerUris
)
} else {
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
val game = getGame(it.uri, true)
val extension = FileUtil.getExtension(it.uri).lowercase()
val filePath = it.uri.toString()
if (externalContentExtensions.contains(extension) &&
mountedContainerUris.add(filePath)) {
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
}
if (Game.extensions.contains(extension)) {
val game = getGame(it.uri, true, false)
if (game != null) {
games.add(game)
}
@@ -153,14 +181,20 @@ object GameHelper {
}
}
fun getGame(uri: Uri, addedToLibrary: Boolean): Game? {
fun getGame(
uri: Uri,
addedToLibrary: Boolean,
registerFilesystemProvider: Boolean = true
): Game? {
val filePath = uri.toString()
if (!GameMetadata.getIsValid(filePath)) {
return null
}
// Needed to update installed content information
NativeLibrary.addFileToFilesystemProvider(filePath)
if (registerFilesystemProvider) {
// Needed to update installed content information
NativeLibrary.addFileToFilesystemProvider(filePath)
}
var name = GameMetadata.getTitle(filePath)
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
@@ -80,16 +80,14 @@ object PathUtil {
}
}
// This really shouldn't be necessary, but the Android API seemingly
// doesn't have a way of doing this?
// Apparently, on certain devices the mount location can vary, so add
// extra cases here if we discover any new ones.
fun getRemovableStoragePath(idString: String): String? {
var pathFile: File
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
pathFile = File("/mnt/media_rw/$idString");
if (pathFile.exists()) {
return pathFile.absolutePath
for (mountPath in possibleMountPaths) {
val pathFile = File(mountPath);
if (pathFile.exists()) {
return pathFile.absolutePath
}
}
return null
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
@@ -52,7 +52,7 @@ object ThemeHelper {
}
private fun getSelectedStaticThemeColor(): Int {
val themeIndex = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false)
val themes = arrayOf(
R.style.Theme_Eden_Main,
R.style.Theme_Yuzu_Main_Violet,
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -11,8 +11,7 @@ import android.graphics.*
import android.util.AttributeSet
import com.google.android.material.card.MaterialCardView
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.features.settings.model.Settings
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
class GradientBorderCardView @JvmOverloads constructor(
context: Context,
@@ -44,12 +43,7 @@ class GradientBorderCardView @JvmOverloads constructor(
}
private fun updateThemeState() {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val themeIndex = try {
prefs.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
} catch (e: Exception) {
0 // Default to Eden theme if error
}
val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false)
isEdenTheme = themeIndex == 0
invalidate()
}
+2 -5
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -27,10 +27,7 @@ if (ARCHITECTURE_arm64)
target_link_libraries(yuzu-android PRIVATE adrenotools)
endif()
if (ENABLE_OPENSSL OR ENABLE_WEB_SERVICE)
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
endif()
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
if (ENABLE_UPDATE_CHECKER)
target_compile_definitions(yuzu-android PUBLIC ENABLE_UPDATE_CHECKER)
endif()
@@ -33,6 +33,12 @@ void AndroidConfig::ReadAndroidValues() {
if (global) {
ReadAndroidUIValues();
ReadUIValues();
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
std::string("ext_content_from_game_dirs"),
std::make_optional(
Settings::values.ext_content_from_game_dirs.GetDefault()));
EndGroup();
ReadOverlayValues();
}
ReadDriverValues();
@@ -56,6 +56,7 @@ namespace AndroidSettings {
Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
Settings::Setting<s32> static_theme_color{linkage, 5, "static_theme_color", Settings::Category::Android};
Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
Settings::Category::Android};
Settings::Setting<s32> app_language{linkage, 0, "app_language", Settings::Category::Android};
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -14,6 +17,14 @@
#include "jni/native.h"
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
if (!surface) {
LOG_INFO(Frontend, "EmuWindow_Android::OnSurfaceChanged received null surface");
m_window_width = 0;
m_window_height = 0;
window_info.render_surface = nullptr;
return;
}
m_window_width = ANativeWindow_getWidth(surface);
m_window_height = ANativeWindow_getHeight(surface);
@@ -96,6 +96,11 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
return false;
}
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
!Loader::IsBootableGameContainer(file, file_type)) {
return false;
}
u64 program_id = 0;
Loader::ResultStatus res = loader->ReadProgramId(program_id);
if (res != Loader::ResultStatus::Success) {
+54 -103
View File
@@ -89,6 +89,8 @@
#include "jni/native.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/capture.h"
#include "video_core/textures/decoders.h"
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/shader_notify.h"
@@ -215,107 +217,8 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
return;
}
const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1));
if (extension == "nsp") {
auto nsp = std::make_shared<FileSys::NSP>(file);
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
std::map<u64, u32> nsp_versions;
std::map<u64, std::string> nsp_version_strings;
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == FileSys::ContentRecordType::Meta) {
const auto meta_nca = std::make_shared<FileSys::NCA>(nca->GetBaseFile());
if (meta_nca->GetStatus() == Loader::ResultStatus::Success) {
const auto section0 = meta_nca->GetSubdirectories();
if (!section0.empty()) {
for (const auto& meta_file : section0[0]->GetFiles()) {
if (meta_file->GetExtension() == "cnmt") {
FileSys::CNMT cnmt(meta_file);
nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
}
}
}
}
}
if (content_type == FileSys::ContentRecordType::Control &&
title_type == FileSys::TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = FileSys::ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
FileSys::NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
nsp_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type == FileSys::TitleType::Update) {
u32 version = 0;
auto ver_it = nsp_versions.find(title_id);
if (ver_it != nsp_versions.end()) {
version = ver_it->second;
}
std::string version_string;
auto str_it = nsp_version_strings.find(title_id);
if (str_it != nsp_version_strings.end()) {
version_string = str_it->second;
}
m_manual_provider->AddEntryWithVersion(
title_type, content_type, title_id, version, version_string,
nca->GetBaseFile());
LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}",
title_id, version, version_string);
} else {
// Use regular AddEntry for non-updates
m_manual_provider->AddEntry(title_type, content_type, title_id,
nca->GetBaseFile());
LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}",
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
}
}
}
return;
}
}
// Handle XCI files
if (extension == "xci") {
FileSys::XCI xci{file};
if (xci.GetStatus() == Loader::ResultStatus::Success) {
const auto nsp = xci.GetSecurePartitionNSP();
if (nsp) {
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
return;
}
if (m_manual_provider->AddEntriesFromContainer(file)) {
return;
}
auto loader = Loader::GetLoader(m_system, file);
@@ -337,6 +240,13 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
}
}
void EmulationSession::ConfigureFilesystemProviderFromGameFolder(const std::string& filepath) {
if (!Settings::values.ext_content_from_game_dirs.GetValue()) {
return;
}
ConfigureFilesystemProvider(filepath);
}
void EmulationSession::InitializeSystem(bool reload) {
if (!reload) {
// Initialize logging system
@@ -780,9 +690,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject i
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
if (auto* native_window = EmulationSession::GetInstance().NativeWindow(); native_window) {
ANativeWindow_release(native_window);
}
EmulationSession::GetInstance().SetNativeWindow(nullptr);
EmulationSession::GetInstance().SurfaceChanged();
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
@@ -969,6 +880,40 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
}
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) {
using namespace VideoCore::Capture;
if (!EmulationSession::GetInstance().IsRunning()) {
return env->NewByteArray(0);
}
const auto tiled = EmulationSession::GetInstance().System().GPU().GetAppletCaptureBuffer();
if (tiled.size() < TiledSize) {
return env->NewByteArray(0);
}
std::vector<u8> linear(LinearWidth * LinearHeight * BytesPerPixel);
Tegra::Texture::UnswizzleTexture(linear, tiled, BytesPerPixel, LinearWidth, LinearHeight,
LinearDepth, BlockHeight, BlockDepth);
auto buffer = env->NewByteArray(static_cast<jsize>(linear.size()));
if (!buffer) {
return env->NewByteArray(0);
}
env->SetByteArrayRegion(buffer, 0, static_cast<jsize>(linear.size()),
reinterpret_cast<const jbyte*>(linear.data()));
return buffer;
}
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureWidth(JNIEnv* env, jclass clazz) {
return static_cast<jint>(VideoCore::Capture::LinearWidth);
}
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureHeight(JNIEnv* env, jclass clazz) {
return static_cast<jint>(VideoCore::Capture::LinearHeight);
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
jboolean reload) {
// Initialize the emulated system.
@@ -1572,6 +1517,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
Common::Android::GetJString(env, jpath));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_addGameFolderFileToFilesystemProvider(
JNIEnv* env, jobject jobj, jstring jpath) {
EmulationSession::GetInstance().ConfigureFilesystemProviderFromGameFolder(
Common::Android::GetJString(env, jpath));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
}
+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 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -46,6 +49,7 @@ public:
const Core::PerfStatsResults& PerfStats();
int ShadersBuilding();
void ConfigureFilesystemProvider(const std::string& filepath);
void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath);
void InitializeSystem(bool reload);
void SetAppletId(int applet_id);
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#E6FFFFFF" />
</shape>
@@ -108,6 +108,22 @@
</FrameLayout>
<FrameLayout
android:id="@+id/paused_frame_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false">
<ImageView
android:id="@+id/paused_frame_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null"
android:scaleType="fitCenter"
android:visibility="gone" />
</FrameLayout>
<FrameLayout
android:id="@+id/input_container"
android:layout_width="match_parent"
@@ -142,6 +158,18 @@
android:layout_height="match_parent"
android:fitsSystemWindows="false">
<ImageView
android:id="@+id/paused_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
android:background="@drawable/circle_white"
android:contentDescription="@string/emulation_unpause"
android:padding="14dp"
android:src="@drawable/ic_play"
android:visibility="gone"
app:tint="@android:color/black" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/show_stats_overlay_text"
style="@style/TextAppearance.Material3.BodySmall"
@@ -1222,12 +1222,12 @@
<!-- Static Themes -->
<string name="static_theme_color">Theme Color</string>
<string name="eden_theme">Eden (Default)</string>
<string name="eden_theme">Eden</string>
<string name="violet">Violet</string>
<string name="blue">Blue</string>
<string name="cyan">Cyan</string>
<string name="red">Red</string>
<string name="green">Green</string>
<string name="green">Green (Default)</string>
<string name="yellow">Yellow</string>
<string name="orange">Orange</string>
<string name="pink">Pink</string>
+5
View File
@@ -557,6 +557,9 @@ struct Values {
SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects",
Category::RendererHacks};
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
Category::RendererHacks};
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks};
@@ -753,6 +756,8 @@ struct Values {
Category::DataStorage};
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
Category::DataStorage};
Setting<bool> ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs",
Category::DataStorage};
std::vector<std::string> external_content_dirs;
// Debugging
-1
View File
@@ -342,7 +342,6 @@ add_library(core STATIC
hle/kernel/message_buffer.h
hle/kernel/physical_core.cpp
hle/kernel/physical_core.h
hle/kernel/physical_memory.h
hle/kernel/slab_helpers.h
hle/kernel/svc.cpp
hle/kernel/svc.h
+3 -6
View File
@@ -4,6 +4,7 @@
#include <numeric>
#include <bit>
#include "common/arm64/native_clock.h"
#include "common/alignment.h"
#include "common/literals.h"
#include "core/arm/nce/arm_nce.h"
#include "core/arm/nce/guest_context.h"
@@ -46,8 +47,7 @@ Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) {
Patcher::~Patcher() = default;
bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code) {
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
const size_t image_size = program_image.size();
@@ -175,10 +175,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
return true;
}
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image,
EntryTrampolines* out_trampolines) {
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines) {
const size_t patch_size = GetSectionSize();
const size_t pre_patch_size = GetPreSectionSize();
+2 -5
View File
@@ -14,7 +14,6 @@
#include "common/settings.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/physical_memory.h"
#include <utility>
using ModuleID = std::array<u8, 32>; // NSO build ID
struct PatchCacheKey {
@@ -56,10 +55,8 @@ public:
}
explicit Patcher();
~Patcher();
bool PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code);
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
bool PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code);
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines);
size_t GetSectionSize() const noexcept;
size_t GetPreSectionSize() const noexcept;
+4 -11
View File
@@ -38,7 +38,6 @@ struct CipherContext {
static inline const std::string GetCipherName(Mode mode, u32 key_size) {
std::string cipher;
std::size_t effective_bits = key_size * 8;
switch (mode) {
case Mode::CTR:
cipher = "CTR";
@@ -53,7 +52,6 @@ static inline const std::string GetCipherName(Mode mode, u32 key_size) {
default:
UNREACHABLE();
}
return fmt::format("AES-{}-{}", effective_bits, cipher);
};
@@ -87,8 +85,7 @@ static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) {
// TODO: WHY TEMPLATE???????
template <typename Key, std::size_t KeySize>
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
: ctx(std::make_unique<CipherContext>()) {
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) : ctx(std::make_unique<CipherContext>()) {
ctx->encryption_context = EVP_CIPHER_CTX_new();
ctx->decryption_context = EVP_CIPHER_CTX_new();
@@ -99,9 +96,7 @@ Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
UNIMPLEMENTED();
}
ASSERT_MSG(ctx->encryption_context && ctx->decryption_context && ctx->cipher,
"OpenSSL cipher context failed init!");
ASSERT(ctx->encryption_context && ctx->decryption_context && ctx->cipher && "OpenSSL cipher context failed init!");
// now init ciphers
ASSERT(EVP_CipherInit_ex2(ctx->encryption_context, ctx->cipher, key.data(), NULL, 1, NULL));
ASSERT(EVP_CipherInit_ex2(ctx->decryption_context, ctx->cipher, key.data(), NULL, 0, NULL));
@@ -165,8 +160,7 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* des
template <typename Key, std::size_t KeySize>
void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8* dest,
std::size_t sector_id, std::size_t sector_size, Op op) {
ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size.");
ASSERT(size % sector_size == 0 && "XTS decryption size must be a multiple of sector size.");
for (std::size_t i = 0; i < size; i += sector_size) {
SetIV(CalculateNintendoTweak(sector_id++));
Transcode(src + i, sector_size, dest + i, op);
@@ -177,8 +171,7 @@ template <typename Key, std::size_t KeySize>
void AESCipher<Key, KeySize>::SetIV(std::span<const u8> data) {
const int ret_enc = EVP_CipherInit_ex(ctx->encryption_context, nullptr, nullptr, nullptr, data.data(), -1);
const int ret_dec = EVP_CipherInit_ex(ctx->decryption_context, nullptr, nullptr, nullptr, data.data(), -1);
ASSERT_MSG(ret_enc == 1 && ret_dec == 1, "Failed to set IV on OpenSSL contexts");
ASSERT(ret_enc == 1 && ret_dec == 1 && "Failed to set IV on OpenSSL contexts");
}
template class AESCipher<Key128>;
-4
View File
@@ -1,4 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace Crypto {} // namespace Crypto
-19
View File
@@ -1,19 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/assert.h"
#include "core/file_sys/vfs.h"
#include "key_manager.h"
#include "mbedtls/cipher.h"
namespace Crypto {
typedef std::array<u8, 0x20> SHA256Hash;
inline SHA256Hash operator"" _HASH(const char* data, size_t len) {
if (len != 0x40)
return {};
}
} // namespace Crypto
+6 -3
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -160,15 +163,15 @@ u32 KIP::GetMainThreadCpuCore() const {
return header.default_core;
}
const std::vector<u8>& KIP::GetTextSection() const {
std::span<const u8> KIP::GetTextSection() const {
return decompressed_sections[0];
}
const std::vector<u8>& KIP::GetRODataSection() const {
std::span<const u8> KIP::GetRODataSection() const {
return decompressed_sections[1];
}
const std::vector<u8>& KIP::GetDataSection() const {
std::span<const u8> KIP::GetDataSection() const {
return decompressed_sections[2];
}
+7 -3
View File
@@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include <string>
#include <vector>
@@ -70,9 +74,9 @@ public:
u32 GetMainThreadStackSize() const;
u32 GetMainThreadCpuCore() const;
const std::vector<u8>& GetTextSection() const;
const std::vector<u8>& GetRODataSection() const;
const std::vector<u8>& GetDataSection() const;
std::span<const u8> GetTextSection() const;
std::span<const u8> GetRODataSection() const;
std::span<const u8> GetDataSection() const;
u32 GetTextOffset() const;
u32 GetRODataOffset() const;
+14 -13
View File
@@ -117,6 +117,12 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
bool IsVersionedExternalUpdateDisabled(const std::vector<std::string>& disabled, u32 version) {
const std::string disabled_key = fmt::format("Update@{}", version);
return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() ||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
}
} // Anonymous namespace
PatchManager::PatchManager(u64 title_id_,
@@ -155,8 +161,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
break;
@@ -175,8 +180,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
break;
@@ -580,8 +584,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
@@ -600,8 +603,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
@@ -704,9 +706,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
version_str = FormatTitleVersion(update_entry.version);
}
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
@@ -732,9 +733,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
version_str = FormatTitleVersion(update_entry.version);
}
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled,
@@ -771,7 +771,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
for (const auto& [slot, entry] : all_updates) {
if (slot == ContentProviderUnionSlot::External) {
if (slot == ContentProviderUnionSlot::External ||
slot == ContentProviderUnionSlot::FrontendManual) {
continue;
}
+226 -247
View File
@@ -104,6 +104,206 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
}
static std::shared_ptr<NSP> OpenContainerAsNsp(const VirtualFile& file, Loader::FileType type) {
if (!file) {
return nullptr;
}
if (type == Loader::FileType::Unknown || type == Loader::FileType::Error) {
type = Loader::IdentifyFile(file);
if (type == Loader::FileType::Unknown) {
type = Loader::GuessFromFilename(file->GetName());
}
}
if (type == Loader::FileType::NSP) {
auto nsp = std::make_shared<NSP>(file);
return nsp->GetStatus() == Loader::ResultStatus::Success ? nsp : nullptr;
}
if (type == Loader::FileType::XCI) {
XCI xci(file);
if (xci.GetStatus() != Loader::ResultStatus::Success) {
return nullptr;
}
auto secure_partition = xci.GetSecurePartitionNSP();
if (secure_partition == nullptr) {
return nullptr;
}
return secure_partition;
}
// SAF-backed files can occasionally fail type-guessing despite being valid NSP/XCI.
// As a last resort, probe both container parsers directly.
{
auto nsp = std::make_shared<NSP>(file);
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
return nsp;
}
}
{
XCI xci(file);
if (xci.GetStatus() == Loader::ResultStatus::Success) {
auto secure_partition = xci.GetSecurePartitionNSP();
if (secure_partition != nullptr) {
return secure_partition;
}
}
}
return nullptr;
}
template <typename Callback>
bool ForEachContainerEntry(const std::shared_ptr<NSP>& nsp, bool only_content,
std::optional<u64> base_program_id, Callback&& on_entry) {
if (!nsp) {
return false;
}
const auto& ncas = nsp->GetNCAs();
if (ncas.empty()) {
return false;
}
std::map<u64, u32> versions;
std::map<u64, std::string> version_strings;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
if (!nca) {
continue;
}
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
for (const auto& inner_file : subdirs[0]->GetFiles()) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
break;
}
}
}
}
if (title_type == TitleType::Update && content_type == ContentRecordType::Control) {
const auto romfs = nca->GetRomFS();
if (!romfs) {
continue;
}
const auto extracted = ExtractRomFS(romfs);
if (!extracted) {
continue;
}
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (!nacp_file) {
continue;
}
const NACP nacp(nacp_file);
auto version_string = nacp.GetVersionString();
if (!version_string.empty()) {
version_strings[title_id] = std::move(version_string);
}
}
}
}
bool added_entries = false;
for (const auto& [title_id, nca_map] : ncas) {
if (base_program_id.has_value() && GetBaseTitleID(title_id) != *base_program_id) {
continue;
}
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (only_content && title_type != TitleType::Update && title_type != TitleType::AOC) {
continue;
}
auto entry_file = nca ? nca->GetBaseFile() : nullptr;
if (!entry_file) {
continue;
}
u32 version = 0;
std::string version_string;
if (title_type == TitleType::Update) {
if (const auto version_it = versions.find(title_id); version_it != versions.end()) {
version = version_it->second;
}
if (const auto version_str_it = version_strings.find(title_id);
version_str_it != version_strings.end()) {
version_string = version_str_it->second;
}
}
on_entry(title_type, content_type, title_id, entry_file, version, version_string);
added_entries = true;
}
}
return added_entries;
}
static void UpsertExternalVersionEntry(std::vector<ExternalUpdateEntry>& multi_version_entries,
u64 title_id, u32 version,
const std::string& version_string,
ContentRecordType content_type, const VirtualFile& file) {
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(),
[title_id, version](const ExternalUpdateEntry& entry) {
return entry.title_id == title_id && entry.version == version;
});
if (it == multi_version_entries.end()) {
ExternalUpdateEntry update_entry;
update_entry.title_id = title_id;
update_entry.version = version;
update_entry.version_string = version_string;
update_entry.files[static_cast<std::size_t>(content_type)] = file;
multi_version_entries.push_back(std::move(update_entry));
return;
}
it->files[static_cast<std::size_t>(content_type)] = file;
if (it->version_string.empty() && !version_string.empty()) {
it->version_string = version_string;
}
}
template <typename EntryMap, typename VersionMap>
static bool AddExternalEntriesFromContainer(const std::shared_ptr<NSP>& nsp, EntryMap& entries,
VersionMap& versions,
std::vector<ExternalUpdateEntry>& multi_version_entries) {
return ForEachContainerEntry(
nsp, true, std::nullopt,
[&entries, &versions,
&multi_version_entries](TitleType title_type, ContentRecordType content_type, u64 title_id,
const VirtualFile& file, u32 version,
const std::string& version_string) {
entries[{title_id, content_type, title_type}] = file;
if (title_type == TitleType::Update) {
versions[title_id] = version;
UpsertExternalVersionEntry(multi_version_entries, title_id, version, version_string,
content_type, file);
}
});
}
ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
switch (type) {
case NCAContentType::Program:
@@ -1008,6 +1208,26 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec
}
}
bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_content,
std::optional<u64> base_program_id) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::Unknown);
if (!nsp) {
return false;
}
return ForEachContainerEntry(
nsp, only_content, base_program_id,
[this](TitleType title_type, ContentRecordType content_type, u64 title_id,
const VirtualFile& entry_file, u32 version, const std::string& version_string) {
if (title_type == TitleType::Update) {
AddEntryWithVersion(title_type, content_type, title_id, version, version_string,
entry_file);
} else {
AddEntry(title_type, content_type, title_id, entry_file);
}
});
}
void ManualContentProvider::ClearAllEntries() {
entries.clear();
multi_version_entries.clear();
@@ -1091,14 +1311,6 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor
return nullptr;
}
bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.files[size_t(type)])
++count;
return count > 0;
}
ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
: load_dirs(std::move(load_directories)) {
ExternalContentProvider::Refresh();
@@ -1159,247 +1371,22 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
}
void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
auto nsp = NSP(file);
if (nsp.GetStatus() != Loader::ResultStatus::Success) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP);
if (!nsp) {
return;
}
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName());
const auto ncas = nsp.GetNCAs();
std::map<u64, u32> nsp_versions;
std::map<u64, std::string> nsp_version_strings; // title_id -> NACP version string
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
const auto section0 = subdirs[0];
const auto files = section0->GetFiles();
for (const auto& inner_file : files) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
const auto cnmt_title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
nsp_versions[cnmt_title_id] = version;
versions[cnmt_title_id] = version;
break;
}
}
}
}
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
nsp_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
continue;
}
auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type);
if (nca_file != nullptr) {
entries[{title_id, content_type, title_type}] = nca_file;
if (title_type == TitleType::Update) {
u32 version = 0;
auto ver_it = nsp_versions.find(title_id);
if (ver_it != nsp_versions.end()) {
version = ver_it->second;
}
version_files[{title_id, version}][size_t(content_type)] = nca_file;
}
LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}",
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
}
}
}
for (const auto& [key, files_map] : version_files) {
const auto& [title_id, version] = key;
std::string ver_str;
auto str_it = nsp_version_strings.find(title_id);
if (str_it != nsp_version_strings.end()) {
ver_str = str_it->second;
}
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
existing.files = files_map;
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
version_exists = true;
break;
}
}
if (!version_exists && !files_map.empty()) {
ExternalUpdateEntry update_entry{
.title_id = title_id,
.version = version,
.version_string = ver_str,
.files = files_map
};
multi_version_entries.push_back(update_entry);
LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
title_id, version, ver_str, files_map.size());
}
}
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
}
void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
auto xci = XCI(file);
if (xci.GetStatus() != Loader::ResultStatus::Success) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI);
if (!nsp) {
return;
}
auto nsp = xci.GetSecurePartitionNSP();
if (nsp == nullptr) {
return;
}
const auto ncas = nsp->GetNCAs();
std::map<u64, u32> xci_versions;
std::map<u64, std::string> xci_version_strings;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
const auto section0 = subdirs[0];
const auto files = section0->GetFiles();
for (const auto& inner_file : files) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
const auto cnmt_title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
xci_versions[cnmt_title_id] = version;
versions[cnmt_title_id] = version;
break;
}
}
}
}
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
xci_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
continue;
}
auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type);
if (nca_file != nullptr) {
entries[{title_id, content_type, title_type}] = nca_file;
if (title_type == TitleType::Update) {
u32 version = 0;
auto ver_it = xci_versions.find(title_id);
if (ver_it != xci_versions.end()) {
version = ver_it->second;
}
version_files[{title_id, version}][size_t(content_type)] = nca_file;
}
}
}
}
for (const auto& [key, files_map] : version_files) {
const auto& [title_id, version] = key;
std::string ver_str;
auto str_it = xci_version_strings.find(title_id);
if (str_it != xci_version_strings.end()) {
ver_str = str_it->second;
}
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
existing.files = files_map;
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
version_exists = true;
break;
}
}
if (!version_exists && !files_map.empty()) {
ExternalUpdateEntry update_entry{
.title_id = title_id,
.version = version,
.version_string = ver_str,
.files = files_map
};
multi_version_entries.push_back(update_entry);
LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
title_id, version, ver_str, files_map.size());
}
}
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
}
bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
@@ -1491,12 +1478,4 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec
return nullptr;
}
bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.files[size_t(type)])
++count;
return count > 1;
}
} // namespace FileSys
+3 -2
View File
@@ -9,6 +9,7 @@
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <ankerl/unordered_dense.h>
@@ -262,6 +263,8 @@ public:
VirtualFile file);
void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id,
u32 version, const std::string& version_string, VirtualFile file);
bool AddEntriesFromContainer(VirtualFile file, bool only_content = false,
std::optional<u64> base_program_id = std::nullopt);
void ClearAllEntries();
void Refresh() override;
@@ -276,7 +279,6 @@ public:
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private:
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
@@ -303,7 +305,6 @@ public:
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private:
void ScanDirectory(const VirtualDir& dir);
+2 -3
View File
@@ -7,9 +7,9 @@
#pragma once
#include <cstddef>
#include <vector>
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/physical_memory.h"
namespace Kernel {
@@ -97,8 +97,7 @@ struct CodeSet final {
#endif
/// The overall data that backs this code set.
Kernel::PhysicalMemory memory;
std::vector<u8> memory;
/// The segments that comprise this code set.
std::array<Segment, 3> segments;
-23
View File
@@ -1,23 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include "common/alignment.h"
namespace Kernel {
// This encapsulation serves 2 purposes:
// - First, to encapsulate host physical memory under a single type and set an
// standard for managing it.
// - Second to ensure all host backing memory used is aligned to 256 bytes due
// to strict alignment restrictions on GPU memory.
using PhysicalMemoryVector = std::vector<u8, Common::AlignmentAllocator<u8, 256>>;
class PhysicalMemory final : public PhysicalMemoryVector {
using PhysicalMemoryVector::PhysicalMemoryVector;
};
} // namespace Kernel
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -93,7 +96,7 @@ union Error::ErrorArguments {
namespace {
template <typename T>
void CopyArgumentData(const std::vector<u8>& data, T& variable) {
void CopyArgumentData(std::span<const u8> data, T& variable) {
ASSERT(data.size() >= sizeof(T));
std::memcpy(&variable, data.data(), sizeof(T));
}
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -775,7 +778,7 @@ Result SoftwareKeyboard::RequestExit() {
// Inline Software Keyboard Requests
void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestFinalize(std::span<const u8> request_data) {
LOG_DEBUG(Service_AM, "Processing Request: Finalize");
ChangeState(SwkbdState::NotInitialized);
@@ -783,17 +786,17 @@ void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) {
ExitKeyboard();
}
void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetUserWordInfo(std::span<const u8> request_data) {
LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented.");
ReplyReleasedUserWordInfo();
}
void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetCustomizeDic(std::span<const u8> request_data) {
LOG_WARNING(Service_AM, "SetCustomizeDic is not implemented.");
}
void SoftwareKeyboard::RequestCalc(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestCalc(std::span<const u8> request_data) {
LOG_DEBUG(Service_AM, "Processing Request: Calc");
ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon));
@@ -930,17 +933,17 @@ void SoftwareKeyboard::RequestCalcNew() {
}
}
void SoftwareKeyboard::RequestSetCustomizedDictionaries(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetCustomizedDictionaries(std::span<const u8> request_data) {
LOG_WARNING(Service_AM, "SetCustomizedDictionaries is not implemented.");
}
void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(std::span<const u8> request_data) {
LOG_WARNING(Service_AM, "(STUBBED) Processing Request: UnsetCustomizedDictionaries");
ReplyUnsetCustomizedDictionaries();
}
void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetChangedStringV2Flag(std::span<const u8> request_data) {
LOG_DEBUG(Service_AM, "Processing Request: SetChangedStringV2Flag");
ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1);
@@ -948,7 +951,7 @@ void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector<u8>& requ
std::memcpy(&use_changed_string_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1);
}
void SoftwareKeyboard::RequestSetMovedCursorV2Flag(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetMovedCursorV2Flag(std::span<const u8> request_data) {
LOG_DEBUG(Service_AM, "Processing Request: SetMovedCursorV2Flag");
ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -124,16 +127,16 @@ private:
// Inline Software Keyboard Requests
void RequestFinalize(const std::vector<u8>& request_data);
void RequestSetUserWordInfo(const std::vector<u8>& request_data);
void RequestSetCustomizeDic(const std::vector<u8>& request_data);
void RequestCalc(const std::vector<u8>& request_data);
void RequestFinalize(std::span<const u8> request_data);
void RequestSetUserWordInfo(std::span<const u8> request_data);
void RequestSetCustomizeDic(std::span<const u8> request_data);
void RequestCalc(std::span<const u8> request_data);
void RequestCalcOld();
void RequestCalcNew();
void RequestSetCustomizedDictionaries(const std::vector<u8>& request_data);
void RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data);
void RequestSetChangedStringV2Flag(const std::vector<u8>& request_data);
void RequestSetMovedCursorV2Flag(const std::vector<u8>& request_data);
void RequestSetCustomizedDictionaries(std::span<const u8> request_data);
void RequestUnsetCustomizedDictionaries(std::span<const u8> request_data);
void RequestSetChangedStringV2Flag(std::span<const u8> request_data);
void RequestSetMovedCursorV2Flag(std::span<const u8> request_data);
// Inline Software Keyboard Replies
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -34,22 +34,21 @@ namespace Service::AM::Frontend {
namespace {
template <typename T>
void ParseRawValue(T& value, const std::vector<u8>& data) {
void ParseRawValue(T& value, std::span<const u8> data) {
static_assert(std::is_trivially_copyable_v<T>,
"It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(&value, data.data(), data.size());
}
template <typename T>
T ParseRawValue(const std::vector<u8>& data) {
T ParseRawValue(std::span<const u8> data) {
T value;
ParseRawValue(value, data);
return value;
}
std::string ParseStringValue(const std::vector<u8>& data) {
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
data.size());
std::string ParseStringValue(std::span<const u8> data) {
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()), data.size());
}
std::string GetMainURL(const std::string& url) {
@@ -72,7 +71,7 @@ std::string ResolveURL(const std::string& url) {
return url.substr(0, index) + "lp1" + url.substr(index + 1);
}
WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
WebArgInputTLVMap ReadWebArgs(std::span<const u8> web_arg, WebArgHeader& web_arg_header) {
std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
if (web_arg.size() == sizeof(WebArgHeader)) {
+14 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -36,8 +36,8 @@ IAudioDevice::IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u
{16, nullptr, "ReleaseAudioInputDeviceNotification"}, //17.0.0+
{17, nullptr, "AcquireAudioOutputDeviceNotification"}, //17.0.0+
{18, nullptr, "ReleaseAudioOutputDeviceNotification"}, //17.0.0+
{19, nullptr, "SetAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
{20, nullptr, "IsAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
{19, D<&IAudioDevice::SetAudioDeviceOutputVolumeAutoTuneEnabled>, "SetAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
{20, D<&IAudioDevice::IsAudioDeviceOutputVolumeAutoTuneEnabled>, "IsAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
{21, nullptr, "IsActiveOutputDeviceEstimatedLowLatency"} //21.0.0+
};
RegisterHandlers(functions);
@@ -170,4 +170,15 @@ Result IAudioDevice::ListAudioOutputDeviceName(
R_SUCCEED();
}
Result IAudioDevice::SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
R_SUCCEED();
}
Result IAudioDevice::IsAudioDeviceOutputVolumeAutoTuneEnabled(Out<bool> out_enabled) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
*out_enabled = false;
R_SUCCEED();
}
} // namespace Service::Audio
@@ -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-License-Identifier: GPL-2.0-or-later
@@ -49,6 +52,8 @@ private:
Result ListAudioOutputDeviceName(
OutArray<AudioDevice::AudioDeviceName, BufferAttr_HipcMapAlias> out_names,
Out<s32> out_count);
Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled);
Result IsAudioDeviceOutputVolumeAutoTuneEnabled(Out<bool> out_enabled);
KernelHelpers::ServiceContext service_context;
std::unique_ptr<AudioCore::Renderer::AudioDevice> impl;
+5 -5
View File
@@ -164,7 +164,7 @@ private:
rb.Push(ResultSuccess);
}
u64 ReadLeb128(const std::vector<u8>& data, std::size_t& offset) {
u64 ReadLeb128(std::span<const u8> data, std::size_t& offset) {
u64 result{};
u32 shift{};
@@ -180,7 +180,7 @@ private:
return result;
}
std::optional<std::string> ReadString(const std::vector<u8>& data, std::size_t& offset,
std::optional<std::string> ReadString(std::span<const u8> data, std::size_t& offset,
std::size_t length) {
if (length == 0) {
return std::nullopt;
@@ -193,7 +193,7 @@ private:
return output;
}
u32_le ReadAsU32(const std::vector<u8>& data, std::size_t& offset, std::size_t length) {
u32_le ReadAsU32(std::span<const u8> data, std::size_t& offset, std::size_t length) {
ASSERT(length == sizeof(u32));
u32_le output{};
std::memcpy(&output, data.data() + offset, sizeof(u32));
@@ -201,7 +201,7 @@ private:
return output;
}
u64_le ReadAsU64(const std::vector<u8>& data, std::size_t& offset, std::size_t length) {
u64_le ReadAsU64(std::span<const u8> data, std::size_t& offset, std::size_t length) {
ASSERT(length == sizeof(u64));
u64_le output{};
std::memcpy(&output, data.data() + offset, sizeof(u64));
@@ -209,7 +209,7 @@ private:
return output;
}
void ParseLog(const LogPacketHeaderEntry entry, const std::vector<u8>& log_data) {
void ParseLog(const LogPacketHeaderEntry entry, std::span<const u8> log_data) {
// Possible entries
std::optional<std::string> text_log;
std::optional<u32> line_number;
@@ -67,7 +67,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{44, D<&IApplicationManagerInterface::GetSdCardMountStatusChangedEvent>, "GetSdCardMountStatusChangedEvent"},
{45, nullptr, "GetGameCardAttachmentEvent"},
{46, nullptr, "GetGameCardAttachmentInfo"},
{47, nullptr, "GetTotalSpaceSize"},
{47, D<&IApplicationManagerInterface::GetTotalSpaceSize>, "GetTotalSpaceSize"},
{48, D<&IApplicationManagerInterface::GetFreeSpaceSize>, "GetFreeSpaceSize"},
{49, nullptr, "GetSdCardRemovedEvent"},
{52, D<&IApplicationManagerInterface::GetGameCardUpdateDetectionEvent>, "GetGameCardUpdateDetectionEvent"},
@@ -751,8 +751,12 @@ Result IApplicationManagerInterface::GetSdCardMountStatusChangedEvent(
R_SUCCEED();
}
Result IApplicationManagerInterface::GetFreeSpaceSize(Out<s64> out_free_space_size,
FileSys::StorageId storage_id) {
Result IApplicationManagerInterface::GetTotalSpaceSize(Out<s64> out_total_space_size, FileSys::StorageId storage_id) {
LOG_DEBUG(Service_NS, "called");
R_RETURN(IContentManagementInterface(system).GetTotalSpaceSize(out_total_space_size, storage_id));
}
Result IApplicationManagerInterface::GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id) {
LOG_DEBUG(Service_NS, "called");
R_RETURN(IContentManagementInterface(system).GetFreeSpaceSize(out_free_space_size, storage_id));
}
@@ -50,6 +50,7 @@ public:
u32 flags, u64 application_id, Uid account_id);
Result CheckSdCardMountStatus();
Result GetSdCardMountStatusChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetTotalSpaceSize(Out<s64> out_total_space_size, FileSys::StorageId storage_id);
Result GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id);
Result GetGameCardUpdateDetectionEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result ResumeAll();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -21,7 +21,6 @@
#include "core/file_sys/system_archive/system_archive.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_memory.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/platform_service_manager.h"
+12 -16
View File
@@ -176,11 +176,9 @@ struct ProcessContext {
// Calculate hash.
Sha256Hash hash;
{
const u64 size = nro_header->GetSize();
const u64 size = nro_header->m_size;
std::vector<u8> nro_data(size);
m_process->GetMemory().ReadBlock(base_address, nro_data.data(), size);
u32 hash_len = 0;
EVP_Digest(nro_data.data(), nro_data.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
}
@@ -204,9 +202,7 @@ struct ProcessContext {
R_THROW(RO::ResultNotAuthorized);
}
Result ValidateNro(ModuleId* out_module_id, u64* out_rx_size, u64* out_ro_size,
u64* out_rw_size, u64 base_address, u64 expected_nro_size,
u64 expected_bss_size) {
Result ValidateNro(ModuleId* out_module_id, u64* out_rx_size, u64* out_ro_size, u64* out_rw_size, u64 base_address, u64 expected_nro_size, u64 expected_bss_size) {
// Ensure we have a process to work on.
R_UNLESS(m_process != nullptr, RO::ResultInvalidProcess);
@@ -215,17 +211,17 @@ struct ProcessContext {
m_process->GetMemory().ReadBlock(base_address, std::addressof(header), sizeof(header));
// Validate header.
R_UNLESS(header.IsMagicValid(), RO::ResultInvalidNro);
R_UNLESS(header.m_magic == NRO_HEADER_MAGIC, RO::ResultInvalidNro);
// Read sizes from header.
const u64 nro_size = header.GetSize();
const u64 text_ofs = header.GetTextOffset();
const u64 text_size = header.GetTextSize();
const u64 ro_ofs = header.GetRoOffset();
const u64 ro_size = header.GetRoSize();
const u64 rw_ofs = header.GetRwOffset();
const u64 rw_size = header.GetRwSize();
const u64 bss_size = header.GetBssSize();
const u64 nro_size = header.m_size;
const u64 text_ofs = header.m_text_offset;
const u64 text_size = header.m_text_size;
const u64 ro_ofs = header.m_ro_offset;
const u64 ro_size = header.m_ro_size;
const u64 rw_ofs = header.m_rw_offset;
const u64 rw_size = header.m_rw_size;
const u64 bss_size = header.m_bss_size;
// Validate sizes meet expected.
R_UNLESS(nro_size == expected_nro_size, RO::ResultInvalidNro);
@@ -251,7 +247,7 @@ struct ProcessContext {
R_TRY(this->ValidateHasNroHash(base_address, std::addressof(header)));
// Check if NRO has already been loaded.
const ModuleId* module_id = header.GetModuleId();
const ModuleId* module_id = std::addressof(header.m_module_id);
R_UNLESS(R_FAILED(this->GetNroInfoByModuleId(nullptr, module_id)), RO::ResultAlreadyLoaded);
// Apply patches to NRO.
+5 -46
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -112,52 +115,8 @@ private:
};
static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader has wrong size");
class NroHeader {
public:
static constexpr u32 Magic = Common::MakeMagic('N', 'R', 'O', '0');
public:
bool IsMagicValid() const {
return m_magic == Magic;
}
u32 GetSize() const {
return m_size;
}
u32 GetTextOffset() const {
return m_text_offset;
}
u32 GetTextSize() const {
return m_text_size;
}
u32 GetRoOffset() const {
return m_ro_offset;
}
u32 GetRoSize() const {
return m_ro_size;
}
u32 GetRwOffset() const {
return m_rw_offset;
}
u32 GetRwSize() const {
return m_rw_size;
}
u32 GetBssSize() const {
return m_bss_size;
}
const ModuleId* GetModuleId() const {
return std::addressof(m_module_id);
}
private:
static constexpr u32 NRO_HEADER_MAGIC = Common::MakeMagic('N', 'R', 'O', '0');
struct NroHeader {
u32 m_entrypoint_insn;
u32 m_mod_offset;
INSERT_PADDING_BYTES_NOINIT(0x8);
+5 -14
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -75,21 +75,16 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
kip->GetKernelCapabilities());
Kernel::CodeSet codeset;
Kernel::PhysicalMemory program_image;
const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment, const std::vector<u8>& data, u32 offset) {
codeset.memory.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
const auto load_segment = [&codeset](Kernel::CodeSet::Segment& segment, std::span<const u8> data, u32 offset) {
segment.addr = offset;
segment.offset = offset;
segment.size = PageAlignSize(u32(data.size()));
program_image.resize(offset + data.size());
std::memcpy(program_image.data() + offset, data.data(), data.size());
std::memcpy(codeset.memory.data() + offset, data.data(), data.size());
};
load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset());
load_segment(codeset.RODataSegment(), kip->GetRODataSection(), kip->GetRODataOffset());
load_segment(codeset.DataSegment(), kip->GetDataSection(), kip->GetDataOffset());
program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
codeset.DataSegment().size += kip->GetBSSSize();
// TODO: this is bad form of ASLR, it sucks
@@ -98,13 +93,9 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
: std::rand()) * 0x734287f27) & 0xfff000;
// Setup the process code layout
if (process
.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), 0, aslr_offset, false)
.IsError()) {
if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) {
return {ResultStatus::ErrorNotInitialized, {}};
}
codeset.memory = std::move(program_image);
const VAddr base_address = GetInteger(process.GetEntryPoint());
process.LoadModule(std::move(codeset), base_address);
+69 -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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -9,11 +9,15 @@
#include <ostream>
#include <string>
#include <concepts>
#include <algorithm>
#include "common/concepts.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/k_process.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/kip.h"
@@ -37,6 +41,49 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
return std::nullopt;
}
std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, FileType type,
u64 program_id = 0,
std::size_t program_index = 0) {
if (!file) {
return nullptr;
}
if (type == FileType::NSP) {
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
}
if (type == FileType::XCI) {
FileSys::XCI xci{file, program_id, program_index};
if (xci.GetStatus() != ResultStatus::Success) {
return nullptr;
}
auto secure_nsp = xci.GetSecurePartitionNSP();
if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
return nullptr;
}
return secure_nsp;
}
return nullptr;
}
bool HasApplicationProgramContent(const std::shared_ptr<FileSys::NSP>& nsp) {
if (!nsp) {
return false;
}
const auto& ncas = nsp->GetNCAs();
return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) {
const auto& nca_map = title_entry.second;
return nca_map.find(
{FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) !=
nca_map.end();
});
}
} // namespace
FileType IdentifyFile(FileSys::VirtualFile file) {
@@ -62,6 +109,27 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
}
}
bool IsContainerType(FileType type) {
return type == FileType::NSP || type == FileType::XCI;
}
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
std::size_t program_index) {
if (!file) {
return false;
}
if (type == FileType::Unknown) {
type = IdentifyFile(file);
}
if (!IsContainerType(type)) {
return false;
}
return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index));
}
FileType GuessFromFilename(const std::string& name) {
if (name == "main")
return FileType::DeconstructedRomDirectory;
+21 -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-License-Identifier: GPL-2.0-or-later
@@ -46,12 +49,29 @@ enum class FileType {
};
/**
* Identifies the type of a bootable file based on the magic value in its header.
* Identifies the type of a supported file/container based on its structure.
* @param file open file
* @return FileType of file
*/
FileType IdentifyFile(FileSys::VirtualFile file);
/**
* Returns whether the file type represents a container format that can bundle multiple titles
* (currently NSP/XCI).
*/
bool IsContainerType(FileType type);
/**
* Returns whether a container file is bootable as a game (has Application/Program content).
*
* @param file open file
* @param type optional file type; if Unknown it is auto-detected.
* @param program_id optional program id hint for multi-program containers.
* @param program_index optional program index hint for multi-program containers.
*/
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown,
u64 program_id = 0, std::size_t program_index = 0);
/**
* Guess the type of a bootable file from its name
* @param name String name of bootable file
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -160,7 +160,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
}
// Build program image
Kernel::PhysicalMemory program_image(PageAlignSize(nro_header.file_size));
std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
std::memcpy(program_image.data(), data.data(), program_image.size());
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
return {};
+40 -59
View File
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cinttypes>
#include <cstring>
#include <vector>
@@ -40,17 +41,6 @@ struct MODHeader {
};
static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
const NSOSegmentHeader& header) {
std::vector<u8> uncompressed_data =
Common::Compression::DecompressDataLZ4(compressed_data, header.size);
ASSERT_MSG(uncompressed_data.size() == header.size, "{} != {}", header.size,
uncompressed_data.size());
return uncompressed_data;
}
constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
@@ -76,24 +66,16 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& in_file) {
return FileType::NSO;
}
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system,
const FileSys::VfsFile& nso_file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm,
std::vector<Core::NCE::Patcher>* patches,
s32 patch_index) {
if (nso_file.GetSize() < sizeof(NSOHeader)) {
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system, const FileSys::VfsFile& nso_file, VAddr load_base, bool should_pass_arguments, bool load_into_process, std::optional<FileSys::PatchManager> pm, std::vector<Core::NCE::Patcher>* patches, s32 patch_index) {
if (nso_file.GetSize() < sizeof(NSOHeader))
return std::nullopt;
}
NSOHeader nso_header{};
if (sizeof(NSOHeader) != nso_file.ReadObject(&nso_header)) {
if (sizeof(NSOHeader) != nso_file.ReadObject(&nso_header))
return std::nullopt;
}
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
return std::nullopt;
if (nso_header.segments.empty())
return std::nullopt;
}
// Allocate some space at the beginning if we are patching in PreText mode.
const size_t module_start = [&]() -> size_t {
@@ -110,42 +92,44 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
return 0;
}();
// Build program image
auto const last_segment_it = &nso_header.segments[nso_header.segments.size() - 1];
// Build program image directly in codeset memory :)
Kernel::CodeSet codeset;
Kernel::PhysicalMemory program_image;
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
std::vector<u8> data = nso_file.ReadBytes(nso_header.segments_compressed_size[i],
nso_header.segments[i].offset);
if (nso_header.IsSegmentCompressed(i)) {
data = DecompressSegment(data, nso_header.segments[i]);
codeset.memory.resize(module_start + last_segment_it->location + last_segment_it->size);
{
std::vector<u8> compressed_data(*std::ranges::max_element(nso_header.segments_compressed_size));
std::vector<u8> decompressed_size(std::ranges::max_element(nso_header.segments, [](auto const& a, auto const& b) {
return a.size < b.size;
})->size);
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
if (nso_header.IsSegmentCompressed(i)) {
int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]);
ASSERT(r == int(nso_header.segments[i].size));
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size);
} else {
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size);
}
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
codeset.segments[i].size = nso_header.segments[i].size;
}
program_image.resize(module_start + nso_header.segments[i].location +
static_cast<u32>(data.size()));
std::memcpy(program_image.data() + module_start + nso_header.segments[i].location,
data.data(), data.size());
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
codeset.segments[i].size = nso_header.segments[i].size;
}
if (should_pass_arguments && !Settings::values.program_args.GetValue().empty()) {
const auto arg_data{Settings::values.program_args.GetValue()};
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
const auto end_offset = program_image.size();
program_image.resize(static_cast<u32>(program_image.size()) +
NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
arg_data.size());
NSOArgumentHeader args_header{NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
const auto end_offset = codeset.memory.size();
codeset.memory.resize(u32(codeset.memory.size()) + NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
std::memcpy(codeset.memory.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
std::memcpy(codeset.memory.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), arg_data.size());
}
codeset.DataSegment().size += nso_header.segments[2].bss_size;
u32 image_size{
PageAlignSize(static_cast<u32>(program_image.size()) + nso_header.segments[2].bss_size)};
program_image.resize(image_size);
u32 image_size = PageAlignSize(u32(codeset.memory.size()) + nso_header.segments[2].bss_size);
codeset.memory.resize(image_size);
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
codeset.segments[i].size = PageAlignSize(codeset.segments[i].size);
@@ -154,8 +138,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
// Apply patches if necessary
const auto name = nso_file.GetName();
if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
std::span<u8> patchable_section(program_image.data() + module_start,
program_image.size() - module_start);
std::span<u8> patchable_section(codeset.memory.data() + module_start, codeset.memory.size() - module_start);
std::vector<u8> pi_header(sizeof(NSOHeader) + patchable_section.size());
std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
std::memcpy(pi_header.data() + sizeof(NSOHeader), patchable_section.data(),
@@ -174,15 +157,15 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
//Set module ID using build_id from the NSO header
patch->SetModuleID(nso_header.build_id);
// Patch SVCs and MRS calls in the guest code
while (!patch->PatchText(program_image, code)) {
while (!patch->PatchText(codeset.memory, code)) {
patch = &patches->emplace_back();
patch->SetModuleID(nso_header.build_id); // In case the patcher is changed for big modules, the new patcher should also have the build_id
}
} else if (patch) {
// Relocate code patch and copy to the program_image.
// Relocate code patch and copy to the program image.
// Save size before RelocateAndCopy (which may resize)
const size_t size_before_relocate = program_image.size();
if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
const size_t size_before_relocate = codeset.memory.size();
if (patch->RelocateAndCopy(load_base, code, codeset.memory, &process.GetPostHandlers())) {
// Update patch section.
auto& patch_segment = codeset.PatchSegment();
auto& post_patch_segment = codeset.PostPatchSegment();
@@ -203,7 +186,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
}
// Refresh image_size to take account the patch section if it was added by RelocateAndCopy
image_size = static_cast<u32>(program_image.size());
image_size = static_cast<u32>(codeset.memory.size());
}
#endif
@@ -234,9 +217,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
}
// Load codeset for current process
codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), load_base);
return load_base + image_size;
}
+25 -11
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -55,19 +58,30 @@ AppLoader_NSP::~AppLoader_NSP() = default;
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
const FileSys::NSP nsp(nsp_file);
if (nsp.GetStatus() == ResultStatus::Success) {
// Extracted Type case
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
return FileType::NSP;
if (nsp.GetStatus() != ResultStatus::Success) {
return FileType::Error;
}
// Extracted Type case
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
return FileType::NSP;
}
// Non-extracted NSPs can legitimately contain only update/DLC content.
// Identify the container format itself; bootability is validated by Load().
if (!nsp.GetNCAs().empty()) {
return FileType::NSP;
}
// Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs.
for (const auto& entry : nsp.GetFiles()) {
if (entry == nullptr) {
continue;
}
// Non-Extracted Type case
const auto program_id = nsp.GetProgramTitleID();
if (!nsp.IsExtractedType() &&
nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr &&
AppLoader_NCA::IdentifyType(
nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) {
const auto& name = entry->GetName();
if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") {
return FileType::NSP;
}
}
+10 -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 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -44,10 +47,13 @@ AppLoader_XCI::~AppLoader_XCI() = default;
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
const FileSys::XCI xci(xci_file);
if (xci.GetStatus() == ResultStatus::Success &&
xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
FileType::NCA) {
if (xci.GetStatus() != ResultStatus::Success) {
return FileType::Error;
}
// Identify XCI as a valid container even when it does not include a bootable Program NCA.
// Bootability is handled by AppLoader_XCI::Load().
if (xci.GetSecurePartitionNSP() != nullptr) {
return FileType::XCI;
}
@@ -356,6 +356,12 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
tr("Fix bloom effects"),
tr("Removes bloom in Burnout."));
INSERT(Settings,
rescale_hack,
tr("Enable Legacy Rescale Pass"),
tr("May fix rescale issues in some games by relying on behavior from the previous implementation.\n"
"Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3."));
// Renderer (Extensions)
INSERT(Settings, dyna_state, tr("Extended Dynamic State"),
tr("Controls the number of features that can be used in Extended Dynamic State.\n"
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -91,6 +94,17 @@ void AlphaTest(EmitContext& ctx) {
} // Anonymous namespace
void EmitPrologue(EmitContext& ctx) {
if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend) {
// Initialize dual-source blending outputs - prevents MoltenVK crash.
const Id zero{ctx.Const(0.0f)};
const Id one{ctx.Const(1.0f)};
const Id default_color{ctx.ConstantComposite(ctx.F32[4], zero, zero, zero, one)};
for (u32 i = 0; i < 2; ++i) {
if (Sirit::ValidId(ctx.frag_color[i])) {
ctx.OpStore(ctx.frag_color[i], default_color);
}
}
}
if (ctx.stage == Stage::VertexB) {
const Id zero{ctx.Const(0.0f)};
const Id one{ctx.Const(1.0f)};
@@ -1670,13 +1670,22 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
break;
case Stage::Fragment:
for (u32 index = 0; index < 8; ++index) {
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
const bool need_dual_source = runtime_info.dual_source_blend && index <= 1;
if (!need_dual_source && !info.stores_frag_color[index] &&
!profile.need_declared_frag_colors) {
continue;
}
const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])};
frag_color[index] = DefineOutput(*this, type, std::nullopt);
Decorate(frag_color[index], spv::Decoration::Location, index);
Name(frag_color[index], fmt::format("frag_color{}", index));
// Correct mapping for dual-source blending
if (runtime_info.dual_source_blend && index <= 1) {
Decorate(frag_color[index], spv::Decoration::Location, 0u);
Decorate(frag_color[index], spv::Decoration::Index, index);
Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary");
} else {
Decorate(frag_color[index], spv::Decoration::Location, index);
Name(frag_color[index], fmt::format("frag_color{}", index));
}
}
if (info.stores_frag_depth) {
frag_depth = DefineOutput(*this, F32[1], std::nullopt);
@@ -304,7 +304,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
Optimization::TexturePass(env, program, host_info);
if (Settings::values.resolution_info.active) {
if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) {
Optimization::RescalingPass(program);
}
Optimization::DeadCodeEliminationPass(program);
@@ -67,11 +67,18 @@ void VisitMark(IR::Block& block, IR::Inst& inst) {
if (must_patch_outside) {
const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
IR::IREmitter ir{block, it};
IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)};
const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})};
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
const IR::Value converted{ir.FPMul(new_bitcast, up_factor)};
inst.ReplaceUsesWith(converted);
if (Settings::values.rescale_hack.GetValue()) {
const IR::F32 new_inst{&*block.PrependNewInst(it, inst)};
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
const IR::Value converted{ir.FPMul(new_inst, up_factor)};
inst.ReplaceUsesWith(converted);
} else {
IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)};
const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})};
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
const IR::Value converted{ir.FPMul(new_bitcast, up_factor)};
inst.ReplaceUsesWith(converted);
}
}
break;
}
+3
View File
@@ -110,6 +110,9 @@ struct RuntimeInfo {
/// Output types for each color attachment
std::array<AttributeType, 8> color_output_types{};
/// Dual source blending
bool dual_source_blend{};
};
} // namespace Shader
@@ -1032,7 +1032,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
VkShaderModule frag_shader = *convert_float_to_depth_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -1062,7 +1062,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend
VkShaderModule frag_shader = *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -1093,7 +1093,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
}
const std::array stages = MakeStages(*full_screen_vert, *module);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -1135,7 +1135,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -61,7 +64,8 @@ public:
.pDescriptorUpdateEntries = entries.data(),
.templateType = type,
.descriptorSetLayout = descriptor_set_layout,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.pipelineBindPoint =
is_compute ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS,
.pipelineLayout = pipeline_layout,
.set = 0,
});
@@ -122,7 +126,7 @@ private:
});
++binding;
num_descriptors += descriptors[i].count;
offset += sizeof(DescriptorUpdateEntry);
offset += sizeof(DescriptorUpdateEntry) * descriptors[i].count;
}
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -6,6 +6,7 @@
#include "common/assert.h"
#include <ranges>
#include <vulkan/vulkan_core.h>
#include "video_core/renderer_vulkan/present/util.h"
namespace Vulkan {
@@ -629,8 +630,8 @@ vk::Sampler CreateCubicSampler(const Device& device, VkCubicFilterWeightsQCOM qc
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.magFilter = VK_FILTER_CUBIC_EXT,
.minFilter = VK_FILTER_CUBIC_EXT,
.magFilter = device.IsExtFilterCubicSupported() ? VK_FILTER_CUBIC_EXT : VK_FILTER_LINEAR,
.minFilter = device.IsExtFilterCubicSupported() ? VK_FILTER_CUBIC_EXT : VK_FILTER_LINEAR,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
@@ -137,14 +137,8 @@ try
memory_allocator,
scheduler,
swapchain,
#ifdef ANDROID
surface)
,
#else
*surface)
,
#endif
blit_swapchain(device_memory,
, blit_swapchain(device_memory,
device,
memory_allocator,
present_manager,
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
@@ -22,7 +22,8 @@ BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const
MemoryAllocator& memory_allocator_, PresentManager& present_manager_,
Scheduler& scheduler_, const PresentFilters& filters_)
: device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_},
present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, image_count{1},
present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_},
image_count{1}, image_index{0},
swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {}
BlitScreen::~BlitScreen() = default;
@@ -87,57 +88,49 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
bool resource_update_required = false;
bool presentation_recreate_required = false;
// Recreate dynamic resources if the adapting filter changed
if (!window_adapt || scaling_filter != filters.get_scaling_filter()) {
resource_update_required = true;
}
// Recreate dynamic resources if the image count changed
const size_t old_swapchain_image_count =
std::exchange(image_count, current_swapchain_image_count);
if (old_swapchain_image_count != current_swapchain_image_count) {
if (image_count != current_swapchain_image_count) {
resource_update_required = true;
image_count = current_swapchain_image_count;
}
// Recreate the presentation frame if the format or dimensions of the window changed
const VkFormat old_swapchain_view_format =
std::exchange(swapchain_view_format, current_swapchain_view_format);
if (old_swapchain_view_format != current_swapchain_view_format ||
if (swapchain_view_format != current_swapchain_view_format ||
layout.width != frame->width || layout.height != frame->height) {
resource_update_required = true;
presentation_recreate_required = true;
swapchain_view_format = current_swapchain_view_format;
}
// If we have a pending resource update, perform it
if (resource_update_required) {
// Wait for idle to ensure no resources are in use
WaitIdle();
// Update window adapt pass
SetWindowAdaptPass();
// Update frame format if needed
if (presentation_recreate_required) {
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
window_adapt->GetRenderPass());
}
image_index = 0;
}
// Add additional layers if needed
const VkExtent2D window_size{
.width = layout.screen.GetWidth(),
.height = layout.screen.GetHeight(),
};
while (layers.size() < framebuffers.size()) {
layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
window_size, window_adapt->GetDescriptorSetLayout(), filters);
if (layers.size() != framebuffers.size()) {
layers.clear();
for (size_t i = 0; i < framebuffers.size(); ++i) {
layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
window_size, window_adapt->GetDescriptorSetLayout(), filters);
}
}
// Perform the draw
window_adapt->Draw(rasterizer, scheduler, image_index, layers, framebuffers, layout, frame);
// Advance to next image
if (++image_index >= image_count) {
image_index = 0;
}
@@ -146,16 +139,20 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& layout,
VkImageView image_view,
VkFormat current_view_format) {
const bool format_updated =
std::exchange(swapchain_view_format, current_view_format) != current_view_format;
bool format_updated = swapchain_view_format != current_view_format;
swapchain_view_format = current_view_format;
if (!window_adapt || scaling_filter != filters.get_scaling_filter() || format_updated) {
WaitIdle();
SetWindowAdaptPass();
image_index = 0;
}
const VkExtent2D extent{
.width = layout.width,
.height = layout.height,
};
return CreateFramebuffer(image_view, extent, window_adapt->GetRenderPass());
}
@@ -108,6 +108,14 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat
// Null buffer not supported, adjust offset and size
offset = 0;
size = 0;
} else {
// Align offset down to minTexelBufferOffsetAlignment
const u32 alignment = static_cast<u32>(device->GetMinTexelBufferOffsetAlignment());
if (alignment > 1) {
const u32 aligned_offset = offset & ~(alignment - 1);
size += offset - aligned_offset;
offset = aligned_offset;
}
}
const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
return offset == view.offset && size == view.size && format == view.format;
@@ -285,7 +285,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
.requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
};
bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
pipeline = device.GetLogical().CreateComputePipeline({
pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -299,7 +299,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
.pSpecializationInfo = nullptr,
},
.layout = *layout,
.basePipelineHandle = nullptr,
.basePipelineHandle = {},
.basePipelineIndex = 0,
});
}
@@ -944,7 +944,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
.codeSize = static_cast<u32>(code.size_bytes()),
.pCode = code.data(),
});
pipelines[i] = device.GetLogical().CreateComputePipeline({
pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -958,7 +958,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
.pSpecializationInfo = nullptr,
},
.layout = *layout,
.basePipelineHandle = nullptr,
.basePipelineHandle = {},
.basePipelineIndex = 0,
});
};
@@ -50,11 +50,14 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
DescriptorLayoutBuilder builder{device};
builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT);
descriptor_set_layout = builder.CreateDescriptorSetLayout(false);
uses_push_descriptor = builder.CanUsePushDescriptor();
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout);
descriptor_update_template =
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, false);
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor);
if (!uses_push_descriptor) {
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
}
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
.pNext = nullptr,
@@ -64,26 +67,24 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
}
pipeline = device.GetLogical().CreateComputePipeline(
{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = flags,
.stage{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext =
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = *spv_module,
.pName = "main",
.pSpecializationInfo = nullptr,
},
.layout = *pipeline_layout,
.basePipelineHandle = 0,
.basePipelineIndex = 0,
pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = flags,
.stage{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext =
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = *spv_module,
.pName = "main",
.pSpecializationInfo = nullptr,
},
*pipeline_cache);
.layout = *pipeline_layout,
.basePipelineHandle = 0,
.basePipelineIndex = 0,
}, *pipeline_cache);
// Log compute pipeline creation
if (Settings::values.gpu_logging_enabled.GetValue()) {
@@ -241,11 +242,16 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data),
rescaling_data.data());
}
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
const vk::Device& dev{device.GetLogical()};
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
descriptor_set, nullptr);
if (uses_push_descriptor) {
cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout,
0, descriptor_data);
} else {
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
const vk::Device& dev{device.GetLogical()};
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
descriptor_set, nullptr);
}
});
}
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -55,6 +58,7 @@ private:
vk::ShaderModule spv_module;
vk::DescriptorSetLayout descriptor_set_layout;
bool uses_push_descriptor{false};
DescriptorAllocator descriptor_allocator;
vk::PipelineLayout pipeline_layout;
vk::DescriptorUpdateTemplate descriptor_update_template;
@@ -474,7 +474,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
buffer_cache.BindHostStageBuffers(stage);
PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling,
samplers_it, views_it);
const auto& info{stage_infos[0]};
const auto& info{stage_infos[stage]};
if (info.uses_render_area) {
render_area.uses_render_area = true;
render_area.words = {static_cast<float>(regs.surface_clip.width),
@@ -946,29 +946,27 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
}
pipeline = device.GetLogical().CreateGraphicsPipeline(
{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = flags,
.stageCount = static_cast<u32>(shader_stages.size()),
.pStages = shader_stages.data(),
.pVertexInputState = &vertex_input_ci,
.pInputAssemblyState = &input_assembly_ci,
.pTessellationState = &tessellation_ci,
.pViewportState = &viewport_ci,
.pRasterizationState = &rasterization_ci,
.pMultisampleState = &multisample_ci,
.pDepthStencilState = &depth_stencil_ci,
.pColorBlendState = &color_blend_ci,
.pDynamicState = &dynamic_state_ci,
.layout = *pipeline_layout,
.renderPass = render_pass,
.subpass = 0,
.basePipelineHandle = nullptr,
.basePipelineIndex = 0,
},
*pipeline_cache);
pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = flags,
.stageCount = static_cast<u32>(shader_stages.size()),
.pStages = shader_stages.data(),
.pVertexInputState = &vertex_input_ci,
.pInputAssemblyState = &input_assembly_ci,
.pTessellationState = &tessellation_ci,
.pViewportState = &viewport_ci,
.pRasterizationState = &rasterization_ci,
.pMultisampleState = &multisample_ci,
.pDepthStencilState = &depth_stencil_ci,
.pColorBlendState = &color_blend_ci,
.pDynamicState = &dynamic_state_ci,
.layout = *pipeline_layout,
.renderPass = render_pass,
.subpass = 0,
.basePipelineHandle = nullptr,
.basePipelineIndex = 0,
}, *pipeline_cache);
// Log graphics pipeline creation
if (Settings::values.gpu_logging_enabled.GetValue()) {
@@ -121,10 +121,8 @@ VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuff
}
}
static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
};
static constexpr VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
vk::CommandBuffer& upload_cmdbuf,
@@ -143,7 +141,7 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
const VkSemaphore* p_wait_sems =
(num_wait_semaphores > 0) ? &wait_semaphore : nullptr;
const VkPipelineStageFlags* p_wait_masks =
(num_wait_semaphores > 0) ? wait_stage_masks.data() : nullptr;
(num_wait_semaphores > 0) ? &wait_stage_mask : nullptr;
const VkSemaphore* p_signal_sems =
(num_signal_semaphores > 0) ? signal_semaphores.data() : nullptr;
const u64 wait_zero = 0; // dummy for binary wait
@@ -180,7 +178,7 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf,
const VkSemaphore* p_wait_sems =
(num_wait_semaphores > 0) ? &wait_semaphore : nullptr;
const VkPipelineStageFlags* p_wait_masks =
(num_wait_semaphores > 0) ? wait_stage_masks.data() : nullptr;
(num_wait_semaphores > 0) ? &wait_stage_mask : nullptr;
const VkSemaphore* p_signal_sems =
(num_signal_semaphores > 0) ? &signal_semaphore : nullptr;
const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
@@ -237,11 +237,38 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
}
info.convert_depth_mode = gl_ndc;
break;
case Shader::Stage::Fragment:
case Shader::Stage::Fragment: {
info.alpha_test_func = MaxwellToCompareFunction(
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
// Check for dual source blending
const auto& blend0 = key.state.attachments[0];
if (blend0.enable != 0) {
using F = Maxwell::Blend::Factor;
const auto src_rgb = blend0.SourceRGBFactor();
const auto dst_rgb = blend0.DestRGBFactor();
const auto src_a = blend0.SourceAlphaFactor();
const auto dst_a = blend0.DestAlphaFactor();
info.dual_source_blend =
src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D ||
src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D ||
src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL ||
src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL ||
dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D ||
dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D ||
dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL ||
dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL ||
src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D ||
src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D ||
src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL ||
src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL ||
dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D ||
dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D ||
dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL ||
dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL;
}
if (device.IsMoltenVK()) {
for (size_t i = 0; i < 8; ++i) {
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[i]);
@@ -258,6 +285,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
}
}
break;
}
default:
break;
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -101,22 +101,14 @@ PresentManager::PresentManager(const vk::Instance& instance_,
MemoryAllocator& memory_allocator_,
Scheduler& scheduler_,
Swapchain& swapchain_,
#ifdef ANDROID
vk::SurfaceKHR& surface_)
#else
VkSurfaceKHR_T* surface_handle_)
#endif
VkSurfaceKHR_T* surface_)
: instance{instance_}
, render_window{render_window_}
, device{device_}
, memory_allocator{memory_allocator_}
, scheduler{scheduler_}
, swapchain{swapchain_}
#ifdef ANDROID
, surface{surface_}
#else
, surface_handle{surface_handle_}
#endif
, blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}
, use_present_thread{Settings::values.async_presentation.GetValue()}
{
@@ -299,11 +291,7 @@ void PresentManager::PresentThread(std::stop_token token) {
}
void PresentManager::RecreateSwapchain(Frame* frame) {
#ifndef ANDROID
swapchain.Create(surface_handle, frame->width, frame->height); // Pass raw pointer
#else
swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer
#endif
swapchain.Create(surface, frame->width, frame->height); // Pass raw pointer
SetImageCount();
}
@@ -322,7 +310,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
// Recreate surface and swapchain if needed.
if (requires_recreation) {
#ifdef ANDROID
surface = CreateSurface(instance, render_window.GetWindowInfo());
surface = *CreateSurface(instance, render_window.GetWindowInfo()).address();
#endif
RecreateSwapchain(frame);
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -15,8 +15,6 @@
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
struct VkSurfaceKHR_T;
namespace Core::Frontend {
class EmuWindow;
} // namespace Core::Frontend
@@ -46,11 +44,7 @@ public:
MemoryAllocator& memory_allocator,
Scheduler& scheduler,
Swapchain& swapchain,
#ifdef ANDROID
vk::SurfaceKHR& surface);
#else
VkSurfaceKHR_T* surface_handle);
#endif
VkSurfaceKHR_T* surface);
~PresentManager();
/// Returns the last used presentation frame
@@ -84,11 +78,7 @@ private:
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
Swapchain& swapchain;
#ifdef ANDROID
vk::SurfaceKHR& surface;
#else
VkSurfaceKHR_T* surface_handle;
#endif
VkSurfaceKHR_T* surface;
vk::CommandPool cmdpool;
std::vector<Frame> frames;
boost::container::deque<Frame*> present_queue;
@@ -1280,7 +1280,7 @@ void QueryCacheRuntime::EndHostConditionalRendering() {
PauseHostConditionalRendering();
impl->hcr_is_set = false;
impl->is_hcr_running = false;
impl->hcr_buffer = nullptr;
impl->hcr_buffer = VkBuffer{};
impl->hcr_offset = 0;
}
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -35,7 +38,7 @@ public:
~QueryCacheRuntime();
template <typename SyncValuesType>
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = VkBuffer{});
void Barriers(bool is_prebarrier);
@@ -270,8 +270,8 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, WRITE_BARRIER);
upload_cmdbuf.End();
cmdbuf.End();
@@ -372,18 +372,12 @@ void Scheduler::EndRenderPass()
};
}
cmdbuf.EndRenderPass();
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0,
nullptr,
nullptr,
vk::Span(barriers.data(), num_images) // Batched image barriers
);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
});
state.renderpass = nullptr;
state.renderpass = VkRenderPass{};
num_renderpass_images = 0;
}
+32 -23
View File
@@ -16,8 +16,8 @@
#include "common/alignment.h"
#include "common/common_types.h"
#include "common/settings.h"
#include "common/polyfill_thread.h"
#include "common/settings.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -44,10 +44,10 @@ public:
~Scheduler();
/// Sends the current execution context to the GPU.
u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
u64 Flush(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
/// Sends the current execution context to the GPU and waits for it to complete.
void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
void Finish(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
/// Waits for the worker thread to finish executing everything. After this function returns it's
/// safe to touch worker resources.
@@ -114,29 +114,36 @@ public:
/// Waits for the given GPU tick, optionally pacing frames.
void Wait(u64 tick, double target_fps = 0.0) {
if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
const auto now = std::chrono::steady_clock::now();
if (start_time == std::chrono::steady_clock::time_point{} || current_target_fps != target_fps) {
start_time = now;
frame_counter = 0;
current_target_fps = target_fps;
}
frame_counter++;
std::chrono::duration<double> frame_interval(1.0 / current_target_fps);
auto target_time = start_time + frame_interval * frame_counter;
if (target_time > now) {
std::this_thread::sleep_until(target_time);
} else {
start_time = now;
frame_counter = 0;
}
}
if (tick > 0) {
if (tick >= master_semaphore->CurrentTick()) {
Flush();
}
master_semaphore->Wait(tick);
}
if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
auto now = std::chrono::steady_clock::now();
if (last_target_fps != target_fps) {
frame_interval = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(1.0 / target_fps));
max_frame_count = static_cast<int>(0.1 * target_fps);
last_target_fps = target_fps;
frame_counter = 0;
start_time = now;
}
frame_counter++;
auto target_time = start_time + frame_interval * frame_counter;
if (target_time >= now) {
auto sleep_time = target_time - now;
if (sleep_time > std::chrono::milliseconds(15)) {
std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1));
}
while (std::chrono::steady_clock::now() < target_time) {
std::this_thread::yield();
}
} else if (frame_counter > max_frame_count) {
frame_counter = 0;
start_time = now;
}
}
}
/// Returns the master timeline semaphore.
@@ -230,8 +237,8 @@ private:
};
struct State {
VkRenderPass renderpass = nullptr;
VkFramebuffer framebuffer = nullptr;
VkRenderPass renderpass{};
VkFramebuffer framebuffer{};
VkExtent2D render_area = {0, 0};
GraphicsPipeline* graphics_pipeline = nullptr;
bool is_rescaling = false;
@@ -281,9 +288,11 @@ private:
std::condition_variable_any event_cv;
std::jthread worker_thread;
std::chrono::steady_clock::duration frame_interval{};
std::chrono::steady_clock::time_point start_time{};
double last_target_fps{};
u64 max_frame_count{};
u64 frame_counter{};
double current_target_fps{};
};
} // namespace Vulkan
@@ -109,38 +109,22 @@ VkCompositeAlphaFlagBitsKHR ChooseAlphaFlags(const VkSurfaceCapabilitiesKHR& cap
} // Anonymous namespace
Swapchain::Swapchain(
#ifdef ANDROID
VkSurfaceKHR surface_,
#else
VkSurfaceKHR_T* surface_handle_,
#endif
VkSurfaceKHR_T* surface_,
const Device& device_,
Scheduler& scheduler_,
u32 width_,
u32 height_)
#ifdef ANDROID
: surface(surface_)
#else
: surface_handle{surface_handle_}
#endif
, device{device_}
, scheduler{scheduler_}
{
#ifdef ANDROID
Create(surface, width_, height_);
#else
Create(surface_handle, width_, height_);
#endif
}
Swapchain::~Swapchain() = default;
void Swapchain::Create(
#ifdef ANDROID
VkSurfaceKHR surface_,
#else
VkSurfaceKHR_T* surface_handle_,
#endif
VkSurfaceKHR_T* surface_,
u32 width_,
u32 height_)
{
@@ -148,18 +132,10 @@ void Swapchain::Create(
is_suboptimal = false;
width = width_;
height = height_;
#ifdef ANDROID
surface = surface_;
#else
surface_handle = surface_handle_;
#endif
const auto physical_device = device.GetPhysical();
#ifdef ANDROID
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)};
#else
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface_handle)};
#endif
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface))};
if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) {
return;
}
@@ -254,14 +230,8 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
const auto physical_device{device.GetPhysical()};
#ifdef ANDROID
const auto formats{physical_device.GetSurfaceFormatsKHR(surface)};
const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface);
#else
const auto formats{physical_device.GetSurfaceFormatsKHR(surface_handle)};
const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface_handle);
#endif
const auto formats{physical_device.GetSurfaceFormatsKHR(VkSurfaceKHR(surface))};
const auto present_modes = physical_device.GetSurfacePresentModesKHR(VkSurfaceKHR(surface));
has_mailbox = std::find(present_modes.begin(), present_modes.end(), VK_PRESENT_MODE_MAILBOX_KHR)
!= present_modes.end();
@@ -290,11 +260,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.pNext = nullptr,
.flags = 0,
#ifdef ANDROID
.surface = surface,
#else
.surface = surface_handle,
#endif
.surface = VkSurfaceKHR(surface),
.minImageCount = requested_image_count,
.imageFormat = surface_format.format,
.imageColorSpace = surface_format.colorSpace,
@@ -313,7 +279,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
.compositeAlpha = alpha_flags,
.presentMode = present_mode,
.clipped = VK_FALSE,
.oldSwapchain = nullptr,
.oldSwapchain = VkSwapchainKHR{},
};
const u32 graphics_family{device.GetGraphicsFamily()};
const u32 present_family{device.GetPresentFamily()};
@@ -345,11 +311,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR;
}
// Request the size again to reduce the possibility of a TOCTOU race condition.
#ifdef ANDROID
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface);
#else
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface_handle);
#endif
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface));
swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height);
// Don't add code within this and the swapchain creation.
swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci);
+4 -18
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -11,8 +11,6 @@
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
struct VkSurfaceKHR_T;
namespace Layout {
struct FramebufferLayout;
}
@@ -25,11 +23,7 @@ class Scheduler;
class Swapchain {
public:
explicit Swapchain(
#ifdef ANDROID
VkSurfaceKHR surface,
#else
VkSurfaceKHR_T* surface_handle,
#endif
VkSurfaceKHR_T* surface,
const Device& device,
Scheduler& scheduler,
u32 width,
@@ -38,11 +32,7 @@ public:
/// Creates (or recreates) the swapchain with a given size.
void Create(
#ifdef ANDROID
VkSurfaceKHR surface,
#else
VkSurfaceKHR_T* surface_handle,
#endif
VkSurfaceKHR_T* surface,
u32 width,
u32 height);
@@ -128,11 +118,7 @@ private:
bool NeedsPresentModeUpdate() const;
#ifdef ANDROID
VkSurfaceKHR surface;
#else
VkSurfaceKHR_T* surface_handle;
#endif
VkSurfaceKHR_T* surface;
const Device& device;
Scheduler& scheduler;
@@ -2500,7 +2500,7 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) {
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([barrier](vk::CommandBuffer cmdbuf) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
});
}
+4 -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -40,3 +40,6 @@
#undef False
#undef None
#undef True
// "Catch-all" handle for both Android and.. the rest of platforms
struct VkSurfaceKHR_T;
@@ -419,7 +419,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
: instance{instance_}, dld{dld_}, physical{physical_},
format_properties(GetFormatProperties(physical)) {
// Get suitability and device properties.
const bool is_suitable = GetSuitability(surface != nullptr);
const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{});
const VkDriverId driver_id = properties.driver.driverID;
@@ -557,20 +557,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
if (is_nvidia) {
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
const auto arch = GetNvidiaArch();
if (arch >= NvidiaArchitecture::Arch_AmpereOrNewer) {
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
features.shader_float16_int8.shaderFloat16 = false;
}
if (nv_major_version >= 510) {
LOG_WARNING(Render_Vulkan,
"NVIDIA Drivers >= 510 do not support MSAA->MSAA image blits. "
"MSAA scaling will use 3D helpers. MSAA resolves work normally.");
cant_blit_msaa = true;
}
// Mali/ NVIDIA proprietary drivers: Shader stencil export not supported
// Use hardware depth/stencil blits instead when available
if (!extensions.shader_stencil_export) {
@@ -594,12 +586,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (is_amd_driver) {
// AMD drivers need a higher amount of Sets per Pool in certain circumstances like in XC2.
sets_per_pool = 96;
// Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken.
if (!features.shader_float16_int8.shaderFloat16) {
LOG_WARNING(Render_Vulkan,
"AMD GCN4 and earlier have broken VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT");
has_broken_cube_compatibility = true;
}
// AMD drivers (2026+) have broken float16 math on DKCR
if (features.shader_float16_int8.shaderFloat16) {
LOG_WARNING(Render_Vulkan,
"AMD drivers (2026+) have broken float16 math");
features.shader_float16_int8.shaderFloat16 = false;
}
}
if (is_qualcomm) {
@@ -107,6 +107,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) \
EXTENSION(EXT, DESCRIPTOR_INDEXING, descriptor_indexing) \
EXTENSION(EXT, FILTER_CUBIC, filter_cubic) \
EXTENSION(IMG, FILTER_CUBIC, filter_cubic_img) \
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights)
// Define extensions which must be supported.
@@ -317,6 +318,11 @@ public:
return properties.properties.limits.minStorageBufferOffsetAlignment;
}
/// Returns texel buffer offset alignment requirement.
VkDeviceSize GetMinTexelBufferOffsetAlignment() const {
return properties.properties.limits.minTexelBufferOffsetAlignment;
}
/// Returns the maximum range for storage buffers.
VkDeviceSize GetMaxStorageBufferRange() const {
return properties.properties.limits.maxStorageBufferRange;
@@ -15,7 +15,7 @@ vk::SurfaceKHR CreateSurface(
const vk::Instance& instance,
[[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) {
[[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch();
VkSurfaceKHR unsafe_surface = nullptr;
VkSurfaceKHR unsafe_surface = VkSurfaceKHR{};
#ifdef _WIN32
if (window_info.type == Core::Frontend::WindowSystemType::Windows) {
+22 -22
View File
@@ -404,13 +404,13 @@ public:
/// Construct a handle transferring the ownership from another handle.
Handle(Handle&& rhs) noexcept
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {}
: handle{std::exchange(rhs.handle, Type{})}, owner{rhs.owner}, dld{rhs.dld} {}
/// Assign the current handle transferring the ownership from another handle.
/// Destroys any previously held object.
Handle& operator=(Handle&& rhs) noexcept {
Release();
handle = std::exchange(rhs.handle, nullptr);
handle = std::exchange(rhs.handle, Type{});
owner = rhs.owner;
dld = rhs.dld;
return *this;
@@ -424,7 +424,7 @@ public:
/// Destroys any held object.
void reset() noexcept {
Release();
handle = nullptr;
handle = Type{};
}
/// Returns the address of the held object.
@@ -440,7 +440,7 @@ public:
/// Returns true when there's a held object.
explicit operator bool() const noexcept {
return handle != nullptr;
return handle != Type{};
}
#ifndef ANDROID
@@ -455,7 +455,7 @@ public:
#endif
protected:
Type handle = nullptr;
Type handle{};
OwnerType owner = nullptr;
const Dispatch* dld = nullptr;
@@ -463,7 +463,7 @@ private:
/// Destroys the held object if it exists.
void Release() noexcept {
if (handle) {
Destroy(owner, handle, *dld);
Destroy(OwnerType(owner), Type(handle), *dld);
}
}
};
@@ -506,7 +506,7 @@ public:
/// Destroys any held object.
void reset() noexcept {
Release();
handle = nullptr;
handle = {};
}
/// Returns the address of the held object.
@@ -522,7 +522,7 @@ public:
/// Returns true when there's a held object.
explicit operator bool() const noexcept {
return handle != nullptr;
return handle != Type{};
}
#ifndef ANDROID
@@ -537,7 +537,7 @@ public:
#endif
protected:
Type handle = nullptr;
Type handle{};
const Dispatch* dld = nullptr;
private:
@@ -607,7 +607,7 @@ private:
std::unique_ptr<AllocationType[]> allocations;
std::size_t num = 0;
VkDevice device = nullptr;
PoolType pool = nullptr;
PoolType pool{};
const DeviceDispatch* dld = nullptr;
};
@@ -669,12 +669,12 @@ public:
Image& operator=(const Image&) = delete;
Image(Image&& rhs) noexcept
: handle{std::exchange(rhs.handle, nullptr)}, usage{rhs.usage}, owner{rhs.owner},
: handle{std::exchange(rhs.handle, VkImage{})}, usage{rhs.usage}, owner{rhs.owner},
allocator{rhs.allocator}, allocation{rhs.allocation}, dld{rhs.dld} {}
Image& operator=(Image&& rhs) noexcept {
Release();
handle = std::exchange(rhs.handle, nullptr);
handle = std::exchange(rhs.handle, VkImage{});
usage = rhs.usage;
owner = rhs.owner;
allocator = rhs.allocator;
@@ -693,11 +693,11 @@ public:
void reset() noexcept {
Release();
handle = nullptr;
handle = VkImage{};
}
explicit operator bool() const noexcept {
return handle != nullptr;
return handle != VkImage{};
}
void SetObjectNameEXT(const char* name) const;
@@ -709,7 +709,7 @@ public:
private:
void Release() const noexcept;
VkImage handle = nullptr;
VkImage handle{};
VkImageUsageFlags usage{};
VkDevice owner = nullptr;
VmaAllocator allocator = nullptr;
@@ -730,13 +730,13 @@ public:
Buffer& operator=(const Buffer&) = delete;
Buffer(Buffer&& rhs) noexcept
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator},
: handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator},
allocation{rhs.allocation}, mapped{rhs.mapped},
is_coherent{rhs.is_coherent}, dld{rhs.dld} {}
Buffer& operator=(Buffer&& rhs) noexcept {
Release();
handle = std::exchange(rhs.handle, nullptr);
handle = std::exchange(rhs.handle, VkBuffer{});
owner = rhs.owner;
allocator = rhs.allocator;
allocation = rhs.allocation;
@@ -756,11 +756,11 @@ public:
void reset() noexcept {
Release();
handle = nullptr;
handle = VkBuffer{};
}
explicit operator bool() const noexcept {
return handle != nullptr;
return handle != VkBuffer{};
}
/// Returns the host mapped memory, an empty span otherwise.
@@ -786,7 +786,7 @@ public:
private:
void Release() const noexcept;
VkBuffer handle = nullptr;
VkBuffer handle{};
VkDevice owner = nullptr;
VmaAllocator allocator = nullptr;
VmaAllocation allocation = nullptr;
@@ -1020,10 +1020,10 @@ public:
[[nodiscard]] PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const;
[[nodiscard]] Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci,
VkPipelineCache cache = nullptr) const;
VkPipelineCache cache = {}) const;
[[nodiscard]] Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci,
VkPipelineCache cache = nullptr) const;
VkPipelineCache cache = {}) const;
[[nodiscard]] Sampler CreateSampler(const VkSamplerCreateInfo& ci) const;
+4 -4
View File
@@ -412,10 +412,10 @@ if (ENABLE_WEB_SERVICE)
target_compile_definitions(yuzu PRIVATE ENABLE_WEB_SERVICE)
endif()
# if (YUZU_USE_QT_MULTIMEDIA)
# target_link_libraries(yuzu PRIVATE Qt6::Multimedia)
# target_compile_definitions(yuzu PRIVATE YUZU_USE_QT_MULTIMEDIA)
# endif ()
if (YUZU_USE_QT_MULTIMEDIA)
target_link_libraries(yuzu PRIVATE Qt6::Multimedia)
target_compile_definitions(yuzu PRIVATE YUZU_USE_QT_MULTIMEDIA)
endif ()
if (YUZU_USE_QT_WEB_ENGINE)
target_link_libraries(yuzu PRIVATE Qt6::WebEngineCore Qt6::WebEngineWidgets)

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