summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.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.kt185
-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/common/common_funcs.h4
-rw-r--r--src/common/elf.h8
-rw-r--r--src/core/hle/kernel/kernel.cpp16
-rw-r--r--src/core/hle/service/caps/caps_a.cpp4
-rw-r--r--src/core/hle/service/jit/jit_context.cpp36
41 files changed, 1095 insertions, 311 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 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..10b1d3547
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -0,0 +1,185 @@
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.IOException
30
31class DriverManagerFragment : Fragment() {
32 private var _binding: FragmentDriverManagerBinding? = null
33 private val binding get() = _binding!!
34
35 private val homeViewModel: HomeViewModel by activityViewModels()
36 private val driverViewModel: DriverViewModel by activityViewModels()
37
38 override fun onCreate(savedInstanceState: Bundle?) {
39 super.onCreate(savedInstanceState)
40 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
41 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
42 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
43 }
44
45 override fun onCreateView(
46 inflater: LayoutInflater,
47 container: ViewGroup?,
48 savedInstanceState: Bundle?
49 ): View {
50 _binding = FragmentDriverManagerBinding.inflate(inflater)
51 return binding.root
52 }
53
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 super.onViewCreated(view, savedInstanceState)
56 homeViewModel.setNavigationVisibility(visible = false, animated = true)
57 homeViewModel.setStatusBarShadeVisibility(visible = false)
58
59 if (!driverViewModel.isInteractionAllowed) {
60 DriversLoadingDialogFragment().show(
61 childFragmentManager,
62 DriversLoadingDialogFragment.TAG
63 )
64 }
65
66 binding.toolbarDrivers.setNavigationOnClickListener {
67 binding.root.findNavController().popBackStack()
68 }
69
70 binding.buttonInstall.setOnClickListener {
71 getDriver.launch(arrayOf("application/zip"))
72 }
73
74 binding.listDrivers.apply {
75 layoutManager = GridLayoutManager(
76 requireContext(),
77 resources.getInteger(R.integer.grid_columns)
78 )
79 adapter = DriverAdapter(driverViewModel)
80 }
81
82 viewLifecycleOwner.lifecycleScope.apply {
83 launch {
84 driverViewModel.driverList.collectLatest {
85 (binding.listDrivers.adapter as DriverAdapter).submitList(it)
86 }
87 }
88 launch {
89 driverViewModel.newDriverInstalled.collect {
90 if (_binding != null && it) {
91 (binding.listDrivers.adapter as DriverAdapter).apply {
92 notifyItemChanged(driverViewModel.previouslySelectedDriver)
93 notifyItemChanged(driverViewModel.selectedDriver)
94 driverViewModel.setNewDriverInstalled(false)
95 }
96 }
97 }
98 }
99 }
100
101 setInsets()
102 }
103
104 // Start installing requested driver
105 override fun onStop() {
106 super.onStop()
107 driverViewModel.onCloseDriverManager()
108 }
109
110 private fun setInsets() =
111 ViewCompat.setOnApplyWindowInsetsListener(
112 binding.root
113 ) { _: View, windowInsets: WindowInsetsCompat ->
114 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
115 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
116
117 val leftInsets = barInsets.left + cutoutInsets.left
118 val rightInsets = barInsets.right + cutoutInsets.right
119
120 val mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams
121 mlpAppBar.leftMargin = leftInsets
122 mlpAppBar.rightMargin = rightInsets
123 binding.toolbarDrivers.layoutParams = mlpAppBar
124
125 val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams
126 mlplistDrivers.leftMargin = leftInsets
127 mlplistDrivers.rightMargin = rightInsets
128 binding.listDrivers.layoutParams = mlplistDrivers
129
130 val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
131 val mlpFab =
132 binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
133 mlpFab.leftMargin = leftInsets + fabSpacing
134 mlpFab.rightMargin = rightInsets + fabSpacing
135 mlpFab.bottomMargin = barInsets.bottom + fabSpacing
136 binding.buttonInstall.layoutParams = mlpFab
137
138 binding.listDrivers.updatePadding(
139 bottom = barInsets.bottom +
140 resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
141 )
142
143 windowInsets
144 }
145
146 private val getDriver =
147 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
148 if (result == null) {
149 return@registerForActivityResult
150 }
151
152 IndeterminateProgressDialogFragment.newInstance(
153 requireActivity(),
154 R.string.installing_driver,
155 false
156 ) {
157 // Ignore file exceptions when a user selects an invalid zip
158 try {
159 GpuDriverHelper.copyDriverToInternalStorage(result)
160 } catch (_: IOException) {
161 return@newInstance getString(R.string.select_gpu_driver_error)
162 }
163
164 val driverData = GpuDriverHelper.customDriverData
165 if (driverData.name == null) {
166 return@newInstance getString(R.string.select_gpu_driver_error)
167 }
168
169 val driverInList =
170 driverViewModel.driverList.value.firstOrNull { it.second == driverData }
171 if (driverInList != null) {
172 return@newInstance getString(R.string.driver_already_installed)
173 } else {
174 driverViewModel.addDriver(
175 Pair(
176 "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}",
177 driverData
178 )
179 )
180 driverViewModel.setNewDriverInstalled(true)
181 }
182 return@newInstance Any()
183 }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
184 }
185}
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/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/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/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/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) {