summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/build.gradle.kts25
-rw-r--r--src/android/app/src/main/AndroidManifest.xml24
-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.kt27
-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/ui/main/MainActivity.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt24
-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.cpp40
-rw-r--r--src/android/app/src/main/res/drawable-xhdpi/tv_banner.pngbin0 -> 7764 bytes
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
-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/video_core/renderer_vulkan/vk_blit_screen.cpp55
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp5
-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
43 files changed, 370 insertions, 229 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 13bb227ff..d4698ae1c 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -74,16 +74,7 @@ android {
74 74
75 // Signed by release key, allowing for upload to Play Store. 75 // Signed by release key, allowing for upload to Play Store.
76 release { 76 release {
77 signingConfig = signingConfigs.getByName("debug") 77 resValue("string", "app_name_suffixed", "yuzu")
78 isMinifyEnabled = true
79 isDebuggable = false
80 proguardFiles(
81 getDefaultProguardFile("proguard-android.txt"),
82 "proguard-rules.pro"
83 )
84 }
85
86 register("relWithVersionCode") {
87 signingConfig = signingConfigs.getByName("debug") 78 signingConfig = signingConfigs.getByName("debug")
88 isMinifyEnabled = true 79 isMinifyEnabled = true
89 isDebuggable = false 80 isDebuggable = false
@@ -96,6 +87,7 @@ android {
96 // builds a release build that doesn't need signing 87 // builds a release build that doesn't need signing
97 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. 88 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
98 register("relWithDebInfo") { 89 register("relWithDebInfo") {
90 resValue("string", "app_name_suffixed", "yuzu Debug Release")
99 signingConfig = signingConfigs.getByName("debug") 91 signingConfig = signingConfigs.getByName("debug")
100 isMinifyEnabled = true 92 isMinifyEnabled = true
101 isDebuggable = true 93 isDebuggable = true
@@ -103,16 +95,19 @@ android {
103 getDefaultProguardFile("proguard-android.txt"), 95 getDefaultProguardFile("proguard-android.txt"),
104 "proguard-rules.pro" 96 "proguard-rules.pro"
105 ) 97 )
106 versionNameSuffix = "-debug" 98 versionNameSuffix = "-relWithDebInfo"
99 applicationIdSuffix = ".relWithDebInfo"
107 isJniDebuggable = true 100 isJniDebuggable = true
108 } 101 }
109 102
110 // Signed by debug key disallowing distribution on Play Store. 103 // Signed by debug key disallowing distribution on Play Store.
111 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. 104 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
112 debug { 105 debug {
106 resValue("string", "app_name_suffixed", "yuzu Debug")
113 isDebuggable = true 107 isDebuggable = true
114 isJniDebuggable = true 108 isJniDebuggable = true
115 versionNameSuffix = "-debug" 109 versionNameSuffix = "-debug"
110 applicationIdSuffix = ".debug"
116 } 111 }
117 } 112 }
118 113
@@ -162,19 +157,19 @@ dependencies {
162 implementation("androidx.appcompat:appcompat:1.6.1") 157 implementation("androidx.appcompat:appcompat:1.6.1")
163 implementation("androidx.recyclerview:recyclerview:1.3.0") 158 implementation("androidx.recyclerview:recyclerview:1.3.0")
164 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 159 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
165 implementation("androidx.fragment:fragment-ktx:1.5.7") 160 implementation("androidx.fragment:fragment-ktx:1.6.0")
166 implementation("androidx.documentfile:documentfile:1.0.1") 161 implementation("androidx.documentfile:documentfile:1.0.1")
167 implementation("com.google.android.material:material:1.9.0") 162 implementation("com.google.android.material:material:1.9.0")
168 implementation("androidx.preference:preference:1.2.0") 163 implementation("androidx.preference:preference:1.2.0")
169 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 164 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
170 implementation("io.coil-kt:coil:2.2.2") 165 implementation("io.coil-kt:coil:2.2.2")
171 implementation("androidx.core:core-splashscreen:1.0.1") 166 implementation("androidx.core:core-splashscreen:1.0.1")
172 implementation("androidx.window:window:1.0.0") 167 implementation("androidx.window:window:1.1.0")
173 implementation("org.ini4j:ini4j:0.5.4") 168 implementation("org.ini4j:ini4j:0.5.4")
174 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 169 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
175 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") 170 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
176 implementation("androidx.navigation:navigation-fragment-ktx:2.5.3") 171 implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
177 implementation("androidx.navigation:navigation-ui-ktx:2.5.3") 172 implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
178 implementation("info.debatty:java-string-similarity:2.0.0") 173 implementation("info.debatty:java-string-similarity:2.0.0")
179 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") 174 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
180} 175}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 43087f2c0..1e92098ec 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -6,17 +6,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
6--> 6-->
7 7
8<manifest xmlns:android="http://schemas.android.com/apk/res/android"> 8<manifest xmlns:android="http://schemas.android.com/apk/res/android">
9 <uses-feature 9 <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
10 android:name="android.hardware.touchscreen" 10 <uses-feature android:name="android.hardware.gamepad" android:required="false" />
11 android:required="false"/> 11 <uses-feature android:name="android.software.leanback" android:required="false" />
12 <uses-feature 12 <uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
13 android:name="android.hardware.gamepad"
14 android:required="false"/>
15
16 <uses-feature
17 android:name="android.hardware.vulkan.version"
18 android:version="0x401000"
19 android:required="true" />
20 13
21 <uses-permission android:name="android.permission.INTERNET" /> 14 <uses-permission android:name="android.permission.INTERNET" />
22 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 15 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -25,13 +18,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
25 18
26 <application 19 <application
27 android:name="org.yuzu.yuzu_emu.YuzuApplication" 20 android:name="org.yuzu.yuzu_emu.YuzuApplication"
28 android:label="@string/app_name" 21 android:label="@string/app_name_suffixed"
29 android:icon="@drawable/ic_launcher" 22 android:icon="@drawable/ic_launcher"
30 android:allowBackup="true" 23 android:allowBackup="true"
31 android:hasFragileUserData="true" 24 android:hasFragileUserData="true"
32 android:supportsRtl="true" 25 android:supportsRtl="true"
33 android:isGame="true" 26 android:isGame="true"
34 android:banner="@drawable/ic_launcher" 27 android:banner="@drawable/tv_banner"
35 android:extractNativeLibs="true" 28 android:extractNativeLibs="true"
36 android:fullBackupContent="@xml/data_extraction_rules" 29 android:fullBackupContent="@xml/data_extraction_rules"
37 android:dataExtractionRules="@xml/data_extraction_rules_api_31" 30 android:dataExtractionRules="@xml/data_extraction_rules_api_31"
@@ -44,9 +37,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
44 37
45 <!-- This intentfilter marks this Activity as the one that gets launched from Home screen. --> 38 <!-- This intentfilter marks this Activity as the one that gets launched from Home screen. -->
46 <intent-filter> 39 <intent-filter>
47 <action android:name="android.intent.action.MAIN"/> 40 <action android:name="android.intent.action.MAIN" />
48 41
49 <category android:name="android.intent.category.LAUNCHER"/> 42 <category android:name="android.intent.category.LAUNCHER" />
43 <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
50 </intent-filter> 44 </intent-filter>
51 </activity> 45 </activity>
52 46
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..20a0394f5 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
@@ -6,15 +6,12 @@ package org.yuzu.yuzu_emu.activities
6import android.app.Activity 6import android.app.Activity
7import android.content.Context 7import android.content.Context
8import android.content.Intent 8import android.content.Intent
9import android.content.res.Configuration
10import android.graphics.Rect 9import android.graphics.Rect
11import android.hardware.Sensor 10import android.hardware.Sensor
12import android.hardware.SensorEvent 11import android.hardware.SensorEvent
13import android.hardware.SensorEventListener 12import android.hardware.SensorEventListener
14import android.hardware.SensorManager 13import android.hardware.SensorManager
15import android.hardware.display.DisplayManager
16import android.os.Bundle 14import android.os.Bundle
17import android.view.Display
18import android.view.InputDevice 15import android.view.InputDevice
19import android.view.KeyEvent 16import android.view.KeyEvent
20import android.view.MotionEvent 17import android.view.MotionEvent
@@ -23,7 +20,6 @@ import android.view.View
23import android.view.inputmethod.InputMethodManager 20import android.view.inputmethod.InputMethodManager
24import androidx.activity.viewModels 21import androidx.activity.viewModels
25import androidx.appcompat.app.AppCompatActivity 22import androidx.appcompat.app.AppCompatActivity
26import androidx.core.content.getSystemService
27import androidx.core.view.WindowCompat 23import androidx.core.view.WindowCompat
28import androidx.core.view.WindowInsetsCompat 24import androidx.core.view.WindowInsetsCompat
29import androidx.core.view.WindowInsetsControllerCompat 25import androidx.core.view.WindowInsetsControllerCompat
@@ -39,7 +35,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
39import org.yuzu.yuzu_emu.fragments.EmulationFragment 35import org.yuzu.yuzu_emu.fragments.EmulationFragment
40import org.yuzu.yuzu_emu.model.Game 36import org.yuzu.yuzu_emu.model.Game
41import org.yuzu.yuzu_emu.utils.ControllerMappingHelper 37import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
42import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
43import org.yuzu.yuzu_emu.utils.ForegroundService 38import org.yuzu.yuzu_emu.utils.ForegroundService
44import org.yuzu.yuzu_emu.utils.InputHandler 39import org.yuzu.yuzu_emu.utils.InputHandler
45import org.yuzu.yuzu_emu.utils.NfcReader 40import org.yuzu.yuzu_emu.utils.NfcReader
@@ -148,11 +143,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
148 super.onResume() 143 super.onResume()
149 nfcReader.startScanning() 144 nfcReader.startScanning()
150 startMotionSensorListener() 145 startMotionSensorListener()
151
152 NativeLibrary.notifyOrientationChange(
153 EmulationMenuSettings.landscapeScreenLayout,
154 getAdjustedRotation()
155 )
156 } 146 }
157 147
158 override fun onPause() { 148 override fun onPause() {
@@ -258,23 +248,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
258 248
259 override fun onAccuracyChanged(sensor: Sensor, i: Int) {} 249 override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
260 250
261 private fun getAdjustedRotation():Int {
262 val rotation = getSystemService<DisplayManager>()!!.getDisplay(Display.DEFAULT_DISPLAY).rotation
263 val config: Configuration = resources.configuration
264
265 if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 ||
266 (config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0) {
267 return rotation
268 }
269 when (rotation) {
270 Surface.ROTATION_0 -> return Surface.ROTATION_90
271 Surface.ROTATION_90 -> return Surface.ROTATION_0
272 Surface.ROTATION_180 -> return Surface.ROTATION_270
273 Surface.ROTATION_270 -> return Surface.ROTATION_180
274 }
275 return rotation
276 }
277
278 private fun restoreState(savedInstanceState: Bundle) { 251 private fun restoreState(savedInstanceState: Bundle) {
279 game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!! 252 game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
280 } 253 }
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/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 124f62f08..3fca0a7e6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -284,10 +284,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
284 if (result == null) 284 if (result == null)
285 return@registerForActivityResult 285 return@registerForActivityResult
286 286
287 if (!FileUtil.hasExtension(result.toString(), "keys")) { 287 if (!FileUtil.hasExtension(result, "keys")) {
288 MessageDialogFragment.newInstance( 288 MessageDialogFragment.newInstance(
289 R.string.reading_keys_failure, 289 R.string.reading_keys_failure,
290 R.string.install_keys_failure_extension_description 290 R.string.install_prod_keys_failure_extension_description
291 ).show(supportFragmentManager, MessageDialogFragment.TAG) 291 ).show(supportFragmentManager, MessageDialogFragment.TAG)
292 return@registerForActivityResult 292 return@registerForActivityResult
293 } 293 }
@@ -379,10 +379,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
379 if (result == null) 379 if (result == null)
380 return@registerForActivityResult 380 return@registerForActivityResult
381 381
382 if (!FileUtil.hasExtension(result.toString(), "bin")) { 382 if (!FileUtil.hasExtension(result, "bin")) {
383 MessageDialogFragment.newInstance( 383 MessageDialogFragment.newInstance(
384 R.string.reading_keys_failure, 384 R.string.reading_keys_failure,
385 R.string.install_keys_failure_extension_description 385 R.string.install_amiibo_keys_failure_extension_description
386 ).show(supportFragmentManager, MessageDialogFragment.TAG) 386 ).show(supportFragmentManager, MessageDialogFragment.TAG)
387 return@registerForActivityResult 387 return@registerForActivityResult
388 } 388 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index 593dad8d3..492b1ad91 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -7,7 +7,9 @@ import android.content.Context
7import android.database.Cursor 7import android.database.Cursor
8import android.net.Uri 8import android.net.Uri
9import android.provider.DocumentsContract 9import android.provider.DocumentsContract
10import android.provider.OpenableColumns
10import androidx.documentfile.provider.DocumentFile 11import androidx.documentfile.provider.DocumentFile
12import org.yuzu.yuzu_emu.YuzuApplication
11import org.yuzu.yuzu_emu.model.MinimalDocumentFile 13import org.yuzu.yuzu_emu.model.MinimalDocumentFile
12import java.io.BufferedInputStream 14import java.io.BufferedInputStream
13import java.io.File 15import java.io.File
@@ -324,7 +326,25 @@ object FileUtil {
324 } 326 }
325 } 327 }
326 328
327 fun hasExtension(path: String, extension: String): Boolean { 329 fun hasExtension(path: String, extension: String): Boolean =
328 return path.substring(path.lastIndexOf(".") + 1).contains(extension) 330 path.substring(path.lastIndexOf(".") + 1).contains(extension)
331
332 fun hasExtension(uri: Uri, extension: String): Boolean {
333 val fileName: String?
334 val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
335 val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
336 cursor?.moveToFirst()
337
338 if (nameIndex == null) {
339 return false
340 }
341
342 fileName = cursor.getString(nameIndex)
343 cursor.close()
344
345 if (fileName == null) {
346 return false
347 }
348 return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
329 } 349 }
330} 350}
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..7ebed5e6a 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"
@@ -93,14 +94,6 @@ public:
93 m_native_window = native_window; 94 m_native_window = native_window;
94 } 95 }
95 96
96 u32 ScreenRotation() const {
97 return m_screen_rotation;
98 }
99
100 void SetScreenRotation(u32 screen_rotation) {
101 m_screen_rotation = screen_rotation;
102 }
103
104 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, 97 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
105 const std::string& custom_driver_name, 98 const std::string& custom_driver_name,
106 const std::string& file_redirect_dir) { 99 const std::string& file_redirect_dir) {
@@ -281,6 +274,10 @@ public:
281 return GetRomMetadata(path).icon; 274 return GetRomMetadata(path).icon;
282 } 275 }
283 276
277 bool GetIsHomebrew(const std::string& path) {
278 return GetRomMetadata(path).isHomebrew;
279 }
280
284 void ResetRomMetadata() { 281 void ResetRomMetadata() {
285 m_rom_metadata_cache.clear(); 282 m_rom_metadata_cache.clear();
286 } 283 }
@@ -348,6 +345,7 @@ private:
348 struct RomMetadata { 345 struct RomMetadata {
349 std::string title; 346 std::string title;
350 std::vector<u8> icon; 347 std::vector<u8> icon;
348 bool isHomebrew;
351 }; 349 };
352 350
353 RomMetadata GetRomMetadata(const std::string& path) { 351 RomMetadata GetRomMetadata(const std::string& path) {
@@ -360,11 +358,17 @@ private:
360 358
361 RomMetadata CacheRomMetadata(const std::string& path) { 359 RomMetadata CacheRomMetadata(const std::string& path) {
362 const auto file = Core::GetGameFileFromPath(m_vfs, path); 360 const auto file = Core::GetGameFileFromPath(m_vfs, path);
363 const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); 361 auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
364 362
365 RomMetadata entry; 363 RomMetadata entry;
366 loader->ReadTitle(entry.title); 364 loader->ReadTitle(entry.title);
367 loader->ReadIcon(entry.icon); 365 loader->ReadIcon(entry.icon);
366 if (loader->GetFileType() == Loader::FileType::NRO) {
367 auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
368 entry.isHomebrew = loader_nro->IsHomebrew();
369 } else {
370 entry.isHomebrew = false;
371 }
368 372
369 m_rom_metadata_cache[path] = entry; 373 m_rom_metadata_cache[path] = entry;
370 374
@@ -388,7 +392,6 @@ private:
388 // Window management 392 // Window management
389 std::unique_ptr<EmuWindow_Android> m_window; 393 std::unique_ptr<EmuWindow_Android> m_window;
390 ANativeWindow* m_native_window{}; 394 ANativeWindow* m_native_window{};
391 u32 m_screen_rotation{};
392 395
393 // Core emulation 396 // Core emulation
394 Core::System m_system; 397 Core::System m_system;
@@ -414,10 +417,6 @@ private:
414 417
415} // Anonymous namespace 418} // Anonymous namespace
416 419
417u32 GetAndroidScreenRotation() {
418 return EmulationSession::GetInstance().ScreenRotation();
419}
420
421static Core::SystemResultStatus RunEmulation(const std::string& filepath) { 420static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
422 Common::Log::Initialize(); 421 Common::Log::Initialize();
423 Common::Log::SetColorConsoleBackendEnabled(true); 422 Common::Log::SetColorConsoleBackendEnabled(true);
@@ -461,13 +460,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
461 EmulationSession::GetInstance().SurfaceChanged(); 460 EmulationSession::GetInstance().SurfaceChanged();
462} 461}
463 462
464void Java_org_yuzu_yuzu_1emu_NativeLibrary_notifyOrientationChange(JNIEnv* env,
465 [[maybe_unused]] jclass clazz,
466 jint layout_option,
467 jint rotation) {
468 return EmulationSession::GetInstance().SetScreenRotation(static_cast<u32>(rotation));
469}
470
471void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, 463void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
472 [[maybe_unused]] jclass clazz, 464 [[maybe_unused]] jclass clazz,
473 jstring j_directory) { 465 jstring j_directory) {
@@ -662,6 +654,12 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv
662 return env->NewStringUTF(""); 654 return env->NewStringUTF("");
663} 655}
664 656
657jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
658 [[maybe_unused]] jclass clazz,
659 [[maybe_unused]] jstring j_filename) {
660 return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
661}
662
665void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation 663void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
666 [[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) { 664 [[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
667 // Create the default config.ini. 665 // Create the default config.ini.
diff --git a/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png b/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png
new file mode 100644
index 000000000..20c770591
--- /dev/null
+++ b/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png
Binary files differ
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 0ae69afb4..6e9d47557 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -65,11 +65,8 @@
65 <string name="invalid_keys_file">Invalid keys file selected</string> 65 <string name="invalid_keys_file">Invalid keys file selected</string>
66 <string name="install_keys_success">Keys successfully installed</string> 66 <string name="install_keys_success">Keys successfully installed</string>
67 <string name="reading_keys_failure">Error reading encryption keys</string> 67 <string name="reading_keys_failure">Error reading encryption keys</string>
68 <string name="install_keys_failure_extension_description"> 68 <string name="install_prod_keys_failure_extension_description">Verify your keys file has a .keys extension and try again.</string>
69 1. Verify your keys have the .keys extension.\n\n 69 <string name="install_amiibo_keys_failure_extension_description">Verify your keys file has a .bin extension and try again.</string>
70 2. Keys must not be stored in the Downloads folder.\n\n
71 Resolve the issue(s) and try again.
72 </string>
73 <string name="invalid_keys_error">Invalid encryption keys</string> 70 <string name="invalid_keys_error">Invalid encryption keys</string>
74 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string> 71 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
75 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string> 72 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
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/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 7cdde992b..acb143fc7 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -37,10 +37,6 @@
37#include "video_core/vulkan_common/vulkan_memory_allocator.h" 37#include "video_core/vulkan_common/vulkan_memory_allocator.h"
38#include "video_core/vulkan_common/vulkan_wrapper.h" 38#include "video_core/vulkan_common/vulkan_wrapper.h"
39 39
40#ifdef ANDROID
41extern u32 GetAndroidScreenRotation();
42#endif
43
44namespace Vulkan { 40namespace Vulkan {
45 41
46namespace { 42namespace {
@@ -78,47 +74,6 @@ struct ScreenRectVertex {
78 } 74 }
79}; 75};
80 76
81#ifdef ANDROID
82
83std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
84 constexpr u32 ROTATION_0 = 0;
85 constexpr u32 ROTATION_90 = 1;
86 constexpr u32 ROTATION_180 = 2;
87 constexpr u32 ROTATION_270 = 3;
88
89 // clang-format off
90 switch (GetAndroidScreenRotation()) {
91 case ROTATION_0:
92 // Desktop
93 return { 2.f / width, 0.f, 0.f, 0.f,
94 0.f, 2.f / height, 0.f, 0.f,
95 0.f, 0.f, 1.f, 0.f,
96 -1.f, -1.f, 0.f, 1.f};
97 case ROTATION_180:
98 // Reverse desktop
99 return {-2.f / width, 0.f, 0.f, 0.f,
100 0.f, -2.f / height, 0.f, 0.f,
101 0.f, 0.f, 1.f, 0.f,
102 1.f, 1.f, 0.f, 1.f};
103 case ROTATION_270:
104 // Reverse landscape
105 return { 0.f, -2.f / width, 0.f, 0.f,
106 2.f / height, 0.f, 0.f, 0.f,
107 0.f, 0.f, 1.f, 0.f,
108 -1.f, 1.f, 0.f, 1.f};
109 case ROTATION_90:
110 default:
111 // Landscape
112 return { 0.f, 2.f / width, 0.f, 0.f,
113 -2.f / height, 0.f, 0.f, 0.f,
114 0.f, 0.f, 1.f, 0.f,
115 1.f, -1.f, 0.f, 1.f};
116 }
117 // clang-format on
118}
119
120#else
121
122std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) { 77std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
123 // clang-format off 78 // clang-format off
124 return { 2.f / width, 0.f, 0.f, 0.f, 79 return { 2.f / width, 0.f, 0.f, 0.f,
@@ -128,8 +83,6 @@ std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
128 // clang-format on 83 // clang-format on
129} 84}
130 85
131#endif
132
133u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) { 86u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
134 using namespace VideoCore::Surface; 87 using namespace VideoCore::Surface;
135 return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)); 88 return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
@@ -1159,7 +1112,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1159 .pNext = nullptr, 1112 .pNext = nullptr,
1160 .flags = 0, 1113 .flags = 0,
1161 .imageType = VK_IMAGE_TYPE_2D, 1114 .imageType = VK_IMAGE_TYPE_2D,
1162 .format = GetFormat(framebuffer), 1115 .format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
1163 .extent = 1116 .extent =
1164 { 1117 {
1165 .width = (up_scale * framebuffer.width) >> down_shift, 1118 .width = (up_scale * framebuffer.width) >> down_shift,
@@ -1180,14 +1133,14 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1180 const auto create_commit = [&](vk::Image& image) { 1133 const auto create_commit = [&](vk::Image& image) {
1181 return memory_allocator.Commit(image, MemoryUsage::DeviceLocal); 1134 return memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
1182 }; 1135 };
1183 const auto create_image_view = [&](vk::Image& image) { 1136 const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) {
1184 return device.GetLogical().CreateImageView(VkImageViewCreateInfo{ 1137 return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
1185 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1138 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
1186 .pNext = nullptr, 1139 .pNext = nullptr,
1187 .flags = 0, 1140 .flags = 0,
1188 .image = *image, 1141 .image = *image,
1189 .viewType = VK_IMAGE_VIEW_TYPE_2D, 1142 .viewType = VK_IMAGE_VIEW_TYPE_2D,
1190 .format = GetFormat(framebuffer), 1143 .format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
1191 .components = 1144 .components =
1192 { 1145 {
1193 .r = VK_COMPONENT_SWIZZLE_IDENTITY, 1146 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -1217,7 +1170,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1217 const u32 down_shift = Settings::values.resolution_info.down_shift; 1170 const u32 down_shift = Settings::values.resolution_info.down_shift;
1218 aa_image = create_image(true, up_scale, down_shift); 1171 aa_image = create_image(true, up_scale, down_shift);
1219 aa_commit = create_commit(aa_image); 1172 aa_commit = create_commit(aa_image);
1220 aa_image_view = create_image_view(aa_image); 1173 aa_image_view = create_image_view(aa_image, true);
1221 VkExtent2D size{ 1174 VkExtent2D size{
1222 .width = (up_scale * framebuffer.width) >> down_shift, 1175 .width = (up_scale * framebuffer.width) >> down_shift,
1223 .height = (up_scale * framebuffer.height) >> down_shift, 1176 .height = (up_scale * framebuffer.height) >> down_shift,
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index afcf34fba..d3cddac69 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -231,7 +231,12 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
231 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, 231 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
232 .queueFamilyIndexCount = 0, 232 .queueFamilyIndexCount = 0,
233 .pQueueFamilyIndices = nullptr, 233 .pQueueFamilyIndices = nullptr,
234#ifdef ANDROID
235 // On Android, do not allow surface rotation to deviate from the frontend.
236 .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
237#else
234 .preTransform = capabilities.currentTransform, 238 .preTransform = capabilities.currentTransform,
239#endif
235 .compositeAlpha = alpha_flags, 240 .compositeAlpha = alpha_flags,
236 .presentMode = present_mode, 241 .presentMode = present_mode,
237 .clipped = VK_FALSE, 242 .clipped = VK_FALSE,
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() {