Compare commits

..

11 Commits

Author SHA1 Message Date
lizzie e5b656e372 [common/logging] Fix long logs, remove clutter on Android logcat (#4310)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

compiler where this occurs

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4260
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-08-29 20:59:57 +02:00
74 changed files with 819 additions and 877 deletions
+2 -2
View File
@@ -340,10 +340,10 @@
"version": "vulkan-sdk-%NUMERIC_VERSION%" "version": "vulkan-sdk-%NUMERIC_VERSION%"
}, },
"xbyak": { "xbyak": {
"hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a", "hash": "e0aa0a603dd3ac1a39d82213df1e73c042831aec2d6b2fe382c899651eaae4ed7e8aeb6be9c41ea0097087c9d091961ab18f313cd6a9e10d621715ffd49bfe36",
"package": "xbyak", "package": "xbyak",
"repo": "herumi/xbyak", "repo": "herumi/xbyak",
"version": "v7.35.2" "version": "v7.40.1"
}, },
"zlib": { "zlib": {
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
+2
View File
@@ -509,6 +509,8 @@ QWidget#contentRichDialog QLabel#label_title_rich {
} }
QWidget#contentDialog QLabel#label_dialog { QWidget#contentDialog QLabel#label_dialog {
background: #2E2E2E;
padding: 20px 65px; padding: 20px 65px;
} }
+24 -8
View File
@@ -43,6 +43,7 @@ This guide will walk you through adding a new boolean toggle setting to Eden's c
Firstly add your desired toggle: Firstly add your desired toggle:
Example: `src/common/setting.h` Example: `src/common/setting.h`
```cpp ```cpp
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions}; SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
``` ```
@@ -67,6 +68,7 @@ Common Categories:
Add the toggle to the Qt UI, where you wish for it to appear and place it there. Add the toggle to the Qt UI, where you wish for it to appear and place it there.
Example: `src/qt_common/config/shared_translation.cpp` Example: `src/qt_common/config/shared_translation.cpp`
```cpp ```cpp
INSERT(Settings, INSERT(Settings,
your_setting_name, your_setting_name,
@@ -91,6 +93,7 @@ INSERT(Settings,
Add where it should be in the settings. Add where it should be in the settings.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
```kts ```kts
RENDERER_YOUR_SETTING_NAME("your_setting_name"), RENDERER_YOUR_SETTING_NAME("your_setting_name"),
``` ```
@@ -106,6 +109,7 @@ RENDERER_YOUR_SETTING_NAME("your_setting_name"),
Add the toggle to the Kotlin (Android) UI Add the toggle to the Kotlin (Android) UI
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
```kts ```kts
put( put(
SwitchSetting( SwitchSetting(
@@ -123,6 +127,7 @@ put(
Add your setting within the right category. Add your setting within the right category.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt` Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
```kts ```kts
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key) add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
``` ```
@@ -137,6 +142,7 @@ add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
Add your setting and description in the appropriate place. Add your setting and description in the appropriate place.
Example: `src/android/app/src/main/res/values/strings.xml` Example: `src/android/app/src/main/res/values/strings.xml`
```xml ```xml
<string name="your_setting_name">Your Setting Display Name</string> <string name="your_setting_name">Your Setting Display Name</string>
<string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string> <string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string>
@@ -150,6 +156,7 @@ Now the UI part is done find a place in the code for the toggle,
And use it to your heart's desire! And use it to your heart's desire!
Example: Example:
```cpp ```cpp
const bool your_value = Settings::values.your_setting_name.GetValue(); const bool your_value = Settings::values.your_setting_name.GetValue();
@@ -196,25 +203,31 @@ Common advantages recap:
#### Accessing Debug Knobs (dev side) #### Accessing Debug Knobs (dev side)
Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set: Use the `Settings::GetDebugKnobAt(u8 i)` function to check if a specific bit is set:
```cpp ```cpp
//cpp side //cpp side
#include "common/settings.h" #include "common/settings.h"
//To use it as a general purpose uint var:
unsigned int debug_knobs = Settings::values.debug_knobs.GetValue();
// Check if bit 0 is set // Check if bit 0 is set
bool feature_enabled = Settings::getDebugKnobAt(0); bool feature_enabled = Settings::GetDebugKnobAt(0);
// Check if bit 15 is set // Check if bit 15 is set
bool another_feature = Settings::getDebugKnobAt(15); bool another_feature = Settings::GetDebugKnobAt(15);
``` ```
```kts ```kts
//kotlin side //kotlin side
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
//To use it as a general purpose uint var
val debug_knobs: Int = UShortSetting.DEBUG_KNOBS.getInt()
// Check if bit x is set // Check if bit x is set
bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15 bool feature_enabled = Settings.GetDebugKnobAt(x); //x as integer from 0 to 15
``` ```
The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise. The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise.
@@ -247,6 +260,7 @@ There are two main confusions when talking about knobs:
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1). Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
Debug knobs are **zero-based**, which means: Debug knobs are **zero-based**, which means:
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise) * The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc. * You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
@@ -259,6 +273,7 @@ Whenever you're instructing tests or reporting results, be precise about whether
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once: ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
Examples: Examples:
- **knobs=0**: no knobs enabled - **knobs=0**: no knobs enabled
- **knobs=1**: knob0 enabled, others disabled - **knobs=1**: knob0 enabled, others disabled
- **knobs=2**: knob1 enabled, others disabled - **knobs=2**: knob1 enabled, others disabled
@@ -270,6 +285,7 @@ Examples:
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once: Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
Examples: Examples:
- **knob0**: knob 0 enabled, others disabled - **knob0**: knob 0 enabled, others disabled
- **knob1**: knob 1 enabled, others disabled - **knob1**: knob 1 enabled, others disabled
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled - **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
@@ -282,12 +298,12 @@ Examples:
```cpp ```cpp
void SomeFunction() { void SomeFunction() {
if (Settings::getDebugKnobAt(0)) { if (Settings::GetDebugKnobAt(0)) {
LOG_DEBUG(Common, "Debug feature 0 is enabled"); LOG_DEBUG(Common, "Debug feature 0 is enabled");
// Additional debug code here // Additional debug code here
} }
if (Settings::getDebugKnobAt(1)) { if (Settings::GetDebugKnobAt(1)) {
LOG_DEBUG(Common, "Debug feature 1 is enabled"); LOG_DEBUG(Common, "Debug feature 1 is enabled");
// Different debug behavior // Different debug behavior
} }
@@ -299,7 +315,7 @@ void SomeFunction() {
```cpp ```cpp
bool UseOptimizedPath() { bool UseOptimizedPath() {
// Skip optimization if debug bit 2 is set for testing // Skip optimization if debug bit 2 is set for testing
return !Settings::getDebugKnobAt(2); return !Settings::GetDebugKnobAt(2);
} }
``` ```
@@ -309,7 +325,7 @@ bool UseOptimizedPath() {
void ExperimentalFeature() { void ExperimentalFeature() {
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3; static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) { if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
// Fallback to stable implementation // Fallback to stable implementation
StableImplementation(); StableImplementation();
return; return;
@@ -220,7 +220,7 @@ object NativeLibrary {
external fun refreshThreadPolicies() external fun refreshThreadPolicies()
external fun getDebugKnobAt(index: Int): Boolean external fun GetDebugKnobAt(index: Int): Boolean
/** /**
* Set the current speed limit to the configured turbo speed. * Set the current speed limit to the configured turbo speed.
@@ -35,8 +35,8 @@ object Settings {
fun getPlayerString(player: Int): String = fun getPlayerString(player: Int): String =
YuzuApplication.appContext.getString(R.string.preferences_player, player) YuzuApplication.appContext.getString(R.string.preferences_player, player)
fun getDebugKnobAt(index: Int): Boolean { fun GetDebugKnobAt(index: Int): Boolean {
return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index) return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index)
} }
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
@@ -11,8 +11,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(override val key: String) : AbstractShortSetting { enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit"), RENDERER_SPEED_LIMIT("speed_limit"),
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"), RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"), RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit")
DEBUG_KNOBS("debug_knobs")
; ;
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal) override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class UShortSetting(override val key: String) : AbstractIntSetting {
DEBUG_KNOBS("debug_knobs")
;
override fun getInt(needsGlobal: Boolean): Int =
NativeConfig.getUnsignedShort(key, needsGlobal)
override fun setInt(value: Int) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setUnsignedShort(key, value)
}
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
override fun reset() = NativeConfig.setUnsignedShort(key, defaultValue)
}
@@ -20,6 +20,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
import org.yuzu.yuzu_emu.network.NetDataValidators import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
@@ -1034,7 +1035,7 @@ abstract class SettingsItem(
) )
put( put(
SpinBoxSetting( SpinBoxSetting(
ShortSetting.DEBUG_KNOBS, UShortSetting.DEBUG_KNOBS,
titleId = R.string.debug_knobs, titleId = R.string.debug_knobs,
descriptionId = R.string.debug_knobs_description, descriptionId = R.string.debug_knobs_description,
valueHint = R.string.debug_knobs_hint, valueHint = R.string.debug_knobs_hint,
@@ -25,6 +25,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
@@ -1326,7 +1327,7 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.general)) add(HeaderSetting(R.string.general))
add(ShortSetting.DEBUG_KNOBS.key) add(UShortSetting.DEBUG_KNOBS.key)
add(StringSetting.PROGRAM_ARGS.key) add(StringSetting.PROGRAM_ARGS.key)
if (!NativeConfig.isPerGameConfigLoaded()) { if (!NativeConfig.isPerGameConfigLoaded()) {
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -27,6 +24,6 @@ object SettingsFile {
fun loadCustomConfig(game: Game) { fun loadCustomConfig(game: Game) {
val fileName = FileUtil.getFilename(Uri.parse(game.path)) val fileName = FileUtil.getFilename(Uri.parse(game.path))
NativeConfig.initializePerGameConfig(game.applicationId, fileName) NativeConfig.initializePerGameConfig(game.programId, fileName)
} }
} }
@@ -189,7 +189,7 @@ class AddonsFragment : Fragment() {
fragmentManager = parentFragmentManager, fragmentManager = parentFragmentManager,
addonViewModel = addonViewModel, addonViewModel = addonViewModel,
documents = documents, documents = documents,
programId = args.game.applicationId programId = args.game.programId
) )
} }
@@ -347,7 +347,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
try { try {
if (GpuDriverHelper.isAdrenoGpu()) { if (GpuDriverHelper.isAdrenoGpu()) {
val programIdHex = game!!.applicationIdHex val programIdHex = game!!.programIdHex
if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) { if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) {
Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex") Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex")
} else { } else {
@@ -59,7 +59,7 @@ class FreedrenoSettingsFragment : Fragment() {
NativeFreedrenoConfig.initializeFreedrenoConfig() NativeFreedrenoConfig.initializeFreedrenoConfig()
if (isPerGameConfig) { if (isPerGameConfig) {
NativeFreedrenoConfig.loadPerGameConfig(game!!.applicationIdHex) NativeFreedrenoConfig.loadPerGameConfig(game!!.programIdHex)
} else { } else {
NativeFreedrenoConfig.reloadFreedrenoConfig() NativeFreedrenoConfig.reloadFreedrenoConfig()
} }
@@ -157,7 +157,7 @@ class FreedrenoSettingsFragment : Fragment() {
binding.buttonSave.setOnClickListener { binding.buttonSave.setOnClickListener {
if (isPerGameConfig) { if (isPerGameConfig) {
NativeFreedrenoConfig.savePerGameConfig(game!!.applicationIdHex) NativeFreedrenoConfig.savePerGameConfig(game!!.programIdHex)
showSnackbar(getString(R.string.freedreno_per_game_saved)) showSnackbar(getString(R.string.freedreno_per_game_saved))
} else { } else {
NativeFreedrenoConfig.saveFreedrenoConfig() NativeFreedrenoConfig.saveFreedrenoConfig()
@@ -455,7 +455,7 @@ class GamePropertiesFragment : Fragment() {
val shaderCacheDir = File( val shaderCacheDir = File(
DirectoryInitialization.userDirectory + DirectoryInitialization.userDirectory +
"/cache/shader/" + args.game.shaderCacheName.lowercase() "/cache/shader/" + args.game.settingsName.lowercase()
) )
if (shaderCacheDir.exists()) { if (shaderCacheDir.exists()) {
add( add(
@@ -600,7 +600,7 @@ class GamePropertiesFragment : Fragment() {
val files = cacheSaveDir.listFiles() val files = cacheSaveDir.listFiles()
var savesFolderFile: File? = null var savesFolderFile: File? = null
if (files != null) { if (files != null) {
val savesFolderName = args.game.applicationIdHex val savesFolderName = args.game.programIdHex
for (file in files) { for (file in files) {
if (file.isDirectory && file.name == savesFolderName) { if (file.isDirectory && file.name == savesFolderName) {
savesFolderFile = file savesFolderFile = file
@@ -232,7 +232,7 @@ class InstallableFragment : Fragment() {
fragmentManager = parentFragmentManager, fragmentManager = parentFragmentManager,
addonViewModel = addonViewModel, addonViewModel = addonViewModel,
documents = documents, documents = documents,
programId = addonViewModel.game?.applicationId programId = addonViewModel.game?.programId
) )
} }
@@ -142,11 +142,10 @@ class AddonViewModel : ViewModel() {
} }
fun onDeleteAddon(patch: Patch) { fun onDeleteAddon(patch: Patch) {
val currentGame = game ?: return
when (PatchType.from(patch.type)) { when (PatchType.from(patch.type)) {
PatchType.Update -> NativeLibrary.removeUpdate(currentGame.programId) PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
PatchType.DLC -> NativeLibrary.removeDLC(currentGame.applicationId) PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
PatchType.Mod -> NativeLibrary.removeMod(currentGame.programId, patch.name) PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
} }
refreshAddons(force = true) refreshAddons(force = true)
} }
@@ -166,7 +165,7 @@ class AddonViewModel : ViewModel() {
} }
NativeConfig.setDisabledAddons( NativeConfig.setDisabledAddons(
currentGame.applicationId, currentGame.programId,
currentList.mapNotNull { currentList.mapNotNull {
if (it.enabled) { if (it.enabled) {
null null
@@ -200,6 +199,6 @@ class AddonViewModel : ViewModel() {
} }
private fun gameKey(game: Game): String { private fun gameKey(game: Game): String {
return "${game.applicationId}|${game.path}" return "${game.programId}|${game.path}"
} }
} }
@@ -150,7 +150,7 @@ class DriverViewModel : ViewModel() {
?: return@withContext ?: return@withContext
val shaderDir = File( val shaderDir = File(
externalFilesDir.absolutePath + externalFilesDir.absolutePath +
"/shader/" + game.shaderCacheName.lowercase() "/shader/" + game.settingsName.lowercase()
) )
if (shaderDir.exists()) { if (shaderDir.exists()) {
shaderDir.deleteRecursively() shaderDir.deleteRecursively()
@@ -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: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -35,26 +35,19 @@ class Game(
val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime" val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${path}_LastPlayed" val keyLastPlayedTime get() = "${path}_LastPlayed"
private val programIdLong: Long
get() = programId.toLongOrNull() ?: 0L
private val applicationIdLong: Long
get() = programIdLong and -8192L
val applicationId: String
get() = applicationIdLong.toString()
val settingsName: String val settingsName: String
get() { get() {
return if (applicationIdLong == 0L) { val programIdLong = programId.toLong()
return if (programIdLong == 0L) {
FileUtil.getFilename(Uri.parse(path)) FileUtil.getFilename(Uri.parse(path))
} else { } else {
"0" + applicationIdLong.toString(16).uppercase() "0" + programIdLong.toString(16).uppercase()
} }
} }
val programIdHex: String val programIdHex: String
get() { get() {
val programIdLong = programId.toLong()
return if (programIdLong == 0L) { return if (programIdLong == 0L) {
"0" "0"
} else { } else {
@@ -62,32 +55,16 @@ class Game(
} }
} }
val shaderCacheName: String
get() = if (programIdLong == 0L) {
FileUtil.getFilename(Uri.parse(path))
} else {
"0" + programIdLong.toString(16).uppercase()
}
val applicationIdHex: String
get() {
return if (applicationIdLong == 0L) {
"0"
} else {
"0" + applicationIdLong.toString(16).uppercase()
}
}
val saveZipName: String val saveZipName: String
get() = "$title ${YuzuApplication.appContext.getString(R.string.save_data).lowercase()} - ${ get() = "$title ${YuzuApplication.appContext.getString(R.string.save_data).lowercase()} - ${
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
}.zip" }.zip"
val saveDir: String val saveDir: String
get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(applicationId) get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(programId)
val addonDir: String val addonDir: String
get() = DirectoryInitialization.userDirectory + "/load/" + applicationIdHex + "/" get() = DirectoryInitialization.userDirectory + "/load/" + programIdHex + "/"
val launchIntent: Intent val launchIntent: Intent
get() = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { get() = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
@@ -47,7 +47,6 @@ import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler import info.debatty.java.stringsimilarity.JaroWinkler
import java.util.Locale import java.util.Locale
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.view.doOnNextLayout
class GamesFragment : Fragment() { class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null private var _binding: FragmentGamesBinding? = null
@@ -59,7 +58,6 @@ 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
@@ -227,12 +225,7 @@ class GamesFragment : Fragment() {
} }
else -> throw IllegalArgumentException("Invalid view type: $savedViewType") else -> throw IllegalArgumentException("Invalid view type: $savedViewType")
} }
if (savedViewType == GameAdapter.VIEW_TYPE_CAROUSEL) { if (savedViewType != GameAdapter.VIEW_TYPE_CAROUSEL) {
(binding.gridGames as? View)?.let { it -> ViewCompat.requestApplyInsets(it)}
doOnNextLayout { //Carousel: important to avoid overlap issues
(this as? CarouselRecyclerView)?.notifyLaidOut(fallbackBottomInset)
}
} else {
(this as? CarouselRecyclerView)?.setupCarousel(false) (this as? CarouselRecyclerView)?.setupCarousel(false)
} }
adapter = gameAdapter adapter = gameAdapter
@@ -590,11 +583,6 @@ class GamesFragment : Fragment() {
qlaunchButton.layoutParams = mlpQLaunch qlaunchButton.layoutParams = mlpQLaunch
} }
val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom)
fallbackBottomInset = bottomInset
(binding.gridGames as? CarouselRecyclerView)?.notifyInsetsReady(bottomInset)
windowInsets windowInsets
} }
} }
@@ -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: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -65,7 +65,7 @@ object CustomSettingsHandler {
// Initialize per-game config // Initialize per-game config
try { try {
val fileName = FileUtil.getFilename(Uri.parse(game.path)) val fileName = FileUtil.getFilename(Uri.parse(game.path))
NativeConfig.initializePerGameConfig(game.applicationId, fileName) NativeConfig.initializePerGameConfig(game.programId, fileName)
Log.info("[CustomSettingsHandler] Successfully applied custom settings") Log.info("[CustomSettingsHandler] Successfully applied custom settings")
return game return game
} catch (e: Exception) { } catch (e: Exception) {
@@ -333,20 +333,20 @@ object CustomSettingsHandler {
*/ */
fun findGameByTitleId(titleId: String, context: Context): Game? { fun findGameByTitleId(titleId: String, context: Context): Game? {
Log.info("[CustomSettingsHandler] Searching for game with title ID: $titleId") Log.info("[CustomSettingsHandler] Searching for game with title ID: $titleId")
// Convert the program ID to the application ID used by per-game settings. // Convert hex title ID to decimal for comparison with programId
val applicationIdLong = try { val programIdDecimal = try {
titleId.toLong(16) and -8192L titleId.toLong(16).toString()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.error("[CustomSettingsHandler] Invalid title ID format: $titleId") Log.error("[CustomSettingsHandler] Invalid title ID format: $titleId")
return null return null
} }
val applicationIdDecimal = applicationIdLong.toString()
// Expected hex format with "0" prefix // Expected hex format with "0" prefix
val expectedHex = "0${applicationIdLong.toString(16).uppercase()}" val expectedHex = "0${titleId.uppercase()}"
// First check cached games for fast lookup // First check cached games for fast lookup
GameHelper.cachedGameList.find { game -> GameHelper.cachedGameList.find { game ->
game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true) game.programId == programIdDecimal ||
game.programIdHex.equals(expectedHex, ignoreCase = true)
}?.let { foundGame -> }?.let { foundGame ->
Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}") Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}")
return foundGame return foundGame
@@ -355,7 +355,8 @@ object CustomSettingsHandler {
Log.info("[CustomSettingsHandler] Game not in cache, scanning full library...") Log.info("[CustomSettingsHandler] Game not in cache, scanning full library...")
val allGames = GameHelper.getGames() val allGames = GameHelper.getGames()
val foundGame = allGames.find { game -> val foundGame = allGames.find { game ->
game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true) game.programId == programIdDecimal ||
game.programIdHex.equals(expectedHex, ignoreCase = true)
} }
if (foundGame != null) { if (foundGame != null) {
Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}") Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}")
@@ -170,12 +170,12 @@ object GameHelper {
val game = getGame(it.uri, true, false) val game = getGame(it.uri, true, false)
if (game != null) { if (game != null) {
games.add(game) games.add(game)
if (game.applicationId != "0") { if (game.programId != "0") {
gamesByProgramId[game.applicationId] = game gamesByProgramId[game.programId] = game
} }
} else if (mountedContainer) { } else if (mountedContainer) {
GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId -> GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId ->
gamesByProgramId[(programId and -8192L).toString()] gamesByProgramId[(programId and 0x800L.inv()).toString()]
}?.let { existingGame -> }?.let { existingGame ->
NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId) NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId)
existingGame.version = GameMetadata.getVersion( existingGame.version = GameMetadata.getVersion(
@@ -80,6 +80,12 @@ object NativeConfig {
@Synchronized @Synchronized
external fun setShort(key: String, value: Short) external fun setShort(key: String, value: Short)
@Synchronized
external fun getUnsignedShort(key: String, needsGlobal: Boolean): Int
@Synchronized
external fun setUnsignedShort(key: String, value: Int)
@Synchronized @Synchronized
external fun getInt(key: String, needsGlobal: Boolean): Int external fun getInt(key: String, needsGlobal: Boolean): Int
@@ -12,13 +12,16 @@ 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,
@@ -32,7 +35,9 @@ class CarouselRecyclerView @JvmOverloads constructor(
private var overlapFactor: Float = 0f private var overlapFactor: Float = 0f
private var overlapPx: Int = 0 private var overlapPx: Int = 0
private var bottomInset: Int = -1 private var bottomInset: Int = 0
private var latestWindowInsets: WindowInsetsCompat? = null
private var cardGeometryInitialized: Boolean = false
private var overlapDecoration: OverlappingDecoration? = null private var overlapDecoration: OverlappingDecoration? = null
private var pagerSnapHelper: PagerSnapHelper? = null private var pagerSnapHelper: PagerSnapHelper? = null
private var scalingScrollListener: OnScrollListener? = null private var scalingScrollListener: OnScrollListener? = null
@@ -91,6 +96,38 @@ class CarouselRecyclerView @JvmOverloads constructor(
init { init {
setChildrenDrawingOrderEnabled(true) setChildrenDrawingOrderEnabled(true)
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
latestWindowInsets = insets
updateCardGeometry()
applyCarouselPadding()
insets
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
ViewCompat.requestApplyInsets(this)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (w != oldw || h != oldh) {
updateCardGeometry()
applyCarouselPadding()
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (isCarouselMode) updateChildScalesAndAlpha()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
ViewCompat.requestApplyInsets(this)
post { updateCardGeometry() }
}
} }
override fun setAdapter(adapter: Adapter<*>?) { override fun setAdapter(adapter: Adapter<*>?) {
@@ -103,6 +140,8 @@ class CarouselRecyclerView @JvmOverloads constructor(
super.setAdapter(adapter) super.setAdapter(adapter)
(adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver) (adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver)
updateCardGeometry()
applyCarouselPadding()
} }
private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int { private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int {
@@ -253,40 +292,71 @@ class CarouselRecyclerView @JvmOverloads constructor(
} }
} }
fun notifyInsetsReady(newBottomInset: Int) { private fun resolveBottomInset(windowInsets: WindowInsetsCompat): Int {
if (bottomInset != newBottomInset) { val navigationBottom = if (FullscreenHelper.isFullscreenEnabled(context)) {
bottomInset = newBottomInset 0
}
if (isCarouselMode) {
setupCarousel(true)
} else { } else {
setupCarousel(false) windowInsets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars()).bottom
} }
val gestureInsets = windowInsets.getInsetsIgnoringVisibility(
WindowInsetsCompat.Type.systemGestures()
)
val cutoutInsets = windowInsets.getInsetsIgnoringVisibility(
WindowInsetsCompat.Type.displayCutout()
)
return maxOf(navigationBottom, gestureInsets.bottom, cutoutInsets.bottom)
} }
fun notifyLaidOut(fallBackBottomInset: Int) { private fun updateCardGeometry() {
if (bottomInset < 0) bottomInset = fallBackBottomInset if (!isCarouselMode || height <= 0) return
var gameAdapter = adapter as? GameAdapter ?: return
var newCardSize = cardSize(bottomInset)
if (gameAdapter.cardSize != newCardSize) {
gameAdapter.setCardSize(newCardSize)
}
if (isCarouselMode) { val gameAdapter = adapter as? GameAdapter ?: return
setupCarousel(true) val windowInsets = latestWindowInsets ?: ViewCompat.getRootWindowInsets(this) ?: return
}
}
fun cardSize(bottomInset: Int): Int { if (cardGeometryInitialized && !hasWindowFocus()) return
val newBottomInset = resolveBottomInset(windowInsets).coerceIn(0, height)
val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1) val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1)
val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn( val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(
0f, 0f,
1f 1f
) )
val scaledHeight = height * userFactor val screenWidth = resources.displayMetrics.widthPixels.toFloat()
val availableHeight = height - bottomInset val screenHeight = resources.displayMetrics.heightPixels.toFloat()
return minOf(scaledHeight.toInt(), availableHeight.toInt()) val aspectFactor = ((screenWidth / screenHeight) / (20f / 9f))
.pow(0.75f)
.coerceIn(0.5f, 1f)
val newCardSize = minOf(
(height * userFactor).toInt(),
height - newBottomInset,
(height * aspectFactor).toInt()
)
if (newCardSize <= 0) return
val insetChanged = bottomInset != newBottomInset
val cardSizeChanged = gameAdapter.cardSize != newCardSize
bottomInset = newBottomInset
cardGeometryInitialized = true
if (cardSizeChanged) gameAdapter.setCardSize(newCardSize)
if (insetChanged || cardSizeChanged) setupCarousel(true)
}
private fun applyCarouselPadding() {
if (!isCarouselMode) return
val gameAdapter = adapter as? GameAdapter ?: return
val cardSize = gameAdapter.cardSize
if (cardSize <= 0 || bottomInset < 0) return
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0)
val sidePadding = (width - cardSize) / 2
if (paddingLeft != sidePadding || paddingTop != topPadding ||
paddingRight != sidePadding || paddingBottom != 0
) {
setPadding(sidePadding, topPadding, sidePadding, 0)
}
clipToPadding = false
} }
fun setupCarousel(enabled: Boolean) { fun setupCarousel(enabled: Boolean) {
@@ -315,9 +385,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
internalFlingMultiplier internalFlingMultiplier
).coerceIn(1f, 5f) ).coerceIn(1f, 5f)
// Detach SnapHelper during setup
pagerSnapHelper?.attachToRecyclerView(null)
// Add overlap decoration if not present // Add overlap decoration if not present
if (overlapDecoration == null) { if (overlapDecoration == null) {
overlapDecoration = OverlappingDecoration(overlapPx) overlapDecoration = OverlappingDecoration(overlapPx)
@@ -335,12 +402,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
addOnScrollListener(scalingScrollListener!!) addOnScrollListener(scalingScrollListener!!)
} }
if (cardSize > 0) { applyCarouselPadding()
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0) // Center vertically
val sidePadding = (width - cardSize) / 2 // Center first/last card
setPadding(sidePadding, topPadding, sidePadding, 0)
clipToPadding = false
}
if (pagerSnapHelper == null) { if (pagerSnapHelper == null) {
pagerSnapHelper = CenterPagerSnapHelper() pagerSnapHelper = CenterPagerSnapHelper()
@@ -362,6 +424,7 @@ 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)
+9 -10
View File
@@ -788,7 +788,7 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
jstring jprogramId, jstring jprogramId,
jstring jupdatePath) { jstring jupdatePath) {
const u64 program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
std::string updatePath = Common::Android::GetJString(env, jupdatePath); std::string updatePath = Common::Android::GetJString(env, jupdatePath);
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>( std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile( EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
@@ -796,7 +796,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
for (const auto& item : nsp->GetNCAs()) { for (const auto& item : nsp->GetNCAs()) {
for (const auto& nca_details : item.second) { for (const auto& nca_details : item.second) {
if (nca_details.second->GetName().ends_with(".cnmt.nca")) { if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
const auto update_id = FileSys::GetBaseTitleID(nca_details.second->GetTitleId()); auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL;
if (update_id == program_id) { if (update_id == program_id) {
return true; return true;
} }
@@ -1300,8 +1300,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jo
Common::RefreshThreadPolicies(); Common::RefreshThreadPolicies();
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) { jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index))); return static_cast<jboolean>(Settings::GetDebugKnobAt(static_cast<u8>(index)));
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) { void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
@@ -1491,7 +1491,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_firmwareVersion(JNIEnv* env, jclas
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_gameRequiresFirmware(JNIEnv* env, jclass clazz, jstring jprogramId) { jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_gameRequiresFirmware(JNIEnv* env, jclass clazz, jstring jprogramId) {
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); auto program_id = EmulationSession::GetProgramId(env, jprogramId);
return FirmwareManager::GameRequiresFirmware(program_id); return FirmwareManager::GameRequiresFirmware(program_id);
} }
@@ -1575,21 +1575,20 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj, void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj,
jstring jprogramId) { jstring jprogramId) {
const auto program_id = EmulationSession::GetProgramId(env, jprogramId); auto program_id = EmulationSession::GetProgramId(env, jprogramId);
ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(), ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(),
program_id); program_id);
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj, void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj,
jstring jprogramId) { jstring jprogramId) {
const auto program_id = FileSys::GetBaseTitleID( auto program_id = EmulationSession::GetProgramId(env, jprogramId);
EmulationSession::GetProgramId(env, jprogramId));
ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id); ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id);
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId, void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId,
jstring jname) { jstring jname) {
const auto program_id = EmulationSession::GetProgramId(env, jprogramId); auto program_id = EmulationSession::GetProgramId(env, jprogramId);
ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(), ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(),
program_id, Common::Android::GetJString(env, jname)); program_id, Common::Android::GetJString(env, jname));
} }
@@ -1636,7 +1635,7 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobje
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
jstring jprogramId) { jstring jprogramId) {
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); auto program_id = EmulationSession::GetProgramId(env, jprogramId);
if (program_id == 0) { if (program_id == 0) {
return Common::Android::ToJString(env, ""); return Common::Android::ToJString(env, "");
} }
+22 -4
View File
@@ -12,7 +12,6 @@
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/file_sys/common_funcs.h"
#include "frontend_common/config.h" #include "frontend_common/config.h"
#include "frontend_common/settings_generator.h" #include "frontend_common/settings_generator.h"
#include "native.h" #include "native.h"
@@ -57,7 +56,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jo
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj, void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
jstring jprogramId, jstring jprogramId,
jstring jfileName) { jstring jfileName) {
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); auto program_id = EmulationSession::GetProgramId(env, jprogramId);
auto file_name = Common::Android::GetJString(env, jfileName); auto file_name = Common::Android::GetJString(env, jfileName);
const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id); const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
per_game_config = per_game_config =
@@ -131,6 +130,25 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob
setting->SetValue(value); setting->SetValue(value);
} }
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey,
jboolean needGlobal) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return -1;
}
return static_cast<jint>(setting->GetValue(static_cast<bool>(needGlobal)));
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey, jint value) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetValue(static_cast<u16>(value));
}
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
jboolean needGlobal) { jboolean needGlobal) {
auto setting = getSetting<int>(env, jkey); auto setting = getSetting<int>(env, jkey);
@@ -323,7 +341,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj, jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj,
jstring jprogramId) { jstring jprogramId) {
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); auto program_id = EmulationSession::GetProgramId(env, jprogramId);
auto& disabledAddons = Settings::values.disabled_addons[program_id]; auto& disabledAddons = Settings::values.disabled_addons[program_id];
jobjectArray jdisabledAddonsArray = jobjectArray jdisabledAddonsArray =
env->NewObjectArray(disabledAddons.size(), Common::Android::GetStringClass(), env->NewObjectArray(disabledAddons.size(), Common::Android::GetStringClass(),
@@ -338,7 +356,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj, void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj,
jstring jprogramId, jstring jprogramId,
jobjectArray jdisabledAddons) { jobjectArray jdisabledAddons) {
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); auto program_id = EmulationSession::GetProgramId(env, jprogramId);
Settings::values.disabled_addons[program_id].clear(); Settings::values.disabled_addons[program_id].clear();
std::vector<std::string> disabled_addons; std::vector<std::string> disabled_addons;
const int size = env->GetArrayLength(jdisabledAddons); const int size = env->GetArrayLength(jdisabledAddons);
+20 -8
View File
@@ -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", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message); __android_log_print(android_log_priority, "YuzuNative", "%s %s:%u:%s: %s", df.class_name, entry.filename, entry.line_num, entry.function, entry.message);
} }
void Flush() noexcept override {} void Flush() noexcept override {}
}; };
@@ -428,21 +428,33 @@ void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, u
auto const flush = ::Settings::values.log_flush_line.GetValue(); 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);
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0'; Entry e{
logging_instance->ForEachBackend([=](Backend& backend) { .message = nullptr,
backend.Write(Entry{ .message_len = 0,
.message = buffer,
.message_len = (std::min)(result.size, sizeof(buffer) - 1),
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin), .timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
.log_class = log_class, .log_class = log_class,
.log_level = log_level, .log_level = log_level,
.filename = TrimSourcePath(filename), .filename = TrimSourcePath(filename),
.function = function, .function = function,
.line_num = line_num, .line_num = line_num,
};
if (result.size <= sizeof(buffer) - 1) {
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0';
e.message = buffer;
e.message_len = (std::min)(result.size, sizeof(buffer) - 1);
logging_instance->ForEachBackend([=](Backend& backend) {
backend.Write(e);
if (flush) backend.Flush();
}); });
if (flush) } else {
backend.Flush(); std::string s = fmt::vformat(format, args);
e.message = s.c_str();
e.message_len = s.size();
logging_instance->ForEachBackend([=](Backend& backend) {
backend.Write(e);
if (flush) backend.Flush();
}); });
} }
} }
}
} // namespace Common::Log } // namespace Common::Log
+4 -4
View File
@@ -11,14 +11,14 @@
namespace Common::Net { namespace Common::Net {
typedef struct { struct Asset {
std::string name; std::string name;
std::string url; std::string url;
std::string path; std::string path;
std::string filename; std::string filename;
} Asset; };
typedef struct Release { struct Release {
std::string title; std::string title;
std::string body; std::string body;
std::string tag; std::string tag;
@@ -39,7 +39,7 @@ typedef struct Release {
static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo); static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo);
static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo); static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo);
static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo); static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo);
} Release; };
// Make a request via httplib, and return the response body if applicable. // Make a request via httplib, and return the response body if applicable.
std::optional<std::string> MakeRequest(const std::string &url, const std::string &path); std::optional<std::string> MakeRequest(const std::string &url, const std::string &path);
+3 -3
View File
@@ -126,9 +126,9 @@ void LogSettings() {
setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category), setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category),
setting->GetLabel()); setting->GetLabel());
if (is_default) if (is_default)
settings_list.push_back(fmt::format("{}: {}\n", name, setting->Canonicalize())); settings_list.push_back(fmt::format("{}: {}", name, setting->Canonicalize()));
else else
settings_list.push_front(fmt::format("{}: {}\n", name, setting->Canonicalize())); settings_list.push_front(fmt::format("{}: {}", name, setting->Canonicalize()));
} }
} }
} }
@@ -146,7 +146,7 @@ void LogSettings() {
#undef LOG_PATH #undef LOG_PATH
} }
bool getDebugKnobAt(u8 i) { bool GetDebugKnobAt(u8 i) {
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0; return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
} }
+2 -2
View File
@@ -904,7 +904,7 @@ struct Values {
0, 0,
65535, 65535,
"debug_knobs", "debug_knobs",
Category::Debugging, Category::System,
Specialization::Countable, Specialization::Countable,
true, true,
true}; true};
@@ -947,7 +947,7 @@ constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
[[nodiscard]] size_t FrameGenMaxGenerations(); [[nodiscard]] size_t FrameGenMaxGenerations();
bool getDebugKnobAt(u8 i); bool GetDebugKnobAt(u8 i);
void UpdateGPUAccuracy(); void UpdateGPUAccuracy();
bool IsGPULevelHigh(); bool IsGPULevelHigh();
+1 -2
View File
@@ -17,7 +17,6 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "launch_timestamp_cache.h" #include "launch_timestamp_cache.h"
#include "core/core_timing.h" #include "core/core_timing.h"
@@ -397,7 +396,7 @@ struct System::Impl {
LOG_ERROR(Core, "Failed to find program id for ROM"); LOG_ERROR(Core, "Failed to find program id for ROM");
} }
GameSettings::LoadOverrides(FileSys::GetBaseTitleID(program_id), gpu_core->Renderer()); GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
if (auto room_member = Network::GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info; Network::GameInfo game_info;
game_info.name = name; game_info.name = name;
+156 -229
View File
@@ -3,10 +3,12 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <map>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <utility> #include <utility>
#include <span>
#include <cctype>
#include <ankerl/unordered_dense.h>
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging.h" #include "common/logging.h"
@@ -22,61 +24,30 @@ enum class IPSFileType {
Error, Error,
}; };
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{ static IPSFileType IdentifyMagic(std::span<const u8> magic) {
{"\\a", "\a"}, if (magic.size() >= 5) {
{"\\b", "\b"}, if (std::memcmp(magic.data(), "PATCH", 5) == 0)
{"\\f", "\f"},
{"\\n", "\n"},
{"\\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; return IPSFileType::IPS;
} if (std::memcmp(magic.data(), "IPS32", 5) == 0)
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::IPS32;
} }
return IPSFileType::Error; return IPSFileType::Error;
} }
static bool IsEOF(IPSFileType type, const std::vector<u8>& data) { static bool IsEOF(IPSFileType type, std::span<const u8> magic) {
static constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}}; return (type == IPSFileType::IPS && magic.size() > 3 && std::memcmp(magic.data(), "EOF", 3) == 0)
if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) { || (type == IPSFileType::IPS32 && magic.size() > 4 && std::memcmp(magic.data(), "EEOF", 4) == 0);
return true;
}
static constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
} }
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
if (in == nullptr || ips == nullptr) if (in == nullptr || ips == nullptr)
return nullptr; return nullptr;
const auto type = IdentifyMagic(ips->ReadBytes(0x5)); auto in_data = in->ReadAllBytes();
auto const type = IdentifyMagic(in_data);
if (type == IPSFileType::Error) if (type == IPSFileType::Error)
return nullptr; return nullptr;
auto in_data = in->ReadAllBytes();
if (in_data.size() == 0) {
return nullptr;
}
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4); std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
u64 offset = 5; // After header u64 offset = 5; // After header
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) { while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
@@ -85,12 +56,9 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
break; break;
} }
u32 real_offset{}; u32 real_offset = (type == IPSFileType::IPS32)
if (type == IPSFileType::IPS32) ? ((temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3])
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3]; : ((temp[0] << 16) | (temp[1] << 8) | temp[2]);
else
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
if (real_offset > in_data.size()) { if (real_offset > in_data.size()) {
return nullptr; return nullptr;
} }
@@ -113,34 +81,35 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
return nullptr; return nullptr;
if (real_offset + rle_size > in_data.size()) if (real_offset + rle_size > in_data.size())
rle_size = static_cast<u16>(in_data.size() - real_offset); rle_size = u16(in_data.size() - real_offset);
std::memset(in_data.data() + real_offset, *data, rle_size); std::memset(in_data.data() + real_offset, *data, rle_size);
} else { // Standard Patch } else { // Standard Patch
auto read = data_size; auto read = data_size;
if (real_offset + read > in_data.size()) if (real_offset + read > in_data.size())
read = static_cast<u16>(in_data.size() - real_offset); read = u16(in_data.size() - real_offset);
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size) if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
return nullptr; return nullptr;
offset += data_size; offset += data_size;
} }
} }
if (IsEOF(type, temp)) {
if (!IsEOF(type, temp)) { return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
}
return nullptr; return nullptr;
} }
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
}
struct IPSwitchRecord {
std::array<uint8_t, 256 - sizeof(size_t)> data;
size_t count;
};
struct IPSwitchCompiler::IPSwitchPatch { struct IPSwitchCompiler::IPSwitchPatch {
std::string name; ankerl::unordered_dense::map<u32, IPSwitchRecord> records;
bool enabled; bool enabled;
std::map<u32, std::vector<u8>> records;
}; };
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
Parse(); Parse(patch_text->ReadAllBytes());
} }
IPSwitchCompiler::~IPSwitchCompiler() = default; IPSwitchCompiler::~IPSwitchCompiler() = default;
@@ -149,201 +118,159 @@ std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
return nso_build_id; return nso_build_id;
} }
bool IPSwitchCompiler::IsValid() const { static IPSwitchRecord EscapeStringSequences(std::string_view sv) {
return valid; IPSwitchRecord r{};
for (auto it = sv.cbegin(); it != sv.cend(); ) {
if (*it == '\\' && it + 1 < sv.cend()) {
switch (it[1]) {
case 'n': r.data[r.count] = '\n'; break;
case 't': r.data[r.count] = '\t'; break;
case 'b': r.data[r.count] = '\b'; break;
case 'r': r.data[r.count] = '\r'; break;
case 'e': r.data[r.count] = '\e'; 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;
static bool StartsWith(std::string_view base, std::string_view check) { it += 2;
return base.size() >= check.size() && base.substr(0, check.size()) == check;
}
static std::string EscapeStringSequences(std::string in) {
for (const auto& seq : ESCAPE_CHARACTER_MAP) {
for (auto index = in.find(seq.first); index != std::string::npos;
index = in.find(seq.first, index)) {
in.replace(index, std::strlen(seq.first), seq.second);
index += std::strlen(seq.second);
}
}
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 { } else {
// hex replacement ++r.count;
const auto value = ++it;
patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9); }
replace = Common::HexStringToVector(value, is_little_endian); }
return r;
} }
if (print_values) { [[nodiscard]] static inline std::array<u8, 32> ReadNSOBuildId(std::string_view const s) {
LOG_INFO(Loader, std::array<u8, 32> r{};
"[IPSwitchCompiler ('{}')] - Patching value at offset {:#08x} " for (std::size_t i = 0; i < s.size(); ++i)
"with byte string '{}'", r[i / 2] |= u8(u8(Common::ToHexNibble(s[i])) << u8((i % 2) * 4));
patch_text->GetName(), offset, Common::HexToString(replace)); return r;
} }
patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace)); void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
} LOG_INFO(Loader, "IPSwitchCompiler: '{}'", patch_text->GetName());
bool is_little_endian = false;
s64 offset_shift = 0;
//bool print_values = false;
patches.push_back(std::move(patch)); auto const parse_line = [&](std::string_view const line) {
} else if (StartsWith(line, "@")) { // Keep in mind lines have trimmed spaces (at the end & start)!
ParseFlag(line); LOG_INFO(Loader, "<{}>", line);
if (line.starts_with("@stop")) {
return false; // Force stop
} else if (line.starts_with("@nsobid-")) { // NSO Build ID Specifier
nso_build_id = ReadNSOBuildId(line.substr(8));
} else if (line.starts_with("@enabled")) {
patches.push_back({{}, true}); //enabled patch
} else if (line.starts_with("@disabled")) {
patches.push_back({{}, false}); //disabled patch
} else if (line.starts_with("@flag offset_shift ")) {
offset_shift = std::strtoll(line.data() + 19, nullptr, 0); // Offset Shift Flag
} else if (line.starts_with("@little-endian")) {
is_little_endian = true; // Set values to read as little endian
} else if (line.starts_with("@big-endian")) {
is_little_endian = false; // Set values to read as big endian
} else if (line.starts_with("@flag print_values")) {
//print_values = true; // Force printing of applied values
} else if (line.starts_with("@")) {
LOG_WARNING(Loader, "Unknown flag {}", line);
} 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()) {
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
};
valid = true; for (auto it = bytes.begin(); it < bytes.end(); ) {
auto const start = it;
auto end = start;
for (; end < bytes.end() && *end != '\n' && *end != '\r'; ++end)
;
it = end + 1; //prepare for next line
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(); ) {
if ((!quote && p + 1 < sline.cend() && p[0] == '/' && p[1] == '/')
|| (!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;
}
}
}
} }
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const { VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
if (in == nullptr || !valid) if (in == nullptr)
return nullptr; return nullptr;
auto in_data = in->ReadAllBytes(); auto in_data = in->ReadAllBytes();
for (const auto& patch : patches) { for (const auto& patch : patches) {
if (!patch.enabled) if (patch.enabled) {
continue;
for (const auto& record : patch.records) { for (const auto& record : patch.records) {
if (record.first >= in_data.size()) if (record.first < in_data.size()) {
continue; auto replace_size = record.second.count;
auto replace_size = record.second.size();
if (record.first + replace_size > in_data.size()) if (record.first + replace_size > in_data.size())
replace_size = in_data.size() - record.first; replace_size = in_data.size() - record.first;
for (std::size_t i = 0; i < replace_size; ++i) std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size);
in_data[i + record.first] = record.second[i];
} }
} }
}
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), }
in->GetContainingDirectory()); return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
} }
} // namespace FileSys } // namespace FileSys
+5 -9
View File
@@ -1,11 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <array> #include <array>
#include <memory>
#include <vector> #include <vector>
#include <span>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/file_sys/vfs/vfs.h" #include "core/file_sys/vfs/vfs.h"
@@ -20,24 +23,17 @@ public:
~IPSwitchCompiler(); ~IPSwitchCompiler();
std::array<u8, 0x20> GetBuildID() const; std::array<u8, 0x20> GetBuildID() const;
bool IsValid() const;
VirtualFile Apply(const VirtualFile& in) const; VirtualFile Apply(const VirtualFile& in) const;
private: private:
struct IPSwitchPatch; struct IPSwitchPatch;
void ParseFlag(const std::string& flag); void ParseFlag(const std::string& flag);
void Parse(); void Parse(std::span<u8 const> bytes);
bool valid = false;
VirtualFile patch_text; VirtualFile patch_text;
std::vector<IPSwitchPatch> patches; std::vector<IPSwitchPatch> patches;
std::array<u8, 0x20> nso_build_id{}; std::array<u8, 0x20> nso_build_id{};
bool is_little_endian = false;
s64 offset_shift = 0;
bool print_values = false;
std::string last_comment = "";
}; };
} // namespace FileSys } // namespace FileSys
+53 -130
View File
@@ -161,7 +161,7 @@ std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64
PatchManager::PatchManager(u64 title_id_, PatchManager::PatchManager(u64 title_id_,
const Service::FileSystem::FileSystemController& fs_controller_, const Service::FileSystem::FileSystemController& fs_controller_,
const ContentProvider& content_provider_) const ContentProvider& content_provider_)
: title_id{title_id_}, application_id{GetBaseTitleID(title_id_)}, fs_controller{fs_controller_}, content_provider{content_provider_} {} : title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
PatchManager::~PatchManager() = default; PatchManager::~PatchManager() = default;
@@ -169,41 +169,13 @@ u64 PatchManager::GetTitleID() const {
return title_id; return title_id;
} }
u64 PatchManager::GetUpdateTitleIDForContent() const {
const auto program_update_id = GetUpdateTitleID(title_id);
if (program_update_id == GetUpdateTitleID(application_id) || content_provider.HasEntry(program_update_id, ContentRecordType::Program)) {
return program_update_id;
}
return GetUpdateTitleID(application_id);
}
std::vector<VirtualDir> PatchManager::GetModificationLoadRoots() const {
std::vector<VirtualDir> roots;
roots.push_back(fs_controller.GetModificationLoadRoot(title_id));
if (application_id != title_id) {
roots.push_back(fs_controller.GetModificationLoadRoot(application_id));
}
std::erase(roots, nullptr);
return roots;
}
std::vector<VirtualDir> PatchManager::GetSDMCModificationLoadRoots() const {
std::vector<VirtualDir> roots;
roots.push_back(fs_controller.GetSDMCModificationLoadRoot(title_id));
if (application_id != title_id) {
roots.push_back(fs_controller.GetSDMCModificationLoadRoot(application_id));
}
std::erase(roots, nullptr);
return roots;
}
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
if (exefs == nullptr) if (exefs == nullptr)
return exefs; return exefs;
const auto& disabled = Settings::values.disabled_addons[application_id]; const auto& disabled = Settings::values.disabled_addons[title_id];
bool update_disabled = true; bool update_disabled = true;
std::optional<u32> enabled_version; std::optional<u32> enabled_version;
@@ -211,7 +183,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
bool checked_manual = false; bool checked_manual = false;
const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider); const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider);
const auto update_tid = GetUpdateTitleIDForContent(); const auto update_tid = GetUpdateTitleID(title_id);
if (content_union) { if (content_union) {
// First, check ExternalContentProvider // First, check ExternalContentProvider
@@ -331,21 +303,17 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
} }
// LayeredExeFS // LayeredExeFS
const auto load_dirs = GetModificationLoadRoots(); const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
const auto sdmc_load_dirs = GetSDMCModificationLoadRoots(); const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
std::vector<VirtualDir> patch_dirs; std::vector<VirtualDir> patch_dirs = {sdmc_load_dir};
for (const auto& sdmc_load_dir : sdmc_load_dirs) { if (load_dir != nullptr) {
patch_dirs.push_back(sdmc_load_dir);
}
for (const auto& load_dir : load_dirs) {
const auto load_patch_dirs = load_dir->GetSubdirectories(); const auto load_patch_dirs = load_dir->GetSubdirectories();
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
} }
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { std::sort(patch_dirs.begin(), patch_dirs.end(),
return l->GetName() < r->GetName(); [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
});
std::vector<VirtualDir> layers; std::vector<VirtualDir> layers;
layers.reserve(patch_dirs.size() + 1); layers.reserve(patch_dirs.size() + 1);
@@ -377,9 +345,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs; return exefs;
} }
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, const std::string& build_id) const {
const std::string& build_id) const { const auto& disabled = Settings::values.disabled_addons[title_id];
const auto& disabled = Settings::values.disabled_addons[application_id];
const auto nso_build_id = fmt::format("{:0<64}", build_id); const auto nso_build_id = fmt::format("{:0<64}", build_id);
std::vector<VirtualFile> out; std::vector<VirtualFile> out;
@@ -393,16 +360,11 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
for (const auto& file : exefs_dir->GetFiles()) { for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") { if (file->GetExtension() == "ips") {
auto name = file->GetName(); auto name = file->GetName();
const auto this_build_id = fmt::format("{:0<64}", name.substr(0, name.find('.')));
const auto this_build_id =
fmt::format("{:0<64}", name.substr(0, name.find('.')));
if (nso_build_id == this_build_id) if (nso_build_id == this_build_id)
out.push_back(file); out.push_back(file);
} else if (file->GetExtension() == "pchtxt") { } else if (file->GetExtension() == "pchtxt") {
IPSwitchCompiler compiler{file}; IPSwitchCompiler compiler{file};
if (!compiler.IsValid())
continue;
const auto this_build_id = Common::HexToString(compiler.GetBuildID()); const auto this_build_id = Common::HexToString(compiler.GetBuildID());
if (nso_build_id == this_build_id) if (nso_build_id == this_build_id)
out.push_back(file); out.push_back(file);
@@ -410,7 +372,6 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
} }
} }
} }
return out; return out;
} }
@@ -444,20 +405,15 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
const auto load_dirs = GetModificationLoadRoots(); const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dirs.empty()) { if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return nso; return nso;
} }
std::vector<VirtualDir> patch_dirs; auto patch_dirs = load_dir->GetSubdirectories();
for (const auto& load_dir : load_dirs) { std::sort(patch_dirs.begin(), patch_dirs.end(),
const auto load_patch_dirs = load_dir->GetSubdirectories(); [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
}
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
return l->GetName() < r->GetName();
});
const auto patches = CollectPatches(patch_dirs, build_id); const auto patches = CollectPatches(patch_dirs, build_id);
auto out = nso; auto out = nso;
@@ -492,39 +448,29 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name)
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
const auto load_dirs = GetModificationLoadRoots(); const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dirs.empty()) { if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return false; return false;
} }
std::vector<VirtualDir> patch_dirs; auto patch_dirs = load_dir->GetSubdirectories();
for (const auto& load_dir : load_dirs) { std::sort(patch_dirs.begin(), patch_dirs.end(),
const auto load_patch_dirs = load_dir->GetSubdirectories(); [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
}
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
return l->GetName() < r->GetName();
});
return !CollectPatches(patch_dirs, build_id).empty(); return !CollectPatches(patch_dirs, build_id).empty();
} }
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const { std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
const auto load_dirs = GetModificationLoadRoots(); const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dirs.empty()) { if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return {}; return {};
} }
const auto& disabled = Settings::values.disabled_addons[application_id]; const auto& disabled = Settings::values.disabled_addons[title_id];
std::vector<VirtualDir> patch_dirs; auto patch_dirs = load_dir->GetSubdirectories();
for (const auto& load_dir : load_dirs) { std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); });
const auto load_patch_dirs = load_dir->GetSubdirectories();
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
}
std::stable_sort(patch_dirs.begin(), patch_dirs.end(),
[](auto const& l, auto const& r) { return l->GetName() < r->GetName(); });
// <mod dir> / <folder> / cheats / <build id>.txt // <mod dir> / <folder> / cheats / <build id>.txt
std::vector<Core::Memory::CheatEntry> out; std::vector<Core::Memory::CheatEntry> out;
@@ -540,7 +486,6 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
} }
// Uncareless user-friendly loading of patches (must start with 'cheat_') // Uncareless user-friendly loading of patches (must start with 'cheat_')
// <mod dir> / <cheat file>.txt // <mod dir> / <cheat file>.txt
for (const auto& load_dir : load_dirs) {
for (auto const& f : load_dir->GetFiles()) { for (auto const& f : load_dir->GetFiles()) {
auto const name = f->GetName(); auto const name = f->GetName();
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) { if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
@@ -554,38 +499,26 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
} }
} }
} }
}
return out; return out;
} }
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, u64 application_id, ContentRecordType type, static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
const Service::FileSystem::FileSystemController& fs_controller) { const Service::FileSystem::FileSystemController& fs_controller) {
std::vector<VirtualDir> load_dirs{fs_controller.GetModificationLoadRoot(title_id)}; const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
std::vector<VirtualDir> sdmc_load_dirs{fs_controller.GetSDMCModificationLoadRoot(title_id)}; const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
if (application_id != title_id) {
load_dirs.push_back(fs_controller.GetModificationLoadRoot(application_id));
sdmc_load_dirs.push_back(fs_controller.GetSDMCModificationLoadRoot(application_id));
}
std::erase(load_dirs, nullptr);
std::erase(sdmc_load_dirs, nullptr);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data && if ((type != ContentRecordType::Program && type != ContentRecordType::Data &&
type != ContentRecordType::HtmlDocument) || type != ContentRecordType::HtmlDocument) ||
(load_dirs.empty() && sdmc_load_dirs.empty())) { (load_dir == nullptr && sdmc_load_dir == nullptr)) {
return; return;
} }
const auto& disabled = Settings::values.disabled_addons[application_id]; const auto& disabled = Settings::values.disabled_addons[title_id];
std::vector<VirtualDir> patch_dirs; std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
for (const auto& load_dir : load_dirs) {
const auto load_patch_dirs = load_dir->GetSubdirectories();
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
}
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
patch_dirs.insert(patch_dirs.end(), sdmc_load_dirs.begin(), sdmc_load_dirs.end()); patch_dirs.push_back(sdmc_load_dir);
} }
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { std::sort(patch_dirs.begin(), patch_dirs.end(),
return l->GetName() < r->GetName(); [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
});
std::vector<VirtualDir> layers; std::vector<VirtualDir> layers;
std::vector<VirtualDir> layers_ext; std::vector<VirtualDir> layers_ext;
@@ -657,8 +590,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
auto romfs = base_romfs; auto romfs = base_romfs;
// Game Updates // Game Updates
const auto update_tid = GetUpdateTitleIDForContent(); const auto update_tid = GetUpdateTitleID(title_id);
const auto& disabled = Settings::values.disabled_addons[application_id]; const auto& disabled = Settings::values.disabled_addons[title_id];
bool update_disabled = true; bool update_disabled = true;
std::optional<u32> enabled_version; std::optional<u32> enabled_version;
@@ -765,7 +698,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
// LayeredFS // LayeredFS
if (apply_layeredfs) { if (apply_layeredfs) {
ApplyLayeredFS(romfs, title_id, application_id, type, fs_controller); ApplyLayeredFS(romfs, title_id, type, fs_controller);
} }
return romfs; return romfs;
@@ -777,10 +710,10 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
std::vector<Patch> out; std::vector<Patch> out;
const auto& disabled = Settings::values.disabled_addons[application_id]; const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates // Game Updates
const auto update_tid = GetUpdateTitleIDForContent(); const auto update_tid = GetUpdateTitleID(title_id);
std::vector<Patch> external_update_patches; std::vector<Patch> external_update_patches;
@@ -929,7 +862,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
.version = "", .version = "",
.type = PatchType::Update, .type = PatchType::Update,
.program_id = title_id, .program_id = title_id,
.title_id = update_tid, .title_id = title_id,
.source = PatchSource::Unknown, .source = PatchSource::Unknown,
.numeric_version = 0}; .numeric_version = 0};
@@ -955,7 +888,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
// General Mods (LayeredFS and IPS) // General Mods (LayeredFS and IPS)
for (const auto& mod_dir : GetModificationLoadRoots()) { const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
if (mod_dir != nullptr) {
for (auto const& f : mod_dir->GetFiles()) for (auto const& f : mod_dir->GetFiles())
if (auto const name = f->GetName(); name.starts_with("cheat_")) { if (auto const name = f->GetName(); name.starts_with("cheat_")) {
auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end(); auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end();
@@ -1022,7 +956,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
// SDMC mod directory (RomFS LayeredFS) // SDMC mod directory (RomFS LayeredFS)
for (const auto& sdmc_mod_dir : GetSDMCModificationLoadRoots()) { const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
if (sdmc_mod_dir != nullptr) {
std::string types; std::string types;
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs")))
AppendCommaIfNotEmpty(types, "LayeredExeFS"); AppendCommaIfNotEmpty(types, "LayeredExeFS");
@@ -1057,10 +992,10 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
dlc_match.reserve(dlc_entries_with_origin.size()); dlc_match.reserve(dlc_entries_with_origin.size());
for (const auto& [slot, entry] : dlc_entries_with_origin) { for (const auto& [slot, entry] : dlc_entries_with_origin) {
const auto base_tid = GetBaseTitleID(entry.title_id); const auto base_tid = GetBaseTitleID(entry.title_id);
const bool matches_base = base_tid == application_id; const bool matches_base = base_tid == title_id;
if (!matches_base) { if (!matches_base) {
LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}", LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}",
entry.title_id, base_tid, application_id); entry.title_id, base_tid, title_id);
continue; continue;
} }
@@ -1135,22 +1070,16 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
std::optional<u32> PatchManager::GetGameVersion() const { std::optional<u32> PatchManager::GetGameVersion() const {
const auto update_tid = GetUpdateTitleIDForContent(); const auto update_tid = GetUpdateTitleID(title_id);
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
return content_provider.GetEntryVersion(update_tid); return content_provider.GetEntryVersion(update_tid);
} }
if (const auto version = content_provider.GetEntryVersion(title_id); version.has_value()) { return content_provider.GetEntryVersion(title_id);
return version;
}
return content_provider.GetEntryVersion(application_id);
} }
PatchManager::Metadata PatchManager::GetControlMetadata() const { PatchManager::Metadata PatchManager::GetControlMetadata() const {
auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr && application_id != title_id) {
base_control_nca = content_provider.GetEntry(application_id, ContentRecordType::Control);
}
if (base_control_nca == nullptr) { if (base_control_nca == nullptr) {
return {}; return {};
} }
@@ -1226,14 +1155,8 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
auto metadata = pm.GetControlMetadata(); auto metadata = pm.GetControlMetadata();
if (metadata.first != nullptr) if (metadata.first != nullptr)
return metadata; return metadata;
const auto update_id = FileSys::GetUpdateTitleID(application_id); const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()};
const auto application_update_id = FileSys::GetUpdateTitleID(GetBaseTitleID(application_id)); return pm_update.GetControlMetadata();
const FileSys::PatchManager pm_update{update_id, system.GetFileSystemController(), system.GetContentProvider()};
metadata = pm_update.GetControlMetadata();
if (metadata.first != nullptr || update_id == application_update_id)
return metadata;
const FileSys::PatchManager pm_application_update{application_update_id, system.GetFileSystemController(), system.GetContentProvider()};
return pm_application_update.GetControlMetadata();
} }
} // namespace FileSys } // namespace FileSys
-5
View File
@@ -10,7 +10,6 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs/vfs_types.h" #include "core/file_sys/vfs/vfs_types.h"
@@ -110,14 +109,10 @@ public:
[[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept; [[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept;
private: private:
[[nodiscard]] u64 GetUpdateTitleIDForContent() const;
[[nodiscard]] std::vector<VirtualDir> GetModificationLoadRoots() const;
[[nodiscard]] std::vector<VirtualDir> GetSDMCModificationLoadRoots() const;
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, [[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const; const std::string& build_id) const;
u64 title_id; u64 title_id;
u64 application_id;
const Service::FileSystem::FileSystemController& fs_controller; const Service::FileSystem::FileSystemController& fs_controller;
const ContentProvider& content_provider; const ContentProvider& content_provider;
}; };
+2 -2
View File
@@ -1216,7 +1216,7 @@ Result KServerSession::ReceiveRequest(KernelCore& kernel, uintptr_t server_messa
} }
Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, bool is_hle) { KPhysicalAddress server_message_paddr, bool is_hle, bool session_closed) {
// Lock the session. // Lock the session.
KScopedLightLock lk{m_lock}; KScopedLightLock lk{m_lock};
@@ -1248,7 +1248,7 @@ Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, u
KEvent* event = request->GetEvent(); KEvent* event = request->GetEvent();
// Check whether we're closed. // Check whether we're closed.
const bool closed = (client_thread == nullptr || m_parent->IsClientClosed()); const bool closed = (client_thread == nullptr || m_parent->IsClientClosed() || session_closed);
Result result = ResultSuccess; Result result = ResultSuccess;
if (!closed) { if (!closed) {
+3 -3
View File
@@ -54,14 +54,14 @@ public:
Result OnRequest(KernelCore& kernel, KSessionRequest* request); Result OnRequest(KernelCore& kernel, KSessionRequest* request);
Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, bool is_hle = false); KPhysicalAddress server_message_paddr, bool is_hle = false, bool session_closed = false);
Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
KPhysicalAddress server_message_paddr, KPhysicalAddress server_message_paddr,
std::shared_ptr<Service::HLERequestContext>* out_context = nullptr, std::shared_ptr<Service::HLERequestContext>* out_context = nullptr,
std::weak_ptr<Service::SessionRequestManager> manager = {}); std::weak_ptr<Service::SessionRequestManager> manager = {});
Result SendReplyHLE(KernelCore& kernel) { Result SendReplyHLE(KernelCore& kernel, bool session_closed = false) {
R_RETURN(this->SendReply(kernel, 0, 0, 0, true)); R_RETURN(this->SendReply(kernel, 0, 0, 0, true, session_closed));
} }
Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr<Service::HLERequestContext>* out_context, Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr<Service::HLERequestContext>* out_context,
+3 -47
View File
@@ -17,56 +17,12 @@
namespace Kernel::Svc { namespace Kernel::Svc {
constexpr auto MAX_MSG_TIME = std::chrono::milliseconds(250);
const auto MAX_MSG_SIZE = 0x1000;
/// Used to output a message on a debug hardware unit - does nothing on a retail unit /// Used to output a message on a debug hardware unit - does nothing on a retail unit
Result OutputDebugString(Core::System& system, u64 address, u64 len) { Result OutputDebugString(Core::System& system, u64 address, u64 len) {
static struct DebugFlusher {
std::string msg_buffer;
std::mutex msg_mutex;
std::condition_variable msg_cv;
std::chrono::steady_clock::time_point last_msg_time;
std::optional<std::jthread> thread;
} flusher_data;
R_SUCCEED_IF(len == 0); R_SUCCEED_IF(len == 0);
// Only start the thread the very first time this function is called std::string msg_buffer(len, 0);
if (!flusher_data.thread) { GetCurrentMemory(system.Kernel()).ReadBlock(address, msg_buffer.data(), len);
flusher_data.thread.emplace([](std::stop_token stop_token) { LOG_INFO(Debug_Emulated, "{}", msg_buffer);
while (!stop_token.stop_requested()) {
std::unique_lock lock(flusher_data.msg_mutex);
flusher_data.msg_cv.wait(lock, [&stop_token] {
return !flusher_data.msg_buffer.empty() || stop_token.stop_requested();
});
if (stop_token.stop_requested() && flusher_data.msg_buffer.empty())
break;
auto timeout = flusher_data.last_msg_time + MAX_MSG_TIME;
bool woke_early = flusher_data.msg_cv.wait_until(lock, timeout, [&stop_token] {
return flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested();
});
if (!woke_early || flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested()) {
if (!flusher_data.msg_buffer.empty()) {
// Remove trailing newline as LOG_INFO adds that anyways
if (flusher_data.msg_buffer.back() == '\n')
flusher_data.msg_buffer.pop_back();
LOG_INFO(Debug_Emulated, "\n{}", flusher_data.msg_buffer);
flusher_data.msg_buffer.clear();
}
if (stop_token.stop_requested()) break;
}
}
flusher_data.msg_cv.notify_all();
});
}
{
std::lock_guard lock(flusher_data.msg_mutex);
const auto old_size = flusher_data.msg_buffer.size();
flusher_data.msg_buffer.resize(old_size + len);
GetCurrentMemory(system.Kernel()).ReadBlock(address, flusher_data.msg_buffer.data() + old_size, len);
flusher_data.last_msg_time = std::chrono::steady_clock::now();
}
flusher_data.msg_cv.notify_one();
R_SUCCEED(); R_SUCCEED();
} }
+2 -11
View File
@@ -6,7 +6,6 @@
#include <optional> #include <optional>
#include "core/core.h" #include "core/core.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/patch_manager.h" #include "core/file_sys/patch_manager.h"
@@ -105,16 +104,8 @@ std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
// TODO(DarkLordZach): When FSController/Game Card Support is added, if // TODO(DarkLordZach): When FSController/Game Card Support is added, if
// current_process_game_card use correct StorageId // current_process_game_card use correct StorageId
auto base_slot = storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program); launch.base_game_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program));
if (!base_slot) { launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
base_slot = storage.GetSlotForEntry(FileSys::GetBaseTitleID(launch.title_id), FileSys::ContentRecordType::Program);
}
launch.base_game_storage_id = GetStorageIdForFrontendSlot(base_slot);
auto update_slot = storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program);
if (!update_slot) {
update_slot = storage.GetSlotForEntry(FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(launch.title_id)), FileSys::ContentRecordType::Program);
}
launch.update_storage_id = GetStorageIdForFrontendSlot(update_slot);
system.GetARPManager().Register(launch.title_id, launch, out_control); system.GetARPManager().Register(launch.title_id, launch, out_control);
return process; return process;
@@ -158,7 +158,7 @@ Result IApplicationFunctions::EnsureSaveData(Out<u64> out_size, Common::UUID use
LOG_INFO(Service_AM, "called, uid={}", user_id.FormattedString()); LOG_INFO(Service_AM, "called, uid={}", user_id.FormattedString());
FileSys::SaveDataAttribute attribute{}; FileSys::SaveDataAttribute attribute{};
attribute.program_id = FileSys::GetBaseTitleID(m_applet->program_id); attribute.program_id = m_applet->program_id;
attribute.user_id = user_id.AsU128(); attribute.user_id = user_id.AsU128();
attribute.type = FileSys::SaveDataType::Account; attribute.type = FileSys::SaveDataType::Account;
@@ -238,7 +238,7 @@ Result IApplicationFunctions::ExtendSaveData(Out<u64> out_required_size, FileSys
static_cast<u8>(type), user_id.FormattedString(), normal_size, journal_size); static_cast<u8>(type), user_id.FormattedString(), normal_size, journal_size);
system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize( system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize(
type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128(), {normal_size, journal_size}); type, m_applet->program_id, user_id.AsU128(), {normal_size, journal_size});
// The following value is used to indicate the amount of space remaining on failure // The following value is used to indicate the amount of space remaining on failure
// due to running out of space. Since we always succeed, this should be 0. // due to running out of space. Since we always succeed, this should be 0.
@@ -252,7 +252,7 @@ Result IApplicationFunctions::GetSaveDataSize(Out<u64> out_normal_size, Out<u64>
LOG_DEBUG(Service_AM, "called with type={} user_id={}", type, user_id.FormattedString()); LOG_DEBUG(Service_AM, "called with type={} user_id={}", type, user_id.FormattedString());
const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize( const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize(
type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128()); type, m_applet->program_id, user_id.AsU128());
*out_normal_size = size.normal; *out_normal_size = size.normal;
*out_journal_size = size.journal; *out_journal_size = size.journal;
@@ -14,7 +14,6 @@
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/bis_factory.h" #include "core/file_sys/bis_factory.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.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/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
@@ -228,12 +227,13 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
std::string src_path(Common::FS::SanitizePath(src_path_)); std::string src_path(Common::FS::SanitizePath(src_path_));
std::string dest_path(Common::FS::SanitizePath(dest_path_)); std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = GetDirectoryRelativeWrapped(backing, src_path); auto src = GetDirectoryRelativeWrapped(backing, src_path);
if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
if (src == nullptr) if (src == nullptr)
return FileSys::ResultPathNotFound; return FileSys::ResultPathNotFound;
if (!src->Rename(Common::FS::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
std::string full_src_path = backing->GetFullPath() + "/" + src_path;
std::string full_dest_path = backing->GetFullPath() + "/" + dest_path;
if (!Common::FS::RenameDir(full_src_path, full_dest_path)) {
return ResultUnknown; return ResultUnknown;
} }
return ResultSuccess; return ResultSuccess;
@@ -340,7 +340,7 @@ Result FileSystemController::RegisterProcess(
registrations.emplace(process_id, Registration{ registrations.emplace(process_id, Registration{
.program_id = program_id, .program_id = program_id,
.romfs_factory = std::move(romfs_factory), .romfs_factory = std::move(romfs_factory),
.save_data_factory = CreateSaveDataFactory(FileSys::GetBaseTitleID(program_id)), .save_data_factory = CreateSaveDataFactory(program_id),
}); });
LOG_DEBUG(Service_FS, "Registered for process {}", process_id); LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
@@ -24,7 +24,7 @@ IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGe
{3, D<&IFileSystem::DeleteDirectory>, "DeleteDirectory"}, {3, D<&IFileSystem::DeleteDirectory>, "DeleteDirectory"},
{4, D<&IFileSystem::DeleteDirectoryRecursively>, "DeleteDirectoryRecursively"}, {4, D<&IFileSystem::DeleteDirectoryRecursively>, "DeleteDirectoryRecursively"},
{5, D<&IFileSystem::RenameFile>, "RenameFile"}, {5, D<&IFileSystem::RenameFile>, "RenameFile"},
{6, nullptr, "RenameDirectory"}, {6, D<&IFileSystem::RenameDirectory>, "RenameDirectory"},
{7, D<&IFileSystem::GetEntryType>, "GetEntryType"}, {7, D<&IFileSystem::GetEntryType>, "GetEntryType"},
{8, D<&IFileSystem::OpenFile>, "OpenFile"}, {8, D<&IFileSystem::OpenFile>, "OpenFile"},
{9, D<&IFileSystem::OpenDirectory>, "OpenDirectory"}, {9, D<&IFileSystem::OpenDirectory>, "OpenDirectory"},
@@ -88,6 +88,14 @@ Result IFileSystem::RenameFile(
R_RETURN(backend->RenameFile(FileSys::Path(old_path->str), FileSys::Path(new_path->str))); R_RETURN(backend->RenameFile(FileSys::Path(old_path->str), FileSys::Path(new_path->str)));
} }
Result IFileSystem::RenameDirectory(
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path) {
LOG_DEBUG(Service_FS, "called. directory '{}' to directory '{}'", old_path->str, new_path->str);
R_RETURN(backend->RenameDirectory(FileSys::Path(old_path->str), FileSys::Path(new_path->str)));
}
Result IFileSystem::OpenFile(OutInterface<IFile> out_interface, Result IFileSystem::OpenFile(OutInterface<IFile> out_interface,
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path,
u32 mode) { u32 mode) {
@@ -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
@@ -36,6 +39,8 @@ public:
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path); const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path);
Result RenameFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path, Result RenameFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path); const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path);
Result RenameDirectory(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path);
Result OpenFile(OutInterface<IFile> out_interface, Result OpenFile(OutInterface<IFile> out_interface,
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, u32 mode); const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, u32 mode);
Result OpenDirectory(OutInterface<IDirectory> out_interface, Result OpenDirectory(OutInterface<IDirectory> out_interface,
@@ -18,7 +18,6 @@
#include "common/settings.h" #include "common/settings.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/core.h" #include "core/core.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/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/fs_directory.h" #include "core/file_sys/fs_directory.h"
@@ -314,7 +313,7 @@ Result FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(OutInterface<IFileSyste
FileSys::ResultInvalidArgument); FileSys::ResultInvalidArgument);
if (attribute.program_id == 0) { if (attribute.program_id == 0) {
attribute.program_id = FileSys::GetBaseTitleID(program_id); attribute.program_id = program_id;
} }
FileSys::VirtualDir dir{}; FileSys::VirtualDir dir{};
+1 -1
View File
@@ -393,7 +393,7 @@ Result ServerManager::CompleteSyncRequest(Session* session) {
} }
// Send the reply. // Send the reply.
res = server_session->SendReplyHLE(m_system.Kernel()); res = server_session->SendReplyHLE(m_system.Kernel(), service_res == IPC::ResultSessionClosed);
// If the session has been closed, we're done. // If the session has been closed, we're done.
if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) { if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) {
-9
View File
@@ -70,15 +70,6 @@ std::optional<IndexedProgram> ResolveIndexedProgram(Core::System& system, u64 pr
return IndexedProgram{std::move(update), target_id, true}; return IndexedProgram{std::move(update), target_id, true};
} }
const auto application_update_id =
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(target_id));
if (application_update_id != update_id) {
if (auto update = provider.GetEntryRaw(application_update_id, FileSys::ContentRecordType::Program)) {
LOG_INFO(Loader, "Program index {} has no base program, loading it from application update {:016X}", program_index, application_update_id);
return IndexedProgram{std::move(update), target_id, true};
}
}
LOG_WARNING(Loader, "No program NCA for {:016X} (index {}), falling back to the container", LOG_WARNING(Loader, "No program NCA for {:016X} (index {}), falling back to the container",
target_id, program_index); target_id, program_index);
return std::nullopt; return std::nullopt;
+1 -7
View File
@@ -11,7 +11,6 @@
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "core/core.h" #include "core/core.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/control_metadata.h" #include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
@@ -77,13 +76,8 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update");
const auto& installed = system.GetContentProvider(); const auto& installed = system.GetContentProvider();
const auto program_update_id = FileSys::GetUpdateTitleID(nca->GetTitleId()); const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()),
auto update_nca = installed.GetEntry(program_update_id, FileSys::ContentRecordType::Program);
if (update_nca == nullptr) {
update_nca = installed.GetEntry(
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(nca->GetTitleId())),
FileSys::ContentRecordType::Program); FileSys::ContentRecordType::Program);
}
if (update_nca) { if (update_nca) {
exefs = update_nca->GetExeFS(); exefs = update_nca->GetExeFS();
+1 -6
View File
@@ -186,13 +186,8 @@ ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
return ResultStatus::ErrorNoPackedUpdate; return ResultStatus::ErrorNoPackedUpdate;
} }
const auto program_update_id = FileSys::GetUpdateTitleID(nsp->GetProgramTitleID()); const auto read = nsp->GetNCAFile(FileSys::GetUpdateTitleID(nsp->GetProgramTitleID()),
auto read = nsp->GetNCAFile(program_update_id, FileSys::ContentRecordType::Program);
if (read == nullptr) {
read = nsp->GetNCAFile(
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(nsp->GetProgramTitleID())),
FileSys::ContentRecordType::Program); FileSys::ContentRecordType::Program);
}
if (read == nullptr) { if (read == nullptr) {
return ResultStatus::ErrorNoPackedUpdate; return ResultStatus::ErrorNoPackedUpdate;
+2 -8
View File
@@ -9,7 +9,6 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.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/control_metadata.h" #include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
@@ -138,13 +137,8 @@ ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
return ResultStatus::ErrorXCIMissingProgramNCA; return ResultStatus::ErrorXCIMissingProgramNCA;
} }
const auto program_update_id = FileSys::GetUpdateTitleID(program_id); const auto read = xci->GetSecurePartitionNSP()->GetNCAFile(
auto read = xci->GetSecurePartitionNSP()->GetNCAFile(program_update_id, FileSys::ContentRecordType::Program); FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program);
if (read == nullptr) {
read = xci->GetSecurePartitionNSP()->GetNCAFile(
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(program_id)),
FileSys::ContentRecordType::Program);
}
if (read == nullptr) { if (read == nullptr) {
return ResultStatus::ErrorNoPackedUpdate; return ResultStatus::ErrorNoPackedUpdate;
} }
@@ -247,7 +247,7 @@ void A32EmitX64::GenTerminalHandlers() {
calculate_location_descriptor(); calculate_location_descriptor();
code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)]); code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)]);
code.sub(eax, 1); code.sub(eax, 1);
code.and_(eax, u32(A32JitState::RSBPtrMask)); code.and_(eax, u32(A32JitState::RSB_PTR_MASK));
code.mov(dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)], eax); code.mov(dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)], eax);
code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_location_descriptors) + rax * sizeof(u64)]); code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_location_descriptors) + rax * sizeof(u64)]);
if (conf.HasOptimization(OptimizationFlag::FastDispatch)) { if (conf.HasOptimization(OptimizationFlag::FastDispatch)) {
@@ -37,9 +37,9 @@ using namespace Backend::X64;
static RunCodeCallbacks GenRunCodeCallbacks(A32::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A32::UserConfig& conf) { static RunCodeCallbacks GenRunCodeCallbacks(A32::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A32::UserConfig& conf) {
return RunCodeCallbacks{ return RunCodeCallbacks{
std::make_unique<ArgCallback>(LookupBlock, reinterpret_cast<u64>(arg)), ArgCallback(LookupBlock, reinterpret_cast<u64>(arg)),
std::make_unique<ArgCallback>(Devirtualize<&A32::UserCallbacks::AddTicks>(cb)), ArgCallback(Devirtualize<&A32::UserCallbacks::AddTicks>(cb)),
std::make_unique<ArgCallback>(Devirtualize<&A32::UserCallbacks::GetTicksRemaining>(cb)), ArgCallback(Devirtualize<&A32::UserCallbacks::GetTicksRemaining>(cb)),
conf.enable_cycle_counting, conf.enable_cycle_counting,
}; };
} }
@@ -79,7 +79,7 @@ struct Jit::Impl {
jit_interface->is_executing = true; jit_interface->is_executing = true;
const CodePtr current_codeptr = [this] { const CodePtr current_codeptr = [this] {
// RSB optimization // RSB optimization
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A32JitState::RSBPtrMask; const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A32JitState::RSB_PTR_MASK;
if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) { if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) {
jit_state.rsb_ptr = new_rsb_ptr; jit_state.rsb_ptr = new_rsb_ptr;
return reinterpret_cast<CodePtr>(jit_state.rsb_codeptrs[new_rsb_ptr]); return reinterpret_cast<CodePtr>(jit_state.rsb_codeptrs[new_rsb_ptr]);
@@ -27,6 +27,9 @@ struct A32JitState {
A32JitState() { ResetRSB(); } A32JitState() { ResetRSB(); }
static constexpr std::size_t RSB_SIZE = 8; // MUST be a power of 2.
static constexpr std::size_t RSB_PTR_MASK = RSB_SIZE - 1;
std::array<u32, 16> Reg{}; // Current register file. std::array<u32, 16> Reg{}; // Current register file.
// TODO: Mode-specific register sets unimplemented. // TODO: Mode-specific register sets unimplemented.
@@ -36,8 +39,9 @@ struct A32JitState {
u32 cpsr_q = 0; u32 cpsr_q = 0;
u32 cpsr_nzcv = 0; u32 cpsr_nzcv = 0;
u32 cpsr_jaifm = 0; u32 cpsr_jaifm = 0;
u32 Cpsr() const; u32 fpsr_exc = 0;
void SetCpsr(u32 cpsr); u32 fpsr_qc = 0;
u32 fpsr_nzcv = 0;
alignas(16) std::array<u32, 64> ExtReg{}; // Extension registers. alignas(16) std::array<u32, 64> ExtReg{}; // Extension registers.
@@ -49,21 +53,19 @@ struct A32JitState {
// Exclusive state // Exclusive state
u32 exclusive_state = 0; u32 exclusive_state = 0;
static constexpr std::size_t RSBSize = 8; // MUST be a power of 2.
static constexpr std::size_t RSBPtrMask = RSBSize - 1;
u32 rsb_ptr = 0; u32 rsb_ptr = 0;
std::array<u64, RSBSize> rsb_location_descriptors; std::array<u64, RSB_SIZE> rsb_location_descriptors;
std::array<u64, RSBSize> rsb_codeptrs; std::array<u64, RSB_SIZE> rsb_codeptrs;
void ResetRSB();
u32 fpsr_exc = 0; u32 Cpsr() const;
u32 fpsr_qc = 0; void SetCpsr(u32 cpsr);
u32 fpsr_nzcv = 0;
void ResetRSB();
u32 Fpscr() const; u32 Fpscr() const;
void SetFpscr(u32 FPSCR); void SetFpscr(u32 FPSCR);
u64 GetUniqueHash() const noexcept { u64 GetUniqueHash() const noexcept {
return (static_cast<u64>(upper_location_descriptor) << 32) | (static_cast<u64>(Reg[15])); return (u64(upper_location_descriptor) << 32) | (u64(Reg[15]));
} }
void TransferJitState(const A32JitState& src, bool reset_rsb) { void TransferJitState(const A32JitState& src, bool reset_rsb) {
@@ -208,7 +208,7 @@ void A64EmitX64::GenTerminalHandlers() {
calculate_location_descriptor(); calculate_location_descriptor();
code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)]); code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)]);
code.sub(eax, 1); code.sub(eax, 1);
code.and_(eax, u32(A64JitState::RSBPtrMask)); code.and_(eax, u32(A64JitState::RSB_PTR_MASK));
code.mov(dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)], eax); code.mov(dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)], eax);
code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_location_descriptors) + rax * sizeof(u64)]); code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_location_descriptors) + rax * sizeof(u64)]);
if (conf.HasOptimization(OptimizationFlag::FastDispatch)) { if (conf.HasOptimization(OptimizationFlag::FastDispatch)) {
@@ -33,9 +33,9 @@ using namespace Backend::X64;
static RunCodeCallbacks GenRunCodeCallbacks(A64::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A64::UserConfig& conf) { static RunCodeCallbacks GenRunCodeCallbacks(A64::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A64::UserConfig& conf) {
return RunCodeCallbacks{ return RunCodeCallbacks{
std::make_unique<ArgCallback>(LookupBlock, reinterpret_cast<u64>(arg)), ArgCallback(LookupBlock, reinterpret_cast<u64>(arg)),
std::make_unique<ArgCallback>(Devirtualize<&A64::UserCallbacks::AddTicks>(cb)), ArgCallback(Devirtualize<&A64::UserCallbacks::AddTicks>(cb)),
std::make_unique<ArgCallback>(Devirtualize<&A64::UserCallbacks::GetTicksRemaining>(cb)), ArgCallback(Devirtualize<&A64::UserCallbacks::GetTicksRemaining>(cb)),
conf.enable_cycle_counting, conf.enable_cycle_counting,
}; };
} }
@@ -78,7 +78,7 @@ public:
// TODO: Check code alignment // TODO: Check code alignment
const CodePtr current_code_ptr = [this] { const CodePtr current_code_ptr = [this] {
// RSB optimization // RSB optimization
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSBPtrMask; const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSB_PTR_MASK;
if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) { if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) {
jit_state.rsb_ptr = new_rsb_ptr; jit_state.rsb_ptr = new_rsb_ptr;
return CodePtr(jit_state.rsb_codeptrs[new_rsb_ptr]); return CodePtr(jit_state.rsb_codeptrs[new_rsb_ptr]);
@@ -29,18 +29,19 @@ struct A64JitState {
A64JitState() { ResetRSB(); } A64JitState() { ResetRSB(); }
// Exclusive state stuff
static constexpr u64 RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFF0ull;
// Return stack buffer
static constexpr size_t RSB_SIZE = 8; // MUST be a power of 2.
static constexpr size_t RSB_PTR_MASK = RSB_SIZE - 1;
std::array<u64, 31> reg{}; std::array<u64, 31> reg{};
u64 sp = 0; u64 sp = 0;
u64 pc = 0; u64 pc = 0;
u32 cpsr_nzcv = 0; u32 cpsr_nzcv = 0;
u32 fpsr_exc = 0;
u32 GetPstate() const { u32 fpsr_qc = 0;
return NZCV::FromX64(cpsr_nzcv); u32 fpcr = 0;
}
void SetPstate(u32 new_pstate) {
cpsr_nzcv = NZCV::ToX64(new_pstate);
}
alignas(16) std::array<u64, 64> vec{}; // Extension registers. alignas(16) std::array<u64, 64> vec{}; // Extension registers.
@@ -50,29 +51,31 @@ struct A64JitState {
volatile u32 halt_reason = 0; volatile u32 halt_reason = 0;
// Exclusive state // Exclusive state
static constexpr u64 RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFF0ull;
u8 exclusive_state = 0; u8 exclusive_state = 0;
static constexpr size_t RSBSize = 8; // MUST be a power of 2.
static constexpr size_t RSBPtrMask = RSBSize - 1;
u32 rsb_ptr = 0; u32 rsb_ptr = 0;
std::array<u64, RSBSize> rsb_location_descriptors; std::array<u64, RSB_SIZE> rsb_location_descriptors;
std::array<u64, RSBSize> rsb_codeptrs; std::array<u64, RSB_SIZE> rsb_codeptrs;
u32 GetPstate() const {
return NZCV::FromX64(cpsr_nzcv);
}
void SetPstate(u32 new_pstate) {
cpsr_nzcv = NZCV::ToX64(new_pstate);
}
void ResetRSB() { void ResetRSB() {
rsb_location_descriptors.fill(0xFFFFFFFFFFFFFFFFull); rsb_location_descriptors.fill(0xFFFFFFFFFFFFFFFFull);
rsb_codeptrs.fill(0); rsb_codeptrs.fill(0);
} }
u32 fpsr_exc = 0;
u32 fpsr_qc = 0;
u32 fpcr = 0;
u32 GetFpcr() const; u32 GetFpcr() const;
u32 GetFpsr() const; u32 GetFpsr() const;
void SetFpcr(u32 value); void SetFpcr(u32 value);
void SetFpsr(u32 value); void SetFpsr(u32 value);
u64 GetUniqueHash() const noexcept { u64 GetUniqueHash() const noexcept {
const u64 fpcr_u64 = static_cast<u64>(fpcr & A64::LocationDescriptor::fpcr_mask) << A64::LocationDescriptor::fpcr_shift; const u64 fpcr_u64 = u64(fpcr & A64::LocationDescriptor::fpcr_mask) << A64::LocationDescriptor::fpcr_shift;
const u64 pc_u64 = pc & A64::LocationDescriptor::pc_mask; const u64 pc_u64 = pc & A64::LocationDescriptor::pc_mask;
return pc_u64 | fpcr_u64; return pc_u64 | fpcr_u64;
} }
@@ -61,73 +61,6 @@ namespace {
constexpr size_t CONSTANT_POOL_SIZE = 2 * 1024 * 1024; constexpr size_t CONSTANT_POOL_SIZE = 2 * 1024 * 1024;
constexpr size_t PRELUDE_COMMIT_SIZE = 16 * 1024 * 1024; constexpr size_t PRELUDE_COMMIT_SIZE = 16 * 1024 * 1024;
class CustomXbyakAllocator : public Xbyak::Allocator {
public:
#ifdef _WIN32
uint8_t* alloc(size_t size) override {
void* p = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
if (p == nullptr) {
using Xbyak::Error;
XBYAK_THROW(Xbyak::ERR_CANT_ALLOC);
}
return static_cast<uint8_t*>(p);
}
void free(uint8_t* p) override {
VirtualFree(static_cast<void*>(p), 0, MEM_RELEASE);
}
bool useProtect() const override { return false; }
#else
static constexpr size_t DYNARMIC_PAGE_SIZE = 4096;
// Can't subclass Xbyak::MmapAllocator because it is not a pure interface
// and doesn't expose its construtor
uint8_t* alloc(size_t size) override {
// Waste a page to store the size
size += DYNARMIC_PAGE_SIZE;
int mode = MAP_PRIVATE;
#if defined(MAP_ANONYMOUS)
mode |= MAP_ANONYMOUS;
#elif defined(MAP_ANON)
mode |= MAP_ANON;
#else
# error "not supported"
#endif
#ifdef MAP_JIT
mode |= MAP_JIT;
#endif
int prot = PROT_READ | PROT_WRITE;
#ifdef PROT_MPROTECT
// https://man.netbsd.org/mprotect.2 specifies that an mprotect() that is LESS
// restrictive than the original mapping MUST fail
prot |= PROT_MPROTECT(PROT_READ) | PROT_MPROTECT(PROT_WRITE) | PROT_MPROTECT(PROT_EXEC);
#endif
void* p = mmap(nullptr, size, prot, mode, -1, 0);
if (p == MAP_FAILED) {
using Xbyak::Error;
XBYAK_THROW(Xbyak::ERR_CANT_ALLOC);
}
std::memcpy(p, &size, sizeof(size_t));
return static_cast<uint8_t*>(p) + DYNARMIC_PAGE_SIZE;
}
void free(uint8_t* p) override {
size_t size;
std::memcpy(&size, p - DYNARMIC_PAGE_SIZE, sizeof(size_t));
munmap(p - DYNARMIC_PAGE_SIZE, size);
}
# ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
bool useProtect() const override { return false; }
# endif
#endif
};
// This is threadsafe as Xbyak::Allocator does not contain any state; it is a pure interface.
CustomXbyakAllocator s_allocator;
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT #ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
void ProtectMemory(const void* base, size_t size, bool is_executable) { void ProtectMemory(const void* base, size_t size, bool is_executable) {
# ifdef _WIN32 # ifdef _WIN32
@@ -145,11 +78,9 @@ void ProtectMemory(const void* base, size_t size, bool is_executable) {
HostFeature GetHostFeatures() { HostFeature GetHostFeatures() {
HostFeature features = {}; HostFeature features = {};
#ifdef DYNARMIC_ENABLE_CPU_FEATURE_DETECTION #ifdef DYNARMIC_ENABLE_CPU_FEATURE_DETECTION
using Cpu = Xbyak::util::Cpu; using Cpu = Xbyak::util::Cpu;
Xbyak::util::Cpu cpu_info; Xbyak::util::Cpu cpu_info{};
if (cpu_info.has(Cpu::tSSSE3)) if (cpu_info.has(Cpu::tSSSE3))
features |= HostFeature::SSSE3; features |= HostFeature::SSSE3;
if (cpu_info.has(Cpu::tSSE41)) if (cpu_info.has(Cpu::tSSE41))
@@ -196,7 +127,6 @@ HostFeature GetHostFeatures() {
features |= HostFeature::GFNI; features |= HostFeature::GFNI;
if (cpu_info.has(Cpu::tWAITPKG)) if (cpu_info.has(Cpu::tWAITPKG))
features |= HostFeature::WAITPKG; features |= HostFeature::WAITPKG;
if (cpu_info.has(Cpu::tBMI2)) { if (cpu_info.has(Cpu::tBMI2)) {
// BMI2 instructions such as pdep and pext have been very slow up until Zen 3. // BMI2 instructions such as pdep and pext have been very slow up until Zen 3.
// Check for Zen 3 or newer by its family (0x19). // Check for Zen 3 or newer by its family (0x19).
@@ -214,7 +144,6 @@ HostFeature GetHostFeatures() {
} }
} }
#endif #endif
return features; return features;
} }
@@ -233,23 +162,27 @@ bool IsUnderRosetta() {
} // anonymous namespace } // anonymous namespace
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
static const auto default_cg_mode = Xbyak::DontSetProtectRWE;
#else
static const auto default_cg_mode = nullptr; //Allow RWE
#endif
BlockOfCode::BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function<void(BlockOfCode&)> rcp) BlockOfCode::BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function<void(BlockOfCode&)> rcp)
: Xbyak::CodeGenerator(total_code_size, default_cg_mode, &s_allocator) : Xbyak::CodeGenerator(total_code_size
, cb(std::move(cb)) #ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
, jsi(jsi) , Xbyak::DontSetProtectRWE
#else
, nullptr //Allow RWE
#endif
, nullptr)
, constant_pool(*this, CONSTANT_POOL_SIZE) , constant_pool(*this, CONSTANT_POOL_SIZE)
, host_features(GetHostFeatures()) { , jsi(jsi)
, cb(std::move(cb))
{
EnableWriting(); EnableWriting();
EnsureMemoryCommitted(PRELUDE_COMMIT_SIZE); EnsureMemoryCommitted(PRELUDE_COMMIT_SIZE);
GenRunCode(rcp); GenRunCode(rcp);
} }
bool BlockOfCode::HasHostFeature(HostFeature feature) const noexcept {
return (GetHostFeatures() & feature) == feature;
}
void BlockOfCode::PreludeComplete() { void BlockOfCode::PreludeComplete() {
prelude_complete = true; prelude_complete = true;
code_begin = getCurr(); code_begin = getCurr();
@@ -341,7 +274,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
mov(rbx, ABI_PARAM2); // save temporarily in non-volatile register mov(rbx, ABI_PARAM2); // save temporarily in non-volatile register
if (cb.enable_cycle_counting) { if (cb.enable_cycle_counting) {
cb.GetTicksRemaining->EmitCall(*this); cb.GetTicksRemaining.EmitCall(*this);
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN); mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN); mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
} }
@@ -388,7 +321,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0); cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
jng(return_to_caller); jng(return_to_caller);
} }
cb.LookupBlock->EmitCall(*this); cb.LookupBlock.EmitCall(*this);
jmp(ABI_RETURN); jmp(ABI_RETURN);
align(); align();
@@ -401,7 +334,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
jng(return_to_caller_mxcsr_already_exited); jng(return_to_caller_mxcsr_already_exited);
} }
SwitchMxcsrOnEntry(); SwitchMxcsrOnEntry();
cb.LookupBlock->EmitCall(*this); cb.LookupBlock.EmitCall(*this);
jmp(ABI_RETURN); jmp(ABI_RETURN);
align(); align();
@@ -415,7 +348,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
L(return_to_caller_mxcsr_already_exited); L(return_to_caller_mxcsr_already_exited);
if (cb.enable_cycle_counting) { if (cb.enable_cycle_counting) {
cb.AddTicks->EmitCall(*this, [this](RegList param) { cb.AddTicks.EmitCall(*this, [this](RegList param) {
mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]); mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]);
sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]); sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]);
}); });
@@ -455,18 +388,18 @@ void BlockOfCode::UpdateTicks() {
return; return;
} }
cb.AddTicks->EmitCall(*this, [this](RegList param) { cb.AddTicks.EmitCall(*this, [this](RegList param) {
mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]); mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]);
sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]); sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]);
}); });
cb.GetTicksRemaining->EmitCall(*this); cb.GetTicksRemaining.EmitCall(*this);
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN); mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN); mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
} }
void BlockOfCode::LookupBlock() { void BlockOfCode::LookupBlock() {
cb.LookupBlock->EmitCall(*this); cb.LookupBlock.EmitCall(*this);
} }
void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) { void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) {
@@ -520,7 +453,7 @@ void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) {
} }
Xbyak::Address BlockOfCode::Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) { Xbyak::Address BlockOfCode::Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
return constant_pool.GetConstant(frame, lower, upper); return constant_pool.GetConstant(*this, frame, lower, upper);
} }
CodePtr BlockOfCode::GetCodeBegin() const { CodePtr BlockOfCode::GetCodeBegin() const {
@@ -31,9 +31,9 @@ namespace Dynarmic::Backend::X64 {
using CodePtr = const void*; using CodePtr = const void*;
struct RunCodeCallbacks { struct RunCodeCallbacks {
std::unique_ptr<Callback> LookupBlock; ArgCallback LookupBlock;
std::unique_ptr<Callback> AddTicks; ArgCallback AddTicks;
std::unique_ptr<Callback> GetTicksRemaining; ArgCallback GetTicksRemaining;
bool enable_cycle_counting; bool enable_cycle_counting;
}; };
@@ -166,27 +166,24 @@ public:
JitStateInfo GetJitStateInfo() const { return jsi; } JitStateInfo GetJitStateInfo() const { return jsi; }
bool HasHostFeature(HostFeature feature) const { bool HasHostFeature(HostFeature feature) const noexcept;
return (host_features & feature) == feature;
}
private: private:
using RunCodeFuncType = HaltReason (*)(void*, CodePtr); using RunCodeFuncType = HaltReason (*)(void*, CodePtr);
static constexpr size_t MXCSR_ALREADY_EXITED = 1 << 0; static constexpr size_t MXCSR_ALREADY_EXITED = 1 << 0;
static constexpr size_t FORCE_RETURN = 1 << 1; static constexpr size_t FORCE_RETURN = 1 << 1;
RunCodeCallbacks cb; ConstantPool constant_pool;
JitStateInfo jsi; JitStateInfo jsi;
std::array<const void*, 4> return_from_run_code;
RunCodeFuncType run_code = nullptr;
RunCodeFuncType step_code = nullptr;
RunCodeCallbacks cb;
CodePtr code_begin = nullptr; CodePtr code_begin = nullptr;
#ifdef _WIN32 #ifdef _WIN32
size_t committed_size = 0; size_t committed_size = 0;
#endif #endif
ConstantPool constant_pool;
RunCodeFuncType run_code = nullptr;
RunCodeFuncType step_code = nullptr;
std::array<const void*, 4> return_from_run_code;
bool prelude_complete = false; bool prelude_complete = false;
const HostFeature host_features;
void GenRunCode(std::function<void(BlockOfCode&)> rcp); void GenRunCode(std::function<void(BlockOfCode&)> rcp);
}; };
@@ -16,8 +16,7 @@
namespace Dynarmic::Backend::X64 { namespace Dynarmic::Backend::X64 {
ConstantPool::ConstantPool(BlockOfCode& code, size_t size) ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
: code(code) : insertion_point(0)
, insertion_point(0)
{ {
code.EnsureMemoryCommitted(align_size + size); code.EnsureMemoryCommitted(align_size + size);
code.int3(); code.int3();
@@ -25,17 +24,17 @@ ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
pool = std::span<ConstantT>(reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size); pool = std::span<ConstantT>(reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size);
} }
Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) { Xbyak::Address ConstantPool::GetConstant(BlockOfCode& code, const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
const auto constant = ConstantT(lower, upper); const auto constant = ConstantT(lower, upper);
auto iter = constant_info.find(constant); auto it = constant_info.find(constant);
if (iter == constant_info.end()) { if (it == constant_info.end()) {
ASSERT(insertion_point < pool.size()); ASSERT(insertion_point < pool.size());
ConstantT& target_constant = pool[insertion_point]; ConstantT& target_constant = pool[insertion_point];
target_constant = constant; target_constant = constant;
iter = constant_info.insert({constant, &target_constant}).first; it = constant_info.insert({constant, &target_constant}).first;
++insertion_point; ++insertion_point;
} }
return frame[code.rip + iter->second]; return frame[code.rip + it->second];
} }
} // namespace Dynarmic::Backend::X64 } // namespace Dynarmic::Backend::X64
@@ -29,7 +29,7 @@ class ConstantPool final {
public: public:
ConstantPool(BlockOfCode& code, size_t size); ConstantPool(BlockOfCode& code, size_t size);
Xbyak::Address GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0); Xbyak::Address GetConstant(BlockOfCode& code, const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
private: private:
static constexpr size_t align_size = 16; // bytes static constexpr size_t align_size = 16; // bytes
@@ -45,7 +45,6 @@ private:
ankerl::unordered_dense::map<ConstantT, void*, ConstantHash> constant_info; ankerl::unordered_dense::map<ConstantT, void*, ConstantHash> constant_info;
std::span<ConstantT> pool; std::span<ConstantT> pool;
BlockOfCode& code;
std::size_t insertion_point; std::size_t insertion_point;
}; };
@@ -12,7 +12,7 @@
namespace Dynarmic::Backend::X64 { namespace Dynarmic::Backend::X64 {
enum class HostFeature : u64 { enum class HostFeature : u32 {
SSSE3 = 1ULL << 0, SSSE3 = 1ULL << 0,
SSE41 = 1ULL << 1, SSE41 = 1ULL << 1,
SSE42 = 1ULL << 2, SSE42 = 1ULL << 2,
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project. /* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage * Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD * SPDX-License-Identifier: 0BSD
@@ -15,7 +18,7 @@ struct JitStateInfo {
: offsetof_guest_MXCSR(offsetof(JitStateType, guest_MXCSR)) : offsetof_guest_MXCSR(offsetof(JitStateType, guest_MXCSR))
, offsetof_asimd_MXCSR(offsetof(JitStateType, asimd_MXCSR)) , offsetof_asimd_MXCSR(offsetof(JitStateType, asimd_MXCSR))
, offsetof_rsb_ptr(offsetof(JitStateType, rsb_ptr)) , offsetof_rsb_ptr(offsetof(JitStateType, rsb_ptr))
, rsb_ptr_mask(JitStateType::RSBPtrMask) , rsb_ptr_mask(JitStateType::RSB_PTR_MASK)
, offsetof_rsb_location_descriptors(offsetof(JitStateType, rsb_location_descriptors)) , offsetof_rsb_location_descriptors(offsetof(JitStateType, rsb_location_descriptors))
, offsetof_rsb_codeptrs(offsetof(JitStateType, rsb_codeptrs)) , offsetof_rsb_codeptrs(offsetof(JitStateType, rsb_codeptrs))
, offsetof_cpsr_nzcv(offsetof(JitStateType, cpsr_nzcv)) , offsetof_cpsr_nzcv(offsetof(JitStateType, cpsr_nzcv))
+9 -29
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: 2024 yuzu Emulator Project // SPDX-FileCopyrightText: 2024 yuzu Emulator Project
@@ -56,14 +56,13 @@ inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_contro
*/ */
inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) { inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
size_t count{}; size_t count{};
const auto application_id = FileSys::GetBaseTitleID(program_id);
const auto& fs_controller = system.GetFileSystemController(); const auto& fs_controller = system.GetFileSystemController();
const auto dlc_entries = system.GetContentProvider().ListEntriesFilter( const auto dlc_entries = system.GetContentProvider().ListEntriesFilter(
FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
std::vector<u64> program_dlc_entries; std::vector<u64> program_dlc_entries;
for (const auto& entry : dlc_entries) { for (const auto& entry : dlc_entries) {
if (FileSys::GetBaseTitleID(entry.title_id) == application_id) { if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
program_dlc_entries.push_back(entry.title_id); program_dlc_entries.push_back(entry.title_id);
} }
} }
@@ -84,17 +83,9 @@ inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
*/ */
inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller, inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller,
const u64 program_id) { const u64 program_id) {
const auto remove_update = [&fs_controller](u64 update_id) { const auto update_id = program_id | 0x800;
return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
};
const auto update_id = FileSys::GetUpdateTitleID(program_id);
const auto application_update_id =
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(program_id));
if (update_id != application_update_id && remove_update(update_id)) {
return true;
}
return remove_update(application_update_id);
} }
/** /**
@@ -120,26 +111,15 @@ inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& f
inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller, inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller,
const u64 program_id, const std::string& mod_name) { const u64 program_id, const std::string& mod_name) {
// Check general Mods (LayeredFS and IPS) // Check general Mods (LayeredFS and IPS)
const auto remove_from_root = [&mod_name](const auto& root) { const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id);
return root != nullptr && root->DeleteSubdirectoryRecursive(mod_name); if (mod_dir != nullptr) {
}; return mod_dir->DeleteSubdirectoryRecursive(mod_name);
if (remove_from_root(fs_controller.GetModificationLoadRoot(program_id))) {
return true;
}
if (FileSys::GetBaseTitleID(program_id) != program_id &&
remove_from_root(
fs_controller.GetModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) {
return true;
} }
// Check SDMC mod directory (RomFS LayeredFS) // Check SDMC mod directory (RomFS LayeredFS)
if (remove_from_root(fs_controller.GetSDMCModificationLoadRoot(program_id))) { const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id);
return true; if (sdmc_mod_dir != nullptr) {
} return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name);
if (FileSys::GetBaseTitleID(program_id) != program_id &&
remove_from_root(
fs_controller.GetSDMCModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) {
return true;
} }
return false; return false;
+1 -2
View File
@@ -7,7 +7,6 @@
#include "common/fs/fs.h" #include "common/fs/fs.h"
#include "common/fs/fs_types.h" #include "common/fs/fs_types.h"
#include "common/logging.h" #include "common/logging.h"
#include "core/file_sys/common_funcs.h"
#include "frontend_common/data_manager.h" #include "frontend_common/data_manager.h"
#include "mod_manager.h" #include "mod_manager.h"
@@ -41,7 +40,7 @@ std::vector<std::filesystem::path> GetModFolder(const std::string& root) {
} }
ModInstallResult InstallMod(const std::filesystem::path& path, const u64 program_id, const bool copy) { ModInstallResult InstallMod(const std::filesystem::path& path, const u64 program_id, const bool copy) {
const auto program_id_string = fmt::format("{:016X}", FileSys::GetBaseTitleID(program_id)); const auto program_id_string = fmt::format("{:016X}", program_id);
const auto mod_name = path.filename(); const auto mod_name = path.filename();
const auto mod_dir = const auto mod_dir =
DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name; DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name;
-2
View File
@@ -5,7 +5,6 @@
#include "common/fs/fs.h" #include "common/fs/fs.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/savedata_factory.h" #include "core/file_sys/savedata_factory.h"
#include "core/hle/service/am/am_types.h" #include "core/hle/service/am/am_types.h"
#include "frontend_common/content_manager.h" #include "frontend_common/content_manager.h"
@@ -306,7 +305,6 @@ void RemoveAllTransferableShaderCaches(u64 program_id) {
} }
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) { void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) {
program_id = FileSys::GetBaseTitleID(program_id);
const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path)); const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path));
const auto config_file_name = const auto config_file_name =
program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini") program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini")
+1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <filesystem> #include <filesystem>
#include <system_error>
#include <JlCompress.h> #include <JlCompress.h>
#include "frontend_common/mod_manager.h" #include "frontend_common/mod_manager.h"
#include "mod.h" #include "mod.h"
@@ -1281,7 +1281,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
})); }, device.StaticPipelineCache()));
return *blit_color_pipelines.back(); return *blit_color_pipelines.back();
} }
@@ -1313,7 +1313,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
})); }, device.StaticPipelineCache()));
return *blit_depth_stencil_pipelines.back(); return *blit_depth_stencil_pipelines.back();
} }
@@ -1366,7 +1366,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
})); }, device.StaticPipelineCache()));
return *clear_color_pipelines.back(); return *clear_color_pipelines.back();
} }
@@ -1422,7 +1422,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
})); }, device.StaticPipelineCache()));
return *clear_stencil_pipelines.back(); return *clear_stencil_pipelines.back();
} }
@@ -1465,7 +1465,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPip
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
})); }, device.StaticPipelineCache()));
return *blit_msaa_color_pipelines.back(); return *blit_msaa_color_pipelines.back();
} }
@@ -1584,7 +1584,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPas
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
})); }, device.StaticPipelineCache()));
return *pipelines.back(); return *pipelines.back();
} }
@@ -1697,7 +1697,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
})); }, device.StaticPipelineCache()));
return *msaa_copy_pipelines.back(); return *msaa_copy_pipelines.back();
} }
@@ -1817,7 +1817,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
}); }, device.StaticPipelineCache());
} }
void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
@@ -1860,7 +1860,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
.subpass = 0, .subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, .basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0, .basePipelineIndex = 0,
}); }, device.StaticPipelineCache());
} }
} // namespace Vulkan } // namespace Vulkan
@@ -521,7 +521,7 @@ static vk::Pipeline CreateWrappedPipelineImpl(
.subpass = 0, .subpass = 0,
.basePipelineHandle = 0, .basePipelineHandle = 0,
.basePipelineIndex = 0, .basePipelineIndex = 0,
}); }, device.StaticPipelineCache());
} }
vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass, vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
@@ -268,7 +268,7 @@ ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, Descriptor
.layout = *layout, .layout = *layout,
.basePipelineHandle = {}, .basePipelineHandle = {},
.basePipelineIndex = 0, .basePipelineIndex = 0,
}); }, device.StaticPipelineCache());
} }
ComputePass::~ComputePass() = default; ComputePass::~ComputePass() = default;
@@ -63,6 +63,8 @@ using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment; using VideoCommon::GraphicsEnvironment;
constexpr u32 CACHE_VERSION = 18; constexpr u32 CACHE_VERSION = 18;
constexpr size_t VULKAN_CACHE_FLUSH_PIPELINES = 128;
constexpr size_t VULKAN_CACHE_FLUSH_MIN_SECONDS = 30;
constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'}; constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
template <typename Container> template <typename Container>
@@ -710,6 +712,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
if (use_vulkan_pipeline_cache) { if (use_vulkan_pipeline_cache) {
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache, SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
CACHE_VERSION); CACHE_VERSION);
size_t size = 0;
vulkan_pipeline_cache.Read(&size, nullptr);
last_cache_size.store(size, std::memory_order_relaxed);
last_flush = std::chrono::steady_clock::now();
} }
if (state.statistics) { if (state.statistics) {
@@ -717,6 +723,35 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
} }
} }
void PipelineCache::QueueVulkanPipelineCacheFlush() {
if (!use_vulkan_pipeline_cache || vulkan_pipeline_cache_filename.empty()) {
return;
}
if (++pipelines_since_flush < VULKAN_CACHE_FLUSH_PIPELINES) {
return;
}
const auto now = std::chrono::steady_clock::now();
const auto megabytes = last_cache_size.load(std::memory_order_relaxed) / (1024 * 1024);
const std::chrono::seconds interval{
std::max<size_t>(VULKAN_CACHE_FLUSH_MIN_SECONDS, megabytes)};
if (last_flush.time_since_epoch().count() != 0 && now - last_flush < interval) {
return;
}
if (flush_in_flight.exchange(true, std::memory_order_acq_rel)) {
return;
}
pipelines_since_flush = 0;
last_flush = now;
serialization_thread.QueueWork([this] {
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
CACHE_VERSION);
size_t size = 0;
vulkan_pipeline_cache.Read(&size, nullptr);
last_cache_size.store(size, std::memory_order_relaxed);
flush_in_flight.store(false, std::memory_order_release);
});
}
GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() { GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() {
const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)}; const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
auto& pipeline{pair->second}; auto& pipeline{pair->second};
@@ -755,7 +790,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics, std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
bool build_in_parallel) try { bool build_in_parallel) try {
auto hash = key.Hash(); auto hash = key.Hash();
LOG_INFO(Render_Vulkan, "{:#016x}", hash); LOG_DEBUG(Render_Vulkan, "{:#016x}", hash);
size_t env_index{0}; size_t env_index{0};
std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs; std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs;
const bool uses_vertex_a{key.unique_hashes[0] != 0}; const bool uses_vertex_a{key.unique_hashes[0] != 0};
@@ -891,6 +926,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
} }
SerializePipeline(key, env_ptrs, pipeline_cache_filename, CACHE_VERSION); SerializePipeline(key, env_ptrs, pipeline_cache_filename, CACHE_VERSION);
}); });
QueueVulkanPipelineCacheFlush();
return pipeline; return pipeline;
} }
@@ -910,6 +946,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env_}, SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env_},
pipeline_cache_filename, CACHE_VERSION); pipeline_cache_filename, CACHE_VERSION);
}); });
QueueVulkanPipelineCacheFlush();
return pipeline; return pipeline;
} }
@@ -922,7 +959,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
return nullptr; return nullptr;
} }
LOG_INFO(Render_Vulkan, "{:#016x}", hash); LOG_DEBUG(Render_Vulkan, "{:#016x}", hash);
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()}; Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
@@ -7,6 +7,8 @@
#pragma once #pragma once
#include <array> #include <array>
#include <atomic>
#include <chrono>
#include <cstddef> #include <cstddef>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
@@ -144,6 +146,8 @@ private:
vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename, vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename,
u32 expected_cache_version); u32 expected_cache_version);
void QueueVulkanPipelineCacheFlush();
const Device& device; const Device& device;
Scheduler& scheduler; Scheduler& scheduler;
DescriptorPool& descriptor_pool; DescriptorPool& descriptor_pool;
@@ -171,6 +175,10 @@ private:
std::filesystem::path vulkan_pipeline_cache_filename; std::filesystem::path vulkan_pipeline_cache_filename;
vk::PipelineCache vulkan_pipeline_cache; vk::PipelineCache vulkan_pipeline_cache;
size_t pipelines_since_flush{};
std::chrono::steady_clock::time_point last_flush{};
std::atomic<size_t> last_cache_size{};
std::atomic_bool flush_in_flight{};
Common::ThreadWorker workers; Common::ThreadWorker workers;
Common::ThreadWorker serialization_thread; Common::ThreadWorker serialization_thread;
@@ -7,6 +7,8 @@
#include <algorithm> #include <algorithm>
#include <bitset> #include <bitset>
#include <chrono> #include <chrono>
#include <filesystem>
#include <fstream>
#include <optional> #include <optional>
#include <thread> #include <thread>
#include <ankerl/unordered_dense.h> #include <ankerl/unordered_dense.h>
@@ -16,6 +18,8 @@
#include <fmt/format.h> #include <fmt/format.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/literals.h" #include "common/literals.h"
#include <ranges> #include <ranges>
#include "common/settings.h" #include "common/settings.h"
@@ -393,6 +397,17 @@ std::vector<const char*> ExtensionListForVulkan(
return output; return output;
} }
constexpr std::array<char, 8> STATIC_CACHE_MAGIC_NUMBER{'e', 'd', 'e', 'n', 's', 't', 'p', 'c'};
constexpr u32 STATIC_CACHE_VERSION = 1;
std::filesystem::path StaticPipelineCacheFilename() {
const auto shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
if (!Common::FS::CreateDir(shader_dir)) {
return {};
}
return shader_dir / "vulkan_static_pipelines.bin";
}
} // Anonymous namespace } // Anonymous namespace
void Device::RemoveExtension(bool& extension, const std::string& extension_name) { void Device::RemoveExtension(bool& extension, const std::string& extension_name) {
@@ -750,15 +765,100 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
vk::Check(vmaCreateAllocator(&allocator_info, &allocator)); vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
owns_static_pipeline_cache = surface != VkSurfaceKHR{};
LoadStaticPipelineCache();
// Initialize GPU logging if enabled // Initialize GPU logging if enabled
InitializeGPULogging(); InitializeGPULogging();
} }
Device::~Device() { Device::~Device() {
SaveStaticPipelineCache();
ShutdownGPULogging(); ShutdownGPULogging();
vmaDestroyAllocator(allocator); vmaDestroyAllocator(allocator);
} }
void Device::LoadStaticPipelineCache() {
const auto create = [this](size_t size, const void* data) {
static_pipeline_cache = logical.CreatePipelineCache({
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.initialDataSize = size,
.pInitialData = data,
});
};
if (!owns_static_pipeline_cache) {
create(0, nullptr);
return;
}
const auto filename = StaticPipelineCacheFilename();
if (filename.empty()) {
create(0, nullptr);
return;
}
std::vector<char> data;
try {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
create(0, nullptr);
return;
}
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
const size_t total = static_cast<size_t>(file.tellg());
file.seekg(0, std::ios::beg);
std::array<char, 8> magic{};
u32 version{};
if (total < magic.size() + sizeof(version)) {
create(0, nullptr);
return;
}
file.read(magic.data(), magic.size())
.read(reinterpret_cast<char*>(&version), sizeof(version));
if (magic != STATIC_CACHE_MAGIC_NUMBER || version != STATIC_CACHE_VERSION) {
create(0, nullptr);
return;
}
data.resize(total - magic.size() - sizeof(version));
file.read(data.data(), static_cast<std::streamsize>(data.size()));
} catch (const std::ios_base::failure& e) {
create(0, nullptr);
return;
}
create(data.size(), data.empty() ? nullptr : data.data());
}
void Device::SaveStaticPipelineCache() const {
if (!owns_static_pipeline_cache || !static_pipeline_cache) {
return;
}
const auto filename = StaticPipelineCacheFilename();
if (filename.empty()) {
return;
}
size_t size = 0;
std::vector<char> data;
static_pipeline_cache.Read(&size, nullptr);
if (size == 0) {
return;
}
data.resize(size);
static_pipeline_cache.Read(&size, data.data());
try {
std::ofstream file(filename, std::ios::binary | std::ios::trunc);
file.exceptions(std::ofstream::failbit);
if (!file.is_open()) {
return;
}
file.write(STATIC_CACHE_MAGIC_NUMBER.data(), STATIC_CACHE_MAGIC_NUMBER.size())
.write(reinterpret_cast<const char*>(&STATIC_CACHE_VERSION),
sizeof(STATIC_CACHE_VERSION))
.write(data.data(), static_cast<std::streamsize>(size));
} catch (const std::ios_base::failure& e) {
Common::FS::RemoveFile(filename);
}
}
VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
FormatType format_type) const { FormatType format_type) const {
if (IsFormatSupported(wanted_format, wanted_usage, format_type)) { if (IsFormatSupported(wanted_format, wanted_usage, format_type)) {
@@ -273,6 +273,10 @@ public:
return physical; return physical;
} }
VkPipelineCache StaticPipelineCache() const noexcept {
return *static_pipeline_cache;
}
/// Returns the main graphics queue. /// Returns the main graphics queue.
vk::Queue GetGraphicsQueue() const { vk::Queue GetGraphicsQueue() const {
return graphics_queue; return graphics_queue;
@@ -1127,6 +1131,9 @@ private:
/// Returns true if the device natively supports blitting depth stencil images. /// Returns true if the device natively supports blitting depth stencil images.
bool TestDepthStencilBlits(VkFormat format) const; bool TestDepthStencilBlits(VkFormat format) const;
void LoadStaticPipelineCache();
void SaveStaticPipelineCache() const;
private: private:
VkInstance instance; ///< Vulkan instance. VkInstance instance; ///< Vulkan instance.
VmaAllocator allocator; ///< VMA allocator. VmaAllocator allocator; ///< VMA allocator.
@@ -1135,6 +1142,8 @@ private:
vk::Device logical; ///< Logical device. vk::Device logical; ///< Logical device.
vk::Queue graphics_queue; ///< Main graphics queue. vk::Queue graphics_queue; ///< Main graphics queue.
vk::Queue present_queue; ///< Main present queue. vk::Queue present_queue; ///< Main present queue.
vk::PipelineCache static_pipeline_cache;
bool owns_static_pipeline_cache{};
u32 instance_version{}; ///< Vulkan instance version. u32 instance_version{}; ///< Vulkan instance version.
u32 graphics_family{}; ///< Main graphics queue family index. u32 graphics_family{}; ///< Main graphics queue family index.
u32 present_family{}; ///< Main present queue family index. u32 present_family{}; ///< Main present queue family index.
@@ -24,7 +24,6 @@
#include "common/settings_input.h" #include "common/settings_input.h"
#include "configuration/shared_widget.h" #include "configuration/shared_widget.h"
#include "core/core.h" #include "core/core.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/xts_archive.h" #include "core/file_sys/xts_archive.h"
@@ -51,7 +50,7 @@
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
std::vector<VkDeviceInfo::Record>& vk_device_records, std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_) Core::System& system_)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{FileSys::GetBaseTitleID(title_id_)}, : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_},
system{system_}, system{system_},
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())}, builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} { tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
@@ -24,7 +24,6 @@
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "configuration/addon/mod_select_dialog.h" #include "configuration/addon/mod_select_dialog.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "frontend_common/mod_manager.h" #include "frontend_common/mod_manager.h"
@@ -138,7 +137,7 @@ void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file_) {
} }
void ConfigurePerGameAddons::SetTitleId(u64 id) { void ConfigurePerGameAddons::SetTitleId(u64 id) {
this->title_id = FileSys::GetBaseTitleID(id); this->title_id = id;
} }
void ConfigurePerGameAddons::InstallMods(const QStringList& mods) { void ConfigurePerGameAddons::InstallMods(const QStringList& mods) {
+2 -5
View File
@@ -1922,7 +1922,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa
std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}; std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())};
const auto config_file_name = title_id == 0 const auto config_file_name = title_id == 0
? Common::FS::PathToUTF8String(file_path.filename()) ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", FileSys::GetBaseTitleID(title_id)); : fmt::format("{:016X}", title_id);
QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig); QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
QtCommon::system->HIDCore().ReloadInputDevices(); QtCommon::system->HIDCore().ReloadInputDevices();
QtCommon::system->ApplySettings(); QtCommon::system->ApplySettings();
@@ -2544,11 +2544,9 @@ void MainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pat
} }
const FileSys::NCA update_nca{packed_update_raw, nullptr}; const FileSys::NCA update_nca{packed_update_raw, nullptr};
const auto selected_update_id = FileSys::GetUpdateTitleID(title_id);
const auto application_update_id = FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(title_id));
if (type != FileSys::ContentRecordType::Program || if (type != FileSys::ContentRecordType::Program ||
update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS || update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS ||
(update_nca.GetTitleId() != selected_update_id && update_nca.GetTitleId() != application_update_id)) { update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) {
packed_update_raw = {}; packed_update_raw = {};
} }
@@ -4423,7 +4421,6 @@ void MainWindow::SetFPSSuffix() {
bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
u64* selected_title_id, u8* selected_content_record_type) { u64* selected_title_id, u8* selected_content_record_type) {
program_id = FileSys::GetBaseTitleID(program_id);
using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>; using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
boost::container::flat_set<ContentInfo> available_title_ids; boost::container::flat_set<ContentInfo> available_title_ids;