summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/build.gradle.kts16
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt117
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt186
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt75
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt41
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt158
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt97
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt228
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt112
-rw-r--r--src/android/app/src/main/res/drawable/ic_build.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_delete.xml9
-rw-r--r--src/android/app/src/main/res/layout/card_driver_option.xml89
-rw-r--r--src/android/app/src/main/res/layout/fragment_driver_manager.xml48
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml7
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml2
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
-rw-r--r--src/android/build.gradle.kts4
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp9
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h2
-rw-r--r--src/audio_core/sink/sink_stream.cpp4
-rw-r--r--src/common/common_funcs.h4
-rw-r--r--src/common/elf.h8
-rw-r--r--src/common/polyfill_thread.h21
-rw-r--r--src/common/settings.cpp5
-rw-r--r--src/common/settings.h5
-rw-r--r--src/common/settings_enums.h2
-rw-r--r--src/core/hle/kernel/k_page_table.cpp5
-rw-r--r--src/core/hle/kernel/kernel.cpp16
-rw-r--r--src/core/hle/service/acc/acc.cpp43
-rw-r--r--src/core/hle/service/acc/acc.h3
-rw-r--r--src/core/hle/service/acc/acc_su.cpp6
-rw-r--r--src/core/hle/service/acc/profile_manager.h3
-rw-r--r--src/core/hle/service/am/am.cpp41
-rw-r--r--src/core/hle/service/am/am.h3
-rw-r--r--src/core/hle/service/caps/caps.cpp2
-rw-r--r--src/core/hle/service/caps/caps_a.cpp4
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp86
-rw-r--r--src/core/hle/service/caps/caps_manager.h9
-rw-r--r--src/core/hle/service/caps/caps_types.h14
-rw-r--r--src/core/hle/service/caps/caps_u.cpp74
-rw-r--r--src/core/hle/service/jit/jit_context.cpp36
-rw-r--r--src/input_common/drivers/udp_client.cpp1
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h46
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h4
-rw-r--r--src/video_core/engines/draw_manager.cpp24
-rw-r--r--src/video_core/engines/draw_manager.h2
-rw-r--r--src/video_core/host1x/codecs/codec.cpp10
-rw-r--r--src/video_core/host_shaders/convert_d24s8_to_abgr8.frag8
-rw-r--r--src/video_core/host_shaders/convert_s8d24_to_abgr8.frag8
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp14
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp13
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp44
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h2
-rw-r--r--src/video_core/texture_cache/texture_cache.h1
-rw-r--r--src/video_core/texture_cache/util.cpp11
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp8
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/applets/qt_controller.cpp39
-rw-r--r--src/yuzu/applets/qt_controller.h6
-rw-r--r--src/yuzu/applets/qt_controller.ui54
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/configuration/configure_input.cpp36
-rw-r--r--src/yuzu/configuration/configure_input.h1
-rw-r--r--src/yuzu/configuration/shared_translation.cpp8
-rw-r--r--src/yuzu/game_list.cpp5
-rw-r--r--src/yuzu/game_list_worker.cpp7
-rw-r--r--src/yuzu/game_list_worker.h5
-rw-r--r--src/yuzu/main.cpp183
-rw-r--r--src/yuzu/main.h18
-rw-r--r--src/yuzu/uisettings.h11
-rw-r--r--src/yuzu/util/util.cpp81
94 files changed, 1846 insertions, 577 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 84a3308b7..ac43d84b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -27,7 +27,7 @@ android {
27 namespace = "org.yuzu.yuzu_emu" 27 namespace = "org.yuzu.yuzu_emu"
28 28
29 compileSdkVersion = "android-34" 29 compileSdkVersion = "android-34"
30 ndkVersion = "25.2.9519653" 30 ndkVersion = "26.1.10909125"
31 31
32 buildFeatures { 32 buildFeatures {
33 viewBinding = true 33 viewBinding = true
@@ -203,23 +203,23 @@ ktlint {
203} 203}
204 204
205dependencies { 205dependencies {
206 implementation("androidx.core:core-ktx:1.10.1") 206 implementation("androidx.core:core-ktx:1.12.0")
207 implementation("androidx.appcompat:appcompat:1.6.1") 207 implementation("androidx.appcompat:appcompat:1.6.1")
208 implementation("androidx.recyclerview:recyclerview:1.3.0") 208 implementation("androidx.recyclerview:recyclerview:1.3.1")
209 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 209 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
210 implementation("androidx.fragment:fragment-ktx:1.6.0") 210 implementation("androidx.fragment:fragment-ktx:1.6.1")
211 implementation("androidx.documentfile:documentfile:1.0.1") 211 implementation("androidx.documentfile:documentfile:1.0.1")
212 implementation("com.google.android.material:material:1.9.0") 212 implementation("com.google.android.material:material:1.9.0")
213 implementation("androidx.preference:preference:1.2.0") 213 implementation("androidx.preference:preference-ktx:1.2.1")
214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
215 implementation("io.coil-kt:coil:2.2.2") 215 implementation("io.coil-kt:coil:2.2.2")
216 implementation("androidx.core:core-splashscreen:1.0.1") 216 implementation("androidx.core:core-splashscreen:1.0.1")
217 implementation("androidx.window:window:1.2.0-beta03") 217 implementation("androidx.window:window:1.2.0-beta03")
218 implementation("org.ini4j:ini4j:0.5.4") 218 implementation("org.ini4j:ini4j:0.5.4")
219 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 219 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
220 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") 220 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
221 implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") 221 implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
222 implementation("androidx.navigation:navigation-ui-ktx:2.6.0") 222 implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
223 implementation("info.debatty:java-string-similarity:2.0.0") 223 implementation("info.debatty:java-string-similarity:2.0.0")
224 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") 224 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
225} 225}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 832c08e15..a67351727 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -28,7 +28,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
28 android:appCategory="game" 28 android:appCategory="game"
29 android:localeConfig="@xml/locales_config" 29 android:localeConfig="@xml/locales_config"
30 android:banner="@drawable/tv_banner" 30 android:banner="@drawable/tv_banner"
31 android:extractNativeLibs="true"
32 android:fullBackupContent="@xml/data_extraction_rules" 31 android:fullBackupContent="@xml/data_extraction_rules"
33 android:dataExtractionRules="@xml/data_extraction_rules_api_31" 32 android:dataExtractionRules="@xml/data_extraction_rules_api_31"
34 android:enableOnBackInvokedCallback="true"> 33 android:enableOnBackInvokedCallback="true">
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 6e39e542b..115f72710 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
@@ -15,13 +15,9 @@ import androidx.annotation.Keep
15import androidx.fragment.app.DialogFragment 15import androidx.fragment.app.DialogFragment
16import com.google.android.material.dialog.MaterialAlertDialogBuilder 16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import java.lang.ref.WeakReference 17import java.lang.ref.WeakReference
18import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
19import org.yuzu.yuzu_emu.activities.EmulationActivity 18import org.yuzu.yuzu_emu.activities.EmulationActivity
20import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath 19import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
21import org.yuzu.yuzu_emu.utils.FileUtil.exists 20import org.yuzu.yuzu_emu.utils.FileUtil
22import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
23import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
24import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
25import org.yuzu.yuzu_emu.utils.Log 21import org.yuzu.yuzu_emu.utils.Log
26import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 22import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
27 23
@@ -75,7 +71,7 @@ object NativeLibrary {
75 return if (isNativePath(path!!)) { 71 return if (isNativePath(path!!)) {
76 YuzuApplication.documentsTree!!.openContentUri(path, openmode) 72 YuzuApplication.documentsTree!!.openContentUri(path, openmode)
77 } else { 73 } else {
78 openContentUri(appContext, path, openmode) 74 FileUtil.openContentUri(path, openmode)
79 } 75 }
80 } 76 }
81 77
@@ -85,7 +81,7 @@ object NativeLibrary {
85 return if (isNativePath(path!!)) { 81 return if (isNativePath(path!!)) {
86 YuzuApplication.documentsTree!!.getFileSize(path) 82 YuzuApplication.documentsTree!!.getFileSize(path)
87 } else { 83 } else {
88 getFileSize(appContext, path) 84 FileUtil.getFileSize(path)
89 } 85 }
90 } 86 }
91 87
@@ -95,7 +91,7 @@ object NativeLibrary {
95 return if (isNativePath(path!!)) { 91 return if (isNativePath(path!!)) {
96 YuzuApplication.documentsTree!!.exists(path) 92 YuzuApplication.documentsTree!!.exists(path)
97 } else { 93 } else {
98 exists(appContext, path) 94 FileUtil.exists(path)
99 } 95 }
100 } 96 }
101 97
@@ -105,7 +101,7 @@ object NativeLibrary {
105 return if (isNativePath(path!!)) { 101 return if (isNativePath(path!!)) {
106 YuzuApplication.documentsTree!!.isDirectory(path) 102 YuzuApplication.documentsTree!!.isDirectory(path)
107 } else { 103 } else {
108 isDirectory(appContext, path) 104 FileUtil.isDirectory(path)
109 } 105 }
110 } 106 }
111 107
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 9561748cb..8c053670c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -47,7 +47,7 @@ class YuzuApplication : Application() {
47 application = this 47 application = this
48 documentsTree = DocumentsTree() 48 documentsTree = DocumentsTree()
49 DirectoryInitialization.start() 49 DirectoryInitialization.start()
50 GpuDriverHelper.initializeDriverParameters(applicationContext) 50 GpuDriverHelper.initializeDriverParameters()
51 NativeLibrary.logDeviceInfo() 51 NativeLibrary.logDeviceInfo()
52 52
53 createNotificationChannels() 53 createNotificationChannels()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
new file mode 100644
index 000000000..0e818cab9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -0,0 +1,117 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.text.TextUtils
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import androidx.recyclerview.widget.AsyncDifferConfig
11import androidx.recyclerview.widget.DiffUtil
12import androidx.recyclerview.widget.ListAdapter
13import androidx.recyclerview.widget.RecyclerView
14import org.yuzu.yuzu_emu.R
15import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
16import org.yuzu.yuzu_emu.model.DriverViewModel
17import org.yuzu.yuzu_emu.utils.GpuDriverHelper
18import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
19
20class DriverAdapter(private val driverViewModel: DriverViewModel) :
21 ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
22 AsyncDifferConfig.Builder(DiffCallback()).build()
23 ) {
24 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
25 val binding =
26 CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
27 return DriverViewHolder(binding)
28 }
29
30 override fun getItemCount(): Int = currentList.size
31
32 override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
33 holder.bind(currentList[position])
34
35 private fun onSelectDriver(position: Int) {
36 driverViewModel.setSelectedDriverIndex(position)
37 notifyItemChanged(driverViewModel.previouslySelectedDriver)
38 notifyItemChanged(driverViewModel.selectedDriver)
39 }
40
41 private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
42 if (driverViewModel.selectedDriver > position) {
43 driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
44 }
45 if (GpuDriverHelper.customDriverData == driverData.second) {
46 driverViewModel.setSelectedDriverIndex(0)
47 }
48 driverViewModel.driversToDelete.add(driverData.first)
49 driverViewModel.removeDriver(driverData)
50 notifyItemRemoved(position)
51 notifyItemChanged(driverViewModel.selectedDriver)
52 }
53
54 inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
55 RecyclerView.ViewHolder(binding.root) {
56 private lateinit var driverData: Pair<String, GpuDriverMetadata>
57
58 fun bind(driverData: Pair<String, GpuDriverMetadata>) {
59 this.driverData = driverData
60 val driver = driverData.second
61
62 binding.apply {
63 radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
64 root.setOnClickListener {
65 onSelectDriver(bindingAdapterPosition)
66 }
67 buttonDelete.setOnClickListener {
68 onDeleteDriver(driverData, bindingAdapterPosition)
69 }
70
71 // Delay marquee by 3s
72 title.postDelayed(
73 {
74 title.isSelected = true
75 title.ellipsize = TextUtils.TruncateAt.MARQUEE
76 version.isSelected = true
77 version.ellipsize = TextUtils.TruncateAt.MARQUEE
78 description.isSelected = true
79 description.ellipsize = TextUtils.TruncateAt.MARQUEE
80 },
81 3000
82 )
83 if (driver.name == null) {
84 title.setText(R.string.system_gpu_driver)
85 description.text = ""
86 version.text = ""
87 version.visibility = View.GONE
88 description.visibility = View.GONE
89 buttonDelete.visibility = View.GONE
90 } else {
91 title.text = driver.name
92 version.text = driver.version
93 description.text = driver.description
94 version.visibility = View.VISIBLE
95 description.visibility = View.VISIBLE
96 buttonDelete.visibility = View.VISIBLE
97 }
98 }
99 }
100 }
101
102 private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
103 override fun areItemsTheSame(
104 oldItem: Pair<String, GpuDriverMetadata>,
105 newItem: Pair<String, GpuDriverMetadata>
106 ): Boolean {
107 return oldItem.first == newItem.first
108 }
109
110 override fun areContentsTheSame(
111 oldItem: Pair<String, GpuDriverMetadata>,
112 newItem: Pair<String, GpuDriverMetadata>
113 ): Boolean {
114 return oldItem.second == newItem.second
115 }
116 }
117}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
new file mode 100644
index 000000000..df21d74b2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -0,0 +1,186 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.os.Bundle
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import androidx.activity.result.contract.ActivityResultContracts
11import androidx.core.view.ViewCompat
12import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels
16import androidx.lifecycle.lifecycleScope
17import androidx.navigation.findNavController
18import androidx.recyclerview.widget.GridLayoutManager
19import com.google.android.material.transition.MaterialSharedAxis
20import kotlinx.coroutines.flow.collectLatest
21import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.adapters.DriverAdapter
24import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
25import org.yuzu.yuzu_emu.model.DriverViewModel
26import org.yuzu.yuzu_emu.model.HomeViewModel
27import org.yuzu.yuzu_emu.utils.FileUtil
28import org.yuzu.yuzu_emu.utils.GpuDriverHelper
29import java.io.File
30import java.io.IOException
31
32class DriverManagerFragment : Fragment() {
33 private var _binding: FragmentDriverManagerBinding? = null
34 private val binding get() = _binding!!
35
36 private val homeViewModel: HomeViewModel by activityViewModels()
37 private val driverViewModel: DriverViewModel by activityViewModels()
38
39 override fun onCreate(savedInstanceState: Bundle?) {
40 super.onCreate(savedInstanceState)
41 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
42 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
43 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
44 }
45
46 override fun onCreateView(
47 inflater: LayoutInflater,
48 container: ViewGroup?,
49 savedInstanceState: Bundle?
50 ): View {
51 _binding = FragmentDriverManagerBinding.inflate(inflater)
52 return binding.root
53 }
54
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 super.onViewCreated(view, savedInstanceState)
57 homeViewModel.setNavigationVisibility(visible = false, animated = true)
58 homeViewModel.setStatusBarShadeVisibility(visible = false)
59
60 if (!driverViewModel.isInteractionAllowed) {
61 DriversLoadingDialogFragment().show(
62 childFragmentManager,
63 DriversLoadingDialogFragment.TAG
64 )
65 }
66
67 binding.toolbarDrivers.setNavigationOnClickListener {
68 binding.root.findNavController().popBackStack()
69 }
70
71 binding.buttonInstall.setOnClickListener {
72 getDriver.launch(arrayOf("application/zip"))
73 }
74
75 binding.listDrivers.apply {
76 layoutManager = GridLayoutManager(
77 requireContext(),
78 resources.getInteger(R.integer.grid_columns)
79 )
80 adapter = DriverAdapter(driverViewModel)
81 }
82
83 viewLifecycleOwner.lifecycleScope.apply {
84 launch {
85 driverViewModel.driverList.collectLatest {
86 (binding.listDrivers.adapter as DriverAdapter).submitList(it)
87 }
88 }
89 launch {
90 driverViewModel.newDriverInstalled.collect {
91 if (_binding != null && it) {
92 (binding.listDrivers.adapter as DriverAdapter).apply {
93 notifyItemChanged(driverViewModel.previouslySelectedDriver)
94 notifyItemChanged(driverViewModel.selectedDriver)
95 driverViewModel.setNewDriverInstalled(false)
96 }
97 }
98 }
99 }
100 }
101
102 setInsets()
103 }
104
105 // Start installing requested driver
106 override fun onStop() {
107 super.onStop()
108 driverViewModel.onCloseDriverManager()
109 }
110
111 private fun setInsets() =
112 ViewCompat.setOnApplyWindowInsetsListener(
113 binding.root
114 ) { _: View, windowInsets: WindowInsetsCompat ->
115 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
116 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
117
118 val leftInsets = barInsets.left + cutoutInsets.left
119 val rightInsets = barInsets.right + cutoutInsets.right
120
121 val mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams
122 mlpAppBar.leftMargin = leftInsets
123 mlpAppBar.rightMargin = rightInsets
124 binding.toolbarDrivers.layoutParams = mlpAppBar
125
126 val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams
127 mlplistDrivers.leftMargin = leftInsets
128 mlplistDrivers.rightMargin = rightInsets
129 binding.listDrivers.layoutParams = mlplistDrivers
130
131 val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
132 val mlpFab =
133 binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
134 mlpFab.leftMargin = leftInsets + fabSpacing
135 mlpFab.rightMargin = rightInsets + fabSpacing
136 mlpFab.bottomMargin = barInsets.bottom + fabSpacing
137 binding.buttonInstall.layoutParams = mlpFab
138
139 binding.listDrivers.updatePadding(
140 bottom = barInsets.bottom +
141 resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
142 )
143
144 windowInsets
145 }
146
147 private val getDriver =
148 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
149 if (result == null) {
150 return@registerForActivityResult
151 }
152
153 IndeterminateProgressDialogFragment.newInstance(
154 requireActivity(),
155 R.string.installing_driver,
156 false
157 ) {
158 val driverPath =
159 "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
160 val driverFile = File(driverPath)
161
162 // Ignore file exceptions when a user selects an invalid zip
163 try {
164 if (!GpuDriverHelper.copyDriverToInternalStorage(result)) {
165 throw IOException("Driver failed validation!")
166 }
167 } catch (_: IOException) {
168 if (driverFile.exists()) {
169 driverFile.delete()
170 }
171 return@newInstance getString(R.string.select_gpu_driver_error)
172 }
173
174 val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
175 val driverInList =
176 driverViewModel.driverList.value.firstOrNull { it.second == driverData }
177 if (driverInList != null) {
178 return@newInstance getString(R.string.driver_already_installed)
179 } else {
180 driverViewModel.addDriver(Pair(driverPath, driverData))
181 driverViewModel.setNewDriverInstalled(true)
182 }
183 return@newInstance Any()
184 }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
185 }
186}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
new file mode 100644
index 000000000..f8c34346a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
@@ -0,0 +1,75 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import androidx.fragment.app.DialogFragment
12import androidx.fragment.app.activityViewModels
13import androidx.lifecycle.Lifecycle
14import androidx.lifecycle.lifecycleScope
15import androidx.lifecycle.repeatOnLifecycle
16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import kotlinx.coroutines.launch
18import org.yuzu.yuzu_emu.R
19import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
20import org.yuzu.yuzu_emu.model.DriverViewModel
21
22class DriversLoadingDialogFragment : DialogFragment() {
23 private val driverViewModel: DriverViewModel by activityViewModels()
24
25 private lateinit var binding: DialogProgressBarBinding
26
27 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
28 binding = DialogProgressBarBinding.inflate(layoutInflater)
29 binding.progressBar.isIndeterminate = true
30
31 isCancelable = false
32
33 return MaterialAlertDialogBuilder(requireContext())
34 .setTitle(R.string.loading)
35 .setView(binding.root)
36 .create()
37 }
38
39 override fun onCreateView(
40 inflater: LayoutInflater,
41 container: ViewGroup?,
42 savedInstanceState: Bundle?
43 ): View = binding.root
44
45 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
46 super.onViewCreated(view, savedInstanceState)
47 viewLifecycleOwner.lifecycleScope.apply {
48 launch {
49 repeatOnLifecycle(Lifecycle.State.RESUMED) {
50 driverViewModel.areDriversLoading.collect { checkForDismiss() }
51 }
52 }
53 launch {
54 repeatOnLifecycle(Lifecycle.State.RESUMED) {
55 driverViewModel.isDriverReady.collect { checkForDismiss() }
56 }
57 }
58 launch {
59 repeatOnLifecycle(Lifecycle.State.RESUMED) {
60 driverViewModel.isDeletingDrivers.collect { checkForDismiss() }
61 }
62 }
63 }
64 }
65
66 private fun checkForDismiss() {
67 if (driverViewModel.isInteractionAllowed) {
68 dismiss()
69 }
70 }
71
72 companion object {
73 const val TAG = "DriversLoadingDialogFragment"
74 }
75}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index e6ad2aa77..598a9d42b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
39import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
40import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers 41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.flow.collect
42import kotlinx.coroutines.flow.collectLatest 43import kotlinx.coroutines.flow.collectLatest
43import kotlinx.coroutines.launch 44import kotlinx.coroutines.launch
44import org.yuzu.yuzu_emu.HomeNavigationDirections 45import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -50,6 +51,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
50import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 51import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
51import org.yuzu.yuzu_emu.features.settings.model.IntSetting 52import org.yuzu.yuzu_emu.features.settings.model.IntSetting
52import org.yuzu.yuzu_emu.features.settings.model.Settings 53import org.yuzu.yuzu_emu.features.settings.model.Settings
54import org.yuzu.yuzu_emu.model.DriverViewModel
53import org.yuzu.yuzu_emu.model.Game 55import org.yuzu.yuzu_emu.model.Game
54import org.yuzu.yuzu_emu.model.EmulationViewModel 56import org.yuzu.yuzu_emu.model.EmulationViewModel
55import org.yuzu.yuzu_emu.overlay.InputOverlay 57import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -70,6 +72,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
70 private lateinit var game: Game 72 private lateinit var game: Game
71 73
72 private val emulationViewModel: EmulationViewModel by activityViewModels() 74 private val emulationViewModel: EmulationViewModel by activityViewModels()
75 private val driverViewModel: DriverViewModel by activityViewModels()
73 76
74 private var isInFoldableLayout = false 77 private var isInFoldableLayout = false
75 78
@@ -299,6 +302,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
299 } 302 }
300 } 303 }
301 } 304 }
305 launch {
306 repeatOnLifecycle(Lifecycle.State.RESUMED) {
307 driverViewModel.isDriverReady.collect {
308 if (it && !emulationState.isRunning) {
309 if (!DirectoryInitialization.areDirectoriesReady) {
310 DirectoryInitialization.start()
311 }
312
313 updateScreenLayout()
314
315 emulationState.run(emulationActivity!!.isActivityRecreated)
316 }
317 }
318 }
319 }
302 } 320 }
303 } 321 }
304 322
@@ -332,17 +350,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
332 } 350 }
333 } 351 }
334 352
335 override fun onResume() {
336 super.onResume()
337 if (!DirectoryInitialization.areDirectoriesReady) {
338 DirectoryInitialization.start()
339 }
340
341 updateScreenLayout()
342
343 emulationState.run(emulationActivity!!.isActivityRecreated)
344 }
345
346 override fun onPause() { 353 override fun onPause() {
347 if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { 354 if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
348 emulationState.pause() 355 emulationState.pause()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 8923c0ea2..fd9785075 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.fragments
5 5
6import android.Manifest 6import android.Manifest
7import android.content.ActivityNotFoundException 7import android.content.ActivityNotFoundException
8import android.content.DialogInterface
9import android.content.Intent 8import android.content.Intent
10import android.content.pm.PackageManager 9import android.content.pm.PackageManager
11import android.os.Bundle 10import android.os.Bundle
@@ -28,7 +27,6 @@ import androidx.fragment.app.activityViewModels
28import androidx.navigation.findNavController 27import androidx.navigation.findNavController
29import androidx.navigation.fragment.findNavController 28import androidx.navigation.fragment.findNavController
30import androidx.recyclerview.widget.LinearLayoutManager 29import androidx.recyclerview.widget.LinearLayoutManager
31import com.google.android.material.dialog.MaterialAlertDialogBuilder
32import com.google.android.material.transition.MaterialSharedAxis 30import com.google.android.material.transition.MaterialSharedAxis
33import org.yuzu.yuzu_emu.BuildConfig 31import org.yuzu.yuzu_emu.BuildConfig
34import org.yuzu.yuzu_emu.HomeNavigationDirections 32import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -37,6 +35,7 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 35import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
38import org.yuzu.yuzu_emu.features.DocumentProvider 36import org.yuzu.yuzu_emu.features.DocumentProvider
39import org.yuzu.yuzu_emu.features.settings.model.Settings 37import org.yuzu.yuzu_emu.features.settings.model.Settings
38import org.yuzu.yuzu_emu.model.DriverViewModel
40import org.yuzu.yuzu_emu.model.HomeSetting 39import org.yuzu.yuzu_emu.model.HomeSetting
41import org.yuzu.yuzu_emu.model.HomeViewModel 40import org.yuzu.yuzu_emu.model.HomeViewModel
42import org.yuzu.yuzu_emu.ui.main.MainActivity 41import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -50,6 +49,7 @@ class HomeSettingsFragment : Fragment() {
50 private lateinit var mainActivity: MainActivity 49 private lateinit var mainActivity: MainActivity
51 50
52 private val homeViewModel: HomeViewModel by activityViewModels() 51 private val homeViewModel: HomeViewModel by activityViewModels()
52 private val driverViewModel: DriverViewModel by activityViewModels()
53 53
54 override fun onCreate(savedInstanceState: Bundle?) { 54 override fun onCreate(savedInstanceState: Bundle?) {
55 super.onCreate(savedInstanceState) 55 super.onCreate(savedInstanceState)
@@ -107,13 +107,17 @@ class HomeSettingsFragment : Fragment() {
107 ) 107 )
108 add( 108 add(
109 HomeSetting( 109 HomeSetting(
110 R.string.install_gpu_driver, 110 R.string.gpu_driver_manager,
111 R.string.install_gpu_driver_description, 111 R.string.install_gpu_driver_description,
112 R.drawable.ic_exit, 112 R.drawable.ic_build,
113 { driverInstaller() }, 113 {
114 binding.root.findNavController()
115 .navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
116 },
114 { GpuDriverHelper.supportsCustomDriverLoading() }, 117 { GpuDriverHelper.supportsCustomDriverLoading() },
115 R.string.custom_driver_not_supported, 118 R.string.custom_driver_not_supported,
116 R.string.custom_driver_not_supported_description 119 R.string.custom_driver_not_supported_description,
120 driverViewModel.selectedDriverMetadata
117 ) 121 )
118 ) 122 )
119 add( 123 add(
@@ -292,31 +296,6 @@ class HomeSettingsFragment : Fragment() {
292 } 296 }
293 } 297 }
294 298
295 private fun driverInstaller() {
296 // Get the driver name for the dialog message.
297 var driverName = GpuDriverHelper.customDriverName
298 if (driverName == null) {
299 driverName = getString(R.string.system_gpu_driver)
300 }
301
302 MaterialAlertDialogBuilder(requireContext())
303 .setTitle(getString(R.string.select_gpu_driver_title))
304 .setMessage(driverName)
305 .setNegativeButton(android.R.string.cancel, null)
306 .setNeutralButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
307 GpuDriverHelper.installDefaultDriver(requireContext())
308 Toast.makeText(
309 requireContext(),
310 R.string.select_gpu_driver_use_default,
311 Toast.LENGTH_SHORT
312 ).show()
313 }
314 .setPositiveButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
315 mainActivity.getDriver.launch(arrayOf("application/zip"))
316 }
317 .show()
318 }
319
320 private fun shareLog() { 299 private fun shareLog() {
321 val file = DocumentFile.fromSingleUri( 300 val file = DocumentFile.fromSingleUri(
322 mainActivity, 301 mainActivity,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index f128deda8..7e467814d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -10,8 +10,8 @@ import android.view.View
10import android.view.ViewGroup 10import android.view.ViewGroup
11import android.widget.Toast 11import android.widget.Toast
12import androidx.appcompat.app.AlertDialog 12import androidx.appcompat.app.AlertDialog
13import androidx.appcompat.app.AppCompatActivity
14import androidx.fragment.app.DialogFragment 13import androidx.fragment.app.DialogFragment
14import androidx.fragment.app.FragmentActivity
15import androidx.fragment.app.activityViewModels 15import androidx.fragment.app.activityViewModels
16import androidx.lifecycle.Lifecycle 16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.ViewModelProvider 17import androidx.lifecycle.ViewModelProvider
@@ -78,6 +78,10 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
78 requireActivity().supportFragmentManager, 78 requireActivity().supportFragmentManager,
79 MessageDialogFragment.TAG 79 MessageDialogFragment.TAG
80 ) 80 )
81
82 else -> {
83 // Do nothing
84 }
81 } 85 }
82 taskViewModel.clear() 86 taskViewModel.clear()
83 } 87 }
@@ -115,7 +119,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
115 private const val CANCELLABLE = "Cancellable" 119 private const val CANCELLABLE = "Cancellable"
116 120
117 fun newInstance( 121 fun newInstance(
118 activity: AppCompatActivity, 122 activity: FragmentActivity,
119 titleId: Int, 123 titleId: Int,
120 cancellable: Boolean = false, 124 cancellable: Boolean = false,
121 task: () -> Any 125 task: () -> Any
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
new file mode 100644
index 000000000..62945ad65
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -0,0 +1,158 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.ViewModel
7import androidx.lifecycle.viewModelScope
8import kotlinx.coroutines.Dispatchers
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.launch
12import kotlinx.coroutines.withContext
13import org.yuzu.yuzu_emu.R
14import org.yuzu.yuzu_emu.YuzuApplication
15import org.yuzu.yuzu_emu.utils.FileUtil
16import org.yuzu.yuzu_emu.utils.GpuDriverHelper
17import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
18import java.io.BufferedOutputStream
19import java.io.File
20
21class DriverViewModel : ViewModel() {
22 private val _areDriversLoading = MutableStateFlow(false)
23 val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading
24
25 private val _isDriverReady = MutableStateFlow(true)
26 val isDriverReady: StateFlow<Boolean> get() = _isDriverReady
27
28 private val _isDeletingDrivers = MutableStateFlow(false)
29 val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers
30
31 private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>())
32 val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
33
34 var previouslySelectedDriver = 0
35 var selectedDriver = -1
36
37 private val _selectedDriverMetadata =
38 MutableStateFlow(
39 GpuDriverHelper.customDriverData.name
40 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
41 )
42 val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata
43
44 private val _newDriverInstalled = MutableStateFlow(false)
45 val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
46
47 val driversToDelete = mutableListOf<String>()
48
49 val isInteractionAllowed
50 get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value
51
52 init {
53 _areDriversLoading.value = true
54 viewModelScope.launch {
55 withContext(Dispatchers.IO) {
56 val drivers = GpuDriverHelper.getDrivers()
57 val currentDriverMetadata = GpuDriverHelper.customDriverData
58 for (i in drivers.indices) {
59 if (drivers[i].second == currentDriverMetadata) {
60 setSelectedDriverIndex(i)
61 break
62 }
63 }
64
65 // If a user had installed a driver before the manager was implemented, this zips
66 // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
67 // be indexed and exported as expected.
68 if (selectedDriver == -1) {
69 val driverToSave =
70 File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
71 driverToSave.createNewFile()
72 FileUtil.zipFromInternalStorage(
73 File(GpuDriverHelper.driverInstallationPath!!),
74 GpuDriverHelper.driverInstallationPath!!,
75 BufferedOutputStream(driverToSave.outputStream())
76 )
77 drivers.add(Pair(driverToSave.path, currentDriverMetadata))
78 setSelectedDriverIndex(drivers.size - 1)
79 }
80
81 _driverList.value = drivers
82 _areDriversLoading.value = false
83 }
84 }
85 }
86
87 fun setSelectedDriverIndex(value: Int) {
88 if (selectedDriver != -1) {
89 previouslySelectedDriver = selectedDriver
90 }
91 selectedDriver = value
92 }
93
94 fun setNewDriverInstalled(value: Boolean) {
95 _newDriverInstalled.value = value
96 }
97
98 fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
99 val driverIndex = _driverList.value.indexOfFirst { it == driverData }
100 if (driverIndex == -1) {
101 setSelectedDriverIndex(_driverList.value.size)
102 _driverList.value.add(driverData)
103 _selectedDriverMetadata.value = driverData.second.name
104 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
105 } else {
106 setSelectedDriverIndex(driverIndex)
107 }
108 }
109
110 fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
111 _driverList.value.remove(driverData)
112 }
113
114 fun onCloseDriverManager() {
115 _isDeletingDrivers.value = true
116 viewModelScope.launch {
117 withContext(Dispatchers.IO) {
118 driversToDelete.forEach {
119 val driver = File(it)
120 if (driver.exists()) {
121 driver.delete()
122 }
123 }
124 driversToDelete.clear()
125 _isDeletingDrivers.value = false
126 }
127 }
128
129 if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) {
130 return
131 }
132
133 _isDriverReady.value = false
134 viewModelScope.launch {
135 withContext(Dispatchers.IO) {
136 if (selectedDriver == 0) {
137 GpuDriverHelper.installDefaultDriver()
138 setDriverReady()
139 return@withContext
140 }
141
142 val driverToInstall = File(driverList.value[selectedDriver].first)
143 if (driverToInstall.exists()) {
144 GpuDriverHelper.installCustomDriver(driverToInstall)
145 } else {
146 GpuDriverHelper.installDefaultDriver()
147 }
148 setDriverReady()
149 }
150 }
151 }
152
153 private fun setDriverReady() {
154 _isDriverReady.value = true
155 _selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name
156 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
157 }
158}
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 0fa5df5e5..233aa4101 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
@@ -29,12 +29,10 @@ import androidx.navigation.fragment.NavHostFragment
29import androidx.navigation.ui.setupWithNavController 29import androidx.navigation.ui.setupWithNavController
30import androidx.preference.PreferenceManager 30import androidx.preference.PreferenceManager
31import com.google.android.material.color.MaterialColors 31import com.google.android.material.color.MaterialColors
32import com.google.android.material.dialog.MaterialAlertDialogBuilder
33import com.google.android.material.navigation.NavigationBarView 32import com.google.android.material.navigation.NavigationBarView
34import kotlinx.coroutines.CoroutineScope 33import kotlinx.coroutines.CoroutineScope
35import java.io.File 34import java.io.File
36import java.io.FilenameFilter 35import java.io.FilenameFilter
37import java.io.IOException
38import kotlinx.coroutines.Dispatchers 36import kotlinx.coroutines.Dispatchers
39import kotlinx.coroutines.launch 37import kotlinx.coroutines.launch
40import kotlinx.coroutines.withContext 38import kotlinx.coroutines.withContext
@@ -43,7 +41,6 @@ import org.yuzu.yuzu_emu.NativeLibrary
43import org.yuzu.yuzu_emu.R 41import org.yuzu.yuzu_emu.R
44import org.yuzu.yuzu_emu.activities.EmulationActivity 42import org.yuzu.yuzu_emu.activities.EmulationActivity
45import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 43import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
46import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
47import org.yuzu.yuzu_emu.features.DocumentProvider 44import org.yuzu.yuzu_emu.features.DocumentProvider
48import org.yuzu.yuzu_emu.features.settings.model.Settings 45import org.yuzu.yuzu_emu.features.settings.model.Settings
49import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 46import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
@@ -343,11 +340,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
343 340
344 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 341 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
345 if (FileUtil.copyUriToInternalStorage( 342 if (FileUtil.copyUriToInternalStorage(
346 applicationContext,
347 result, 343 result,
348 dstPath, 344 dstPath,
349 "prod.keys" 345 "prod.keys"
350 ) 346 ) != null
351 ) { 347 ) {
352 if (NativeLibrary.reloadKeys()) { 348 if (NativeLibrary.reloadKeys()) {
353 Toast.makeText( 349 Toast.makeText(
@@ -446,11 +442,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
446 442
447 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 443 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
448 if (FileUtil.copyUriToInternalStorage( 444 if (FileUtil.copyUriToInternalStorage(
449 applicationContext,
450 result, 445 result,
451 dstPath, 446 dstPath,
452 "key_retail.bin" 447 "key_retail.bin"
453 ) 448 ) != null
454 ) { 449 ) {
455 if (NativeLibrary.reloadKeys()) { 450 if (NativeLibrary.reloadKeys()) {
456 Toast.makeText( 451 Toast.makeText(
@@ -469,59 +464,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
469 } 464 }
470 } 465 }
471 466
472 val getDriver =
473 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
474 if (result == null) {
475 return@registerForActivityResult
476 }
477
478 val takeFlags =
479 Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
480 contentResolver.takePersistableUriPermission(
481 result,
482 takeFlags
483 )
484
485 val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
486 progressBinding.progressBar.isIndeterminate = true
487 val installationDialog = MaterialAlertDialogBuilder(this)
488 .setTitle(R.string.installing_driver)
489 .setView(progressBinding.root)
490 .show()
491
492 lifecycleScope.launch {
493 withContext(Dispatchers.IO) {
494 // Ignore file exceptions when a user selects an invalid zip
495 try {
496 GpuDriverHelper.installCustomDriver(applicationContext, result)
497 } catch (_: IOException) {
498 }
499
500 withContext(Dispatchers.Main) {
501 installationDialog.dismiss()
502
503 val driverName = GpuDriverHelper.customDriverName
504 if (driverName != null) {
505 Toast.makeText(
506 applicationContext,
507 getString(
508 R.string.select_gpu_driver_install_success,
509 driverName
510 ),
511 Toast.LENGTH_SHORT
512 ).show()
513 } else {
514 Toast.makeText(
515 applicationContext,
516 R.string.select_gpu_driver_error,
517 Toast.LENGTH_LONG
518 ).show()
519 }
520 }
521 }
522 }
523 }
524
525 val installGameUpdate = registerForActivityResult( 467 val installGameUpdate = registerForActivityResult(
526 ActivityResultContracts.OpenMultipleDocuments() 468 ActivityResultContracts.OpenMultipleDocuments()
527 ) { documents: List<Uri> -> 469 ) { documents: List<Uri> ->
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
index cf226ad94..eafcf9e42 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
@@ -7,7 +7,6 @@ import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import java.io.File 8import java.io.File
9import java.util.* 9import java.util.*
10import org.yuzu.yuzu_emu.YuzuApplication
11import org.yuzu.yuzu_emu.model.MinimalDocumentFile 10import org.yuzu.yuzu_emu.model.MinimalDocumentFile
12 11
13class DocumentsTree { 12class DocumentsTree {
@@ -22,7 +21,7 @@ class DocumentsTree {
22 21
23 fun openContentUri(filepath: String, openMode: String?): Int { 22 fun openContentUri(filepath: String, openMode: String?): Int {
24 val node = resolvePath(filepath) ?: return -1 23 val node = resolvePath(filepath) ?: return -1
25 return FileUtil.openContentUri(YuzuApplication.appContext, node.uri.toString(), openMode) 24 return FileUtil.openContentUri(node.uri.toString(), openMode)
26 } 25 }
27 26
28 fun getFileSize(filepath: String): Long { 27 fun getFileSize(filepath: String): Long {
@@ -30,7 +29,7 @@ class DocumentsTree {
30 return if (node == null || node.isDirectory) { 29 return if (node == null || node.isDirectory) {
31 0 30 0
32 } else { 31 } else {
33 FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) 32 FileUtil.getFileSize(node.uri.toString())
34 } 33 }
35 } 34 }
36 35
@@ -67,7 +66,7 @@ class DocumentsTree {
67 * @param parent parent node of this level 66 * @param parent parent node of this level
68 */ 67 */
69 private fun structTree(parent: DocumentsNode) { 68 private fun structTree(parent: DocumentsNode) {
70 val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!) 69 val documents = FileUtil.listFiles(parent.uri!!)
71 for (document in documents) { 70 for (document in documents) {
72 val node = DocumentsNode(document) 71 val node = DocumentsNode(document)
73 node.parent = parent 72 node.parent = parent
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 c3f53f1c5..5ee74a52c 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
@@ -3,7 +3,6 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context
7import android.database.Cursor 6import android.database.Cursor
8import android.net.Uri 7import android.net.Uri
9import android.provider.DocumentsContract 8import android.provider.DocumentsContract
@@ -11,7 +10,6 @@ import androidx.documentfile.provider.DocumentFile
11import kotlinx.coroutines.flow.StateFlow 10import kotlinx.coroutines.flow.StateFlow
12import java.io.BufferedInputStream 11import java.io.BufferedInputStream
13import java.io.File 12import java.io.File
14import java.io.FileOutputStream
15import java.io.IOException 13import java.io.IOException
16import java.io.InputStream 14import java.io.InputStream
17import java.net.URLDecoder 15import java.net.URLDecoder
@@ -21,6 +19,8 @@ import org.yuzu.yuzu_emu.YuzuApplication
21import org.yuzu.yuzu_emu.model.MinimalDocumentFile 19import org.yuzu.yuzu_emu.model.MinimalDocumentFile
22import org.yuzu.yuzu_emu.model.TaskState 20import org.yuzu.yuzu_emu.model.TaskState
23import java.io.BufferedOutputStream 21import java.io.BufferedOutputStream
22import java.lang.NullPointerException
23import java.nio.charset.StandardCharsets
24import java.util.zip.ZipOutputStream 24import java.util.zip.ZipOutputStream
25 25
26object FileUtil { 26object FileUtil {
@@ -29,6 +29,8 @@ object FileUtil {
29 const val APPLICATION_OCTET_STREAM = "application/octet-stream" 29 const val APPLICATION_OCTET_STREAM = "application/octet-stream"
30 const val TEXT_PLAIN = "text/plain" 30 const val TEXT_PLAIN = "text/plain"
31 31
32 private val context get() = YuzuApplication.appContext
33
32 /** 34 /**
33 * Create a file from directory with filename. 35 * Create a file from directory with filename.
34 * @param context Application context 36 * @param context Application context
@@ -36,11 +38,11 @@ object FileUtil {
36 * @param filename file display name. 38 * @param filename file display name.
37 * @return boolean 39 * @return boolean
38 */ 40 */
39 fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? { 41 fun createFile(directory: String?, filename: String): DocumentFile? {
40 var decodedFilename = filename 42 var decodedFilename = filename
41 try { 43 try {
42 val directoryUri = Uri.parse(directory) 44 val directoryUri = Uri.parse(directory)
43 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null 45 val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
44 decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD) 46 decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
45 var mimeType = APPLICATION_OCTET_STREAM 47 var mimeType = APPLICATION_OCTET_STREAM
46 if (decodedFilename.endsWith(".txt")) { 48 if (decodedFilename.endsWith(".txt")) {
@@ -56,16 +58,15 @@ object FileUtil {
56 58
57 /** 59 /**
58 * Create a directory from directory with filename. 60 * Create a directory from directory with filename.
59 * @param context Application context
60 * @param directory parent path for directory. 61 * @param directory parent path for directory.
61 * @param directoryName directory display name. 62 * @param directoryName directory display name.
62 * @return boolean 63 * @return boolean
63 */ 64 */
64 fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? { 65 fun createDir(directory: String?, directoryName: String?): DocumentFile? {
65 var decodedDirectoryName = directoryName 66 var decodedDirectoryName = directoryName
66 try { 67 try {
67 val directoryUri = Uri.parse(directory) 68 val directoryUri = Uri.parse(directory)
68 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null 69 val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
69 decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD) 70 decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
70 val isExist = parent.findFile(decodedDirectoryName) 71 val isExist = parent.findFile(decodedDirectoryName)
71 return isExist ?: parent.createDirectory(decodedDirectoryName) 72 return isExist ?: parent.createDirectory(decodedDirectoryName)
@@ -77,13 +78,12 @@ object FileUtil {
77 78
78 /** 79 /**
79 * Open content uri and return file descriptor to JNI. 80 * Open content uri and return file descriptor to JNI.
80 * @param context Application context
81 * @param path Native content uri path 81 * @param path Native content uri path
82 * @param openMode will be one of "r", "r", "rw", "wa", "rwa" 82 * @param openMode will be one of "r", "r", "rw", "wa", "rwa"
83 * @return file descriptor 83 * @return file descriptor
84 */ 84 */
85 @JvmStatic 85 @JvmStatic
86 fun openContentUri(context: Context, path: String, openMode: String?): Int { 86 fun openContentUri(path: String, openMode: String?): Int {
87 try { 87 try {
88 val uri = Uri.parse(path) 88 val uri = Uri.parse(path)
89 val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!) 89 val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
@@ -103,11 +103,10 @@ object FileUtil {
103 /** 103 /**
104 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow 104 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
105 * This function will be faster than DoucmentFile.listFiles 105 * This function will be faster than DoucmentFile.listFiles
106 * @param context Application context
107 * @param uri Directory uri. 106 * @param uri Directory uri.
108 * @return CheapDocument lists. 107 * @return CheapDocument lists.
109 */ 108 */
110 fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> { 109 fun listFiles(uri: Uri): Array<MinimalDocumentFile> {
111 val resolver = context.contentResolver 110 val resolver = context.contentResolver
112 val columns = arrayOf( 111 val columns = arrayOf(
113 DocumentsContract.Document.COLUMN_DOCUMENT_ID, 112 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -145,7 +144,7 @@ object FileUtil {
145 * @param path Native content uri path 144 * @param path Native content uri path
146 * @return bool 145 * @return bool
147 */ 146 */
148 fun exists(context: Context, path: String?): Boolean { 147 fun exists(path: String?): Boolean {
149 var c: Cursor? = null 148 var c: Cursor? = null
150 try { 149 try {
151 val mUri = Uri.parse(path) 150 val mUri = Uri.parse(path)
@@ -165,7 +164,7 @@ object FileUtil {
165 * @param path content uri path 164 * @param path content uri path
166 * @return bool 165 * @return bool
167 */ 166 */
168 fun isDirectory(context: Context, path: String): Boolean { 167 fun isDirectory(path: String): Boolean {
169 val resolver = context.contentResolver 168 val resolver = context.contentResolver
170 val columns = arrayOf( 169 val columns = arrayOf(
171 DocumentsContract.Document.COLUMN_MIME_TYPE 170 DocumentsContract.Document.COLUMN_MIME_TYPE
@@ -210,10 +209,10 @@ object FileUtil {
210 return filename 209 return filename
211 } 210 }
212 211
213 fun getFilesName(context: Context, path: String): Array<String> { 212 fun getFilesName(path: String): Array<String> {
214 val uri = Uri.parse(path) 213 val uri = Uri.parse(path)
215 val files: MutableList<String> = ArrayList() 214 val files: MutableList<String> = ArrayList()
216 for (file in listFiles(context, uri)) { 215 for (file in listFiles(uri)) {
217 files.add(file.filename) 216 files.add(file.filename)
218 } 217 }
219 return files.toTypedArray() 218 return files.toTypedArray()
@@ -225,7 +224,7 @@ object FileUtil {
225 * @return long file size 224 * @return long file size
226 */ 225 */
227 @JvmStatic 226 @JvmStatic
228 fun getFileSize(context: Context, path: String): Long { 227 fun getFileSize(path: String): Long {
229 val resolver = context.contentResolver 228 val resolver = context.contentResolver
230 val columns = arrayOf( 229 val columns = arrayOf(
231 DocumentsContract.Document.COLUMN_SIZE 230 DocumentsContract.Document.COLUMN_SIZE
@@ -245,44 +244,38 @@ object FileUtil {
245 return size 244 return size
246 } 245 }
247 246
247 /**
248 * Creates an input stream with a given [Uri] and copies its data to the given path. This will
249 * overwrite any pre-existing files.
250 *
251 * @param sourceUri The [Uri] to copy data from
252 * @param destinationParentPath Destination directory
253 * @param destinationFilename Optionally renames the file once copied
254 */
248 fun copyUriToInternalStorage( 255 fun copyUriToInternalStorage(
249 context: Context, 256 sourceUri: Uri,
250 sourceUri: Uri?,
251 destinationParentPath: String, 257 destinationParentPath: String,
252 destinationFilename: String 258 destinationFilename: String = ""
253 ): Boolean { 259 ): File? =
254 var input: InputStream? = null
255 var output: FileOutputStream? = null
256 try { 260 try {
257 input = context.contentResolver.openInputStream(sourceUri!!) 261 val fileName =
258 output = FileOutputStream("$destinationParentPath/$destinationFilename") 262 if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename"
259 val buffer = ByteArray(1024) 263 val inputStream = context.contentResolver.openInputStream(sourceUri)!!
260 var len: Int 264
261 while (input!!.read(buffer).also { len = it } != -1) { 265 val destinationFile = File("$destinationParentPath$fileName")
262 output.write(buffer, 0, len) 266 if (destinationFile.exists()) {
263 } 267 destinationFile.delete()
264 output.flush()
265 return true
266 } catch (e: Exception) {
267 Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
268 } finally {
269 if (input != null) {
270 try {
271 input.close()
272 } catch (e: IOException) {
273 Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
274 }
275 } 268 }
276 if (output != null) { 269
277 try { 270 destinationFile.outputStream().use { fos ->
278 output.close() 271 inputStream.use { it.copyTo(fos) }
279 } catch (e: IOException) {
280 Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
281 }
282 } 272 }
273 destinationFile
274 } catch (e: IOException) {
275 null
276 } catch (e: NullPointerException) {
277 null
283 } 278 }
284 return false
285 }
286 279
287 /** 280 /**
288 * Extracts the given zip file into the given directory. 281 * Extracts the given zip file into the given directory.
@@ -368,4 +361,12 @@ object FileUtil {
368 return fileName.substring(fileName.lastIndexOf(".") + 1) 361 return fileName.substring(fileName.lastIndexOf(".") + 1)
369 .lowercase() 362 .lowercase()
370 } 363 }
364
365 @Throws(IOException::class)
366 fun getStringFromFile(file: File): String =
367 String(file.readBytes(), StandardCharsets.UTF_8)
368
369 @Throws(IOException::class)
370 fun getStringFromInputStream(stream: InputStream): String =
371 String(stream.readBytes(), StandardCharsets.UTF_8)
371} 372}
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 e0ee29c9b..9001ca9ab 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
@@ -30,7 +30,7 @@ object GameHelper {
30 // Ensure keys are loaded so that ROM metadata can be decrypted. 30 // Ensure keys are loaded so that ROM metadata can be decrypted.
31 NativeLibrary.reloadKeys() 31 NativeLibrary.reloadKeys()
32 32
33 addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3) 33 addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
34 34
35 // Cache list of games found on disk 35 // Cache list of games found on disk
36 val serializedGames = mutableSetOf<String>() 36 val serializedGames = mutableSetOf<String>()
@@ -58,7 +58,7 @@ object GameHelper {
58 if (it.isDirectory) { 58 if (it.isDirectory) {
59 addGamesRecursive( 59 addGamesRecursive(
60 games, 60 games,
61 FileUtil.listFiles(YuzuApplication.appContext, it.uri), 61 FileUtil.listFiles(it.uri),
62 depth - 1 62 depth - 1
63 ) 63 )
64 } else { 64 } else {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
index 1d4695a2a..f6882ce6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -3,64 +3,33 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context
7import android.net.Uri 6import android.net.Uri
7import android.os.Build
8import java.io.BufferedInputStream 8import java.io.BufferedInputStream
9import java.io.File 9import java.io.File
10import java.io.FileInputStream
11import java.io.FileOutputStream
12import java.io.IOException 10import java.io.IOException
13import java.util.zip.ZipInputStream
14import org.yuzu.yuzu_emu.NativeLibrary 11import org.yuzu.yuzu_emu.NativeLibrary
15import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage 12import org.yuzu.yuzu_emu.YuzuApplication
13import java.util.zip.ZipException
14import java.util.zip.ZipFile
16 15
17object GpuDriverHelper { 16object GpuDriverHelper {
18 private const val META_JSON_FILENAME = "meta.json" 17 private const val META_JSON_FILENAME = "meta.json"
19 private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
20 private var fileRedirectionPath: String? = null 18 private var fileRedirectionPath: String? = null
21 private var driverInstallationPath: String? = null 19 var driverInstallationPath: String? = null
22 private var hookLibPath: String? = null 20 private var hookLibPath: String? = null
23 21
24 @Throws(IOException::class) 22 val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/"
25 private fun unzip(zipFilePath: String, destDir: String) {
26 val dir = File(destDir)
27
28 // Create output directory if it doesn't exist
29 if (!dir.exists()) dir.mkdirs()
30
31 // Unpack the files.
32 val inputStream = FileInputStream(zipFilePath)
33 val zis = ZipInputStream(BufferedInputStream(inputStream))
34 val buffer = ByteArray(1024)
35 var ze = zis.nextEntry
36 while (ze != null) {
37 val newFile = File(destDir, ze.name)
38 val canonicalPath = newFile.canonicalPath
39 if (!canonicalPath.startsWith(destDir + ze.name)) {
40 throw SecurityException("Zip file attempted path traversal! " + ze.name)
41 }
42
43 newFile.parentFile!!.mkdirs()
44 val fos = FileOutputStream(newFile)
45 var len: Int
46 while (zis.read(buffer).also { len = it } > 0) {
47 fos.write(buffer, 0, len)
48 }
49 fos.close()
50 zis.closeEntry()
51 ze = zis.nextEntry
52 }
53 zis.closeEntry()
54 }
55 23
56 fun initializeDriverParameters(context: Context) { 24 fun initializeDriverParameters() {
57 try { 25 try {
58 // Initialize the file redirection directory. 26 // Initialize the file redirection directory.
59 fileRedirectionPath = 27 fileRedirectionPath = YuzuApplication.appContext
60 context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/" 28 .getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
61 29
62 // Initialize the driver installation directory. 30 // Initialize the driver installation directory.
63 driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/" 31 driverInstallationPath = YuzuApplication.appContext
32 .filesDir.canonicalPath + "/gpu_driver/"
64 } catch (e: IOException) { 33 } catch (e: IOException) {
65 throw RuntimeException(e) 34 throw RuntimeException(e)
66 } 35 }
@@ -69,68 +38,169 @@ object GpuDriverHelper {
69 initializeDirectories() 38 initializeDirectories()
70 39
71 // Initialize hook libraries directory. 40 // Initialize hook libraries directory.
72 hookLibPath = context.applicationInfo.nativeLibraryDir + "/" 41 hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
73 42
74 // Initialize GPU driver. 43 // Initialize GPU driver.
75 NativeLibrary.initializeGpuDriver( 44 NativeLibrary.initializeGpuDriver(
76 hookLibPath, 45 hookLibPath,
77 driverInstallationPath, 46 driverInstallationPath,
78 customDriverLibraryName, 47 customDriverData.libraryName,
79 fileRedirectionPath 48 fileRedirectionPath
80 ) 49 )
81 } 50 }
82 51
83 fun installDefaultDriver(context: Context) { 52 fun getDrivers(): MutableList<Pair<String, GpuDriverMetadata>> {
53 val driverZips = File(driverStoragePath).listFiles()
54 val drivers: MutableList<Pair<String, GpuDriverMetadata>> =
55 driverZips
56 ?.mapNotNull {
57 val metadata = getMetadataFromZip(it)
58 metadata.name?.let { _ -> Pair(it.path, metadata) }
59 }
60 ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
61 ?.distinct()
62 ?.toMutableList() ?: mutableListOf()
63
64 // TODO: Get system driver information
65 drivers.add(0, Pair("", GpuDriverMetadata()))
66 return drivers
67 }
68
69 fun installDefaultDriver() {
84 // Removing the installed driver will result in the backend using the default system driver. 70 // Removing the installed driver will result in the backend using the default system driver.
85 val driverInstallationDir = File(driverInstallationPath!!) 71 File(driverInstallationPath!!).deleteRecursively()
86 deleteRecursive(driverInstallationDir) 72 initializeDriverParameters()
87 initializeDriverParameters(context) 73 }
74
75 fun copyDriverToInternalStorage(driverUri: Uri): Boolean {
76 // Ensure we have directories.
77 initializeDirectories()
78
79 // Copy the zip file URI to user data
80 val copiedFile =
81 FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
82
83 // Validate driver
84 val metadata = getMetadataFromZip(copiedFile)
85 if (metadata.name == null) {
86 copiedFile.delete()
87 return false
88 }
89
90 if (metadata.minApi > Build.VERSION.SDK_INT) {
91 copiedFile.delete()
92 return false
93 }
94 return true
88 } 95 }
89 96
90 fun installCustomDriver(context: Context, driverPathUri: Uri?) { 97 /**
98 * Copies driver zip into user data directory so that it can be exported along with
99 * other user data and also unzipped into the installation directory
100 */
101 fun installCustomDriver(driverUri: Uri): Boolean {
91 // Revert to system default in the event the specified driver is bad. 102 // Revert to system default in the event the specified driver is bad.
92 installDefaultDriver(context) 103 installDefaultDriver()
93 104
94 // Ensure we have directories. 105 // Ensure we have directories.
95 initializeDirectories() 106 initializeDirectories()
96 107
97 // Copy the zip file URI into our private storage. 108 // Copy the zip file URI to user data
98 copyUriToInternalStorage( 109 val copiedFile =
99 context, 110 FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
100 driverPathUri, 111
101 driverInstallationPath!!, 112 // Validate driver
102 DRIVER_INTERNAL_FILENAME 113 val metadata = getMetadataFromZip(copiedFile)
103 ) 114 if (metadata.name == null) {
115 copiedFile.delete()
116 return false
117 }
118
119 if (metadata.minApi > Build.VERSION.SDK_INT) {
120 copiedFile.delete()
121 return false
122 }
104 123
105 // Unzip the driver. 124 // Unzip the driver.
106 try { 125 try {
107 unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!) 126 FileUtil.unzipToInternalStorage(
127 BufferedInputStream(copiedFile.inputStream()),
128 File(driverInstallationPath!!)
129 )
108 } catch (e: SecurityException) { 130 } catch (e: SecurityException) {
109 return 131 return false
110 } 132 }
111 133
112 // Initialize the driver parameters. 134 // Initialize the driver parameters.
113 initializeDriverParameters(context) 135 initializeDriverParameters()
136
137 return true
114 } 138 }
115 139
116 external fun supportsCustomDriverLoading(): Boolean 140 /**
141 * Unzips driver into installation directory
142 */
143 fun installCustomDriver(driver: File): Boolean {
144 // Revert to system default in the event the specified driver is bad.
145 installDefaultDriver()
117 146
118 // Parse the custom driver metadata to retrieve the name. 147 // Ensure we have directories.
119 val customDriverName: String? 148 initializeDirectories()
120 get() { 149
121 val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME) 150 // Validate driver
122 return metadata.name 151 val metadata = getMetadataFromZip(driver)
152 if (metadata.name == null) {
153 driver.delete()
154 return false
123 } 155 }
124 156
125 // Parse the custom driver metadata to retrieve the library name. 157 // Unzip the driver to the private installation directory
126 private val customDriverLibraryName: String? 158 try {
127 get() { 159 FileUtil.unzipToInternalStorage(
128 // Parse the custom driver metadata to retrieve the library name. 160 BufferedInputStream(driver.inputStream()),
129 val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME) 161 File(driverInstallationPath!!)
130 return metadata.libraryName 162 )
163 } catch (e: SecurityException) {
164 return false
131 } 165 }
132 166
133 private fun initializeDirectories() { 167 // Initialize the driver parameters.
168 initializeDriverParameters()
169
170 return true
171 }
172
173 /**
174 * Takes in a zip file and reads the meta.json file for presentation to the UI
175 *
176 * @param driver Zip containing driver and meta.json file
177 * @return A non-null [GpuDriverMetadata] instance that may have null members
178 */
179 fun getMetadataFromZip(driver: File): GpuDriverMetadata {
180 try {
181 ZipFile(driver).use { zf ->
182 val entries = zf.entries()
183 while (entries.hasMoreElements()) {
184 val entry = entries.nextElement()
185 if (!entry.isDirectory && entry.name.lowercase().contains(".json")) {
186 zf.getInputStream(entry).use {
187 return GpuDriverMetadata(it, entry.size)
188 }
189 }
190 }
191 }
192 } catch (_: ZipException) {
193 }
194 return GpuDriverMetadata()
195 }
196
197 external fun supportsCustomDriverLoading(): Boolean
198
199 // Parse the custom driver metadata to retrieve the name.
200 val customDriverData: GpuDriverMetadata
201 get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
202
203 fun initializeDirectories() {
134 // Ensure the file redirection directory exists. 204 // Ensure the file redirection directory exists.
135 val fileRedirectionDir = File(fileRedirectionPath!!) 205 val fileRedirectionDir = File(fileRedirectionPath!!)
136 if (!fileRedirectionDir.exists()) { 206 if (!fileRedirectionDir.exists()) {
@@ -141,14 +211,10 @@ object GpuDriverHelper {
141 if (!driverInstallationDir.exists()) { 211 if (!driverInstallationDir.exists()) {
142 driverInstallationDir.mkdirs() 212 driverInstallationDir.mkdirs()
143 } 213 }
144 } 214 // Ensure the driver storage directory exists
145 215 val driverStorageDirectory = File(driverStoragePath)
146 private fun deleteRecursive(fileOrDirectory: File) { 216 if (!driverStorageDirectory.exists()) {
147 if (fileOrDirectory.isDirectory) { 217 driverStorageDirectory.mkdirs()
148 for (child in fileOrDirectory.listFiles()!!) {
149 deleteRecursive(child)
150 }
151 } 218 }
152 fileOrDirectory.delete()
153 } 219 }
154} 220}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
index a4e64070a..511a4171a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
@@ -4,29 +4,29 @@
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import java.io.IOException 6import java.io.IOException
7import java.nio.charset.StandardCharsets
8import java.nio.file.Files
9import java.nio.file.Paths
10import org.json.JSONException 7import org.json.JSONException
11import org.json.JSONObject 8import org.json.JSONObject
9import java.io.File
10import java.io.InputStream
12 11
13class GpuDriverMetadata(metadataFilePath: String) { 12class GpuDriverMetadata {
14 var name: String? = null 13 /**
15 var description: String? = null 14 * Tries to get driver metadata information from a meta.json [File]
16 var author: String? = null 15 *
17 var vendor: String? = null 16 * @param metadataFile meta.json file provided with a GPU driver
18 var driverVersion: String? = null 17 */
19 var minApi = 0 18 constructor(metadataFile: File) {
20 var libraryName: String? = null 19 if (metadataFile.length() > MAX_META_SIZE_BYTES) {
20 return
21 }
21 22
22 init {
23 try { 23 try {
24 val json = JSONObject(getStringFromFile(metadataFilePath)) 24 val json = JSONObject(FileUtil.getStringFromFile(metadataFile))
25 name = json.getString("name") 25 name = json.getString("name")
26 description = json.getString("description") 26 description = json.getString("description")
27 author = json.getString("author") 27 author = json.getString("author")
28 vendor = json.getString("vendor") 28 vendor = json.getString("vendor")
29 driverVersion = json.getString("driverVersion") 29 version = json.getString("driverVersion")
30 minApi = json.getInt("minApi") 30 minApi = json.getInt("minApi")
31 libraryName = json.getString("libraryName") 31 libraryName = json.getString("libraryName")
32 } catch (e: JSONException) { 32 } catch (e: JSONException) {
@@ -36,12 +36,84 @@ class GpuDriverMetadata(metadataFilePath: String) {
36 } 36 }
37 } 37 }
38 38
39 companion object { 39 /**
40 @Throws(IOException::class) 40 * Tries to get driver metadata information from an input stream that's intended to be
41 private fun getStringFromFile(filePath: String): String { 41 * from a zip file
42 val path = Paths.get(filePath) 42 *
43 val bytes = Files.readAllBytes(path) 43 * @param metadataStream ZipEntry input stream
44 return String(bytes, StandardCharsets.UTF_8) 44 * @param size Size of the file in bytes
45 */
46 constructor(metadataStream: InputStream, size: Long) {
47 if (size > MAX_META_SIZE_BYTES) {
48 return
45 } 49 }
50
51 try {
52 val json = JSONObject(FileUtil.getStringFromInputStream(metadataStream))
53 name = json.getString("name")
54 description = json.getString("description")
55 author = json.getString("author")
56 vendor = json.getString("vendor")
57 version = json.getString("driverVersion")
58 minApi = json.getInt("minApi")
59 libraryName = json.getString("libraryName")
60 } catch (e: JSONException) {
61 // JSON is malformed, ignore and treat as unsupported metadata.
62 } catch (e: IOException) {
63 // File is inaccessible, ignore and treat as unsupported metadata.
64 }
65 }
66
67 /**
68 * Creates an empty metadata instance
69 */
70 constructor()
71
72 override fun equals(other: Any?): Boolean {
73 if (other !is GpuDriverMetadata) {
74 return false
75 }
76
77 return other.name == name &&
78 other.description == description &&
79 other.author == author &&
80 other.vendor == vendor &&
81 other.version == version &&
82 other.minApi == minApi &&
83 other.libraryName == libraryName
84 }
85
86 override fun hashCode(): Int {
87 var result = name?.hashCode() ?: 0
88 result = 31 * result + (description?.hashCode() ?: 0)
89 result = 31 * result + (author?.hashCode() ?: 0)
90 result = 31 * result + (vendor?.hashCode() ?: 0)
91 result = 31 * result + (version?.hashCode() ?: 0)
92 result = 31 * result + minApi
93 result = 31 * result + (libraryName?.hashCode() ?: 0)
94 return result
95 }
96
97 override fun toString(): String =
98 """
99 Name - $name
100 Description - $description
101 Author - $author
102 Vendor - $vendor
103 Version - $version
104 Min API - $minApi
105 Library Name - $libraryName
106 """.trimMargin().trimIndent()
107
108 var name: String? = null
109 var description: String? = null
110 var author: String? = null
111 var vendor: String? = null
112 var version: String? = null
113 var minApi = 0
114 var libraryName: String? = null
115
116 companion object {
117 private const val MAX_META_SIZE_BYTES = 500000
46 } 118 }
47} 119}
diff --git a/src/android/app/src/main/res/drawable/ic_build.xml b/src/android/app/src/main/res/drawable/ic_build.xml
new file mode 100644
index 000000000..91d52f1b8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_build.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_delete.xml b/src/android/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 000000000..d26a79711
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
9</vector>
diff --git a/src/android/app/src/main/res/layout/card_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml
new file mode 100644
index 000000000..1dd9a6d7d
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_driver_option.xml
@@ -0,0 +1,89 @@
1<?xml version="1.0" encoding="utf-8"?>
2<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 style="?attr/materialCardViewOutlinedStyle"
6 android:layout_width="match_parent"
7 android:layout_height="wrap_content"
8 android:layout_marginHorizontal="16dp"
9 android:layout_marginVertical="12dp"
10 android:background="?attr/selectableItemBackground"
11 android:clickable="true"
12 android:focusable="true">
13
14 <LinearLayout
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:orientation="horizontal"
18 android:layout_gravity="center"
19 android:padding="16dp">
20
21 <RadioButton
22 android:id="@+id/radio_button"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:layout_gravity="center_vertical"
26 android:clickable="false"
27 android:checked="false" />
28
29 <LinearLayout
30 android:layout_width="0dp"
31 android:layout_height="wrap_content"
32 android:layout_weight="1"
33 android:orientation="vertical"
34 android:layout_gravity="center_vertical">
35
36 <com.google.android.material.textview.MaterialTextView
37 android:id="@+id/title"
38 style="@style/TextAppearance.Material3.TitleMedium"
39 android:layout_width="match_parent"
40 android:layout_height="wrap_content"
41 android:ellipsize="none"
42 android:marqueeRepeatLimit="marquee_forever"
43 android:requiresFadingEdge="horizontal"
44 android:singleLine="true"
45 android:textAlignment="viewStart"
46 tools:text="@string/select_gpu_driver_default" />
47
48 <com.google.android.material.textview.MaterialTextView
49 android:id="@+id/version"
50 style="@style/TextAppearance.Material3.BodyMedium"
51 android:layout_width="match_parent"
52 android:layout_height="wrap_content"
53 android:layout_marginTop="6dp"
54 android:ellipsize="none"
55 android:marqueeRepeatLimit="marquee_forever"
56 android:requiresFadingEdge="horizontal"
57 android:singleLine="true"
58 android:textAlignment="viewStart"
59 tools:text="@string/install_gpu_driver_description" />
60
61 <com.google.android.material.textview.MaterialTextView
62 android:id="@+id/description"
63 style="@style/TextAppearance.Material3.BodyMedium"
64 android:layout_width="match_parent"
65 android:layout_height="wrap_content"
66 android:layout_marginTop="6dp"
67 android:ellipsize="none"
68 android:marqueeRepeatLimit="marquee_forever"
69 android:requiresFadingEdge="horizontal"
70 android:singleLine="true"
71 android:textAlignment="viewStart"
72 tools:text="@string/install_gpu_driver_description" />
73
74 </LinearLayout>
75
76 <Button
77 android:id="@+id/button_delete"
78 style="@style/Widget.Material3.Button.IconButton"
79 android:layout_width="wrap_content"
80 android:layout_height="wrap_content"
81 android:layout_gravity="center_vertical"
82 android:contentDescription="@string/delete"
83 android:tooltipText="@string/delete"
84 app:icon="@drawable/ic_delete"
85 app:iconTint="?attr/colorControlNormal" />
86
87 </LinearLayout>
88
89</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/fragment_driver_manager.xml b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
new file mode 100644
index 000000000..6cea2d164
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
@@ -0,0 +1,48 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/coordinator_licenses"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface">
8
9 <androidx.coordinatorlayout.widget.CoordinatorLayout
10 android:layout_width="match_parent"
11 android:layout_height="match_parent">
12
13 <com.google.android.material.appbar.AppBarLayout
14 android:id="@+id/appbar_drivers"
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:fitsSystemWindows="true"
18 app:liftOnScrollTargetViewId="@id/list_drivers">
19
20 <com.google.android.material.appbar.MaterialToolbar
21 android:id="@+id/toolbar_drivers"
22 android:layout_width="match_parent"
23 android:layout_height="?attr/actionBarSize"
24 app:navigationIcon="@drawable/ic_back"
25 app:title="@string/gpu_driver_manager" />
26
27 </com.google.android.material.appbar.AppBarLayout>
28
29 <androidx.recyclerview.widget.RecyclerView
30 android:id="@+id/list_drivers"
31 android:layout_width="match_parent"
32 android:layout_height="match_parent"
33 android:clipToPadding="false"
34 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
35
36 </androidx.coordinatorlayout.widget.CoordinatorLayout>
37
38 <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
39 android:id="@+id/button_install"
40 android:layout_width="wrap_content"
41 android:layout_height="wrap_content"
42 android:layout_gravity="bottom|end"
43 android:text="@string/install"
44 app:icon="@drawable/ic_add"
45 app:layout_constraintBottom_toBottomOf="parent"
46 app:layout_constraintEnd_toEndOf="parent" />
47
48</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 2356b802b..82749359d 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -22,6 +22,9 @@
22 <action 22 <action
23 android:id="@+id/action_homeSettingsFragment_to_installableFragment" 23 android:id="@+id/action_homeSettingsFragment_to_installableFragment"
24 app:destination="@id/installableFragment" /> 24 app:destination="@id/installableFragment" />
25 <action
26 android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
27 app:destination="@id/driverManagerFragment" />
25 </fragment> 28 </fragment>
26 29
27 <fragment 30 <fragment
@@ -95,5 +98,9 @@
95 android:id="@+id/installableFragment" 98 android:id="@+id/installableFragment"
96 android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment" 99 android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
97 android:label="InstallableFragment" /> 100 android:label="InstallableFragment" />
101 <fragment
102 android:id="@+id/driverManagerFragment"
103 android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
104 android:label="DriverManagerFragment" />
98 105
99</navigation> 106</navigation>
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml
index dd0f36392..72a47fbdb 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -168,9 +168,7 @@
168 <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string> 168 <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
169 <string name="select_gpu_driver_install">Installieren</string> 169 <string name="select_gpu_driver_install">Installieren</string>
170 <string name="select_gpu_driver_default">Standard</string> 170 <string name="select_gpu_driver_default">Standard</string>
171 <string name="select_gpu_driver_install_success">%s wurde installiert</string>
172 <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string> 171 <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
173 <string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string>
174 <string name="system_gpu_driver">System GPU-Treiber</string> 172 <string name="system_gpu_driver">System GPU-Treiber</string>
175 <string name="installing_driver">Treiber wird installiert...</string> 173 <string name="installing_driver">Treiber wird installiert...</string>
176 174
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index d398f862f..e5bdd5889 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string> 171 <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
172 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
173 <string name="select_gpu_driver_default">Predeterminado</string> 173 <string name="select_gpu_driver_default">Predeterminado</string>
174 <string name="select_gpu_driver_install_success">Instalado %s</string>
175 <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string> 174 <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
176 <string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string>
177 <string name="system_gpu_driver">Driver GPU del sistema</string> 175 <string name="system_gpu_driver">Driver GPU del sistema</string>
178 <string name="installing_driver">Instalando driver...</string> 176 <string name="installing_driver">Instalando driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index a7abd9077..1e02828aa 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string> 171 <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
172 <string name="select_gpu_driver_install">Installer</string> 172 <string name="select_gpu_driver_install">Installer</string>
173 <string name="select_gpu_driver_default">Défaut</string> 173 <string name="select_gpu_driver_default">Défaut</string>
174 <string name="select_gpu_driver_install_success">%s Installé</string>
175 <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string> 174 <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
176 <string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string>
177 <string name="system_gpu_driver">Pilote du GPU du système</string> 175 <string name="system_gpu_driver">Pilote du GPU du système</string>
178 <string name="installing_driver">Installation du pilote...</string> 176 <string name="installing_driver">Installation du pilote...</string>
179 177
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index b18161801..09c9345b0 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string> 171 <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
172 <string name="select_gpu_driver_install">Installa</string> 172 <string name="select_gpu_driver_install">Installa</string>
173 <string name="select_gpu_driver_default">Predefinito</string> 173 <string name="select_gpu_driver_default">Predefinito</string>
174 <string name="select_gpu_driver_install_success">Installato%s</string>
175 <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string> 174 <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
176 <string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string>
177 <string name="system_gpu_driver">Driver GPU del sistema</string> 175 <string name="system_gpu_driver">Driver GPU del sistema</string>
178 <string name="installing_driver">Installando i driver...</string> 176 <string name="installing_driver">Installando i driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index 88fa5a0bb..a0ea78bef 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -170,9 +170,7 @@
170 <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string> 170 <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
171 <string name="select_gpu_driver_install">インストール</string> 171 <string name="select_gpu_driver_install">インストール</string>
172 <string name="select_gpu_driver_default">デフォルト</string> 172 <string name="select_gpu_driver_default">デフォルト</string>
173 <string name="select_gpu_driver_install_success">%s をインストールしました</string>
174 <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string> 173 <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
175 <string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
176 <string name="system_gpu_driver">システムのGPUドライバ</string> 174 <string name="system_gpu_driver">システムのGPUドライバ</string>
177 <string name="installing_driver">インストール中…</string> 175 <string name="installing_driver">インストール中…</string>
178 176
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index 4b658255c..214f95706 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string> 171 <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
172 <string name="select_gpu_driver_install">설치</string> 172 <string name="select_gpu_driver_install">설치</string>
173 <string name="select_gpu_driver_default">기본값</string> 173 <string name="select_gpu_driver_default">기본값</string>
174 <string name="select_gpu_driver_install_success">설치된 %s</string>
175 <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string> 174 <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
176 <string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
177 <string name="system_gpu_driver">시스템 GPU 드라이버</string> 175 <string name="system_gpu_driver">시스템 GPU 드라이버</string>
178 <string name="installing_driver">드라이버 설치 중...</string> 176 <string name="installing_driver">드라이버 설치 중...</string>
179 177
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index dd602a389..5443cef42 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string> 171 <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string>
172 <string name="select_gpu_driver_install">Installer</string> 172 <string name="select_gpu_driver_install">Installer</string>
173 <string name="select_gpu_driver_default">Standard</string> 173 <string name="select_gpu_driver_default">Standard</string>
174 <string name="select_gpu_driver_install_success">Installert %s</string>
175 <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string> 174 <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
176 <string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string>
177 <string name="system_gpu_driver">Systemets GPU-driver</string> 175 <string name="system_gpu_driver">Systemets GPU-driver</string>
178 <string name="installing_driver">Installerer driver...</string> 176 <string name="installing_driver">Installerer driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index 2fdd1f952..899e233d0 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string> 171 <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
172 <string name="select_gpu_driver_install">Zainstaluj</string> 172 <string name="select_gpu_driver_install">Zainstaluj</string>
173 <string name="select_gpu_driver_default">Domyślne</string> 173 <string name="select_gpu_driver_default">Domyślne</string>
174 <string name="select_gpu_driver_install_success">Zainstalowano %s</string>
175 <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string> 174 <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
176 <string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string>
177 <string name="system_gpu_driver">Systemowy sterownik GPU</string> 175 <string name="system_gpu_driver">Systemowy sterownik GPU</string>
178 <string name="installing_driver">Instalowanie sterownika...</string> 176 <string name="installing_driver">Instalowanie sterownika...</string>
179 177
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 2f26367fe..caa095364 100644
--- a/src/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> 171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
172 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
173 <string name="select_gpu_driver_default">Padrão</string> 173 <string name="select_gpu_driver_default">Padrão</string>
174 <string name="select_gpu_driver_install_success">Instalado%s</string>
175 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> 174 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
176 <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
177 <string name="system_gpu_driver">Driver do GPU padrão</string> 175 <string name="system_gpu_driver">Driver do GPU padrão</string>
178 <string name="installing_driver">A instalar o Driver...</string> 176 <string name="installing_driver">A instalar o Driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index 4e1eb4cd7..0a1a47fbb 100644
--- a/src/android/app/src/main/res/values-pt-rPT/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> 171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
172 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
173 <string name="select_gpu_driver_default">Padrão</string> 173 <string name="select_gpu_driver_default">Padrão</string>
174 <string name="select_gpu_driver_install_success">Instalado%s</string>
175 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> 174 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
176 <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
177 <string name="system_gpu_driver">Driver do GPU padrão</string> 175 <string name="system_gpu_driver">Driver do GPU padrão</string>
178 <string name="installing_driver">A instalar o Driver...</string> 176 <string name="installing_driver">A instalar o Driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index f5695dc93..0bef035d6 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string> 171 <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
172 <string name="select_gpu_driver_install">Установить</string> 172 <string name="select_gpu_driver_install">Установить</string>
173 <string name="select_gpu_driver_default">По умолчанию</string> 173 <string name="select_gpu_driver_default">По умолчанию</string>
174 <string name="select_gpu_driver_install_success">Установлено %s</string>
175 <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string> 174 <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
176 <string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
177 <string name="system_gpu_driver">Системный драйвер ГП</string> 175 <string name="system_gpu_driver">Системный драйвер ГП</string>
178 <string name="installing_driver">Установка драйвера...</string> 176 <string name="installing_driver">Установка драйвера...</string>
179 177
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index 061bc6f04..5b789ee98 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string> 171 <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
172 <string name="select_gpu_driver_install">Встановити</string> 172 <string name="select_gpu_driver_install">Встановити</string>
173 <string name="select_gpu_driver_default">За замовчуванням</string> 173 <string name="select_gpu_driver_default">За замовчуванням</string>
174 <string name="select_gpu_driver_install_success">Встановлено %s</string>
175 <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string> 174 <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
176 <string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
177 <string name="system_gpu_driver">Системний драйвер ГП</string> 175 <string name="system_gpu_driver">Системний драйвер ГП</string>
178 <string name="installing_driver">Встановлення драйвера...</string> 176 <string name="installing_driver">Встановлення драйвера...</string>
179 177
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index fe6dd5eaa..c0e885751 100644
--- a/src/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string> 171 <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
172 <string name="select_gpu_driver_install">安装</string> 172 <string name="select_gpu_driver_install">安装</string>
173 <string name="select_gpu_driver_default">系统默认</string> 173 <string name="select_gpu_driver_default">系统默认</string>
174 <string name="select_gpu_driver_install_success">已安装 %s</string>
175 <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string> 174 <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
176 <string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
177 <string name="system_gpu_driver">系统 GPU 驱动程序</string> 175 <string name="system_gpu_driver">系统 GPU 驱动程序</string>
178 <string name="installing_driver">正在安装驱动程序…</string> 176 <string name="installing_driver">正在安装驱动程序…</string>
179 177
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index 9b3e54224..4a21bf893 100644
--- a/src/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string> 171 <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
172 <string name="select_gpu_driver_install">安裝</string> 172 <string name="select_gpu_driver_install">安裝</string>
173 <string name="select_gpu_driver_default">預設</string> 173 <string name="select_gpu_driver_default">預設</string>
174 <string name="select_gpu_driver_install_success">已安裝 %s</string>
175 <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string> 174 <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
176 <string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
177 <string name="system_gpu_driver">系統 GPU 驅動程式</string> 175 <string name="system_gpu_driver">系統 GPU 驅動程式</string>
178 <string name="installing_driver">正在安裝驅動程式…</string> 176 <string name="installing_driver">正在安裝驅動程式…</string>
179 177
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 7b2296d95..ef855ea6f 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -13,6 +13,8 @@
13 <dimen name="menu_width">256dp</dimen> 13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen> 14 <dimen name="card_width">165dp</dimen>
15 <dimen name="icon_inset">24dp</dimen> 15 <dimen name="icon_inset">24dp</dimen>
16 <dimen name="spacing_bottom_list_fab">72dp</dimen>
17 <dimen name="spacing_fab">24dp</dimen>
16 18
17 <dimen name="dialog_margin">20dp</dimen> 19 <dimen name="dialog_margin">20dp</dimen>
18 <dimen name="elevated_app_bar">3dp</dimen> 20 <dimen name="elevated_app_bar">3dp</dimen>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index e51edf872..9e4854221 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -72,6 +72,7 @@
72 <string name="invalid_keys_error">Invalid encryption keys</string> 72 <string name="invalid_keys_error">Invalid encryption keys</string>
73 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string> 73 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
74 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string> 74 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
75 <string name="gpu_driver_manager">GPU Driver Manager</string>
75 <string name="install_gpu_driver">Install GPU driver</string> 76 <string name="install_gpu_driver">Install GPU driver</string>
76 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> 77 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
77 <string name="advanced_settings">Advanced settings</string> 78 <string name="advanced_settings">Advanced settings</string>
@@ -234,15 +235,17 @@
234 <string name="export_failed">Export failed</string> 235 <string name="export_failed">Export failed</string>
235 <string name="import_failed">Import failed</string> 236 <string name="import_failed">Import failed</string>
236 <string name="cancelling">Cancelling</string> 237 <string name="cancelling">Cancelling</string>
238 <string name="install">Install</string>
239 <string name="delete">Delete</string>
237 240
238 <!-- GPU driver installation --> 241 <!-- GPU driver installation -->
239 <string name="select_gpu_driver">Select GPU driver</string> 242 <string name="select_gpu_driver">Select GPU driver</string>
240 <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string> 243 <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
241 <string name="select_gpu_driver_install">Install</string> 244 <string name="select_gpu_driver_install">Install</string>
242 <string name="select_gpu_driver_default">Default</string> 245 <string name="select_gpu_driver_default">Default</string>
243 <string name="select_gpu_driver_install_success">Installed %s</string>
244 <string name="select_gpu_driver_use_default">Using default GPU driver</string> 246 <string name="select_gpu_driver_use_default">Using default GPU driver</string>
245 <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string> 247 <string name="select_gpu_driver_error">Invalid driver selected</string>
248 <string name="driver_already_installed">Driver already installed</string>
246 <string name="system_gpu_driver">System GPU driver</string> 249 <string name="system_gpu_driver">System GPU driver</string>
247 <string name="installing_driver">Installing driver…</string> 250 <string name="installing_driver">Installing driver…</string>
248 251
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 80f370c16..51e559321 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -3,8 +3,8 @@
3 3
4// Top-level build file where you can add configuration options common to all sub-projects/modules. 4// Top-level build file where you can add configuration options common to all sub-projects/modules.
5plugins { 5plugins {
6 id("com.android.application") version "8.0.2" apply false 6 id("com.android.application") version "8.1.2" apply false
7 id("com.android.library") version "8.0.2" apply false 7 id("com.android.library") version "8.1.2" apply false
8 id("org.jetbrains.kotlin.android") version "1.8.21" apply false 8 id("org.jetbrains.kotlin.android") version "1.8.21" apply false
9} 9}
10 10
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
index 972d5e45b..ef301d8b4 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
@@ -77,6 +77,7 @@ void AudioRenderer::Wait() {
77 "{}, got {}", 77 "{}, got {}",
78 Message::RenderResponse, msg); 78 Message::RenderResponse, msg);
79 } 79 }
80 PostDSPClearCommandBuffer();
80} 81}
81 82
82void AudioRenderer::Send(Direction dir, u32 message) { 83void AudioRenderer::Send(Direction dir, u32 message) {
@@ -96,6 +97,14 @@ void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u
96 command_buffers[session_id].reset_buffer = reset; 97 command_buffers[session_id].reset_buffer = reset;
97} 98}
98 99
100void AudioRenderer::PostDSPClearCommandBuffer() noexcept {
101 for (auto& buffer : command_buffers) {
102 buffer.buffer = 0;
103 buffer.size = 0;
104 buffer.reset_buffer = false;
105 }
106}
107
99u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { 108u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
100 return command_buffers[session_id].remaining_command_count; 109 return command_buffers[session_id].remaining_command_count;
101} 110}
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
index 85874d88a..57b89d9fe 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
@@ -85,6 +85,8 @@ private:
85 */ 85 */
86 void CreateSinkStreams(); 86 void CreateSinkStreams();
87 87
88 void PostDSPClearCommandBuffer() noexcept;
89
88 /// Core system 90 /// Core system
89 Core::System& system; 91 Core::System& system;
90 /// The output sink the AudioRenderer will send samples to 92 /// The output sink the AudioRenderer will send samples to
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 6081352a2..d66d04fae 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -204,6 +204,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
204 // paused and we'll desync, so just play silence. 204 // paused and we'll desync, so just play silence.
205 if (system.IsPaused() || system.IsShuttingDown()) { 205 if (system.IsPaused() || system.IsShuttingDown()) {
206 if (system.IsShuttingDown()) { 206 if (system.IsShuttingDown()) {
207 {
208 std::scoped_lock lk{release_mutex};
209 queued_buffers.store(0);
210 }
207 release_cv.notify_one(); 211 release_cv.notify_one();
208 } 212 }
209 213
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 0dad9338a..47d028d48 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -39,8 +39,12 @@
39#define Crash() exit(1) 39#define Crash() exit(1)
40#endif 40#endif
41 41
42#define LTO_NOINLINE __attribute__((noinline))
43
42#else // _MSC_VER 44#else // _MSC_VER
43 45
46#define LTO_NOINLINE
47
44// Locale Cross-Compatibility 48// Locale Cross-Compatibility
45#define locale_t _locale_t 49#define locale_t _locale_t
46 50
diff --git a/src/common/elf.h b/src/common/elf.h
index 14a5e9597..0b728dc54 100644
--- a/src/common/elf.h
+++ b/src/common/elf.h
@@ -211,6 +211,11 @@ struct Elf64_Rela {
211 Elf64_Sxword r_addend; /* Addend */ 211 Elf64_Sxword r_addend; /* Addend */
212}; 212};
213 213
214/* RELR relocation table entry */
215
216using Elf32_Relr = Elf32_Word;
217using Elf64_Relr = Elf64_Xword;
218
214/* How to extract and insert information held in the r_info field. */ 219/* How to extract and insert information held in the r_info field. */
215 220
216static inline u32 Elf32RelSymIndex(Elf32_Word r_info) { 221static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
@@ -328,6 +333,9 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
328constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */ 333constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
329constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */ 334constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
330constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */ 335constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
336constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */
337constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */
338constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */
331 339
332} // namespace ELF 340} // namespace ELF
333} // namespace Common 341} // namespace Common
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h
index 41cbb9ed5..12e59a893 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -15,12 +15,13 @@
15#include <condition_variable> 15#include <condition_variable>
16#include <stop_token> 16#include <stop_token>
17#include <thread> 17#include <thread>
18#include <utility>
18 19
19namespace Common { 20namespace Common {
20 21
21template <typename Condvar, typename Lock, typename Pred> 22template <typename Condvar, typename Lock, typename Pred>
22void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) { 23void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
23 cv.wait(lk, token, std::move(pred)); 24 cv.wait(lk, token, std::forward<Pred>(pred));
24} 25}
25 26
26template <typename Rep, typename Period> 27template <typename Rep, typename Period>
@@ -109,7 +110,7 @@ public:
109 110
110 // Insert the callback. 111 // Insert the callback.
111 stop_state_callback ret = ++m_next_callback; 112 stop_state_callback ret = ++m_next_callback;
112 m_callbacks.emplace(ret, move(f)); 113 m_callbacks.emplace(ret, std::move(f));
113 return ret; 114 return ret;
114 } 115 }
115 116
@@ -162,7 +163,7 @@ private:
162 friend class stop_source; 163 friend class stop_source;
163 template <typename Callback> 164 template <typename Callback>
164 friend class stop_callback; 165 friend class stop_callback;
165 stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {} 166 stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
166 167
167private: 168private:
168 shared_ptr<polyfill::stop_state> m_stop_state; 169 shared_ptr<polyfill::stop_state> m_stop_state;
@@ -198,7 +199,7 @@ public:
198private: 199private:
199 friend class jthread; 200 friend class jthread;
200 explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) 201 explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
201 : m_stop_state(move(stop_state)) {} 202 : m_stop_state(std::move(stop_state)) {}
202 203
203private: 204private:
204 shared_ptr<polyfill::stop_state> m_stop_state; 205 shared_ptr<polyfill::stop_state> m_stop_state;
@@ -218,16 +219,16 @@ public:
218 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) 219 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
219 : m_stop_state(st.m_stop_state) { 220 : m_stop_state(st.m_stop_state) {
220 if (m_stop_state) { 221 if (m_stop_state) {
221 m_callback = m_stop_state->insert_callback(move(cb)); 222 m_callback = m_stop_state->insert_callback(std::move(cb));
222 } 223 }
223 } 224 }
224 template <typename C> 225 template <typename C>
225 requires constructible_from<Callback, C> 226 requires constructible_from<Callback, C>
226 explicit stop_callback(stop_token&& st, 227 explicit stop_callback(stop_token&& st,
227 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) 228 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
228 : m_stop_state(move(st.m_stop_state)) { 229 : m_stop_state(std::move(st.m_stop_state)) {
229 if (m_stop_state) { 230 if (m_stop_state) {
230 m_callback = m_stop_state->insert_callback(move(cb)); 231 m_callback = m_stop_state->insert_callback(std::move(cb));
231 } 232 }
232 } 233 }
233 ~stop_callback() { 234 ~stop_callback() {
@@ -260,7 +261,7 @@ public:
260 typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> 261 typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
261 explicit jthread(F&& f, Args&&... args) 262 explicit jthread(F&& f, Args&&... args)
262 : m_stop_state(make_shared<polyfill::stop_state>()), 263 : m_stop_state(make_shared<polyfill::stop_state>()),
263 m_thread(make_thread(move(f), move(args)...)) {} 264 m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
264 265
265 ~jthread() { 266 ~jthread() {
266 if (joinable()) { 267 if (joinable()) {
@@ -317,9 +318,9 @@ private:
317 template <typename F, typename... Args> 318 template <typename F, typename... Args>
318 thread make_thread(F&& f, Args&&... args) { 319 thread make_thread(F&& f, Args&&... args) {
319 if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { 320 if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
320 return thread(move(f), get_stop_token(), move(args)...); 321 return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
321 } else { 322 } else {
322 return thread(move(f), move(args)...); 323 return thread(std::forward<F>(f), std::forward<Args>(args)...);
323 } 324 }
324 } 325 }
325 326
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 3fde3cae6..98b43e49c 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -45,6 +45,7 @@ SWITCHABLE(CpuAccuracy, true);
45SWITCHABLE(FullscreenMode, true); 45SWITCHABLE(FullscreenMode, true);
46SWITCHABLE(GpuAccuracy, true); 46SWITCHABLE(GpuAccuracy, true);
47SWITCHABLE(Language, true); 47SWITCHABLE(Language, true);
48SWITCHABLE(MemoryLayout, true);
48SWITCHABLE(NvdecEmulation, false); 49SWITCHABLE(NvdecEmulation, false);
49SWITCHABLE(Region, true); 50SWITCHABLE(Region, true);
50SWITCHABLE(RendererBackend, true); 51SWITCHABLE(RendererBackend, true);
@@ -61,6 +62,10 @@ SWITCHABLE(u32, false);
61SWITCHABLE(u8, false); 62SWITCHABLE(u8, false);
62SWITCHABLE(u8, true); 63SWITCHABLE(u8, true);
63 64
65// Used in UISettings
66// TODO see if we can move this to uisettings.cpp
67SWITCHABLE(ConfirmStop, true);
68
64#undef SETTING 69#undef SETTING
65#undef SWITCHABLE 70#undef SWITCHABLE
66#endif 71#endif
diff --git a/src/common/settings.h b/src/common/settings.h
index 98ab0ec2e..236e33bee 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -67,6 +67,7 @@ SWITCHABLE(CpuAccuracy, true);
67SWITCHABLE(FullscreenMode, true); 67SWITCHABLE(FullscreenMode, true);
68SWITCHABLE(GpuAccuracy, true); 68SWITCHABLE(GpuAccuracy, true);
69SWITCHABLE(Language, true); 69SWITCHABLE(Language, true);
70SWITCHABLE(MemoryLayout, true);
70SWITCHABLE(NvdecEmulation, false); 71SWITCHABLE(NvdecEmulation, false);
71SWITCHABLE(Region, true); 72SWITCHABLE(Region, true);
72SWITCHABLE(RendererBackend, true); 73SWITCHABLE(RendererBackend, true);
@@ -83,6 +84,10 @@ SWITCHABLE(u32, false);
83SWITCHABLE(u8, false); 84SWITCHABLE(u8, false);
84SWITCHABLE(u8, true); 85SWITCHABLE(u8, true);
85 86
87// Used in UISettings
88// TODO see if we can move this to uisettings.h
89SWITCHABLE(ConfirmStop, true);
90
86#undef SETTING 91#undef SETTING
87#undef SWITCHABLE 92#undef SWITCHABLE
88#endif 93#endif
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index 815cafe15..11429d7a8 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -133,6 +133,8 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
133 133
134ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb); 134ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
135 135
136ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
137
136ENUM(FullscreenMode, Borderless, Exclusive); 138ENUM(FullscreenMode, Borderless, Exclusive);
137 139
138ENUM(NvdecEmulation, Off, Cpu, Gpu); 140ENUM(NvdecEmulation, Off, Cpu, Gpu);
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 1fbfbf31f..0b0cef984 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -3405,6 +3405,11 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K
3405 new_attr, KMemoryBlockDisableMergeAttribute::Locked, 3405 new_attr, KMemoryBlockDisableMergeAttribute::Locked,
3406 KMemoryBlockDisableMergeAttribute::None); 3406 KMemoryBlockDisableMergeAttribute::None);
3407 3407
3408 // If we have an output page group, open.
3409 if (out_pg) {
3410 out_pg->Open();
3411 }
3412
3408 R_SUCCEED(); 3413 R_SUCCEED();
3409} 3414}
3410 3415
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index a1134b7e2..cb025c3d6 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -373,7 +373,7 @@ struct KernelCore::Impl {
373 static inline thread_local u8 host_thread_id = UINT8_MAX; 373 static inline thread_local u8 host_thread_id = UINT8_MAX;
374 374
375 /// Sets the host thread ID for the caller. 375 /// Sets the host thread ID for the caller.
376 u32 SetHostThreadId(std::size_t core_id) { 376 LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
377 // This should only be called during core init. 377 // This should only be called during core init.
378 ASSERT(host_thread_id == UINT8_MAX); 378 ASSERT(host_thread_id == UINT8_MAX);
379 379
@@ -384,13 +384,13 @@ struct KernelCore::Impl {
384 } 384 }
385 385
386 /// Gets the host thread ID for the caller 386 /// Gets the host thread ID for the caller
387 u32 GetHostThreadId() const { 387 LTO_NOINLINE u32 GetHostThreadId() const {
388 return host_thread_id; 388 return host_thread_id;
389 } 389 }
390 390
391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time 391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time
392 KThread* GetHostDummyThread(KThread* existing_thread) { 392 LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
393 const auto initialize{[](KThread* thread) { 393 const auto initialize{[](KThread* thread) LTO_NOINLINE {
394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); 394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
395 return thread; 395 return thread;
396 }}; 396 }};
@@ -424,11 +424,11 @@ struct KernelCore::Impl {
424 424
425 static inline thread_local bool is_phantom_mode_for_singlecore{false}; 425 static inline thread_local bool is_phantom_mode_for_singlecore{false};
426 426
427 bool IsPhantomModeForSingleCore() const { 427 LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
428 return is_phantom_mode_for_singlecore; 428 return is_phantom_mode_for_singlecore;
429 } 429 }
430 430
431 void SetIsPhantomModeForSingleCore(bool value) { 431 LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
432 ASSERT(!is_multicore); 432 ASSERT(!is_multicore);
433 is_phantom_mode_for_singlecore = value; 433 is_phantom_mode_for_singlecore = value;
434 } 434 }
@@ -439,14 +439,14 @@ struct KernelCore::Impl {
439 439
440 static inline thread_local KThread* current_thread{nullptr}; 440 static inline thread_local KThread* current_thread{nullptr};
441 441
442 KThread* GetCurrentEmuThread() { 442 LTO_NOINLINE KThread* GetCurrentEmuThread() {
443 if (!current_thread) { 443 if (!current_thread) {
444 current_thread = GetHostDummyThread(nullptr); 444 current_thread = GetHostDummyThread(nullptr);
445 } 445 }
446 return current_thread; 446 return current_thread;
447 } 447 }
448 448
449 void SetCurrentEmuThread(KThread* thread) { 449 LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
450 current_thread = thread; 450 current_thread = thread;
451 } 451 }
452 452
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index b971401e6..b7d14060c 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -49,7 +49,7 @@ public:
49 : ServiceFramework{system_, "IManagerForSystemService"} { 49 : ServiceFramework{system_, "IManagerForSystemService"} {
50 // clang-format off 50 // clang-format off
51 static const FunctionInfo functions[] = { 51 static const FunctionInfo functions[] = {
52 {0, nullptr, "CheckAvailability"}, 52 {0, &IManagerForSystemService::CheckAvailability, "CheckAvailability"},
53 {1, nullptr, "GetAccountId"}, 53 {1, nullptr, "GetAccountId"},
54 {2, nullptr, "EnsureIdTokenCacheAsync"}, 54 {2, nullptr, "EnsureIdTokenCacheAsync"},
55 {3, nullptr, "LoadIdTokenCache"}, 55 {3, nullptr, "LoadIdTokenCache"},
@@ -78,6 +78,13 @@ public:
78 78
79 RegisterHandlers(functions); 79 RegisterHandlers(functions);
80 } 80 }
81
82private:
83 void CheckAvailability(HLERequestContext& ctx) {
84 LOG_WARNING(Service_ACC, "(STUBBED) called");
85 IPC::ResponseBuilder rb{ctx, 2};
86 rb.Push(ResultSuccess);
87 }
81}; 88};
82 89
83// 3.0.0+ 90// 3.0.0+
@@ -837,6 +844,29 @@ void Module::Interface::InitializeApplicationInfoV2(HLERequestContext& ctx) {
837 rb.Push(ResultSuccess); 844 rb.Push(ResultSuccess);
838} 845}
839 846
847void Module::Interface::BeginUserRegistration(HLERequestContext& ctx) {
848 const auto user_id = Common::UUID::MakeRandom();
849 profile_manager->CreateNewUser(user_id, "yuzu");
850
851 LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
852
853 IPC::ResponseBuilder rb{ctx, 6};
854 rb.Push(ResultSuccess);
855 rb.PushRaw(user_id);
856}
857
858void Module::Interface::CompleteUserRegistration(HLERequestContext& ctx) {
859 IPC::RequestParser rp{ctx};
860 Common::UUID user_id = rp.PopRaw<Common::UUID>();
861
862 LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
863
864 profile_manager->WriteUserSaveFile();
865
866 IPC::ResponseBuilder rb{ctx, 2};
867 rb.Push(ResultSuccess);
868}
869
840void Module::Interface::GetProfileEditor(HLERequestContext& ctx) { 870void Module::Interface::GetProfileEditor(HLERequestContext& ctx) {
841 IPC::RequestParser rp{ctx}; 871 IPC::RequestParser rp{ctx};
842 Common::UUID user_id = rp.PopRaw<Common::UUID>(); 872 Common::UUID user_id = rp.PopRaw<Common::UUID>();
@@ -880,6 +910,17 @@ void Module::Interface::StoreSaveDataThumbnailApplication(HLERequestContext& ctx
880 StoreSaveDataThumbnail(ctx, uuid, tid); 910 StoreSaveDataThumbnail(ctx, uuid, tid);
881} 911}
882 912
913void Module::Interface::GetBaasAccountManagerForSystemService(HLERequestContext& ctx) {
914 IPC::RequestParser rp{ctx};
915 const auto uuid = rp.PopRaw<Common::UUID>();
916
917 LOG_INFO(Service_ACC, "called, uuid=0x{}", uuid.RawString());
918
919 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
920 rb.Push(ResultSuccess);
921 rb.PushIpcInterface<IManagerForSystemService>(system, uuid);
922}
923
883void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) { 924void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) {
884 IPC::RequestParser rp{ctx}; 925 IPC::RequestParser rp{ctx};
885 const auto uuid = rp.PopRaw<Common::UUID>(); 926 const auto uuid = rp.PopRaw<Common::UUID>();
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 6b4735c2f..0395229b4 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -33,10 +33,13 @@ public:
33 void TrySelectUserWithoutInteraction(HLERequestContext& ctx); 33 void TrySelectUserWithoutInteraction(HLERequestContext& ctx);
34 void IsUserAccountSwitchLocked(HLERequestContext& ctx); 34 void IsUserAccountSwitchLocked(HLERequestContext& ctx);
35 void InitializeApplicationInfoV2(HLERequestContext& ctx); 35 void InitializeApplicationInfoV2(HLERequestContext& ctx);
36 void BeginUserRegistration(HLERequestContext& ctx);
37 void CompleteUserRegistration(HLERequestContext& ctx);
36 void GetProfileEditor(HLERequestContext& ctx); 38 void GetProfileEditor(HLERequestContext& ctx);
37 void ListQualifiedUsers(HLERequestContext& ctx); 39 void ListQualifiedUsers(HLERequestContext& ctx);
38 void ListOpenContextStoredUsers(HLERequestContext& ctx); 40 void ListOpenContextStoredUsers(HLERequestContext& ctx);
39 void StoreSaveDataThumbnailApplication(HLERequestContext& ctx); 41 void StoreSaveDataThumbnailApplication(HLERequestContext& ctx);
42 void GetBaasAccountManagerForSystemService(HLERequestContext& ctx);
40 void StoreSaveDataThumbnailSystem(HLERequestContext& ctx); 43 void StoreSaveDataThumbnailSystem(HLERequestContext& ctx);
41 44
42 private: 45 private:
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index d9882ecd3..770d13ec5 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -23,7 +23,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
23 {99, nullptr, "DebugActivateOpenContextRetention"}, 23 {99, nullptr, "DebugActivateOpenContextRetention"},
24 {100, nullptr, "GetUserRegistrationNotifier"}, 24 {100, nullptr, "GetUserRegistrationNotifier"},
25 {101, nullptr, "GetUserStateChangeNotifier"}, 25 {101, nullptr, "GetUserStateChangeNotifier"},
26 {102, nullptr, "GetBaasAccountManagerForSystemService"}, 26 {102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"},
27 {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, 27 {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
28 {104, nullptr, "GetProfileUpdateNotifier"}, 28 {104, nullptr, "GetProfileUpdateNotifier"},
29 {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, 29 {105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
@@ -40,8 +40,8 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
40 {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, 40 {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
41 {190, nullptr, "GetUserLastOpenedApplication"}, 41 {190, nullptr, "GetUserLastOpenedApplication"},
42 {191, nullptr, "ActivateOpenContextHolder"}, 42 {191, nullptr, "ActivateOpenContextHolder"},
43 {200, nullptr, "BeginUserRegistration"}, 43 {200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"},
44 {201, nullptr, "CompleteUserRegistration"}, 44 {201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
45 {202, nullptr, "CancelUserRegistration"}, 45 {202, nullptr, "CancelUserRegistration"},
46 {203, nullptr, "DeleteUser"}, 46 {203, nullptr, "DeleteUser"},
47 {204, nullptr, "SetUserPosition"}, 47 {204, nullptr, "SetUserPosition"},
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 993a5a57a..900e32200 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -96,9 +96,10 @@ public:
96 bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, 96 bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
97 const UserData& data_new); 97 const UserData& data_new);
98 98
99 void WriteUserSaveFile();
100
99private: 101private:
100 void ParseUserSaveFile(); 102 void ParseUserSaveFile();
101 void WriteUserSaveFile();
102 std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); 103 std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
103 bool RemoveProfileAtIndex(std::size_t index); 104 bool RemoveProfileAtIndex(std::size_t index);
104 105
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index ac376b55a..98765b81a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -210,8 +210,8 @@ IDisplayController::IDisplayController(Core::System& system_)
210 {21, nullptr, "ClearAppletTransitionBuffer"}, 210 {21, nullptr, "ClearAppletTransitionBuffer"},
211 {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"}, 211 {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"},
212 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, 212 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
213 {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, 213 {24, &IDisplayController::AcquireLastForegroundCaptureSharedBuffer, "AcquireLastForegroundCaptureSharedBuffer"},
214 {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, 214 {25, &IDisplayController::ReleaseLastForegroundCaptureSharedBuffer, "ReleaseLastForegroundCaptureSharedBuffer"},
215 {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"}, 215 {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"},
216 {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"}, 216 {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"},
217 {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, 217 {28, nullptr, "TakeScreenShotOfOwnLayerEx"},
@@ -239,6 +239,22 @@ void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
239 rb.Push(ResultSuccess); 239 rb.Push(ResultSuccess);
240} 240}
241 241
242void IDisplayController::AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
243 LOG_WARNING(Service_AM, "(STUBBED) called");
244
245 IPC::ResponseBuilder rb{ctx, 4};
246 rb.Push(ResultSuccess);
247 rb.Push(1U);
248 rb.Push(0);
249}
250
251void IDisplayController::ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
252 LOG_WARNING(Service_AM, "(STUBBED) called");
253
254 IPC::ResponseBuilder rb{ctx, 2};
255 rb.Push(ResultSuccess);
256}
257
242void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) { 258void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
243 LOG_WARNING(Service_AM, "(STUBBED) called"); 259 LOG_WARNING(Service_AM, "(STUBBED) called");
244 260
@@ -1557,7 +1573,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1557 {100, nullptr, "CreateGameMovieTrimmer"}, 1573 {100, nullptr, "CreateGameMovieTrimmer"},
1558 {101, nullptr, "ReserveResourceForMovieOperation"}, 1574 {101, nullptr, "ReserveResourceForMovieOperation"},
1559 {102, nullptr, "UnreserveResourceForMovieOperation"}, 1575 {102, nullptr, "UnreserveResourceForMovieOperation"},
1560 {110, nullptr, "GetMainAppletAvailableUsers"}, 1576 {110, &ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers, "GetMainAppletAvailableUsers"},
1561 {120, nullptr, "GetLaunchStorageInfoForDebug"}, 1577 {120, nullptr, "GetLaunchStorageInfoForDebug"},
1562 {130, nullptr, "GetGpuErrorDetectedSystemEvent"}, 1578 {130, nullptr, "GetGpuErrorDetectedSystemEvent"},
1563 {140, nullptr, "SetApplicationMemoryReservation"}, 1579 {140, nullptr, "SetApplicationMemoryReservation"},
@@ -1652,6 +1668,25 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
1652 rb.PushRaw(applet_info); 1668 rb.PushRaw(applet_info);
1653} 1669}
1654 1670
1671void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {
1672 const Service::Account::ProfileManager manager{};
1673 bool is_empty{true};
1674 s32 user_count{-1};
1675
1676 LOG_INFO(Service_AM, "called");
1677
1678 if (manager.GetUserCount() > 0) {
1679 is_empty = false;
1680 user_count = static_cast<s32>(manager.GetUserCount());
1681 ctx.WriteBuffer(manager.GetAllUsers());
1682 }
1683
1684 IPC::ResponseBuilder rb{ctx, 4};
1685 rb.Push(ResultSuccess);
1686 rb.Push<u8>(is_empty);
1687 rb.Push(user_count);
1688}
1689
1655void ILibraryAppletSelfAccessor::PushInShowAlbum() { 1690void ILibraryAppletSelfAccessor::PushInShowAlbum() {
1656 const Applets::CommonArguments arguments{ 1691 const Applets::CommonArguments arguments{
1657 .arguments_version = Applets::CommonArgumentVersion::Version3, 1692 .arguments_version = Applets::CommonArgumentVersion::Version3,
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 4a045cfd4..64b3f3fe2 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -124,6 +124,8 @@ public:
124private: 124private:
125 void GetCallerAppletCaptureImageEx(HLERequestContext& ctx); 125 void GetCallerAppletCaptureImageEx(HLERequestContext& ctx);
126 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx); 126 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
127 void AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
128 void ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
127 void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); 129 void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
128 void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); 130 void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
129}; 131};
@@ -345,6 +347,7 @@ private:
345 void GetLibraryAppletInfo(HLERequestContext& ctx); 347 void GetLibraryAppletInfo(HLERequestContext& ctx);
346 void ExitProcessAndReturn(HLERequestContext& ctx); 348 void ExitProcessAndReturn(HLERequestContext& ctx);
347 void GetCallerAppletIdentityInfo(HLERequestContext& ctx); 349 void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
350 void GetMainAppletAvailableUsers(HLERequestContext& ctx);
348 351
349 void PushInShowAlbum(); 352 void PushInShowAlbum();
350 void PushInShowCabinetData(); 353 void PushInShowCabinetData();
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 286f9fd10..31dd98140 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -16,7 +16,7 @@ namespace Service::Capture {
16 16
17void LoopProcess(Core::System& system) { 17void LoopProcess(Core::System& system) {
18 auto server_manager = std::make_unique<ServerManager>(system); 18 auto server_manager = std::make_unique<ServerManager>(system);
19 auto album_manager = std::make_shared<AlbumManager>(); 19 auto album_manager = std::make_shared<AlbumManager>(system);
20 20
21 server_manager->RegisterNamedService( 21 server_manager->RegisterNamedService(
22 "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager)); 22 "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index e22f72bf6..9925720a3 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -128,9 +128,9 @@ void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
128 ctx.WriteBuffer(entries); 128 ctx.WriteBuffer(entries);
129 } 129 }
130 130
131 IPC::ResponseBuilder rb{ctx, 3}; 131 IPC::ResponseBuilder rb{ctx, 4};
132 rb.Push(result); 132 rb.Push(result);
133 rb.Push(entries.size()); 133 rb.Push<u64>(entries.size());
134} 134}
135 135
136void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) { 136void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
index 2df6a930a..2b4e3f076 100644
--- a/src/core/hle/service/caps/caps_manager.cpp
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -8,12 +8,15 @@
8#include "common/fs/file.h" 8#include "common/fs/file.h"
9#include "common/fs/path_util.h" 9#include "common/fs/path_util.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/core.h"
11#include "core/hle/service/caps/caps_manager.h" 12#include "core/hle/service/caps/caps_manager.h"
12#include "core/hle/service/caps/caps_result.h" 13#include "core/hle/service/caps/caps_result.h"
14#include "core/hle/service/time/time_manager.h"
15#include "core/hle/service/time/time_zone_content_manager.h"
13 16
14namespace Service::Capture { 17namespace Service::Capture {
15 18
16AlbumManager::AlbumManager() {} 19AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
17 20
18AlbumManager::~AlbumManager() = default; 21AlbumManager::~AlbumManager() = default;
19 22
@@ -83,6 +86,34 @@ Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, Albu
83} 86}
84 87
85Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries, 88Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
89 ContentType contex_type, s64 start_posix_time,
90 s64 end_posix_time, u64 aruid) const {
91 if (!is_mounted) {
92 return ResultIsNotMounted;
93 }
94
95 std::vector<ApplicationAlbumEntry> album_entries;
96 const auto start_date = ConvertToAlbumDateTime(start_posix_time);
97 const auto end_date = ConvertToAlbumDateTime(end_posix_time);
98 const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
99
100 if (result.IsError()) {
101 return result;
102 }
103
104 for (const auto& album_entry : album_entries) {
105 ApplicationAlbumFileEntry entry{
106 .entry = album_entry,
107 .datetime = album_entry.datetime,
108 .unknown = {},
109 };
110 out_entries.push_back(entry);
111 }
112
113 return ResultSuccess;
114}
115
116Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
86 ContentType contex_type, AlbumFileDateTime start_date, 117 ContentType contex_type, AlbumFileDateTime start_date,
87 AlbumFileDateTime end_date, u64 aruid) const { 118 AlbumFileDateTime end_date, u64 aruid) const {
88 if (!is_mounted) { 119 if (!is_mounted) {
@@ -93,31 +124,25 @@ Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& ou
93 if (file_id.type != contex_type) { 124 if (file_id.type != contex_type) {
94 continue; 125 continue;
95 } 126 }
96
97 if (file_id.date > start_date) { 127 if (file_id.date > start_date) {
98 continue; 128 continue;
99 } 129 }
100
101 if (file_id.date < end_date) { 130 if (file_id.date < end_date) {
102 continue; 131 continue;
103 } 132 }
104
105 if (out_entries.size() >= SdAlbumFileLimit) { 133 if (out_entries.size() >= SdAlbumFileLimit) {
106 break; 134 break;
107 } 135 }
108 136
109 const auto entry_size = Common::FS::GetSize(path); 137 const auto entry_size = Common::FS::GetSize(path);
110 ApplicationAlbumFileEntry entry{.entry = 138 ApplicationAlbumEntry entry{
111 { 139 .size = entry_size,
112 .size = entry_size, 140 .hash{},
113 .hash{}, 141 .datetime = file_id.date,
114 .datetime = file_id.date, 142 .storage = file_id.storage,
115 .storage = file_id.storage, 143 .content = contex_type,
116 .content = contex_type, 144 .unknown = 1,
117 .unknown = 1, 145 };
118 },
119 .datetime = file_id.date,
120 .unknown = {}};
121 out_entries.push_back(entry); 146 out_entries.push_back(entry);
122 } 147 }
123 148
@@ -274,12 +299,12 @@ Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem:
274 .application_id = static_cast<u64>(std::stoll(application, 0, 16)), 299 .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
275 .date = 300 .date =
276 { 301 {
277 .year = static_cast<u16>(std::stoi(year)), 302 .year = static_cast<s16>(std::stoi(year)),
278 .month = static_cast<u8>(std::stoi(month)), 303 .month = static_cast<s8>(std::stoi(month)),
279 .day = static_cast<u8>(std::stoi(day)), 304 .day = static_cast<s8>(std::stoi(day)),
280 .hour = static_cast<u8>(std::stoi(hour)), 305 .hour = static_cast<s8>(std::stoi(hour)),
281 .minute = static_cast<u8>(std::stoi(minute)), 306 .minute = static_cast<s8>(std::stoi(minute)),
282 .second = static_cast<u8>(std::stoi(second)), 307 .second = static_cast<s8>(std::stoi(second)),
283 .unique_id = 0, 308 .unique_id = 0,
284 }, 309 },
285 .storage = AlbumStorage::Sd, 310 .storage = AlbumStorage::Sd,
@@ -339,4 +364,23 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
339 364
340 return ResultSuccess; 365 return ResultSuccess;
341} 366}
367
368AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
369 Time::TimeZone::CalendarInfo calendar_date{};
370 const auto& time_zone_manager =
371 system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
372
373 time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
374
375 return {
376 .year = calendar_date.time.year,
377 .month = calendar_date.time.month,
378 .day = calendar_date.time.day,
379 .hour = calendar_date.time.hour,
380 .minute = calendar_date.time.minute,
381 .second = calendar_date.time.second,
382 .unique_id = 0,
383 };
384}
385
342} // namespace Service::Capture 386} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h
index 8337c655c..f65eb12c1 100644
--- a/src/core/hle/service/caps/caps_manager.h
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -37,7 +37,7 @@ namespace Service::Capture {
37 37
38class AlbumManager { 38class AlbumManager {
39public: 39public:
40 explicit AlbumManager(); 40 explicit AlbumManager(Core::System& system_);
41 ~AlbumManager(); 41 ~AlbumManager();
42 42
43 Result DeleteAlbumFile(const AlbumFileId& file_id); 43 Result DeleteAlbumFile(const AlbumFileId& file_id);
@@ -45,6 +45,9 @@ public:
45 Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage, 45 Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
46 u8 flags) const; 46 u8 flags) const;
47 Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries, 47 Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
48 ContentType contex_type, s64 start_posix_time, s64 end_posix_time,
49 u64 aruid) const;
50 Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
48 ContentType contex_type, AlbumFileDateTime start_date, 51 ContentType contex_type, AlbumFileDateTime start_date,
49 AlbumFileDateTime end_date, u64 aruid) const; 52 AlbumFileDateTime end_date, u64 aruid) const;
50 Result GetAutoSavingStorage(bool& out_is_autosaving) const; 53 Result GetAutoSavingStorage(bool& out_is_autosaving) const;
@@ -65,8 +68,12 @@ private:
65 Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width, 68 Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
66 int height, ScreenShotDecoderFlag flag) const; 69 int height, ScreenShotDecoderFlag flag) const;
67 70
71 AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
72
68 bool is_mounted{}; 73 bool is_mounted{};
69 std::unordered_map<AlbumFileId, std::filesystem::path> album_files; 74 std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
75
76 Core::System& system;
70}; 77};
71 78
72} // namespace Service::Capture 79} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h
index bf6061273..7fd357954 100644
--- a/src/core/hle/service/caps/caps_types.h
+++ b/src/core/hle/service/caps/caps_types.h
@@ -41,13 +41,13 @@ enum class ScreenShotDecoderFlag : u64 {
41 41
42// This is nn::capsrv::AlbumFileDateTime 42// This is nn::capsrv::AlbumFileDateTime
43struct AlbumFileDateTime { 43struct AlbumFileDateTime {
44 u16 year{}; 44 s16 year{};
45 u8 month{}; 45 s8 month{};
46 u8 day{}; 46 s8 day{};
47 u8 hour{}; 47 s8 hour{};
48 u8 minute{}; 48 s8 minute{};
49 u8 second{}; 49 s8 second{};
50 u8 unique_id{}; 50 s8 unique_id{};
51 51
52 friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default; 52 friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
53 friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) { 53 friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index 260f25490..b6b33fb2f 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -50,22 +50,35 @@ void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
50 50
51void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) { 51void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
52 IPC::RequestParser rp{ctx}; 52 IPC::RequestParser rp{ctx};
53 const auto pid{rp.Pop<s32>()}; 53 struct Parameters {
54 const auto content_type{rp.PopEnum<ContentType>()}; 54 ContentType content_type;
55 const auto start_posix_time{rp.Pop<s64>()}; 55 INSERT_PADDING_BYTES(7);
56 const auto end_posix_time{rp.Pop<s64>()}; 56 s64 start_posix_time;
57 const auto applet_resource_user_id{rp.Pop<u64>()}; 57 s64 end_posix_time;
58 u64 applet_resource_user_id;
59 };
60 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
61
62 const auto parameters{rp.PopRaw<Parameters>()};
58 63
59 LOG_WARNING(Service_Capture, 64 LOG_WARNING(Service_Capture,
60 "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " 65 "(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, "
61 "end_posix_time={}, applet_resource_user_id={}", 66 "applet_resource_user_id={}",
62 pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id); 67 parameters.content_type, parameters.start_posix_time, parameters.end_posix_time,
68 parameters.applet_resource_user_id);
63 69
64 // TODO: Translate posix to DateTime 70 Result result = ResultSuccess;
71
72 if (result.IsSuccess()) {
73 result = manager->IsAlbumMounted(AlbumStorage::Sd);
74 }
65 75
66 std::vector<ApplicationAlbumFileEntry> entries; 76 std::vector<ApplicationAlbumFileEntry> entries;
67 const Result result = 77 if (result.IsSuccess()) {
68 manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id); 78 result = manager->GetAlbumFileList(entries, parameters.content_type,
79 parameters.start_posix_time, parameters.end_posix_time,
80 parameters.applet_resource_user_id);
81 }
69 82
70 if (!entries.empty()) { 83 if (!entries.empty()) {
71 ctx.WriteBuffer(entries); 84 ctx.WriteBuffer(entries);
@@ -78,19 +91,38 @@ void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestCo
78 91
79void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) { 92void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
80 IPC::RequestParser rp{ctx}; 93 IPC::RequestParser rp{ctx};
81 const auto pid{rp.Pop<s32>()}; 94 struct Parameters {
82 const auto content_type{rp.PopEnum<ContentType>()}; 95 ContentType content_type;
83 const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()}; 96 INSERT_PADDING_BYTES(1);
84 const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()}; 97 AlbumFileDateTime start_date_time;
85 const auto applet_resource_user_id{rp.Pop<u64>()}; 98 AlbumFileDateTime end_date_time;
99 INSERT_PADDING_BYTES(6);
100 u64 applet_resource_user_id;
101 };
102 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
103
104 const auto parameters{rp.PopRaw<Parameters>()};
86 105
87 LOG_WARNING(Service_Capture, 106 LOG_WARNING(Service_Capture,
88 "(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid, 107 "(STUBBED) called. content_type={}, start_date={}/{}/{}, "
89 content_type, applet_resource_user_id); 108 "end_date={}/{}/{}, applet_resource_user_id={}",
109 parameters.content_type, parameters.start_date_time.year,
110 parameters.start_date_time.month, parameters.start_date_time.day,
111 parameters.end_date_time.year, parameters.end_date_time.month,
112 parameters.end_date_time.day, parameters.applet_resource_user_id);
90 113
91 std::vector<ApplicationAlbumFileEntry> entries; 114 Result result = ResultSuccess;
92 const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time, 115
93 end_date_time, applet_resource_user_id); 116 if (result.IsSuccess()) {
117 result = manager->IsAlbumMounted(AlbumStorage::Sd);
118 }
119
120 std::vector<ApplicationAlbumEntry> entries;
121 if (result.IsSuccess()) {
122 result =
123 manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time,
124 parameters.end_date_time, parameters.applet_resource_user_id);
125 }
94 126
95 if (!entries.empty()) { 127 if (!entries.empty()) {
96 ctx.WriteBuffer(entries); 128 ctx.WriteBuffer(entries);
diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp
index 4ed3f02e2..0090e8568 100644
--- a/src/core/hle/service/jit/jit_context.cpp
+++ b/src/core/hle/service/jit/jit_context.cpp
@@ -156,6 +156,8 @@ public:
156 156
157 bool LoadNRO(std::span<const u8> data) { 157 bool LoadNRO(std::span<const u8> data) {
158 local_memory.clear(); 158 local_memory.clear();
159
160 relocbase = local_memory.size();
159 local_memory.insert(local_memory.end(), data.begin(), data.end()); 161 local_memory.insert(local_memory.end(), data.begin(), data.end());
160 162
161 if (FixupRelocations()) { 163 if (FixupRelocations()) {
@@ -181,8 +183,8 @@ public:
181 // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html 183 // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
182 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html 184 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
183 VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; 185 VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
184 VAddr rela_dyn = 0; 186 VAddr rela_dyn = 0, relr_dyn = 0;
185 size_t num_rela = 0; 187 size_t num_rela = 0, num_relr = 0;
186 while (true) { 188 while (true) {
187 const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; 189 const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
188 dynamic_offset += sizeof(Elf64_Dyn); 190 dynamic_offset += sizeof(Elf64_Dyn);
@@ -196,6 +198,12 @@ public:
196 if (dyn.d_tag == ElfDtRelasz) { 198 if (dyn.d_tag == ElfDtRelasz) {
197 num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); 199 num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
198 } 200 }
201 if (dyn.d_tag == ElfDtRelr) {
202 relr_dyn = dyn.d_un.d_ptr;
203 }
204 if (dyn.d_tag == ElfDtRelrsz) {
205 num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
206 }
199 } 207 }
200 208
201 for (size_t i = 0; i < num_rela; i++) { 209 for (size_t i = 0; i < num_rela; i++) {
@@ -207,6 +215,29 @@ public:
207 callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); 215 callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
208 } 216 }
209 217
218 VAddr relr_where = 0;
219 for (size_t i = 0; i < num_relr; i++) {
220 const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
221 const auto incr{[&](VAddr where) {
222 callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
223 }};
224
225 if ((relr & 1) == 0) {
226 // where pointer
227 relr_where = relocbase + relr;
228 incr(relr_where);
229 relr_where += sizeof(Elf64_Addr);
230 } else {
231 // bitmap
232 for (int bit = 1; bit < 64; bit++) {
233 if ((relr & (1ULL << bit)) != 0) {
234 incr(relr_where + i * sizeof(Elf64_Addr));
235 }
236 }
237 relr_where += 63 * sizeof(Elf64_Addr);
238 }
239 }
240
210 return true; 241 return true;
211 } 242 }
212 243
@@ -313,6 +344,7 @@ public:
313 Core::Memory::Memory& memory; 344 Core::Memory::Memory& memory;
314 VAddr top_of_stack; 345 VAddr top_of_stack;
315 VAddr heap_pointer; 346 VAddr heap_pointer;
347 VAddr relocbase;
316}; 348};
317 349
318void DynarmicCallbacks64::CallSVC(u32 swi) { 350void DynarmicCallbacks64::CallSVC(u32 swi) {
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 808b21069..77db60e92 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -338,6 +338,7 @@ void UDPClient::StartCommunication(std::size_t client, const std::string& host,
338 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { 338 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
339 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); 339 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
340 PreSetController(identifier); 340 PreSetController(identifier);
341 PreSetMotion(identifier, 0);
341 } 342 }
342} 343}
343 344
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 9e90c587c..9b2698fad 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -544,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
544 it++; 544 it++;
545 } 545 }
546 546
547 boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads; 547 boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads;
548 u64 total_size_bytes = 0; 548 u64 total_size_bytes = 0;
549 u64 largest_copy = 0; 549 u64 largest_copy = 0;
550 for (const IntervalSet& intervals : committed_ranges) { 550 for (const IntervalSet& intervals : committed_ranges) {
@@ -914,6 +914,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
914 914
915 const u32 offset = buffer.Offset(binding.cpu_addr); 915 const u32 offset = buffer.Offset(binding.cpu_addr);
916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; 916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
917
918 if (is_written) {
919 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
920 }
921
917 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 922 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
918 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); 923 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
919 ++binding_index; 924 ++binding_index;
@@ -931,6 +936,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
931 const u32 size = binding.size; 936 const u32 size = binding.size;
932 SynchronizeBuffer(buffer, binding.cpu_addr, size); 937 SynchronizeBuffer(buffer, binding.cpu_addr, size);
933 938
939 const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0;
940 if (is_written) {
941 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
942 }
943
934 const u32 offset = buffer.Offset(binding.cpu_addr); 944 const u32 offset = buffer.Offset(binding.cpu_addr);
935 const PixelFormat format = binding.format; 945 const PixelFormat format = binding.format;
936 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 946 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -962,6 +972,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
962 const u32 size = binding.size; 972 const u32 size = binding.size;
963 SynchronizeBuffer(buffer, binding.cpu_addr, size); 973 SynchronizeBuffer(buffer, binding.cpu_addr, size);
964 974
975 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
976
965 const u32 offset = buffer.Offset(binding.cpu_addr); 977 const u32 offset = buffer.Offset(binding.cpu_addr);
966 host_bindings.buffers.push_back(&buffer); 978 host_bindings.buffers.push_back(&buffer);
967 host_bindings.offsets.push_back(offset); 979 host_bindings.offsets.push_back(offset);
@@ -1011,6 +1023,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
1011 const u32 offset = buffer.Offset(binding.cpu_addr); 1023 const u32 offset = buffer.Offset(binding.cpu_addr);
1012 const bool is_written = 1024 const bool is_written =
1013 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; 1025 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
1026
1027 if (is_written) {
1028 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
1029 }
1030
1014 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 1031 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
1015 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); 1032 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
1016 ++binding_index; 1033 ++binding_index;
@@ -1028,6 +1045,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
1028 const u32 size = binding.size; 1045 const u32 size = binding.size;
1029 SynchronizeBuffer(buffer, binding.cpu_addr, size); 1046 SynchronizeBuffer(buffer, binding.cpu_addr, size);
1030 1047
1048 const bool is_written =
1049 ((channel_state->written_compute_texture_buffers >> index) & 1) != 0;
1050 if (is_written) {
1051 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
1052 }
1053
1031 const u32 offset = buffer.Offset(binding.cpu_addr); 1054 const u32 offset = buffer.Offset(binding.cpu_addr);
1032 const PixelFormat format = binding.format; 1055 const PixelFormat format = binding.format;
1033 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 1056 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -1201,16 +1224,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
1201 1224
1202template <class P> 1225template <class P>
1203void BufferCache<P>::UpdateStorageBuffers(size_t stage) { 1226void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
1204 const u32 written_mask = channel_state->written_storage_buffers[stage];
1205 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { 1227 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
1206 // Resolve buffer 1228 // Resolve buffer
1207 Binding& binding = channel_state->storage_buffers[stage][index]; 1229 Binding& binding = channel_state->storage_buffers[stage][index];
1208 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1230 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1209 binding.buffer_id = buffer_id; 1231 binding.buffer_id = buffer_id;
1210 // Mark buffer as written if needed
1211 if (((written_mask >> index) & 1) != 0) {
1212 MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
1213 }
1214 }); 1232 });
1215} 1233}
1216 1234
@@ -1219,10 +1237,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
1219 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { 1237 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
1220 Binding& binding = channel_state->texture_buffers[stage][index]; 1238 Binding& binding = channel_state->texture_buffers[stage][index];
1221 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1239 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1222 // Mark buffer as written if needed
1223 if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
1224 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1225 }
1226 }); 1240 });
1227} 1241}
1228 1242
@@ -1252,7 +1266,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
1252 .size = size, 1266 .size = size,
1253 .buffer_id = buffer_id, 1267 .buffer_id = buffer_id,
1254 }; 1268 };
1255 MarkWrittenBuffer(buffer_id, *cpu_addr, size);
1256} 1269}
1257 1270
1258template <class P> 1271template <class P>
@@ -1279,10 +1292,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
1279 // Resolve buffer 1292 // Resolve buffer
1280 Binding& binding = channel_state->compute_storage_buffers[index]; 1293 Binding& binding = channel_state->compute_storage_buffers[index];
1281 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1294 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1282 // Mark as written if needed
1283 if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
1284 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1285 }
1286 }); 1295 });
1287} 1296}
1288 1297
@@ -1291,18 +1300,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
1291 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { 1300 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
1292 Binding& binding = channel_state->compute_texture_buffers[index]; 1301 Binding& binding = channel_state->compute_texture_buffers[index];
1293 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1302 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1294 // Mark as written if needed
1295 if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
1296 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1297 }
1298 }); 1303 });
1299} 1304}
1300 1305
1301template <class P> 1306template <class P>
1302void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { 1307void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
1303 if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
1304 SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
1305 }
1306 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); 1308 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
1307 1309
1308 const IntervalType base_interval{cpu_addr, cpu_addr + size}; 1310 const IntervalType base_interval{cpu_addr, cpu_addr + size};
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index c4f6e8d12..eed267361 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -62,7 +62,11 @@ using BufferId = SlotId;
62using VideoCore::Surface::PixelFormat; 62using VideoCore::Surface::PixelFormat;
63using namespace Common::Literals; 63using namespace Common::Literals;
64 64
65#ifdef __APPLE__
66constexpr u32 NUM_VERTEX_BUFFERS = 16;
67#else
65constexpr u32 NUM_VERTEX_BUFFERS = 32; 68constexpr u32 NUM_VERTEX_BUFFERS = 32;
69#endif
66constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4; 70constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
67constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18; 71constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
68constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; 72constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp
index f34090791..d77ff455b 100644
--- a/src/video_core/engines/draw_manager.cpp
+++ b/src/video_core/engines/draw_manager.cpp
@@ -48,8 +48,14 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
48 SetInlineIndexBuffer(regs.inline_index_4x8.index3); 48 SetInlineIndexBuffer(regs.inline_index_4x8.index3);
49 break; 49 break;
50 case MAXWELL3D_REG_INDEX(vertex_array_instance_first): 50 case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
51 DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
52 regs.vertex_array_instance_first.start.Value(),
53 regs.vertex_array_instance_first.count.Value(), false);
54 break;
51 case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): { 55 case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
52 LOG_WARNING(HW_GPU, "(STUBBED) called"); 56 DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
57 regs.vertex_array_instance_subsequent.start.Value(),
58 regs.vertex_array_instance_subsequent.count.Value(), true);
53 break; 59 break;
54 } 60 }
55 case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { 61 case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
@@ -84,6 +90,22 @@ void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 ve
84 ProcessDraw(false, num_instances); 90 ProcessDraw(false, num_instances);
85} 91}
86 92
93void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
94 bool subsequent) {
95 draw_state.topology = topology;
96 draw_state.vertex_buffer.first = vertex_first;
97 draw_state.vertex_buffer.count = vertex_count;
98
99 if (!subsequent) {
100 draw_state.instance_count = 1;
101 }
102
103 draw_state.base_instance = draw_state.instance_count - 1;
104 draw_state.draw_mode = DrawMode::Instance;
105 draw_state.instance_count++;
106 ProcessDraw(false, 1);
107}
108
87void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, 109void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
88 u32 base_index, u32 base_instance, u32 num_instances) { 110 u32 base_index, u32 base_instance, u32 num_instances) {
89 const auto& regs{maxwell3d->regs}; 111 const auto& regs{maxwell3d->regs};
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h
index 18d959143..cfc8127fc 100644
--- a/src/video_core/engines/draw_manager.h
+++ b/src/video_core/engines/draw_manager.h
@@ -66,6 +66,8 @@ public:
66 66
67 void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, 67 void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
68 u32 base_instance, u32 num_instances); 68 u32 base_instance, u32 num_instances);
69 void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
70 bool subsequent);
69 71
70 void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, 72 void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
71 u32 base_instance, u32 num_instances); 73 u32 base_instance, u32 num_instances);
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index 8d7da50fc..dbcf508e5 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -137,16 +137,6 @@ bool Codec::CreateGpuAvDevice() {
137 break; 137 break;
138 } 138 }
139 if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) { 139 if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) {
140#if defined(__unix__)
141 // Some linux decoding backends are reported to crash with this config method
142 // TODO(ameerj): Properly support this method
143 if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0) {
144 // skip zero-copy decoders, we don't currently support them
145 LOG_DEBUG(Service_NVDRV, "Skipping decoder {} with unsupported capability {}.",
146 av_hwdevice_get_type_name(type), config->methods);
147 continue;
148 }
149#endif
150 LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); 140 LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
151 av_codec_ctx->pix_fmt = config->pix_fmt; 141 av_codec_ctx->pix_fmt = config->pix_fmt;
152 return true; 142 return true;
diff --git a/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag b/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
index d33131d7c..b81a54056 100644
--- a/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
+++ b/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
@@ -3,16 +3,16 @@
3 3
4#version 450 4#version 450
5 5
6precision mediump int;
7precision highp float;
8
6layout(binding = 0) uniform sampler2D depth_tex; 9layout(binding = 0) uniform sampler2D depth_tex;
7layout(binding = 1) uniform isampler2D stencil_tex; 10layout(binding = 1) uniform usampler2D stencil_tex;
8 11
9layout(location = 0) out vec4 color; 12layout(location = 0) out vec4 color;
10 13
11void main() { 14void main() {
12 ivec2 coord = ivec2(gl_FragCoord.xy); 15 ivec2 coord = ivec2(gl_FragCoord.xy);
13 uint depth = uint(textureLod(depth_tex, coord, 0).r * (exp2(24.0) - 1.0f));
14 uint stencil = uint(textureLod(stencil_tex, coord, 0).r);
15
16 highp uint depth_val = 16 highp uint depth_val =
17 uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0)); 17 uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0));
18 lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r; 18 lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r;
diff --git a/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag b/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag
index 31db7d426..6a457981d 100644
--- a/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag
+++ b/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag
@@ -3,16 +3,16 @@
3 3
4#version 450 4#version 450
5 5
6precision mediump int;
7precision highp float;
8
6layout(binding = 0) uniform sampler2D depth_tex; 9layout(binding = 0) uniform sampler2D depth_tex;
7layout(binding = 1) uniform isampler2D stencil_tex; 10layout(binding = 1) uniform usampler2D stencil_tex;
8 11
9layout(location = 0) out vec4 color; 12layout(location = 0) out vec4 color;
10 13
11void main() { 14void main() {
12 ivec2 coord = ivec2(gl_FragCoord.xy); 15 ivec2 coord = ivec2(gl_FragCoord.xy);
13 uint depth = uint(textureLod(depth_tex, coord, 0).r * (exp2(24.0) - 1.0f));
14 uint stencil = uint(textureLod(stencil_tex, coord, 0).r);
15
16 highp uint depth_val = 16 highp uint depth_val =
17 uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0)); 17 uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0));
18 lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r; 18 lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 9cafd2983..512eef575 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -1048,6 +1048,10 @@ void Image::Scale(bool up_scale) {
1048} 1048}
1049 1049
1050bool Image::ScaleUp(bool ignore) { 1050bool Image::ScaleUp(bool ignore) {
1051 const auto& resolution = runtime->resolution;
1052 if (!resolution.active) {
1053 return false;
1054 }
1051 if (True(flags & ImageFlagBits::Rescaled)) { 1055 if (True(flags & ImageFlagBits::Rescaled)) {
1052 return false; 1056 return false;
1053 } 1057 }
@@ -1060,9 +1064,6 @@ bool Image::ScaleUp(bool ignore) {
1060 return false; 1064 return false;
1061 } 1065 }
1062 flags |= ImageFlagBits::Rescaled; 1066 flags |= ImageFlagBits::Rescaled;
1063 if (!runtime->resolution.active) {
1064 return false;
1065 }
1066 has_scaled = true; 1067 has_scaled = true;
1067 if (ignore) { 1068 if (ignore) {
1068 current_texture = upscaled_backup.handle; 1069 current_texture = upscaled_backup.handle;
@@ -1073,13 +1074,14 @@ bool Image::ScaleUp(bool ignore) {
1073} 1074}
1074 1075
1075bool Image::ScaleDown(bool ignore) { 1076bool Image::ScaleDown(bool ignore) {
1076 if (False(flags & ImageFlagBits::Rescaled)) { 1077 const auto& resolution = runtime->resolution;
1078 if (!resolution.active) {
1077 return false; 1079 return false;
1078 } 1080 }
1079 flags &= ~ImageFlagBits::Rescaled; 1081 if (False(flags & ImageFlagBits::Rescaled)) {
1080 if (!runtime->resolution.active) {
1081 return false; 1082 return false;
1082 } 1083 }
1084 flags &= ~ImageFlagBits::Rescaled;
1083 if (ignore) { 1085 if (ignore) {
1084 current_texture = texture.handle; 1086 current_texture = texture.handle;
1085 return true; 1087 return true;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 3676eaaa9..e71b87e99 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -118,6 +118,8 @@ public:
118 118
119 void InsertUploadMemoryBarrier(); 119 void InsertUploadMemoryBarrier();
120 120
121 void TransitionImageLayout(Image& image) {}
122
121 FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; 123 FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
122 124
123 bool HasNativeBgr() const noexcept { 125 bool HasNativeBgr() const noexcept {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 83f2b6045..61d03daae 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -975,6 +975,19 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs
975 if (!state_tracker.TouchScissors()) { 975 if (!state_tracker.TouchScissors()) {
976 return; 976 return;
977 } 977 }
978 if (!regs.viewport_scale_offset_enabled) {
979 const auto x = static_cast<float>(regs.surface_clip.x);
980 const auto y = static_cast<float>(regs.surface_clip.y);
981 const auto width = static_cast<float>(regs.surface_clip.width);
982 const auto height = static_cast<float>(regs.surface_clip.height);
983 VkRect2D scissor;
984 scissor.offset.x = static_cast<u32>(x);
985 scissor.offset.y = static_cast<u32>(y);
986 scissor.extent.width = static_cast<u32>(width != 0.0f ? width : 1.0f);
987 scissor.extent.height = static_cast<u32>(height != 0.0f ? height : 1.0f);
988 scheduler.Record([scissor](vk::CommandBuffer cmdbuf) { cmdbuf.SetScissor(0, scissor); });
989 return;
990 }
978 u32 up_scale = 1; 991 u32 up_scale = 1;
979 u32 down_shift = 0; 992 u32 down_shift = 0;
980 if (texture_cache.IsRescaling()) { 993 if (texture_cache.IsRescaling()) {
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 00ab47268..93773a69f 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1530,15 +1530,15 @@ bool Image::IsRescaled() const noexcept {
1530} 1530}
1531 1531
1532bool Image::ScaleUp(bool ignore) { 1532bool Image::ScaleUp(bool ignore) {
1533 const auto& resolution = runtime->resolution;
1534 if (!resolution.active) {
1535 return false;
1536 }
1533 if (True(flags & ImageFlagBits::Rescaled)) { 1537 if (True(flags & ImageFlagBits::Rescaled)) {
1534 return false; 1538 return false;
1535 } 1539 }
1536 ASSERT(info.type != ImageType::Linear); 1540 ASSERT(info.type != ImageType::Linear);
1537 flags |= ImageFlagBits::Rescaled; 1541 flags |= ImageFlagBits::Rescaled;
1538 const auto& resolution = runtime->resolution;
1539 if (!resolution.active) {
1540 return false;
1541 }
1542 has_scaled = true; 1542 has_scaled = true;
1543 if (!scaled_image) { 1543 if (!scaled_image) {
1544 const bool is_2d = info.type == ImageType::e2D; 1544 const bool is_2d = info.type == ImageType::e2D;
@@ -1567,15 +1567,15 @@ bool Image::ScaleUp(bool ignore) {
1567} 1567}
1568 1568
1569bool Image::ScaleDown(bool ignore) { 1569bool Image::ScaleDown(bool ignore) {
1570 const auto& resolution = runtime->resolution;
1571 if (!resolution.active) {
1572 return false;
1573 }
1570 if (False(flags & ImageFlagBits::Rescaled)) { 1574 if (False(flags & ImageFlagBits::Rescaled)) {
1571 return false; 1575 return false;
1572 } 1576 }
1573 ASSERT(info.type != ImageType::Linear); 1577 ASSERT(info.type != ImageType::Linear);
1574 flags &= ~ImageFlagBits::Rescaled; 1578 flags &= ~ImageFlagBits::Rescaled;
1575 const auto& resolution = runtime->resolution;
1576 if (!resolution.active) {
1577 return false;
1578 }
1579 current_image = *original_image; 1579 current_image = *original_image;
1580 if (ignore) { 1580 if (ignore) {
1581 return true; 1581 return true;
@@ -2013,4 +2013,32 @@ void TextureCacheRuntime::AccelerateImageUpload(
2013 ASSERT(false); 2013 ASSERT(false);
2014} 2014}
2015 2015
2016void TextureCacheRuntime::TransitionImageLayout(Image& image) {
2017 if (!image.ExchangeInitialization()) {
2018 VkImageMemoryBarrier barrier{
2019 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
2020 .pNext = nullptr,
2021 .srcAccessMask = VK_ACCESS_NONE,
2022 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
2023 .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
2024 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
2025 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
2026 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
2027 .image = image.Handle(),
2028 .subresourceRange{
2029 .aspectMask = image.AspectMask(),
2030 .baseMipLevel = 0,
2031 .levelCount = VK_REMAINING_MIP_LEVELS,
2032 .baseArrayLayer = 0,
2033 .layerCount = VK_REMAINING_ARRAY_LAYERS,
2034 },
2035 };
2036 scheduler.RequestOutsideRenderPassOperationContext();
2037 scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) {
2038 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
2039 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
2040 });
2041 }
2042}
2043
2016} // namespace Vulkan 2044} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index d6c5a15cc..7a0807709 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -92,6 +92,8 @@ public:
92 92
93 void InsertUploadMemoryBarrier() {} 93 void InsertUploadMemoryBarrier() {}
94 94
95 void TransitionImageLayout(Image& image);
96
95 bool HasBrokenTextureViewFormats() const noexcept { 97 bool HasBrokenTextureViewFormats() const noexcept {
96 // No known Vulkan driver has broken image views 98 // No known Vulkan driver has broken image views
97 return false; 99 return false;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 1bdb0def5..d575c57ca 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1016,6 +1016,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
1016 1016
1017 if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { 1017 if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
1018 LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); 1018 LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
1019 runtime.TransitionImageLayout(image);
1019 return; 1020 return;
1020 } 1021 }
1021 if (True(image.flags & ImageFlagBits::AsynchronousDecode)) { 1022 if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 8151cabf0..15596c925 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -167,6 +167,13 @@ template <u32 GOB_EXTENT>
167} 167}
168 168
169[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { 169[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) {
170 if (level == 0 && info.num_levels == 1) {
171 return Extent3D{
172 .width = info.block.width,
173 .height = info.block.height,
174 .depth = info.block.depth,
175 };
176 }
170 const Extent3D blocks = NumLevelBlocks(info, level); 177 const Extent3D blocks = NumLevelBlocks(info, level);
171 return Extent3D{ 178 return Extent3D{
172 .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width), 179 .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width),
@@ -1293,9 +1300,9 @@ u32 MapSizeBytes(const ImageBase& image) {
1293 1300
1294static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) == 1301static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) ==
1295 0x7f8000); 1302 0x7f8000);
1296static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x4000); 1303static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x40000);
1297 1304
1298static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x4000); 1305static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x40000);
1299 1306
1300static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == 1307static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) ==
1301 0x2afc00); 1308 0x2afc00);
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 82767fdf0..8dd1667f3 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -66,9 +66,10 @@ struct Range {
66 switch (usage) { 66 switch (usage) {
67 case MemoryUsage::Upload: 67 case MemoryUsage::Upload:
68 case MemoryUsage::Stream: 68 case MemoryUsage::Stream:
69 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; 69 return VMA_ALLOCATION_CREATE_MAPPED_BIT |
70 VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
70 case MemoryUsage::Download: 71 case MemoryUsage::Download:
71 return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; 72 return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
72 case MemoryUsage::DeviceLocal: 73 case MemoryUsage::DeviceLocal:
73 return {}; 74 return {};
74 } 75 }
@@ -252,8 +253,7 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
252 253
253vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { 254vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
254 const VmaAllocationCreateInfo alloc_ci = { 255 const VmaAllocationCreateInfo alloc_ci = {
255 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT | 256 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
256 MemoryUsageVmaFlags(usage),
257 .usage = MemoryUsageVma(usage), 257 .usage = MemoryUsageVma(usage),
258 .requiredFlags = 0, 258 .requiredFlags = 0,
259 .preferredFlags = MemoryUsagePreferedVmaFlags(usage), 259 .preferredFlags = MemoryUsagePreferedVmaFlags(usage),
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 9ebece907..34208ed74 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -384,7 +384,7 @@ if (USE_DISCORD_PRESENCE)
384 discord_impl.cpp 384 discord_impl.cpp
385 discord_impl.h 385 discord_impl.h
386 ) 386 )
387 target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib) 387 target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network)
388 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) 388 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
389endif() 389endif()
390 390
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index d15559518..ca0e14fad 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -23,6 +23,7 @@
23#include "yuzu/configuration/configure_vibration.h" 23#include "yuzu/configuration/configure_vibration.h"
24#include "yuzu/configuration/input_profiles.h" 24#include "yuzu/configuration/input_profiles.h"
25#include "yuzu/main.h" 25#include "yuzu/main.h"
26#include "yuzu/util/controller_navigation.h"
26 27
27namespace { 28namespace {
28 29
@@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
132 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, 133 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
133 }; 134 };
134 135
136 ui->labelError->setVisible(false);
137
135 // Setup/load everything prior to setting up connections. 138 // Setup/load everything prior to setting up connections.
136 // This avoids unintentionally changing the states of elements while loading them in. 139 // This avoids unintentionally changing the states of elements while loading them in.
137 SetSupportedControllers(); 140 SetSupportedControllers();
@@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
143 146
144 LoadConfiguration(); 147 LoadConfiguration();
145 148
149 controller_navigation = new ControllerNavigation(system.HIDCore(), this);
150
146 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { 151 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
147 SetExplainText(i); 152 SetExplainText(i);
148 UpdateControllerIcon(i); 153 UpdateControllerIcon(i);
@@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
151 156
152 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { 157 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
153 if (checked) { 158 if (checked) {
159 // Hide eventual error message about number of controllers
160 ui->labelError->setVisible(false);
154 for (std::size_t index = 0; index <= i; ++index) { 161 for (std::size_t index = 0; index <= i; ++index) {
155 connected_controller_checkboxes[index]->setChecked(checked); 162 connected_controller_checkboxes[index]->setChecked(checked);
156 } 163 }
@@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
199 connect(ui->buttonBox, &QDialogButtonBox::accepted, this, 206 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
200 &QtControllerSelectorDialog::ApplyConfiguration); 207 &QtControllerSelectorDialog::ApplyConfiguration);
201 208
209 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
210 [this](Qt::Key key) {
211 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
212 QCoreApplication::postEvent(this, event);
213 });
214
202 // Enhancement: Check if the parameters have already been met before disconnecting controllers. 215 // Enhancement: Check if the parameters have already been met before disconnecting controllers.
203 // If all the parameters are met AND only allows a single player, 216 // If all the parameters are met AND only allows a single player,
204 // stop the constructor here as we do not need to continue. 217 // stop the constructor here as we do not need to continue.
@@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
217} 230}
218 231
219QtControllerSelectorDialog::~QtControllerSelectorDialog() { 232QtControllerSelectorDialog::~QtControllerSelectorDialog() {
233 controller_navigation->UnloadController();
220 system.HIDCore().DisableAllControllerConfiguration(); 234 system.HIDCore().DisableAllControllerConfiguration();
221} 235}
222 236
@@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
291 dialog.exec(); 305 dialog.exec();
292} 306}
293 307
308void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
309 const auto num_connected_players = static_cast<int>(
310 std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
311 [](const QGroupBox* player) { return player->isChecked(); }));
312
313 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
314 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
315
316 if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
317 // Display error message when trying to validate using "Enter" and "OK" button is disabled
318 ui->labelError->setVisible(true);
319 return;
320 } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
321 // Remove a player if possible
322 connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
323 return;
324 } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
325 // Add a player, if possible
326 ui->labelError->setVisible(false);
327 connected_controller_checkboxes[num_connected_players]->setChecked(true);
328 return;
329 }
330 QDialog::keyPressEvent(evt);
331}
332
294bool QtControllerSelectorDialog::CheckIfParametersMet() { 333bool QtControllerSelectorDialog::CheckIfParametersMet() {
295 // Here, we check and validate the current configuration against all applicable parameters. 334 // Here, we check and validate the current configuration against all applicable parameters.
296 const auto num_connected_players = static_cast<int>( 335 const auto num_connected_players = static_cast<int>(
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 2fdc35857..7f0673d06 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -34,6 +34,8 @@ class HIDCore;
34enum class NpadStyleIndex : u8; 34enum class NpadStyleIndex : u8;
35} // namespace Core::HID 35} // namespace Core::HID
36 36
37class ControllerNavigation;
38
37class QtControllerSelectorDialog final : public QDialog { 39class QtControllerSelectorDialog final : public QDialog {
38 Q_OBJECT 40 Q_OBJECT
39 41
@@ -46,6 +48,8 @@ public:
46 48
47 int exec() override; 49 int exec() override;
48 50
51 void keyPressEvent(QKeyEvent* evt) override;
52
49private: 53private:
50 // Applies the current configuration. 54 // Applies the current configuration.
51 void ApplyConfiguration(); 55 void ApplyConfiguration();
@@ -110,6 +114,8 @@ private:
110 114
111 Core::System& system; 115 Core::System& system;
112 116
117 ControllerNavigation* controller_navigation = nullptr;
118
113 // This is true if and only if all parameters are met. Otherwise, this is false. 119 // This is true if and only if all parameters are met. Otherwise, this is false.
114 // This determines whether the "OK" button can be clicked to exit the applet. 120 // This determines whether the "OK" button can be clicked to exit the applet.
115 bool parameters_met{false}; 121 bool parameters_met{false};
diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui
index 729e921ee..6f7cb3c13 100644
--- a/src/yuzu/applets/qt_controller.ui
+++ b/src/yuzu/applets/qt_controller.ui
@@ -2624,13 +2624,53 @@
2624 </spacer> 2624 </spacer>
2625 </item> 2625 </item>
2626 <item alignment="Qt::AlignBottom"> 2626 <item alignment="Qt::AlignBottom">
2627 <widget class="QDialogButtonBox" name="buttonBox"> 2627 <widget class="QWidget" name="closeButtons" native="true">
2628 <property name="enabled"> 2628 <layout class="QVBoxLayout" name="verticalLayout_46">
2629 <bool>true</bool> 2629 <property name="spacing">
2630 </property> 2630 <number>7</number>
2631 <property name="standardButtons"> 2631 </property>
2632 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> 2632 <property name="leftMargin">
2633 </property> 2633 <number>0</number>
2634 </property>
2635 <property name="topMargin">
2636 <number>0</number>
2637 </property>
2638 <property name="rightMargin">
2639 <number>0</number>
2640 </property>
2641 <property name="bottomMargin">
2642 <number>0</number>
2643 </property>
2644 <item>
2645 <widget class="QLabel" name="labelError">
2646 <property name="enabled">
2647 <bool>true</bool>
2648 </property>
2649 <property name="styleSheet">
2650 <string notr="true">QLabel { color : red; }</string>
2651 </property>
2652 <property name="text">
2653 <string>Not enough controllers</string>
2654 </property>
2655 <property name="alignment">
2656 <set>Qt::AlignCenter</set>
2657 </property>
2658 <property name="margin">
2659 <number>0</number>
2660 </property>
2661 </widget>
2662 </item>
2663 <item>
2664 <widget class="QDialogButtonBox" name="buttonBox">
2665 <property name="enabled">
2666 <bool>true</bool>
2667 </property>
2668 <property name="standardButtons">
2669 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
2670 </property>
2671 </widget>
2672 </item>
2673 </layout>
2634 </widget> 2674 </widget>
2635 </item> 2675 </item>
2636 </layout> 2676 </layout>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 1de093447..d5157c502 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -128,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
128 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, 128 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
129 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, 129 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
130 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, 130 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
131 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}}, 131 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}},
132 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}}, 132 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}},
133 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 133 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
134 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 134 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
135 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 135 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index e8f9ebfd8..5a48e388b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -115,17 +115,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
115 for (std::size_t i = 0; i < player_tabs.size(); ++i) { 115 for (std::size_t i = 0; i < player_tabs.size(); ++i) {
116 player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); 116 player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
117 player_tabs[i]->layout()->addWidget(player_controllers[i]); 117 player_tabs[i]->layout()->addWidget(player_controllers[i]);
118 connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { 118 connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) {
119 // Ensures that the controllers are always connected in sequential order 119 // Ensures that the controllers are always connected in sequential order
120 if (is_connected) { 120 this->propagateMouseClickOnPlayers(i, checked, true);
121 for (std::size_t index = 0; index <= i; ++index) {
122 player_connected[index]->setChecked(is_connected);
123 }
124 } else {
125 for (std::size_t index = i; index < player_tabs.size(); ++index) {
126 player_connected[index]->setChecked(is_connected);
127 }
128 }
129 }); 121 });
130 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, 122 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
131 &ConfigureInput::UpdateAllInputDevices); 123 &ConfigureInput::UpdateAllInputDevices);
@@ -183,6 +175,30 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
183 LoadConfiguration(); 175 LoadConfiguration();
184} 176}
185 177
178void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) {
179 // Origin has already been toggled
180 if (!origin) {
181 player_connected[player_index]->setChecked(checked);
182 }
183
184 if (checked) {
185 // Check all previous buttons when checked
186 if (player_index > 0) {
187 propagateMouseClickOnPlayers(player_index - 1, checked, false);
188 }
189 } else {
190 // Unchecked all following buttons when unchecked
191 if (player_index < player_tabs.size() - 1) {
192 // Reconnect current player if it was the last one checked
193 // (player number was reduced by more than one)
194 if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) {
195 player_connected[player_index]->setCheckState(Qt::Checked);
196 }
197 propagateMouseClickOnPlayers(player_index + 1, checked, false);
198 }
199 }
200}
201
186QList<QWidget*> ConfigureInput::GetSubTabs() const { 202QList<QWidget*> ConfigureInput::GetSubTabs() const {
187 return { 203 return {
188 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, 204 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index c89189c36..abb7f7089 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -56,6 +56,7 @@ private:
56 void UpdateDockedState(bool is_handheld); 56 void UpdateDockedState(bool is_handheld);
57 void UpdateAllInputDevices(); 57 void UpdateAllInputDevices();
58 void UpdateAllInputProfiles(std::size_t player_index); 58 void UpdateAllInputProfiles(std::size_t player_index);
59 void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
59 60
60 /// Load configuration settings. 61 /// Load configuration settings.
61 void LoadConfiguration(); 62 void LoadConfiguration();
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index a4e8af1b4..3fe448f27 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -157,6 +157,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
157 INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); 157 INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
158 INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); 158 INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
159 INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); 159 INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
160 INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", "");
160 INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); 161 INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
161 INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); 162 INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
162 163
@@ -383,6 +384,13 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
383 translations->insert( 384 translations->insert(
384 {Settings::EnumMetadata<Settings::ConsoleMode>::Index(), 385 {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
385 {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); 386 {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
387 translations->insert(
388 {Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
389 {
390 PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"),
391 PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"),
392 PAIR(ConfirmStop, Ask_Never, "Never ask"),
393 }});
386 394
387#undef PAIR 395#undef PAIR
388#undef CTX_PAIR 396#undef CTX_PAIR
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 74f48031a..2bb1a0239 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -826,12 +826,13 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
826 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); 826 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
827 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); 827 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
828 828
829 // Before deleting rows, cancel the worker so that it is not using them
830 emit ShouldCancelWorker();
831
829 // Delete any rows that might already exist if we're repopulating 832 // Delete any rows that might already exist if we're repopulating
830 item_model->removeRows(0, item_model->rowCount()); 833 item_model->removeRows(0, item_model->rowCount());
831 search_field->clear(); 834 search_field->clear();
832 835
833 emit ShouldCancelWorker();
834
835 GameListWorker* worker = 836 GameListWorker* worker =
836 new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); 837 new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
837 838
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 588f1dd6e..077ced12b 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -293,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
293void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, 293void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
294 GameListDir* parent_dir) { 294 GameListDir* parent_dir) {
295 const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { 295 const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
296 if (stop_processing) { 296 if (stop_requested) {
297 // Breaks the callback loop. 297 // Breaks the callback loop.
298 return false; 298 return false;
299 } 299 }
@@ -399,7 +399,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
399} 399}
400 400
401void GameListWorker::run() { 401void GameListWorker::run() {
402 stop_processing = false;
403 provider->ClearAllEntries(); 402 provider->ClearAllEntries();
404 403
405 for (UISettings::GameDir& game_dir : game_dirs) { 404 for (UISettings::GameDir& game_dir : game_dirs) {
@@ -427,9 +426,11 @@ void GameListWorker::run() {
427 } 426 }
428 427
429 emit Finished(watch_list); 428 emit Finished(watch_list);
429 processing_completed.Set();
430} 430}
431 431
432void GameListWorker::Cancel() { 432void GameListWorker::Cancel() {
433 this->disconnect(); 433 this->disconnect();
434 stop_processing = true; 434 stop_requested.store(true);
435 processing_completed.Wait();
435} 436}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 2bb0a0cb6..54dc05e30 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -12,6 +12,7 @@
12#include <QRunnable> 12#include <QRunnable>
13#include <QString> 13#include <QString>
14 14
15#include "common/thread.h"
15#include "yuzu/compatibility_list.h" 16#include "yuzu/compatibility_list.h"
16#include "yuzu/play_time_manager.h" 17#include "yuzu/play_time_manager.h"
17 18
@@ -82,7 +83,9 @@ private:
82 const PlayTime::PlayTimeManager& play_time_manager; 83 const PlayTime::PlayTimeManager& play_time_manager;
83 84
84 QStringList watch_list; 85 QStringList watch_list;
85 std::atomic_bool stop_processing; 86
87 Common::Event processing_completed;
88 std::atomic_bool stop_requested = false;
86 89
87 Core::System& system; 90 Core::System& system;
88}; 91};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 5427758c1..1431cf2fe 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -67,6 +67,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
67#define QT_NO_OPENGL 67#define QT_NO_OPENGL
68#include <QClipboard> 68#include <QClipboard>
69#include <QDesktopServices> 69#include <QDesktopServices>
70#include <QDir>
70#include <QFile> 71#include <QFile>
71#include <QFileDialog> 72#include <QFileDialog>
72#include <QInputDialog> 73#include <QInputDialog>
@@ -76,6 +77,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
76#include <QPushButton> 77#include <QPushButton>
77#include <QScreen> 78#include <QScreen>
78#include <QShortcut> 79#include <QShortcut>
80#include <QStandardPaths>
79#include <QStatusBar> 81#include <QStatusBar>
80#include <QString> 82#include <QString>
81#include <QSysInfo> 83#include <QSysInfo>
@@ -209,7 +211,7 @@ void GMainWindow::ShowTelemetryCallout() {
209 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " 211 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
210 "data is collected</a> to help improve yuzu. " 212 "data is collected</a> to help improve yuzu. "
211 "<br/><br/>Would you like to share your usage data with us?"); 213 "<br/><br/>Would you like to share your usage data with us?");
212 if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { 214 if (!question(this, tr("Telemetry"), telemetry_message)) {
213 Settings::values.enable_telemetry = false; 215 Settings::values.enable_telemetry = false;
214 system->ApplySettings(); 216 system->ApplySettings();
215 } 217 }
@@ -2418,9 +2420,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
2418 } 2420 }
2419 }(); 2421 }();
2420 2422
2421 if (QMessageBox::question(this, tr("Remove Entry"), entry_question, 2423 if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No,
2422 QMessageBox::Yes | QMessageBox::No, 2424 QMessageBox::No)) {
2423 QMessageBox::No) != QMessageBox::Yes) {
2424 return; 2425 return;
2425 } 2426 }
2426 2427
@@ -2519,8 +2520,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2519 } 2520 }
2520 }(); 2521 }();
2521 2522
2522 if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, 2523 if (!GMainWindow::question(this, tr("Remove File"), question,
2523 QMessageBox::No) != QMessageBox::Yes) { 2524 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
2524 return; 2525 return;
2525 } 2526 }
2526 2527
@@ -2869,44 +2870,50 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2869#endif // __linux__ 2870#endif // __linux__
2870 2871
2871 std::filesystem::path target_directory{}; 2872 std::filesystem::path target_directory{};
2872 // Determine target directory for shortcut
2873#if defined(WIN32)
2874 const char* home = std::getenv("USERPROFILE");
2875#else
2876 const char* home = std::getenv("HOME");
2877#endif
2878 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2879 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2880 2873
2881 if (target == GameListShortcutTarget::Desktop) { 2874 switch (target) {
2882 target_directory = home_path / "Desktop"; 2875 case GameListShortcutTarget::Desktop: {
2883 if (!Common::FS::IsDir(target_directory)) { 2876 const QString desktop_path =
2884 QMessageBox::critical( 2877 QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
2885 this, tr("Create Shortcut"), 2878 target_directory = desktop_path.toUtf8().toStdString();
2886 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") 2879 break;
2887 .arg(QString::fromStdString(target_directory.generic_string())), 2880 }
2888 QMessageBox::StandardButton::Ok); 2881 case GameListShortcutTarget::Applications: {
2889 return; 2882 const QString applications_path =
2890 } 2883 QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
2891 } else if (target == GameListShortcutTarget::Applications) { 2884 if (applications_path.isEmpty()) {
2892 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / 2885 const char* home = std::getenv("HOME");
2893 "applications"; 2886 if (home != nullptr) {
2894 if (!Common::FS::CreateDirs(target_directory)) { 2887 target_directory = std::filesystem::path(home) / ".local/share/applications";
2895 QMessageBox::critical( 2888 }
2896 this, tr("Create Shortcut"), 2889 } else {
2897 tr("Cannot create shortcut in applications menu. Path \"%1\" " 2890 target_directory = applications_path.toUtf8().toStdString();
2898 "does not exist and cannot be created.")
2899 .arg(QString::fromStdString(target_directory.generic_string())),
2900 QMessageBox::StandardButton::Ok);
2901 return;
2902 } 2891 }
2892 break;
2893 }
2894 default:
2895 return;
2896 }
2897
2898 const QDir dir(QString::fromStdString(target_directory.generic_string()));
2899 if (!dir.exists()) {
2900 QMessageBox::critical(this, tr("Create Shortcut"),
2901 tr("Cannot create shortcut. Path \"%1\" does not exist.")
2902 .arg(QString::fromStdString(target_directory.generic_string())),
2903 QMessageBox::StandardButton::Ok);
2904 return;
2903 } 2905 }
2904 2906
2905 const std::string game_file_name = std::filesystem::path(game_path).filename().string(); 2907 const std::string game_file_name = std::filesystem::path(game_path).filename().string();
2906 // Determine full paths for icon and shortcut 2908 // Determine full paths for icon and shortcut
2907#if defined(__linux__) || defined(__FreeBSD__) 2909#if defined(__linux__) || defined(__FreeBSD__)
2910 const char* home = std::getenv("HOME");
2911 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2912 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2913
2908 std::filesystem::path system_icons_path = 2914 std::filesystem::path system_icons_path =
2909 (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) / 2915 (xdg_data_home == nullptr ? home_path / ".local/share/"
2916 : std::filesystem::path(xdg_data_home)) /
2910 "icons/hicolor/256x256"; 2917 "icons/hicolor/256x256";
2911 if (!Common::FS::CreateDirs(system_icons_path)) { 2918 if (!Common::FS::CreateDirs(system_icons_path)) {
2912 QMessageBox::critical( 2919 QMessageBox::critical(
@@ -3401,10 +3408,13 @@ void GMainWindow::OnRestartGame() {
3401 if (!system->IsPoweredOn()) { 3408 if (!system->IsPoweredOn()) {
3402 return; 3409 return;
3403 } 3410 }
3404 // Make a copy since ShutdownGame edits game_path 3411
3405 const auto current_game = QString(current_game_path); 3412 if (ConfirmShutdownGame()) {
3406 ShutdownGame(); 3413 // Make a copy since ShutdownGame edits game_path
3407 BootGame(current_game); 3414 const auto current_game = QString(current_game_path);
3415 ShutdownGame();
3416 BootGame(current_game);
3417 }
3408} 3418}
3409 3419
3410void GMainWindow::OnPauseGame() { 3420void GMainWindow::OnPauseGame() {
@@ -3426,18 +3436,39 @@ void GMainWindow::OnPauseContinueGame() {
3426} 3436}
3427 3437
3428void GMainWindow::OnStopGame() { 3438void GMainWindow::OnStopGame() {
3429 if (system->GetExitLocked() && !ConfirmForceLockedExit()) { 3439 if (ConfirmShutdownGame()) {
3430 return; 3440 play_time_manager->Stop();
3441 // Update game list to show new play time
3442 game_list->PopulateAsync(UISettings::values.game_dirs);
3443 if (OnShutdownBegin()) {
3444 OnShutdownBeginDialog();
3445 } else {
3446 OnEmulationStopped();
3447 }
3431 } 3448 }
3449}
3432 3450
3433 play_time_manager->Stop(); 3451bool GMainWindow::ConfirmShutdownGame() {
3434 // Update game list to show new play time 3452 if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) {
3435 game_list->PopulateAsync(UISettings::values.game_dirs); 3453 if (system->GetExitLocked()) {
3436 if (OnShutdownBegin()) { 3454 if (!ConfirmForceLockedExit()) {
3437 OnShutdownBeginDialog(); 3455 return false;
3456 }
3457 } else {
3458 if (!ConfirmChangeGame()) {
3459 return false;
3460 }
3461 }
3438 } else { 3462 } else {
3439 OnEmulationStopped(); 3463 if (UISettings::values.confirm_before_stopping.GetValue() ==
3464 ConfirmStop::Ask_Based_On_Game &&
3465 system->GetExitLocked()) {
3466 if (!ConfirmForceLockedExit()) {
3467 return false;
3468 }
3469 }
3440 } 3470 }
3471 return true;
3441} 3472}
3442 3473
3443void GMainWindow::OnLoadComplete() { 3474void GMainWindow::OnLoadComplete() {
@@ -3817,22 +3848,11 @@ void GMainWindow::OnTasRecord() {
3817 const bool is_recording = input_subsystem->GetTas()->Record(); 3848 const bool is_recording = input_subsystem->GetTas()->Record();
3818 if (!is_recording) { 3849 if (!is_recording) {
3819 is_tas_recording_dialog_active = true; 3850 is_tas_recording_dialog_active = true;
3820 ControllerNavigation* controller_navigation = 3851
3821 new ControllerNavigation(system->HIDCore(), this); 3852 bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
3822 // Use QMessageBox instead of question so we can link controller navigation 3853 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
3823 QMessageBox* box_dialog = new QMessageBox(); 3854
3824 box_dialog->setWindowTitle(tr("TAS Recording")); 3855 input_subsystem->GetTas()->SaveRecording(answer);
3825 box_dialog->setText(tr("Overwrite file of player 1?"));
3826 box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
3827 box_dialog->setDefaultButton(QMessageBox::Yes);
3828 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
3829 [box_dialog](Qt::Key key) {
3830 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
3831 QCoreApplication::postEvent(box_dialog, event);
3832 });
3833 int res = box_dialog->exec();
3834 controller_navigation->UnloadController();
3835 input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
3836 is_tas_recording_dialog_active = false; 3856 is_tas_recording_dialog_active = false;
3837 } 3857 }
3838 OnTasStateChanged(); 3858 OnTasStateChanged();
@@ -4073,6 +4093,29 @@ void GMainWindow::OnLoadAmiibo() {
4073 LoadAmiibo(filename); 4093 LoadAmiibo(filename);
4074} 4094}
4075 4095
4096bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
4097 QMessageBox::StandardButtons buttons,
4098 QMessageBox::StandardButton defaultButton) {
4099
4100 QMessageBox* box_dialog = new QMessageBox(parent);
4101 box_dialog->setWindowTitle(title);
4102 box_dialog->setText(text);
4103 box_dialog->setStandardButtons(buttons);
4104 box_dialog->setDefaultButton(defaultButton);
4105
4106 ControllerNavigation* controller_navigation =
4107 new ControllerNavigation(system->HIDCore(), box_dialog);
4108 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
4109 [box_dialog](Qt::Key key) {
4110 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
4111 QCoreApplication::postEvent(box_dialog, event);
4112 });
4113 int res = box_dialog->exec();
4114
4115 controller_navigation->UnloadController();
4116 return res == QMessageBox::Yes;
4117}
4118
4076void GMainWindow::LoadAmiibo(const QString& filename) { 4119void GMainWindow::LoadAmiibo(const QString& filename) {
4077 auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); 4120 auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
4078 const QString title = tr("Error loading Amiibo data"); 4121 const QString title = tr("Error loading Amiibo data");
@@ -4806,8 +4849,7 @@ bool GMainWindow::ConfirmClose() {
4806 return true; 4849 return true;
4807 } 4850 }
4808 const auto text = tr("Are you sure you want to close yuzu?"); 4851 const auto text = tr("Are you sure you want to close yuzu?");
4809 const auto answer = QMessageBox::question(this, tr("yuzu"), text); 4852 return question(this, tr("yuzu"), text);
4810 return answer != QMessageBox::No;
4811} 4853}
4812 4854
4813void GMainWindow::closeEvent(QCloseEvent* event) { 4855void GMainWindow::closeEvent(QCloseEvent* event) {
@@ -4900,11 +4942,11 @@ bool GMainWindow::ConfirmChangeGame() {
4900 if (emu_thread == nullptr) 4942 if (emu_thread == nullptr)
4901 return true; 4943 return true;
4902 4944
4903 const auto answer = QMessageBox::question( 4945 // Use custom question to link controller navigation
4946 return question(
4904 this, tr("yuzu"), 4947 this, tr("yuzu"),
4905 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), 4948 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
4906 QMessageBox::Yes | QMessageBox::No, QMessageBox::No); 4949 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
4907 return answer != QMessageBox::No;
4908} 4950}
4909 4951
4910bool GMainWindow::ConfirmForceLockedExit() { 4952bool GMainWindow::ConfirmForceLockedExit() {
@@ -4914,8 +4956,7 @@ bool GMainWindow::ConfirmForceLockedExit() {
4914 const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" 4956 const auto text = tr("The currently running application has requested yuzu to not exit.\n\n"
4915 "Would you like to bypass this and exit anyway?"); 4957 "Would you like to bypass this and exit anyway?");
4916 4958
4917 const auto answer = QMessageBox::question(this, tr("yuzu"), text); 4959 return question(this, tr("yuzu"), text);
4918 return answer != QMessageBox::No;
4919} 4960}
4920 4961
4921void GMainWindow::RequestGameExit() { 4962void GMainWindow::RequestGameExit() {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 2346eb3bd..270a40c5f 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -7,6 +7,7 @@
7#include <optional> 7#include <optional>
8 8
9#include <QMainWindow> 9#include <QMainWindow>
10#include <QMessageBox>
10#include <QTimer> 11#include <QTimer>
11#include <QTranslator> 12#include <QTranslator>
12 13
@@ -15,6 +16,7 @@
15#include "input_common/drivers/tas_input.h" 16#include "input_common/drivers/tas_input.h"
16#include "yuzu/compatibility_list.h" 17#include "yuzu/compatibility_list.h"
17#include "yuzu/hotkeys.h" 18#include "yuzu/hotkeys.h"
19#include "yuzu/util/controller_navigation.h"
18 20
19#ifdef __unix__ 21#ifdef __unix__
20#include <QVariant> 22#include <QVariant>
@@ -424,6 +426,11 @@ private:
424 bool CheckSystemArchiveDecryption(); 426 bool CheckSystemArchiveDecryption();
425 bool CheckFirmwarePresence(); 427 bool CheckFirmwarePresence();
426 void ConfigureFilesystemProvider(const std::string& filepath); 428 void ConfigureFilesystemProvider(const std::string& filepath);
429 /**
430 * Open (or not) the right confirm dialog based on current setting and game exit lock
431 * @returns true if the player confirmed or the settings do no require it
432 */
433 bool ConfirmShutdownGame();
427 434
428 QString GetTasStateDescription() const; 435 QString GetTasStateDescription() const;
429 bool CreateShortcut(const std::string& shortcut_path, const std::string& title, 436 bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
@@ -431,6 +438,17 @@ private:
431 const std::string& command, const std::string& arguments, 438 const std::string& command, const std::string& arguments,
432 const std::string& categories, const std::string& keywords); 439 const std::string& categories, const std::string& keywords);
433 440
441 /**
442 * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
443 * The only difference is that it returns a boolean.
444 *
445 * @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button.
446 */
447 bool question(QWidget* parent, const QString& title, const QString& text,
448 QMessageBox::StandardButtons buttons =
449 QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
450 QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
451
434 std::unique_ptr<Ui::MainWindow> ui; 452 std::unique_ptr<Ui::MainWindow> ui;
435 453
436 std::unique_ptr<Core::System> system; 454 std::unique_ptr<Core::System> system;
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 975008159..b62ff620c 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -16,7 +16,9 @@
16#include "common/settings_enums.h" 16#include "common/settings_enums.h"
17 17
18using Settings::Category; 18using Settings::Category;
19using Settings::ConfirmStop;
19using Settings::Setting; 20using Settings::Setting;
21using Settings::SwitchableSetting;
20 22
21#ifndef CANNOT_EXPLICITLY_INSTANTIATE 23#ifndef CANNOT_EXPLICITLY_INSTANTIATE
22namespace Settings { 24namespace Settings {
@@ -94,6 +96,15 @@ struct Values {
94 Setting<bool> confirm_before_closing{ 96 Setting<bool> confirm_before_closing{
95 linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, 97 linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
96 true, true}; 98 true, true};
99
100 SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
101 ConfirmStop::Ask_Always,
102 "confirmStop",
103 Category::UiGeneral,
104 Settings::Specialization::Default,
105 true,
106 true};
107
97 Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; 108 Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
98 Setting<bool> pause_when_in_background{linkage, 109 Setting<bool> pause_when_in_background{linkage,
99 false, 110 false,
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 61cf00176..f2854c8ec 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -63,25 +63,15 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
63 }; 63 };
64#pragma pack(pop) 64#pragma pack(pop)
65 65
66 QImage source_image = image.convertToFormat(QImage::Format_RGB32); 66 const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
67 constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
67 constexpr int bytes_per_pixel = 4; 68 constexpr int bytes_per_pixel = 4;
68 const int image_size = source_image.width() * source_image.height() * bytes_per_pixel; 69
69 70 const IconDir icon_dir{
70 BITMAPINFOHEADER info_header{}; 71 .id_reserved = 0,
71 info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(), 72 .id_type = 1,
72 info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1, 73 .id_count = static_cast<WORD>(scale_sizes.size()),
73 info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB; 74 };
74
75 const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
76 const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
77 .height = static_cast<BYTE>(source_image.height() * 2),
78 .color_count = 0,
79 .reserved = 0,
80 .planes = 1,
81 .bit_count = bytes_per_pixel * 8,
82 .bytes_in_res =
83 static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
84 .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
85 75
86 Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write, 76 Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
87 Common::FS::FileType::BinaryFile); 77 Common::FS::FileType::BinaryFile);
@@ -92,20 +82,55 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
92 if (!icon_file.Write(icon_dir)) { 82 if (!icon_file.Write(icon_dir)) {
93 return false; 83 return false;
94 } 84 }
95 if (!icon_file.Write(icon_entry)) { 85
96 return false; 86 std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
97 } 87 for (std::size_t i = 0; i < scale_sizes.size(); i++) {
98 if (!icon_file.Write(info_header)) { 88 const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
99 return false; 89 const IconDirEntry icon_entry{
90 .width = static_cast<BYTE>(scale_sizes[i]),
91 .height = static_cast<BYTE>(scale_sizes[i]),
92 .color_count = 0,
93 .reserved = 0,
94 .planes = 1,
95 .bit_count = bytes_per_pixel * 8,
96 .bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
97 .image_offset = static_cast<DWORD>(image_offset),
98 };
99 image_offset += icon_entry.bytes_in_res;
100 if (!icon_file.Write(icon_entry)) {
101 return false;
102 }
100 } 103 }
101 104
102 for (int y = 0; y < image.height(); y++) { 105 for (std::size_t i = 0; i < scale_sizes.size(); i++) {
103 const auto* line = source_image.scanLine(source_image.height() - 1 - y); 106 const QImage scaled_image = source_image.scaled(
104 std::vector<u8> line_data(source_image.width() * bytes_per_pixel); 107 scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
105 std::memcpy(line_data.data(), line, line_data.size()); 108 const BITMAPINFOHEADER info_header{
106 if (!icon_file.Write(line_data)) { 109 .biSize = sizeof(BITMAPINFOHEADER),
110 .biWidth = scaled_image.width(),
111 .biHeight = scaled_image.height() * 2,
112 .biPlanes = 1,
113 .biBitCount = bytes_per_pixel * 8,
114 .biCompression = BI_RGB,
115 .biSizeImage{},
116 .biXPelsPerMeter{},
117 .biYPelsPerMeter{},
118 .biClrUsed{},
119 .biClrImportant{},
120 };
121
122 if (!icon_file.Write(info_header)) {
107 return false; 123 return false;
108 } 124 }
125
126 for (int y = 0; y < scaled_image.height(); y++) {
127 const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
128 std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
129 std::memcpy(line_data.data(), line, line_data.size());
130 if (!icon_file.Write(line_data)) {
131 return false;
132 }
133 }
109 } 134 }
110 icon_file.Close(); 135 icon_file.Close();
111 136