summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt26
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt4
-rw-r--r--src/android/app/src/main/jni/native.cpp20
-rw-r--r--src/audio_core/audio_core.cpp8
-rw-r--r--src/audio_core/audio_core.h14
-rw-r--r--src/common/fs/fs_paths.h1
-rw-r--r--src/common/fs/path_util.cpp1
-rw-r--r--src/common/fs/path_util.h1
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/common/settings.h1
-rw-r--r--src/common/uuid.cpp2
-rw-r--r--src/core/core.cpp18
-rw-r--r--src/core/core.h3
-rw-r--r--src/core/hle/service/nfc/common/device.cpp160
-rw-r--r--src/core/hle/service/nfc/common/device.h10
-rw-r--r--src/core/hle/service/nfc/common/device_manager.cpp14
-rw-r--r--src/core/hle/service/nfc/nfc_interface.cpp8
-rw-r--r--src/core/hle/service/nfc/nfc_result.h20
-rw-r--r--src/core/hle/service/nfp/nfp_interface.cpp6
-rw-r--r--src/core/hle/service/nfp/nfp_result.h2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp4
-rw-r--r--src/core/loader/nro.cpp13
-rw-r--r--src/core/loader/nro.h2
-rw-r--r--src/yuzu/configuration/config.cpp8
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp8
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui10
-rw-r--r--src/yuzu/main.cpp8
35 files changed, 316 insertions, 91 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index c11b6bc16..22af9e435 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -223,6 +223,8 @@ object NativeLibrary {
223 223
224 external fun getCompany(filename: String): String 224 external fun getCompany(filename: String): String
225 225
226 external fun isHomebrew(filename: String): Boolean
227
226 external fun setAppDirectory(directory: String) 228 external fun setAppDirectory(directory: String)
227 229
228 external fun initializeGpuDriver( 230 external fun initializeGpuDriver(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 94d5156cf..f4db61cb3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -263,7 +263,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
263 val config: Configuration = resources.configuration 263 val config: Configuration = resources.configuration
264 264
265 if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 || 265 if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 ||
266 (config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0) { 266 (config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0 ||
267 (config.screenLayout and Configuration.SCREENLAYOUT_SIZE_SMALL) != 0) {
267 return rotation 268 return rotation
268 } 269 }
269 when (rotation) { 270 when (rotation) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index ebc0f164a..adbe3696b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -127,13 +127,7 @@ class SearchFragment : Fragment() {
127 } 127 }
128 } 128 }
129 129
130 R.id.chip_homebrew -> { 130 R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
131 baseList.filter {
132 Log.error("Guh - ${it.path}")
133 FileUtil.hasExtension(it.path, "nro")
134 || FileUtil.hasExtension(it.path, "nso")
135 }
136 }
137 131
138 R.id.chip_retail -> baseList.filter { 132 R.id.chip_retail -> baseList.filter {
139 FileUtil.hasExtension(it.path, "xci") 133 FileUtil.hasExtension(it.path, "xci")
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index 2a17653b2..3d6782c49 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -16,7 +16,8 @@ class Game(
16 val regions: String, 16 val regions: String,
17 val path: String, 17 val path: String,
18 val gameId: String, 18 val gameId: String,
19 val company: String 19 val company: String,
20 val isHomebrew: Boolean
20) : Parcelable { 21) : Parcelable {
21 val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime" 22 val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
22 val keyLastPlayedTime get() = "${gameId}_LastPlayed" 23 val keyLastPlayedTime get() = "${gameId}_LastPlayed"
@@ -31,6 +32,7 @@ class Game(
31 && path == other.path 32 && path == other.path
32 && gameId == other.gameId 33 && gameId == other.gameId
33 && company == other.company 34 && company == other.company
35 && isHomebrew == other.isHomebrew
34 } 36 }
35 37
36 companion object { 38 companion object {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 7059856f1..d9b301210 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager
13import kotlinx.coroutines.Dispatchers 13import kotlinx.coroutines.Dispatchers
14import kotlinx.coroutines.launch 14import kotlinx.coroutines.launch
15import kotlinx.coroutines.withContext 15import kotlinx.coroutines.withContext
16import kotlinx.serialization.ExperimentalSerializationApi
17import kotlinx.serialization.MissingFieldException
16import kotlinx.serialization.decodeFromString 18import kotlinx.serialization.decodeFromString
17import kotlinx.serialization.json.Json 19import kotlinx.serialization.json.Json
18import org.yuzu.yuzu_emu.NativeLibrary 20import org.yuzu.yuzu_emu.NativeLibrary
@@ -20,6 +22,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
20import org.yuzu.yuzu_emu.utils.GameHelper 22import org.yuzu.yuzu_emu.utils.GameHelper
21import java.util.Locale 23import java.util.Locale
22 24
25@OptIn(ExperimentalSerializationApi::class)
23class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
24 private val _games = MutableLiveData<List<Game>>(emptyList()) 27 private val _games = MutableLiveData<List<Game>>(emptyList())
25 val games: LiveData<List<Game>> get() = _games 28 val games: LiveData<List<Game>> get() = _games
@@ -49,7 +52,13 @@ class GamesViewModel : ViewModel() {
49 if (storedGames!!.isNotEmpty()) { 52 if (storedGames!!.isNotEmpty()) {
50 val deserializedGames = mutableSetOf<Game>() 53 val deserializedGames = mutableSetOf<Game>()
51 storedGames.forEach { 54 storedGames.forEach {
52 val game: Game = Json.decodeFromString(it) 55 val game: Game
56 try {
57 game = Json.decodeFromString(it)
58 } catch (e: MissingFieldException) {
59 return@forEach
60 }
61
53 val gameExists = 62 val gameExists =
54 DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path)) 63 DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
55 ?.exists() 64 ?.exists()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index c9f5797ac..aa424c768 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -765,18 +765,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
765 // If we have API access, calculate the safe area to draw the overlay 765 // If we have API access, calculate the safe area to draw the overlay
766 var cutoutLeft = 0 766 var cutoutLeft = 0
767 var cutoutBottom = 0 767 var cutoutBottom = 0
768 val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout 768 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
769 if (insets != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 769 val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
770 if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) 770 if (insets != null) {
771 insets.boundingRectTop.bottom.toFloat() else maxY 771 if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
772 if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) 772 insets.boundingRectTop.bottom.toFloat() else maxY
773 insets.boundingRectRight.left.toFloat() else maxX 773 if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
774 774 insets.boundingRectRight.left.toFloat() else maxX
775 minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left 775
776 minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom 776 minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
777 777 minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
778 cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left 778
779 cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom 779 cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
780 cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
781 }
780 } 782 }
781 783
782 // This makes sure that if we have an inset on one side of the screen, we mirror it on 784 // This makes sure that if we have an inset on one side of the screen, we mirror it on
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index ba6b5783e..42b207618 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
6import android.content.SharedPreferences 6import android.content.SharedPreferences
7import android.net.Uri 7import android.net.Uri
8import androidx.preference.PreferenceManager 8import androidx.preference.PreferenceManager
9import kotlinx.serialization.decodeFromString
10import kotlinx.serialization.encodeToString 9import kotlinx.serialization.encodeToString
11import kotlinx.serialization.json.Json 10import kotlinx.serialization.json.Json
12import org.yuzu.yuzu_emu.NativeLibrary 11import org.yuzu.yuzu_emu.NativeLibrary
@@ -83,7 +82,8 @@ object GameHelper {
83 NativeLibrary.getRegions(filePath), 82 NativeLibrary.getRegions(filePath),
84 filePath, 83 filePath,
85 gameId, 84 gameId,
86 NativeLibrary.getCompany(filePath) 85 NativeLibrary.getCompany(filePath),
86 NativeLibrary.isHomebrew(filePath)
87 ) 87 )
88 88
89 val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L) 89 val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index b87e04b3d..03cb0b74b 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -13,6 +13,7 @@
13 13
14#include <android/api-level.h> 14#include <android/api-level.h>
15#include <android/native_window_jni.h> 15#include <android/native_window_jni.h>
16#include <core/loader/nro.h>
16 17
17#include "common/detached_tasks.h" 18#include "common/detached_tasks.h"
18#include "common/dynamic_library.h" 19#include "common/dynamic_library.h"
@@ -281,6 +282,10 @@ public:
281 return GetRomMetadata(path).icon; 282 return GetRomMetadata(path).icon;
282 } 283 }
283 284
285 bool GetIsHomebrew(const std::string& path) {
286 return GetRomMetadata(path).isHomebrew;
287 }
288
284 void ResetRomMetadata() { 289 void ResetRomMetadata() {
285 m_rom_metadata_cache.clear(); 290 m_rom_metadata_cache.clear();
286 } 291 }
@@ -348,6 +353,7 @@ private:
348 struct RomMetadata { 353 struct RomMetadata {
349 std::string title; 354 std::string title;
350 std::vector<u8> icon; 355 std::vector<u8> icon;
356 bool isHomebrew;
351 }; 357 };
352 358
353 RomMetadata GetRomMetadata(const std::string& path) { 359 RomMetadata GetRomMetadata(const std::string& path) {
@@ -360,11 +366,17 @@ private:
360 366
361 RomMetadata CacheRomMetadata(const std::string& path) { 367 RomMetadata CacheRomMetadata(const std::string& path) {
362 const auto file = Core::GetGameFileFromPath(m_vfs, path); 368 const auto file = Core::GetGameFileFromPath(m_vfs, path);
363 const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); 369 auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
364 370
365 RomMetadata entry; 371 RomMetadata entry;
366 loader->ReadTitle(entry.title); 372 loader->ReadTitle(entry.title);
367 loader->ReadIcon(entry.icon); 373 loader->ReadIcon(entry.icon);
374 if (loader->GetFileType() == Loader::FileType::NRO) {
375 auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
376 entry.isHomebrew = loader_nro->IsHomebrew();
377 } else {
378 entry.isHomebrew = false;
379 }
368 380
369 m_rom_metadata_cache[path] = entry; 381 m_rom_metadata_cache[path] = entry;
370 382
@@ -662,6 +674,12 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv
662 return env->NewStringUTF(""); 674 return env->NewStringUTF("");
663} 675}
664 676
677jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
678 [[maybe_unused]] jclass clazz,
679 [[maybe_unused]] jstring j_filename) {
680 return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
681}
682
665void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation 683void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
666 [[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) { 684 [[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
667 // Create the default config.ini. 685 // Create the default config.ini.
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 07a679c32..703ef4494 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -47,12 +47,4 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
47 return *adsp; 47 return *adsp;
48} 48}
49 49
50void AudioCore::SetNVDECActive(bool active) {
51 nvdec_active = active;
52}
53
54bool AudioCore::IsNVDECActive() const {
55 return nvdec_active;
56}
57
58} // namespace AudioCore 50} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index e33e00a3e..ea047773e 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -57,18 +57,6 @@ public:
57 */ 57 */
58 AudioRenderer::ADSP::ADSP& GetADSP(); 58 AudioRenderer::ADSP::ADSP& GetADSP();
59 59
60 /**
61 * Toggle NVDEC state, used to avoid stall in playback.
62 *
63 * @param active - Set true if nvdec is active, otherwise false.
64 */
65 void SetNVDECActive(bool active);
66
67 /**
68 * Get NVDEC state.
69 */
70 bool IsNVDECActive() const;
71
72private: 60private:
73 /** 61 /**
74 * Create the sinks on startup. 62 * Create the sinks on startup.
@@ -83,8 +71,6 @@ private:
83 std::unique_ptr<Sink::Sink> input_sink; 71 std::unique_ptr<Sink::Sink> input_sink;
84 /// The ADSP in the sysmodule 72 /// The ADSP in the sysmodule
85 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; 73 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
86 /// Is NVDec currently active?
87 bool nvdec_active{false};
88}; 74};
89 75
90} // namespace AudioCore 76} // namespace AudioCore
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index c77c112f1..61bac9eba 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -10,6 +10,7 @@
10 10
11// Sub-directories contained within a yuzu data directory 11// Sub-directories contained within a yuzu data directory
12 12
13#define AMIIBO_DIR "amiibo"
13#define CACHE_DIR "cache" 14#define CACHE_DIR "cache"
14#define CONFIG_DIR "config" 15#define CONFIG_DIR "config"
15#define DUMP_DIR "dump" 16#define DUMP_DIR "dump"
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index e026a13d9..d71cfacc6 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -114,6 +114,7 @@ public:
114#endif 114#endif
115 115
116 GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); 116 GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
117 GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
117 GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache); 118 GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
118 GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config); 119 GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
119 GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR); 120 GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 7cfe85b70..ba28964d0 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -12,6 +12,7 @@ namespace Common::FS {
12 12
13enum class YuzuPath { 13enum class YuzuPath {
14 YuzuDir, // Where yuzu stores its data. 14 YuzuDir, // Where yuzu stores its data.
15 AmiiboDir, // Where Amiibo backups are stored.
15 CacheDir, // Where cached filesystem data is stored. 16 CacheDir, // Where cached filesystem data is stored.
16 ConfigDir, // Where config files are stored. 17 ConfigDir, // Where config files are stored.
17 DumpDir, // Where dumped data is stored. 18 DumpDir, // Where dumped data is stored.
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index ff53e80bb..9ff3edabb 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -235,6 +235,7 @@ void RestoreGlobalState(bool is_powered_on) {
235 values.bg_green.SetGlobal(true); 235 values.bg_green.SetGlobal(true);
236 values.bg_blue.SetGlobal(true); 236 values.bg_blue.SetGlobal(true);
237 values.enable_compute_pipelines.SetGlobal(true); 237 values.enable_compute_pipelines.SetGlobal(true);
238 values.use_video_framerate.SetGlobal(true);
238 239
239 // System 240 // System
240 values.language_index.SetGlobal(true); 241 values.language_index.SetGlobal(true);
diff --git a/src/common/settings.h b/src/common/settings.h
index 7f865b2a7..9682281b0 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -482,6 +482,7 @@ struct Values {
482 SwitchableSetting<AstcRecompression, true> astc_recompression{ 482 SwitchableSetting<AstcRecompression, true> astc_recompression{
483 AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3, 483 AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
484 "astc_recompression"}; 484 "astc_recompression"};
485 SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
485 486
486 SwitchableSetting<u8> bg_red{0, "bg_red"}; 487 SwitchableSetting<u8> bg_red{0, "bg_red"};
487 SwitchableSetting<u8> bg_green{0, "bg_green"}; 488 SwitchableSetting<u8> bg_green{0, "bg_green"};
diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp
index 89e1ed225..035df7fe0 100644
--- a/src/common/uuid.cpp
+++ b/src/common/uuid.cpp
@@ -48,7 +48,7 @@ std::array<u8, 0x10> ConstructFromRawString(std::string_view raw_string) {
48} 48}
49 49
50std::array<u8, 0x10> ConstructFromFormattedString(std::string_view formatted_string) { 50std::array<u8, 0x10> ConstructFromFormattedString(std::string_view formatted_string) {
51 std::array<u8, 0x10> uuid; 51 std::array<u8, 0x10> uuid{};
52 52
53 size_t i = 0; 53 size_t i = 0;
54 54
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 4406ae30e..7ba704f18 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -216,6 +216,14 @@ struct System::Impl {
216 } 216 }
217 } 217 }
218 218
219 void SetNVDECActive(bool is_nvdec_active) {
220 nvdec_active = is_nvdec_active;
221 }
222
223 bool GetNVDECActive() {
224 return nvdec_active;
225 }
226
219 void InitializeDebugger(System& system, u16 port) { 227 void InitializeDebugger(System& system, u16 port) {
220 debugger = std::make_unique<Debugger>(system, port); 228 debugger = std::make_unique<Debugger>(system, port);
221 } 229 }
@@ -485,6 +493,8 @@ struct System::Impl {
485 std::atomic_bool is_powered_on{}; 493 std::atomic_bool is_powered_on{};
486 bool exit_lock = false; 494 bool exit_lock = false;
487 495
496 bool nvdec_active{};
497
488 Reporter reporter; 498 Reporter reporter;
489 std::unique_ptr<Memory::CheatEngine> cheat_engine; 499 std::unique_ptr<Memory::CheatEngine> cheat_engine;
490 std::unique_ptr<Tools::Freezer> memory_freezer; 500 std::unique_ptr<Tools::Freezer> memory_freezer;
@@ -594,6 +604,14 @@ void System::UnstallApplication() {
594 impl->UnstallApplication(); 604 impl->UnstallApplication();
595} 605}
596 606
607void System::SetNVDECActive(bool is_nvdec_active) {
608 impl->SetNVDECActive(is_nvdec_active);
609}
610
611bool System::GetNVDECActive() {
612 return impl->GetNVDECActive();
613}
614
597void System::InitializeDebugger() { 615void System::InitializeDebugger() {
598 impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue()); 616 impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
599} 617}
diff --git a/src/core/core.h b/src/core/core.h
index 4f153154f..ff2e4bd30 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -189,6 +189,9 @@ public:
189 std::unique_lock<std::mutex> StallApplication(); 189 std::unique_lock<std::mutex> StallApplication();
190 void UnstallApplication(); 190 void UnstallApplication();
191 191
192 void SetNVDECActive(bool is_nvdec_active);
193 [[nodiscard]] bool GetNVDECActive();
194
192 /** 195 /**
193 * Initialize the debugger. 196 * Initialize the debugger.
194 */ 197 */
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 0bd7900e1..b14f682b5 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -12,6 +12,11 @@
12#pragma warning(pop) 12#pragma warning(pop)
13#endif 13#endif
14 14
15#include <fmt/format.h>
16
17#include "common/fs/file.h"
18#include "common/fs/fs.h"
19#include "common/fs/path_util.h"
15#include "common/input.h" 20#include "common/input.h"
16#include "common/logging/log.h" 21#include "common/logging/log.h"
17#include "common/string_util.h" 22#include "common/string_util.h"
@@ -136,7 +141,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
136 if (!NFP::AmiiboCrypto::IsKeyAvailable()) { 141 if (!NFP::AmiiboCrypto::IsKeyAvailable()) {
137 LOG_INFO(Service_NFC, "Loading amiibo without keys"); 142 LOG_INFO(Service_NFC, "Loading amiibo without keys");
138 memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); 143 memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
139 BuildAmiiboWithoutKeys(); 144 BuildAmiiboWithoutKeys(tag_data, encrypted_tag_data);
140 is_plain_amiibo = true; 145 is_plain_amiibo = true;
141 is_write_protected = true; 146 is_write_protected = true;
142 return true; 147 return true;
@@ -366,16 +371,25 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
366 371
367 // The loaded amiibo is not encrypted 372 // The loaded amiibo is not encrypted
368 if (is_plain_amiibo) { 373 if (is_plain_amiibo) {
374 std::vector<u8> data(sizeof(NFP::NTAG215File));
375 memcpy(data.data(), &tag_data, sizeof(tag_data));
376 WriteBackupData(tag_data.uid, data);
377
369 device_state = DeviceState::TagMounted; 378 device_state = DeviceState::TagMounted;
370 mount_target = mount_target_; 379 mount_target = mount_target_;
371 return ResultSuccess; 380 return ResultSuccess;
372 } 381 }
373 382
374 if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { 383 if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
375 LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state); 384 bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess();
376 return ResultCorruptedData; 385 LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
386 return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
377 } 387 }
378 388
389 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
390 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
391 WriteBackupData(encrypted_tag_data.uuid.uid, data);
392
379 device_state = DeviceState::TagMounted; 393 device_state = DeviceState::TagMounted;
380 mount_target = mount_target_; 394 mount_target = mount_target_;
381 return ResultSuccess; 395 return ResultSuccess;
@@ -470,6 +484,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
470 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); 484 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
471 if (is_plain_amiibo) { 485 if (is_plain_amiibo) {
472 memcpy(data.data(), &tag_data, sizeof(tag_data)); 486 memcpy(data.data(), &tag_data, sizeof(tag_data));
487 WriteBackupData(tag_data.uid, data);
473 } else { 488 } else {
474 if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { 489 if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
475 LOG_ERROR(Service_NFP, "Failed to encode data"); 490 LOG_ERROR(Service_NFP, "Failed to encode data");
@@ -477,6 +492,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
477 } 492 }
478 493
479 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); 494 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
495 WriteBackupData(encrypted_tag_data.uuid.uid, data);
480 } 496 }
481 497
482 if (!npad_device->WriteNfc(data)) { 498 if (!npad_device->WriteNfc(data)) {
@@ -488,7 +504,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
488} 504}
489 505
490Result NfcDevice::Restore() { 506Result NfcDevice::Restore() {
491 if (device_state != DeviceState::TagMounted) { 507 if (device_state != DeviceState::TagFound) {
492 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 508 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
493 if (device_state == DeviceState::TagRemoved) { 509 if (device_state == DeviceState::TagRemoved) {
494 return ResultTagRemoved; 510 return ResultTagRemoved;
@@ -496,13 +512,59 @@ Result NfcDevice::Restore() {
496 return ResultWrongDeviceState; 512 return ResultWrongDeviceState;
497 } 513 }
498 514
499 if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { 515 NFC::TagInfo tag_info{};
500 LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); 516 std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
501 return ResultWrongDeviceState; 517 Result result = GetTagInfo(tag_info, false);
518
519 if (result.IsError()) {
520 return result;
502 } 521 }
503 522
504 // TODO: Load amiibo from backup on system 523 result = ReadBackupData(tag_info.uuid, data);
505 LOG_ERROR(Service_NFP, "Not Implemented"); 524
525 if (result.IsError()) {
526 return result;
527 }
528
529 NFP::NTAG215File temporary_tag_data{};
530 NFP::EncryptedNTAG215File temporary_encrypted_tag_data{};
531
532 // Fallback for encrypted amiibos without keys
533 if (is_write_protected) {
534 return ResultWriteAmiiboFailed;
535 }
536
537 // Fallback for plain amiibos
538 if (is_plain_amiibo) {
539 LOG_INFO(Service_NFP, "Restoring backup of plain amiibo");
540 memcpy(&temporary_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
541 temporary_encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(temporary_tag_data);
542 }
543
544 if (!is_plain_amiibo) {
545 LOG_INFO(Service_NFP, "Restoring backup of encrypted amiibo");
546 temporary_tag_data = {};
547 memcpy(&temporary_encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
548 }
549
550 if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) {
551 return ResultNotAnAmiibo;
552 }
553
554 if (!is_plain_amiibo) {
555 if (!NFP::AmiiboCrypto::DecodeAmiibo(temporary_encrypted_tag_data, temporary_tag_data)) {
556 LOG_ERROR(Service_NFP, "Can't decode amiibo");
557 return ResultCorruptedData;
558 }
559 }
560
561 // Overwrite tag contents with backup and mount the tag
562 tag_data = temporary_tag_data;
563 encrypted_tag_data = temporary_encrypted_tag_data;
564 device_state = DeviceState::TagMounted;
565 mount_target = NFP::MountTarget::All;
566 is_data_moddified = true;
567
506 return ResultSuccess; 568 return ResultSuccess;
507} 569}
508 570
@@ -1132,13 +1194,69 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) {
1132 return FlushWithBreak(break_type); 1194 return FlushWithBreak(break_type);
1133} 1195}
1134 1196
1135Result NfcDevice::ReadBackupData(std::span<u8> data) const { 1197Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const {
1136 // Not implemented 1198 constexpr auto backup_dir = "backup";
1199 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
1200 const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
1201
1202 if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) {
1203 return ResultUnableToAccessBackupFile;
1204 }
1205
1137 return ResultSuccess; 1206 return ResultSuccess;
1138} 1207}
1139 1208
1140Result NfcDevice::WriteBackupData(std::span<const u8> data) { 1209Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const {
1141 // Not implemented 1210 constexpr auto backup_dir = "backup";
1211 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
1212 const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
1213
1214 const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
1215 Common::FS::FileAccessMode::Read,
1216 Common::FS::FileType::BinaryFile};
1217
1218 if (!keys_file.IsOpen()) {
1219 LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
1220 return ResultUnableToAccessBackupFile;
1221 }
1222
1223 if (keys_file.Read(data) != data.size()) {
1224 LOG_ERROR(Service_NFP, "Failed to read amiibo backup");
1225 return ResultUnableToAccessBackupFile;
1226 }
1227
1228 return ResultSuccess;
1229}
1230
1231Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) {
1232 constexpr auto backup_dir = "backup";
1233 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
1234 const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
1235
1236 if (HasBackup(uid).IsError()) {
1237 if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) {
1238 return ResultBackupPathAlreadyExist;
1239 }
1240
1241 if (!Common::FS::NewFile(yuzu_amiibo_dir / backup_dir / file_name)) {
1242 return ResultBackupPathAlreadyExist;
1243 }
1244 }
1245
1246 const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
1247 Common::FS::FileAccessMode::ReadWrite,
1248 Common::FS::FileType::BinaryFile};
1249
1250 if (!keys_file.IsOpen()) {
1251 LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
1252 return ResultUnableToAccessBackupFile;
1253 }
1254
1255 if (keys_file.Write(data) != data.size()) {
1256 LOG_ERROR(Service_NFP, "Failed to write amiibo backup");
1257 return ResultUnableToAccessBackupFile;
1258 }
1259
1142 return ResultSuccess; 1260 return ResultSuccess;
1143} 1261}
1144 1262
@@ -1177,7 +1295,8 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
1177 return amiibo_name; 1295 return amiibo_name;
1178} 1296}
1179 1297
1180void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) { 1298void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings,
1299 const NFP::AmiiboName& amiibo_name) const {
1181 std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{}; 1300 std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};
1182 1301
1183 // Convert from utf8 to utf16 1302 // Convert from utf8 to utf16
@@ -1258,22 +1377,23 @@ void NfcDevice::UpdateRegisterInfoCrc() {
1258 tag_data.register_info_crc = crc.checksum(); 1377 tag_data.register_info_crc = crc.checksum();
1259} 1378}
1260 1379
1261void NfcDevice::BuildAmiiboWithoutKeys() { 1380void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
1381 const NFP::EncryptedNTAG215File& encrypted_file) const {
1262 Service::Mii::MiiManager manager; 1382 Service::Mii::MiiManager manager;
1263 auto& settings = tag_data.settings; 1383 auto& settings = stubbed_tag_data.settings;
1264 1384
1265 tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_tag_data); 1385 stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
1266 1386
1267 // Common info 1387 // Common info
1268 tag_data.write_counter = 0; 1388 stubbed_tag_data.write_counter = 0;
1269 tag_data.amiibo_version = 0; 1389 stubbed_tag_data.amiibo_version = 0;
1270 settings.write_date = GetAmiiboDate(GetCurrentPosixTime()); 1390 settings.write_date = GetAmiiboDate(GetCurrentPosixTime());
1271 1391
1272 // Register info 1392 // Register info
1273 SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); 1393 SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
1274 settings.settings.font_region.Assign(0); 1394 settings.settings.font_region.Assign(0);
1275 settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); 1395 settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
1276 tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); 1396 stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
1277 1397
1278 // Admin info 1398 // Admin info
1279 settings.settings.amiibo_initialized.Assign(1); 1399 settings.settings.amiibo_initialized.Assign(1);
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 6a37e8458..6f049b687 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -86,8 +86,9 @@ public:
86 Result GetAll(NFP::NfpData& data) const; 86 Result GetAll(NFP::NfpData& data) const;
87 Result SetAll(const NFP::NfpData& data); 87 Result SetAll(const NFP::NfpData& data);
88 Result BreakTag(NFP::BreakType break_type); 88 Result BreakTag(NFP::BreakType break_type);
89 Result ReadBackupData(std::span<u8> data) const; 89 Result HasBackup(const NFC::UniqueSerialNumber& uid) const;
90 Result WriteBackupData(std::span<const u8> data); 90 Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const;
91 Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data);
91 Result WriteNtf(std::span<const u8> data); 92 Result WriteNtf(std::span<const u8> data);
92 93
93 u64 GetHandle() const; 94 u64 GetHandle() const;
@@ -103,14 +104,15 @@ private:
103 void CloseNfcTag(); 104 void CloseNfcTag();
104 105
105 NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const; 106 NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
106 void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name); 107 void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) const;
107 NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const; 108 NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const;
108 u64 GetCurrentPosixTime() const; 109 u64 GetCurrentPosixTime() const;
109 u64 RemoveVersionByte(u64 application_id) const; 110 u64 RemoveVersionByte(u64 application_id) const;
110 void UpdateSettingsCrc(); 111 void UpdateSettingsCrc();
111 void UpdateRegisterInfoCrc(); 112 void UpdateRegisterInfoCrc();
112 113
113 void BuildAmiiboWithoutKeys(); 114 void BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
115 const NFP::EncryptedNTAG215File& encrypted_file) const;
114 116
115 bool is_controller_set{}; 117 bool is_controller_set{};
116 int callback_key; 118 int callback_key;
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp
index d5deaaf27..cffd602df 100644
--- a/src/core/hle/service/nfc/common/device_manager.cpp
+++ b/src/core/hle/service/nfc/common/device_manager.cpp
@@ -543,9 +543,14 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
543 543
544 std::shared_ptr<NfcDevice> device = nullptr; 544 std::shared_ptr<NfcDevice> device = nullptr;
545 auto result = GetDeviceHandle(device_handle, device); 545 auto result = GetDeviceHandle(device_handle, device);
546 NFC::TagInfo tag_info{};
546 547
547 if (result.IsSuccess()) { 548 if (result.IsSuccess()) {
548 result = device->ReadBackupData(data); 549 result = device->GetTagInfo(tag_info, false);
550 }
551
552 if (result.IsSuccess()) {
553 result = device->ReadBackupData(tag_info.uuid, data);
549 result = VerifyDeviceResult(device, result); 554 result = VerifyDeviceResult(device, result);
550 } 555 }
551 556
@@ -557,9 +562,14 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
557 562
558 std::shared_ptr<NfcDevice> device = nullptr; 563 std::shared_ptr<NfcDevice> device = nullptr;
559 auto result = GetDeviceHandle(device_handle, device); 564 auto result = GetDeviceHandle(device_handle, device);
565 NFC::TagInfo tag_info{};
566
567 if (result.IsSuccess()) {
568 result = device->GetTagInfo(tag_info, false);
569 }
560 570
561 if (result.IsSuccess()) { 571 if (result.IsSuccess()) {
562 result = device->WriteBackupData(data); 572 result = device->WriteBackupData(tag_info.uuid, data);
563 result = VerifyDeviceResult(device, result); 573 result = VerifyDeviceResult(device, result);
564 } 574 }
565 575
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp
index 0fa29d398..198d0f2b9 100644
--- a/src/core/hle/service/nfc/nfc_interface.cpp
+++ b/src/core/hle/service/nfc/nfc_interface.cpp
@@ -302,7 +302,7 @@ Result NfcInterface::TranslateResultToServiceError(Result result) const {
302 return TranslateResultToNfp(result); 302 return TranslateResultToNfp(result);
303 } 303 }
304 default: 304 default:
305 if (result != ResultUnknown216) { 305 if (result != ResultBackupPathAlreadyExist) {
306 return result; 306 return result;
307 } 307 }
308 return ResultUnknown74; 308 return ResultUnknown74;
@@ -343,6 +343,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
343 if (result == ResultApplicationAreaIsNotInitialized) { 343 if (result == ResultApplicationAreaIsNotInitialized) {
344 return NFP::ResultApplicationAreaIsNotInitialized; 344 return NFP::ResultApplicationAreaIsNotInitialized;
345 } 345 }
346 if (result == ResultCorruptedDataWithBackup) {
347 return NFP::ResultCorruptedDataWithBackup;
348 }
346 if (result == ResultCorruptedData) { 349 if (result == ResultCorruptedData) {
347 return NFP::ResultCorruptedData; 350 return NFP::ResultCorruptedData;
348 } 351 }
@@ -355,6 +358,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
355 if (result == ResultNotAnAmiibo) { 358 if (result == ResultNotAnAmiibo) {
356 return NFP::ResultNotAnAmiibo; 359 return NFP::ResultNotAnAmiibo;
357 } 360 }
361 if (result == ResultUnableToAccessBackupFile) {
362 return NFP::ResultUnableToAccessBackupFile;
363 }
358 LOG_WARNING(Service_NFC, "Result conversion not handled"); 364 LOG_WARNING(Service_NFC, "Result conversion not handled");
359 return result; 365 return result;
360} 366}
diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h
index 917d79ef8..59a808740 100644
--- a/src/core/hle/service/nfc/nfc_result.h
+++ b/src/core/hle/service/nfc/nfc_result.h
@@ -9,20 +9,22 @@ namespace Service::NFC {
9 9
10constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64); 10constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64);
11constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65); 11constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65);
12constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68); 12constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFC, 68);
13constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73); 13constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73);
14constexpr Result ResultUnknown74(ErrorModule::NFC, 74); 14constexpr Result ResultUnknown74(ErrorModule::NFC, 74);
15constexpr Result ResultUnknown76(ErrorModule::NFC, 76); 15constexpr Result ResultUnknown76(ErrorModule::NFC, 76);
16constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77); 16constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77);
17constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80); 17constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80);
18constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88); 18constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFC, 88);
19constexpr Result ResultTagRemoved(ErrorModule::NFC, 97); 19constexpr Result ResultTagRemoved(ErrorModule::NFC, 97);
20constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120); 20constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFC, 113);
21constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); 21constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFC, 120);
22constexpr Result ResultCorruptedData(ErrorModule::NFP, 144); 22constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFC, 128);
23constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152); 23constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136);
24constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168); 24constexpr Result ResultCorruptedData(ErrorModule::NFC, 144);
25constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178); 25constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152);
26constexpr Result ResultUnknown216(ErrorModule::NFC, 216); 26constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168);
27constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178);
28constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216);
27 29
28} // namespace Service::NFC 30} // namespace Service::NFC
diff --git a/src/core/hle/service/nfp/nfp_interface.cpp b/src/core/hle/service/nfp/nfp_interface.cpp
index 21d159154..34ef9d82d 100644
--- a/src/core/hle/service/nfp/nfp_interface.cpp
+++ b/src/core/hle/service/nfp/nfp_interface.cpp
@@ -126,7 +126,7 @@ void Interface::Flush(HLERequestContext& ctx) {
126void Interface::Restore(HLERequestContext& ctx) { 126void Interface::Restore(HLERequestContext& ctx) {
127 IPC::RequestParser rp{ctx}; 127 IPC::RequestParser rp{ctx};
128 const auto device_handle{rp.Pop<u64>()}; 128 const auto device_handle{rp.Pop<u64>()};
129 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); 129 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
130 130
131 auto result = GetManager()->Restore(device_handle); 131 auto result = GetManager()->Restore(device_handle);
132 result = TranslateResultToServiceError(result); 132 result = TranslateResultToServiceError(result);
@@ -394,7 +394,7 @@ void Interface::BreakTag(HLERequestContext& ctx) {
394void Interface::ReadBackupData(HLERequestContext& ctx) { 394void Interface::ReadBackupData(HLERequestContext& ctx) {
395 IPC::RequestParser rp{ctx}; 395 IPC::RequestParser rp{ctx};
396 const auto device_handle{rp.Pop<u64>()}; 396 const auto device_handle{rp.Pop<u64>()};
397 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); 397 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
398 398
399 std::vector<u8> backup_data{}; 399 std::vector<u8> backup_data{};
400 auto result = GetManager()->ReadBackupData(device_handle, backup_data); 400 auto result = GetManager()->ReadBackupData(device_handle, backup_data);
@@ -412,7 +412,7 @@ void Interface::WriteBackupData(HLERequestContext& ctx) {
412 IPC::RequestParser rp{ctx}; 412 IPC::RequestParser rp{ctx};
413 const auto device_handle{rp.Pop<u64>()}; 413 const auto device_handle{rp.Pop<u64>()};
414 const auto backup_data_buffer{ctx.ReadBuffer()}; 414 const auto backup_data_buffer{ctx.ReadBuffer()};
415 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); 415 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
416 416
417 auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer); 417 auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer);
418 result = TranslateResultToServiceError(result); 418 result = TranslateResultToServiceError(result);
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
index 4c126cd81..618533843 100644
--- a/src/core/hle/service/nfp/nfp_result.h
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -17,9 +17,11 @@ constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
17constexpr Result ResultTagRemoved(ErrorModule::NFP, 97); 17constexpr Result ResultTagRemoved(ErrorModule::NFP, 97);
18constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120); 18constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
19constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); 19constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
20constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFP, 136);
20constexpr Result ResultCorruptedData(ErrorModule::NFP, 144); 21constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
21constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152); 22constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
22constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168); 23constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
23constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178); 24constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
25constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFP, 200);
24 26
25} // namespace Service::NFP 27} // namespace Service::NFP
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index 0c7aee1b8..dc45169ad 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -69,7 +69,7 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
69 69
70void nvhost_nvdec::OnOpen(DeviceFD fd) { 70void nvhost_nvdec::OnOpen(DeviceFD fd) {
71 LOG_INFO(Service_NVDRV, "NVDEC video stream started"); 71 LOG_INFO(Service_NVDRV, "NVDEC video stream started");
72 system.AudioCore().SetNVDECActive(true); 72 system.SetNVDECActive(true);
73} 73}
74 74
75void nvhost_nvdec::OnClose(DeviceFD fd) { 75void nvhost_nvdec::OnClose(DeviceFD fd) {
@@ -79,7 +79,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) {
79 if (iter != host1x_file.fd_to_id.end()) { 79 if (iter != host1x_file.fd_to_id.end()) {
80 system.GPU().ClearCdmaInstance(iter->second); 80 system.GPU().ClearCdmaInstance(iter->second);
81 } 81 }
82 system.AudioCore().SetNVDECActive(false); 82 system.SetNVDECActive(false);
83} 83}
84 84
85} // namespace Service::Nvidia::Devices 85} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 4988e6e17..da2d5890f 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -324,6 +324,10 @@ s64 Nvnflinger::GetNextTicks() const {
324 speed_scale = 0.01f; 324 speed_scale = 0.01f;
325 } 325 }
326 } 326 }
327 if (system.GetNVDECActive() && settings.use_video_framerate.GetValue()) {
328 // Run at intended presentation rate during video playback.
329 speed_scale = 1.f;
330 }
327 331
328 // As an extension, treat nonpositive swap interval as framerate multiplier. 332 // As an extension, treat nonpositive swap interval as framerate multiplier.
329 const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval) 333 const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval)
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 73d04d7ee..7be6cf5f3 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -33,7 +33,8 @@ static_assert(sizeof(NroSegmentHeader) == 0x8, "NroSegmentHeader has incorrect s
33struct NroHeader { 33struct NroHeader {
34 INSERT_PADDING_BYTES(0x4); 34 INSERT_PADDING_BYTES(0x4);
35 u32_le module_header_offset; 35 u32_le module_header_offset;
36 INSERT_PADDING_BYTES(0x8); 36 u32 magic_ext1;
37 u32 magic_ext2;
37 u32_le magic; 38 u32_le magic;
38 INSERT_PADDING_BYTES(0x4); 39 INSERT_PADDING_BYTES(0x4);
39 u32_le file_size; 40 u32_le file_size;
@@ -124,6 +125,16 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) {
124 return FileType::Error; 125 return FileType::Error;
125} 126}
126 127
128bool AppLoader_NRO::IsHomebrew() {
129 // Read NSO header
130 NroHeader nro_header{};
131 if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
132 return false;
133 }
134 return nro_header.magic_ext1 == Common::MakeMagic('H', 'O', 'M', 'E') &&
135 nro_header.magic_ext2 == Common::MakeMagic('B', 'R', 'E', 'W');
136}
137
127static constexpr u32 PageAlignSize(u32 size) { 138static constexpr u32 PageAlignSize(u32 size) {
128 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK); 139 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
129} 140}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index ccb77b581..8de6eebc6 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -38,6 +38,8 @@ public:
38 */ 38 */
39 static FileType IdentifyType(const FileSys::VirtualFile& nro_file); 39 static FileType IdentifyType(const FileSys::VirtualFile& nro_file);
40 40
41 bool IsHomebrew();
42
41 FileType GetFileType() const override { 43 FileType GetFileType() const override {
42 return IdentifyType(file); 44 return IdentifyType(file);
43 } 45 }
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 6288fef62..bac9dff90 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -101,6 +101,12 @@ const std::map<Settings::RendererBackend, QString> Config::renderer_backend_text
101 {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))}, 101 {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
102}; 102};
103 103
104const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
105 {Settings::ShaderBackend::GLSL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
106 {Settings::ShaderBackend::GLASM, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
107 {Settings::ShaderBackend::SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
108};
109
104// This shouldn't have anything except static initializers (no functions). So 110// This shouldn't have anything except static initializers (no functions). So
105// QKeySequence(...).toString() is NOT ALLOWED HERE. 111// QKeySequence(...).toString() is NOT ALLOWED HERE.
106// This must be in alphabetical order according to action name as it must have the same order as 112// This must be in alphabetical order according to action name as it must have the same order as
@@ -754,6 +760,7 @@ void Config::ReadRendererValues() {
754 ReadGlobalSetting(Settings::values.use_fast_gpu_time); 760 ReadGlobalSetting(Settings::values.use_fast_gpu_time);
755 ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); 761 ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
756 ReadGlobalSetting(Settings::values.enable_compute_pipelines); 762 ReadGlobalSetting(Settings::values.enable_compute_pipelines);
763 ReadGlobalSetting(Settings::values.use_video_framerate);
757 ReadGlobalSetting(Settings::values.bg_red); 764 ReadGlobalSetting(Settings::values.bg_red);
758 ReadGlobalSetting(Settings::values.bg_green); 765 ReadGlobalSetting(Settings::values.bg_green);
759 ReadGlobalSetting(Settings::values.bg_blue); 766 ReadGlobalSetting(Settings::values.bg_blue);
@@ -1409,6 +1416,7 @@ void Config::SaveRendererValues() {
1409 WriteGlobalSetting(Settings::values.use_fast_gpu_time); 1416 WriteGlobalSetting(Settings::values.use_fast_gpu_time);
1410 WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); 1417 WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
1411 WriteGlobalSetting(Settings::values.enable_compute_pipelines); 1418 WriteGlobalSetting(Settings::values.enable_compute_pipelines);
1419 WriteGlobalSetting(Settings::values.use_video_framerate);
1412 WriteGlobalSetting(Settings::values.bg_red); 1420 WriteGlobalSetting(Settings::values.bg_red);
1413 WriteGlobalSetting(Settings::values.bg_green); 1421 WriteGlobalSetting(Settings::values.bg_green);
1414 WriteGlobalSetting(Settings::values.bg_blue); 1422 WriteGlobalSetting(Settings::values.bg_blue);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ad590ea9e..0fd4baf6b 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -54,6 +54,7 @@ public:
54 static const std::map<bool, QString> use_docked_mode_texts_map; 54 static const std::map<bool, QString> use_docked_mode_texts_map;
55 static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map; 55 static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map;
56 static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map; 56 static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
57 static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
57 58
58 static constexpr UISettings::Theme default_theme{ 59 static constexpr UISettings::Theme default_theme{
59#ifdef _WIN32 60#ifdef _WIN32
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index 896863f87..0463ac8b9 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -42,6 +42,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
42 Settings::values.use_vulkan_driver_pipeline_cache.GetValue()); 42 Settings::values.use_vulkan_driver_pipeline_cache.GetValue());
43 ui->enable_compute_pipelines_checkbox->setChecked( 43 ui->enable_compute_pipelines_checkbox->setChecked(
44 Settings::values.enable_compute_pipelines.GetValue()); 44 Settings::values.enable_compute_pipelines.GetValue());
45 ui->use_video_framerate_checkbox->setChecked(Settings::values.use_video_framerate.GetValue());
45 46
46 if (Settings::IsConfiguringGlobal()) { 47 if (Settings::IsConfiguringGlobal()) {
47 ui->gpu_accuracy->setCurrentIndex( 48 ui->gpu_accuracy->setCurrentIndex(
@@ -91,6 +92,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
91 ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines, 92 ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines,
92 ui->enable_compute_pipelines_checkbox, 93 ui->enable_compute_pipelines_checkbox,
93 enable_compute_pipelines); 94 enable_compute_pipelines);
95 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_video_framerate,
96 ui->use_video_framerate_checkbox, use_video_framerate);
94} 97}
95 98
96void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { 99void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -125,6 +128,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
125 Settings::values.max_anisotropy.UsingGlobal()); 128 Settings::values.max_anisotropy.UsingGlobal());
126 ui->enable_compute_pipelines_checkbox->setEnabled( 129 ui->enable_compute_pipelines_checkbox->setEnabled(
127 Settings::values.enable_compute_pipelines.UsingGlobal()); 130 Settings::values.enable_compute_pipelines.UsingGlobal());
131 ui->use_video_framerate_checkbox->setEnabled(
132 Settings::values.use_video_framerate.UsingGlobal());
128 133
129 return; 134 return;
130 } 135 }
@@ -149,6 +154,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
149 ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox, 154 ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox,
150 Settings::values.enable_compute_pipelines, 155 Settings::values.enable_compute_pipelines,
151 enable_compute_pipelines); 156 enable_compute_pipelines);
157 ConfigurationShared::SetColoredTristate(ui->use_video_framerate_checkbox,
158 Settings::values.use_video_framerate,
159 use_video_framerate);
152 ConfigurationShared::SetColoredComboBox( 160 ConfigurationShared::SetColoredComboBox(
153 ui->gpu_accuracy, ui->label_gpu_accuracy, 161 ui->gpu_accuracy, ui->label_gpu_accuracy,
154 static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); 162 static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 1c7b636b9..a4dc8ceb0 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -47,6 +47,7 @@ private:
47 ConfigurationShared::CheckState use_fast_gpu_time; 47 ConfigurationShared::CheckState use_fast_gpu_time;
48 ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache; 48 ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache;
49 ConfigurationShared::CheckState enable_compute_pipelines; 49 ConfigurationShared::CheckState enable_compute_pipelines;
50 ConfigurationShared::CheckState use_video_framerate;
50 51
51 const Core::System& system; 52 const Core::System& system;
52}; 53};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index 37757a918..e7f0ef6be 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -192,6 +192,16 @@ Compute pipelines are always enabled on all other drivers.</string>
192 </widget> 192 </widget>
193 </item> 193 </item>
194 <item> 194 <item>
195 <widget class="QCheckBox" name="use_video_framerate_checkbox">
196 <property name="toolTip">
197 <string>Run the game at normal speed during video playback, even when the framerate is unlocked.</string>
198 </property>
199 <property name="text">
200 <string>Sync to framerate of video playback</string>
201 </property>
202 </widget>
203 </item>
204 <item>
195 <widget class="QWidget" name="af_layout" native="true"> 205 <widget class="QWidget" name="af_layout" native="true">
196 <layout class="QHBoxLayout" name="horizontalLayout_1"> 206 <layout class="QHBoxLayout" name="horizontalLayout_1">
197 <property name="leftMargin"> 207 <property name="leftMargin">
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 145fea5f1..9d06b21b6 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -4116,7 +4116,13 @@ void GMainWindow::UpdateDockedButton() {
4116void GMainWindow::UpdateAPIText() { 4116void GMainWindow::UpdateAPIText() {
4117 const auto api = Settings::values.renderer_backend.GetValue(); 4117 const auto api = Settings::values.renderer_backend.GetValue();
4118 const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second; 4118 const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second;
4119 renderer_status_button->setText(renderer_status_text.toUpper()); 4119 renderer_status_button->setText(
4120 api == Settings::RendererBackend::OpenGL
4121 ? tr("%1 %2").arg(
4122 renderer_status_text.toUpper(),
4123 Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue())
4124 ->second)
4125 : renderer_status_text.toUpper());
4120} 4126}
4121 4127
4122void GMainWindow::UpdateFilterText() { 4128void GMainWindow::UpdateFilterText() {