Compare commits

..

24 Commits

Author SHA1 Message Date
CamilleLaVey f2f05293a6 Pure and simply meow 2026-08-27 02:30:35 -04:00
CamilleLaVey ad075400a4 Let's try this on memory reclamations 2026-08-27 01:21:10 -04:00
CamilleLaVey 06dba55923 Extended the impact on AHB fallback 2026-08-26 22:10:00 -04:00
CamilleLaVey 8d19ee6dc5 What if we just rely entirely on SGSR common than using EdgeDir? 2026-08-26 19:06:23 -04:00
CamilleLaVey 3e91569d36 Another intent 2026-08-26 18:40:10 -04:00
CamilleLaVey 6010f60803 Just cries internally 2026-08-26 18:05:03 -04:00
CamilleLaVey c14b1de71e Meow. 2026-08-26 17:47:12 -04:00
CamilleLaVey 14cc49e727 Adjustments on upload for textures 2026-08-26 17:00:18 -04:00
CamilleLaVey e88923331c Fix build 2026-08-26 15:04:18 -04:00
CamilleLaVey 78918e995d Fix msvc build 2026-08-26 14:50:42 -04:00
CamilleLaVey 522440bcb2 [TEST] Bring the unswizzle work from tiled-gpu-v2 x2 + revert of vertex, indexes from AHB path 2026-08-26 14:49:01 -04:00
CamilleLaVey c1d8b7dd42 Just a quick test 2026-08-26 05:09:12 -04:00
CamilleLaVey 28d7c97d71 Added vertex, uniform and indexes on the upload path 2026-08-26 04:52:52 -04:00
CamilleLaVey 8a8763d975 Meowly The Build Destroyer 2026-08-26 04:11:59 -04:00
CamilleLaVey 54d8d5b0a8 Another intent on write/read on the fly 2026-08-26 04:06:25 -04:00
CamilleLaVey b9b9a0d704 Changes on the maps counts again 2026-08-26 03:12:06 -04:00
CamilleLaVey b8ce711575 Step back + keeping fixes from unified download + shared mem refactor 2026-08-25 21:36:17 -04:00
CamilleLaVey 46cd1e5d65 another take 2026-08-25 21:36:17 -04:00
CamilleLaVey ae561d49aa Keep AHB limitations 2026-08-25 21:36:17 -04:00
CamilleLaVey 9f45d357e9 [TEST] Another increased on windows size 2026-08-25 21:36:17 -04:00
CamilleLaVey 2c8c3062c4 Increased windows size 2026-08-25 21:36:17 -04:00
CamilleLaVey 51bdb62d74 [TEST] Increased window limiter on AHB 2026-08-25 21:36:17 -04:00
CamilleLaVey ed3838ccbb [TEST] Adjustment AHB to tiled-gpu-v2 state 2026-08-25 21:36:17 -04:00
CamilleLaVey e8c253f511 [memory, vulkan] Initial implementation for Unified Memory 2026-08-25 21:36:17 -04:00
305 changed files with 4839 additions and 3014 deletions
@@ -0,0 +1,26 @@
From b3622608433c183ba868a1dc8dd9cf285eb3b916 Mon Sep 17 00:00:00 2001
From: Dario Petrillo <dario.pk1@gmail.com>
Date: Thu, 27 Nov 2025 23:12:38 +0100
Subject: [PATCH] avoid extra memset when clearing an empty table
---
include/ankerl/unordered_dense.h | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h
index 0835342..4938212 100644
--- a/include/ankerl/unordered_dense.h
+++ b/include/ankerl/unordered_dense.h
@@ -1490,8 +1490,10 @@ class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, bas
// modifiers //////////////////////////////////////////////////////////////
void clear() {
- m_values.clear();
- clear_buckets();
+ if (!empty()) {
+ m_values.clear();
+ clear_buckets();
+ }
}
auto insert(value_type const& value) -> std::pair<iterator, bool> {
+1
View File
@@ -540,6 +540,7 @@ add_subdirectory(externals)
# pass targets from externals # pass targets from externals
# TODO(crueter): CPMUtil Propagate func? # TODO(crueter): CPMUtil Propagate func?
find_package(enet) find_package(enet)
find_package(unordered_dense REQUIRED)
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
find_package(xbyak) find_package(xbyak)
+13 -2
View File
@@ -301,6 +301,17 @@
"repo": "eden-emu/tzdb_to_nx", "repo": "eden-emu/tzdb_to_nx",
"version": "230326" "version": "230326"
}, },
"unordered-dense": {
"bundled": true,
"find_args": "CONFIG",
"hash": "d2106f6640f6bfb81755e4b8bfb64982e46ec4a507cacdb38f940123212ccf35a20b43c70c6f01d7bfb8c246d1a16f7845d8052971949cea9def1475e3fa02c8",
"package": "unordered_dense",
"patches": [
"0001-avoid-memset-when-clearing-an-empty-table.patch"
],
"repo": "martinus/unordered_dense",
"version": "7b55cab841"
},
"vulkan-headers": { "vulkan-headers": {
"hash": "d2846ea228415772645eea4b52a9efd33e6a563043dd3de059e798be6391a8f0ca089f455ae420ff22574939ed0f48ed7c6ff3d5a9987d5231dbf3b3f89b484b", "hash": "d2846ea228415772645eea4b52a9efd33e6a563043dd3de059e798be6391a8f0ca089f455ae420ff22574939ed0f48ed7c6ff3d5a9987d5231dbf3b3f89b484b",
"min_version": "1.4.317", "min_version": "1.4.317",
@@ -329,10 +340,10 @@
"version": "vulkan-sdk-%NUMERIC_VERSION%" "version": "vulkan-sdk-%NUMERIC_VERSION%"
}, },
"xbyak": { "xbyak": {
"hash": "e0aa0a603dd3ac1a39d82213df1e73c042831aec2d6b2fe382c899651eaae4ed7e8aeb6be9c41ea0097087c9d091961ab18f313cd6a9e10d621715ffd49bfe36", "hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a",
"package": "xbyak", "package": "xbyak",
"repo": "herumi/xbyak", "repo": "herumi/xbyak",
"version": "v7.40.1" "version": "v7.35.2"
}, },
"zlib": { "zlib": {
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
-2
View File
@@ -509,8 +509,6 @@ QWidget#contentRichDialog QLabel#label_title_rich {
} }
QWidget#contentDialog QLabel#label_dialog { QWidget#contentDialog QLabel#label_dialog {
background: #2E2E2E;
padding: 20px 65px; padding: 20px 65px;
} }
+5 -4
View File
@@ -80,6 +80,7 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c
* [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on * [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on
* This package is known to be broken on the AUR. * This package is known to be broken on the AUR.
* [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on * [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on
* [unordered-dense](https://github.com/martinus/unordered_dense)
On amd64: On amd64:
@@ -118,7 +119,7 @@ Now, install all deps:
sudo emerge -a \ sudo emerge -a \
app-arch/lz4 app-arch/zstd app-arch/unzip \ app-arch/lz4 app-arch/zstd app-arch/unzip \
dev-libs/libfmt dev-libs/libusb dev-libs/mcl dev-libs/sirit \ dev-libs/libfmt dev-libs/libusb dev-libs/mcl dev-libs/sirit \
dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \ dev-libs/unordered_dense dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \ dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
dev-util/vulkan-utility-libraries dev-util/glslang \ dev-util/vulkan-utility-libraries dev-util/glslang \
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \ media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
@@ -259,7 +260,7 @@ brew install molten-vk
<details> <details>
<summary>FreeBSD</summary> <summary>FreeBSD</summary>
As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib vulkan-headers quazip-qt6` As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6`
If using FreeBSD 12 or prior, use `devel/pkg-config` instead. If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
@@ -293,7 +294,7 @@ pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gm
<summary>DragonFlyBSD</summary> <summary>DragonFlyBSD</summary>
```sh ```sh
pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl3 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 libva-vdpau-driver libva-utils libva-intel-driver pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl3 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver
``` ```
[Caveats](./Caveats.md#dragonflybsd). [Caveats](./Caveats.md#dragonflybsd).
@@ -327,7 +328,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
```sh ```sh
BASE="git make autoconf libtool automake-wrapper jq patch" BASE="git make autoconf libtool automake-wrapper jq patch"
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb openssl SDL3" MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL3"
# Either x86_64 or clang-aarch64 (Windows on ARM) # Either x86_64 or clang-aarch64 (Windows on ARM)
packages="$BASE" packages="$BASE"
for pkg in $MINGW; do for pkg in $MINGW; do
+11 -27
View File
@@ -43,7 +43,6 @@ This guide will walk you through adding a new boolean toggle setting to Eden's c
Firstly add your desired toggle: Firstly add your desired toggle:
Example: `src/common/setting.h` Example: `src/common/setting.h`
```cpp ```cpp
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions}; SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
``` ```
@@ -68,7 +67,6 @@ Common Categories:
Add the toggle to the Qt UI, where you wish for it to appear and place it there. Add the toggle to the Qt UI, where you wish for it to appear and place it there.
Example: `src/qt_common/config/shared_translation.cpp` Example: `src/qt_common/config/shared_translation.cpp`
```cpp ```cpp
INSERT(Settings, INSERT(Settings,
your_setting_name, your_setting_name,
@@ -93,7 +91,6 @@ INSERT(Settings,
Add where it should be in the settings. Add where it should be in the settings.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
```kts ```kts
RENDERER_YOUR_SETTING_NAME("your_setting_name"), RENDERER_YOUR_SETTING_NAME("your_setting_name"),
``` ```
@@ -109,7 +106,6 @@ RENDERER_YOUR_SETTING_NAME("your_setting_name"),
Add the toggle to the Kotlin (Android) UI Add the toggle to the Kotlin (Android) UI
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
```kts ```kts
put( put(
SwitchSetting( SwitchSetting(
@@ -127,7 +123,6 @@ put(
Add your setting within the right category. Add your setting within the right category.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
```kts ```kts
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key) add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
``` ```
@@ -142,7 +137,6 @@ add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
Add your setting and description in the appropriate place. Add your setting and description in the appropriate place.
Example: `src/android/app/src/main/res/values/strings.xml` Example: `src/android/app/src/main/res/values/strings.xml`
```xml ```xml
<string name="your_setting_name">Your Setting Display Name</string> <string name="your_setting_name">Your Setting Display Name</string>
<string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string> <string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string>
@@ -156,7 +150,6 @@ Now the UI part is done find a place in the code for the toggle,
And use it to your heart's desire! And use it to your heart's desire!
Example: Example:
```cpp ```cpp
const bool your_value = Settings::values.your_setting_name.GetValue(); const bool your_value = Settings::values.your_setting_name.GetValue();
@@ -203,31 +196,25 @@ Common advantages recap:
#### Accessing Debug Knobs (dev side) #### Accessing Debug Knobs (dev side)
Use the `Settings::GetDebugKnobAt(u8 i)` function to check if a specific bit is set: Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set:
```cpp ```cpp
//cpp side //cpp side
#include "common/settings.h" #include "common/settings.h"
//To use it as a general purpose uint var:
unsigned int debug_knobs = Settings::values.debug_knobs.GetValue();
// Check if bit 0 is set // Check if bit 0 is set
bool feature_enabled = Settings::GetDebugKnobAt(0); bool feature_enabled = Settings::getDebugKnobAt(0);
// Check if bit 15 is set // Check if bit 15 is set
bool another_feature = Settings::GetDebugKnobAt(15); bool another_feature = Settings::getDebugKnobAt(15);
``` ```
```kts ```kts
//kotlin side //kotlin side
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
//To use it as a general purpose uint var
val debug_knobs: Int = UShortSetting.DEBUG_KNOBS.getInt()
// Check if bit x is set // Check if bit x is set
bool feature_enabled = Settings.GetDebugKnobAt(x); //x as integer from 0 to 15 bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15
``` ```
The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise. The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise.
@@ -260,7 +247,6 @@ There are two main confusions when talking about knobs:
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1). Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
Debug knobs are **zero-based**, which means: Debug knobs are **zero-based**, which means:
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise) * The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc. * You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
@@ -273,7 +259,6 @@ Whenever you're instructing tests or reporting results, be precise about whether
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once: ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
Examples: Examples:
- **knobs=0**: no knobs enabled - **knobs=0**: no knobs enabled
- **knobs=1**: knob0 enabled, others disabled - **knobs=1**: knob0 enabled, others disabled
- **knobs=2**: knob1 enabled, others disabled - **knobs=2**: knob1 enabled, others disabled
@@ -285,7 +270,6 @@ Examples:
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once: Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
Examples: Examples:
- **knob0**: knob 0 enabled, others disabled - **knob0**: knob 0 enabled, others disabled
- **knob1**: knob 1 enabled, others disabled - **knob1**: knob 1 enabled, others disabled
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled - **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
@@ -298,12 +282,12 @@ Examples:
```cpp ```cpp
void SomeFunction() { void SomeFunction() {
if (Settings::GetDebugKnobAt(0)) { if (Settings::getDebugKnobAt(0)) {
LOG_DEBUG(Common, "Debug feature 0 is enabled"); LOG_DEBUG(Common, "Debug feature 0 is enabled");
// Additional debug code here // Additional debug code here
} }
if (Settings::GetDebugKnobAt(1)) { if (Settings::getDebugKnobAt(1)) {
LOG_DEBUG(Common, "Debug feature 1 is enabled"); LOG_DEBUG(Common, "Debug feature 1 is enabled");
// Different debug behavior // Different debug behavior
} }
@@ -315,7 +299,7 @@ void SomeFunction() {
```cpp ```cpp
bool UseOptimizedPath() { bool UseOptimizedPath() {
// Skip optimization if debug bit 2 is set for testing // Skip optimization if debug bit 2 is set for testing
return !Settings::GetDebugKnobAt(2); return !Settings::getDebugKnobAt(2);
} }
``` ```
@@ -324,13 +308,13 @@ bool UseOptimizedPath() {
```cpp ```cpp
void ExperimentalFeature() { void ExperimentalFeature() {
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3; static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) { if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
// Fallback to stable implementation // Fallback to stable implementation
StableImplementation(); StableImplementation();
return; return;
} }
// Experimental implementation // Experimental implementation
ExperimentalImplementation(); ExperimentalImplementation();
} }
+26
View File
@@ -308,6 +308,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
``` ```
### unordered_dense
```
MIT License
Copyright (c) 2022 Martin Leitner-Ankerl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
### xbyak ### xbyak
``` ```
+3
View File
@@ -58,6 +58,9 @@ if (WIN32 AND NOT TARGET LLVM::Demangle)
add_library(LLVM::Demangle ALIAS demangle) add_library(LLVM::Demangle ALIAS demangle)
endif() endif()
# unordered_dense
AddJsonPackage(unordered-dense)
# httplib # httplib
if (IOS) if (IOS)
set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF) set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF)
@@ -220,7 +220,7 @@ object NativeLibrary {
external fun refreshThreadPolicies() external fun refreshThreadPolicies()
external fun GetDebugKnobAt(index: Int): Boolean external fun getDebugKnobAt(index: Int): Boolean
/** /**
* Set the current speed limit to the configured turbo speed. * Set the current speed limit to the configured turbo speed.
@@ -27,6 +27,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"), RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
RENDERER_ASYNC_PRESENTATION("async_presentation"), RENDERER_ASYNC_PRESENTATION("async_presentation"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"), RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"), ENABLE_BUFFER_HISTORY("enable_buffer_history"),
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"), USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
@@ -35,8 +35,8 @@ object Settings {
fun getPlayerString(player: Int): String = fun getPlayerString(player: Int): String =
YuzuApplication.appContext.getString(R.string.preferences_player, player) YuzuApplication.appContext.getString(R.string.preferences_player, player)
fun GetDebugKnobAt(index: Int): Boolean { fun getDebugKnobAt(index: Int): Boolean {
return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index) return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index)
} }
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
@@ -11,7 +11,8 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(override val key: String) : AbstractShortSetting { enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit"), RENDERER_SPEED_LIMIT("speed_limit"),
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"), RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit") RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
DEBUG_KNOBS("debug_knobs")
; ;
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal) override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
@@ -28,4 +29,4 @@ enum class ShortSetting(override val key: String) : AbstractShortSetting {
override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString() override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString()
override fun reset() = NativeConfig.setShort(key, defaultValue) override fun reset() = NativeConfig.setShort(key, defaultValue)
} }
@@ -11,7 +11,6 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class StringSetting(override val key: String) : AbstractStringSetting { enum class StringSetting(override val key: String) : AbstractStringSetting {
DRIVER_PATH("driver_path"), DRIVER_PATH("driver_path"),
DEVICE_NAME("device_name"), DEVICE_NAME("device_name"),
LOG_FILTER("log_filter"),
PROGRAM_ARGS("program_args"), PROGRAM_ARGS("program_args"),
WEB_TOKEN("eden_token"), WEB_TOKEN("eden_token"),
@@ -1,27 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class UShortSetting(override val key: String) : AbstractIntSetting {
DEBUG_KNOBS("debug_knobs")
;
override fun getInt(needsGlobal: Boolean): Int =
NativeConfig.getUnsignedShort(key, needsGlobal)
override fun setInt(value: Int) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setUnsignedShort(key, value)
}
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
override fun reset() = NativeConfig.setUnsignedShort(key, defaultValue)
}
@@ -20,7 +20,6 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
import org.yuzu.yuzu_emu.network.NetDataValidators import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
@@ -790,6 +789,13 @@ abstract class SettingsItem(
descriptionId = R.string.renderer_asynchronous_shaders_description descriptionId = R.string.renderer_asynchronous_shaders_description
) )
) )
put(
SwitchSetting(
BooleanSetting.RENDERER_UNIFIED_MEMORY,
titleId = R.string.renderer_unified_memory,
descriptionId = R.string.renderer_unified_memory_description
)
)
put( put(
SingleChoiceSetting( SingleChoiceSetting(
IntSetting.FAST_GPU_TIME, IntSetting.FAST_GPU_TIME,
@@ -1033,16 +1039,9 @@ abstract class SettingsItem(
descriptionId = R.string.use_auto_stub_description descriptionId = R.string.use_auto_stub_description
) )
) )
put(
StringInputSetting(
StringSetting.LOG_FILTER,
titleId = R.string.log_filter,
descriptionId = R.string.log_filter_description
)
)
put( put(
SpinBoxSetting( SpinBoxSetting(
UShortSetting.DEBUG_KNOBS, ShortSetting.DEBUG_KNOBS,
titleId = R.string.debug_knobs, titleId = R.string.debug_knobs,
descriptionId = R.string.debug_knobs_description, descriptionId = R.string.debug_knobs_description,
valueHint = R.string.debug_knobs_hint, valueHint = R.string.debug_knobs_hint,
@@ -25,7 +25,6 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
@@ -345,6 +344,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key) add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
add(IntSetting.ANDROID_PIPELINE_WORKERS.key) add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key) add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
@@ -1323,12 +1323,11 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.log)) add(HeaderSetting(R.string.log))
add(BooleanSetting.DEBUG_FLUSH_BY_LINE.key) add(BooleanSetting.DEBUG_FLUSH_BY_LINE.key)
add(StringSetting.LOG_FILTER.key)
} }
add(HeaderSetting(R.string.general)) add(HeaderSetting(R.string.general))
add(UShortSetting.DEBUG_KNOBS.key) add(ShortSetting.DEBUG_KNOBS.key)
add(StringSetting.PROGRAM_ARGS.key) add(StringSetting.PROGRAM_ARGS.key)
if (!NativeConfig.isPerGameConfigLoaded()) { if (!NativeConfig.isPerGameConfigLoaded()) {
@@ -47,6 +47,7 @@ import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler import info.debatty.java.stringsimilarity.JaroWinkler
import java.util.Locale import java.util.Locale
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.view.doOnNextLayout
class GamesFragment : Fragment() { class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null private var _binding: FragmentGamesBinding? = null
@@ -58,6 +59,7 @@ class GamesFragment : Fragment() {
private var originalHeaderLeftMargin: Int? = null private var originalHeaderLeftMargin: Int? = null
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
private var fallbackBottomInset: Int = 0
private var pendingPostReloadListSettle = false private var pendingPostReloadListSettle = false
private var pendingPostReloadListSettleGeneration = 0 private var pendingPostReloadListSettleGeneration = 0
private var gameListSubmitGeneration = 0 private var gameListSubmitGeneration = 0
@@ -225,7 +227,12 @@ class GamesFragment : Fragment() {
} }
else -> throw IllegalArgumentException("Invalid view type: $savedViewType") else -> throw IllegalArgumentException("Invalid view type: $savedViewType")
} }
if (savedViewType != GameAdapter.VIEW_TYPE_CAROUSEL) { if (savedViewType == GameAdapter.VIEW_TYPE_CAROUSEL) {
(binding.gridGames as? View)?.let { it -> ViewCompat.requestApplyInsets(it)}
doOnNextLayout { //Carousel: important to avoid overlap issues
(this as? CarouselRecyclerView)?.notifyLaidOut(fallbackBottomInset)
}
} else {
(this as? CarouselRecyclerView)?.setupCarousel(false) (this as? CarouselRecyclerView)?.setupCarousel(false)
} }
adapter = gameAdapter adapter = gameAdapter
@@ -583,6 +590,11 @@ class GamesFragment : Fragment() {
qlaunchButton.layoutParams = mlpQLaunch qlaunchButton.layoutParams = mlpQLaunch
} }
val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom)
fallbackBottomInset = bottomInset
(binding.gridGames as? CarouselRecyclerView)?.notifyInsetsReady(bottomInset)
windowInsets windowInsets
} }
} }
@@ -80,12 +80,6 @@ object NativeConfig {
@Synchronized @Synchronized
external fun setShort(key: String, value: Short) external fun setShort(key: String, value: Short)
@Synchronized
external fun getUnsignedShort(key: String, needsGlobal: Boolean): Int
@Synchronized
external fun setUnsignedShort(key: String, value: Int)
@Synchronized @Synchronized
external fun getInt(key: String, needsGlobal: Boolean): Int external fun getInt(key: String, needsGlobal: Boolean): Int
@@ -12,16 +12,13 @@ import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin import kotlin.math.sin
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter import org.yuzu.yuzu_emu.adapters.GameAdapter
import androidx.core.view.doOnNextLayout import androidx.core.view.doOnNextLayout
import androidx.core.view.ViewCompat
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import org.yuzu.yuzu_emu.utils.FullscreenHelper
/** /**
* CarouselRecyclerView encapsulates all carousel content for the games UI. * CarouselRecyclerView encapsulates all carousel content for the games UI.
* It manages overlapping cards, center snapping, custom drawing order, * It manages overlapping cards, center snapping, custom drawing order,
@@ -35,9 +32,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
private var overlapFactor: Float = 0f private var overlapFactor: Float = 0f
private var overlapPx: Int = 0 private var overlapPx: Int = 0
private var bottomInset: Int = 0 private var bottomInset: Int = -1
private var latestWindowInsets: WindowInsetsCompat? = null
private var cardGeometryInitialized: Boolean = false
private var overlapDecoration: OverlappingDecoration? = null private var overlapDecoration: OverlappingDecoration? = null
private var pagerSnapHelper: PagerSnapHelper? = null private var pagerSnapHelper: PagerSnapHelper? = null
private var scalingScrollListener: OnScrollListener? = null private var scalingScrollListener: OnScrollListener? = null
@@ -96,38 +91,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
init { init {
setChildrenDrawingOrderEnabled(true) setChildrenDrawingOrderEnabled(true)
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
latestWindowInsets = insets
updateCardGeometry()
applyCarouselPadding()
insets
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
ViewCompat.requestApplyInsets(this)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (w != oldw || h != oldh) {
updateCardGeometry()
applyCarouselPadding()
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (isCarouselMode) updateChildScalesAndAlpha()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
ViewCompat.requestApplyInsets(this)
post { updateCardGeometry() }
}
} }
override fun setAdapter(adapter: Adapter<*>?) { override fun setAdapter(adapter: Adapter<*>?) {
@@ -140,8 +103,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
super.setAdapter(adapter) super.setAdapter(adapter)
(adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver) (adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver)
updateCardGeometry()
applyCarouselPadding()
} }
private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int { private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int {
@@ -292,71 +253,40 @@ class CarouselRecyclerView @JvmOverloads constructor(
} }
} }
private fun resolveBottomInset(windowInsets: WindowInsetsCompat): Int { fun notifyInsetsReady(newBottomInset: Int) {
val navigationBottom = if (FullscreenHelper.isFullscreenEnabled(context)) { if (bottomInset != newBottomInset) {
0 bottomInset = newBottomInset
} else { }
windowInsets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars()).bottom
if (isCarouselMode) {
setupCarousel(true)
} else {
setupCarousel(false)
} }
val gestureInsets = windowInsets.getInsetsIgnoringVisibility(
WindowInsetsCompat.Type.systemGestures()
)
val cutoutInsets = windowInsets.getInsetsIgnoringVisibility(
WindowInsetsCompat.Type.displayCutout()
)
return maxOf(navigationBottom, gestureInsets.bottom, cutoutInsets.bottom)
} }
private fun updateCardGeometry() { fun notifyLaidOut(fallBackBottomInset: Int) {
if (!isCarouselMode || height <= 0) return if (bottomInset < 0) bottomInset = fallBackBottomInset
var gameAdapter = adapter as? GameAdapter ?: return
var newCardSize = cardSize(bottomInset)
if (gameAdapter.cardSize != newCardSize) {
gameAdapter.setCardSize(newCardSize)
}
val gameAdapter = adapter as? GameAdapter ?: return if (isCarouselMode) {
val windowInsets = latestWindowInsets ?: ViewCompat.getRootWindowInsets(this) ?: return setupCarousel(true)
}
}
if (cardGeometryInitialized && !hasWindowFocus()) return fun cardSize(bottomInset: Int): Int {
val newBottomInset = resolveBottomInset(windowInsets).coerceIn(0, height)
val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1) val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1)
val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn( val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(
0f, 0f,
1f 1f
) )
val screenWidth = resources.displayMetrics.widthPixels.toFloat() val scaledHeight = height * userFactor
val screenHeight = resources.displayMetrics.heightPixels.toFloat() val availableHeight = height - bottomInset
val aspectFactor = ((screenWidth / screenHeight) / (20f / 9f)) return minOf(scaledHeight.toInt(), availableHeight.toInt())
.pow(0.75f)
.coerceIn(0.5f, 1f)
val newCardSize = minOf(
(height * userFactor).toInt(),
height - newBottomInset,
(height * aspectFactor).toInt()
)
if (newCardSize <= 0) return
val insetChanged = bottomInset != newBottomInset
val cardSizeChanged = gameAdapter.cardSize != newCardSize
bottomInset = newBottomInset
cardGeometryInitialized = true
if (cardSizeChanged) gameAdapter.setCardSize(newCardSize)
if (insetChanged || cardSizeChanged) setupCarousel(true)
}
private fun applyCarouselPadding() {
if (!isCarouselMode) return
val gameAdapter = adapter as? GameAdapter ?: return
val cardSize = gameAdapter.cardSize
if (cardSize <= 0 || bottomInset < 0) return
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0)
val sidePadding = (width - cardSize) / 2
if (paddingLeft != sidePadding || paddingTop != topPadding ||
paddingRight != sidePadding || paddingBottom != 0
) {
setPadding(sidePadding, topPadding, sidePadding, 0)
}
clipToPadding = false
} }
fun setupCarousel(enabled: Boolean) { fun setupCarousel(enabled: Boolean) {
@@ -385,6 +315,9 @@ class CarouselRecyclerView @JvmOverloads constructor(
internalFlingMultiplier internalFlingMultiplier
).coerceIn(1f, 5f) ).coerceIn(1f, 5f)
// Detach SnapHelper during setup
pagerSnapHelper?.attachToRecyclerView(null)
// Add overlap decoration if not present // Add overlap decoration if not present
if (overlapDecoration == null) { if (overlapDecoration == null) {
overlapDecoration = OverlappingDecoration(overlapPx) overlapDecoration = OverlappingDecoration(overlapPx)
@@ -402,7 +335,12 @@ class CarouselRecyclerView @JvmOverloads constructor(
addOnScrollListener(scalingScrollListener!!) addOnScrollListener(scalingScrollListener!!)
} }
applyCarouselPadding() if (cardSize > 0) {
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0) // Center vertically
val sidePadding = (width - cardSize) / 2 // Center first/last card
setPadding(sidePadding, topPadding, sidePadding, 0)
clipToPadding = false
}
if (pagerSnapHelper == null) { if (pagerSnapHelper == null) {
pagerSnapHelper = CenterPagerSnapHelper() pagerSnapHelper = CenterPagerSnapHelper()
@@ -424,7 +362,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
} }
savedItemAnimator = null savedItemAnimator = null
} }
cardGeometryInitialized = false
useCustomDrawingOrder = false useCustomDrawingOrder = false
// Reset padding and fling // Reset padding and fling
setPadding(0, 0, 0, 0) setPadding(0, 0, 0, 0)
@@ -20,7 +20,7 @@ struct RomMetadata {
std::vector<u8> icon; std::vector<u8> icon;
bool isHomebrew; bool isHomebrew;
}; };
static ::Common::unordered_map<std::string, RomMetadata> m_rom_metadata_cache; static ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
static RomMetadata CacheRomMetadata(const std::string& path) { static RomMetadata CacheRomMetadata(const std::string& path) {
auto& instance = EmulationSession::GetInstance(); auto& instance = EmulationSession::GetInstance();
+6 -61
View File
@@ -76,7 +76,6 @@ extern "C" {
#include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h" #include "core/frontend/applets/web_browser.h"
#include "common/android/applets/web_browser.h" #include "common/android/applets/web_browser.h"
#include "core/file_sys/common_funcs.h"
#include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
@@ -312,23 +311,11 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
ConfigureFilesystemProvider(filepath); ConfigureFilesystemProvider(filepath);
// Load the ROM. // Load the ROM.
const u64 previous_program_id =
program_index != 0 && m_next_program_id.load() >
static_cast<u64>(Service::AM::AppletProgramId::MaxProgramId)
? m_next_program_id.load()
: 0;
Service::AM::FrontendAppletParameters params{ Service::AM::FrontendAppletParameters params{
.program_id = previous_program_id,
.applet_id = static_cast<Service::AM::AppletId>(m_applet_id), .applet_id = static_cast<Service::AM::AppletId>(m_applet_id),
.launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated .launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated
: Service::AM::LaunchType::ApplicationInitiated, : Service::AM::LaunchType::ApplicationInitiated,
.program_index = static_cast<s32>(program_index), .program_index = static_cast<s32>(program_index),
.previous_program_index =
previous_program_id != 0
? static_cast<s32>(previous_program_id -
FileSys::GetBaseTitleID(previous_program_id))
: -1,
}; };
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params); m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params);
@@ -341,12 +328,8 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
m_system.GetCpuManager().OnGpuReady(); m_system.GetCpuManager().OnGpuReady();
m_system.RegisterExitCallback([&] { HaltEmulation(); }); m_system.RegisterExitCallback([&] { HaltEmulation(); });
m_system.RegisterApplicationChangedCallback(
[&](u64 changed_program_id) { RequestDiskShaderCacheReload(changed_program_id); });
// Register an ExecuteProgram callback such that Core can execute a sub-program // Register an ExecuteProgram callback such that Core can execute a sub-program
m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) { m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) {
m_next_program_id = m_system.GetApplicationProcessProgramID();
m_next_program_index = program_index_; m_next_program_index = program_index_;
EmulationSession::GetInstance().HaltEmulation(); EmulationSession::GetInstance().HaltEmulation();
}); });
@@ -424,58 +407,20 @@ void EmulationSession::RunEmulation() {
} }
while (true) { while (true) {
std::optional<u64> reload_title;
{ {
[[maybe_unused]] std::unique_lock lock(m_mutex); [[maybe_unused]] std::unique_lock lock(m_mutex);
if (m_cv.wait_for(lock, std::chrono::milliseconds(800), [&]() { if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
return !m_is_running || m_pending_shader_cache_title.has_value(); [&]() { return !m_is_running; })) {
})) { // Emulation halted.
if (!m_is_running) { break;
break;
}
reload_title = std::exchange(m_pending_shader_cache_title, std::nullopt);
} }
} }
if (reload_title.has_value())
ReloadDiskShaderCache(*reload_title);
} }
// Reset current applet ID. // Reset current applet ID.
m_applet_id = static_cast<int>(Service::AM::AppletId::Application); m_applet_id = static_cast<int>(Service::AM::AppletId::Application);
} }
void EmulationSession::RequestDiskShaderCacheReload(u64 program_id) {
{
std::scoped_lock lock(m_mutex);
m_pending_shader_cache_title = program_id;
}
m_cv.notify_one();
}
void EmulationSession::ReloadDiskShaderCache(u64 program_id) {
if (!Settings::values.use_disk_shader_cache.GetValue())
return;
LOG_INFO(Frontend, "Reloading disk shader cache for {:016X}", program_id);
const bool was_paused = m_is_paused;
m_system.Pause();
m_system.GPU().WaitForIdle();
m_system.GPU().ObtainContext();
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
m_system.Renderer().ReadRasterizer()->LoadDiskResources(program_id, std::stop_token{},
LoadDiskCacheProgress);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
m_system.GPU().ReleaseContext();
if (!was_paused)
m_system.Run();
}
Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() { Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
return m_software_keyboard; return m_software_keyboard;
} }
@@ -1300,8 +1245,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jo
Common::RefreshThreadPolicies(); Common::RefreshThreadPolicies();
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDebugKnobAt(JNIEnv* env, jobject jobj, jint index) { jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::GetDebugKnobAt(static_cast<u8>(index))); return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) { void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
-6
View File
@@ -4,8 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
#include <android/native_window_jni.h> #include <android/native_window_jni.h>
#include "common/android/applets/software_keyboard.h" #include "common/android/applets/software_keyboard.h"
#include "core/core.h" #include "core/core.h"
@@ -46,7 +44,6 @@ public:
void HaltEmulation(); void HaltEmulation();
void RunEmulation(); void RunEmulation();
void ShutdownEmulation(); void ShutdownEmulation();
void RequestDiskShaderCacheReload(u64 program_id);
const Core::PerfStatsResults& PerfStats(); const Core::PerfStatsResults& PerfStats();
int ShadersBuilding(); int ShadersBuilding();
@@ -68,7 +65,6 @@ private:
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max); static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
static void OnEmulationStopped(Core::SystemResultStatus result); static void OnEmulationStopped(Core::SystemResultStatus result);
static void ChangeProgram(std::size_t program_index); static void ChangeProgram(std::size_t program_index);
void ReloadDiskShaderCache(u64 program_id);
private: private:
// Window management // Window management
@@ -87,7 +83,6 @@ private:
Common::Android::SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; Common::Android::SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
int m_applet_id{1}; int m_applet_id{1};
std::optional<u64> m_pending_shader_cache_title;
// GPU driver parameters // GPU driver parameters
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
@@ -98,5 +93,4 @@ private:
// Program index for next boot // Program index for next boot
std::atomic<s32> m_next_program_index = -1; std::atomic<s32> m_next_program_index = -1;
std::atomic<u64> m_next_program_id = 0;
}; };
@@ -130,25 +130,6 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob
setting->SetValue(value); setting->SetValue(value);
} }
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey,
jboolean needGlobal) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return -1;
}
return static_cast<jint>(setting->GetValue(static_cast<bool>(needGlobal)));
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey, jint value) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetValue(static_cast<u16>(value));
}
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
jboolean needGlobal) { jboolean needGlobal) {
auto setting = getSetting<int>(env, jkey); auto setting = getSetting<int>(env, jkey);
@@ -21,7 +21,7 @@
#include "input_common/drivers/virtual_gamepad.h" #include "input_common/drivers/virtual_gamepad.h"
#include "native.h" #include "native.h"
::Common::unordered_map<std::string, std::unique_ptr<AndroidConfig>> map_profiles; ankerl::unordered_dense::map<std::string, std::unique_ptr<AndroidConfig>> map_profiles;
bool IsHandheldOnly() { bool IsHandheldOnly() {
const auto npad_style_set = const auto npad_style_set =
@@ -585,6 +585,8 @@
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string> <string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string> <string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string> <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="renderer_unified_memory">Unified memory access</string>
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string> <string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string> <string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string> <string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
@@ -636,8 +638,6 @@
<string name="log">Logging</string> <string name="log">Logging</string>
<string name="flush_by_line">Flush debug logs by line</string> <string name="flush_by_line">Flush debug logs by line</string>
<string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string> <string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string>
<string name="log_filter">Log filter</string>
<string name="log_filter_description">Controls Eden\'s log categories. Example: *:Info Service.LM:Debug</string>
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">GPU Logging</string> <string name="gpu_logging_header">GPU Logging</string>
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -10,12 +7,13 @@
namespace AudioCore::ADSP::OpusDecoder { namespace AudioCore::ADSP::OpusDecoder {
namespace { namespace {
constexpr u32 OpusStreamCountMax = 255; bool IsValidChannelCount(u32 channel_count) {
return channel_count == 1 || channel_count == 2;
}
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) { bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax && return total_stream_count > 0 && static_cast<s32>(stereo_stream_count) >= 0 &&
static_cast<s32>(stereo_stream_count) >= 0 && stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count);
stereo_stream_count <= total_stream_count;
} }
} // namespace } // namespace
+1 -4
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -108,7 +105,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
shared_memory.host_send_data[4] = total_stream_count; shared_memory.host_send_data[4] = total_stream_count;
shared_memory.host_send_data[5] = stereo_stream_count; shared_memory.host_send_data[5] = stereo_stream_count;
ASSERT(channel_count <= shared_memory.channel_mapping.size()); ASSERT(channel_count <= MaxChannels);
std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8)); std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8));
opus_decoder.Send(ADSP::Direction::DSP, opus_decoder.Send(ADSP::Direction::DSP,
+27 -28
View File
@@ -19,23 +19,6 @@
namespace AudioCore::Sink { namespace AudioCore::Sink {
namespace { namespace {
[[nodiscard]] bool InitializeAudio() {
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
// See https://github.com/PCSX2/pcsx2/pull/12312
// "SDL and cubeb backends previously resulted in different names for the output which
// caused them be identified as different applications by the OS."
//
// Keep in sync with cubeb_sink.cpp name.
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return false;
}
}
return true;
}
SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) { SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) {
int device_count = 0; int device_count = 0;
SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count) SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count)
@@ -221,14 +204,20 @@ private:
}; };
SDLSink::SDLSink(std::string_view target_device_name) { SDLSink::SDLSink(std::string_view target_device_name) {
if (InitializeAudio()) { if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (target_device_name != auto_device_name && !target_device_name.empty()) { if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
output_device = target_device_name; LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
} else { return;
output_device.clear();
} }
device_channels = 2;
} }
if (target_device_name != auto_device_name && !target_device_name.empty()) {
output_device = target_device_name;
} else {
output_device.clear();
}
device_channels = 2;
} }
SDLSink::~SDLSink() = default; SDLSink::~SDLSink() = default;
@@ -276,10 +265,15 @@ void SDLSink::SetSystemVolume(f32 volume) {
} }
std::vector<std::string> ListSDLSinkDevices(bool capture) { std::vector<std::string> ListSDLSinkDevices(bool capture) {
if (!InitializeAudio())
return {}; //no devices
std::vector<std::string> device_list; std::vector<std::string> device_list;
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return {};
}
}
int device_count = 0; int device_count = 0;
SDL_AudioDeviceID* devices = SDL_AudioDeviceID* devices =
capture ? SDL_GetAudioRecordingDevices(&device_count) capture ? SDL_GetAudioRecordingDevices(&device_count)
@@ -310,8 +304,13 @@ bool IsSDLSuitable() {
return false; return false;
#else #else
// Check SDL can init // Check SDL can init
if (!InitializeAudio()! if (!SDL_WasInit(SDL_INIT_AUDIO)) {
return false; if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
SDL_GetError());
return false;
}
}
// We can set any latency frequency we want with SDL, so no need to check that. // We can set any latency frequency we want with SDL, so no need to check that.
+3 -3
View File
@@ -89,6 +89,7 @@ add_library(
param_package.h param_package.h
parent_of_member.h parent_of_member.h
point.h point.h
quaternion.h
range_map.h range_map.h
range_mutex.h range_mutex.h
range_sets.h range_sets.h
@@ -146,8 +147,7 @@ add_library(
cpu_features.cpp cpu_features.cpp
cpu_features.h cpu_features.h
httplib.h httplib.h
net/net.h net/net.cpp net/net.h net/net.cpp)
container/unordered_map.h container/unordered_set.h)
if(WIN32) if(WIN32)
target_sources(common PRIVATE windows/timer_resolution.cpp target_sources(common PRIVATE windows/timer_resolution.cpp
@@ -241,7 +241,7 @@ if (lz4_ADDED)
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
endif() endif()
target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads) target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads unordered_dense::unordered_dense)
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd) target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd)
# Please refer to src/common/demangle.cpp # Please refer to src/common/demangle.cpp
-16
View File
@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/container_hash.h"
#include <boost/unordered/unordered_flat_map.hpp>
namespace Common {
template <class Key, class T, class Hash = std::hash<Key>, class Pred = std::equal_to<Key>,
class Allocator = std::allocator<std::pair<const Key, T>>>
using unordered_map = boost::unordered::unordered_flat_map<Key, T, Hash, Pred, Allocator>;
}
-16
View File
@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/container_hash.h"
#include <boost/unordered/unordered_flat_set.hpp>
namespace Common {
template <class Key, class Hash = std::hash<Key>, class Pred = std::equal_to<Key>,
class Allocator = std::allocator<Key>>
using unordered_set = boost::unordered::unordered_flat_set<Key, Hash, Pred, Allocator>;
}
-33
View File
@@ -10,11 +10,8 @@
#include <array> #include <array>
#include <climits> #include <climits>
#include <cstdint> #include <cstdint>
#include <functional>
#include <limits> #include <limits>
#include <tuple>
#include <type_traits> #include <type_traits>
#include <utility>
#include <vector> #include <vector>
namespace Common { namespace Common {
@@ -72,17 +69,10 @@ struct HashCombineImpl<64> {
} // namespace detail } // namespace detail
template <typename T> template <typename T>
requires std::is_unsigned_v<T>
inline void HashCombine(std::size_t& seed, const T& v) { inline void HashCombine(std::size_t& seed, const T& v) {
seed = detail::HashCombineImpl<sizeof(std::size_t) * CHAR_BIT>::fn(seed, detail::HashValue(v)); seed = detail::HashCombineImpl<sizeof(std::size_t) * CHAR_BIT>::fn(seed, detail::HashValue(v));
} }
template <typename T>
requires (!std::is_unsigned_v<T>)
inline void HashCombine(std::size_t& seed, const T& v) {
seed = detail::HashCombineImpl<sizeof(std::size_t) * CHAR_BIT>::fn(seed, std::hash<T>{}(v));
}
template <typename It> template <typename It>
inline std::size_t HashRange(It first, It last) { inline std::size_t HashRange(It first, It last) {
std::size_t seed = 0; std::size_t seed = 0;
@@ -105,26 +95,3 @@ std::size_t HashValue(const std::vector<T, Allocator>& v) {
} }
} // namespace Common } // namespace Common
namespace std {
template <typename... Args>
struct hash<std::tuple<Args...>> {
std::size_t operator()(const std::tuple<Args...>& t) const noexcept {
std::size_t seed = 0;
std::apply([&seed](const Args&... args) { (Common::HashCombine(seed, args), ...); }, t);
return seed;
}
};
template <class A, class B>
struct hash<std::pair<A, B>> {
std::size_t operator()(const std::pair<A, B>& p) const noexcept {
std::size_t seed = 0;
Common::HashCombine(seed, p.first);
Common::HashCombine(seed, p.second);
return seed;
}
};
} // namespace std
+3 -3
View File
@@ -7,7 +7,7 @@
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/fs/fs.h" #include "common/fs/fs.h"
@@ -196,8 +196,8 @@ private:
SetLegacyPathImpl(legacy_path, new_path); SetLegacyPathImpl(legacy_path, new_path);
} }
::Common::unordered_map<EdenPath, fs::path> eden_paths; ankerl::unordered_dense::map<EdenPath, fs::path> eden_paths;
::Common::unordered_map<EmuPath, fs::path> legacy_paths; ankerl::unordered_dense::map<EmuPath, fs::path> legacy_paths;
}; };
bool ValidatePath(const fs::path& path) { bool ValidatePath(const fs::path& path) {
+22 -28
View File
@@ -4,34 +4,17 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include "common/heap_tracker.h" #include "common/heap_tracker.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/memory_detect.h"
#include "common/assert.h" #include "common/assert.h"
namespace Common { namespace Common {
namespace {
s64 GetMaxPermissibleResidentMapCount() {
// Default value.
s64 value = 65530;
// Try to read how many mappings we can make.
std::ifstream s("/proc/sys/vm/max_map_count");
s >> value;
// Print, for debug.
LOG_INFO(HW_Memory, "Current maximum map count: {}", value);
// Allow 20000 maps for other code and to account for split inaccuracy.
return std::max<s64>(value - 20000, 0);
}
} // namespace
HeapTracker::HeapTracker(Common::HostMemory& buffer) HeapTracker::HeapTracker(Common::HostMemory& buffer)
: m_buffer(buffer), m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {} : m_buffer(buffer),
m_has_hardware_buffer_backing(!buffer.BackingHardwareBuffers().empty()),
m_max_resident_map_count(static_cast<s64>(GetPermissibleMapCount())) {}
HeapTracker::~HeapTracker() = default; HeapTracker::~HeapTracker() = default;
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length, void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
@@ -85,7 +68,8 @@ void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_hea
// If resident, erase from resident map. // If resident, erase from resident map.
if (item->is_resident) { if (item->is_resident) {
ASSERT(--m_resident_map_count >= 0); m_resident_map_count -= this->HostMapCount(item->paddr, item->size);
ASSERT(m_resident_map_count >= 0);
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item)); m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
} }
@@ -191,7 +175,7 @@ bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
// This map is now resident. // This map is now resident.
it->is_resident = true; it->is_resident = true;
m_resident_map_count++; m_resident_map_count += this->HostMapCount(it->paddr, it->size);
m_resident_mappings.insert(*it); m_resident_mappings.insert(*it);
} }
@@ -213,17 +197,17 @@ void HeapTracker::RebuildSeparateHeapAddressSpace() {
// Despite being worse in theory, this has proven to be better in practice than more // Despite being worse in theory, this has proven to be better in practice than more
// regularly dumping a smaller amount, because it significantly reduces average case // regularly dumping a smaller amount, because it significantly reduces average case
// lock contention. // lock contention.
std::size_t const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2; s64 const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2;
std::size_t const evict_count = m_resident_map_count - desired_count;
auto it = m_resident_mappings.begin(); auto it = m_resident_mappings.begin();
for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) { while (m_resident_map_count > desired_count && it != m_resident_mappings.end()) {
// Unmark and unmap. // Unmark and unmap.
it->is_resident = false; it->is_resident = false;
m_buffer.Unmap(it->vaddr, it->size, false); m_buffer.Unmap(it->vaddr, it->size, false);
// Advance. // Advance.
ASSERT(--m_resident_map_count >= 0); m_resident_map_count -= this->HostMapCount(it->paddr, it->size);
ASSERT(m_resident_map_count >= 0);
it = m_resident_mappings.erase(it); it = m_resident_mappings.erase(it);
} }
} }
@@ -245,6 +229,7 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
// Cache the original values. // Cache the original values.
auto* const left = std::addressof(*it); auto* const left = std::addressof(*it);
const size_t orig_size = left->size; const size_t orig_size = left->size;
const s64 orig_host_map_count = this->HostMapCount(left->paddr, orig_size);
// Adjust the left map. // Adjust the left map.
const size_t left_size = offset - left->vaddr; const size_t left_size = offset - left->vaddr;
@@ -266,11 +251,20 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
// If resident, also insert into resident map. // If resident, also insert into resident map.
if (right->is_resident) { if (right->is_resident) {
m_resident_map_count++; m_resident_map_count += this->HostMapCount(left->paddr, left->size) +
this->HostMapCount(right->paddr, right->size) -
orig_host_map_count;
m_resident_mappings.insert(*right); m_resident_mappings.insert(*right);
} }
} }
s64 HeapTracker::HostMapCount(PAddr paddr, size_t size) const {
if (!m_has_hardware_buffer_backing) {
return size != 0 ? 1 : 0;
}
return static_cast<s64>(m_buffer.BackingMapCount(paddr, size));
}
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) { HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
const SeparateHeapMap key{ const SeparateHeapMap key{
.vaddr = offset, .vaddr = offset,
+6
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -82,10 +85,13 @@ private:
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset); AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
s64 HostMapCount(PAddr paddr, size_t size) const;
void RebuildSeparateHeapAddressSpace(); void RebuildSeparateHeapAddressSpace();
private: private:
Common::HostMemory& m_buffer; Common::HostMemory& m_buffer;
const bool m_has_hardware_buffer_backing;
const s64 m_max_resident_map_count; const s64 m_max_resident_map_count;
std::shared_mutex m_rebuild_lock{}; std::shared_mutex m_rebuild_lock{};
+395 -8
View File
@@ -7,7 +7,7 @@
#ifdef _WIN32 #ifdef _WIN32
#include <iterator> #include <iterator>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include <boost/icl/separate_interval_set.hpp> #include <boost/icl/separate_interval_set.hpp>
#include <windows.h> #include <windows.h>
#include "common/dynamic_library.h" #include "common/dynamic_library.h"
@@ -51,14 +51,65 @@
#endif // ^^^ POSIX ^^^ #endif // ^^^ POSIX ^^^
#include <atomic>
#include <mutex> #include <mutex>
#include <random> #include <random>
#include <vector>
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/free_region_manager.h" #include "common/free_region_manager.h"
#include "common/host_memory.h" #include "common/host_memory.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/memory_detect.h"
#include "common/settings.h"
#ifdef __ANDROID__
#include <cerrno>
#include <dlfcn.h>
#include <sys/ioctl.h>
#include <android/hardware_buffer.h>
namespace {
struct NativeHandle {
int version;
int numFds;
int numInts;
int data[1];
};
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
if (lib == nullptr) {
return nullptr;
}
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
}
struct DmaBufSync {
u64 flags;
};
constexpr u64 DmaBufSyncRead = 1ULL << 0;
constexpr u64 DmaBufSyncWrite = 1ULL << 1;
constexpr u64 DmaBufSyncStart = 0ULL << 2;
constexpr u64 DmaBufSyncEnd = 1ULL << 2;
void SyncDmaBufCpuAccess(int fd, u64 phase) {
DmaBufSync sync{.flags = phase | DmaBufSyncRead | DmaBufSyncWrite};
while (ioctl(fd, _IOW('b', 0, DmaBufSync), &sync) != 0) {
if (errno != EINTR) {
return;
}
}
}
} // namespace
#endif
#if defined(__ANDROID__) && __ANDROID_API__ < 30 #if defined(__ANDROID__) && __ANDROID_API__ < 30
#include <sys/syscall.h> #include <sys/syscall.h>
@@ -75,6 +126,12 @@ namespace Common {
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000; [[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000; [[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
static std::atomic<u64> committed_backing_size{};
u64 GetCommittedBackingSize() noexcept {
return committed_backing_size.load(std::memory_order_relaxed);
}
#ifdef _WIN32 #ifdef _WIN32
// Manually imported for MinGW compatibility // Manually imported for MinGW compatibility
@@ -123,7 +180,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
class HostMemory::Impl { class HostMemory::Impl {
public: public:
explicit Impl(size_t backing_size_, size_t virtual_size_) explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
: backing_size{backing_size_} : backing_size{backing_size_}
, virtual_size{virtual_size_} , virtual_size{virtual_size_}
, process{GetCurrentProcess()} , process{GetCurrentProcess()}
@@ -229,6 +286,10 @@ public:
UNREACHABLE(); UNREACHABLE();
} }
bool IsBackingShared() const noexcept {
return true;
}
const size_t backing_size; ///< Size of the backing memory in bytes const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@@ -391,7 +452,7 @@ private:
std::mutex placeholder_mutex; ///< Mutex for placeholders std::mutex placeholder_mutex; ///< Mutex for placeholders
boost::icl::separate_interval_set<size_t> placeholders; ///< Mapped placeholders boost::icl::separate_interval_set<size_t> placeholders; ///< Mapped placeholders
::Common::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset ankerl::unordered_dense::map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
}; };
#elif defined(__OPENORBIS__) || defined(__managarm__) #elif defined(__OPENORBIS__) || defined(__managarm__)
@@ -501,9 +562,10 @@ static int shm_open_anon(int flags, mode_t mode) {
class HostMemory::Impl { class HostMemory::Impl {
public: public:
explicit Impl(size_t backing_size_, size_t virtual_size_) explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size{backing_size_} : backing_size{backing_size_}
, virtual_size{virtual_size_} , virtual_size{virtual_size_}
, preferred_offset{preferred_offset_}
{} {}
bool Init() { bool Init() {
@@ -543,10 +605,15 @@ public:
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones"); LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (fd > 0) { if (fd > 0) {
fd = -1;
close(fd); close(fd);
} }
fd = -1;
} else { } else {
#ifdef __ANDROID__
if (InitAhbBacking()) {
return InitVirtual();
}
#endif
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
} }
if (backing_base == MAP_FAILED) { if (backing_base == MAP_FAILED) {
@@ -554,7 +621,10 @@ public:
return false; return false;
} }
// Virtual memory initialization return InitVirtual();
}
bool InitVirtual() {
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size)); virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
if (virtual_base == MAP_FAILED) { if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
@@ -567,6 +637,244 @@ public:
return true; return true;
} }
#ifdef __ANDROID__
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
return AHardwareBuffer_Desc{
.width = static_cast<u32>(len),
.height = 1,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_BLOB,
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
.stride = 0,
.rfu0 = 0,
.rfu1 = 0,
};
}
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
return false;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
return false;
}
const int probe_fd = handle->data[0];
bool ok = true;
const auto try_map = [&](int prot, off_t offset) {
if (!ok) {
return;
}
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
if (ptr == MAP_FAILED) {
ok = false;
return;
}
munmap(ptr, PageAlignment);
};
try_map(PROT_READ | PROT_WRITE, 0);
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment));
#ifdef ARCHITECTURE_arm64
try_map(PROT_READ | PROT_EXEC, 0);
#endif
AHardwareBuffer_release(buffer);
return ok;
}
size_t ComputeAhbBudget(size_t window_size) const {
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
constexpr u64 BaselineFootprint = 6ULL << 30;
if (total_physical <= BaselineFootprint) {
return 0;
}
const u64 permissible_maps = Common::GetPermissibleMapCount();
if (permissible_maps == 0) {
return 0;
}
u64 budget = (total_physical - BaselineFootprint) / 2;
constexpr u64 MapSlotsPerWindow = 64;
const u64 affordable_windows = permissible_maps / MapSlotsPerWindow;
budget = (std::min)(budget, affordable_windows * window_size);
const u64 available = Common::GetAvailablePhysicalMemory();
if (available != 0) {
budget = (std::min)(budget, available / 2);
}
budget = (std::min)(budget, static_cast<u64>(backing_size));
budget = Common::AlignDown(budget, window_size);
constexpr u64 MinimumBudget = 256ULL << 20;
if (budget < MinimumBudget) {
return 0;
}
return static_cast<size_t>(budget);
}
bool InitAhbBacking() {
if (!Settings::values.use_unified_memory.GetValue()) {
return false;
}
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
ResolveGetNativeHandle();
if (get_native_handle == nullptr) {
return false;
}
constexpr size_t window_size = 256ULL << 20;
const size_t budget = ComputeAhbBudget(window_size);
if (budget == 0) {
return false;
}
if (!ProbeAhbBacking(get_native_handle)) {
return false;
}
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
const size_t max_windows = (std::min)(budget, aligned_backing) / window_size;
std::vector<AHardwareBuffer*> buffers;
std::vector<int> buffer_fds;
const auto cleanup = [&] {
for (AHardwareBuffer* buffer : buffers) {
AHardwareBuffer_release(buffer);
}
buffers.clear();
buffer_fds.clear();
};
for (size_t i = 0; i < max_windows; ++i) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
break;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
break;
}
const int buffer_fd = handle->data[0];
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
if (buffer_len < static_cast<off_t>(window_size)) {
AHardwareBuffer_release(buffer);
break;
}
buffers.push_back(buffer);
buffer_fds.push_back(buffer_fd);
}
const size_t num_windows = buffers.size();
if (num_windows == 0) {
return false;
}
const size_t region_size = num_windows * window_size;
const size_t region_base = Common::AlignDown(
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
if (base == MAP_FAILED) {
cleanup();
return false;
}
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
off_t map_offset) {
if (len == 0) {
return true;
}
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
map_offset) == MAP_FAILED) {
munmap(base, backing_size);
cleanup();
return false;
}
return true;
};
if (!map_over_reservation(0, region_base, fd, 0)) {
return false;
}
for (size_t i = 0; i < num_windows; ++i) {
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
0)) {
return false;
}
}
const size_t tail_offset = region_base + region_size;
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
static_cast<off_t>(tail_offset))) {
return false;
}
backing_base = base;
ahb_windows = std::move(buffers);
ahb_fds = std::move(buffer_fds);
ahb_window_size = window_size;
ahb_base = region_base;
ahb_bytes = region_size;
committed_backing_size.store(region_size, std::memory_order_relaxed);
for (const int window_fd : ahb_fds) {
SyncDmaBufCpuAccess(window_fd, DmaBufSyncStart);
}
return true;
}
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
while (length > 0) {
int map_fd = fd;
off_t map_offset = static_cast<off_t>(host_offset);
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t relative = host_offset - ahb_base;
const size_t window = relative / ahb_window_size;
const size_t local = relative % ahb_window_size;
map_fd = ahb_fds[window];
map_offset = static_cast<off_t>(local);
chunk = (std::min)(chunk, ahb_window_size - local);
}
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
virtual_offset += chunk;
host_offset += chunk;
length -= chunk;
}
}
size_t BackingMapCount(size_t host_offset, size_t length) const noexcept {
if (length == 0) {
return 0;
}
if (ahb_bytes == 0) {
return 1;
}
size_t count = 0;
while (length > 0) {
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t local = (host_offset - ahb_base) % ahb_window_size;
chunk = (std::min)(chunk, ahb_window_size - local);
}
host_offset += chunk;
length -= chunk;
++count;
}
return count;
}
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
return ahb_windows;
}
size_t AhbWindowSize() const noexcept {
return ahb_bytes != 0 ? ahb_window_size : 0;
}
size_t AhbBase() const noexcept {
return ahb_base;
}
#endif
~Impl() { ~Impl() {
Release(); Release();
} }
@@ -587,6 +895,12 @@ public:
#ifdef ARCHITECTURE_arm64 #ifdef ARCHITECTURE_arm64
if (True(perms & MemoryPermission::Execute)) if (True(perms & MemoryPermission::Execute))
prot_flags |= PROT_EXEC; prot_flags |= PROT_EXEC;
#endif
#ifdef __ANDROID__
if (ahb_bytes != 0) {
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
return;
}
#endif #endif
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED; int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset); void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
@@ -632,8 +946,18 @@ public:
virtual_base = nullptr; virtual_base = nullptr;
} }
bool IsBackingShared() const noexcept {
#ifdef __ANDROID__
if (ahb_bytes != 0) {
return true;
}
#endif
return fd >= 0;
}
const size_t backing_size; ///< Size of the backing memory in bytes const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
const size_t preferred_offset;
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)}; u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)}; u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
@@ -656,6 +980,21 @@ private:
int ret = close(fd); int ret = close(fd);
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno)); ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
} }
#ifdef __ANDROID__
for (const int window_fd : ahb_fds) {
SyncDmaBufCpuAccess(window_fd, DmaBufSyncEnd);
}
for (AHardwareBuffer* buffer : ahb_windows) {
AHardwareBuffer_release(buffer);
}
ahb_windows.clear();
ahb_fds.clear();
if (ahb_bytes != 0) {
committed_backing_size.store(0, std::memory_order_relaxed);
ahb_bytes = 0;
}
#endif
} }
void AdjustMap(size_t* virtual_offset, size_t* length) { void AdjustMap(size_t* virtual_offset, size_t* length) {
@@ -681,11 +1020,19 @@ private:
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
FreeRegionManager free_manager{}; FreeRegionManager free_manager{};
#ifdef __ANDROID__
std::vector<AHardwareBuffer*> ahb_windows;
std::vector<int> ahb_fds;
size_t ahb_window_size{};
size_t ahb_base{};
size_t ahb_bytes{};
#endif
}; };
#endif // ^^^ POSIX ^^^ #endif // ^^^ POSIX ^^^
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size(backing_size_) : backing_size(backing_size_)
, virtual_size(virtual_size_) , virtual_size(virtual_size_)
{ {
@@ -697,7 +1044,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
#else #else
// Try to allocate a fastmem arena. // Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors. // The implementation will fail with std::bad_alloc on errors.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize); impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
if (impl->Init()) { if (impl->Init()) {
backing_base = impl->backing_base; backing_base = impl->backing_base;
virtual_base = impl->virtual_base; virtual_base = impl->virtual_base;
@@ -767,6 +1114,46 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
std::memset(backing_base + physical_offset, fill_value, length); std::memset(backing_base + physical_offset, fill_value, length);
} }
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
#else
return {};
#endif
}
size_t HostMemory::BackingMapCount(size_t host_offset, size_t length) const noexcept {
#ifdef __ANDROID__
return impl ? impl->BackingMapCount(host_offset, length) : (length != 0 ? 1 : 0);
#else
return length != 0 ? 1 : 0;
#endif
}
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindowSize() : 0;
#else
return 0;
#endif
}
bool HostMemory::IsBackingShared() const noexcept {
#if defined(__OPENORBIS__) || defined(__managarm__)
return false;
#else
return impl && impl->IsBackingShared();
#endif
}
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbBase() : 0;
#else
return 0;
#endif
}
void HostMemory::EnableDirectMappedAddress() { void HostMemory::EnableDirectMappedAddress() {
#if !(defined(__OPENORBIS__) || defined(__managarm__)) #if !(defined(__OPENORBIS__) || defined(__managarm__))
if (impl) { if (impl) {
+20 -1
View File
@@ -8,12 +8,17 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/virtual_buffer.h" #include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Common { namespace Common {
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
enum class MemoryPermission : u32 { enum class MemoryPermission : u32 {
Read = 1 << 0, Read = 1 << 0,
Write = 1 << 1, Write = 1 << 1,
@@ -28,7 +33,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
*/ */
class HostMemory { class HostMemory {
public: public:
explicit HostMemory(size_t backing_size_, size_t virtual_size_); explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
~HostMemory(); ~HostMemory();
/** /**
@@ -62,6 +67,20 @@ public:
return backing_base; return backing_base;
} }
[[nodiscard]] size_t BackingSize() const noexcept {
return backing_size;
}
[[nodiscard]] size_t BackingMapCount(size_t host_offset, size_t length) const noexcept;
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
[[nodiscard]] bool IsBackingShared() const noexcept;
[[nodiscard]] u8* VirtualBasePointer() noexcept { [[nodiscard]] u8* VirtualBasePointer() noexcept {
return virtual_base; return virtual_base;
} }
+2 -2
View File
@@ -9,7 +9,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "common/logging.h" #include "common/logging.h"
@@ -412,7 +412,7 @@ public:
namespace Impl { namespace Impl {
template <typename InputDeviceType> template <typename InputDeviceType>
using FactoryListType = ::Common::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>; using FactoryListType = ankerl::unordered_dense::map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
template <typename InputDeviceType> template <typename InputDeviceType>
struct FactoryList { struct FactoryList {
+17 -29
View File
@@ -224,7 +224,7 @@ struct ColorConsoleBackend final : public Backend {
auto const df = GetDirectFormatArgs(entry); auto const df = GetDirectFormatArgs(entry);
// more restrictive, because take for example this simple prelude: // more restrictive, because take for example this simple prelude:
// [ 50.872256] Config <Info> common/settings.cpp:142:LogSettings: // [ 50.872256] Config <Info> common/settings.cpp:142:LogSettings:
char buffer[256]; char buffer[128];
auto result = fmt::format_to_n(buffer, sizeof(buffer) - 1, "\x1b{}[{:4d}.{:06d}] {} <{}> {}:{}:{}: ", color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message); auto result = fmt::format_to_n(buffer, sizeof(buffer) - 1, "\x1b{}[{:4d}.{:06d}] {} <{}> {}:{}:{}: ", color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
std::fwrite(buffer, 1, (std::min)(sizeof(buffer) - 1, result.size), stdout); std::fwrite(buffer, 1, (std::min)(sizeof(buffer) - 1, result.size), stdout);
std::fwrite(entry.message, 1, entry.message_len, stdout); std::fwrite(entry.message, 1, entry.message_len, stdout);
@@ -329,7 +329,7 @@ struct LogcatBackend : public Backend {
} }
}(); }();
auto const df = GetDirectFormatArgs(entry); auto const df = GetDirectFormatArgs(entry);
__android_log_print(android_log_priority, "YuzuNative", "%s %s:%u:%s: %s", df.class_name, entry.filename, entry.line_num, entry.function, entry.message); __android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
} }
void Flush() noexcept override {} void Flush() noexcept override {}
}; };
@@ -425,36 +425,24 @@ void SetColorConsoleBackendEnabled(bool enabled) {
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) { void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) { if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) {
auto const flush = ::Settings::values.log_flush_line.GetValue();
char buffer[BUFSIZ]; char buffer[BUFSIZ];
auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args); auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args);
Entry e{ buffer[result.size] = '\0';
.message = nullptr, auto const flush = ::Settings::values.log_flush_line.GetValue();
.message_len = 0, logging_instance->ForEachBackend([=](Backend& backend) {
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin), backend.Write(Entry{
.log_class = log_class, .message = buffer,
.log_level = log_level, .message_len = (std::min)(sizeof(buffer) - 1, result.size),
.filename = TrimSourcePath(filename), .timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
.function = function, .log_class = log_class,
.line_num = line_num, .log_level = log_level,
}; .filename = TrimSourcePath(filename),
if (result.size <= sizeof(buffer) - 1) { .function = function,
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0'; .line_num = line_num,
e.message = buffer;
e.message_len = (std::min)(result.size, sizeof(buffer) - 1);
logging_instance->ForEachBackend([=](Backend& backend) {
backend.Write(e);
if (flush) backend.Flush();
}); });
} else { if (flush)
std::string s = fmt::vformat(format, args); backend.Flush();
e.message = s.c_str(); });
e.message_len = s.size();
logging_instance->ForEachBackend([=](Backend& backend) {
backend.Write(e);
if (flush) backend.Flush();
});
}
} }
} }
} // namespace Common::Log } // namespace Common::Log
+64
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -17,6 +20,10 @@
#endif #endif
#endif #endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "common/memory_detect.h" #include "common/memory_detect.h"
namespace Common { namespace Common {
@@ -69,4 +76,61 @@ const MemoryInfo& GetMemInfo() {
return mem_info; return mem_info;
} }
u64 GetPermissibleMapCount() {
constexpr u64 DefaultMapCount = 65530;
constexpr u64 ReservedMaps = 20000;
u64 count = DefaultMapCount;
#ifdef __linux__
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
char line[32];
if (std::fgets(line, sizeof(line), file) != nullptr) {
const u64 parsed = std::strtoull(line, nullptr, 10);
if (parsed != 0) {
count = parsed;
}
}
std::fclose(file);
}
#endif
if (count <= ReservedMaps) {
return 0;
}
return count - ReservedMaps;
}
u64 GetAvailablePhysicalMemory() {
#ifdef _WIN32
MEMORYSTATUSEX memorystatus;
memorystatus.dwLength = sizeof(memorystatus);
if (GlobalMemoryStatusEx(&memorystatus) == 0) {
return 0;
}
return memorystatus.ullAvailPhys;
#elif defined(__linux__)
static constexpr char AvailableKey[] = "MemAvailable:";
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
char line[256];
u64 available = 0;
while (std::fgets(line, sizeof(line), file) != nullptr) {
if (std::strncmp(line, AvailableKey, sizeof(AvailableKey) - 1) != 0) {
continue;
}
available = std::strtoull(line + sizeof(AvailableKey) - 1, nullptr, 10) * 1024;
break;
}
std::fclose(file);
if (available != 0) {
return available;
}
}
struct sysinfo meminfo;
if (sysinfo(&meminfo) != 0) {
return 0;
}
return static_cast<u64>(meminfo.freeram) * static_cast<u64>(meminfo.mem_unit);
#else
return 0;
#endif
}
} // namespace Common } // namespace Common
+7
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -18,4 +21,8 @@ struct MemoryInfo {
*/ */
[[nodiscard]] const MemoryInfo& GetMemInfo(); [[nodiscard]] const MemoryInfo& GetMemInfo();
[[nodiscard]] u64 GetPermissibleMapCount();
[[nodiscard]] u64 GetAvailablePhysicalMemory();
} // namespace Common } // namespace Common
+4 -4
View File
@@ -11,14 +11,14 @@
namespace Common::Net { namespace Common::Net {
struct Asset { typedef struct {
std::string name; std::string name;
std::string url; std::string url;
std::string path; std::string path;
std::string filename; std::string filename;
}; } Asset;
struct Release { typedef struct Release {
std::string title; std::string title;
std::string body; std::string body;
std::string tag; std::string tag;
@@ -39,7 +39,7 @@ struct Release {
static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo); static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo);
static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo); static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo);
static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo); static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo);
}; } Release;
// Make a request via httplib, and return the response body if applicable. // Make a request via httplib, and return the response body if applicable.
std::optional<std::string> MakeRequest(const std::string &url, const std::string &path); std::optional<std::string> MakeRequest(const std::string &url, const std::string &path);
+2 -2
View File
@@ -8,14 +8,14 @@
#include <initializer_list> #include <initializer_list>
#include <string> #include <string>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
namespace Common { namespace Common {
/// A string-based key-value container supporting serializing to and deserializing from a string /// A string-based key-value container supporting serializing to and deserializing from a string
class ParamPackage { class ParamPackage {
public: public:
using DataType = ::Common::unordered_map<std::string, std::string>; using DataType = ankerl::unordered_dense::map<std::string, std::string>;
ParamPackage() = default; ParamPackage() = default;
explicit ParamPackage(const std::string& serialized); explicit ParamPackage(const std::string& serialized);
+79
View File
@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/vector_math.h"
namespace Common {
template <typename T>
class Quaternion {
public:
Vec3<T> xyz;
T w{};
[[nodiscard]] Quaternion<decltype(-T{})> Inverse() const {
return {-xyz, w};
}
[[nodiscard]] Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
return {xyz + other.xyz, w + other.w};
}
[[nodiscard]] Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
return {xyz - other.xyz, w - other.w};
}
[[nodiscard]] Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(
const Quaternion& other) const {
return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
w * other.w - Dot(xyz, other.xyz)};
}
[[nodiscard]] Quaternion<T> Normalized() const {
T length = std::sqrt(xyz.Length2() + w * w);
return {xyz / length, w / length};
}
[[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const {
const T x2 = xyz[0] * xyz[0];
const T y2 = xyz[1] * xyz[1];
const T z2 = xyz[2] * xyz[2];
const T xy = xyz[0] * xyz[1];
const T wz = w * xyz[2];
const T xz = xyz[0] * xyz[2];
const T wy = w * xyz[1];
const T yz = xyz[1] * xyz[2];
const T wx = w * xyz[0];
return {1.0f - 2.0f * (y2 + z2),
2.0f * (xy + wz),
2.0f * (xz - wy),
0.0f,
2.0f * (xy - wz),
1.0f - 2.0f * (x2 + z2),
2.0f * (yz + wx),
0.0f,
2.0f * (xz + wy),
2.0f * (yz - wx),
1.0f - 2.0f * (x2 + y2),
0.0f,
0.0f,
0.0f,
0.0f,
1.0f};
}
};
template <typename T>
[[nodiscard]] auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) {
return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
}
[[nodiscard]] inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) {
return {axis * std::sin(angle / 2), std::cos(angle / 2)};
}
} // namespace Common
+7 -5
View File
@@ -126,15 +126,17 @@ void LogSettings() {
setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category), setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category),
setting->GetLabel()); setting->GetLabel());
if (is_default) if (is_default)
settings_list.push_back(fmt::format("{}: {}", name, setting->Canonicalize())); settings_list.push_back(fmt::format("{}: {}\n", name, setting->Canonicalize()));
else else
settings_list.push_front(fmt::format("{}: {}", name, setting->Canonicalize())); settings_list.push_front(fmt::format("{}: {}\n", name, setting->Canonicalize()));
} }
} }
} }
LOG_INFO(Config, "Eden Configuration:");
std::string settings_str{};
for (auto const& e : settings_list) for (auto const& e : settings_list)
LOG_INFO(Config, "{}", e); settings_str += e;
LOG_INFO(Config, "Eden Configuration:\n{}", settings_str);
#define LOG_PATH(NAME) \ #define LOG_PATH(NAME) \
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME))) LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
LOG_PATH(CacheDir); LOG_PATH(CacheDir);
@@ -146,7 +148,7 @@ void LogSettings() {
#undef LOG_PATH #undef LOG_PATH
} }
bool GetDebugKnobAt(u8 i) { bool getDebugKnobAt(u8 i) {
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0; return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
} }
+5 -2
View File
@@ -645,6 +645,9 @@ struct Values {
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks}; Category::RendererHacks};
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
Category::RendererHacks};
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage, SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
GpuUnswizzleSize::Large, GpuUnswizzleSize::Large,
"gpu_unswizzle_texture_size", "gpu_unswizzle_texture_size",
@@ -904,7 +907,7 @@ struct Values {
0, 0,
65535, 65535,
"debug_knobs", "debug_knobs",
Category::System, Category::Debugging,
Specialization::Countable, Specialization::Countable,
true, true,
true}; true};
@@ -947,7 +950,7 @@ constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
[[nodiscard]] size_t FrameGenMaxGenerations(); [[nodiscard]] size_t FrameGenMaxGenerations();
bool GetDebugKnobAt(u8 i); bool getDebugKnobAt(u8 i);
void UpdateGPUAccuracy(); void UpdateGPUAccuracy();
bool IsGPULevelHigh(); bool IsGPULevelHigh();
+2
View File
@@ -7,4 +7,6 @@
#define STB_IMAGE_IMPLEMENTATION 1 #define STB_IMAGE_IMPLEMENTATION 1
#define STB_IMAGE_RESIZE_IMPLEMENTATION 1 #define STB_IMAGE_RESIZE_IMPLEMENTATION 1
#define STB_IMAGE_WRITE_IMPLEMENTATION 1 #define STB_IMAGE_WRITE_IMPLEMENTATION 1
#define STBI_ONLY_JPEG 1
#include "common/stb.h" #include "common/stb.h"
-1
View File
@@ -7,7 +7,6 @@
#pragma once #pragma once
#define STBI_ONLY_JPEG 1 #define STBI_ONLY_JPEG 1
#define STBI_WRITE_NO_STDIO 1
#include <stb_image.h> #include <stb_image.h>
#include <stb_image_resize.h> #include <stb_image_resize.h>
#include <stb_image_write.h> #include <stb_image_write.h>
+560 -85
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Tony Wasserka // SPDX-FileCopyrightText: 2014 Tony Wasserka
@@ -21,7 +21,7 @@ class Vec2;
template <typename T> template <typename T>
class Vec3; class Vec3;
template <typename T> template <typename T>
class Quaternion; class Vec4;
template <typename T> template <typename T>
class Vec2 { class Vec2 {
@@ -30,82 +30,141 @@ public:
T y{}; T y{};
constexpr Vec2() = default; constexpr Vec2() = default;
constexpr Vec2(const T& x_, const T& y_) noexcept : x(x_), y(y_) {} constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {}
[[nodiscard]] constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const noexcept { template <typename T2>
[[nodiscard]] constexpr Vec2<T2> Cast() const {
return Vec2<T2>(static_cast<T2>(x), static_cast<T2>(y));
}
[[nodiscard]] static constexpr Vec2 AssignToAll(const T& f) {
return Vec2{f, f};
}
[[nodiscard]] constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
return {x + other.x, y + other.y}; return {x + other.x, y + other.y};
} }
constexpr Vec2& operator+=(const Vec2& other) noexcept { constexpr Vec2& operator+=(const Vec2& other) {
x += other.x; x += other.x;
y += other.y; y += other.y;
return *this; return *this;
} }
[[nodiscard]] constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const noexcept { [[nodiscard]] constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
return {x - other.x, y - other.y}; return {x - other.x, y - other.y};
} }
constexpr Vec2& operator-=(const Vec2& other) noexcept { constexpr Vec2& operator-=(const Vec2& other) {
x -= other.x; x -= other.x;
y -= other.y; y -= other.y;
return *this; return *this;
} }
template <typename U = T> template <typename U = T>
[[nodiscard]] constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const noexcept { [[nodiscard]] constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y}; return {-x, -y};
} }
[[nodiscard]] constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const noexcept { [[nodiscard]] constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
return {x * other.x, y * other.y}; return {x * other.x, y * other.y};
} }
template <typename V> template <typename V>
[[nodiscard]] constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const noexcept { [[nodiscard]] constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const {
using TV = decltype(T{} * V{}); using TV = decltype(T{} * V{});
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
return { return {
TV(C(x) * C(f)), static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
TV(C(y) * C(f)), static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
}; };
} }
template <typename V> template <typename V>
constexpr Vec2& operator*=(const V& f) noexcept { constexpr Vec2& operator*=(const V& f) {
return *this = *this * f; *this = *this * f;
return *this;
} }
template <typename V> template <typename V>
[[nodiscard]] constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const noexcept { [[nodiscard]] constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
using TV = decltype(T{} / V{}); using TV = decltype(T{} / V{});
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
return { return {
TV(C(x) / C(f)), static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
TV(C(y) / C(f)), static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
}; };
} }
template <typename V> template <typename V>
constexpr Vec2& operator/=(const V& f) noexcept { constexpr Vec2& operator/=(const V& f) {
return *this = *this / f; *this = *this / f;
return *this;
} }
[[nodiscard]] constexpr T Length2() const noexcept { [[nodiscard]] constexpr T Length2() const {
return x * x + y * y; return x * x + y * y;
} }
// Only implemented for T=float // Only implemented for T=float
[[nodiscard]] float Length() const; [[nodiscard]] float Length() const;
[[nodiscard]] float Normalize(); // returns the previous length, which is often useful
[[nodiscard]] constexpr T& operator[](std::size_t i) noexcept { [[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i); return *((&x) + i);
} }
[[nodiscard]] constexpr const T& operator[](std::size_t i) const noexcept { [[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i); return *((&x) + i);
} }
constexpr void SetZero() {
x = 0;
y = 0;
}
// Common aliases: UV (texel coordinates), ST (texture coordinates)
[[nodiscard]] constexpr T& u() {
return x;
}
[[nodiscard]] constexpr T& v() {
return y;
}
[[nodiscard]] constexpr T& s() {
return x;
}
[[nodiscard]] constexpr T& t() {
return y;
}
[[nodiscard]] constexpr const T& u() const {
return x;
}
[[nodiscard]] constexpr const T& v() const {
return y;
}
[[nodiscard]] constexpr const T& s() const {
return x;
}
[[nodiscard]] constexpr const T& t() const {
return y;
}
// swizzlers - create a subvector of specific components
[[nodiscard]] constexpr Vec2 yx() const {
return Vec2(y, x);
}
[[nodiscard]] constexpr Vec2 vu() const {
return Vec2(y, x);
}
[[nodiscard]] constexpr Vec2 ts() const {
return Vec2(y, x);
}
}; };
template <typename T, typename V> template <typename T, typename V>
[[nodiscard]] constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) noexcept { [[nodiscard]] constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
return Vec2<T>(T(C(f) * C(vec.x)), T(C(f) * C(vec.y)));
return Vec2<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)),
static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y)));
} }
using Vec2f = Vec2<float>; using Vec2f = Vec2<float>;
@@ -115,6 +174,13 @@ inline float Vec2<float>::Length() const {
return std::sqrt(x * x + y * y); return std::sqrt(x * x + y * y);
} }
template <>
inline float Vec2<float>::Normalize() {
float length = Length();
*this /= length;
return length;
}
template <typename T> template <typename T>
class Vec3 { class Vec3 {
public: public:
@@ -123,24 +189,33 @@ public:
T z{}; T z{};
constexpr Vec3() = default; constexpr Vec3() = default;
constexpr Vec3(const T& x_, const T& y_, const T& z_) noexcept : x(x_), y(y_), z(z_) {} constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {}
[[nodiscard]] constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const noexcept { template <typename T2>
[[nodiscard]] constexpr Vec3<T2> Cast() const {
return Vec3<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z));
}
[[nodiscard]] static constexpr Vec3 AssignToAll(const T& f) {
return Vec3(f, f, f);
}
[[nodiscard]] constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
return {x + other.x, y + other.y, z + other.z}; return {x + other.x, y + other.y, z + other.z};
} }
constexpr Vec3 operator+=(const Vec3& other) noexcept { constexpr Vec3& operator+=(const Vec3& other) {
x += other.x; x += other.x;
y += other.y; y += other.y;
z += other.z; z += other.z;
return *this; return *this;
} }
[[nodiscard]] constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const noexcept { [[nodiscard]] constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
return {x - other.x, y - other.y, z - other.z}; return {x - other.x, y - other.y, z - other.z};
} }
constexpr Vec3 operator-=(const Vec3& other) noexcept { constexpr Vec3& operator-=(const Vec3& other) {
x -= other.x; x -= other.x;
y -= other.y; y -= other.y;
z -= other.z; z -= other.z;
@@ -148,68 +223,177 @@ public:
} }
template <typename U = T> template <typename U = T>
[[nodiscard]] constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const noexcept { [[nodiscard]] constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y, -z}; return {-x, -y, -z};
} }
[[nodiscard]] constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const noexcept { [[nodiscard]] constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
return {x * other.x, y * other.y, z * other.z}; return {x * other.x, y * other.y, z * other.z};
} }
template <typename V> template <typename V>
[[nodiscard]] constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const noexcept { [[nodiscard]] constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
using TV = decltype(T{} * V{}); using TV = decltype(T{} * V{});
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
return { return {
TV(C(x) * C(f)), static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
TV(C(y) * C(f)), static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
TV(C(z) * C(f)), static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)),
}; };
} }
template <typename V> template <typename V>
constexpr Vec3 operator*=(const V& f) noexcept { constexpr Vec3& operator*=(const V& f) {
return *this = *this * f; *this = *this * f;
return *this;
} }
template <typename V> template <typename V>
[[nodiscard]] constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const noexcept { [[nodiscard]] constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
using TV = decltype(T{} / V{}); using TV = decltype(T{} / V{});
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
return { return {
TV(C(x) / C(f)), static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
TV(C(y) / C(f)), static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
TV(C(z) / C(f)), static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)),
}; };
} }
template <typename V> template <typename V>
constexpr Vec3& operator/=(const V& f) noexcept { constexpr Vec3& operator/=(const V& f) {
return *this = *this / f; *this = *this / f;
return *this;
} }
[[nodiscard]] constexpr T Length2() const noexcept { void RotateFromOrigin(float roll, float pitch, float yaw) {
float temp = y;
y = std::cos(roll) * y - std::sin(roll) * z;
z = std::sin(roll) * temp + std::cos(roll) * z;
temp = x;
x = std::cos(pitch) * x + std::sin(pitch) * z;
z = -std::sin(pitch) * temp + std::cos(pitch) * z;
temp = x;
x = std::cos(yaw) * x - std::sin(yaw) * y;
y = std::sin(yaw) * temp + std::cos(yaw) * y;
}
[[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z; return x * x + y * y + z * z;
} }
// Only implemented for T=float // Only implemented for T=float
[[nodiscard]] float Length() const; [[nodiscard]] float Length() const;
[[nodiscard]] Vec3 Normalized() const; [[nodiscard]] Vec3 Normalized() const;
[[nodiscard]] float Normalize(); // returns the previous length, which is often useful
[[nodiscard]] constexpr T& operator[](std::size_t i) noexcept { [[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i); return *((&x) + i);
} }
[[nodiscard]] constexpr const T& operator[](std::size_t i) const noexcept { [[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i); return *((&x) + i);
} }
constexpr void SetZero() {
x = 0;
y = 0;
z = 0;
}
// Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates)
[[nodiscard]] constexpr T& u() {
return x;
}
[[nodiscard]] constexpr T& v() {
return y;
}
[[nodiscard]] constexpr T& w() {
return z;
}
[[nodiscard]] constexpr T& r() {
return x;
}
[[nodiscard]] constexpr T& g() {
return y;
}
[[nodiscard]] constexpr T& b() {
return z;
}
[[nodiscard]] constexpr T& s() {
return x;
}
[[nodiscard]] constexpr T& t() {
return y;
}
[[nodiscard]] constexpr T& q() {
return z;
}
[[nodiscard]] constexpr const T& u() const {
return x;
}
[[nodiscard]] constexpr const T& v() const {
return y;
}
[[nodiscard]] constexpr const T& w() const {
return z;
}
[[nodiscard]] constexpr const T& r() const {
return x;
}
[[nodiscard]] constexpr const T& g() const {
return y;
}
[[nodiscard]] constexpr const T& b() const {
return z;
}
[[nodiscard]] constexpr const T& s() const {
return x;
}
[[nodiscard]] constexpr const T& t() const {
return y;
}
[[nodiscard]] constexpr const T& q() const {
return z;
}
// swizzlers - create a subvector of specific components
// e.g. Vec2 uv() { return Vec2(x,y); }
// _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all
// component names (x<->r) and permutations (xy<->yx)
#define _DEFINE_SWIZZLER2(a, b, name) \
[[nodiscard]] constexpr Vec2<T> name() const { return Vec2<T>(a, b); }
#define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \
_DEFINE_SWIZZLER2(a, b, a##b); \
_DEFINE_SWIZZLER2(a, b, a2##b2); \
_DEFINE_SWIZZLER2(a, b, a3##b3); \
_DEFINE_SWIZZLER2(a, b, a4##b4); \
_DEFINE_SWIZZLER2(b, a, b##a); \
_DEFINE_SWIZZLER2(b, a, b2##a2); \
_DEFINE_SWIZZLER2(b, a, b3##a3); \
_DEFINE_SWIZZLER2(b, a, b4##a4)
DEFINE_SWIZZLER2(x, y, r, g, u, v, s, t);
DEFINE_SWIZZLER2(x, z, r, b, u, w, s, q);
DEFINE_SWIZZLER2(y, z, g, b, v, w, t, q);
#undef DEFINE_SWIZZLER2
#undef _DEFINE_SWIZZLER2
}; };
template <typename T, typename V> template <typename T, typename V>
[[nodiscard]] constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) noexcept { [[nodiscard]] constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
return Vec3<T>(T(C(f) * C(vec.x)), T(C(f) * C(vec.y)), T(C(f) * C(vec.z)));
return Vec3<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)),
static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y)),
static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.z)));
} }
template <> template <>
@@ -222,62 +406,353 @@ inline Vec3<float> Vec3<float>::Normalized() const {
return *this / Length(); return *this / Length();
} }
template <>
inline float Vec3<float>::Normalize() {
float length = Length();
*this /= length;
return length;
}
using Vec3f = Vec3<float>; using Vec3f = Vec3<float>;
template <typename T> template <typename T>
class Quaternion { class Vec4 {
public: public:
Vec3<T> xyz; T x{};
T y{};
T z{};
T w{}; T w{};
[[nodiscard]] Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const noexcept { constexpr Vec4() = default;
return {xyz + other.xyz, w + other.w}; constexpr Vec4(const T& x_, const T& y_, const T& z_, const T& w_)
: x(x_), y(y_), z(z_), w(w_) {}
template <typename T2>
[[nodiscard]] constexpr Vec4<T2> Cast() const {
return Vec4<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z),
static_cast<T2>(w));
} }
[[nodiscard]] Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const noexcept { [[nodiscard]] static constexpr Vec4 AssignToAll(const T& f) {
return {xyz - other.xyz, w - other.w}; return Vec4(f, f, f, f);
} }
[[nodiscard]] Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const noexcept { [[nodiscard]] constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz), w * other.w - Dot(xyz, other.xyz)}; return {x + other.x, y + other.y, z + other.z, w + other.w};
} }
[[nodiscard]] Quaternion<T> Normalized() const noexcept { constexpr Vec4& operator+=(const Vec4& other) {
T length = std::sqrt(xyz.Length2() + w * w); x += other.x;
return {xyz / length, w / length}; y += other.y;
z += other.z;
w += other.w;
return *this;
} }
[[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const { [[nodiscard]] constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
const T x2 = xyz[0] * xyz[0]; return {x - other.x, y - other.y, z - other.z, w - other.w};
const T y2 = xyz[1] * xyz[1]; }
const T z2 = xyz[2] * xyz[2];
const T xy = xyz[0] * xyz[1]; constexpr Vec4& operator-=(const Vec4& other) {
const T wz = w * xyz[2]; x -= other.x;
const T xz = xyz[0] * xyz[2]; y -= other.y;
const T wy = w * xyz[1]; z -= other.z;
const T yz = xyz[1] * xyz[2]; w -= other.w;
const T wx = w * xyz[0]; return *this;
}
template <typename U = T>
[[nodiscard]] constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y, -z, -w};
}
[[nodiscard]] constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
return {x * other.x, y * other.y, z * other.z, w * other.w};
}
template <typename V>
[[nodiscard]] constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
using TV = decltype(T{} * V{});
using C = std::common_type_t<T, V>;
return { return {
1.0f - 2.0f * (y2 + z2), static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
2.0f * (xy + wz), static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
2.0f * (xz - wy), static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)),
0.0f, static_cast<TV>(static_cast<C>(w) * static_cast<C>(f)),
2.0f * (xy - wz),
1.0f - 2.0f * (x2 + z2),
2.0f * (yz + wx),
0.0f,
2.0f * (xz + wy),
2.0f * (yz - wx),
1.0f - 2.0f * (x2 + y2),
0.0f,
0.0f,
0.0f,
0.0f,
1.0f
}; };
} }
template <typename V>
constexpr Vec4& operator*=(const V& f) {
*this = *this * f;
return *this;
}
template <typename V>
[[nodiscard]] constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
using TV = decltype(T{} / V{});
using C = std::common_type_t<T, V>;
return {
static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)),
static_cast<TV>(static_cast<C>(w) / static_cast<C>(f)),
};
}
template <typename V>
constexpr Vec4& operator/=(const V& f) {
*this = *this / f;
return *this;
}
[[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z + w * w;
}
[[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
[[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
constexpr void SetZero() {
x = 0;
y = 0;
z = 0;
w = 0;
}
// Common alias: RGBA (colors)
[[nodiscard]] constexpr T& r() {
return x;
}
[[nodiscard]] constexpr T& g() {
return y;
}
[[nodiscard]] constexpr T& b() {
return z;
}
[[nodiscard]] constexpr T& a() {
return w;
}
[[nodiscard]] constexpr const T& r() const {
return x;
}
[[nodiscard]] constexpr const T& g() const {
return y;
}
[[nodiscard]] constexpr const T& b() const {
return z;
}
[[nodiscard]] constexpr const T& a() const {
return w;
}
// Swizzlers - Create a subvector of specific components
// e.g. Vec2 uv() { return Vec2(x,y); }
// _DEFINE_SWIZZLER2 defines a single such function
// DEFINE_SWIZZLER2_COMP1 defines one-component functions for all component names (x<->r)
// DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and
// permutations (xy<->yx)
#define _DEFINE_SWIZZLER2(a, b, name) \
[[nodiscard]] constexpr Vec2<T> name() const { return Vec2<T>(a, b); }
#define DEFINE_SWIZZLER2_COMP1(a, a2) \
_DEFINE_SWIZZLER2(a, a, a##a); \
_DEFINE_SWIZZLER2(a, a, a2##a2)
#define DEFINE_SWIZZLER2_COMP2(a, b, a2, b2) \
_DEFINE_SWIZZLER2(a, b, a##b); \
_DEFINE_SWIZZLER2(a, b, a2##b2); \
_DEFINE_SWIZZLER2(b, a, b##a); \
_DEFINE_SWIZZLER2(b, a, b2##a2)
DEFINE_SWIZZLER2_COMP2(x, y, r, g);
DEFINE_SWIZZLER2_COMP2(x, z, r, b);
DEFINE_SWIZZLER2_COMP2(x, w, r, a);
DEFINE_SWIZZLER2_COMP2(y, z, g, b);
DEFINE_SWIZZLER2_COMP2(y, w, g, a);
DEFINE_SWIZZLER2_COMP2(z, w, b, a);
DEFINE_SWIZZLER2_COMP1(x, r);
DEFINE_SWIZZLER2_COMP1(y, g);
DEFINE_SWIZZLER2_COMP1(z, b);
DEFINE_SWIZZLER2_COMP1(w, a);
#undef DEFINE_SWIZZLER2_COMP1
#undef DEFINE_SWIZZLER2_COMP2
#undef _DEFINE_SWIZZLER2
#define _DEFINE_SWIZZLER3(a, b, c, name) \
[[nodiscard]] constexpr Vec3<T> name() const { return Vec3<T>(a, b, c); }
#define DEFINE_SWIZZLER3_COMP1(a, a2) \
_DEFINE_SWIZZLER3(a, a, a, a##a##a); \
_DEFINE_SWIZZLER3(a, a, a, a2##a2##a2)
#define DEFINE_SWIZZLER3_COMP3(a, b, c, a2, b2, c2) \
_DEFINE_SWIZZLER3(a, b, c, a##b##c); \
_DEFINE_SWIZZLER3(a, c, b, a##c##b); \
_DEFINE_SWIZZLER3(b, a, c, b##a##c); \
_DEFINE_SWIZZLER3(b, c, a, b##c##a); \
_DEFINE_SWIZZLER3(c, a, b, c##a##b); \
_DEFINE_SWIZZLER3(c, b, a, c##b##a); \
_DEFINE_SWIZZLER3(a, b, c, a2##b2##c2); \
_DEFINE_SWIZZLER3(a, c, b, a2##c2##b2); \
_DEFINE_SWIZZLER3(b, a, c, b2##a2##c2); \
_DEFINE_SWIZZLER3(b, c, a, b2##c2##a2); \
_DEFINE_SWIZZLER3(c, a, b, c2##a2##b2); \
_DEFINE_SWIZZLER3(c, b, a, c2##b2##a2)
DEFINE_SWIZZLER3_COMP3(x, y, z, r, g, b);
DEFINE_SWIZZLER3_COMP3(x, y, w, r, g, a);
DEFINE_SWIZZLER3_COMP3(x, z, w, r, b, a);
DEFINE_SWIZZLER3_COMP3(y, z, w, g, b, a);
DEFINE_SWIZZLER3_COMP1(x, r);
DEFINE_SWIZZLER3_COMP1(y, g);
DEFINE_SWIZZLER3_COMP1(z, b);
DEFINE_SWIZZLER3_COMP1(w, a);
#undef DEFINE_SWIZZLER3_COMP1
#undef DEFINE_SWIZZLER3_COMP3
#undef _DEFINE_SWIZZLER3
}; };
template <typename T, typename V>
[[nodiscard]] constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
using TV = decltype(V{} * T{});
using C = std::common_type_t<T, V>;
return {
static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.x)),
static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.y)),
static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.z)),
static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.w)),
};
}
using Vec4f = Vec4<float>;
template <typename T>
constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) {
return a.x * b.x + a.y * b.y;
}
template <typename T>
[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
template <typename T>
[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
template <>
[[nodiscard]] inline float Dot(const Vec4<float>& a, const Vec4<float>& b) {
#ifdef __ARM_NEON
float32x4_t va = vld1q_f32(&a.x);
float32x4_t vb = vld1q_f32(&b.x);
float32x4_t result = vmulq_f32(va, vb);
#if defined(__aarch64__) // Use vaddvq_f32 in ARMv8 architectures
return vaddvq_f32(result);
#else // Use manual addition for older architectures
float32x2_t sum2 = vadd_f32(vget_high_f32(result), vget_low_f32(result));
return vget_lane_f32(vpadd_f32(sum2, sum2), 0);
#endif
#else
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
#endif
}
template <typename T>
[[nodiscard]] constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a,
const Vec3<T>& b) {
return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}
// linear interpolation via float: 0.0=begin, 1.0=end
template <typename X>
[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
const float t) {
return begin * (1.f - t) + end * t;
}
// linear interpolation via int: 0=begin, base=end
template <typename X, int base>
[[nodiscard]] constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin,
const X& end,
const int t) {
return (begin * (base - t) + end * t) / base;
}
// bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second
// interpolation.
template <typename X>
[[nodiscard]] constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11,
const float s, const float t) {
auto y0 = Lerp(x00, x01, s);
auto y1 = Lerp(x10, x11, s);
return Lerp(y0, y1, t);
}
// Utility vector factories
template <typename T>
[[nodiscard]] constexpr Vec2<T> MakeVec(const T& x, const T& y) {
return Vec2<T>{x, y};
}
template <typename T>
[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
return Vec3<T>{x, y, z};
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
return MakeVec(x, y, zw[0], zw[1]);
}
template <typename T>
[[nodiscard]] constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
return MakeVec(xy[0], xy[1], z);
}
template <typename T>
[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
return MakeVec(x, yz[0], yz[1]);
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
return Vec4<T>{x, y, z, w};
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
return MakeVec(xy[0], xy[1], z, w);
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
return MakeVec(x, yz[0], yz[1], w);
}
// NOTE: This has priority over "Vec2<Vec2<T>> MakeVec(const Vec2<T>& x, const Vec2<T>& y)".
// Even if someone wanted to use an odd object like Vec2<Vec2<T>>, the compiler would error
// out soon enough due to misuse of the returned structure.
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
return MakeVec(xy[0], xy[1], zw[0], zw[1]);
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
return MakeVec(xyz[0], xyz[1], xyz[2], w);
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
return MakeVec(x, yzw[0], yzw[1], yzw[2]);
}
} // namespace Common } // namespace Common
+3 -4
View File
@@ -22,11 +22,10 @@
#endif #endif
// You must ensure this matches with src/common/x64/xbyak.h on root dir // You must ensure this matches with src/common/x64/xbyak.h on root dir
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include "common/container/unordered_set.h"
#include <boost/unordered_map.hpp> #include <boost/unordered_map.hpp>
#define XBYAK_STD_UNORDERED_SET ::Common::unordered_set #define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set
#define XBYAK_STD_UNORDERED_MAP ::Common::unordered_map #define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map
#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap #define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap
#include <xbyak/xbyak.h> #include <xbyak/xbyak.h>
#include <xbyak/xbyak_util.h> #include <xbyak/xbyak_util.h>
+1 -1
View File
@@ -8,7 +8,7 @@
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include <dynarmic/interface/A64/a64.h> #include <dynarmic/interface/A64/a64.h>
#include <dynarmic/interface/code_page.h> #include <dynarmic/interface/code_page.h>
+2 -2
View File
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <span> #include <span>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include <vector> #include <vector>
#include <oaknut/code_block.hpp> #include <oaknut/code_block.hpp>
#include <oaknut/oaknut.hpp> #include <oaknut/oaknut.hpp>
@@ -46,7 +46,7 @@ enum class PatchMode : u32 {
using ModuleTextAddress = u64; using ModuleTextAddress = u64;
using PatchTextAddress = u64; using PatchTextAddress = u64;
using EntryTrampolines = ::Common::unordered_map<ModuleTextAddress, PatchTextAddress>; using EntryTrampolines = ankerl::unordered_dense::map<ModuleTextAddress, PatchTextAddress>;
class Patcher { class Patcher {
public: public:
+13 -78
View File
@@ -4,7 +4,6 @@
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <unordered_map>
#include <utility> #include <utility>
#include "game_settings.h" #include "game_settings.h"
@@ -120,6 +119,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
core_timing.SetMulticore(is_multicore); core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); }); core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -147,7 +147,8 @@ struct System::Impl {
!device_memory.has_value() || !device_memory.has_value() ||
is_multicore != Settings::values.use_multi_core.GetValue() || is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() != extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
Settings::MemoryLayout::Memory_4Gb); Settings::MemoryLayout::Memory_4Gb) ||
unified_memory != Settings::values.use_unified_memory.GetValue();
if (!must_reinitialize) { if (!must_reinitialize) {
return; return;
@@ -158,6 +159,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = extended_memory_layout =
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
Initialize(system); Initialize(system);
} }
@@ -249,30 +251,12 @@ struct System::Impl {
} }
} }
void NotifyNVDECChannelOpen(u64 process_id) { void SetNVDECActive(bool is_nvdec_active) {
std::scoped_lock lock{nvdec_active_mutex}; nvdec_active = is_nvdec_active;
++nvdec_active_channels[process_id];
}
void NotifyNVDECChannelClose(u64 process_id) {
std::scoped_lock lock{nvdec_active_mutex};
const auto it = nvdec_active_channels.find(process_id);
if (it == nvdec_active_channels.end()) {
return;
}
if (--it->second == 0) {
nvdec_active_channels.erase(it);
}
} }
bool GetNVDECActive() { bool GetNVDECActive() {
std::scoped_lock lock{nvdec_active_mutex}; return nvdec_active;
return !nvdec_active_channels.empty();
}
bool IsNVDECActiveForProcess(u64 process_id) {
std::scoped_lock lock{nvdec_active_mutex};
return nvdec_active_channels.contains(process_id);
} }
void InitializeDebugger(System& system, u16 port) { void InitializeDebugger(System& system, u16 port) {
@@ -314,10 +298,6 @@ struct System::Impl {
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) { SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) {
InitializeKernel(system); InitializeKernel(system);
if (params.applet_type == Service::AM::AppletType::Application) {
current_application_filepath = filepath;
}
const auto file = GetGameFileFromPath(virtual_filesystem, filepath); const auto file = GetGameFileFromPath(virtual_filesystem, filepath);
// Create the application process // Create the application process
@@ -353,7 +333,7 @@ struct System::Impl {
LaunchTimestampCache::SaveLaunchTimestamp(params.program_id); LaunchTimestampCache::SaveLaunchTimestamp(params.program_id);
// Make the process created be the application // Make the process created be the application
kernel.SetApplicationProcess(process->GetHandle()); kernel.MakeApplicationProcess(process->GetHandle());
// Set up the rest of the system. // Set up the rest of the system.
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)}; SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
@@ -490,7 +470,6 @@ struct System::Impl {
Core::SpeedLimiter speed_limiter; Core::SpeedLimiter speed_limiter;
ExecuteProgramCallback execute_program_callback; ExecuteProgramCallback execute_program_callback;
ExitCallback exit_callback; ExitCallback exit_callback;
ApplicationChangedCallback application_changed_callback;
std::optional<Service::Services> services; std::optional<Service::Services> services;
std::optional<Core::Debugger> debugger; std::optional<Core::Debugger> debugger;
@@ -512,8 +491,6 @@ struct System::Impl {
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{}; std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<u8, 0x20> build_id{}; std::array<u8, 0x20> build_id{};
std::string current_application_filepath;
/// Service manager /// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager; std::shared_ptr<Service::SM::ServiceManager> service_manager;
/// ContentProviderUnion instance /// ContentProviderUnion instance
@@ -524,15 +501,15 @@ struct System::Impl {
mutable std::mutex suspend_guard; mutable std::mutex suspend_guard;
std::mutex general_channel_mutex; std::mutex general_channel_mutex;
std::mutex nvdec_active_mutex;
std::unordered_map<u64, u32> nvdec_active_channels;
std::atomic_bool is_paused{}; std::atomic_bool is_paused{};
std::atomic_bool is_shutting_down{}; std::atomic_bool is_shutting_down{};
std::atomic_bool is_powered_on{}; std::atomic_bool is_powered_on{};
bool is_multicore : 1 = false; bool is_multicore : 1 = false;
bool extended_memory_layout : 1 = false; bool extended_memory_layout : 1 = false;
bool unified_memory : 1 = false;
bool exit_locked : 1 = false; bool exit_locked : 1 = false;
bool exit_requested : 1 = false; bool exit_requested : 1 = false;
bool nvdec_active : 1 = false;
void EnsureGeneralChannelInitialized(System& system) { void EnsureGeneralChannelInitialized(System& system) {
if (!general_channel_event) { if (!general_channel_event) {
@@ -596,22 +573,14 @@ void System::UnstallApplication() {
impl->UnstallApplication(); impl->UnstallApplication();
} }
void System::NotifyNVDECChannelOpen(u64 process_id) { void System::SetNVDECActive(bool is_nvdec_active) {
impl->NotifyNVDECChannelOpen(process_id); impl->SetNVDECActive(is_nvdec_active);
}
void System::NotifyNVDECChannelClose(u64 process_id) {
impl->NotifyNVDECChannelClose(process_id);
} }
bool System::GetNVDECActive() { bool System::GetNVDECActive() {
return impl->GetNVDECActive(); return impl->GetNVDECActive();
} }
bool System::IsNVDECActiveForProcess(u64 process_id) {
return impl->IsNVDECActiveForProcess(process_id);
}
void System::InitializeDebugger() { void System::InitializeDebugger() {
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue()); impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
} }
@@ -762,25 +731,7 @@ const Core::SpeedLimiter& System::SpeedLimiter() const {
} }
u64 System::GetApplicationProcessProgramID() const { u64 System::GetApplicationProcessProgramID() const {
const auto* const process = impl->kernel.ApplicationProcess(); return impl->kernel.ApplicationProcess()->GetProgramId();
return process != nullptr ? process->GetProgramId() : 0;
}
u64 System::GetProgramIdForProcessId(u64 process_id) const {
auto process = impl->kernel.GetProcessByProcessId(process_id);
return process.IsNull() ? 0 : process->GetProgramId();
}
u64 System::ResolveCallerProgramId(u64 process_id) const {
if (const auto program_id = this->GetProgramIdForProcessId(process_id); program_id != 0) {
return program_id;
}
const auto fallback = this->GetApplicationProcessProgramID();
LOG_WARNING(Core,
"Could not resolve caller process_id={}, falling back to application {:016X}",
process_id, fallback);
return fallback;
} }
Loader::ResultStatus System::GetGameName(std::string& out) const { Loader::ResultStatus System::GetGameName(std::string& out) const {
@@ -963,10 +914,6 @@ void System::ExecuteProgram(std::size_t program_index) {
} }
} }
const std::string& System::GetCurrentApplicationFilePath() const {
return impl->current_application_filepath;
}
/// @brief Gets a reference to the user channel stack. /// @brief Gets a reference to the user channel stack.
/// It is used to transfer data between programs. /// It is used to transfer data between programs.
std::vector<std::vector<u8>>& System::GetUserChannel() { std::vector<std::vector<u8>>& System::GetUserChannel() {
@@ -1018,18 +965,6 @@ void System::Exit() {
} }
} }
void System::RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback) {
impl->application_changed_callback = std::move(callback);
}
void System::NotifyApplicationChanged(u64 program_id) {
//LOG_DEBUG(Core, "Running application changed to {:016X}", program_id);
if (impl->application_changed_callback) {
impl->application_changed_callback(program_id);
}
}
void System::ApplySettings() { void System::ApplySettings() {
impl->RefreshTime(*this); impl->RefreshTime(*this);
+1 -12
View File
@@ -191,10 +191,8 @@ public:
std::unique_lock<std::mutex> StallApplication(); std::unique_lock<std::mutex> StallApplication();
void UnstallApplication(); void UnstallApplication();
void NotifyNVDECChannelOpen(u64 process_id); void SetNVDECActive(bool is_nvdec_active);
void NotifyNVDECChannelClose(u64 process_id);
[[nodiscard]] bool GetNVDECActive(); [[nodiscard]] bool GetNVDECActive();
[[nodiscard]] bool IsNVDECActiveForProcess(u64 process_id);
/** /**
* Initialize the debugger. * Initialize the debugger.
@@ -324,10 +322,6 @@ public:
[[nodiscard]] u64 GetApplicationProcessProgramID() const; [[nodiscard]] u64 GetApplicationProcessProgramID() const;
[[nodiscard]] u64 GetProgramIdForProcessId(u64 process_id) const;
[[nodiscard]] u64 ResolveCallerProgramId(u64 process_id) const;
/// Gets the name of the current game /// Gets the name of the current game
[[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const; [[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const;
@@ -428,7 +422,6 @@ public:
void PushGeneralChannelData(std::vector<u8>&& data); void PushGeneralChannelData(std::vector<u8>&& data);
bool TryPopGeneralChannel(std::vector<u8>& out_data); bool TryPopGeneralChannel(std::vector<u8>& out_data);
[[nodiscard]] Service::Event& GetGeneralChannelEvent(); [[nodiscard]] Service::Event& GetGeneralChannelEvent();
[[nodiscard]] const std::string& GetCurrentApplicationFilePath() const;
/// Type used for the frontend to designate a callback for System to exit the application. /// Type used for the frontend to designate a callback for System to exit the application.
using ExitCallback = std::function<void()>; using ExitCallback = std::function<void()>;
@@ -442,10 +435,6 @@ public:
/// Instructs the frontend to exit the application. /// Instructs the frontend to exit the application.
void Exit(); void Exit();
using ApplicationChangedCallback = std::function<void(u64 program_id)>;
void RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback);
void NotifyApplicationChanged(u64 program_id);
/// Applies any changes to settings to this core instance. /// Applies any changes to settings to this core instance.
void ApplySettings(); void ApplySettings();
+13 -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 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -12,9 +15,18 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
constexpr size_t VirtualReserveSize = 1ULL << 39; constexpr size_t VirtualReserveSize = 1ULL << 39;
#endif #endif
namespace {
size_t ApplicationPoolOffset() {
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
const size_t dram_size = Init::GetIntendedMemorySize();
const size_t application_pool_size = Init::GetApplicationPoolSize();
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
}
}
DeviceMemory::DeviceMemory() DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(), : buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize} {} VirtualReserveSize, ApplicationPoolOffset()} {}
DeviceMemory::~DeviceMemory() = default; DeviceMemory::~DeviceMemory() = default;
+35
View File
@@ -20,6 +20,8 @@
#include "common/scratch_buffer.h" #include "common/scratch_buffer.h"
#include "common/virtual_buffer.h" #include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Core { namespace Core {
constexpr size_t DEVICE_PAGEBITS = 12ULL; constexpr size_t DEVICE_PAGEBITS = 12ULL;
@@ -95,6 +97,34 @@ public:
ApplyOpOnPAddr(address, buffer, operation); ApplyOpOnPAddr(address, buffer, operation);
} }
u8* GetPhysicalBase() noexcept {
return reinterpret_cast<u8*>(physical_base);
}
const u8* GetPhysicalBase() const noexcept {
return reinterpret_cast<const u8*>(physical_base);
}
size_t GetPhysicalSize() const noexcept {
return physical_size;
}
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
return ahb_windows;
}
size_t GetBackingHardwareBufferWindowSize() const noexcept {
return ahb_window_size;
}
size_t GetBackingHardwareBufferBase() const noexcept {
return ahb_base;
}
bool IsBackingShared() const noexcept {
return backing_is_shared;
}
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const { PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
PAddr subbits = PAddr(address & page_mask); PAddr subbits = PAddr(address & page_mask);
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr; auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
@@ -171,6 +201,11 @@ private:
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl; std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
const uintptr_t physical_base; const uintptr_t physical_base;
const size_t physical_size;
const std::span<AHardwareBuffer* const> ahb_windows;
const size_t ahb_window_size;
const size_t ahb_base;
const bool backing_is_shared;
DeviceInterface* device_inter; DeviceInterface* device_inter;
struct TrackedEntry { struct TrackedEntry {
+5
View File
@@ -171,6 +171,11 @@ struct DeviceMemoryManagerAllocator {
template <typename Traits> template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_) DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())} : physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
, physical_size{device_memory_.buffer.BackingSize()}
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
, device_inter{nullptr} , device_inter{nullptr}
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS)) , compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS) , tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
+2 -2
View File
@@ -29,7 +29,7 @@ static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
return std::max<u8>(key_generation, 1) - 1; return std::max<u8>(key_generation, 1) - 1;
} }
NCA::NCA(VirtualFile file_, const NCA* base_nca, bool allow_missing_base) NCA::NCA(VirtualFile file_, const NCA* base_nca)
: file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) { if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile; status = Loader::ResultStatus::ErrorNullFile;
@@ -110,7 +110,7 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca, bool allow_missing_base)
} }
} }
if (is_update && base_nca == nullptr && !allow_missing_base) { if (is_update && base_nca == nullptr) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
} else { } else {
status = Loader::ResultStatus::Success; status = Loader::ResultStatus::Success;
+1 -4
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -66,7 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
// After construction, use GetStatus to determine if the file is valid and ready to be used. // After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory { class NCA : public ReadOnlyVfsDirectory {
public: public:
explicit NCA(VirtualFile file, const NCA* base_nca = nullptr, bool allow_missing_base = false); explicit NCA(VirtualFile file, const NCA* base_nca = nullptr);
~NCA() override; ~NCA() override;
Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetStatus() const;
@@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging.h"
#include "core/file_sys/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_indirect_storage.h" #include "core/file_sys/fssystem/fssystem_indirect_storage.h"
@@ -100,18 +99,6 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s
R_SUCCEED(); R_SUCCEED();
} }
void IndirectStorage::ReportMissingOriginal(s64 offset, s64 size) {
if (m_reported_missing_original) {
return;
}
m_reported_missing_original = true;
LOG_ERROR(Common_Filesystem,
"Patch storage requests {:#x} bytes at {:#x} from a base storage that is not "
"present; this data will read as garbage",
size, offset);
}
size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const { size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Validate pre-conditions. // Validate pre-conditions.
ASSERT(this->IsInitialized()); ASSERT(this->IsInitialized());
@@ -85,9 +85,6 @@ public:
void SetStorage(s32 idx, VirtualFile storage) { void SetStorage(s32 idx, VirtualFile storage) {
ASSERT(0 <= idx && idx < StorageCount); ASSERT(0 <= idx && idx < StorageCount);
m_data_storage[idx] = storage; m_data_storage[idx] = storage;
if (idx == 0) {
m_original_missing = storage == nullptr || storage->GetSize() == 0;
}
} }
template <typename T> template <typename T>
@@ -133,9 +130,6 @@ protected:
template <bool ContinuousCheck, bool RangeCheck, typename F> template <bool ContinuousCheck, bool RangeCheck, typename F>
Result OperatePerEntry(s64 offset, s64 size, F func); Result OperatePerEntry(s64 offset, s64 size, F func);
// Launching another game makes the original storage inaccessable.
// This is a helper for multi-nca games.
void ReportMissingOriginal(s64 offset, s64 size);
private: private:
struct ContinuousReadingEntry { struct ContinuousReadingEntry {
@@ -160,8 +154,6 @@ private:
private: private:
mutable BucketTree m_table; mutable BucketTree m_table;
std::array<VirtualFile, StorageCount> m_data_storage; std::array<VirtualFile, StorageCount> m_data_storage;
bool m_original_missing{false};
bool m_reported_missing_original{false};
}; };
template <bool ContinuousCheck, bool RangeCheck, typename F> template <bool ContinuousCheck, bool RangeCheck, typename F>
@@ -280,10 +272,6 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
if (needs_operate) { if (needs_operate) {
const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
if (cur_entry.storage_index == 0 && m_original_missing) {
this->ReportMissingOriginal(cur_offset, cur_size);
}
if constexpr (RangeCheck) { if constexpr (RangeCheck) {
// Get the current data storage's size. // Get the current data storage's size.
s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize(); s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
+240 -193
View File
@@ -3,12 +3,10 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <map>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <utility> #include <utility>
#include <span>
#include <cctype>
#include "common/container/unordered_map.h"
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging.h" #include "common/logging.h"
@@ -24,30 +22,61 @@ enum class IPSFileType {
Error, Error,
}; };
static IPSFileType IdentifyMagic(std::span<const u8> magic) { constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{
if (magic.size() >= 5) { {"\\a", "\a"},
if (std::memcmp(magic.data(), "PATCH", 5) == 0) {"\\b", "\b"},
return IPSFileType::IPS; {"\\f", "\f"},
if (std::memcmp(magic.data(), "IPS32", 5) == 0) {"\\n", "\n"},
return IPSFileType::IPS32; {"\\r", "\r"},
{"\\t", "\t"},
{"\\v", "\v"},
{"\\\\", "\\"},
{"\\\'", "\'"},
{"\\\"", "\""},
{"\\\?", "\?"},
}};
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
if (magic.size() != 5) {
return IPSFileType::Error;
} }
static constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
return IPSFileType::IPS;
}
static constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
return IPSFileType::IPS32;
}
return IPSFileType::Error; return IPSFileType::Error;
} }
static bool IsEOF(IPSFileType type, std::span<const u8> magic) { static bool IsEOF(IPSFileType type, const std::vector<u8>& data) {
return (type == IPSFileType::IPS && magic.size() > 3 && std::memcmp(magic.data(), "EOF", 3) == 0) static constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
|| (type == IPSFileType::IPS32 && magic.size() > 4 && std::memcmp(magic.data(), "EEOF", 4) == 0); if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
return true;
}
static constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
} }
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
if (in == nullptr || ips == nullptr) if (in == nullptr || ips == nullptr)
return nullptr; return nullptr;
auto in_data = in->ReadAllBytes(); const auto type = IdentifyMagic(ips->ReadBytes(0x5));
auto const type = IdentifyMagic(in_data);
if (type == IPSFileType::Error) if (type == IPSFileType::Error)
return nullptr; return nullptr;
auto in_data = in->ReadAllBytes();
if (in_data.size() == 0) {
return nullptr;
}
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4); std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
u64 offset = 5; // After header u64 offset = 5; // After header
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) { while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
@@ -56,9 +85,12 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
break; break;
} }
u32 real_offset = (type == IPSFileType::IPS32) u32 real_offset{};
? ((temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3]) if (type == IPSFileType::IPS32)
: ((temp[0] << 16) | (temp[1] << 8) | temp[2]); real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
else
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
if (real_offset > in_data.size()) { if (real_offset > in_data.size()) {
return nullptr; return nullptr;
} }
@@ -81,35 +113,34 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
return nullptr; return nullptr;
if (real_offset + rle_size > in_data.size()) if (real_offset + rle_size > in_data.size())
rle_size = u16(in_data.size() - real_offset); rle_size = static_cast<u16>(in_data.size() - real_offset);
std::memset(in_data.data() + real_offset, *data, rle_size); std::memset(in_data.data() + real_offset, *data, rle_size);
} else { // Standard Patch } else { // Standard Patch
auto read = data_size; auto read = data_size;
if (real_offset + read > in_data.size()) if (real_offset + read > in_data.size())
read = u16(in_data.size() - real_offset); read = static_cast<u16>(in_data.size() - real_offset);
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size) if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
return nullptr; return nullptr;
offset += data_size; offset += data_size;
} }
} }
if (IsEOF(type, temp)) {
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory()); if (!IsEOF(type, temp)) {
return nullptr;
} }
return nullptr;
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
} }
struct IPSwitchRecord {
std::array<uint8_t, 256 - sizeof(size_t)> data;
size_t count;
};
struct IPSwitchCompiler::IPSwitchPatch { struct IPSwitchCompiler::IPSwitchPatch {
::Common::unordered_map<u32, IPSwitchRecord> records; std::string name;
bool enabled; bool enabled;
std::map<u32, std::vector<u8>> records;
}; };
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
Parse(patch_text->ReadAllBytes()); Parse();
} }
IPSwitchCompiler::~IPSwitchCompiler() = default; IPSwitchCompiler::~IPSwitchCompiler() = default;
@@ -118,185 +149,201 @@ std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
return nso_build_id; return nso_build_id;
} }
static IPSwitchRecord EscapeStringSequences(std::string_view sv) { bool IPSwitchCompiler::IsValid() const {
IPSwitchRecord r{}; return valid;
for (auto it = sv.cbegin(); it != sv.cend(); ) {
if (*it == '\\' && it + 1 < sv.cend()) {
switch (it[1]) {
case 'a': r.data[r.count] = '\a'; break;
case 'b': r.data[r.count] = '\b'; break;
case 'e': r.data[r.count] = '\e'; break;
case 'f': r.data[r.count] = '\f'; break;
case 'n': r.data[r.count] = '\n'; break;
case 'r': r.data[r.count] = '\r'; break;
case 't': r.data[r.count] = '\t'; break;
case 'v': r.data[r.count] = '\v'; break;
case '?': r.data[r.count] = '\?'; break;
default: r.data[r.count] = it[1]; break;
}
++r.count;
it += 2;
} else {
++r.count;
++it;
}
}
return r;
} }
void IPSwitchCompiler::Parse(std::span<u8 const> bytes) { static bool StartsWith(std::string_view base, std::string_view check) {
LOG_INFO(Loader, "IPSwitchCompiler: '{}'", patch_text->GetName()); return base.size() >= check.size() && base.substr(0, check.size()) == check;
bool is_little_endian = true; }
s64 offset_shift = 0;
//bool print_values = false;
auto const parse_line = [&](std::string_view const line) {
// Keep in mind lines have trimmed spaces (at the end & start)!
LOG_INFO(Loader, "<{}>", line);
// IPSwitch is case insensitive
// Yes this is how the logic goes for the main reference parsers!
if (line.size() > 2 && line[0] == '@') {
switch (line[1]) {
// yes, @nsobid too -- NSO Build ID Specifier
case 'n':
case 'N':
nso_build_id = Common::HexStringToArray<0x20>(fmt::format("{:0<64}", line.substr(8)));
break;
// @stop
case 's':
case 'S':
return false;
// @enabled
case 'e':
case 'E':
patches.push_back({{}, true});
break;
// @disabled
case 'd':
case 'D':
patches.push_back({{}, false});
break;
// @flag
case 'f':
case 'F': {
if (line.starts_with("@flag offset_shift")) {
offset_shift = std::strtoll(line.data() + 19, nullptr, 0); // Offset Shift Flag
} else if (line.starts_with("@flag print_values")) {
//print_values = true; // Force printing of applied values
}
break;
}
case 'l':
case 'L':
is_little_endian = true;
break;
// IPS parsers dont support big endian no more, we do due to backcompat
case 'b':
case 'B':
is_little_endian = false;
break;
default:
LOG_WARNING(Loader, "Unknown flag {}", line);
break;
}
} else {
size_t offset = size_t(std::strtoul(line.data(), nullptr, 16));
offset += size_t(offset_shift);
if (auto const first_quote = line.find_first_of("\"\'"); first_quote != std::string::npos) {
// string replacement
char quote = line[first_quote];
auto const start = line.cbegin() + first_quote + 1;
auto end = start;
for (; end < line.cend() && *end != quote; )
end += (*end == '\\') ? 2 : 1;
if (start <= line.cend() && end <= line.cend()) {
LOG_INFO(Loader, "[S] value @ {:#08X} ", offset);
patches.back().records.insert_or_assign(u32(offset), EscapeStringSequences({start, end}));
} else {
LOG_WARNING(Loader, "invalid string");
}
} else if (auto const first_space = line.find_last_of(" /\t\r\n"); first_space != std::string::npos) {
IPSwitchRecord r{}; // hex replacement
auto const start = line.cbegin() + first_space + 1;
auto const end = line.cend();
if (start <= line.cend() && end <= line.cend()) {
// Actually IPS wants ordering from {lsb, ..., msb} -- so LE and BE are inverted, fun!
auto const hs = Common::HexStringToVector({start, end}, is_little_endian);
std::memcpy(r.data.data(), hs.data(), hs.size());
r.count = hs.size();
LOG_INFO(Loader, "[H] value @ {:#08X}", offset);
patches.back().records.insert_or_assign(u32(offset), std::move(r));
} else {
LOG_WARNING(Loader, "invalid line");
}
} else {
LOG_WARNING(Loader, "unhandled line!");
}
}
return true; //continue
};
for (auto it = bytes.begin(); it < bytes.end(); ) { static std::string EscapeStringSequences(std::string in) {
auto const start = it; for (const auto& seq : ESCAPE_CHARACTER_MAP) {
auto end = start; for (auto index = in.find(seq.first); index != std::string::npos;
for (; end < bytes.end() && *end != '\n' && *end != '\r'; ++end) index = in.find(seq.first, index)) {
; in.replace(index, std::strlen(seq.first), seq.second);
it = end + 1; //prepare for next line index += std::strlen(seq.second);
std::string_view const sline{
reinterpret_cast<const char*>(bytes.data() + std::distance(bytes.begin(), start)),
size_t(std::distance(start, end))
};
if (sline.size() > 0) {
auto p = sline.cbegin();
// skip space off line
for (; p < sline.cend() && std::isspace(*p); ++p)
;
// now make a nominal preprocessed line: remove comments
char quote = '\0';
auto const sline_start = p;
for (; p < sline.cend(); ) {
// we dont check for "//", IPS checks for '/' only...
if ((!quote && p[0] == '/')
|| (!quote && p[0] == '#')) {
break;
} else if (p[0] == '\"' || p[0] == '\'') {
quote = (p[0] == quote) ? '\0' : p[0];
++p;
} else if (p + 1 < sline.cend() && p[0] == '\\') {
p += 2;
} else {
++p;
}
}
// now we have the preprocessed string ;)
std::string_view pp_str(sline_start, p);
if (pp_str.size() > 0 && !parse_line(pp_str)) {
break;
}
} }
} }
return in;
}
void IPSwitchCompiler::ParseFlag(const std::string& line) {
if (StartsWith(line, "@flag offset_shift ")) {
// Offset Shift Flag
offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
} else if (StartsWith(line, "@little-endian")) {
// Set values to read as little endian
is_little_endian = true;
} else if (StartsWith(line, "@big-endian")) {
// Set values to read as big endian
is_little_endian = false;
} else if (StartsWith(line, "@flag print_values")) {
// Force printing of applied values
print_values = true;
}
}
void IPSwitchCompiler::Parse() {
const auto bytes = patch_text->ReadAllBytes();
std::stringstream s;
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
std::vector<std::string> lines;
std::string stream_line;
while (std::getline(s, stream_line)) {
// Remove a trailing \r
if (!stream_line.empty() && stream_line.back() == '\r')
stream_line.pop_back();
lines.push_back(std::move(stream_line));
}
for (std::size_t i = 0; i < lines.size(); ++i) {
auto line = lines[i];
// Remove midline comments
std::size_t comment_index = std::string::npos;
bool within_string = false;
for (std::size_t k = 0; k < line.size(); ++k) {
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
within_string = !within_string;
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
comment_index = k;
break;
}
}
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
last_comment = line.substr(comment_index + 2);
line = line.substr(0, comment_index);
}
if (StartsWith(line, "@stop")) {
// Force stop
break;
} else if (StartsWith(line, "@nsobid-")) {
// NSO Build ID Specifier
const auto raw_build_id = fmt::format("{:0<64}", line.substr(8));
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
} else if (StartsWith(line, "#")) {
// Mandatory Comment
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
patch_text->GetName(), line.substr(1));
} else if (StartsWith(line, "//")) {
// Normal Comment
last_comment = line.substr(2);
if (last_comment.find_first_not_of(' ') == std::string::npos)
continue;
if (last_comment.find_first_not_of(' ') != 0)
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
// Start of patch
const auto enabled = StartsWith(line, "@enabled");
if (i == 0)
return;
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
patch_text->GetName(), last_comment, line.substr(1));
IPSwitchPatch patch{last_comment, enabled, {}};
// Read rest of patch
while (true) {
if (i + 1 >= lines.size()) {
break;
}
const auto& patch_line = lines[++i];
// Patch line may contain comments
if (StartsWith(patch_line, "//") || StartsWith(patch_line, "#")) {
continue;
}
// Start of new patch
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
--i;
break;
}
// Check for a flag
if (StartsWith(patch_line, "@")) {
ParseFlag(patch_line);
continue;
}
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
if (patch_line.length() < 11)
break;
auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
offset += static_cast<unsigned long>(offset_shift);
std::vector<u8> replace;
// 9 - first char of replacement val
if (patch_line[9] == '\"') {
// string replacement
auto end_index = patch_line.find('\"', 10);
if (end_index == std::string::npos || end_index < 10)
return;
while (patch_line[end_index - 1] == '\\') {
end_index = patch_line.find('\"', end_index + 1);
if (end_index == std::string::npos || end_index < 10)
return;
}
auto value = patch_line.substr(10, end_index - 10);
value = EscapeStringSequences(value);
replace.reserve(value.size());
std::copy(value.begin(), value.end(), std::back_inserter(replace));
} else {
// hex replacement
const auto value =
patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9);
replace = Common::HexStringToVector(value, is_little_endian);
}
if (print_values) {
LOG_INFO(Loader,
"[IPSwitchCompiler ('{}')] - Patching value at offset {:#08x} "
"with byte string '{}'",
patch_text->GetName(), offset, Common::HexToString(replace));
}
patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace));
}
patches.push_back(std::move(patch));
} else if (StartsWith(line, "@")) {
ParseFlag(line);
}
}
valid = true;
} }
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const { VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
if (in == nullptr) if (in == nullptr || !valid)
return nullptr; return nullptr;
auto in_data = in->ReadAllBytes(); auto in_data = in->ReadAllBytes();
for (const auto& patch : patches) { for (const auto& patch : patches) {
if (patch.enabled) { if (!patch.enabled)
for (const auto& record : patch.records) { continue;
if (record.first < in_data.size()) {
auto replace_size = record.second.count; for (const auto& record : patch.records) {
if (record.first + replace_size > in_data.size()) if (record.first >= in_data.size())
replace_size = in_data.size() - record.first; continue;
std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size); auto replace_size = record.second.size();
} else { if (record.first + replace_size > in_data.size())
LOG_WARNING(Loader, "record offs={:x},size={:x}", record.first, record.second.data.size()); replace_size = in_data.size() - record.first;
} for (std::size_t i = 0; i < replace_size; ++i)
} in_data[i + record.first] = record.second[i];
} }
} }
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
} }
} // namespace FileSys } // namespace FileSys
+9 -5
View File
@@ -1,14 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <array> #include <array>
#include <memory>
#include <vector> #include <vector>
#include <span>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/file_sys/vfs/vfs.h" #include "core/file_sys/vfs/vfs.h"
@@ -23,17 +20,24 @@ public:
~IPSwitchCompiler(); ~IPSwitchCompiler();
std::array<u8, 0x20> GetBuildID() const; std::array<u8, 0x20> GetBuildID() const;
bool IsValid() const;
VirtualFile Apply(const VirtualFile& in) const; VirtualFile Apply(const VirtualFile& in) const;
private: private:
struct IPSwitchPatch; struct IPSwitchPatch;
void ParseFlag(const std::string& flag); void ParseFlag(const std::string& flag);
void Parse(std::span<u8 const> bytes); void Parse();
bool valid = false;
VirtualFile patch_text; VirtualFile patch_text;
std::vector<IPSwitchPatch> patches; std::vector<IPSwitchPatch> patches;
std::array<u8, 0x20> nso_build_id{}; std::array<u8, 0x20> nso_build_id{};
bool is_little_endian = false;
s64 offset_shift = 0;
bool print_values = false;
std::string last_comment = "";
}; };
} // namespace FileSys } // namespace FileSys
+2 -2
View File
@@ -48,11 +48,11 @@ struct ContentRecord {
std::array<u8, 0x10> nca_id; std::array<u8, 0x10> nca_id;
std::array<u8, 0x6> size; std::array<u8, 0x6> size;
ContentRecordType type; ContentRecordType type;
u8 id_offset; INSERT_PADDING_BYTES(1);
}; };
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size."); static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, 0}; constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
struct MetaRecord { struct MetaRecord {
u64_le title_id; u64_le title_id;
+9 -2
View File
@@ -345,7 +345,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs; return exefs;
} }
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, const std::string& build_id) const { std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const {
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[title_id];
const auto nso_build_id = fmt::format("{:0<64}", build_id); const auto nso_build_id = fmt::format("{:0<64}", build_id);
@@ -360,11 +361,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
for (const auto& file : exefs_dir->GetFiles()) { for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") { if (file->GetExtension() == "ips") {
auto name = file->GetName(); auto name = file->GetName();
const auto this_build_id = fmt::format("{:0<64}", name.substr(0, name.find('.')));
const auto this_build_id =
fmt::format("{:0<64}", name.substr(0, name.find('.')));
if (nso_build_id == this_build_id) if (nso_build_id == this_build_id)
out.push_back(file); out.push_back(file);
} else if (file->GetExtension() == "pchtxt") { } else if (file->GetExtension() == "pchtxt") {
IPSwitchCompiler compiler{file}; IPSwitchCompiler compiler{file};
if (!compiler.IsValid())
continue;
const auto this_build_id = Common::HexToString(compiler.GetBuildID()); const auto this_build_id = Common::HexToString(compiler.GetBuildID());
if (nso_build_id == this_build_id) if (nso_build_id == this_build_id)
out.push_back(file); out.push_back(file);
@@ -372,6 +378,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
} }
} }
} }
return out; return out;
} }
+6 -31
View File
@@ -566,46 +566,21 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
return file; return file;
} }
static std::optional<NcaID> CheckMapForContentRecord(const ::Common::unordered_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) { static std::optional<NcaID> CheckMapForContentRecord(const ankerl::unordered_dense::map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
auto cmnt_iter = map.find(title_id); const auto cmnt_iter = map.find(title_id);
u8 id_offset = 0;
if (cmnt_iter == map.cend()) { if (cmnt_iter == map.cend()) {
const auto program_index = title_id & AOC_TITLE_ID_MASK; return std::nullopt;
if (program_index == 0) {
return std::nullopt;
}
cmnt_iter = map.find(title_id & ~AOC_TITLE_ID_MASK);
if (cmnt_iter == map.cend()) {
return std::nullopt;
}
id_offset = static_cast<u8>(program_index);
} }
const auto& cnmt = cmnt_iter->second; const auto& cnmt = cmnt_iter->second;
const auto& content_records = cnmt.GetContentRecords(); const auto& content_records = cnmt.GetContentRecords();
const auto iter = std::find_if(content_records.cbegin(), content_records.cend(), const auto iter = std::find_if(content_records.cbegin(), content_records.cend(),
[type, id_offset](const ContentRecord& rec) { [type](const ContentRecord& rec) { return rec.type == type; });
return rec.type == type && rec.id_offset == id_offset; if (iter == content_records.cend()) {
});
if (iter != content_records.cend()) {
return std::make_optional(iter->nca_id);
}
if (id_offset != 0) {
return std::nullopt; return std::nullopt;
} }
const auto fallback_iter = return std::make_optional(iter->nca_id);
std::find_if(content_records.cbegin(), content_records.cend(),
[type](const ContentRecord& rec) { return rec.type == type; });
if (fallback_iter == content_records.cend()) {
return std::nullopt;
}
return std::make_optional(fallback_iter->nca_id);
} }
std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id, std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
+6 -6
View File
@@ -12,7 +12,7 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include <boost/container/flat_map.hpp> #include <boost/container/flat_map.hpp>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
@@ -209,11 +209,11 @@ private:
ContentProviderParsingFunction parser; ContentProviderParsingFunction parser;
// maps tid -> NcaID of meta // maps tid -> NcaID of meta
::Common::unordered_map<u64, NcaID> meta_id; ankerl::unordered_dense::map<u64, NcaID> meta_id;
// maps tid -> meta // maps tid -> meta
::Common::unordered_map<u64, CNMT> meta; ankerl::unordered_dense::map<u64, CNMT> meta;
// maps tid -> meta for CNMT in yuzu_meta // maps tid -> meta for CNMT in yuzu_meta
::Common::unordered_map<u64, CNMT> yuzu_meta; ankerl::unordered_dense::map<u64, CNMT> yuzu_meta;
}; };
enum class ContentProviderUnionSlot { enum class ContentProviderUnionSlot {
@@ -313,8 +313,8 @@ private:
void ProcessXCI(const VirtualFile& file); void ProcessXCI(const VirtualFile& file);
std::vector<VirtualDir> load_dirs; std::vector<VirtualDir> load_dirs;
::Common::unordered_map<std::tuple<u64, ContentRecordType, TitleType>, VirtualFile> entries; ankerl::unordered_dense::map<std::tuple<u64, ContentRecordType, TitleType>, VirtualFile> entries;
::Common::unordered_map<u64, u32> versions; ankerl::unordered_dense::map<u64, u32> versions;
std::vector<ExternalUpdateEntry> multi_version_entries; std::vector<ExternalUpdateEntry> multi_version_entries;
}; };
+6 -8
View File
@@ -12,7 +12,6 @@
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging.h" #include "common/logging.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h" #include "core/file_sys/partition_filesystem.h"
@@ -66,12 +65,10 @@ u64 NSP::GetProgramTitleID() const {
} }
auto program_id = expected_program_id; auto program_id = expected_program_id;
if (program_id == 0 && !program_status.empty()) { if (program_id == 0) {
program_id = std::min_element(program_status.cbegin(), program_status.cend(), if (!program_status.empty()) {
[](const auto& lhs, const auto& rhs) { program_id = program_status.begin()->first;
return lhs.first < rhs.first; }
})
->first;
} }
program_id = program_id + program_index; program_id = program_id + program_index;
@@ -276,7 +273,8 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
// If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and // If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and
// 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular // 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular
// update NCA. // update NCA.
if ((next_nca->GetTitleId() & AOC_TITLE_ID_MASK) != 0) { if ((next_nca->GetTitleId() & 0x7FF) != 0 &&
(next_nca->GetTitleId() & 0x800) == 0) {
ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
std::move(next_nca); std::move(next_nca);
} else { } else {
+3 -3
View File
@@ -6,7 +6,7 @@
#include <algorithm> #include <algorithm>
#include <set> #include <set>
#include "common/container/unordered_set.h" #include <ankerl/unordered_dense.h>
#include <utility> #include <utility>
#include "core/file_sys/vfs/vfs_layered.h" #include "core/file_sys/vfs/vfs_layered.h"
@@ -63,7 +63,7 @@ std::string LayeredVfsDirectory::GetFullPath() const {
std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const { std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualFile> out; std::vector<VirtualFile> out;
::Common::unordered_set<std::string> out_names; ankerl::unordered_dense::set<std::string> out_names;
for (const auto& layer : dirs) { for (const auto& layer : dirs) {
for (auto& file : layer->GetFiles()) { for (auto& file : layer->GetFiles()) {
@@ -79,7 +79,7 @@ std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const { std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
std::vector<VirtualDir> out; std::vector<VirtualDir> out;
::Common::unordered_set<std::string> out_names; ankerl::unordered_dense::set<std::string> out_names;
for (const auto& layer : dirs) { for (const auto& layer : dirs) {
for (const auto& sd : layer->GetSubdirectories()) { for (const auto& sd : layer->GetSubdirectories()) {
+2 -2
View File
@@ -78,7 +78,7 @@ private:
std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{}; std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
std::map<KProcessAddress, u64> m_debug_page_refcounts{}; std::map<KProcessAddress, u64> m_debug_page_refcounts{};
#ifdef HAS_NCE #ifdef HAS_NCE
::Common::unordered_map<u64, u64> m_post_handlers{}; ankerl::unordered_dense::map<u64, u64> m_post_handlers{};
#endif #endif
std::unique_ptr<Core::ExclusiveMonitor> m_exclusive_monitor; std::unique_ptr<Core::ExclusiveMonitor> m_exclusive_monitor;
Core::Memory::Memory m_memory; Core::Memory::Memory m_memory;
@@ -494,7 +494,7 @@ public:
static void Switch(KernelCore& kernel, KProcess* cur_process, KProcess* next_process); static void Switch(KernelCore& kernel, KProcess* cur_process, KProcess* next_process);
#ifdef HAS_NCE #ifdef HAS_NCE
::Common::unordered_map<u64, u64>& GetPostHandlers() noexcept { ankerl::unordered_dense::map<u64, u64>& GetPostHandlers() noexcept {
return m_post_handlers; return m_post_handlers;
} }
#endif #endif
+2 -2
View File
@@ -1216,7 +1216,7 @@ Result KServerSession::ReceiveRequest(KernelCore& kernel, uintptr_t server_messa
} }
Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, bool is_hle, bool session_closed) { KPhysicalAddress server_message_paddr, bool is_hle) {
// Lock the session. // Lock the session.
KScopedLightLock lk{m_lock}; KScopedLightLock lk{m_lock};
@@ -1248,7 +1248,7 @@ Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, u
KEvent* event = request->GetEvent(); KEvent* event = request->GetEvent();
// Check whether we're closed. // Check whether we're closed.
const bool closed = (client_thread == nullptr || m_parent->IsClientClosed() || session_closed); const bool closed = (client_thread == nullptr || m_parent->IsClientClosed());
Result result = ResultSuccess; Result result = ResultSuccess;
if (!closed) { if (!closed) {
+3 -3
View File
@@ -54,14 +54,14 @@ public:
Result OnRequest(KernelCore& kernel, KSessionRequest* request); Result OnRequest(KernelCore& kernel, KSessionRequest* request);
Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, bool is_hle = false, bool session_closed = false); KPhysicalAddress server_message_paddr, bool is_hle = false);
Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, KPhysicalAddress server_message_paddr,
std::shared_ptr<Service::HLERequestContext>* out_context = nullptr, std::shared_ptr<Service::HLERequestContext>* out_context = nullptr,
std::weak_ptr<Service::SessionRequestManager> manager = {}); std::weak_ptr<Service::SessionRequestManager> manager = {});
Result SendReplyHLE(KernelCore& kernel, bool session_closed = false) { Result SendReplyHLE(KernelCore& kernel) {
R_RETURN(this->SendReply(kernel, 0, 0, 0, true, session_closed)); R_RETURN(this->SendReply(kernel, 0, 0, 0, true));
} }
Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr<Service::HLERequestContext>* out_context, Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr<Service::HLERequestContext>* out_context,
+7 -25
View File
@@ -10,8 +10,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include "common/container/unordered_set.h"
#include <utility> #include <utility>
#include "common/assert.h" #include "common/assert.h"
@@ -350,18 +349,9 @@ struct KernelCore::Impl {
object_name_global_data.emplace(kernel); object_name_global_data.emplace(kernel);
} }
void SetApplicationProcess(KernelCore& kernel, KProcess* process) { void MakeApplicationProcess(KernelCore& kernel, KProcess* process) {
if (application_process == process)
return;
KProcess* const previous = application_process;
application_process = process; application_process = process;
application_process->Open(kernel);
if (application_process != nullptr)
application_process->Open(kernel);
if (previous != nullptr)
previous->Close(kernel);
} }
/// Sets the host thread ID for the caller. /// Sets the host thread ID for the caller.
@@ -793,8 +783,8 @@ struct KernelCore::Impl {
std::optional<KObjectNameGlobalData> object_name_global_data; std::optional<KObjectNameGlobalData> object_name_global_data;
::Common::unordered_set<KAutoObject*> registered_objects; ankerl::unordered_dense::set<KAutoObject*> registered_objects;
::Common::unordered_set<KAutoObject*> registered_in_use_objects; ankerl::unordered_dense::set<KAutoObject*> registered_in_use_objects;
std::mutex server_lock; std::mutex server_lock;
std::vector<std::unique_ptr<Service::ServerManager>> server_managers; std::vector<std::unique_ptr<Service::ServerManager>> server_managers;
@@ -889,8 +879,8 @@ void KernelCore::RemoveProcess(KProcess* process) {
} }
} }
void KernelCore::SetApplicationProcess(KProcess* process) { void KernelCore::MakeApplicationProcess(KProcess* process) {
impl->SetApplicationProcess(*this, process); impl->MakeApplicationProcess(*this, process);
} }
KProcess* KernelCore::ApplicationProcess() { KProcess* KernelCore::ApplicationProcess() {
@@ -901,14 +891,6 @@ const KProcess* KernelCore::ApplicationProcess() const {
return impl->application_process; return impl->application_process;
} }
KScopedAutoObject<KProcess> KernelCore::GetProcessByProcessId(u64 process_id) {
std::scoped_lock lk{impl->process_list_lock};
for (auto* const process : impl->process_list)
if (process != nullptr && process->GetProcessId() == process_id)
return {*this, process};
return {*this, nullptr};
}
std::list<KScopedAutoObject<KProcess>> KernelCore::GetProcessList() { std::list<KScopedAutoObject<KProcess>> KernelCore::GetProcessList() {
std::list<KScopedAutoObject<KProcess>> processes; std::list<KScopedAutoObject<KProcess>> processes;
std::scoped_lock lk{impl->process_list_lock}; std::scoped_lock lk{impl->process_list_lock};
+3 -6
View File
@@ -11,7 +11,7 @@
#include <list> #include <list>
#include <memory> #include <memory>
#include <string> #include <string>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include <vector> #include <vector>
#include "common/polyfill_thread.h" #include "common/polyfill_thread.h"
@@ -124,8 +124,8 @@ public:
void AppendNewProcess(KProcess* process); void AppendNewProcess(KProcess* process);
void RemoveProcess(KProcess* process); void RemoveProcess(KProcess* process);
/// Makes the given process the current application process. /// Makes the given process the new application process.
void SetApplicationProcess(KProcess* process); void MakeApplicationProcess(KProcess* process);
/// Retrieves a pointer to the application process. /// Retrieves a pointer to the application process.
KProcess* ApplicationProcess(); KProcess* ApplicationProcess();
@@ -133,9 +133,6 @@ public:
/// Retrieves a const pointer to the application process. /// Retrieves a const pointer to the application process.
const KProcess* ApplicationProcess() const; const KProcess* ApplicationProcess() const;
/// Retrieves the process with the given process ID, or a null object.
KScopedAutoObject<KProcess> GetProcessByProcessId(u64 process_id);
/// Retrieves the list of processes. /// Retrieves the list of processes.
std::list<KScopedAutoObject<KProcess>> GetProcessList(); std::list<KScopedAutoObject<KProcess>> GetProcessList();
+47 -3
View File
@@ -17,12 +17,56 @@
namespace Kernel::Svc { namespace Kernel::Svc {
constexpr auto MAX_MSG_TIME = std::chrono::milliseconds(250);
const auto MAX_MSG_SIZE = 0x1000;
/// Used to output a message on a debug hardware unit - does nothing on a retail unit /// Used to output a message on a debug hardware unit - does nothing on a retail unit
Result OutputDebugString(Core::System& system, u64 address, u64 len) { Result OutputDebugString(Core::System& system, u64 address, u64 len) {
static struct DebugFlusher {
std::string msg_buffer;
std::mutex msg_mutex;
std::condition_variable msg_cv;
std::chrono::steady_clock::time_point last_msg_time;
std::optional<std::jthread> thread;
} flusher_data;
R_SUCCEED_IF(len == 0); R_SUCCEED_IF(len == 0);
std::string msg_buffer(len, 0); // Only start the thread the very first time this function is called
GetCurrentMemory(system.Kernel()).ReadBlock(address, msg_buffer.data(), len); if (!flusher_data.thread) {
LOG_INFO(Debug_Emulated, "{}", msg_buffer); flusher_data.thread.emplace([](std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
std::unique_lock lock(flusher_data.msg_mutex);
flusher_data.msg_cv.wait(lock, [&stop_token] {
return !flusher_data.msg_buffer.empty() || stop_token.stop_requested();
});
if (stop_token.stop_requested() && flusher_data.msg_buffer.empty())
break;
auto timeout = flusher_data.last_msg_time + MAX_MSG_TIME;
bool woke_early = flusher_data.msg_cv.wait_until(lock, timeout, [&stop_token] {
return flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested();
});
if (!woke_early || flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested()) {
if (!flusher_data.msg_buffer.empty()) {
// Remove trailing newline as LOG_INFO adds that anyways
if (flusher_data.msg_buffer.back() == '\n')
flusher_data.msg_buffer.pop_back();
LOG_INFO(Debug_Emulated, "\n{}", flusher_data.msg_buffer);
flusher_data.msg_buffer.clear();
}
if (stop_token.stop_requested()) break;
}
}
flusher_data.msg_cv.notify_all();
});
}
{
std::lock_guard lock(flusher_data.msg_mutex);
const auto old_size = flusher_data.msg_buffer.size();
flusher_data.msg_buffer.resize(old_size + len);
GetCurrentMemory(system.Kernel()).ReadBlock(address, flusher_data.msg_buffer.data() + old_size, len);
flusher_data.last_msg_time = std::chrono::steady_clock::now();
}
flusher_data.msg_cv.notify_one();
R_SUCCEED(); R_SUCCEED();
} }
-4
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -13,7 +10,6 @@ namespace Service::AM {
constexpr Result ResultNoDataInChannel{ErrorModule::AM, 2}; constexpr Result ResultNoDataInChannel{ErrorModule::AM, 2};
constexpr Result ResultNoMessages{ErrorModule::AM, 3}; constexpr Result ResultNoMessages{ErrorModule::AM, 3};
constexpr Result ResultLibraryAppletTerminated{ErrorModule::AM, 22}; constexpr Result ResultLibraryAppletTerminated{ErrorModule::AM, 22};
constexpr Result ResultApplicationRecordNotFound{ErrorModule::AM, 37};
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
constexpr Result ResultInvalidStorageType{ErrorModule::AM, 511}; constexpr Result ResultInvalidStorageType{ErrorModule::AM, 511};
constexpr Result ResultFatalSectionCountImbalance{ErrorModule::AM, 512}; constexpr Result ResultFatalSectionCountImbalance{ErrorModule::AM, 512};
+1 -9
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -201,14 +201,6 @@ enum class ProgramSpecifyKind : u32 {
RestartProgram = 2, RestartProgram = 2,
}; };
// Maufeat: Use enums for zindex instead of using random zindex numbers
enum AppletZIndex : s32 {
Background = 0,
Foreground = 1,
ForegroundVisible = 2,
Overlay = 3,
};
struct CommonArguments { struct CommonArguments {
CommonArgumentVersion arguments_version; CommonArgumentVersion arguments_version;
CommonArgumentSize size; CommonArgumentSize size;
+9 -9
View File
@@ -56,22 +56,22 @@ void Applet::UpdateSuspensionStateLocked(bool force_message) {
} }
} }
void Applet::SetInteractibleLocked(bool pad_interactible, bool touch_interactible) { void Applet::SetInteractibleLocked(bool interactible) {
if (is_pad_interactible == pad_interactible && is_touch_interactible == touch_interactible) { if (is_interactible == interactible) {
return; return;
} }
is_pad_interactible = pad_interactible; is_interactible = interactible;
is_touch_interactible = touch_interactible;
const bool exit_requested = lifecycle_manager.GetExitRequested(); const bool exit_requested = lifecycle_manager.GetExitRequested();
const bool pad_enabled = pad_interactible && !exit_requested; const bool input_enabled = interactible && !exit_requested;
const bool touch_enabled = touch_interactible && !exit_requested;
LOG_DEBUG(Service_AM, "applet={} pad={} touch={} exit_requested={}", if (applet_id == AppletId::OverlayDisplay || applet_id == AppletId::Application) {
static_cast<u32>(applet_id), pad_enabled, touch_enabled, exit_requested); LOG_DEBUG(Service_AM, "called, applet={} interactible={} exit_requested={} input_enabled={} overlay_in_foreground={}",
static_cast<u32>(applet_id), interactible, exit_requested, input_enabled, overlay_in_foreground);
}
hid_registration.EnableAppletToGetInput(pad_enabled, touch_enabled); hid_registration.EnableAppletToGetInput(input_enabled);
} }
void Applet::OnProcessTerminatedLocked() { void Applet::OnProcessTerminatedLocked() {
+3 -5
View File
@@ -125,11 +125,9 @@ struct Applet {
bool album_image_taken_notification_enabled{}; bool album_image_taken_notification_enabled{};
bool record_volume_muted{}; bool record_volume_muted{};
bool is_activity_runnable{}; bool is_activity_runnable{};
bool is_pad_interactible{true}; bool is_interactible{true};
bool is_touch_interactible{true};
bool window_visible{true}; bool window_visible{true};
bool overlay_watching_short_home_button{false}; bool overlay_in_foreground{false};
bool overlay_handling_touch_input{false};
// Events // Events
Event overlay_event; Event overlay_event;
@@ -150,7 +148,7 @@ struct Applet {
// Process state management // Process state management
void UpdateSuspensionStateLocked(bool force_message); void UpdateSuspensionStateLocked(bool force_message);
void SetInteractibleLocked(bool pad_interactible, bool touch_interactible); void SetInteractibleLocked(bool interactible);
void OnProcessTerminatedLocked(); void OnProcessTerminatedLocked();
}; };
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -6,7 +6,6 @@
#include "core/core.h" #include "core/core.h"
#include "core/hle/service/am/display_layer_manager.h" #include "core/hle/service/am/display_layer_manager.h"
#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/sm/sm.h" #include "core/hle/service/sm/sm.h"
#include "core/hle/service/vi/application_display_service.h" #include "core/hle/service/vi/application_display_service.h"
#include "core/hle/service/vi/container.h" #include "core/hle/service/vi/container.h"
@@ -34,7 +33,6 @@ void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* pro
m_system_shared_buffer_id = 0; m_system_shared_buffer_id = 0;
m_system_shared_layer_id = 0; m_system_shared_layer_id = 0;
m_applet_id = applet_id; m_applet_id = applet_id;
m_library_applet_mode = mode;
m_buffer_sharing_enabled = false; m_buffer_sharing_enabled = false;
m_blending_enabled = mode == LibraryAppletMode::PartialForeground || m_blending_enabled = mode == LibraryAppletMode::PartialForeground ||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay; mode == LibraryAppletMode::PartialForegroundIndirectDisplay;
@@ -74,16 +72,14 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()})); out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id); m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerStackMask(*out_layer_id,
this->GetLayerStackMask());
if (m_applet_id != AppletId::Application) { if (m_applet_id != AppletId::Application) {
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id); (void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
if (m_applet_id == AppletId::OverlayDisplay) { if (m_applet_id == AppletId::OverlayDisplay) {
(void)m_manager_display_service->SetLayerZIndex(Overlay, *out_layer_id); (void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true); (void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
} else { } else {
(void)m_manager_display_service->SetLayerZIndex(Foreground, *out_layer_id); (void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
} }
} }
@@ -126,12 +122,10 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
// Ensure the overlay layer is visible // Ensure the overlay layer is visible
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id); m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
(void)m_display_service->GetContainer()->SetLayerStackMask(m_system_shared_layer_id,
this->GetLayerStackMask());
m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id); m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
s32 initial_z = Foreground; s32 initial_z = 1;
if (m_applet_id == AppletId::OverlayDisplay) { if (m_applet_id == AppletId::OverlayDisplay) {
initial_z = Overlay; initial_z = -1;
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true); (void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
} }
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id); m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
@@ -148,36 +142,6 @@ Result DisplayLayerManager::GetSystemSharedLayerHandle(u64* out_system_shared_bu
R_SUCCEED(); R_SUCCEED();
} }
u32 DisplayLayerManager::GetLayerStackMask() const {
using Nvnflinger::LayerStackBit;
using Nvnflinger::LayerStackId;
constexpr u32 Displayed = LayerStackBit(LayerStackId::Default);
constexpr u32 Screenshot = LayerStackBit(LayerStackId::Screenshot);
constexpr u32 Recording = LayerStackBit(LayerStackId::Recording);
constexpr u32 LastFrame = LayerStackBit(LayerStackId::LastFrame);
constexpr u32 Debug = LayerStackBit(LayerStackId::ApplicationForDebug);
switch (m_applet_id) {
case AppletId::Application:
return Displayed | Screenshot | Recording | LastFrame | Debug;
case AppletId::OverlayDisplay:
return Displayed;
case AppletId::QLaunch:
return Displayed;
default:
break;
}
switch (m_library_applet_mode) {
case LibraryAppletMode::AllForeground:
case LibraryAppletMode::AllForegroundInitiallyHidden:
return Displayed | Screenshot | LastFrame;
default:
return Displayed | Screenshot;
}
}
void DisplayLayerManager::SetWindowVisibility(bool visible) { void DisplayLayerManager::SetWindowVisibility(bool visible) {
if (m_visible == visible) { if (m_visible == visible) {
return; return;
@@ -221,17 +185,10 @@ void DisplayLayerManager::SetOverlayZIndex(s32 z_index) {
} }
Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written, Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written,
s32* out_fbshare_layer_index, s32* out_fbshare_layer_index) {
VI::CaptureKind kind) {
R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied); R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);
R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->WriteAppletCaptureBuffer( R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->WriteAppletCaptureBuffer(
out_was_written, out_fbshare_layer_index, kind)); out_was_written, out_fbshare_layer_index));
}
Result DisplayLayerManager::ClearAppletCaptureBuffer(s32 fbshare_layer_index, u32 color) {
R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);
R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->ClearAppletCaptureBuffer(
fbshare_layer_index, color));
} }
} // namespace Service::AM } // namespace Service::AM
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -23,7 +23,6 @@ class KProcess;
namespace Service::VI { namespace Service::VI {
class IApplicationDisplayService; class IApplicationDisplayService;
class IManagerDisplayService; class IManagerDisplayService;
enum class CaptureKind : u32;
} // namespace Service::VI } // namespace Service::VI
namespace Service::AM { namespace Service::AM {
@@ -49,13 +48,9 @@ public:
void SetOverlayZIndex(s32 z_index); void SetOverlayZIndex(s32 z_index);
Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index, Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index);
VI::CaptureKind kind);
Result ClearAppletCaptureBuffer(s32 fbshare_layer_index, u32 color);
private: private:
u32 GetLayerStackMask() const;
Kernel::KProcess* m_process{}; Kernel::KProcess* m_process{};
std::shared_ptr<VI::IApplicationDisplayService> m_display_service{}; std::shared_ptr<VI::IApplicationDisplayService> m_display_service{};
std::shared_ptr<VI::IManagerDisplayService> m_manager_display_service{}; std::shared_ptr<VI::IManagerDisplayService> m_manager_display_service{};
@@ -64,7 +59,6 @@ private:
u64 m_system_shared_buffer_id{}; u64 m_system_shared_buffer_id{};
u64 m_system_shared_layer_id{}; u64 m_system_shared_layer_id{};
AppletId m_applet_id{}; AppletId m_applet_id{};
LibraryAppletMode m_library_applet_mode{};
bool m_buffer_sharing_enabled{}; bool m_buffer_sharing_enabled{};
bool m_blending_enabled{}; bool m_blending_enabled{};
bool m_visible{true}; bool m_visible{true};
@@ -7,7 +7,7 @@
#pragma once #pragma once
#include <array> #include <array>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include <vector> #include <vector>
#include "common/common_funcs.h" #include "common/common_funcs.h"
@@ -176,6 +176,6 @@ struct WebCommonReturnValue {
}; };
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
using WebArgInputTLVMap = ::Common::unordered_map<WebArgInputTLVType, std::vector<u8>>; using WebArgInputTLVMap = ankerl::unordered_dense::map<WebArgInputTLVType, std::vector<u8>>;
} // namespace Service::AM::Frontend } // namespace Service::AM::Frontend
+6 -11
View File
@@ -36,17 +36,12 @@ HidRegistration::~HidRegistration() {
} }
} }
void HidRegistration::EnableAppletToGetInput(bool enable_pad, bool enable_touch) { void HidRegistration::EnableAppletToGetInput(bool enable) {
if (!m_process.IsInitialized()) if (m_process.IsInitialized()) {
return; m_hid_server->GetResourceManager()->SetAruidValidForVibration(m_process.GetProcessId(),
enable);
const auto resource_manager = m_hid_server->GetResourceManager(); m_hid_server->GetResourceManager()->EnableInput(m_process.GetProcessId(), enable);
const u64 aruid = m_process.GetProcessId(); }
resource_manager->EnablePadInput(aruid, enable_pad);
resource_manager->EnableTouchScreen(aruid, enable_touch);
resource_manager->SetAruidValidForVibration(aruid, enable_pad);
} }
} // namespace Service::AM } // namespace Service::AM
+1 -1
View File
@@ -28,7 +28,7 @@ public:
~HidRegistration(); ~HidRegistration();
void RegisterCurrentProcess(); void RegisterCurrentProcess();
void EnableAppletToGetInput(bool enable_pad, bool enable_touch); void EnableAppletToGetInput(bool enable);
private: private:
Process& m_process; Process& m_process;
@@ -4,8 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/hle/service/am/applet.h" #include "core/hle/service/am/applet.h"
#include "core/hle/service/am/service/applet_common_functions.h" #include "core/hle/service/am/service/applet_common_functions.h"
#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/cmif_serialization.h"
@@ -80,7 +78,7 @@ Result IAppletCommonFunctions::SetCpuBoostRequestPriority(s32 priority) {
Result IAppletCommonFunctions::GetCurrentApplicationId(Out<u64> out_application_id) { Result IAppletCommonFunctions::GetCurrentApplicationId(Out<u64> out_application_id) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_WARNING(Service_AM, "(STUBBED) called");
*out_application_id = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID()); *out_application_id = system.GetApplicationProcessProgramID() & ~0xFFFULL;
R_SUCCEED(); R_SUCCEED();
} }
@@ -6,10 +6,8 @@
#include <openssl/evp.h> #include <openssl/evp.h>
#include "common/hex_util.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/uuid.h" #include "common/uuid.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/control_metadata.h" #include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h" #include "core/file_sys/registered_cache.h"
@@ -30,27 +28,6 @@
namespace Service::AM { namespace Service::AM {
namespace {
FileSys::PatchManager::Metadata GetApplicationMetadata(Core::System& system, u64 program_id) {
auto metadata = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, program_id);
if (metadata.first != nullptr) {
return metadata;
}
const auto application_id = FileSys::GetBaseTitleID(program_id);
if (application_id == program_id) {
return metadata;
}
LOG_DEBUG(Service_AM,
"no metadata for program_id={:016X}, falling back to application_id={:016X}",
program_id, application_id);
return FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, application_id);
}
} // Anonymous namespace
IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ptr<Applet> applet) IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ptr<Applet> applet)
: ServiceFramework{system_, "IApplicationFunctions"}, m_applet{std::move(applet)} { : ServiceFramework{system_, "IApplicationFunctions"}, m_applet{std::move(applet)} {
// clang-format off // clang-format off
@@ -179,7 +156,7 @@ Result IApplicationFunctions::GetDesiredLanguage(Out<u64> out_language_code) {
// Default to 0 (all languages supported) // Default to 0 (all languages supported)
u32 supported_languages = 0; u32 supported_languages = 0;
const auto res = GetApplicationMetadata(system, m_applet->program_id); const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
if (res.first != nullptr) { if (res.first != nullptr) {
supported_languages = res.first->GetSupportedLanguages(); supported_languages = res.first->GetSupportedLanguages();
} }
@@ -217,7 +194,7 @@ Result IApplicationFunctions::SetTerminateResult(Result terminate_result) {
Result IApplicationFunctions::GetDisplayVersion(Out<DisplayVersion> out_display_version) { Result IApplicationFunctions::GetDisplayVersion(Out<DisplayVersion> out_display_version) {
LOG_DEBUG(Service_AM, "called"); LOG_DEBUG(Service_AM, "called");
const auto res = GetApplicationMetadata(system, m_applet->program_id); const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
if (res.first != nullptr) { if (res.first != nullptr) {
const auto& version = res.first->GetVersionString(); const auto& version = res.first->GetVersionString();
std::memcpy(out_display_version->string.data(), version.data(), std::memcpy(out_display_version->string.data(), version.data(),
@@ -347,7 +324,6 @@ Result IApplicationFunctions::NotifyRunning(Out<bool> out_became_running) {
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) { Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
LOG_WARNING(Service_AM, "(stubbed)"); LOG_WARNING(Service_AM, "(stubbed)");
R_UNLESS(out_pseudo_device_id, ResultUnknown);
// This should be hashed with the device specific hash // This should be hashed with the device specific hash
// for now this will do // for now this will do
@@ -454,21 +430,15 @@ Result IApplicationFunctions::ExecuteProgram(ProgramSpecifyKind kind, u64 value)
// https://switchbrew.org/wiki/Applet_Manager_services#CreateApplicationAndRequestToStart // https://switchbrew.org/wiki/Applet_Manager_services#CreateApplicationAndRequestToStart
Result IApplicationFunctions::CreateApplicationAndRequestToStart(u64 application_id) { Result IApplicationFunctions::CreateApplicationAndRequestToStart(u64 application_id) {
LOG_INFO(Service_AM, "called, application_id={:016X} current_program_id={:016X}", application_id, m_applet->program_id); LOG_INFO(Service_AM, "called, application_id={:016X}", application_id);
if (application_id == 0 || // If application_id is 0, relaunch the current application
FileSys::GetBaseTitleID(application_id) == FileSys::GetBaseTitleID(m_applet->program_id)) { const u64 target_application_id =
const auto program_index = application_id == 0 (application_id == 0) ? m_applet->program_id : application_id;
? 0
: application_id - FileSys::GetBaseTitleID(application_id);
system.GetUserChannel() = m_applet->user_channel_launch_parameter; system.GetUserChannel() = m_applet->user_channel_launch_parameter;
system.ExecuteProgram(program_index); system.ExecuteProgram(target_application_id);
R_SUCCEED(); R_SUCCEED();
}
LOG_ERROR(Service_AM, "Launching a different application ({:016X}) is not implemented!", application_id);
R_THROW(ResultUnknown);
} }
Result IApplicationFunctions::ClearUserChannel() { Result IApplicationFunctions::ClearUserChannel() {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -8,7 +8,6 @@
#include "core/hle/service/am/applet.h" #include "core/hle/service/am/applet.h"
#include "core/hle/service/am/service/display_controller.h" #include "core/hle/service/am/service/display_controller.h"
#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/vi/shared_buffer_manager.h"
namespace Service::AM { namespace Service::AM {
@@ -72,16 +71,16 @@ Result IDisplayController::TakeScreenShotOfOwnLayer(bool unknown0, s32 fbshare_l
} }
Result IDisplayController::ClearCaptureBuffer(bool unknown0, s32 fbshare_layer_index, u32 color) { Result IDisplayController::ClearCaptureBuffer(bool unknown0, s32 fbshare_layer_index, u32 color) {
LOG_DEBUG(Service_AM, "called, unknown0={} fbshare_layer_index={} color={:#x}", unknown0, LOG_WARNING(Service_AM, "(STUBBED) called, unknown0={} fbshare_layer_index={} color={:#x}",
fbshare_layer_index, color); unknown0, fbshare_layer_index, color);
R_RETURN(applet->display_layer_manager.ClearAppletCaptureBuffer(fbshare_layer_index, color)); R_SUCCEED();
} }
Result IDisplayController::AcquireLastForegroundCaptureSharedBuffer( Result IDisplayController::AcquireLastForegroundCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) { Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
LOG_DEBUG(Service_AM, "called"); LOG_WARNING(Service_AM, "(STUBBED) called");
R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer( R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
out_was_written, out_fbshare_layer_index, VI::CaptureKind::LastForeground)); out_fbshare_layer_index));
} }
Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() { Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() {
@@ -91,9 +90,9 @@ Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() {
Result IDisplayController::AcquireCallerAppletCaptureSharedBuffer( Result IDisplayController::AcquireCallerAppletCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) { Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
LOG_DEBUG(Service_AM, "called"); LOG_WARNING(Service_AM, "(STUBBED) called");
R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer( R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
out_was_written, out_fbshare_layer_index, VI::CaptureKind::CallerApplet)); out_fbshare_layer_index));
} }
Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() { Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() {
@@ -103,9 +102,9 @@ Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() {
Result IDisplayController::AcquireLastApplicationCaptureSharedBuffer( Result IDisplayController::AcquireLastApplicationCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) { Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
LOG_DEBUG(Service_AM, "called"); LOG_WARNING(Service_AM, "(STUBBED) called");
R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer( R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
out_was_written, out_fbshare_layer_index, VI::CaptureKind::LastApplication)); out_fbshare_layer_index));
} }
Result IDisplayController::ReleaseLastApplicationCaptureSharedBuffer() { Result IDisplayController::ReleaseLastApplicationCaptureSharedBuffer() {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hle/service/am/applet.h" #include "core/hle/service/am/applet.h"
@@ -22,7 +22,7 @@ namespace Service::AM {
{10, nullptr, "StartShutdownSequenceForOverlay"}, {10, nullptr, "StartShutdownSequenceForOverlay"},
{11, nullptr, "StartRebootSequenceForOverlay"}, {11, nullptr, "StartRebootSequenceForOverlay"},
{20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"}, {20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"},
{21, D<&IOverlayFunctions::SetHandlingTouchScreenInputEnabled>, "SetHandlingTouchScreenInputEnabled"}, {21, nullptr, "SetHandlingTouchScreenInputEnabled"},
{30, nullptr, "SetHealthWarningShowingState"}, {30, nullptr, "SetHealthWarningShowingState"},
{31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"}, {31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"},
{40, nullptr, "GetApplicationNintendoLogo"}, {40, nullptr, "GetApplicationNintendoLogo"},
@@ -43,12 +43,10 @@ namespace Service::AM {
Result IOverlayFunctions::BeginToWatchShortHomeButtonMessage() { Result IOverlayFunctions::BeginToWatchShortHomeButtonMessage() {
LOG_DEBUG(Service_AM, "called"); LOG_DEBUG(Service_AM, "called");
{ m_applet->overlay_in_foreground = true;
std::scoped_lock lk{m_applet->lock}; m_applet->home_button_short_pressed_blocked = false;
m_applet->overlay_watching_short_home_button = true;
}
if (auto* window_system = system.GetAppletManager().GetWindowSystem()) { if (auto *window_system = system.GetAppletManager().GetWindowSystem()) {
window_system->RequestUpdate(); window_system->RequestUpdate();
} }
@@ -58,26 +56,16 @@ namespace Service::AM {
Result IOverlayFunctions::EndToWatchShortHomeButtonMessage() { Result IOverlayFunctions::EndToWatchShortHomeButtonMessage() {
LOG_DEBUG(Service_AM, "called"); LOG_DEBUG(Service_AM, "called");
{ m_applet->overlay_in_foreground = false;
std::scoped_lock lk{m_applet->lock}; m_applet->home_button_short_pressed_blocked = false;
m_applet->overlay_watching_short_home_button = false;
}
if (auto* window_system = system.GetAppletManager().GetWindowSystem()) { if (auto *window_system = system.GetAppletManager().GetWindowSystem()) {
window_system->RequestUpdate(); window_system->RequestUpdate();
} }
R_SUCCEED(); R_SUCCEED();
} }
Result IOverlayFunctions::SetHandlingTouchScreenInputEnabled(bool enabled) {
LOG_DEBUG(Service_AM, "called, enabled={}", enabled);
std::scoped_lock lk{m_applet->lock};
m_applet->overlay_handling_touch_input = enabled;
R_SUCCEED();
}
Result IOverlayFunctions::GetApplicationIdForLogo(Out<u64> out_application_id) { Result IOverlayFunctions::GetApplicationIdForLogo(Out<u64> out_application_id) {
LOG_DEBUG(Service_AM, "called"); LOG_DEBUG(Service_AM, "called");
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once
@@ -20,7 +20,6 @@ namespace Service::AM {
Result SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled); Result SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled);
Result IsHealthWarningRequired(Out<bool> is_required); Result IsHealthWarningRequired(Out<bool> is_required);
Result SetHandlingHomeButtonShortPressedEnabled(bool enabled); Result SetHandlingHomeButtonShortPressedEnabled(bool enabled);
Result SetHandlingTouchScreenInputEnabled(bool enabled);
Result Unknown70(); Result Unknown70();
private: private:
+80 -136
View File
@@ -4,11 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
#include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/am/am_results.h" #include "core/hle/service/am/am_results.h"
#include "core/hle/service/am/applet.h" #include "core/hle/service/am/applet.h"
#include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/applet_manager.h"
@@ -36,82 +32,46 @@ void WindowSystem::RequestUpdate() {
} }
void WindowSystem::Update() { void WindowSystem::Update() {
{ std::scoped_lock lk{m_lock};
std::scoped_lock lk{m_lock};
LOG_DEBUG(Service_AM, "called, home_menu={} application={} overlay={}", LOG_DEBUG(Service_AM, "called, home_menu={} application={} overlay={}",
m_home_menu != nullptr, m_application != nullptr, m_overlay_display != nullptr); m_home_menu != nullptr, m_application != nullptr, m_overlay_display != nullptr);
// Loop through all applets and remove terminated applets. // Loop through all applets and remove terminated applets.
this->PruneTerminatedAppletsLocked(); this->PruneTerminatedAppletsLocked();
// If the home menu is being locked into the foreground, handle that. // If the home menu is being locked into the foreground, handle that.
if (!this->LockHomeMenuIntoForegroundLocked()) { if (this->LockHomeMenuIntoForegroundLocked()) {
const bool overlay_takes_input = this->DoesOverlayTakeInputLocked(); return;
this->UpdateAppletStateLocked(m_overlay_display, true, overlay_takes_input);
this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu, overlay_takes_input);
this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application, overlay_takes_input);
}
} }
this->NotifyApplicationChangedIfNeeded(); bool overlay_blocks_input = false;
if (m_overlay_display) {
std::scoped_lock lk_overlay{m_overlay_display->lock};
overlay_blocks_input = m_overlay_display->overlay_in_foreground;
}
// Recursively update each applet root.
this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu, overlay_blocks_input);
this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application, overlay_blocks_input);
this->UpdateAppletStateLocked(m_overlay_display, true, false); // overlay is always updated, never blocked
} }
void WindowSystem::TrackApplet(std::shared_ptr<Applet> applet, bool is_application) { void WindowSystem::TrackApplet(std::shared_ptr<Applet> applet, bool is_application) {
{ std::scoped_lock lk{m_lock};
std::scoped_lock lk{m_lock};
if (applet->applet_id == AppletId::QLaunch) { if (applet->applet_id == AppletId::QLaunch) {
ASSERT(m_home_menu == nullptr); ASSERT(m_home_menu == nullptr);
m_home_menu = applet.get(); m_home_menu = applet.get();
} else if (applet->applet_id == AppletId::OverlayDisplay) { } else if (applet->applet_id == AppletId::OverlayDisplay) {
m_overlay_display = applet.get(); m_overlay_display = applet.get();
} else if (is_application) { } else if (is_application) {
ASSERT(m_application == nullptr); ASSERT(m_application == nullptr);
m_application = applet.get(); m_application = applet.get();
}
this->UpdateCurrentApplicationLocked();
m_event_observer->TrackAppletProcess(*applet);
m_applets.emplace(applet->aruid.pid, std::move(applet));
} }
this->NotifyApplicationChangedIfNeeded(); m_event_observer->TrackAppletProcess(*applet);
} m_applets.emplace(applet->aruid.pid, std::move(applet));
void WindowSystem::UpdateCurrentApplicationLocked() {
const Applet* const candidate = m_application != nullptr ? m_application : m_home_menu;
if (candidate == nullptr) {
return;
}
auto* const process = candidate->process->GetHandle();
if (process == nullptr || process == m_system.Kernel().ApplicationProcess()) {
return;
}
LOG_INFO(Service_AM, "Current application is now {:016X}", candidate->program_id);
m_system.Kernel().SetApplicationProcess(process);
Settings::SetCurrentProgramID(candidate->program_id);
m_pending_application_notification = candidate->program_id;
}
void WindowSystem::NotifyApplicationChangedIfNeeded() {
std::optional<u64> program_id;
{
std::scoped_lock lk{m_lock};
program_id = std::exchange(m_pending_application_notification, std::nullopt);
}
if (!program_id.has_value()) {
return;
}
m_system.NotifyApplicationChanged(*program_id);
} }
std::shared_ptr<Applet> WindowSystem::GetByAppletResourceUserId(u64 aruid) { std::shared_ptr<Applet> WindowSystem::GetByAppletResourceUserId(u64 aruid) {
@@ -209,40 +169,18 @@ void WindowSystem::OnExitRequested() {
} }
void WindowSystem::SendButtonAppletMessageLocked(AppletMessage message) { void WindowSystem::SendButtonAppletMessageLocked(AppletMessage message) {
const auto is_blocked = [message](const Applet& applet) { if (m_home_menu) {
if (message == AppletMessage::DetectShortPressingHomeButton && std::scoped_lock lk_home{m_home_menu->lock};
applet.applet_id == AppletId::OverlayDisplay && m_home_menu->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message);
!applet.overlay_watching_short_home_button) { }
return true; if (m_overlay_display) {
} std::scoped_lock lk_overlay{m_overlay_display->lock};
m_overlay_display->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message);
switch (message) { }
case AppletMessage::DetectShortPressingHomeButton: if (m_application) {
return applet.home_button_short_pressed_blocked; std::scoped_lock lk_application{m_application->lock};
case AppletMessage::DetectLongPressingHomeButton: m_application->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message);
return applet.home_button_long_pressed_blocked; }
default:
return false;
}
};
const auto send_to = [&](Applet* applet) {
if (!applet) {
return;
}
std::scoped_lock lk{applet->lock};
if (is_blocked(*applet)) {
LOG_DEBUG(Service_AM, "Applet {} is blocking message {}",
static_cast<u32>(applet->applet_id), static_cast<u32>(message));
return;
}
applet->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message);
};
send_to(m_home_menu);
send_to(m_overlay_display);
send_to(m_application);
if (m_event_observer) { if (m_event_observer) {
m_event_observer->RequestUpdate(); m_event_observer->RequestUpdate();
} }
@@ -254,9 +192,19 @@ void WindowSystem::OnSystemButtonPress(SystemButtonType type) {
case SystemButtonType::HomeButtonShortPressing: case SystemButtonType::HomeButtonShortPressing:
SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingHomeButton); SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingHomeButton);
break; break;
case SystemButtonType::HomeButtonLongPressing: case SystemButtonType::HomeButtonLongPressing: {
// Toggle overlay foreground visibility on long home press
if (m_overlay_display) {
std::scoped_lock lk_overlay{m_overlay_display->lock};
m_overlay_display->overlay_in_foreground = !m_overlay_display->overlay_in_foreground;
LOG_INFO(Service_AM, "Overlay long-press toggle: overlay_in_foreground={} window_visible={}", m_overlay_display->overlay_in_foreground, m_overlay_display->window_visible);
}
SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingHomeButton); SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingHomeButton);
break; // Force a state update after toggling overlay
if (m_event_observer) {
m_event_observer->RequestUpdate();
}
break; }
case SystemButtonType::CaptureButtonShortPressing: case SystemButtonType::CaptureButtonShortPressing:
SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingCaptureButton); SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingCaptureButton);
break; break;
@@ -369,8 +317,6 @@ void WindowSystem::PruneTerminatedAppletsLocked() {
m_overlay_display = nullptr; m_overlay_display = nullptr;
} }
this->UpdateCurrentApplicationLocked();
// Finalize applet. // Finalize applet.
applet->OnProcessTerminatedLocked(); applet->OnProcessTerminatedLocked();
@@ -443,20 +389,7 @@ void WindowSystem::TerminateChildAppletsLocked(Applet* applet) {
applet->lock.lock(); applet->lock.lock();
} }
bool WindowSystem::IsOverlayOpenLocked(const Applet& overlay) const { void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking) {
return overlay.window_visible && overlay.overlay_watching_short_home_button;
}
bool WindowSystem::DoesOverlayTakeInputLocked() const {
if (m_overlay_display == nullptr) {
return false;
}
std::scoped_lock lk{m_overlay_display->lock};
return this->IsOverlayOpenLocked(*m_overlay_display);
}
void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_takes_input) {
// With no applet, we don't have anything to do. // With no applet, we don't have anything to do.
if (!applet) { if (!applet) {
return; return;
@@ -487,18 +420,24 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
return false; return false;
}(); }();
const bool is_overlay = applet->applet_id == AppletId::OverlayDisplay;
// Update visibility state. // Update visibility state.
const bool should_be_visible = // Overlay applets should always be visible when window_visible is true, regardless of foreground state
is_overlay ? applet->window_visible : (is_foreground && applet->window_visible); const bool should_be_visible = (applet->applet_id == AppletId::OverlayDisplay)
? applet->window_visible
: (is_foreground && applet->window_visible);
applet->display_layer_manager.SetWindowVisibility(should_be_visible); applet->display_layer_manager.SetWindowVisibility(should_be_visible);
const bool needs_hid_input =
is_overlay ? this->IsOverlayOpenLocked(*applet)
: (is_foreground && applet->window_visible && !overlay_takes_input);
applet->SetInteractibleLocked(needs_hid_input, needs_hid_input); const bool should_be_interactible = (applet->applet_id == AppletId::OverlayDisplay)
? applet->overlay_in_foreground
: (is_foreground && applet->window_visible && !overlay_blocking);
if (applet->applet_id == AppletId::OverlayDisplay || applet->applet_id == AppletId::Application) {
LOG_DEBUG(Service_AM, "UpdateAppletStateLocked: applet={} overlay_in_foreground={} is_foreground={} window_visible={} overlay_blocking={} should_be_interactible={}",
static_cast<u32>(applet->applet_id), applet->overlay_in_foreground, is_foreground, applet->window_visible, overlay_blocking, should_be_interactible);
}
applet->SetInteractibleLocked(should_be_interactible);
// Update focus state and suspension. // Update focus state and suspension.
const bool is_obscured = has_obscuring_child_applets || !applet->window_visible; const bool is_obscured = has_obscuring_child_applets || !applet->window_visible;
@@ -514,18 +453,23 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
applet->UpdateSuspensionStateLocked(true); applet->UpdateSuspensionStateLocked(true);
} }
// Layer ordering. Composition sorts back-to-front. Now with enums for calrity. // Z-index logic like in reference C# implementation (tuned for overlay extremes)
s32 z_index = Background; s32 z_index = 0;
if (is_overlay) { const bool now_foreground = inherited_foreground;
z_index = Overlay; if (applet->applet_id == AppletId::OverlayDisplay) {
} else if (inherited_foreground) { z_index = applet->overlay_in_foreground ? 100000 : -1;
z_index = is_obscured ? Foreground : ForegroundVisible; } else if (now_foreground && !is_obscured) {
z_index = 2;
} else if (now_foreground) {
z_index = 1;
} else {
z_index = 0;
} }
applet->display_layer_manager.SetOverlayZIndex(z_index); applet->display_layer_manager.SetOverlayZIndex(z_index);
// Recurse into child applets. // Recurse into child applets.
for (const auto& child_applet : applet->child_applets) { for (const auto& child_applet : applet->child_applets) {
this->UpdateAppletStateLocked(child_applet.get(), is_foreground, overlay_takes_input); this->UpdateAppletStateLocked(child_applet.get(), is_foreground, overlay_blocking);
} }
} }
+1 -8
View File
@@ -9,7 +9,6 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/service/am/am_types.h" #include "core/hle/service/am/am_types.h"
@@ -61,16 +60,11 @@ public:
void OnPowerButtonPressed(ButtonPressDuration type) {} void OnPowerButtonPressed(ButtonPressDuration type) {}
private: private:
void UpdateCurrentApplicationLocked();
void NotifyApplicationChangedIfNeeded();
void PruneTerminatedAppletsLocked(); void PruneTerminatedAppletsLocked();
bool RestartAppletProcessLocked(Applet* applet); bool RestartAppletProcessLocked(Applet* applet);
bool LockHomeMenuIntoForegroundLocked(); bool LockHomeMenuIntoForegroundLocked();
void TerminateChildAppletsLocked(Applet* applet); void TerminateChildAppletsLocked(Applet* applet);
bool IsOverlayOpenLocked(const Applet& overlay) const; void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking = false);
bool DoesOverlayTakeInputLocked() const;
void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_takes_input);
void SendButtonAppletMessageLocked(AppletMessage message); void SendButtonAppletMessageLocked(AppletMessage message);
private: private:
@@ -94,7 +88,6 @@ private:
// Applet map by aruid. // Applet map by aruid.
std::map<u64, std::shared_ptr<Applet>> m_applets{}; std::map<u64, std::shared_ptr<Applet>> m_applets{};
std::optional<u64> m_pending_application_notification{};
}; };
} // namespace Service::AM } // namespace Service::AM
@@ -23,7 +23,6 @@
#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/server_manager.h" #include "core/hle/service/server_manager.h"
#include "core/hle/service/am/am_results.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
namespace Service::AOC { namespace Service::AOC {
@@ -32,10 +31,6 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return FileSys::GetBaseTitleID(title_id) == base; return FileSys::GetBaseTitleID(title_id) == base;
} }
static u64 GetCallerBaseTitleID(Core::System& system, const ClientProcessId& process_id) {
return FileSys::GetBaseTitleID(system.ResolveCallerProgramId(*process_id));
}
static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) { static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
std::vector<u64> add_on_content; std::vector<u64> add_on_content;
const auto& rcu = system.GetContentProvider(); const auto& rcu = system.GetContentProvider();
@@ -96,7 +91,7 @@ IAddOnContentManager::~IAddOnContentManager() {
Result IAddOnContentManager::CountAddOnContent(Out<u32> out_count, ClientProcessId process_id) { Result IAddOnContentManager::CountAddOnContent(Out<u32> out_count, ClientProcessId process_id) {
LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid);
const auto current = GetCallerBaseTitleID(system, process_id); const auto current = system.GetApplicationProcessProgramID();
const auto& disabled = Settings::values.disabled_addons[current]; const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) { if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
@@ -117,7 +112,7 @@ Result IAddOnContentManager::ListAddOnContent(Out<u32> out_count,
LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count, LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count,
process_id.pid); process_id.pid);
const auto current = GetCallerBaseTitleID(system, process_id); const auto current = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID());
std::vector<u32> out; std::vector<u32> out;
const auto& disabled = Settings::values.disabled_addons[current]; const auto& disabled = Settings::values.disabled_addons[current];
@@ -131,7 +126,8 @@ Result IAddOnContentManager::ListAddOnContent(Out<u32> out_count,
} }
} }
R_UNLESS(out.size() >= offset, AM::ResultApplicationRecordNotFound); // TODO(DarkLordZach): Find the correct error code.
R_UNLESS(out.size() >= offset, ResultUnknown);
*out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count)); *out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
std::rotate(out.begin(), out.begin() + offset, out.end()); std::rotate(out.begin(), out.begin() + offset, out.end());
@@ -145,7 +141,7 @@ Result IAddOnContentManager::GetAddOnContentBaseId(Out<u64> out_title_id,
ClientProcessId process_id) { ClientProcessId process_id) {
LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid);
const auto title_id = system.ResolveCallerProgramId(*process_id); const auto title_id = system.GetApplicationProcessProgramID();
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()}; system.GetContentProvider()};
+7 -5
View File
@@ -24,8 +24,8 @@ static u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
return out; return out;
} }
IBcatService::IBcatService(Core::System& system_, BcatBackend& backend_, u64 program_id_) IBcatService::IBcatService(Core::System& system_, BcatBackend& backend_)
: ServiceFramework{system_, "IBcatService"}, backend{backend_}, program_id{program_id_}, : ServiceFramework{system_, "IBcatService"}, backend{backend_},
progress{{ progress{{
ProgressServiceBackend{system_, "Normal"}, ProgressServiceBackend{system_, "Normal"},
ProgressServiceBackend{system_, "Directory"}, ProgressServiceBackend{system_, "Directory"},
@@ -70,7 +70,8 @@ Result IBcatService::RequestSyncDeliveryCache(
LOG_DEBUG(Service_BCAT, "called"); LOG_DEBUG(Service_BCAT, "called");
auto& progress_backend{GetProgressBackend(SyncType::Normal)}; auto& progress_backend{GetProgressBackend(SyncType::Normal)};
backend.Synchronize(system.Kernel(), {program_id, GetCurrentBuildID(system.GetApplicationProcessBuildID())}, backend.Synchronize(system.Kernel(), {system.GetApplicationProcessProgramID(),
GetCurrentBuildID(system.GetApplicationProcessBuildID())},
GetProgressBackend(SyncType::Normal)); GetProgressBackend(SyncType::Normal));
*out_interface = std::make_shared<IDeliveryCacheProgressService>( *out_interface = std::make_shared<IDeliveryCacheProgressService>(
@@ -85,8 +86,9 @@ Result IBcatService::RequestSyncDeliveryCacheWithDirectoryName(
LOG_DEBUG(Service_BCAT, "called, name={}", name); LOG_DEBUG(Service_BCAT, "called, name={}", name);
auto& progress_backend{GetProgressBackend(SyncType::Directory)}; auto& progress_backend{GetProgressBackend(SyncType::Directory)};
backend.SynchronizeDirectory(system.Kernel(), {program_id, GetCurrentBuildID(system.GetApplicationProcessBuildID())}, backend.SynchronizeDirectory(system.Kernel(), {system.GetApplicationProcessProgramID(),
name, progress_backend); GetCurrentBuildID(system.GetApplicationProcessBuildID())},
name, progress_backend);
*out_interface = std::make_shared<IDeliveryCacheProgressService>( *out_interface = std::make_shared<IDeliveryCacheProgressService>(
system, progress_backend.GetEvent(), progress_backend.GetImpl()); system, progress_backend.GetEvent(), progress_backend.GetImpl());
+1 -5
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@@ -22,7 +19,7 @@ class IDeliveryCacheProgressService;
class IBcatService final : public ServiceFramework<IBcatService> { class IBcatService final : public ServiceFramework<IBcatService> {
public: public:
explicit IBcatService(Core::System& system_, BcatBackend& backend_, u64 program_id_); explicit IBcatService(Core::System& system_, BcatBackend& backend_);
~IBcatService() override; ~IBcatService() override;
private: private:
@@ -42,7 +39,6 @@ private:
const ProgressServiceBackend& GetProgressBackend(SyncType type) const; const ProgressServiceBackend& GetProgressBackend(SyncType type) const;
BcatBackend& backend; BcatBackend& backend;
u64 program_id;
std::array<ProgressServiceBackend, static_cast<size_t>(SyncType::Count)> progress; std::array<ProgressServiceBackend, static_cast<size_t>(SyncType::Count)> progress;
}; };
@@ -40,8 +40,8 @@ std::vector<u8> default_logo_small;
std::vector<u8> default_logo_large; std::vector<u8> default_logo_large;
bool default_logos_loaded = false; bool default_logos_loaded = false;
::Common::unordered_map<std::string, std::vector<u8>> news_images_small; ankerl::unordered_dense::map<std::string, std::vector<u8>> news_images_small;
::Common::unordered_map<std::string, std::vector<u8>> news_images_large; ankerl::unordered_dense::map<std::string, std::vector<u8>> news_images_large;
std::mutex images_mutex; std::mutex images_mutex;
@@ -12,7 +12,7 @@
#include <optional> #include <optional>
#include <span> #include <span>
#include <string> #include <string>
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
@@ -93,7 +93,7 @@ private:
static s64 Now(); static s64 Now();
mutable std::mutex mtx; mutable std::mutex mtx;
::Common::unordered_map<std::string, StoredNews> items; ankerl::unordered_dense::map<std::string, StoredNews> items;
size_t open_counter{}; size_t open_counter{};
}; };
@@ -1,10 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/service/bcat/bcat_service.h" #include "core/hle/service/bcat/bcat_service.h"
#include "core/hle/service/bcat/delivery_cache_storage_service.h" #include "core/hle/service/bcat/delivery_cache_storage_service.h"
#include "core/hle/service/bcat/service_creator.h" #include "core/hle/service/bcat/service_creator.h"
@@ -41,8 +37,7 @@ IServiceCreator::~IServiceCreator() = default;
Result IServiceCreator::CreateBcatService(ClientProcessId process_id, Result IServiceCreator::CreateBcatService(ClientProcessId process_id,
OutInterface<IBcatService> out_interface) { OutInterface<IBcatService> out_interface) {
LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid); LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid);
*out_interface = *out_interface = std::make_shared<IBcatService>(system, *backend);
std::make_shared<IBcatService>(system, *backend, system.ResolveCallerProgramId(*process_id));
R_SUCCEED(); R_SUCCEED();
} }
@@ -50,7 +45,7 @@ Result IServiceCreator::CreateDeliveryCacheStorageService(
ClientProcessId process_id, OutInterface<IDeliveryCacheStorageService> out_interface) { ClientProcessId process_id, OutInterface<IDeliveryCacheStorageService> out_interface) {
LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid); LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid);
const auto title_id = system.ResolveCallerProgramId(*process_id); const auto title_id = system.GetApplicationProcessProgramID();
*out_interface = *out_interface =
std::make_shared<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id)); std::make_shared<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
R_SUCCEED(); R_SUCCEED();
+2 -7
View File
@@ -243,15 +243,10 @@ Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
return SaveScreenShot(out_entry, attribute, report_option, {}, image_data, aruid); return SaveScreenShot(out_entry, attribute, report_option, {}, image_data, aruid);
} }
Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, AlbumReportOption report_option, const ApplicationData& app_data, std::span<const u8> image_data, u64 aruid) {
const ScreenShotAttribute& attribute,
AlbumReportOption report_option,
const ApplicationData& app_data, std::span<const u8> image_data,
u64 aruid) {
R_UNLESS(!image_data.empty(), ResultUnknown); //TODO: ??? R_UNLESS(!image_data.empty(), ResultUnknown); //TODO: ???
const u64 title_id = system.ResolveCallerProgramId(aruid); const u64 title_id = system.GetApplicationProcessProgramID();
auto static_service = auto static_service =
system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true); system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true);
+2 -2
View File
@@ -6,7 +6,7 @@
#pragma once #pragma once
#include "common/container/unordered_map.h" #include <ankerl/unordered_dense.h>
#include "common/fs/fs.h" #include "common/fs/fs.h"
#include "core/hle/result.h" #include "core/hle/result.h"
@@ -88,7 +88,7 @@ private:
AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const; AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
bool is_mounted{}; bool is_mounted{};
::Common::unordered_map<AlbumFileId, std::filesystem::path> album_files; ankerl::unordered_dense::map<AlbumFileId, std::filesystem::path> album_files;
Core::System& system; Core::System& system;
}; };
+1 -2
View File
@@ -95,8 +95,7 @@ void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption r
manager->FlipVerticallyOnWrite(invert_y); manager->FlipVerticallyOnWrite(invert_y);
manager->SaveScreenShot(entry, attribute, report_option, image_data, {}); manager->SaveScreenShot(entry, attribute, report_option, image_data, {});
}, },
layout, layout);
Nvnflinger::LayerStackId::Screenshot);
} }
} // namespace Service::Capture } // namespace Service::Capture

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