diff options
Diffstat (limited to 'src')
59 files changed, 1338 insertions, 419 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 84a3308b7..ac43d84b7 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -27,7 +27,7 @@ android { | |||
| 27 | namespace = "org.yuzu.yuzu_emu" | 27 | namespace = "org.yuzu.yuzu_emu" |
| 28 | 28 | ||
| 29 | compileSdkVersion = "android-34" | 29 | compileSdkVersion = "android-34" |
| 30 | ndkVersion = "25.2.9519653" | 30 | ndkVersion = "26.1.10909125" |
| 31 | 31 | ||
| 32 | buildFeatures { | 32 | buildFeatures { |
| 33 | viewBinding = true | 33 | viewBinding = true |
| @@ -203,23 +203,23 @@ ktlint { | |||
| 203 | } | 203 | } |
| 204 | 204 | ||
| 205 | dependencies { | 205 | dependencies { |
| 206 | implementation("androidx.core:core-ktx:1.10.1") | 206 | implementation("androidx.core:core-ktx:1.12.0") |
| 207 | implementation("androidx.appcompat:appcompat:1.6.1") | 207 | implementation("androidx.appcompat:appcompat:1.6.1") |
| 208 | implementation("androidx.recyclerview:recyclerview:1.3.0") | 208 | implementation("androidx.recyclerview:recyclerview:1.3.1") |
| 209 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") | 209 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") |
| 210 | implementation("androidx.fragment:fragment-ktx:1.6.0") | 210 | implementation("androidx.fragment:fragment-ktx:1.6.1") |
| 211 | implementation("androidx.documentfile:documentfile:1.0.1") | 211 | implementation("androidx.documentfile:documentfile:1.0.1") |
| 212 | implementation("com.google.android.material:material:1.9.0") | 212 | implementation("com.google.android.material:material:1.9.0") |
| 213 | implementation("androidx.preference:preference:1.2.0") | 213 | implementation("androidx.preference:preference-ktx:1.2.1") |
| 214 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") | 214 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") |
| 215 | implementation("io.coil-kt:coil:2.2.2") | 215 | implementation("io.coil-kt:coil:2.2.2") |
| 216 | implementation("androidx.core:core-splashscreen:1.0.1") | 216 | implementation("androidx.core:core-splashscreen:1.0.1") |
| 217 | implementation("androidx.window:window:1.2.0-beta03") | 217 | implementation("androidx.window:window:1.2.0-beta03") |
| 218 | implementation("org.ini4j:ini4j:0.5.4") | 218 | implementation("org.ini4j:ini4j:0.5.4") |
| 219 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") | 219 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") |
| 220 | implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") | 220 | implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") |
| 221 | implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") | 221 | implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") |
| 222 | implementation("androidx.navigation:navigation-ui-ktx:2.6.0") | 222 | implementation("androidx.navigation:navigation-ui-ktx:2.7.4") |
| 223 | implementation("info.debatty:java-string-similarity:2.0.0") | 223 | implementation("info.debatty:java-string-similarity:2.0.0") |
| 224 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") | 224 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") |
| 225 | } | 225 | } |
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 832c08e15..a67351727 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml | |||
| @@ -28,7 +28,6 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 28 | android:appCategory="game" | 28 | android:appCategory="game" |
| 29 | android:localeConfig="@xml/locales_config" | 29 | android:localeConfig="@xml/locales_config" |
| 30 | android:banner="@drawable/tv_banner" | 30 | android:banner="@drawable/tv_banner" |
| 31 | android:extractNativeLibs="true" | ||
| 32 | android:fullBackupContent="@xml/data_extraction_rules" | 31 | android:fullBackupContent="@xml/data_extraction_rules" |
| 33 | android:dataExtractionRules="@xml/data_extraction_rules_api_31" | 32 | android:dataExtractionRules="@xml/data_extraction_rules_api_31" |
| 34 | android:enableOnBackInvokedCallback="true"> | 33 | android:enableOnBackInvokedCallback="true"> |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 6e39e542b..115f72710 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt | |||
| @@ -15,13 +15,9 @@ import androidx.annotation.Keep | |||
| 15 | import androidx.fragment.app.DialogFragment | 15 | import androidx.fragment.app.DialogFragment |
| 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 17 | import java.lang.ref.WeakReference | 17 | import java.lang.ref.WeakReference |
| 18 | import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext | ||
| 19 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 18 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 20 | import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath | 19 | import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath |
| 21 | import org.yuzu.yuzu_emu.utils.FileUtil.exists | 20 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 22 | import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize | ||
| 23 | import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory | ||
| 24 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri | ||
| 25 | import org.yuzu.yuzu_emu.utils.Log | 21 | import org.yuzu.yuzu_emu.utils.Log |
| 26 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | 22 | import 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 | |||
| 4 | package org.yuzu.yuzu_emu.adapters | ||
| 5 | |||
| 6 | import android.text.TextUtils | ||
| 7 | import android.view.LayoutInflater | ||
| 8 | import android.view.View | ||
| 9 | import android.view.ViewGroup | ||
| 10 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 11 | import androidx.recyclerview.widget.DiffUtil | ||
| 12 | import androidx.recyclerview.widget.ListAdapter | ||
| 13 | import androidx.recyclerview.widget.RecyclerView | ||
| 14 | import org.yuzu.yuzu_emu.R | ||
| 15 | import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding | ||
| 16 | import org.yuzu.yuzu_emu.model.DriverViewModel | ||
| 17 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | ||
| 18 | import org.yuzu.yuzu_emu.utils.GpuDriverMetadata | ||
| 19 | |||
| 20 | class DriverAdapter(private val driverViewModel: DriverViewModel) : | ||
| 21 | ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>( | ||
| 22 | AsyncDifferConfig.Builder(DiffCallback()).build() | ||
| 23 | ) { | ||
| 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder { | ||
| 25 | val binding = | ||
| 26 | CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||
| 27 | return DriverViewHolder(binding) | ||
| 28 | } | ||
| 29 | |||
| 30 | override fun getItemCount(): Int = currentList.size | ||
| 31 | |||
| 32 | override fun onBindViewHolder(holder: DriverViewHolder, position: Int) = | ||
| 33 | holder.bind(currentList[position]) | ||
| 34 | |||
| 35 | private fun onSelectDriver(position: Int) { | ||
| 36 | driverViewModel.setSelectedDriverIndex(position) | ||
| 37 | notifyItemChanged(driverViewModel.previouslySelectedDriver) | ||
| 38 | notifyItemChanged(driverViewModel.selectedDriver) | ||
| 39 | } | ||
| 40 | |||
| 41 | private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) { | ||
| 42 | if (driverViewModel.selectedDriver > position) { | ||
| 43 | driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1) | ||
| 44 | } | ||
| 45 | if (GpuDriverHelper.customDriverData == driverData.second) { | ||
| 46 | driverViewModel.setSelectedDriverIndex(0) | ||
| 47 | } | ||
| 48 | driverViewModel.driversToDelete.add(driverData.first) | ||
| 49 | driverViewModel.removeDriver(driverData) | ||
| 50 | notifyItemRemoved(position) | ||
| 51 | notifyItemChanged(driverViewModel.selectedDriver) | ||
| 52 | } | ||
| 53 | |||
| 54 | inner class DriverViewHolder(val binding: CardDriverOptionBinding) : | ||
| 55 | RecyclerView.ViewHolder(binding.root) { | ||
| 56 | private lateinit var driverData: Pair<String, GpuDriverMetadata> | ||
| 57 | |||
| 58 | fun bind(driverData: Pair<String, GpuDriverMetadata>) { | ||
| 59 | this.driverData = driverData | ||
| 60 | val driver = driverData.second | ||
| 61 | |||
| 62 | binding.apply { | ||
| 63 | radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition | ||
| 64 | root.setOnClickListener { | ||
| 65 | onSelectDriver(bindingAdapterPosition) | ||
| 66 | } | ||
| 67 | buttonDelete.setOnClickListener { | ||
| 68 | onDeleteDriver(driverData, bindingAdapterPosition) | ||
| 69 | } | ||
| 70 | |||
| 71 | // Delay marquee by 3s | ||
| 72 | title.postDelayed( | ||
| 73 | { | ||
| 74 | title.isSelected = true | ||
| 75 | title.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
| 76 | version.isSelected = true | ||
| 77 | version.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
| 78 | description.isSelected = true | ||
| 79 | description.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
| 80 | }, | ||
| 81 | 3000 | ||
| 82 | ) | ||
| 83 | if (driver.name == null) { | ||
| 84 | title.setText(R.string.system_gpu_driver) | ||
| 85 | description.text = "" | ||
| 86 | version.text = "" | ||
| 87 | version.visibility = View.GONE | ||
| 88 | description.visibility = View.GONE | ||
| 89 | buttonDelete.visibility = View.GONE | ||
| 90 | } else { | ||
| 91 | title.text = driver.name | ||
| 92 | version.text = driver.version | ||
| 93 | description.text = driver.description | ||
| 94 | version.visibility = View.VISIBLE | ||
| 95 | description.visibility = View.VISIBLE | ||
| 96 | buttonDelete.visibility = View.VISIBLE | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() { | ||
| 103 | override fun areItemsTheSame( | ||
| 104 | oldItem: Pair<String, GpuDriverMetadata>, | ||
| 105 | newItem: Pair<String, GpuDriverMetadata> | ||
| 106 | ): Boolean { | ||
| 107 | return oldItem.first == newItem.first | ||
| 108 | } | ||
| 109 | |||
| 110 | override fun areContentsTheSame( | ||
| 111 | oldItem: Pair<String, GpuDriverMetadata>, | ||
| 112 | newItem: Pair<String, GpuDriverMetadata> | ||
| 113 | ): Boolean { | ||
| 114 | return oldItem.second == newItem.second | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt new file mode 100644 index 000000000..df21d74b2 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt | |||
| @@ -0,0 +1,186 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.os.Bundle | ||
| 7 | import android.view.LayoutInflater | ||
| 8 | import android.view.View | ||
| 9 | import android.view.ViewGroup | ||
| 10 | import androidx.activity.result.contract.ActivityResultContracts | ||
| 11 | import androidx.core.view.ViewCompat | ||
| 12 | import androidx.core.view.WindowInsetsCompat | ||
| 13 | import androidx.core.view.updatePadding | ||
| 14 | import androidx.fragment.app.Fragment | ||
| 15 | import androidx.fragment.app.activityViewModels | ||
| 16 | import androidx.lifecycle.lifecycleScope | ||
| 17 | import androidx.navigation.findNavController | ||
| 18 | import androidx.recyclerview.widget.GridLayoutManager | ||
| 19 | import com.google.android.material.transition.MaterialSharedAxis | ||
| 20 | import kotlinx.coroutines.flow.collectLatest | ||
| 21 | import kotlinx.coroutines.launch | ||
| 22 | import org.yuzu.yuzu_emu.R | ||
| 23 | import org.yuzu.yuzu_emu.adapters.DriverAdapter | ||
| 24 | import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding | ||
| 25 | import org.yuzu.yuzu_emu.model.DriverViewModel | ||
| 26 | import org.yuzu.yuzu_emu.model.HomeViewModel | ||
| 27 | import org.yuzu.yuzu_emu.utils.FileUtil | ||
| 28 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | ||
| 29 | import java.io.File | ||
| 30 | import java.io.IOException | ||
| 31 | |||
| 32 | class DriverManagerFragment : Fragment() { | ||
| 33 | private var _binding: FragmentDriverManagerBinding? = null | ||
| 34 | private val binding get() = _binding!! | ||
| 35 | |||
| 36 | private val homeViewModel: HomeViewModel by activityViewModels() | ||
| 37 | private val driverViewModel: DriverViewModel by activityViewModels() | ||
| 38 | |||
| 39 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 40 | super.onCreate(savedInstanceState) | ||
| 41 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 42 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
| 43 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
| 44 | } | ||
| 45 | |||
| 46 | override fun onCreateView( | ||
| 47 | inflater: LayoutInflater, | ||
| 48 | container: ViewGroup?, | ||
| 49 | savedInstanceState: Bundle? | ||
| 50 | ): View { | ||
| 51 | _binding = FragmentDriverManagerBinding.inflate(inflater) | ||
| 52 | return binding.root | ||
| 53 | } | ||
| 54 | |||
| 55 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 56 | super.onViewCreated(view, savedInstanceState) | ||
| 57 | homeViewModel.setNavigationVisibility(visible = false, animated = true) | ||
| 58 | homeViewModel.setStatusBarShadeVisibility(visible = false) | ||
| 59 | |||
| 60 | if (!driverViewModel.isInteractionAllowed) { | ||
| 61 | DriversLoadingDialogFragment().show( | ||
| 62 | childFragmentManager, | ||
| 63 | DriversLoadingDialogFragment.TAG | ||
| 64 | ) | ||
| 65 | } | ||
| 66 | |||
| 67 | binding.toolbarDrivers.setNavigationOnClickListener { | ||
| 68 | binding.root.findNavController().popBackStack() | ||
| 69 | } | ||
| 70 | |||
| 71 | binding.buttonInstall.setOnClickListener { | ||
| 72 | getDriver.launch(arrayOf("application/zip")) | ||
| 73 | } | ||
| 74 | |||
| 75 | binding.listDrivers.apply { | ||
| 76 | layoutManager = GridLayoutManager( | ||
| 77 | requireContext(), | ||
| 78 | resources.getInteger(R.integer.grid_columns) | ||
| 79 | ) | ||
| 80 | adapter = DriverAdapter(driverViewModel) | ||
| 81 | } | ||
| 82 | |||
| 83 | viewLifecycleOwner.lifecycleScope.apply { | ||
| 84 | launch { | ||
| 85 | driverViewModel.driverList.collectLatest { | ||
| 86 | (binding.listDrivers.adapter as DriverAdapter).submitList(it) | ||
| 87 | } | ||
| 88 | } | ||
| 89 | launch { | ||
| 90 | driverViewModel.newDriverInstalled.collect { | ||
| 91 | if (_binding != null && it) { | ||
| 92 | (binding.listDrivers.adapter as DriverAdapter).apply { | ||
| 93 | notifyItemChanged(driverViewModel.previouslySelectedDriver) | ||
| 94 | notifyItemChanged(driverViewModel.selectedDriver) | ||
| 95 | driverViewModel.setNewDriverInstalled(false) | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | setInsets() | ||
| 103 | } | ||
| 104 | |||
| 105 | // Start installing requested driver | ||
| 106 | override fun onStop() { | ||
| 107 | super.onStop() | ||
| 108 | driverViewModel.onCloseDriverManager() | ||
| 109 | } | ||
| 110 | |||
| 111 | private fun setInsets() = | ||
| 112 | ViewCompat.setOnApplyWindowInsetsListener( | ||
| 113 | binding.root | ||
| 114 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 115 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | ||
| 116 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | ||
| 117 | |||
| 118 | val leftInsets = barInsets.left + cutoutInsets.left | ||
| 119 | val rightInsets = barInsets.right + cutoutInsets.right | ||
| 120 | |||
| 121 | val mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams | ||
| 122 | mlpAppBar.leftMargin = leftInsets | ||
| 123 | mlpAppBar.rightMargin = rightInsets | ||
| 124 | binding.toolbarDrivers.layoutParams = mlpAppBar | ||
| 125 | |||
| 126 | val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams | ||
| 127 | mlplistDrivers.leftMargin = leftInsets | ||
| 128 | mlplistDrivers.rightMargin = rightInsets | ||
| 129 | binding.listDrivers.layoutParams = mlplistDrivers | ||
| 130 | |||
| 131 | val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab) | ||
| 132 | val mlpFab = | ||
| 133 | binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams | ||
| 134 | mlpFab.leftMargin = leftInsets + fabSpacing | ||
| 135 | mlpFab.rightMargin = rightInsets + fabSpacing | ||
| 136 | mlpFab.bottomMargin = barInsets.bottom + fabSpacing | ||
| 137 | binding.buttonInstall.layoutParams = mlpFab | ||
| 138 | |||
| 139 | binding.listDrivers.updatePadding( | ||
| 140 | bottom = barInsets.bottom + | ||
| 141 | resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab) | ||
| 142 | ) | ||
| 143 | |||
| 144 | windowInsets | ||
| 145 | } | ||
| 146 | |||
| 147 | private val getDriver = | ||
| 148 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | ||
| 149 | if (result == null) { | ||
| 150 | return@registerForActivityResult | ||
| 151 | } | ||
| 152 | |||
| 153 | IndeterminateProgressDialogFragment.newInstance( | ||
| 154 | requireActivity(), | ||
| 155 | R.string.installing_driver, | ||
| 156 | false | ||
| 157 | ) { | ||
| 158 | val driverPath = | ||
| 159 | "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}" | ||
| 160 | val driverFile = File(driverPath) | ||
| 161 | |||
| 162 | // Ignore file exceptions when a user selects an invalid zip | ||
| 163 | try { | ||
| 164 | if (!GpuDriverHelper.copyDriverToInternalStorage(result)) { | ||
| 165 | throw IOException("Driver failed validation!") | ||
| 166 | } | ||
| 167 | } catch (_: IOException) { | ||
| 168 | if (driverFile.exists()) { | ||
| 169 | driverFile.delete() | ||
| 170 | } | ||
| 171 | return@newInstance getString(R.string.select_gpu_driver_error) | ||
| 172 | } | ||
| 173 | |||
| 174 | val driverData = GpuDriverHelper.getMetadataFromZip(driverFile) | ||
| 175 | val driverInList = | ||
| 176 | driverViewModel.driverList.value.firstOrNull { it.second == driverData } | ||
| 177 | if (driverInList != null) { | ||
| 178 | return@newInstance getString(R.string.driver_already_installed) | ||
| 179 | } else { | ||
| 180 | driverViewModel.addDriver(Pair(driverPath, driverData)) | ||
| 181 | driverViewModel.setNewDriverInstalled(true) | ||
| 182 | } | ||
| 183 | return@newInstance Any() | ||
| 184 | }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG) | ||
| 185 | } | ||
| 186 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt new file mode 100644 index 000000000..f8c34346a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 11 | import androidx.fragment.app.DialogFragment | ||
| 12 | import androidx.fragment.app.activityViewModels | ||
| 13 | import androidx.lifecycle.Lifecycle | ||
| 14 | import androidx.lifecycle.lifecycleScope | ||
| 15 | import androidx.lifecycle.repeatOnLifecycle | ||
| 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 17 | import kotlinx.coroutines.launch | ||
| 18 | import org.yuzu.yuzu_emu.R | ||
| 19 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | ||
| 20 | import org.yuzu.yuzu_emu.model.DriverViewModel | ||
| 21 | |||
| 22 | class 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 | |||
| 39 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 39 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 40 | import com.google.android.material.slider.Slider | 40 | import com.google.android.material.slider.Slider |
| 41 | import kotlinx.coroutines.Dispatchers | 41 | import kotlinx.coroutines.Dispatchers |
| 42 | import kotlinx.coroutines.flow.collect | ||
| 42 | import kotlinx.coroutines.flow.collectLatest | 43 | import kotlinx.coroutines.flow.collectLatest |
| 43 | import kotlinx.coroutines.launch | 44 | import kotlinx.coroutines.launch |
| 44 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 45 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
| @@ -50,6 +51,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding | |||
| 50 | import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding | 51 | import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding |
| 51 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 52 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
| 52 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 53 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 54 | import org.yuzu.yuzu_emu.model.DriverViewModel | ||
| 53 | import org.yuzu.yuzu_emu.model.Game | 55 | import org.yuzu.yuzu_emu.model.Game |
| 54 | import org.yuzu.yuzu_emu.model.EmulationViewModel | 56 | import org.yuzu.yuzu_emu.model.EmulationViewModel |
| 55 | import org.yuzu.yuzu_emu.overlay.InputOverlay | 57 | import 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 | ||
| 6 | import android.Manifest | 6 | import android.Manifest |
| 7 | import android.content.ActivityNotFoundException | 7 | import android.content.ActivityNotFoundException |
| 8 | import android.content.DialogInterface | ||
| 9 | import android.content.Intent | 8 | import android.content.Intent |
| 10 | import android.content.pm.PackageManager | 9 | import android.content.pm.PackageManager |
| 11 | import android.os.Bundle | 10 | import android.os.Bundle |
| @@ -28,7 +27,6 @@ import androidx.fragment.app.activityViewModels | |||
| 28 | import androidx.navigation.findNavController | 27 | import androidx.navigation.findNavController |
| 29 | import androidx.navigation.fragment.findNavController | 28 | import androidx.navigation.fragment.findNavController |
| 30 | import androidx.recyclerview.widget.LinearLayoutManager | 29 | import androidx.recyclerview.widget.LinearLayoutManager |
| 31 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 32 | import com.google.android.material.transition.MaterialSharedAxis | 30 | import com.google.android.material.transition.MaterialSharedAxis |
| 33 | import org.yuzu.yuzu_emu.BuildConfig | 31 | import org.yuzu.yuzu_emu.BuildConfig |
| 34 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 32 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
| @@ -37,6 +35,7 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter | |||
| 37 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding | 35 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding |
| 38 | import org.yuzu.yuzu_emu.features.DocumentProvider | 36 | import org.yuzu.yuzu_emu.features.DocumentProvider |
| 39 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 37 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 38 | import org.yuzu.yuzu_emu.model.DriverViewModel | ||
| 40 | import org.yuzu.yuzu_emu.model.HomeSetting | 39 | import org.yuzu.yuzu_emu.model.HomeSetting |
| 41 | import org.yuzu.yuzu_emu.model.HomeViewModel | 40 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 42 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 41 | import 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 | |||
| 10 | import android.view.ViewGroup | 10 | import android.view.ViewGroup |
| 11 | import android.widget.Toast | 11 | import android.widget.Toast |
| 12 | import androidx.appcompat.app.AlertDialog | 12 | import androidx.appcompat.app.AlertDialog |
| 13 | import androidx.appcompat.app.AppCompatActivity | ||
| 14 | import androidx.fragment.app.DialogFragment | 13 | import androidx.fragment.app.DialogFragment |
| 14 | import androidx.fragment.app.FragmentActivity | ||
| 15 | import androidx.fragment.app.activityViewModels | 15 | import androidx.fragment.app.activityViewModels |
| 16 | import androidx.lifecycle.Lifecycle | 16 | import androidx.lifecycle.Lifecycle |
| 17 | import androidx.lifecycle.ViewModelProvider | 17 | import 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 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import androidx.lifecycle.ViewModel | ||
| 7 | import androidx.lifecycle.viewModelScope | ||
| 8 | import kotlinx.coroutines.Dispatchers | ||
| 9 | import kotlinx.coroutines.flow.MutableStateFlow | ||
| 10 | import kotlinx.coroutines.flow.StateFlow | ||
| 11 | import kotlinx.coroutines.launch | ||
| 12 | import kotlinx.coroutines.withContext | ||
| 13 | import org.yuzu.yuzu_emu.R | ||
| 14 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 15 | import org.yuzu.yuzu_emu.utils.FileUtil | ||
| 16 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | ||
| 17 | import org.yuzu.yuzu_emu.utils.GpuDriverMetadata | ||
| 18 | import java.io.BufferedOutputStream | ||
| 19 | import java.io.File | ||
| 20 | |||
| 21 | class 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 | |||
| 29 | import androidx.navigation.ui.setupWithNavController | 29 | import androidx.navigation.ui.setupWithNavController |
| 30 | import androidx.preference.PreferenceManager | 30 | import androidx.preference.PreferenceManager |
| 31 | import com.google.android.material.color.MaterialColors | 31 | import com.google.android.material.color.MaterialColors |
| 32 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 33 | import com.google.android.material.navigation.NavigationBarView | 32 | import com.google.android.material.navigation.NavigationBarView |
| 34 | import kotlinx.coroutines.CoroutineScope | 33 | import kotlinx.coroutines.CoroutineScope |
| 35 | import java.io.File | 34 | import java.io.File |
| 36 | import java.io.FilenameFilter | 35 | import java.io.FilenameFilter |
| 37 | import java.io.IOException | ||
| 38 | import kotlinx.coroutines.Dispatchers | 36 | import kotlinx.coroutines.Dispatchers |
| 39 | import kotlinx.coroutines.launch | 37 | import kotlinx.coroutines.launch |
| 40 | import kotlinx.coroutines.withContext | 38 | import kotlinx.coroutines.withContext |
| @@ -43,7 +41,6 @@ import org.yuzu.yuzu_emu.NativeLibrary | |||
| 43 | import org.yuzu.yuzu_emu.R | 41 | import org.yuzu.yuzu_emu.R |
| 44 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 42 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 45 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding | 43 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding |
| 46 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | ||
| 47 | import org.yuzu.yuzu_emu.features.DocumentProvider | 44 | import org.yuzu.yuzu_emu.features.DocumentProvider |
| 48 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 45 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 49 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 46 | import 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 | |||
| 7 | import androidx.documentfile.provider.DocumentFile | 7 | import androidx.documentfile.provider.DocumentFile |
| 8 | import java.io.File | 8 | import java.io.File |
| 9 | import java.util.* | 9 | import java.util.* |
| 10 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 11 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | 10 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile |
| 12 | 11 | ||
| 13 | class DocumentsTree { | 12 | class 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 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 7 | import android.database.Cursor | 6 | import android.database.Cursor |
| 8 | import android.net.Uri | 7 | import android.net.Uri |
| 9 | import android.provider.DocumentsContract | 8 | import android.provider.DocumentsContract |
| @@ -11,7 +10,6 @@ import androidx.documentfile.provider.DocumentFile | |||
| 11 | import kotlinx.coroutines.flow.StateFlow | 10 | import kotlinx.coroutines.flow.StateFlow |
| 12 | import java.io.BufferedInputStream | 11 | import java.io.BufferedInputStream |
| 13 | import java.io.File | 12 | import java.io.File |
| 14 | import java.io.FileOutputStream | ||
| 15 | import java.io.IOException | 13 | import java.io.IOException |
| 16 | import java.io.InputStream | 14 | import java.io.InputStream |
| 17 | import java.net.URLDecoder | 15 | import java.net.URLDecoder |
| @@ -21,6 +19,8 @@ import org.yuzu.yuzu_emu.YuzuApplication | |||
| 21 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | 19 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile |
| 22 | import org.yuzu.yuzu_emu.model.TaskState | 20 | import org.yuzu.yuzu_emu.model.TaskState |
| 23 | import java.io.BufferedOutputStream | 21 | import java.io.BufferedOutputStream |
| 22 | import java.lang.NullPointerException | ||
| 23 | import java.nio.charset.StandardCharsets | ||
| 24 | import java.util.zip.ZipOutputStream | 24 | import java.util.zip.ZipOutputStream |
| 25 | 25 | ||
| 26 | object FileUtil { | 26 | object 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 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 7 | import android.net.Uri | 6 | import android.net.Uri |
| 7 | import android.os.Build | ||
| 8 | import java.io.BufferedInputStream | 8 | import java.io.BufferedInputStream |
| 9 | import java.io.File | 9 | import java.io.File |
| 10 | import java.io.FileInputStream | ||
| 11 | import java.io.FileOutputStream | ||
| 12 | import java.io.IOException | 10 | import java.io.IOException |
| 13 | import java.util.zip.ZipInputStream | ||
| 14 | import org.yuzu.yuzu_emu.NativeLibrary | 11 | import org.yuzu.yuzu_emu.NativeLibrary |
| 15 | import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage | 12 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import java.util.zip.ZipException | ||
| 14 | import java.util.zip.ZipFile | ||
| 16 | 15 | ||
| 17 | object GpuDriverHelper { | 16 | object 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 @@ | |||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import java.io.IOException | 6 | import java.io.IOException |
| 7 | import java.nio.charset.StandardCharsets | ||
| 8 | import java.nio.file.Files | ||
| 9 | import java.nio.file.Paths | ||
| 10 | import org.json.JSONException | 7 | import org.json.JSONException |
| 11 | import org.json.JSONObject | 8 | import org.json.JSONObject |
| 9 | import java.io.File | ||
| 10 | import java.io.InputStream | ||
| 12 | 11 | ||
| 13 | class GpuDriverMetadata(metadataFilePath: String) { | 12 | class GpuDriverMetadata { |
| 14 | var name: String? = null | 13 | /** |
| 15 | var description: String? = null | 14 | * Tries to get driver metadata information from a meta.json [File] |
| 16 | var author: String? = null | 15 | * |
| 17 | var vendor: String? = null | 16 | * @param metadataFile meta.json file provided with a GPU driver |
| 18 | var driverVersion: String? = null | 17 | */ |
| 19 | var minApi = 0 | 18 | constructor(metadataFile: File) { |
| 20 | var libraryName: String? = null | 19 | if (metadataFile.length() > MAX_META_SIZE_BYTES) { |
| 20 | return | ||
| 21 | } | ||
| 21 | 22 | ||
| 22 | init { | ||
| 23 | try { | 23 | try { |
| 24 | val json = JSONObject(getStringFromFile(metadataFilePath)) | 24 | val json = JSONObject(FileUtil.getStringFromFile(metadataFile)) |
| 25 | name = json.getString("name") | 25 | name = json.getString("name") |
| 26 | description = json.getString("description") | 26 | description = json.getString("description") |
| 27 | author = json.getString("author") | 27 | author = json.getString("author") |
| 28 | vendor = json.getString("vendor") | 28 | vendor = json.getString("vendor") |
| 29 | driverVersion = json.getString("driverVersion") | 29 | version = json.getString("driverVersion") |
| 30 | minApi = json.getInt("minApi") | 30 | minApi = json.getInt("minApi") |
| 31 | libraryName = json.getString("libraryName") | 31 | libraryName = json.getString("libraryName") |
| 32 | } catch (e: JSONException) { | 32 | } catch (e: JSONException) { |
| @@ -36,12 +36,84 @@ class GpuDriverMetadata(metadataFilePath: String) { | |||
| 36 | } | 36 | } |
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | companion object { | 39 | /** |
| 40 | @Throws(IOException::class) | 40 | * Tries to get driver metadata information from an input stream that's intended to be |
| 41 | private fun getStringFromFile(filePath: String): String { | 41 | * from a zip file |
| 42 | val path = Paths.get(filePath) | 42 | * |
| 43 | val bytes = Files.readAllBytes(path) | 43 | * @param metadataStream ZipEntry input stream |
| 44 | return String(bytes, StandardCharsets.UTF_8) | 44 | * @param size Size of the file in bytes |
| 45 | */ | ||
| 46 | constructor(metadataStream: InputStream, size: Long) { | ||
| 47 | if (size > MAX_META_SIZE_BYTES) { | ||
| 48 | return | ||
| 45 | } | 49 | } |
| 50 | |||
| 51 | try { | ||
| 52 | val json = JSONObject(FileUtil.getStringFromInputStream(metadataStream)) | ||
| 53 | name = json.getString("name") | ||
| 54 | description = json.getString("description") | ||
| 55 | author = json.getString("author") | ||
| 56 | vendor = json.getString("vendor") | ||
| 57 | version = json.getString("driverVersion") | ||
| 58 | minApi = json.getInt("minApi") | ||
| 59 | libraryName = json.getString("libraryName") | ||
| 60 | } catch (e: JSONException) { | ||
| 61 | // JSON is malformed, ignore and treat as unsupported metadata. | ||
| 62 | } catch (e: IOException) { | ||
| 63 | // File is inaccessible, ignore and treat as unsupported metadata. | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Creates an empty metadata instance | ||
| 69 | */ | ||
| 70 | constructor() | ||
| 71 | |||
| 72 | override fun equals(other: Any?): Boolean { | ||
| 73 | if (other !is GpuDriverMetadata) { | ||
| 74 | return false | ||
| 75 | } | ||
| 76 | |||
| 77 | return other.name == name && | ||
| 78 | other.description == description && | ||
| 79 | other.author == author && | ||
| 80 | other.vendor == vendor && | ||
| 81 | other.version == version && | ||
| 82 | other.minApi == minApi && | ||
| 83 | other.libraryName == libraryName | ||
| 84 | } | ||
| 85 | |||
| 86 | override fun hashCode(): Int { | ||
| 87 | var result = name?.hashCode() ?: 0 | ||
| 88 | result = 31 * result + (description?.hashCode() ?: 0) | ||
| 89 | result = 31 * result + (author?.hashCode() ?: 0) | ||
| 90 | result = 31 * result + (vendor?.hashCode() ?: 0) | ||
| 91 | result = 31 * result + (version?.hashCode() ?: 0) | ||
| 92 | result = 31 * result + minApi | ||
| 93 | result = 31 * result + (libraryName?.hashCode() ?: 0) | ||
| 94 | return result | ||
| 95 | } | ||
| 96 | |||
| 97 | override fun toString(): String = | ||
| 98 | """ | ||
| 99 | Name - $name | ||
| 100 | Description - $description | ||
| 101 | Author - $author | ||
| 102 | Vendor - $vendor | ||
| 103 | Version - $version | ||
| 104 | Min API - $minApi | ||
| 105 | Library Name - $libraryName | ||
| 106 | """.trimMargin().trimIndent() | ||
| 107 | |||
| 108 | var name: String? = null | ||
| 109 | var description: String? = null | ||
| 110 | var author: String? = null | ||
| 111 | var vendor: String? = null | ||
| 112 | var version: String? = null | ||
| 113 | var minApi = 0 | ||
| 114 | var libraryName: String? = null | ||
| 115 | |||
| 116 | companion object { | ||
| 117 | private const val MAX_META_SIZE_BYTES = 500000 | ||
| 46 | } | 118 | } |
| 47 | } | 119 | } |
diff --git a/src/android/app/src/main/res/drawable/ic_build.xml b/src/android/app/src/main/res/drawable/ic_build.xml new file mode 100644 index 000000000..91d52f1b8 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_build.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_delete.xml b/src/android/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 000000000..d26a79711 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_delete.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/layout/card_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml new file mode 100644 index 000000000..1dd9a6d7d --- /dev/null +++ b/src/android/app/src/main/res/layout/card_driver_option.xml | |||
| @@ -0,0 +1,89 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | style="?attr/materialCardViewOutlinedStyle" | ||
| 6 | android:layout_width="match_parent" | ||
| 7 | android:layout_height="wrap_content" | ||
| 8 | android:layout_marginHorizontal="16dp" | ||
| 9 | android:layout_marginVertical="12dp" | ||
| 10 | android:background="?attr/selectableItemBackground" | ||
| 11 | android:clickable="true" | ||
| 12 | android:focusable="true"> | ||
| 13 | |||
| 14 | <LinearLayout | ||
| 15 | android:layout_width="match_parent" | ||
| 16 | android:layout_height="wrap_content" | ||
| 17 | android:orientation="horizontal" | ||
| 18 | android:layout_gravity="center" | ||
| 19 | android:padding="16dp"> | ||
| 20 | |||
| 21 | <RadioButton | ||
| 22 | android:id="@+id/radio_button" | ||
| 23 | android:layout_width="wrap_content" | ||
| 24 | android:layout_height="wrap_content" | ||
| 25 | android:layout_gravity="center_vertical" | ||
| 26 | android:clickable="false" | ||
| 27 | android:checked="false" /> | ||
| 28 | |||
| 29 | <LinearLayout | ||
| 30 | android:layout_width="0dp" | ||
| 31 | android:layout_height="wrap_content" | ||
| 32 | android:layout_weight="1" | ||
| 33 | android:orientation="vertical" | ||
| 34 | android:layout_gravity="center_vertical"> | ||
| 35 | |||
| 36 | <com.google.android.material.textview.MaterialTextView | ||
| 37 | android:id="@+id/title" | ||
| 38 | style="@style/TextAppearance.Material3.TitleMedium" | ||
| 39 | android:layout_width="match_parent" | ||
| 40 | android:layout_height="wrap_content" | ||
| 41 | android:ellipsize="none" | ||
| 42 | android:marqueeRepeatLimit="marquee_forever" | ||
| 43 | android:requiresFadingEdge="horizontal" | ||
| 44 | android:singleLine="true" | ||
| 45 | android:textAlignment="viewStart" | ||
| 46 | tools:text="@string/select_gpu_driver_default" /> | ||
| 47 | |||
| 48 | <com.google.android.material.textview.MaterialTextView | ||
| 49 | android:id="@+id/version" | ||
| 50 | style="@style/TextAppearance.Material3.BodyMedium" | ||
| 51 | android:layout_width="match_parent" | ||
| 52 | android:layout_height="wrap_content" | ||
| 53 | android:layout_marginTop="6dp" | ||
| 54 | android:ellipsize="none" | ||
| 55 | android:marqueeRepeatLimit="marquee_forever" | ||
| 56 | android:requiresFadingEdge="horizontal" | ||
| 57 | android:singleLine="true" | ||
| 58 | android:textAlignment="viewStart" | ||
| 59 | tools:text="@string/install_gpu_driver_description" /> | ||
| 60 | |||
| 61 | <com.google.android.material.textview.MaterialTextView | ||
| 62 | android:id="@+id/description" | ||
| 63 | style="@style/TextAppearance.Material3.BodyMedium" | ||
| 64 | android:layout_width="match_parent" | ||
| 65 | android:layout_height="wrap_content" | ||
| 66 | android:layout_marginTop="6dp" | ||
| 67 | android:ellipsize="none" | ||
| 68 | android:marqueeRepeatLimit="marquee_forever" | ||
| 69 | android:requiresFadingEdge="horizontal" | ||
| 70 | android:singleLine="true" | ||
| 71 | android:textAlignment="viewStart" | ||
| 72 | tools:text="@string/install_gpu_driver_description" /> | ||
| 73 | |||
| 74 | </LinearLayout> | ||
| 75 | |||
| 76 | <Button | ||
| 77 | android:id="@+id/button_delete" | ||
| 78 | style="@style/Widget.Material3.Button.IconButton" | ||
| 79 | android:layout_width="wrap_content" | ||
| 80 | android:layout_height="wrap_content" | ||
| 81 | android:layout_gravity="center_vertical" | ||
| 82 | android:contentDescription="@string/delete" | ||
| 83 | android:tooltipText="@string/delete" | ||
| 84 | app:icon="@drawable/ic_delete" | ||
| 85 | app:iconTint="?attr/colorControlNormal" /> | ||
| 86 | |||
| 87 | </LinearLayout> | ||
| 88 | |||
| 89 | </com.google.android.material.card.MaterialCardView> | ||
diff --git a/src/android/app/src/main/res/layout/fragment_driver_manager.xml b/src/android/app/src/main/res/layout/fragment_driver_manager.xml new file mode 100644 index 000000000..6cea2d164 --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_driver_manager.xml | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | android:id="@+id/coordinator_licenses" | ||
| 5 | android:layout_width="match_parent" | ||
| 6 | android:layout_height="match_parent" | ||
| 7 | android:background="?attr/colorSurface"> | ||
| 8 | |||
| 9 | <androidx.coordinatorlayout.widget.CoordinatorLayout | ||
| 10 | android:layout_width="match_parent" | ||
| 11 | android:layout_height="match_parent"> | ||
| 12 | |||
| 13 | <com.google.android.material.appbar.AppBarLayout | ||
| 14 | android:id="@+id/appbar_drivers" | ||
| 15 | android:layout_width="match_parent" | ||
| 16 | android:layout_height="wrap_content" | ||
| 17 | android:fitsSystemWindows="true" | ||
| 18 | app:liftOnScrollTargetViewId="@id/list_drivers"> | ||
| 19 | |||
| 20 | <com.google.android.material.appbar.MaterialToolbar | ||
| 21 | android:id="@+id/toolbar_drivers" | ||
| 22 | android:layout_width="match_parent" | ||
| 23 | android:layout_height="?attr/actionBarSize" | ||
| 24 | app:navigationIcon="@drawable/ic_back" | ||
| 25 | app:title="@string/gpu_driver_manager" /> | ||
| 26 | |||
| 27 | </com.google.android.material.appbar.AppBarLayout> | ||
| 28 | |||
| 29 | <androidx.recyclerview.widget.RecyclerView | ||
| 30 | android:id="@+id/list_drivers" | ||
| 31 | android:layout_width="match_parent" | ||
| 32 | android:layout_height="match_parent" | ||
| 33 | android:clipToPadding="false" | ||
| 34 | app:layout_behavior="@string/appbar_scrolling_view_behavior" /> | ||
| 35 | |||
| 36 | </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||
| 37 | |||
| 38 | <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton | ||
| 39 | android:id="@+id/button_install" | ||
| 40 | android:layout_width="wrap_content" | ||
| 41 | android:layout_height="wrap_content" | ||
| 42 | android:layout_gravity="bottom|end" | ||
| 43 | android:text="@string/install" | ||
| 44 | app:icon="@drawable/ic_add" | ||
| 45 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 46 | app:layout_constraintEnd_toEndOf="parent" /> | ||
| 47 | |||
| 48 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 2356b802b..82749359d 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml | |||
| @@ -22,6 +22,9 @@ | |||
| 22 | <action | 22 | <action |
| 23 | android:id="@+id/action_homeSettingsFragment_to_installableFragment" | 23 | android:id="@+id/action_homeSettingsFragment_to_installableFragment" |
| 24 | app:destination="@id/installableFragment" /> | 24 | app:destination="@id/installableFragment" /> |
| 25 | <action | ||
| 26 | android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment" | ||
| 27 | app:destination="@id/driverManagerFragment" /> | ||
| 25 | </fragment> | 28 | </fragment> |
| 26 | 29 | ||
| 27 | <fragment | 30 | <fragment |
| @@ -95,5 +98,9 @@ | |||
| 95 | android:id="@+id/installableFragment" | 98 | android:id="@+id/installableFragment" |
| 96 | android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment" | 99 | android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment" |
| 97 | android:label="InstallableFragment" /> | 100 | android:label="InstallableFragment" /> |
| 101 | <fragment | ||
| 102 | android:id="@+id/driverManagerFragment" | ||
| 103 | android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment" | ||
| 104 | android:label="DriverManagerFragment" /> | ||
| 98 | 105 | ||
| 99 | </navigation> | 106 | </navigation> |
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index dd0f36392..72a47fbdb 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml | |||
| @@ -168,9 +168,7 @@ | |||
| 168 | <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string> | 168 | <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string> |
| 169 | <string name="select_gpu_driver_install">Installieren</string> | 169 | <string name="select_gpu_driver_install">Installieren</string> |
| 170 | <string name="select_gpu_driver_default">Standard</string> | 170 | <string name="select_gpu_driver_default">Standard</string> |
| 171 | <string name="select_gpu_driver_install_success">%s wurde installiert</string> | ||
| 172 | <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string> | 171 | <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string> |
| 173 | <string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string> | ||
| 174 | <string name="system_gpu_driver">System GPU-Treiber</string> | 172 | <string name="system_gpu_driver">System GPU-Treiber</string> |
| 175 | <string name="installing_driver">Treiber wird installiert...</string> | 173 | <string name="installing_driver">Treiber wird installiert...</string> |
| 176 | 174 | ||
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index d398f862f..e5bdd5889 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string> | 171 | <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string> |
| 172 | <string name="select_gpu_driver_install">Instalar</string> | 172 | <string name="select_gpu_driver_install">Instalar</string> |
| 173 | <string name="select_gpu_driver_default">Predeterminado</string> | 173 | <string name="select_gpu_driver_default">Predeterminado</string> |
| 174 | <string name="select_gpu_driver_install_success">Instalado %s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string> | 174 | <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string> |
| 176 | <string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string> | ||
| 177 | <string name="system_gpu_driver">Driver GPU del sistema</string> | 175 | <string name="system_gpu_driver">Driver GPU del sistema</string> |
| 178 | <string name="installing_driver">Instalando driver...</string> | 176 | <string name="installing_driver">Instalando driver...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index a7abd9077..1e02828aa 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string> | 171 | <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string> |
| 172 | <string name="select_gpu_driver_install">Installer</string> | 172 | <string name="select_gpu_driver_install">Installer</string> |
| 173 | <string name="select_gpu_driver_default">Défaut</string> | 173 | <string name="select_gpu_driver_default">Défaut</string> |
| 174 | <string name="select_gpu_driver_install_success">%s Installé</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string> | 174 | <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string> |
| 176 | <string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string> | ||
| 177 | <string name="system_gpu_driver">Pilote du GPU du système</string> | 175 | <string name="system_gpu_driver">Pilote du GPU du système</string> |
| 178 | <string name="installing_driver">Installation du pilote...</string> | 176 | <string name="installing_driver">Installation du pilote...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index b18161801..09c9345b0 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string> | 171 | <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string> |
| 172 | <string name="select_gpu_driver_install">Installa</string> | 172 | <string name="select_gpu_driver_install">Installa</string> |
| 173 | <string name="select_gpu_driver_default">Predefinito</string> | 173 | <string name="select_gpu_driver_default">Predefinito</string> |
| 174 | <string name="select_gpu_driver_install_success">Installato%s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string> | 174 | <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string> |
| 176 | <string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string> | ||
| 177 | <string name="system_gpu_driver">Driver GPU del sistema</string> | 175 | <string name="system_gpu_driver">Driver GPU del sistema</string> |
| 178 | <string name="installing_driver">Installando i driver...</string> | 176 | <string name="installing_driver">Installando i driver...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index 88fa5a0bb..a0ea78bef 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml | |||
| @@ -170,9 +170,7 @@ | |||
| 170 | <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string> | 170 | <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string> |
| 171 | <string name="select_gpu_driver_install">インストール</string> | 171 | <string name="select_gpu_driver_install">インストール</string> |
| 172 | <string name="select_gpu_driver_default">デフォルト</string> | 172 | <string name="select_gpu_driver_default">デフォルト</string> |
| 173 | <string name="select_gpu_driver_install_success">%s をインストールしました</string> | ||
| 174 | <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string> | 173 | <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string> |
| 175 | <string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string> | ||
| 176 | <string name="system_gpu_driver">システムのGPUドライバ</string> | 174 | <string name="system_gpu_driver">システムのGPUドライバ</string> |
| 177 | <string name="installing_driver">インストール中…</string> | 175 | <string name="installing_driver">インストール中…</string> |
| 178 | 176 | ||
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index 4b658255c..214f95706 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string> | 171 | <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string> |
| 172 | <string name="select_gpu_driver_install">설치</string> | 172 | <string name="select_gpu_driver_install">설치</string> |
| 173 | <string name="select_gpu_driver_default">기본값</string> | 173 | <string name="select_gpu_driver_default">기본값</string> |
| 174 | <string name="select_gpu_driver_install_success">설치된 %s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string> | 174 | <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string> |
| 176 | <string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string> | ||
| 177 | <string name="system_gpu_driver">시스템 GPU 드라이버</string> | 175 | <string name="system_gpu_driver">시스템 GPU 드라이버</string> |
| 178 | <string name="installing_driver">드라이버 설치 중...</string> | 176 | <string name="installing_driver">드라이버 설치 중...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index dd602a389..5443cef42 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string> | 171 | <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string> |
| 172 | <string name="select_gpu_driver_install">Installer</string> | 172 | <string name="select_gpu_driver_install">Installer</string> |
| 173 | <string name="select_gpu_driver_default">Standard</string> | 173 | <string name="select_gpu_driver_default">Standard</string> |
| 174 | <string name="select_gpu_driver_install_success">Installert %s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string> | 174 | <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string> |
| 176 | <string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string> | ||
| 177 | <string name="system_gpu_driver">Systemets GPU-driver</string> | 175 | <string name="system_gpu_driver">Systemets GPU-driver</string> |
| 178 | <string name="installing_driver">Installerer driver...</string> | 176 | <string name="installing_driver">Installerer driver...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index 2fdd1f952..899e233d0 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string> | 171 | <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string> |
| 172 | <string name="select_gpu_driver_install">Zainstaluj</string> | 172 | <string name="select_gpu_driver_install">Zainstaluj</string> |
| 173 | <string name="select_gpu_driver_default">Domyślne</string> | 173 | <string name="select_gpu_driver_default">Domyślne</string> |
| 174 | <string name="select_gpu_driver_install_success">Zainstalowano %s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string> | 174 | <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string> |
| 176 | <string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string> | ||
| 177 | <string name="system_gpu_driver">Systemowy sterownik GPU</string> | 175 | <string name="system_gpu_driver">Systemowy sterownik GPU</string> |
| 178 | <string name="installing_driver">Instalowanie sterownika...</string> | 176 | <string name="installing_driver">Instalowanie sterownika...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index 2f26367fe..caa095364 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> | 171 | <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> |
| 172 | <string name="select_gpu_driver_install">Instalar</string> | 172 | <string name="select_gpu_driver_install">Instalar</string> |
| 173 | <string name="select_gpu_driver_default">Padrão</string> | 173 | <string name="select_gpu_driver_default">Padrão</string> |
| 174 | <string name="select_gpu_driver_install_success">Instalado%s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> | 174 | <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> |
| 176 | <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string> | ||
| 177 | <string name="system_gpu_driver">Driver do GPU padrão</string> | 175 | <string name="system_gpu_driver">Driver do GPU padrão</string> |
| 178 | <string name="installing_driver">A instalar o Driver...</string> | 176 | <string name="installing_driver">A instalar o Driver...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 4e1eb4cd7..0a1a47fbb 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> | 171 | <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> |
| 172 | <string name="select_gpu_driver_install">Instalar</string> | 172 | <string name="select_gpu_driver_install">Instalar</string> |
| 173 | <string name="select_gpu_driver_default">Padrão</string> | 173 | <string name="select_gpu_driver_default">Padrão</string> |
| 174 | <string name="select_gpu_driver_install_success">Instalado%s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> | 174 | <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> |
| 176 | <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string> | ||
| 177 | <string name="system_gpu_driver">Driver do GPU padrão</string> | 175 | <string name="system_gpu_driver">Driver do GPU padrão</string> |
| 178 | <string name="installing_driver">A instalar o Driver...</string> | 176 | <string name="installing_driver">A instalar o Driver...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index f5695dc93..0bef035d6 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string> | 171 | <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string> |
| 172 | <string name="select_gpu_driver_install">Установить</string> | 172 | <string name="select_gpu_driver_install">Установить</string> |
| 173 | <string name="select_gpu_driver_default">По умолчанию</string> | 173 | <string name="select_gpu_driver_default">По умолчанию</string> |
| 174 | <string name="select_gpu_driver_install_success">Установлено %s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string> | 174 | <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string> |
| 176 | <string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string> | ||
| 177 | <string name="system_gpu_driver">Системный драйвер ГП</string> | 175 | <string name="system_gpu_driver">Системный драйвер ГП</string> |
| 178 | <string name="installing_driver">Установка драйвера...</string> | 176 | <string name="installing_driver">Установка драйвера...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 061bc6f04..5b789ee98 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string> | 171 | <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string> |
| 172 | <string name="select_gpu_driver_install">Встановити</string> | 172 | <string name="select_gpu_driver_install">Встановити</string> |
| 173 | <string name="select_gpu_driver_default">За замовчуванням</string> | 173 | <string name="select_gpu_driver_default">За замовчуванням</string> |
| 174 | <string name="select_gpu_driver_install_success">Встановлено %s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string> | 174 | <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string> |
| 176 | <string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string> | ||
| 177 | <string name="system_gpu_driver">Системний драйвер ГП</string> | 175 | <string name="system_gpu_driver">Системний драйвер ГП</string> |
| 178 | <string name="installing_driver">Встановлення драйвера...</string> | 176 | <string name="installing_driver">Встановлення драйвера...</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index fe6dd5eaa..c0e885751 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string> | 171 | <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string> |
| 172 | <string name="select_gpu_driver_install">安装</string> | 172 | <string name="select_gpu_driver_install">安装</string> |
| 173 | <string name="select_gpu_driver_default">系统默认</string> | 173 | <string name="select_gpu_driver_default">系统默认</string> |
| 174 | <string name="select_gpu_driver_install_success">已安装 %s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string> | 174 | <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string> |
| 176 | <string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string> | ||
| 177 | <string name="system_gpu_driver">系统 GPU 驱动程序</string> | 175 | <string name="system_gpu_driver">系统 GPU 驱动程序</string> |
| 178 | <string name="installing_driver">正在安装驱动程序…</string> | 176 | <string name="installing_driver">正在安装驱动程序…</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 9b3e54224..4a21bf893 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml | |||
| @@ -171,9 +171,7 @@ | |||
| 171 | <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string> | 171 | <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string> |
| 172 | <string name="select_gpu_driver_install">安裝</string> | 172 | <string name="select_gpu_driver_install">安裝</string> |
| 173 | <string name="select_gpu_driver_default">預設</string> | 173 | <string name="select_gpu_driver_default">預設</string> |
| 174 | <string name="select_gpu_driver_install_success">已安裝 %s</string> | ||
| 175 | <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string> | 174 | <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string> |
| 176 | <string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string> | ||
| 177 | <string name="system_gpu_driver">系統 GPU 驅動程式</string> | 175 | <string name="system_gpu_driver">系統 GPU 驅動程式</string> |
| 178 | <string name="installing_driver">正在安裝驅動程式…</string> | 176 | <string name="installing_driver">正在安裝驅動程式…</string> |
| 179 | 177 | ||
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml index 7b2296d95..ef855ea6f 100644 --- a/src/android/app/src/main/res/values/dimens.xml +++ b/src/android/app/src/main/res/values/dimens.xml | |||
| @@ -13,6 +13,8 @@ | |||
| 13 | <dimen name="menu_width">256dp</dimen> | 13 | <dimen name="menu_width">256dp</dimen> |
| 14 | <dimen name="card_width">165dp</dimen> | 14 | <dimen name="card_width">165dp</dimen> |
| 15 | <dimen name="icon_inset">24dp</dimen> | 15 | <dimen name="icon_inset">24dp</dimen> |
| 16 | <dimen name="spacing_bottom_list_fab">72dp</dimen> | ||
| 17 | <dimen name="spacing_fab">24dp</dimen> | ||
| 16 | 18 | ||
| 17 | <dimen name="dialog_margin">20dp</dimen> | 19 | <dimen name="dialog_margin">20dp</dimen> |
| 18 | <dimen name="elevated_app_bar">3dp</dimen> | 20 | <dimen name="elevated_app_bar">3dp</dimen> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index e51edf872..9e4854221 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -72,6 +72,7 @@ | |||
| 72 | <string name="invalid_keys_error">Invalid encryption keys</string> | 72 | <string name="invalid_keys_error">Invalid encryption keys</string> |
| 73 | <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string> | 73 | <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string> |
| 74 | <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string> | 74 | <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string> |
| 75 | <string name="gpu_driver_manager">GPU Driver Manager</string> | ||
| 75 | <string name="install_gpu_driver">Install GPU driver</string> | 76 | <string name="install_gpu_driver">Install GPU driver</string> |
| 76 | <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> | 77 | <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> |
| 77 | <string name="advanced_settings">Advanced settings</string> | 78 | <string name="advanced_settings">Advanced settings</string> |
| @@ -234,15 +235,17 @@ | |||
| 234 | <string name="export_failed">Export failed</string> | 235 | <string name="export_failed">Export failed</string> |
| 235 | <string name="import_failed">Import failed</string> | 236 | <string name="import_failed">Import failed</string> |
| 236 | <string name="cancelling">Cancelling</string> | 237 | <string name="cancelling">Cancelling</string> |
| 238 | <string name="install">Install</string> | ||
| 239 | <string name="delete">Delete</string> | ||
| 237 | 240 | ||
| 238 | <!-- GPU driver installation --> | 241 | <!-- GPU driver installation --> |
| 239 | <string name="select_gpu_driver">Select GPU driver</string> | 242 | <string name="select_gpu_driver">Select GPU driver</string> |
| 240 | <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string> | 243 | <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string> |
| 241 | <string name="select_gpu_driver_install">Install</string> | 244 | <string name="select_gpu_driver_install">Install</string> |
| 242 | <string name="select_gpu_driver_default">Default</string> | 245 | <string name="select_gpu_driver_default">Default</string> |
| 243 | <string name="select_gpu_driver_install_success">Installed %s</string> | ||
| 244 | <string name="select_gpu_driver_use_default">Using default GPU driver</string> | 246 | <string name="select_gpu_driver_use_default">Using default GPU driver</string> |
| 245 | <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string> | 247 | <string name="select_gpu_driver_error">Invalid driver selected</string> |
| 248 | <string name="driver_already_installed">Driver already installed</string> | ||
| 246 | <string name="system_gpu_driver">System GPU driver</string> | 249 | <string name="system_gpu_driver">System GPU driver</string> |
| 247 | <string name="installing_driver">Installing driver…</string> | 250 | <string name="installing_driver">Installing driver…</string> |
| 248 | 251 | ||
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts index 80f370c16..51e559321 100644 --- a/src/android/build.gradle.kts +++ b/src/android/build.gradle.kts | |||
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | // Top-level build file where you can add configuration options common to all sub-projects/modules. | 4 | // Top-level build file where you can add configuration options common to all sub-projects/modules. |
| 5 | plugins { | 5 | plugins { |
| 6 | id("com.android.application") version "8.0.2" apply false | 6 | id("com.android.application") version "8.1.2" apply false |
| 7 | id("com.android.library") version "8.0.2" apply false | 7 | id("com.android.library") version "8.1.2" apply false |
| 8 | id("org.jetbrains.kotlin.android") version "1.8.21" apply false | 8 | id("org.jetbrains.kotlin.android") version "1.8.21" apply false |
| 9 | } | 9 | } |
| 10 | 10 | ||
diff --git a/src/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 | |||
| 216 | using Elf32_Relr = Elf32_Word; | ||
| 217 | using 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 | ||
| 216 | static inline u32 Elf32RelSymIndex(Elf32_Word r_info) { | 221 | static inline u32 Elf32RelSymIndex(Elf32_Word r_info) { |
| @@ -328,6 +333,9 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */ | |||
| 328 | constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */ | 333 | constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */ |
| 329 | constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */ | 334 | constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */ |
| 330 | constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */ | 335 | constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */ |
| 336 | constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */ | ||
| 337 | constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */ | ||
| 338 | constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */ | ||
| 331 | 339 | ||
| 332 | } // namespace ELF | 340 | } // namespace ELF |
| 333 | } // namespace Common | 341 | } // namespace Common |
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h index 41cbb9ed5..12e59a893 100644 --- a/src/common/polyfill_thread.h +++ b/src/common/polyfill_thread.h | |||
| @@ -15,12 +15,13 @@ | |||
| 15 | #include <condition_variable> | 15 | #include <condition_variable> |
| 16 | #include <stop_token> | 16 | #include <stop_token> |
| 17 | #include <thread> | 17 | #include <thread> |
| 18 | #include <utility> | ||
| 18 | 19 | ||
| 19 | namespace Common { | 20 | namespace Common { |
| 20 | 21 | ||
| 21 | template <typename Condvar, typename Lock, typename Pred> | 22 | template <typename Condvar, typename Lock, typename Pred> |
| 22 | void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) { | 23 | void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) { |
| 23 | cv.wait(lk, token, std::move(pred)); | 24 | cv.wait(lk, token, std::forward<Pred>(pred)); |
| 24 | } | 25 | } |
| 25 | 26 | ||
| 26 | template <typename Rep, typename Period> | 27 | template <typename Rep, typename Period> |
| @@ -109,7 +110,7 @@ public: | |||
| 109 | 110 | ||
| 110 | // Insert the callback. | 111 | // Insert the callback. |
| 111 | stop_state_callback ret = ++m_next_callback; | 112 | stop_state_callback ret = ++m_next_callback; |
| 112 | m_callbacks.emplace(ret, move(f)); | 113 | m_callbacks.emplace(ret, std::move(f)); |
| 113 | return ret; | 114 | return ret; |
| 114 | } | 115 | } |
| 115 | 116 | ||
| @@ -162,7 +163,7 @@ private: | |||
| 162 | friend class stop_source; | 163 | friend class stop_source; |
| 163 | template <typename Callback> | 164 | template <typename Callback> |
| 164 | friend class stop_callback; | 165 | friend class stop_callback; |
| 165 | stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {} | 166 | stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {} |
| 166 | 167 | ||
| 167 | private: | 168 | private: |
| 168 | shared_ptr<polyfill::stop_state> m_stop_state; | 169 | shared_ptr<polyfill::stop_state> m_stop_state; |
| @@ -198,7 +199,7 @@ public: | |||
| 198 | private: | 199 | private: |
| 199 | friend class jthread; | 200 | friend class jthread; |
| 200 | explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) | 201 | explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) |
| 201 | : m_stop_state(move(stop_state)) {} | 202 | : m_stop_state(std::move(stop_state)) {} |
| 202 | 203 | ||
| 203 | private: | 204 | private: |
| 204 | shared_ptr<polyfill::stop_state> m_stop_state; | 205 | shared_ptr<polyfill::stop_state> m_stop_state; |
| @@ -218,16 +219,16 @@ public: | |||
| 218 | C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) | 219 | C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) |
| 219 | : m_stop_state(st.m_stop_state) { | 220 | : m_stop_state(st.m_stop_state) { |
| 220 | if (m_stop_state) { | 221 | if (m_stop_state) { |
| 221 | m_callback = m_stop_state->insert_callback(move(cb)); | 222 | m_callback = m_stop_state->insert_callback(std::move(cb)); |
| 222 | } | 223 | } |
| 223 | } | 224 | } |
| 224 | template <typename C> | 225 | template <typename C> |
| 225 | requires constructible_from<Callback, C> | 226 | requires constructible_from<Callback, C> |
| 226 | explicit stop_callback(stop_token&& st, | 227 | explicit stop_callback(stop_token&& st, |
| 227 | C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) | 228 | C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) |
| 228 | : m_stop_state(move(st.m_stop_state)) { | 229 | : m_stop_state(std::move(st.m_stop_state)) { |
| 229 | if (m_stop_state) { | 230 | if (m_stop_state) { |
| 230 | m_callback = m_stop_state->insert_callback(move(cb)); | 231 | m_callback = m_stop_state->insert_callback(std::move(cb)); |
| 231 | } | 232 | } |
| 232 | } | 233 | } |
| 233 | ~stop_callback() { | 234 | ~stop_callback() { |
| @@ -260,7 +261,7 @@ public: | |||
| 260 | typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> | 261 | typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> |
| 261 | explicit jthread(F&& f, Args&&... args) | 262 | explicit jthread(F&& f, Args&&... args) |
| 262 | : m_stop_state(make_shared<polyfill::stop_state>()), | 263 | : m_stop_state(make_shared<polyfill::stop_state>()), |
| 263 | m_thread(make_thread(move(f), move(args)...)) {} | 264 | m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {} |
| 264 | 265 | ||
| 265 | ~jthread() { | 266 | ~jthread() { |
| 266 | if (joinable()) { | 267 | if (joinable()) { |
| @@ -317,9 +318,9 @@ private: | |||
| 317 | template <typename F, typename... Args> | 318 | template <typename F, typename... Args> |
| 318 | thread make_thread(F&& f, Args&&... args) { | 319 | thread make_thread(F&& f, Args&&... args) { |
| 319 | if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { | 320 | if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { |
| 320 | return thread(move(f), get_stop_token(), move(args)...); | 321 | return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...); |
| 321 | } else { | 322 | } else { |
| 322 | return thread(move(f), move(args)...); | 323 | return thread(std::forward<F>(f), std::forward<Args>(args)...); |
| 323 | } | 324 | } |
| 324 | } | 325 | } |
| 325 | 326 | ||
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 1fbfbf31f..0b0cef984 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp | |||
| @@ -3405,6 +3405,11 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K | |||
| 3405 | new_attr, KMemoryBlockDisableMergeAttribute::Locked, | 3405 | new_attr, KMemoryBlockDisableMergeAttribute::Locked, |
| 3406 | KMemoryBlockDisableMergeAttribute::None); | 3406 | KMemoryBlockDisableMergeAttribute::None); |
| 3407 | 3407 | ||
| 3408 | // If we have an output page group, open. | ||
| 3409 | if (out_pg) { | ||
| 3410 | out_pg->Open(); | ||
| 3411 | } | ||
| 3412 | |||
| 3408 | R_SUCCEED(); | 3413 | R_SUCCEED(); |
| 3409 | } | 3414 | } |
| 3410 | 3415 | ||
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index a1134b7e2..cb025c3d6 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp | |||
| @@ -373,7 +373,7 @@ struct KernelCore::Impl { | |||
| 373 | static inline thread_local u8 host_thread_id = UINT8_MAX; | 373 | static inline thread_local u8 host_thread_id = UINT8_MAX; |
| 374 | 374 | ||
| 375 | /// Sets the host thread ID for the caller. | 375 | /// Sets the host thread ID for the caller. |
| 376 | u32 SetHostThreadId(std::size_t core_id) { | 376 | LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) { |
| 377 | // This should only be called during core init. | 377 | // This should only be called during core init. |
| 378 | ASSERT(host_thread_id == UINT8_MAX); | 378 | ASSERT(host_thread_id == UINT8_MAX); |
| 379 | 379 | ||
| @@ -384,13 +384,13 @@ struct KernelCore::Impl { | |||
| 384 | } | 384 | } |
| 385 | 385 | ||
| 386 | /// Gets the host thread ID for the caller | 386 | /// Gets the host thread ID for the caller |
| 387 | u32 GetHostThreadId() const { | 387 | LTO_NOINLINE u32 GetHostThreadId() const { |
| 388 | return host_thread_id; | 388 | return host_thread_id; |
| 389 | } | 389 | } |
| 390 | 390 | ||
| 391 | // Gets the dummy KThread for the caller, allocating a new one if this is the first time | 391 | // Gets the dummy KThread for the caller, allocating a new one if this is the first time |
| 392 | KThread* GetHostDummyThread(KThread* existing_thread) { | 392 | LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) { |
| 393 | const auto initialize{[](KThread* thread) { | 393 | const auto initialize{[](KThread* thread) LTO_NOINLINE { |
| 394 | ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); | 394 | ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); |
| 395 | return thread; | 395 | return thread; |
| 396 | }}; | 396 | }}; |
| @@ -424,11 +424,11 @@ struct KernelCore::Impl { | |||
| 424 | 424 | ||
| 425 | static inline thread_local bool is_phantom_mode_for_singlecore{false}; | 425 | static inline thread_local bool is_phantom_mode_for_singlecore{false}; |
| 426 | 426 | ||
| 427 | bool IsPhantomModeForSingleCore() const { | 427 | LTO_NOINLINE bool IsPhantomModeForSingleCore() const { |
| 428 | return is_phantom_mode_for_singlecore; | 428 | return is_phantom_mode_for_singlecore; |
| 429 | } | 429 | } |
| 430 | 430 | ||
| 431 | void SetIsPhantomModeForSingleCore(bool value) { | 431 | LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) { |
| 432 | ASSERT(!is_multicore); | 432 | ASSERT(!is_multicore); |
| 433 | is_phantom_mode_for_singlecore = value; | 433 | is_phantom_mode_for_singlecore = value; |
| 434 | } | 434 | } |
| @@ -439,14 +439,14 @@ struct KernelCore::Impl { | |||
| 439 | 439 | ||
| 440 | static inline thread_local KThread* current_thread{nullptr}; | 440 | static inline thread_local KThread* current_thread{nullptr}; |
| 441 | 441 | ||
| 442 | KThread* GetCurrentEmuThread() { | 442 | LTO_NOINLINE KThread* GetCurrentEmuThread() { |
| 443 | if (!current_thread) { | 443 | if (!current_thread) { |
| 444 | current_thread = GetHostDummyThread(nullptr); | 444 | current_thread = GetHostDummyThread(nullptr); |
| 445 | } | 445 | } |
| 446 | return current_thread; | 446 | return current_thread; |
| 447 | } | 447 | } |
| 448 | 448 | ||
| 449 | void SetCurrentEmuThread(KThread* thread) { | 449 | LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) { |
| 450 | current_thread = thread; | 450 | current_thread = thread; |
| 451 | } | 451 | } |
| 452 | 452 | ||
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp index 286f9fd10..31dd98140 100644 --- a/src/core/hle/service/caps/caps.cpp +++ b/src/core/hle/service/caps/caps.cpp | |||
| @@ -16,7 +16,7 @@ namespace Service::Capture { | |||
| 16 | 16 | ||
| 17 | void LoopProcess(Core::System& system) { | 17 | void LoopProcess(Core::System& system) { |
| 18 | auto server_manager = std::make_unique<ServerManager>(system); | 18 | auto server_manager = std::make_unique<ServerManager>(system); |
| 19 | auto album_manager = std::make_shared<AlbumManager>(); | 19 | auto album_manager = std::make_shared<AlbumManager>(system); |
| 20 | 20 | ||
| 21 | server_manager->RegisterNamedService( | 21 | server_manager->RegisterNamedService( |
| 22 | "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager)); | 22 | "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager)); |
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp index e22f72bf6..9925720a3 100644 --- a/src/core/hle/service/caps/caps_a.cpp +++ b/src/core/hle/service/caps/caps_a.cpp | |||
| @@ -128,9 +128,9 @@ void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) { | |||
| 128 | ctx.WriteBuffer(entries); | 128 | ctx.WriteBuffer(entries); |
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | IPC::ResponseBuilder rb{ctx, 3}; | 131 | IPC::ResponseBuilder rb{ctx, 4}; |
| 132 | rb.Push(result); | 132 | rb.Push(result); |
| 133 | rb.Push(entries.size()); | 133 | rb.Push<u64>(entries.size()); |
| 134 | } | 134 | } |
| 135 | 135 | ||
| 136 | void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) { | 136 | void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) { |
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp index 2df6a930a..2b4e3f076 100644 --- a/src/core/hle/service/caps/caps_manager.cpp +++ b/src/core/hle/service/caps/caps_manager.cpp | |||
| @@ -8,12 +8,15 @@ | |||
| 8 | #include "common/fs/file.h" | 8 | #include "common/fs/file.h" |
| 9 | #include "common/fs/path_util.h" | 9 | #include "common/fs/path_util.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "core/core.h" | ||
| 11 | #include "core/hle/service/caps/caps_manager.h" | 12 | #include "core/hle/service/caps/caps_manager.h" |
| 12 | #include "core/hle/service/caps/caps_result.h" | 13 | #include "core/hle/service/caps/caps_result.h" |
| 14 | #include "core/hle/service/time/time_manager.h" | ||
| 15 | #include "core/hle/service/time/time_zone_content_manager.h" | ||
| 13 | 16 | ||
| 14 | namespace Service::Capture { | 17 | namespace Service::Capture { |
| 15 | 18 | ||
| 16 | AlbumManager::AlbumManager() {} | 19 | AlbumManager::AlbumManager(Core::System& system_) : system{system_} {} |
| 17 | 20 | ||
| 18 | AlbumManager::~AlbumManager() = default; | 21 | AlbumManager::~AlbumManager() = default; |
| 19 | 22 | ||
| @@ -83,6 +86,34 @@ Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, Albu | |||
| 83 | } | 86 | } |
| 84 | 87 | ||
| 85 | Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries, | 88 | Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries, |
| 89 | ContentType contex_type, s64 start_posix_time, | ||
| 90 | s64 end_posix_time, u64 aruid) const { | ||
| 91 | if (!is_mounted) { | ||
| 92 | return ResultIsNotMounted; | ||
| 93 | } | ||
| 94 | |||
| 95 | std::vector<ApplicationAlbumEntry> album_entries; | ||
| 96 | const auto start_date = ConvertToAlbumDateTime(start_posix_time); | ||
| 97 | const auto end_date = ConvertToAlbumDateTime(end_posix_time); | ||
| 98 | const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid); | ||
| 99 | |||
| 100 | if (result.IsError()) { | ||
| 101 | return result; | ||
| 102 | } | ||
| 103 | |||
| 104 | for (const auto& album_entry : album_entries) { | ||
| 105 | ApplicationAlbumFileEntry entry{ | ||
| 106 | .entry = album_entry, | ||
| 107 | .datetime = album_entry.datetime, | ||
| 108 | .unknown = {}, | ||
| 109 | }; | ||
| 110 | out_entries.push_back(entry); | ||
| 111 | } | ||
| 112 | |||
| 113 | return ResultSuccess; | ||
| 114 | } | ||
| 115 | |||
| 116 | Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries, | ||
| 86 | ContentType contex_type, AlbumFileDateTime start_date, | 117 | ContentType contex_type, AlbumFileDateTime start_date, |
| 87 | AlbumFileDateTime end_date, u64 aruid) const { | 118 | AlbumFileDateTime end_date, u64 aruid) const { |
| 88 | if (!is_mounted) { | 119 | if (!is_mounted) { |
| @@ -93,31 +124,25 @@ Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& ou | |||
| 93 | if (file_id.type != contex_type) { | 124 | if (file_id.type != contex_type) { |
| 94 | continue; | 125 | continue; |
| 95 | } | 126 | } |
| 96 | |||
| 97 | if (file_id.date > start_date) { | 127 | if (file_id.date > start_date) { |
| 98 | continue; | 128 | continue; |
| 99 | } | 129 | } |
| 100 | |||
| 101 | if (file_id.date < end_date) { | 130 | if (file_id.date < end_date) { |
| 102 | continue; | 131 | continue; |
| 103 | } | 132 | } |
| 104 | |||
| 105 | if (out_entries.size() >= SdAlbumFileLimit) { | 133 | if (out_entries.size() >= SdAlbumFileLimit) { |
| 106 | break; | 134 | break; |
| 107 | } | 135 | } |
| 108 | 136 | ||
| 109 | const auto entry_size = Common::FS::GetSize(path); | 137 | const auto entry_size = Common::FS::GetSize(path); |
| 110 | ApplicationAlbumFileEntry entry{.entry = | 138 | ApplicationAlbumEntry entry{ |
| 111 | { | 139 | .size = entry_size, |
| 112 | .size = entry_size, | 140 | .hash{}, |
| 113 | .hash{}, | 141 | .datetime = file_id.date, |
| 114 | .datetime = file_id.date, | 142 | .storage = file_id.storage, |
| 115 | .storage = file_id.storage, | 143 | .content = contex_type, |
| 116 | .content = contex_type, | 144 | .unknown = 1, |
| 117 | .unknown = 1, | 145 | }; |
| 118 | }, | ||
| 119 | .datetime = file_id.date, | ||
| 120 | .unknown = {}}; | ||
| 121 | out_entries.push_back(entry); | 146 | out_entries.push_back(entry); |
| 122 | } | 147 | } |
| 123 | 148 | ||
| @@ -274,12 +299,12 @@ Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem: | |||
| 274 | .application_id = static_cast<u64>(std::stoll(application, 0, 16)), | 299 | .application_id = static_cast<u64>(std::stoll(application, 0, 16)), |
| 275 | .date = | 300 | .date = |
| 276 | { | 301 | { |
| 277 | .year = static_cast<u16>(std::stoi(year)), | 302 | .year = static_cast<s16>(std::stoi(year)), |
| 278 | .month = static_cast<u8>(std::stoi(month)), | 303 | .month = static_cast<s8>(std::stoi(month)), |
| 279 | .day = static_cast<u8>(std::stoi(day)), | 304 | .day = static_cast<s8>(std::stoi(day)), |
| 280 | .hour = static_cast<u8>(std::stoi(hour)), | 305 | .hour = static_cast<s8>(std::stoi(hour)), |
| 281 | .minute = static_cast<u8>(std::stoi(minute)), | 306 | .minute = static_cast<s8>(std::stoi(minute)), |
| 282 | .second = static_cast<u8>(std::stoi(second)), | 307 | .second = static_cast<s8>(std::stoi(second)), |
| 283 | .unique_id = 0, | 308 | .unique_id = 0, |
| 284 | }, | 309 | }, |
| 285 | .storage = AlbumStorage::Sd, | 310 | .storage = AlbumStorage::Sd, |
| @@ -339,4 +364,23 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p | |||
| 339 | 364 | ||
| 340 | return ResultSuccess; | 365 | return ResultSuccess; |
| 341 | } | 366 | } |
| 367 | |||
| 368 | AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const { | ||
| 369 | Time::TimeZone::CalendarInfo calendar_date{}; | ||
| 370 | const auto& time_zone_manager = | ||
| 371 | system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager(); | ||
| 372 | |||
| 373 | time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date); | ||
| 374 | |||
| 375 | return { | ||
| 376 | .year = calendar_date.time.year, | ||
| 377 | .month = calendar_date.time.month, | ||
| 378 | .day = calendar_date.time.day, | ||
| 379 | .hour = calendar_date.time.hour, | ||
| 380 | .minute = calendar_date.time.minute, | ||
| 381 | .second = calendar_date.time.second, | ||
| 382 | .unique_id = 0, | ||
| 383 | }; | ||
| 384 | } | ||
| 385 | |||
| 342 | } // namespace Service::Capture | 386 | } // namespace Service::Capture |
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h index 8337c655c..f65eb12c1 100644 --- a/src/core/hle/service/caps/caps_manager.h +++ b/src/core/hle/service/caps/caps_manager.h | |||
| @@ -37,7 +37,7 @@ namespace Service::Capture { | |||
| 37 | 37 | ||
| 38 | class AlbumManager { | 38 | class AlbumManager { |
| 39 | public: | 39 | public: |
| 40 | explicit AlbumManager(); | 40 | explicit AlbumManager(Core::System& system_); |
| 41 | ~AlbumManager(); | 41 | ~AlbumManager(); |
| 42 | 42 | ||
| 43 | Result DeleteAlbumFile(const AlbumFileId& file_id); | 43 | Result DeleteAlbumFile(const AlbumFileId& file_id); |
| @@ -45,6 +45,9 @@ public: | |||
| 45 | Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage, | 45 | Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage, |
| 46 | u8 flags) const; | 46 | u8 flags) const; |
| 47 | Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries, | 47 | Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries, |
| 48 | ContentType contex_type, s64 start_posix_time, s64 end_posix_time, | ||
| 49 | u64 aruid) const; | ||
| 50 | Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries, | ||
| 48 | ContentType contex_type, AlbumFileDateTime start_date, | 51 | ContentType contex_type, AlbumFileDateTime start_date, |
| 49 | AlbumFileDateTime end_date, u64 aruid) const; | 52 | AlbumFileDateTime end_date, u64 aruid) const; |
| 50 | Result GetAutoSavingStorage(bool& out_is_autosaving) const; | 53 | Result GetAutoSavingStorage(bool& out_is_autosaving) const; |
| @@ -65,8 +68,12 @@ private: | |||
| 65 | Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width, | 68 | Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width, |
| 66 | int height, ScreenShotDecoderFlag flag) const; | 69 | int height, ScreenShotDecoderFlag flag) const; |
| 67 | 70 | ||
| 71 | AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const; | ||
| 72 | |||
| 68 | bool is_mounted{}; | 73 | bool is_mounted{}; |
| 69 | std::unordered_map<AlbumFileId, std::filesystem::path> album_files; | 74 | std::unordered_map<AlbumFileId, std::filesystem::path> album_files; |
| 75 | |||
| 76 | Core::System& system; | ||
| 70 | }; | 77 | }; |
| 71 | 78 | ||
| 72 | } // namespace Service::Capture | 79 | } // namespace Service::Capture |
diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h index bf6061273..7fd357954 100644 --- a/src/core/hle/service/caps/caps_types.h +++ b/src/core/hle/service/caps/caps_types.h | |||
| @@ -41,13 +41,13 @@ enum class ScreenShotDecoderFlag : u64 { | |||
| 41 | 41 | ||
| 42 | // This is nn::capsrv::AlbumFileDateTime | 42 | // This is nn::capsrv::AlbumFileDateTime |
| 43 | struct AlbumFileDateTime { | 43 | struct AlbumFileDateTime { |
| 44 | u16 year{}; | 44 | s16 year{}; |
| 45 | u8 month{}; | 45 | s8 month{}; |
| 46 | u8 day{}; | 46 | s8 day{}; |
| 47 | u8 hour{}; | 47 | s8 hour{}; |
| 48 | u8 minute{}; | 48 | s8 minute{}; |
| 49 | u8 second{}; | 49 | s8 second{}; |
| 50 | u8 unique_id{}; | 50 | s8 unique_id{}; |
| 51 | 51 | ||
| 52 | friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default; | 52 | friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default; |
| 53 | friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) { | 53 | friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) { |
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp index 260f25490..b6b33fb2f 100644 --- a/src/core/hle/service/caps/caps_u.cpp +++ b/src/core/hle/service/caps/caps_u.cpp | |||
| @@ -50,22 +50,35 @@ void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) { | |||
| 50 | 50 | ||
| 51 | void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) { | 51 | void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) { |
| 52 | IPC::RequestParser rp{ctx}; | 52 | IPC::RequestParser rp{ctx}; |
| 53 | const auto pid{rp.Pop<s32>()}; | 53 | struct Parameters { |
| 54 | const auto content_type{rp.PopEnum<ContentType>()}; | 54 | ContentType content_type; |
| 55 | const auto start_posix_time{rp.Pop<s64>()}; | 55 | INSERT_PADDING_BYTES(7); |
| 56 | const auto end_posix_time{rp.Pop<s64>()}; | 56 | s64 start_posix_time; |
| 57 | const auto applet_resource_user_id{rp.Pop<u64>()}; | 57 | s64 end_posix_time; |
| 58 | u64 applet_resource_user_id; | ||
| 59 | }; | ||
| 60 | static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size."); | ||
| 61 | |||
| 62 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 58 | 63 | ||
| 59 | LOG_WARNING(Service_Capture, | 64 | LOG_WARNING(Service_Capture, |
| 60 | "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " | 65 | "(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, " |
| 61 | "end_posix_time={}, applet_resource_user_id={}", | 66 | "applet_resource_user_id={}", |
| 62 | pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id); | 67 | parameters.content_type, parameters.start_posix_time, parameters.end_posix_time, |
| 68 | parameters.applet_resource_user_id); | ||
| 63 | 69 | ||
| 64 | // TODO: Translate posix to DateTime | 70 | Result result = ResultSuccess; |
| 71 | |||
| 72 | if (result.IsSuccess()) { | ||
| 73 | result = manager->IsAlbumMounted(AlbumStorage::Sd); | ||
| 74 | } | ||
| 65 | 75 | ||
| 66 | std::vector<ApplicationAlbumFileEntry> entries; | 76 | std::vector<ApplicationAlbumFileEntry> entries; |
| 67 | const Result result = | 77 | if (result.IsSuccess()) { |
| 68 | manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id); | 78 | result = manager->GetAlbumFileList(entries, parameters.content_type, |
| 79 | parameters.start_posix_time, parameters.end_posix_time, | ||
| 80 | parameters.applet_resource_user_id); | ||
| 81 | } | ||
| 69 | 82 | ||
| 70 | if (!entries.empty()) { | 83 | if (!entries.empty()) { |
| 71 | ctx.WriteBuffer(entries); | 84 | ctx.WriteBuffer(entries); |
| @@ -78,19 +91,38 @@ void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestCo | |||
| 78 | 91 | ||
| 79 | void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) { | 92 | void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) { |
| 80 | IPC::RequestParser rp{ctx}; | 93 | IPC::RequestParser rp{ctx}; |
| 81 | const auto pid{rp.Pop<s32>()}; | 94 | struct Parameters { |
| 82 | const auto content_type{rp.PopEnum<ContentType>()}; | 95 | ContentType content_type; |
| 83 | const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()}; | 96 | INSERT_PADDING_BYTES(1); |
| 84 | const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()}; | 97 | AlbumFileDateTime start_date_time; |
| 85 | const auto applet_resource_user_id{rp.Pop<u64>()}; | 98 | AlbumFileDateTime end_date_time; |
| 99 | INSERT_PADDING_BYTES(6); | ||
| 100 | u64 applet_resource_user_id; | ||
| 101 | }; | ||
| 102 | static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size."); | ||
| 103 | |||
| 104 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 86 | 105 | ||
| 87 | LOG_WARNING(Service_Capture, | 106 | LOG_WARNING(Service_Capture, |
| 88 | "(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid, | 107 | "(STUBBED) called. content_type={}, start_date={}/{}/{}, " |
| 89 | content_type, applet_resource_user_id); | 108 | "end_date={}/{}/{}, applet_resource_user_id={}", |
| 109 | parameters.content_type, parameters.start_date_time.year, | ||
| 110 | parameters.start_date_time.month, parameters.start_date_time.day, | ||
| 111 | parameters.end_date_time.year, parameters.end_date_time.month, | ||
| 112 | parameters.end_date_time.day, parameters.applet_resource_user_id); | ||
| 90 | 113 | ||
| 91 | std::vector<ApplicationAlbumFileEntry> entries; | 114 | Result result = ResultSuccess; |
| 92 | const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time, | 115 | |
| 93 | end_date_time, applet_resource_user_id); | 116 | if (result.IsSuccess()) { |
| 117 | result = manager->IsAlbumMounted(AlbumStorage::Sd); | ||
| 118 | } | ||
| 119 | |||
| 120 | std::vector<ApplicationAlbumEntry> entries; | ||
| 121 | if (result.IsSuccess()) { | ||
| 122 | result = | ||
| 123 | manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time, | ||
| 124 | parameters.end_date_time, parameters.applet_resource_user_id); | ||
| 125 | } | ||
| 94 | 126 | ||
| 95 | if (!entries.empty()) { | 127 | if (!entries.empty()) { |
| 96 | ctx.WriteBuffer(entries); | 128 | ctx.WriteBuffer(entries); |
diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp index 4ed3f02e2..0090e8568 100644 --- a/src/core/hle/service/jit/jit_context.cpp +++ b/src/core/hle/service/jit/jit_context.cpp | |||
| @@ -156,6 +156,8 @@ public: | |||
| 156 | 156 | ||
| 157 | bool LoadNRO(std::span<const u8> data) { | 157 | bool LoadNRO(std::span<const u8> data) { |
| 158 | local_memory.clear(); | 158 | local_memory.clear(); |
| 159 | |||
| 160 | relocbase = local_memory.size(); | ||
| 159 | local_memory.insert(local_memory.end(), data.begin(), data.end()); | 161 | local_memory.insert(local_memory.end(), data.begin(), data.end()); |
| 160 | 162 | ||
| 161 | if (FixupRelocations()) { | 163 | if (FixupRelocations()) { |
| @@ -181,8 +183,8 @@ public: | |||
| 181 | // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html | 183 | // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html |
| 182 | // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html | 184 | // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html |
| 183 | VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; | 185 | VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; |
| 184 | VAddr rela_dyn = 0; | 186 | VAddr rela_dyn = 0, relr_dyn = 0; |
| 185 | size_t num_rela = 0; | 187 | size_t num_rela = 0, num_relr = 0; |
| 186 | while (true) { | 188 | while (true) { |
| 187 | const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; | 189 | const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; |
| 188 | dynamic_offset += sizeof(Elf64_Dyn); | 190 | dynamic_offset += sizeof(Elf64_Dyn); |
| @@ -196,6 +198,12 @@ public: | |||
| 196 | if (dyn.d_tag == ElfDtRelasz) { | 198 | if (dyn.d_tag == ElfDtRelasz) { |
| 197 | num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); | 199 | num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); |
| 198 | } | 200 | } |
| 201 | if (dyn.d_tag == ElfDtRelr) { | ||
| 202 | relr_dyn = dyn.d_un.d_ptr; | ||
| 203 | } | ||
| 204 | if (dyn.d_tag == ElfDtRelrsz) { | ||
| 205 | num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr); | ||
| 206 | } | ||
| 199 | } | 207 | } |
| 200 | 208 | ||
| 201 | for (size_t i = 0; i < num_rela; i++) { | 209 | for (size_t i = 0; i < num_rela; i++) { |
| @@ -207,6 +215,29 @@ public: | |||
| 207 | callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); | 215 | callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); |
| 208 | } | 216 | } |
| 209 | 217 | ||
| 218 | VAddr relr_where = 0; | ||
| 219 | for (size_t i = 0; i < num_relr; i++) { | ||
| 220 | const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))}; | ||
| 221 | const auto incr{[&](VAddr where) { | ||
| 222 | callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase); | ||
| 223 | }}; | ||
| 224 | |||
| 225 | if ((relr & 1) == 0) { | ||
| 226 | // where pointer | ||
| 227 | relr_where = relocbase + relr; | ||
| 228 | incr(relr_where); | ||
| 229 | relr_where += sizeof(Elf64_Addr); | ||
| 230 | } else { | ||
| 231 | // bitmap | ||
| 232 | for (int bit = 1; bit < 64; bit++) { | ||
| 233 | if ((relr & (1ULL << bit)) != 0) { | ||
| 234 | incr(relr_where + i * sizeof(Elf64_Addr)); | ||
| 235 | } | ||
| 236 | } | ||
| 237 | relr_where += 63 * sizeof(Elf64_Addr); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 210 | return true; | 241 | return true; |
| 211 | } | 242 | } |
| 212 | 243 | ||
| @@ -313,6 +344,7 @@ public: | |||
| 313 | Core::Memory::Memory& memory; | 344 | Core::Memory::Memory& memory; |
| 314 | VAddr top_of_stack; | 345 | VAddr top_of_stack; |
| 315 | VAddr heap_pointer; | 346 | VAddr heap_pointer; |
| 347 | VAddr relocbase; | ||
| 316 | }; | 348 | }; |
| 317 | 349 | ||
| 318 | void DynarmicCallbacks64::CallSVC(u32 swi) { | 350 | void DynarmicCallbacks64::CallSVC(u32 swi) { |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 3676eaaa9..e71b87e99 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h | |||
| @@ -118,6 +118,8 @@ public: | |||
| 118 | 118 | ||
| 119 | void InsertUploadMemoryBarrier(); | 119 | void InsertUploadMemoryBarrier(); |
| 120 | 120 | ||
| 121 | void TransitionImageLayout(Image& image) {} | ||
| 122 | |||
| 121 | FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; | 123 | FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; |
| 122 | 124 | ||
| 123 | bool HasNativeBgr() const noexcept { | 125 | bool HasNativeBgr() const noexcept { |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 1b8bb9662..93773a69f 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -2013,4 +2013,32 @@ void TextureCacheRuntime::AccelerateImageUpload( | |||
| 2013 | ASSERT(false); | 2013 | ASSERT(false); |
| 2014 | } | 2014 | } |
| 2015 | 2015 | ||
| 2016 | void TextureCacheRuntime::TransitionImageLayout(Image& image) { | ||
| 2017 | if (!image.ExchangeInitialization()) { | ||
| 2018 | VkImageMemoryBarrier barrier{ | ||
| 2019 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | ||
| 2020 | .pNext = nullptr, | ||
| 2021 | .srcAccessMask = VK_ACCESS_NONE, | ||
| 2022 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, | ||
| 2023 | .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, | ||
| 2024 | .newLayout = VK_IMAGE_LAYOUT_GENERAL, | ||
| 2025 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||
| 2026 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||
| 2027 | .image = image.Handle(), | ||
| 2028 | .subresourceRange{ | ||
| 2029 | .aspectMask = image.AspectMask(), | ||
| 2030 | .baseMipLevel = 0, | ||
| 2031 | .levelCount = VK_REMAINING_MIP_LEVELS, | ||
| 2032 | .baseArrayLayer = 0, | ||
| 2033 | .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||
| 2034 | }, | ||
| 2035 | }; | ||
| 2036 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 2037 | scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) { | ||
| 2038 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | ||
| 2039 | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier); | ||
| 2040 | }); | ||
| 2041 | } | ||
| 2042 | } | ||
| 2043 | |||
| 2016 | } // namespace Vulkan | 2044 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index d6c5a15cc..7a0807709 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h | |||
| @@ -92,6 +92,8 @@ public: | |||
| 92 | 92 | ||
| 93 | void InsertUploadMemoryBarrier() {} | 93 | void InsertUploadMemoryBarrier() {} |
| 94 | 94 | ||
| 95 | void TransitionImageLayout(Image& image); | ||
| 96 | |||
| 95 | bool HasBrokenTextureViewFormats() const noexcept { | 97 | bool HasBrokenTextureViewFormats() const noexcept { |
| 96 | // No known Vulkan driver has broken image views | 98 | // No known Vulkan driver has broken image views |
| 97 | return false; | 99 | return false; |
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 1bdb0def5..d575c57ca 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h | |||
| @@ -1016,6 +1016,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) { | |||
| 1016 | 1016 | ||
| 1017 | if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { | 1017 | if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { |
| 1018 | LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); | 1018 | LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); |
| 1019 | runtime.TransitionImageLayout(image); | ||
| 1019 | return; | 1020 | return; |
| 1020 | } | 1021 | } |
| 1021 | if (True(image.flags & ImageFlagBits::AsynchronousDecode)) { | 1022 | if (True(image.flags & ImageFlagBits::AsynchronousDecode)) { |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 74f48031a..2bb1a0239 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -826,12 +826,13 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 826 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); | 826 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); |
| 827 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | 827 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); |
| 828 | 828 | ||
| 829 | // Before deleting rows, cancel the worker so that it is not using them | ||
| 830 | emit ShouldCancelWorker(); | ||
| 831 | |||
| 829 | // Delete any rows that might already exist if we're repopulating | 832 | // Delete any rows that might already exist if we're repopulating |
| 830 | item_model->removeRows(0, item_model->rowCount()); | 833 | item_model->removeRows(0, item_model->rowCount()); |
| 831 | search_field->clear(); | 834 | search_field->clear(); |
| 832 | 835 | ||
| 833 | emit ShouldCancelWorker(); | ||
| 834 | |||
| 835 | GameListWorker* worker = | 836 | GameListWorker* worker = |
| 836 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); | 837 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); |
| 837 | 838 | ||
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 588f1dd6e..077ced12b 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -293,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | |||
| 293 | void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, | 293 | void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, |
| 294 | GameListDir* parent_dir) { | 294 | GameListDir* parent_dir) { |
| 295 | const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { | 295 | const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { |
| 296 | if (stop_processing) { | 296 | if (stop_requested) { |
| 297 | // Breaks the callback loop. | 297 | // Breaks the callback loop. |
| 298 | return false; | 298 | return false; |
| 299 | } | 299 | } |
| @@ -399,7 +399,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 399 | } | 399 | } |
| 400 | 400 | ||
| 401 | void GameListWorker::run() { | 401 | void GameListWorker::run() { |
| 402 | stop_processing = false; | ||
| 403 | provider->ClearAllEntries(); | 402 | provider->ClearAllEntries(); |
| 404 | 403 | ||
| 405 | for (UISettings::GameDir& game_dir : game_dirs) { | 404 | for (UISettings::GameDir& game_dir : game_dirs) { |
| @@ -427,9 +426,11 @@ void GameListWorker::run() { | |||
| 427 | } | 426 | } |
| 428 | 427 | ||
| 429 | emit Finished(watch_list); | 428 | emit Finished(watch_list); |
| 429 | processing_completed.Set(); | ||
| 430 | } | 430 | } |
| 431 | 431 | ||
| 432 | void GameListWorker::Cancel() { | 432 | void GameListWorker::Cancel() { |
| 433 | this->disconnect(); | 433 | this->disconnect(); |
| 434 | stop_processing = true; | 434 | stop_requested.store(true); |
| 435 | processing_completed.Wait(); | ||
| 435 | } | 436 | } |
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 2bb0a0cb6..54dc05e30 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include <QRunnable> | 12 | #include <QRunnable> |
| 13 | #include <QString> | 13 | #include <QString> |
| 14 | 14 | ||
| 15 | #include "common/thread.h" | ||
| 15 | #include "yuzu/compatibility_list.h" | 16 | #include "yuzu/compatibility_list.h" |
| 16 | #include "yuzu/play_time_manager.h" | 17 | #include "yuzu/play_time_manager.h" |
| 17 | 18 | ||
| @@ -82,7 +83,9 @@ private: | |||
| 82 | const PlayTime::PlayTimeManager& play_time_manager; | 83 | const PlayTime::PlayTimeManager& play_time_manager; |
| 83 | 84 | ||
| 84 | QStringList watch_list; | 85 | QStringList watch_list; |
| 85 | std::atomic_bool stop_processing; | 86 | |
| 87 | Common::Event processing_completed; | ||
| 88 | std::atomic_bool stop_requested = false; | ||
| 86 | 89 | ||
| 87 | Core::System& system; | 90 | Core::System& system; |
| 88 | }; | 91 | }; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 425f546f7..1431cf2fe 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -67,6 +67,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 67 | #define QT_NO_OPENGL | 67 | #define QT_NO_OPENGL |
| 68 | #include <QClipboard> | 68 | #include <QClipboard> |
| 69 | #include <QDesktopServices> | 69 | #include <QDesktopServices> |
| 70 | #include <QDir> | ||
| 70 | #include <QFile> | 71 | #include <QFile> |
| 71 | #include <QFileDialog> | 72 | #include <QFileDialog> |
| 72 | #include <QInputDialog> | 73 | #include <QInputDialog> |
| @@ -76,6 +77,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 76 | #include <QPushButton> | 77 | #include <QPushButton> |
| 77 | #include <QScreen> | 78 | #include <QScreen> |
| 78 | #include <QShortcut> | 79 | #include <QShortcut> |
| 80 | #include <QStandardPaths> | ||
| 79 | #include <QStatusBar> | 81 | #include <QStatusBar> |
| 80 | #include <QString> | 82 | #include <QString> |
| 81 | #include <QSysInfo> | 83 | #include <QSysInfo> |
| @@ -2868,44 +2870,50 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga | |||
| 2868 | #endif // __linux__ | 2870 | #endif // __linux__ |
| 2869 | 2871 | ||
| 2870 | std::filesystem::path target_directory{}; | 2872 | std::filesystem::path target_directory{}; |
| 2871 | // Determine target directory for shortcut | ||
| 2872 | #if defined(WIN32) | ||
| 2873 | const char* home = std::getenv("USERPROFILE"); | ||
| 2874 | #else | ||
| 2875 | const char* home = std::getenv("HOME"); | ||
| 2876 | #endif | ||
| 2877 | const std::filesystem::path home_path = (home == nullptr ? "~" : home); | ||
| 2878 | const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); | ||
| 2879 | 2873 | ||
| 2880 | if (target == GameListShortcutTarget::Desktop) { | 2874 | switch (target) { |
| 2881 | target_directory = home_path / "Desktop"; | 2875 | case GameListShortcutTarget::Desktop: { |
| 2882 | if (!Common::FS::IsDir(target_directory)) { | 2876 | const QString desktop_path = |
| 2883 | QMessageBox::critical( | 2877 | QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); |
| 2884 | this, tr("Create Shortcut"), | 2878 | target_directory = desktop_path.toUtf8().toStdString(); |
| 2885 | tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") | 2879 | break; |
| 2886 | .arg(QString::fromStdString(target_directory.generic_string())), | 2880 | } |
| 2887 | QMessageBox::StandardButton::Ok); | 2881 | case GameListShortcutTarget::Applications: { |
| 2888 | return; | 2882 | const QString applications_path = |
| 2889 | } | 2883 | QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); |
| 2890 | } else if (target == GameListShortcutTarget::Applications) { | 2884 | if (applications_path.isEmpty()) { |
| 2891 | target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / | 2885 | const char* home = std::getenv("HOME"); |
| 2892 | "applications"; | 2886 | if (home != nullptr) { |
| 2893 | if (!Common::FS::CreateDirs(target_directory)) { | 2887 | target_directory = std::filesystem::path(home) / ".local/share/applications"; |
| 2894 | QMessageBox::critical( | 2888 | } |
| 2895 | this, tr("Create Shortcut"), | 2889 | } else { |
| 2896 | tr("Cannot create shortcut in applications menu. Path \"%1\" " | 2890 | target_directory = applications_path.toUtf8().toStdString(); |
| 2897 | "does not exist and cannot be created.") | ||
| 2898 | .arg(QString::fromStdString(target_directory.generic_string())), | ||
| 2899 | QMessageBox::StandardButton::Ok); | ||
| 2900 | return; | ||
| 2901 | } | 2891 | } |
| 2892 | break; | ||
| 2893 | } | ||
| 2894 | default: | ||
| 2895 | return; | ||
| 2896 | } | ||
| 2897 | |||
| 2898 | const QDir dir(QString::fromStdString(target_directory.generic_string())); | ||
| 2899 | if (!dir.exists()) { | ||
| 2900 | QMessageBox::critical(this, tr("Create Shortcut"), | ||
| 2901 | tr("Cannot create shortcut. Path \"%1\" does not exist.") | ||
| 2902 | .arg(QString::fromStdString(target_directory.generic_string())), | ||
| 2903 | QMessageBox::StandardButton::Ok); | ||
| 2904 | return; | ||
| 2902 | } | 2905 | } |
| 2903 | 2906 | ||
| 2904 | const std::string game_file_name = std::filesystem::path(game_path).filename().string(); | 2907 | const std::string game_file_name = std::filesystem::path(game_path).filename().string(); |
| 2905 | // Determine full paths for icon and shortcut | 2908 | // Determine full paths for icon and shortcut |
| 2906 | #if defined(__linux__) || defined(__FreeBSD__) | 2909 | #if defined(__linux__) || defined(__FreeBSD__) |
| 2910 | const char* home = std::getenv("HOME"); | ||
| 2911 | const std::filesystem::path home_path = (home == nullptr ? "~" : home); | ||
| 2912 | const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); | ||
| 2913 | |||
| 2907 | std::filesystem::path system_icons_path = | 2914 | std::filesystem::path system_icons_path = |
| 2908 | (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) / | 2915 | (xdg_data_home == nullptr ? home_path / ".local/share/" |
| 2916 | : std::filesystem::path(xdg_data_home)) / | ||
| 2909 | "icons/hicolor/256x256"; | 2917 | "icons/hicolor/256x256"; |
| 2910 | if (!Common::FS::CreateDirs(system_icons_path)) { | 2918 | if (!Common::FS::CreateDirs(system_icons_path)) { |
| 2911 | QMessageBox::critical( | 2919 | QMessageBox::critical( |