diff options
Diffstat (limited to 'src')
94 files changed, 1846 insertions, 577 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 84a3308b7..ac43d84b7 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -27,7 +27,7 @@ android { | |||
| 27 | namespace = "org.yuzu.yuzu_emu" | 27 | namespace = "org.yuzu.yuzu_emu" |
| 28 | 28 | ||
| 29 | compileSdkVersion = "android-34" | 29 | compileSdkVersion = "android-34" |
| 30 | ndkVersion = "25.2.9519653" | 30 | ndkVersion = "26.1.10909125" |
| 31 | 31 | ||
| 32 | buildFeatures { | 32 | buildFeatures { |
| 33 | viewBinding = true | 33 | viewBinding = true |
| @@ -203,23 +203,23 @@ ktlint { | |||
| 203 | } | 203 | } |
| 204 | 204 | ||
| 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/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp index 972d5e45b..ef301d8b4 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp | |||
| @@ -77,6 +77,7 @@ void AudioRenderer::Wait() { | |||
| 77 | "{}, got {}", | 77 | "{}, got {}", |
| 78 | Message::RenderResponse, msg); | 78 | Message::RenderResponse, msg); |
| 79 | } | 79 | } |
| 80 | PostDSPClearCommandBuffer(); | ||
| 80 | } | 81 | } |
| 81 | 82 | ||
| 82 | void AudioRenderer::Send(Direction dir, u32 message) { | 83 | void AudioRenderer::Send(Direction dir, u32 message) { |
| @@ -96,6 +97,14 @@ void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u | |||
| 96 | command_buffers[session_id].reset_buffer = reset; | 97 | command_buffers[session_id].reset_buffer = reset; |
| 97 | } | 98 | } |
| 98 | 99 | ||
| 100 | void AudioRenderer::PostDSPClearCommandBuffer() noexcept { | ||
| 101 | for (auto& buffer : command_buffers) { | ||
| 102 | buffer.buffer = 0; | ||
| 103 | buffer.size = 0; | ||
| 104 | buffer.reset_buffer = false; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 99 | u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { | 108 | u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { |
| 100 | return command_buffers[session_id].remaining_command_count; | 109 | return command_buffers[session_id].remaining_command_count; |
| 101 | } | 110 | } |
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h index 85874d88a..57b89d9fe 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h | |||
| @@ -85,6 +85,8 @@ private: | |||
| 85 | */ | 85 | */ |
| 86 | void CreateSinkStreams(); | 86 | void CreateSinkStreams(); |
| 87 | 87 | ||
| 88 | void PostDSPClearCommandBuffer() noexcept; | ||
| 89 | |||
| 88 | /// Core system | 90 | /// Core system |
| 89 | Core::System& system; | 91 | Core::System& system; |
| 90 | /// The output sink the AudioRenderer will send samples to | 92 | /// The output sink the AudioRenderer will send samples to |
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 6081352a2..d66d04fae 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp | |||
| @@ -204,6 +204,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz | |||
| 204 | // paused and we'll desync, so just play silence. | 204 | // paused and we'll desync, so just play silence. |
| 205 | if (system.IsPaused() || system.IsShuttingDown()) { | 205 | if (system.IsPaused() || system.IsShuttingDown()) { |
| 206 | if (system.IsShuttingDown()) { | 206 | if (system.IsShuttingDown()) { |
| 207 | { | ||
| 208 | std::scoped_lock lk{release_mutex}; | ||
| 209 | queued_buffers.store(0); | ||
| 210 | } | ||
| 207 | release_cv.notify_one(); | 211 | release_cv.notify_one(); |
| 208 | } | 212 | } |
| 209 | 213 | ||
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 0dad9338a..47d028d48 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h | |||
| @@ -39,8 +39,12 @@ | |||
| 39 | #define Crash() exit(1) | 39 | #define Crash() exit(1) |
| 40 | #endif | 40 | #endif |
| 41 | 41 | ||
| 42 | #define LTO_NOINLINE __attribute__((noinline)) | ||
| 43 | |||
| 42 | #else // _MSC_VER | 44 | #else // _MSC_VER |
| 43 | 45 | ||
| 46 | #define LTO_NOINLINE | ||
| 47 | |||
| 44 | // Locale Cross-Compatibility | 48 | // Locale Cross-Compatibility |
| 45 | #define locale_t _locale_t | 49 | #define locale_t _locale_t |
| 46 | 50 | ||
diff --git a/src/common/elf.h b/src/common/elf.h index 14a5e9597..0b728dc54 100644 --- a/src/common/elf.h +++ b/src/common/elf.h | |||
| @@ -211,6 +211,11 @@ struct Elf64_Rela { | |||
| 211 | Elf64_Sxword r_addend; /* Addend */ | 211 | Elf64_Sxword r_addend; /* Addend */ |
| 212 | }; | 212 | }; |
| 213 | 213 | ||
| 214 | /* RELR relocation table entry */ | ||
| 215 | |||
| 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/common/settings.cpp b/src/common/settings.cpp index 3fde3cae6..98b43e49c 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -45,6 +45,7 @@ SWITCHABLE(CpuAccuracy, true); | |||
| 45 | SWITCHABLE(FullscreenMode, true); | 45 | SWITCHABLE(FullscreenMode, true); |
| 46 | SWITCHABLE(GpuAccuracy, true); | 46 | SWITCHABLE(GpuAccuracy, true); |
| 47 | SWITCHABLE(Language, true); | 47 | SWITCHABLE(Language, true); |
| 48 | SWITCHABLE(MemoryLayout, true); | ||
| 48 | SWITCHABLE(NvdecEmulation, false); | 49 | SWITCHABLE(NvdecEmulation, false); |
| 49 | SWITCHABLE(Region, true); | 50 | SWITCHABLE(Region, true); |
| 50 | SWITCHABLE(RendererBackend, true); | 51 | SWITCHABLE(RendererBackend, true); |
| @@ -61,6 +62,10 @@ SWITCHABLE(u32, false); | |||
| 61 | SWITCHABLE(u8, false); | 62 | SWITCHABLE(u8, false); |
| 62 | SWITCHABLE(u8, true); | 63 | SWITCHABLE(u8, true); |
| 63 | 64 | ||
| 65 | // Used in UISettings | ||
| 66 | // TODO see if we can move this to uisettings.cpp | ||
| 67 | SWITCHABLE(ConfirmStop, true); | ||
| 68 | |||
| 64 | #undef SETTING | 69 | #undef SETTING |
| 65 | #undef SWITCHABLE | 70 | #undef SWITCHABLE |
| 66 | #endif | 71 | #endif |
diff --git a/src/common/settings.h b/src/common/settings.h index 98ab0ec2e..236e33bee 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -67,6 +67,7 @@ SWITCHABLE(CpuAccuracy, true); | |||
| 67 | SWITCHABLE(FullscreenMode, true); | 67 | SWITCHABLE(FullscreenMode, true); |
| 68 | SWITCHABLE(GpuAccuracy, true); | 68 | SWITCHABLE(GpuAccuracy, true); |
| 69 | SWITCHABLE(Language, true); | 69 | SWITCHABLE(Language, true); |
| 70 | SWITCHABLE(MemoryLayout, true); | ||
| 70 | SWITCHABLE(NvdecEmulation, false); | 71 | SWITCHABLE(NvdecEmulation, false); |
| 71 | SWITCHABLE(Region, true); | 72 | SWITCHABLE(Region, true); |
| 72 | SWITCHABLE(RendererBackend, true); | 73 | SWITCHABLE(RendererBackend, true); |
| @@ -83,6 +84,10 @@ SWITCHABLE(u32, false); | |||
| 83 | SWITCHABLE(u8, false); | 84 | SWITCHABLE(u8, false); |
| 84 | SWITCHABLE(u8, true); | 85 | SWITCHABLE(u8, true); |
| 85 | 86 | ||
| 87 | // Used in UISettings | ||
| 88 | // TODO see if we can move this to uisettings.h | ||
| 89 | SWITCHABLE(ConfirmStop, true); | ||
| 90 | |||
| 86 | #undef SETTING | 91 | #undef SETTING |
| 87 | #undef SWITCHABLE | 92 | #undef SWITCHABLE |
| 88 | #endif | 93 | #endif |
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 815cafe15..11429d7a8 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h | |||
| @@ -133,6 +133,8 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid); | |||
| 133 | 133 | ||
| 134 | ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb); | 134 | ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb); |
| 135 | 135 | ||
| 136 | ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never); | ||
| 137 | |||
| 136 | ENUM(FullscreenMode, Borderless, Exclusive); | 138 | ENUM(FullscreenMode, Borderless, Exclusive); |
| 137 | 139 | ||
| 138 | ENUM(NvdecEmulation, Off, Cpu, Gpu); | 140 | ENUM(NvdecEmulation, Off, Cpu, Gpu); |
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 1fbfbf31f..0b0cef984 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp | |||
| @@ -3405,6 +3405,11 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K | |||
| 3405 | new_attr, KMemoryBlockDisableMergeAttribute::Locked, | 3405 | new_attr, KMemoryBlockDisableMergeAttribute::Locked, |
| 3406 | KMemoryBlockDisableMergeAttribute::None); | 3406 | KMemoryBlockDisableMergeAttribute::None); |
| 3407 | 3407 | ||
| 3408 | // If we have an output page group, open. | ||
| 3409 | if (out_pg) { | ||
| 3410 | out_pg->Open(); | ||
| 3411 | } | ||
| 3412 | |||
| 3408 | R_SUCCEED(); | 3413 | R_SUCCEED(); |
| 3409 | } | 3414 | } |
| 3410 | 3415 | ||
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index a1134b7e2..cb025c3d6 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp | |||
| @@ -373,7 +373,7 @@ struct KernelCore::Impl { | |||
| 373 | static inline thread_local u8 host_thread_id = UINT8_MAX; | 373 | static inline thread_local u8 host_thread_id = UINT8_MAX; |
| 374 | 374 | ||
| 375 | /// Sets the host thread ID for the caller. | 375 | /// Sets the host thread ID for the caller. |
| 376 | u32 SetHostThreadId(std::size_t core_id) { | 376 | LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) { |
| 377 | // This should only be called during core init. | 377 | // This should only be called during core init. |
| 378 | ASSERT(host_thread_id == UINT8_MAX); | 378 | ASSERT(host_thread_id == UINT8_MAX); |
| 379 | 379 | ||
| @@ -384,13 +384,13 @@ struct KernelCore::Impl { | |||
| 384 | } | 384 | } |
| 385 | 385 | ||
| 386 | /// Gets the host thread ID for the caller | 386 | /// Gets the host thread ID for the caller |
| 387 | u32 GetHostThreadId() const { | 387 | LTO_NOINLINE u32 GetHostThreadId() const { |
| 388 | return host_thread_id; | 388 | return host_thread_id; |
| 389 | } | 389 | } |
| 390 | 390 | ||
| 391 | // Gets the dummy KThread for the caller, allocating a new one if this is the first time | 391 | // Gets the dummy KThread for the caller, allocating a new one if this is the first time |
| 392 | KThread* GetHostDummyThread(KThread* existing_thread) { | 392 | LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) { |
| 393 | const auto initialize{[](KThread* thread) { | 393 | const auto initialize{[](KThread* thread) LTO_NOINLINE { |
| 394 | ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); | 394 | ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); |
| 395 | return thread; | 395 | return thread; |
| 396 | }}; | 396 | }}; |
| @@ -424,11 +424,11 @@ struct KernelCore::Impl { | |||
| 424 | 424 | ||
| 425 | static inline thread_local bool is_phantom_mode_for_singlecore{false}; | 425 | static inline thread_local bool is_phantom_mode_for_singlecore{false}; |
| 426 | 426 | ||
| 427 | bool IsPhantomModeForSingleCore() const { | 427 | LTO_NOINLINE bool IsPhantomModeForSingleCore() const { |
| 428 | return is_phantom_mode_for_singlecore; | 428 | return is_phantom_mode_for_singlecore; |
| 429 | } | 429 | } |
| 430 | 430 | ||
| 431 | void SetIsPhantomModeForSingleCore(bool value) { | 431 | LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) { |
| 432 | ASSERT(!is_multicore); | 432 | ASSERT(!is_multicore); |
| 433 | is_phantom_mode_for_singlecore = value; | 433 | is_phantom_mode_for_singlecore = value; |
| 434 | } | 434 | } |
| @@ -439,14 +439,14 @@ struct KernelCore::Impl { | |||
| 439 | 439 | ||
| 440 | static inline thread_local KThread* current_thread{nullptr}; | 440 | static inline thread_local KThread* current_thread{nullptr}; |
| 441 | 441 | ||
| 442 | KThread* GetCurrentEmuThread() { | 442 | LTO_NOINLINE KThread* GetCurrentEmuThread() { |
| 443 | if (!current_thread) { | 443 | if (!current_thread) { |
| 444 | current_thread = GetHostDummyThread(nullptr); | 444 | current_thread = GetHostDummyThread(nullptr); |
| 445 | } | 445 | } |
| 446 | return current_thread; | 446 | return current_thread; |
| 447 | } | 447 | } |
| 448 | 448 | ||
| 449 | void SetCurrentEmuThread(KThread* thread) { | 449 | LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) { |
| 450 | current_thread = thread; | 450 | current_thread = thread; |
| 451 | } | 451 | } |
| 452 | 452 | ||
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index b971401e6..b7d14060c 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp | |||
| @@ -49,7 +49,7 @@ public: | |||
| 49 | : ServiceFramework{system_, "IManagerForSystemService"} { | 49 | : ServiceFramework{system_, "IManagerForSystemService"} { |
| 50 | // clang-format off | 50 | // clang-format off |
| 51 | static const FunctionInfo functions[] = { | 51 | static const FunctionInfo functions[] = { |
| 52 | {0, nullptr, "CheckAvailability"}, | 52 | {0, &IManagerForSystemService::CheckAvailability, "CheckAvailability"}, |
| 53 | {1, nullptr, "GetAccountId"}, | 53 | {1, nullptr, "GetAccountId"}, |
| 54 | {2, nullptr, "EnsureIdTokenCacheAsync"}, | 54 | {2, nullptr, "EnsureIdTokenCacheAsync"}, |
| 55 | {3, nullptr, "LoadIdTokenCache"}, | 55 | {3, nullptr, "LoadIdTokenCache"}, |
| @@ -78,6 +78,13 @@ public: | |||
| 78 | 78 | ||
| 79 | RegisterHandlers(functions); | 79 | RegisterHandlers(functions); |
| 80 | } | 80 | } |
| 81 | |||
| 82 | private: | ||
| 83 | void CheckAvailability(HLERequestContext& ctx) { | ||
| 84 | LOG_WARNING(Service_ACC, "(STUBBED) called"); | ||
| 85 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 86 | rb.Push(ResultSuccess); | ||
| 87 | } | ||
| 81 | }; | 88 | }; |
| 82 | 89 | ||
| 83 | // 3.0.0+ | 90 | // 3.0.0+ |
| @@ -837,6 +844,29 @@ void Module::Interface::InitializeApplicationInfoV2(HLERequestContext& ctx) { | |||
| 837 | rb.Push(ResultSuccess); | 844 | rb.Push(ResultSuccess); |
| 838 | } | 845 | } |
| 839 | 846 | ||
| 847 | void Module::Interface::BeginUserRegistration(HLERequestContext& ctx) { | ||
| 848 | const auto user_id = Common::UUID::MakeRandom(); | ||
| 849 | profile_manager->CreateNewUser(user_id, "yuzu"); | ||
| 850 | |||
| 851 | LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString()); | ||
| 852 | |||
| 853 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 854 | rb.Push(ResultSuccess); | ||
| 855 | rb.PushRaw(user_id); | ||
| 856 | } | ||
| 857 | |||
| 858 | void Module::Interface::CompleteUserRegistration(HLERequestContext& ctx) { | ||
| 859 | IPC::RequestParser rp{ctx}; | ||
| 860 | Common::UUID user_id = rp.PopRaw<Common::UUID>(); | ||
| 861 | |||
| 862 | LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString()); | ||
| 863 | |||
| 864 | profile_manager->WriteUserSaveFile(); | ||
| 865 | |||
| 866 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 867 | rb.Push(ResultSuccess); | ||
| 868 | } | ||
| 869 | |||
| 840 | void Module::Interface::GetProfileEditor(HLERequestContext& ctx) { | 870 | void Module::Interface::GetProfileEditor(HLERequestContext& ctx) { |
| 841 | IPC::RequestParser rp{ctx}; | 871 | IPC::RequestParser rp{ctx}; |
| 842 | Common::UUID user_id = rp.PopRaw<Common::UUID>(); | 872 | Common::UUID user_id = rp.PopRaw<Common::UUID>(); |
| @@ -880,6 +910,17 @@ void Module::Interface::StoreSaveDataThumbnailApplication(HLERequestContext& ctx | |||
| 880 | StoreSaveDataThumbnail(ctx, uuid, tid); | 910 | StoreSaveDataThumbnail(ctx, uuid, tid); |
| 881 | } | 911 | } |
| 882 | 912 | ||
| 913 | void Module::Interface::GetBaasAccountManagerForSystemService(HLERequestContext& ctx) { | ||
| 914 | IPC::RequestParser rp{ctx}; | ||
| 915 | const auto uuid = rp.PopRaw<Common::UUID>(); | ||
| 916 | |||
| 917 | LOG_INFO(Service_ACC, "called, uuid=0x{}", uuid.RawString()); | ||
| 918 | |||
| 919 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 920 | rb.Push(ResultSuccess); | ||
| 921 | rb.PushIpcInterface<IManagerForSystemService>(system, uuid); | ||
| 922 | } | ||
| 923 | |||
| 883 | void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) { | 924 | void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) { |
| 884 | IPC::RequestParser rp{ctx}; | 925 | IPC::RequestParser rp{ctx}; |
| 885 | const auto uuid = rp.PopRaw<Common::UUID>(); | 926 | const auto uuid = rp.PopRaw<Common::UUID>(); |
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 6b4735c2f..0395229b4 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h | |||
| @@ -33,10 +33,13 @@ public: | |||
| 33 | void TrySelectUserWithoutInteraction(HLERequestContext& ctx); | 33 | void TrySelectUserWithoutInteraction(HLERequestContext& ctx); |
| 34 | void IsUserAccountSwitchLocked(HLERequestContext& ctx); | 34 | void IsUserAccountSwitchLocked(HLERequestContext& ctx); |
| 35 | void InitializeApplicationInfoV2(HLERequestContext& ctx); | 35 | void InitializeApplicationInfoV2(HLERequestContext& ctx); |
| 36 | void BeginUserRegistration(HLERequestContext& ctx); | ||
| 37 | void CompleteUserRegistration(HLERequestContext& ctx); | ||
| 36 | void GetProfileEditor(HLERequestContext& ctx); | 38 | void GetProfileEditor(HLERequestContext& ctx); |
| 37 | void ListQualifiedUsers(HLERequestContext& ctx); | 39 | void ListQualifiedUsers(HLERequestContext& ctx); |
| 38 | void ListOpenContextStoredUsers(HLERequestContext& ctx); | 40 | void ListOpenContextStoredUsers(HLERequestContext& ctx); |
| 39 | void StoreSaveDataThumbnailApplication(HLERequestContext& ctx); | 41 | void StoreSaveDataThumbnailApplication(HLERequestContext& ctx); |
| 42 | void GetBaasAccountManagerForSystemService(HLERequestContext& ctx); | ||
| 40 | void StoreSaveDataThumbnailSystem(HLERequestContext& ctx); | 43 | void StoreSaveDataThumbnailSystem(HLERequestContext& ctx); |
| 41 | 44 | ||
| 42 | private: | 45 | private: |
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index d9882ecd3..770d13ec5 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp | |||
| @@ -23,7 +23,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> | |||
| 23 | {99, nullptr, "DebugActivateOpenContextRetention"}, | 23 | {99, nullptr, "DebugActivateOpenContextRetention"}, |
| 24 | {100, nullptr, "GetUserRegistrationNotifier"}, | 24 | {100, nullptr, "GetUserRegistrationNotifier"}, |
| 25 | {101, nullptr, "GetUserStateChangeNotifier"}, | 25 | {101, nullptr, "GetUserStateChangeNotifier"}, |
| 26 | {102, nullptr, "GetBaasAccountManagerForSystemService"}, | 26 | {102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"}, |
| 27 | {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, | 27 | {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, |
| 28 | {104, nullptr, "GetProfileUpdateNotifier"}, | 28 | {104, nullptr, "GetProfileUpdateNotifier"}, |
| 29 | {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, | 29 | {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, |
| @@ -40,8 +40,8 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> | |||
| 40 | {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, | 40 | {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, |
| 41 | {190, nullptr, "GetUserLastOpenedApplication"}, | 41 | {190, nullptr, "GetUserLastOpenedApplication"}, |
| 42 | {191, nullptr, "ActivateOpenContextHolder"}, | 42 | {191, nullptr, "ActivateOpenContextHolder"}, |
| 43 | {200, nullptr, "BeginUserRegistration"}, | 43 | {200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"}, |
| 44 | {201, nullptr, "CompleteUserRegistration"}, | 44 | {201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"}, |
| 45 | {202, nullptr, "CancelUserRegistration"}, | 45 | {202, nullptr, "CancelUserRegistration"}, |
| 46 | {203, nullptr, "DeleteUser"}, | 46 | {203, nullptr, "DeleteUser"}, |
| 47 | {204, nullptr, "SetUserPosition"}, | 47 | {204, nullptr, "SetUserPosition"}, |
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index 993a5a57a..900e32200 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h | |||
| @@ -96,9 +96,10 @@ public: | |||
| 96 | bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, | 96 | bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, |
| 97 | const UserData& data_new); | 97 | const UserData& data_new); |
| 98 | 98 | ||
| 99 | void WriteUserSaveFile(); | ||
| 100 | |||
| 99 | private: | 101 | private: |
| 100 | void ParseUserSaveFile(); | 102 | void ParseUserSaveFile(); |
| 101 | void WriteUserSaveFile(); | ||
| 102 | std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); | 103 | std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); |
| 103 | bool RemoveProfileAtIndex(std::size_t index); | 104 | bool RemoveProfileAtIndex(std::size_t index); |
| 104 | 105 | ||
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index ac376b55a..98765b81a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp | |||
| @@ -210,8 +210,8 @@ IDisplayController::IDisplayController(Core::System& system_) | |||
| 210 | {21, nullptr, "ClearAppletTransitionBuffer"}, | 210 | {21, nullptr, "ClearAppletTransitionBuffer"}, |
| 211 | {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"}, | 211 | {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"}, |
| 212 | {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, | 212 | {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, |
| 213 | {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, | 213 | {24, &IDisplayController::AcquireLastForegroundCaptureSharedBuffer, "AcquireLastForegroundCaptureSharedBuffer"}, |
| 214 | {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, | 214 | {25, &IDisplayController::ReleaseLastForegroundCaptureSharedBuffer, "ReleaseLastForegroundCaptureSharedBuffer"}, |
| 215 | {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"}, | 215 | {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"}, |
| 216 | {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"}, | 216 | {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"}, |
| 217 | {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, | 217 | {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, |
| @@ -239,6 +239,22 @@ void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) { | |||
| 239 | rb.Push(ResultSuccess); | 239 | rb.Push(ResultSuccess); |
| 240 | } | 240 | } |
| 241 | 241 | ||
| 242 | void IDisplayController::AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) { | ||
| 243 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 244 | |||
| 245 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 246 | rb.Push(ResultSuccess); | ||
| 247 | rb.Push(1U); | ||
| 248 | rb.Push(0); | ||
| 249 | } | ||
| 250 | |||
| 251 | void IDisplayController::ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) { | ||
| 252 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 253 | |||
| 254 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 255 | rb.Push(ResultSuccess); | ||
| 256 | } | ||
| 257 | |||
| 242 | void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) { | 258 | void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) { |
| 243 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 259 | LOG_WARNING(Service_AM, "(STUBBED) called"); |
| 244 | 260 | ||
| @@ -1557,7 +1573,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) | |||
| 1557 | {100, nullptr, "CreateGameMovieTrimmer"}, | 1573 | {100, nullptr, "CreateGameMovieTrimmer"}, |
| 1558 | {101, nullptr, "ReserveResourceForMovieOperation"}, | 1574 | {101, nullptr, "ReserveResourceForMovieOperation"}, |
| 1559 | {102, nullptr, "UnreserveResourceForMovieOperation"}, | 1575 | {102, nullptr, "UnreserveResourceForMovieOperation"}, |
| 1560 | {110, nullptr, "GetMainAppletAvailableUsers"}, | 1576 | {110, &ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers, "GetMainAppletAvailableUsers"}, |
| 1561 | {120, nullptr, "GetLaunchStorageInfoForDebug"}, | 1577 | {120, nullptr, "GetLaunchStorageInfoForDebug"}, |
| 1562 | {130, nullptr, "GetGpuErrorDetectedSystemEvent"}, | 1578 | {130, nullptr, "GetGpuErrorDetectedSystemEvent"}, |
| 1563 | {140, nullptr, "SetApplicationMemoryReservation"}, | 1579 | {140, nullptr, "SetApplicationMemoryReservation"}, |
| @@ -1652,6 +1668,25 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& | |||
| 1652 | rb.PushRaw(applet_info); | 1668 | rb.PushRaw(applet_info); |
| 1653 | } | 1669 | } |
| 1654 | 1670 | ||
| 1671 | void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) { | ||
| 1672 | const Service::Account::ProfileManager manager{}; | ||
| 1673 | bool is_empty{true}; | ||
| 1674 | s32 user_count{-1}; | ||
| 1675 | |||
| 1676 | LOG_INFO(Service_AM, "called"); | ||
| 1677 | |||
| 1678 | if (manager.GetUserCount() > 0) { | ||
| 1679 | is_empty = false; | ||
| 1680 | user_count = static_cast<s32>(manager.GetUserCount()); | ||
| 1681 | ctx.WriteBuffer(manager.GetAllUsers()); | ||
| 1682 | } | ||
| 1683 | |||
| 1684 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 1685 | rb.Push(ResultSuccess); | ||
| 1686 | rb.Push<u8>(is_empty); | ||
| 1687 | rb.Push(user_count); | ||
| 1688 | } | ||
| 1689 | |||
| 1655 | void ILibraryAppletSelfAccessor::PushInShowAlbum() { | 1690 | void ILibraryAppletSelfAccessor::PushInShowAlbum() { |
| 1656 | const Applets::CommonArguments arguments{ | 1691 | const Applets::CommonArguments arguments{ |
| 1657 | .arguments_version = Applets::CommonArgumentVersion::Version3, | 1692 | .arguments_version = Applets::CommonArgumentVersion::Version3, |
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 4a045cfd4..64b3f3fe2 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h | |||
| @@ -124,6 +124,8 @@ public: | |||
| 124 | private: | 124 | private: |
| 125 | void GetCallerAppletCaptureImageEx(HLERequestContext& ctx); | 125 | void GetCallerAppletCaptureImageEx(HLERequestContext& ctx); |
| 126 | void TakeScreenShotOfOwnLayer(HLERequestContext& ctx); | 126 | void TakeScreenShotOfOwnLayer(HLERequestContext& ctx); |
| 127 | void AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx); | ||
| 128 | void ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx); | ||
| 127 | void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); | 129 | void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); |
| 128 | void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); | 130 | void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); |
| 129 | }; | 131 | }; |
| @@ -345,6 +347,7 @@ private: | |||
| 345 | void GetLibraryAppletInfo(HLERequestContext& ctx); | 347 | void GetLibraryAppletInfo(HLERequestContext& ctx); |
| 346 | void ExitProcessAndReturn(HLERequestContext& ctx); | 348 | void ExitProcessAndReturn(HLERequestContext& ctx); |
| 347 | void GetCallerAppletIdentityInfo(HLERequestContext& ctx); | 349 | void GetCallerAppletIdentityInfo(HLERequestContext& ctx); |
| 350 | void GetMainAppletAvailableUsers(HLERequestContext& ctx); | ||
| 348 | 351 | ||
| 349 | void PushInShowAlbum(); | 352 | void PushInShowAlbum(); |
| 350 | void PushInShowCabinetData(); | 353 | void PushInShowCabinetData(); |
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp index 286f9fd10..31dd98140 100644 --- a/src/core/hle/service/caps/caps.cpp +++ b/src/core/hle/service/caps/caps.cpp | |||
| @@ -16,7 +16,7 @@ namespace Service::Capture { | |||
| 16 | 16 | ||
| 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/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp index 808b21069..77db60e92 100644 --- a/src/input_common/drivers/udp_client.cpp +++ b/src/input_common/drivers/udp_client.cpp | |||
| @@ -338,6 +338,7 @@ void UDPClient::StartCommunication(std::size_t client, const std::string& host, | |||
| 338 | for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { | 338 | for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { |
| 339 | const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); | 339 | const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); |
| 340 | PreSetController(identifier); | 340 | PreSetController(identifier); |
| 341 | PreSetMotion(identifier, 0); | ||
| 341 | } | 342 | } |
| 342 | } | 343 | } |
| 343 | 344 | ||
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 9e90c587c..9b2698fad 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h | |||
| @@ -544,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() { | |||
| 544 | it++; | 544 | it++; |
| 545 | } | 545 | } |
| 546 | 546 | ||
| 547 | boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads; | 547 | boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads; |
| 548 | u64 total_size_bytes = 0; | 548 | u64 total_size_bytes = 0; |
| 549 | u64 largest_copy = 0; | 549 | u64 largest_copy = 0; |
| 550 | for (const IntervalSet& intervals : committed_ranges) { | 550 | for (const IntervalSet& intervals : committed_ranges) { |
| @@ -914,6 +914,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) { | |||
| 914 | 914 | ||
| 915 | const u32 offset = buffer.Offset(binding.cpu_addr); | 915 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 916 | const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; | 916 | const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; |
| 917 | |||
| 918 | if (is_written) { | ||
| 919 | MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); | ||
| 920 | } | ||
| 921 | |||
| 917 | if constexpr (NEEDS_BIND_STORAGE_INDEX) { | 922 | if constexpr (NEEDS_BIND_STORAGE_INDEX) { |
| 918 | runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); | 923 | runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); |
| 919 | ++binding_index; | 924 | ++binding_index; |
| @@ -931,6 +936,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) { | |||
| 931 | const u32 size = binding.size; | 936 | const u32 size = binding.size; |
| 932 | SynchronizeBuffer(buffer, binding.cpu_addr, size); | 937 | SynchronizeBuffer(buffer, binding.cpu_addr, size); |
| 933 | 938 | ||
| 939 | const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0; | ||
| 940 | if (is_written) { | ||
| 941 | MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); | ||
| 942 | } | ||
| 943 | |||
| 934 | const u32 offset = buffer.Offset(binding.cpu_addr); | 944 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 935 | const PixelFormat format = binding.format; | 945 | const PixelFormat format = binding.format; |
| 936 | if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { | 946 | if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { |
| @@ -962,6 +972,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { | |||
| 962 | const u32 size = binding.size; | 972 | const u32 size = binding.size; |
| 963 | SynchronizeBuffer(buffer, binding.cpu_addr, size); | 973 | SynchronizeBuffer(buffer, binding.cpu_addr, size); |
| 964 | 974 | ||
| 975 | MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); | ||
| 976 | |||
| 965 | const u32 offset = buffer.Offset(binding.cpu_addr); | 977 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 966 | host_bindings.buffers.push_back(&buffer); | 978 | host_bindings.buffers.push_back(&buffer); |
| 967 | host_bindings.offsets.push_back(offset); | 979 | host_bindings.offsets.push_back(offset); |
| @@ -1011,6 +1023,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() { | |||
| 1011 | const u32 offset = buffer.Offset(binding.cpu_addr); | 1023 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 1012 | const bool is_written = | 1024 | const bool is_written = |
| 1013 | ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; | 1025 | ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; |
| 1026 | |||
| 1027 | if (is_written) { | ||
| 1028 | MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); | ||
| 1029 | } | ||
| 1030 | |||
| 1014 | if constexpr (NEEDS_BIND_STORAGE_INDEX) { | 1031 | if constexpr (NEEDS_BIND_STORAGE_INDEX) { |
| 1015 | runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); | 1032 | runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); |
| 1016 | ++binding_index; | 1033 | ++binding_index; |
| @@ -1028,6 +1045,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() { | |||
| 1028 | const u32 size = binding.size; | 1045 | const u32 size = binding.size; |
| 1029 | SynchronizeBuffer(buffer, binding.cpu_addr, size); | 1046 | SynchronizeBuffer(buffer, binding.cpu_addr, size); |
| 1030 | 1047 | ||
| 1048 | const bool is_written = | ||
| 1049 | ((channel_state->written_compute_texture_buffers >> index) & 1) != 0; | ||
| 1050 | if (is_written) { | ||
| 1051 | MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); | ||
| 1052 | } | ||
| 1053 | |||
| 1031 | const u32 offset = buffer.Offset(binding.cpu_addr); | 1054 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 1032 | const PixelFormat format = binding.format; | 1055 | const PixelFormat format = binding.format; |
| 1033 | if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { | 1056 | if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { |
| @@ -1201,16 +1224,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) { | |||
| 1201 | 1224 | ||
| 1202 | template <class P> | 1225 | template <class P> |
| 1203 | void BufferCache<P>::UpdateStorageBuffers(size_t stage) { | 1226 | void BufferCache<P>::UpdateStorageBuffers(size_t stage) { |
| 1204 | const u32 written_mask = channel_state->written_storage_buffers[stage]; | ||
| 1205 | ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { | 1227 | ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { |
| 1206 | // Resolve buffer | 1228 | // Resolve buffer |
| 1207 | Binding& binding = channel_state->storage_buffers[stage][index]; | 1229 | Binding& binding = channel_state->storage_buffers[stage][index]; |
| 1208 | const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); | 1230 | const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); |
| 1209 | binding.buffer_id = buffer_id; | 1231 | binding.buffer_id = buffer_id; |
| 1210 | // Mark buffer as written if needed | ||
| 1211 | if (((written_mask >> index) & 1) != 0) { | ||
| 1212 | MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size); | ||
| 1213 | } | ||
| 1214 | }); | 1232 | }); |
| 1215 | } | 1233 | } |
| 1216 | 1234 | ||
| @@ -1219,10 +1237,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) { | |||
| 1219 | ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { | 1237 | ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { |
| 1220 | Binding& binding = channel_state->texture_buffers[stage][index]; | 1238 | Binding& binding = channel_state->texture_buffers[stage][index]; |
| 1221 | binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); | 1239 | binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); |
| 1222 | // Mark buffer as written if needed | ||
| 1223 | if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) { | ||
| 1224 | MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); | ||
| 1225 | } | ||
| 1226 | }); | 1240 | }); |
| 1227 | } | 1241 | } |
| 1228 | 1242 | ||
| @@ -1252,7 +1266,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) { | |||
| 1252 | .size = size, | 1266 | .size = size, |
| 1253 | .buffer_id = buffer_id, | 1267 | .buffer_id = buffer_id, |
| 1254 | }; | 1268 | }; |
| 1255 | MarkWrittenBuffer(buffer_id, *cpu_addr, size); | ||
| 1256 | } | 1269 | } |
| 1257 | 1270 | ||
| 1258 | template <class P> | 1271 | template <class P> |
| @@ -1279,10 +1292,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() { | |||
| 1279 | // Resolve buffer | 1292 | // Resolve buffer |
| 1280 | Binding& binding = channel_state->compute_storage_buffers[index]; | 1293 | Binding& binding = channel_state->compute_storage_buffers[index]; |
| 1281 | binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); | 1294 | binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); |
| 1282 | // Mark as written if needed | ||
| 1283 | if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) { | ||
| 1284 | MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); | ||
| 1285 | } | ||
| 1286 | }); | 1295 | }); |
| 1287 | } | 1296 | } |
| 1288 | 1297 | ||
| @@ -1291,18 +1300,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() { | |||
| 1291 | ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { | 1300 | ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { |
| 1292 | Binding& binding = channel_state->compute_texture_buffers[index]; | 1301 | Binding& binding = channel_state->compute_texture_buffers[index]; |
| 1293 | binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); | 1302 | binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); |
| 1294 | // Mark as written if needed | ||
| 1295 | if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) { | ||
| 1296 | MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); | ||
| 1297 | } | ||
| 1298 | }); | 1303 | }); |
| 1299 | } | 1304 | } |
| 1300 | 1305 | ||
| 1301 | template <class P> | 1306 | template <class P> |
| 1302 | void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { | 1307 | void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { |
| 1303 | if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) { | ||
| 1304 | SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size); | ||
| 1305 | } | ||
| 1306 | memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); | 1308 | memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); |
| 1307 | 1309 | ||
| 1308 | const IntervalType base_interval{cpu_addr, cpu_addr + size}; | 1310 | const IntervalType base_interval{cpu_addr, cpu_addr + size}; |
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index c4f6e8d12..eed267361 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h | |||
| @@ -62,7 +62,11 @@ using BufferId = SlotId; | |||
| 62 | using VideoCore::Surface::PixelFormat; | 62 | using VideoCore::Surface::PixelFormat; |
| 63 | using namespace Common::Literals; | 63 | using namespace Common::Literals; |
| 64 | 64 | ||
| 65 | #ifdef __APPLE__ | ||
| 66 | constexpr u32 NUM_VERTEX_BUFFERS = 16; | ||
| 67 | #else | ||
| 65 | constexpr u32 NUM_VERTEX_BUFFERS = 32; | 68 | constexpr u32 NUM_VERTEX_BUFFERS = 32; |
| 69 | #endif | ||
| 66 | constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4; | 70 | constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4; |
| 67 | constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18; | 71 | constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18; |
| 68 | constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; | 72 | constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; |
diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp index f34090791..d77ff455b 100644 --- a/src/video_core/engines/draw_manager.cpp +++ b/src/video_core/engines/draw_manager.cpp | |||
| @@ -48,8 +48,14 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) { | |||
| 48 | SetInlineIndexBuffer(regs.inline_index_4x8.index3); | 48 | SetInlineIndexBuffer(regs.inline_index_4x8.index3); |
| 49 | break; | 49 | break; |
| 50 | case MAXWELL3D_REG_INDEX(vertex_array_instance_first): | 50 | case MAXWELL3D_REG_INDEX(vertex_array_instance_first): |
| 51 | DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(), | ||
| 52 | regs.vertex_array_instance_first.start.Value(), | ||
| 53 | regs.vertex_array_instance_first.count.Value(), false); | ||
| 54 | break; | ||
| 51 | case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): { | 55 | case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): { |
| 52 | LOG_WARNING(HW_GPU, "(STUBBED) called"); | 56 | DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(), |
| 57 | regs.vertex_array_instance_subsequent.start.Value(), | ||
| 58 | regs.vertex_array_instance_subsequent.count.Value(), true); | ||
| 53 | break; | 59 | break; |
| 54 | } | 60 | } |
| 55 | case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { | 61 | case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { |
| @@ -84,6 +90,22 @@ void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 ve | |||
| 84 | ProcessDraw(false, num_instances); | 90 | ProcessDraw(false, num_instances); |
| 85 | } | 91 | } |
| 86 | 92 | ||
| 93 | void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, | ||
| 94 | bool subsequent) { | ||
| 95 | draw_state.topology = topology; | ||
| 96 | draw_state.vertex_buffer.first = vertex_first; | ||
| 97 | draw_state.vertex_buffer.count = vertex_count; | ||
| 98 | |||
| 99 | if (!subsequent) { | ||
| 100 | draw_state.instance_count = 1; | ||
| 101 | } | ||
| 102 | |||
| 103 | draw_state.base_instance = draw_state.instance_count - 1; | ||
| 104 | draw_state.draw_mode = DrawMode::Instance; | ||
| 105 | draw_state.instance_count++; | ||
| 106 | ProcessDraw(false, 1); | ||
| 107 | } | ||
| 108 | |||
| 87 | void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, | 109 | void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, |
| 88 | u32 base_index, u32 base_instance, u32 num_instances) { | 110 | u32 base_index, u32 base_instance, u32 num_instances) { |
| 89 | const auto& regs{maxwell3d->regs}; | 111 | const auto& regs{maxwell3d->regs}; |
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h index 18d959143..cfc8127fc 100644 --- a/src/video_core/engines/draw_manager.h +++ b/src/video_core/engines/draw_manager.h | |||
| @@ -66,6 +66,8 @@ public: | |||
| 66 | 66 | ||
| 67 | void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, | 67 | void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, |
| 68 | u32 base_instance, u32 num_instances); | 68 | u32 base_instance, u32 num_instances); |
| 69 | void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, | ||
| 70 | bool subsequent); | ||
| 69 | 71 | ||
| 70 | void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, | 72 | void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, |
| 71 | u32 base_instance, u32 num_instances); | 73 | u32 base_instance, u32 num_instances); |
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp index 8d7da50fc..dbcf508e5 100644 --- a/src/video_core/host1x/codecs/codec.cpp +++ b/src/video_core/host1x/codecs/codec.cpp | |||
| @@ -137,16 +137,6 @@ bool Codec::CreateGpuAvDevice() { | |||
| 137 | break; | 137 | break; |
| 138 | } | 138 | } |
| 139 | if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) { | 139 | if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) { |
| 140 | #if defined(__unix__) | ||
| 141 | // Some linux decoding backends are reported to crash with this config method | ||
| 142 | // TODO(ameerj): Properly support this method | ||
| 143 | if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0) { | ||
| 144 | // skip zero-copy decoders, we don't currently support them | ||
| 145 | LOG_DEBUG(Service_NVDRV, "Skipping decoder {} with unsupported capability {}.", | ||
| 146 | av_hwdevice_get_type_name(type), config->methods); | ||
| 147 | continue; | ||
| 148 | } | ||
| 149 | #endif | ||
| 150 | LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); | 140 | LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); |
| 151 | av_codec_ctx->pix_fmt = config->pix_fmt; | 141 | av_codec_ctx->pix_fmt = config->pix_fmt; |
| 152 | return true; | 142 | return true; |
diff --git a/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag b/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag index d33131d7c..b81a54056 100644 --- a/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag +++ b/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag | |||
| @@ -3,16 +3,16 @@ | |||
| 3 | 3 | ||
| 4 | #version 450 | 4 | #version 450 |
| 5 | 5 | ||
| 6 | precision mediump int; | ||
| 7 | precision highp float; | ||
| 8 | |||
| 6 | layout(binding = 0) uniform sampler2D depth_tex; | 9 | layout(binding = 0) uniform sampler2D depth_tex; |
| 7 | layout(binding = 1) uniform isampler2D stencil_tex; | 10 | layout(binding = 1) uniform usampler2D stencil_tex; |
| 8 | 11 | ||
| 9 | layout(location = 0) out vec4 color; | 12 | layout(location = 0) out vec4 color; |
| 10 | 13 | ||
| 11 | void main() { | 14 | void main() { |
| 12 | ivec2 coord = ivec2(gl_FragCoord.xy); | 15 | ivec2 coord = ivec2(gl_FragCoord.xy); |
| 13 | uint depth = uint(textureLod(depth_tex, coord, 0).r * (exp2(24.0) - 1.0f)); | ||
| 14 | uint stencil = uint(textureLod(stencil_tex, coord, 0).r); | ||
| 15 | |||
| 16 | highp uint depth_val = | 16 | highp uint depth_val = |
| 17 | uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0)); | 17 | uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0)); |
| 18 | lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r; | 18 | lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r; |
diff --git a/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag b/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag index 31db7d426..6a457981d 100644 --- a/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag +++ b/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag | |||
| @@ -3,16 +3,16 @@ | |||
| 3 | 3 | ||
| 4 | #version 450 | 4 | #version 450 |
| 5 | 5 | ||
| 6 | precision mediump int; | ||
| 7 | precision highp float; | ||
| 8 | |||
| 6 | layout(binding = 0) uniform sampler2D depth_tex; | 9 | layout(binding = 0) uniform sampler2D depth_tex; |
| 7 | layout(binding = 1) uniform isampler2D stencil_tex; | 10 | layout(binding = 1) uniform usampler2D stencil_tex; |
| 8 | 11 | ||
| 9 | layout(location = 0) out vec4 color; | 12 | layout(location = 0) out vec4 color; |
| 10 | 13 | ||
| 11 | void main() { | 14 | void main() { |
| 12 | ivec2 coord = ivec2(gl_FragCoord.xy); | 15 | ivec2 coord = ivec2(gl_FragCoord.xy); |
| 13 | uint depth = uint(textureLod(depth_tex, coord, 0).r * (exp2(24.0) - 1.0f)); | ||
| 14 | uint stencil = uint(textureLod(stencil_tex, coord, 0).r); | ||
| 15 | |||
| 16 | highp uint depth_val = | 16 | highp uint depth_val = |
| 17 | uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0)); | 17 | uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0)); |
| 18 | lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r; | 18 | lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r; |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 9cafd2983..512eef575 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp | |||
| @@ -1048,6 +1048,10 @@ void Image::Scale(bool up_scale) { | |||
| 1048 | } | 1048 | } |
| 1049 | 1049 | ||
| 1050 | bool Image::ScaleUp(bool ignore) { | 1050 | bool Image::ScaleUp(bool ignore) { |
| 1051 | const auto& resolution = runtime->resolution; | ||
| 1052 | if (!resolution.active) { | ||
| 1053 | return false; | ||
| 1054 | } | ||
| 1051 | if (True(flags & ImageFlagBits::Rescaled)) { | 1055 | if (True(flags & ImageFlagBits::Rescaled)) { |
| 1052 | return false; | 1056 | return false; |
| 1053 | } | 1057 | } |
| @@ -1060,9 +1064,6 @@ bool Image::ScaleUp(bool ignore) { | |||
| 1060 | return false; | 1064 | return false; |
| 1061 | } | 1065 | } |
| 1062 | flags |= ImageFlagBits::Rescaled; | 1066 | flags |= ImageFlagBits::Rescaled; |
| 1063 | if (!runtime->resolution.active) { | ||
| 1064 | return false; | ||
| 1065 | } | ||
| 1066 | has_scaled = true; | 1067 | has_scaled = true; |
| 1067 | if (ignore) { | 1068 | if (ignore) { |
| 1068 | current_texture = upscaled_backup.handle; | 1069 | current_texture = upscaled_backup.handle; |
| @@ -1073,13 +1074,14 @@ bool Image::ScaleUp(bool ignore) { | |||
| 1073 | } | 1074 | } |
| 1074 | 1075 | ||
| 1075 | bool Image::ScaleDown(bool ignore) { | 1076 | bool Image::ScaleDown(bool ignore) { |
| 1076 | if (False(flags & ImageFlagBits::Rescaled)) { | 1077 | const auto& resolution = runtime->resolution; |
| 1078 | if (!resolution.active) { | ||
| 1077 | return false; | 1079 | return false; |
| 1078 | } | 1080 | } |
| 1079 | flags &= ~ImageFlagBits::Rescaled; | 1081 | if (False(flags & ImageFlagBits::Rescaled)) { |
| 1080 | if (!runtime->resolution.active) { | ||
| 1081 | return false; | 1082 | return false; |
| 1082 | } | 1083 | } |
| 1084 | flags &= ~ImageFlagBits::Rescaled; | ||
| 1083 | if (ignore) { | 1085 | if (ignore) { |
| 1084 | current_texture = texture.handle; | 1086 | current_texture = texture.handle; |
| 1085 | return true; | 1087 | return true; |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 3676eaaa9..e71b87e99 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h | |||
| @@ -118,6 +118,8 @@ public: | |||
| 118 | 118 | ||
| 119 | void InsertUploadMemoryBarrier(); | 119 | void InsertUploadMemoryBarrier(); |
| 120 | 120 | ||
| 121 | void TransitionImageLayout(Image& image) {} | ||
| 122 | |||
| 121 | FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; | 123 | FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; |
| 122 | 124 | ||
| 123 | bool HasNativeBgr() const noexcept { | 125 | bool HasNativeBgr() const noexcept { |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 83f2b6045..61d03daae 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -975,6 +975,19 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs | |||
| 975 | if (!state_tracker.TouchScissors()) { | 975 | if (!state_tracker.TouchScissors()) { |
| 976 | return; | 976 | return; |
| 977 | } | 977 | } |
| 978 | if (!regs.viewport_scale_offset_enabled) { | ||
| 979 | const auto x = static_cast<float>(regs.surface_clip.x); | ||
| 980 | const auto y = static_cast<float>(regs.surface_clip.y); | ||
| 981 | const auto width = static_cast<float>(regs.surface_clip.width); | ||
| 982 | const auto height = static_cast<float>(regs.surface_clip.height); | ||
| 983 | VkRect2D scissor; | ||
| 984 | scissor.offset.x = static_cast<u32>(x); | ||
| 985 | scissor.offset.y = static_cast<u32>(y); | ||
| 986 | scissor.extent.width = static_cast<u32>(width != 0.0f ? width : 1.0f); | ||
| 987 | scissor.extent.height = static_cast<u32>(height != 0.0f ? height : 1.0f); | ||
| 988 | scheduler.Record([scissor](vk::CommandBuffer cmdbuf) { cmdbuf.SetScissor(0, scissor); }); | ||
| 989 | return; | ||
| 990 | } | ||
| 978 | u32 up_scale = 1; | 991 | u32 up_scale = 1; |
| 979 | u32 down_shift = 0; | 992 | u32 down_shift = 0; |
| 980 | if (texture_cache.IsRescaling()) { | 993 | if (texture_cache.IsRescaling()) { |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 00ab47268..93773a69f 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -1530,15 +1530,15 @@ bool Image::IsRescaled() const noexcept { | |||
| 1530 | } | 1530 | } |
| 1531 | 1531 | ||
| 1532 | bool Image::ScaleUp(bool ignore) { | 1532 | bool Image::ScaleUp(bool ignore) { |
| 1533 | const auto& resolution = runtime->resolution; | ||
| 1534 | if (!resolution.active) { | ||
| 1535 | return false; | ||
| 1536 | } | ||
| 1533 | if (True(flags & ImageFlagBits::Rescaled)) { | 1537 | if (True(flags & ImageFlagBits::Rescaled)) { |
| 1534 | return false; | 1538 | return false; |
| 1535 | } | 1539 | } |
| 1536 | ASSERT(info.type != ImageType::Linear); | 1540 | ASSERT(info.type != ImageType::Linear); |
| 1537 | flags |= ImageFlagBits::Rescaled; | 1541 | flags |= ImageFlagBits::Rescaled; |
| 1538 | const auto& resolution = runtime->resolution; | ||
| 1539 | if (!resolution.active) { | ||
| 1540 | return false; | ||
| 1541 | } | ||
| 1542 | has_scaled = true; | 1542 | has_scaled = true; |
| 1543 | if (!scaled_image) { | 1543 | if (!scaled_image) { |
| 1544 | const bool is_2d = info.type == ImageType::e2D; | 1544 | const bool is_2d = info.type == ImageType::e2D; |
| @@ -1567,15 +1567,15 @@ bool Image::ScaleUp(bool ignore) { | |||
| 1567 | } | 1567 | } |
| 1568 | 1568 | ||
| 1569 | bool Image::ScaleDown(bool ignore) { | 1569 | bool Image::ScaleDown(bool ignore) { |
| 1570 | const auto& resolution = runtime->resolution; | ||
| 1571 | if (!resolution.active) { | ||
| 1572 | return false; | ||
| 1573 | } | ||
| 1570 | if (False(flags & ImageFlagBits::Rescaled)) { | 1574 | if (False(flags & ImageFlagBits::Rescaled)) { |
| 1571 | return false; | 1575 | return false; |
| 1572 | } | 1576 | } |
| 1573 | ASSERT(info.type != ImageType::Linear); | 1577 | ASSERT(info.type != ImageType::Linear); |
| 1574 | flags &= ~ImageFlagBits::Rescaled; | 1578 | flags &= ~ImageFlagBits::Rescaled; |
| 1575 | const auto& resolution = runtime->resolution; | ||
| 1576 | if (!resolution.active) { | ||
| 1577 | return false; | ||
| 1578 | } | ||
| 1579 | current_image = *original_image; | 1579 | current_image = *original_image; |
| 1580 | if (ignore) { | 1580 | if (ignore) { |
| 1581 | return true; | 1581 | return true; |
| @@ -2013,4 +2013,32 @@ void TextureCacheRuntime::AccelerateImageUpload( | |||
| 2013 | ASSERT(false); | 2013 | ASSERT(false); |
| 2014 | } | 2014 | } |
| 2015 | 2015 | ||
| 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/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index 8151cabf0..15596c925 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp | |||
| @@ -167,6 +167,13 @@ template <u32 GOB_EXTENT> | |||
| 167 | } | 167 | } |
| 168 | 168 | ||
| 169 | [[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { | 169 | [[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { |
| 170 | if (level == 0 && info.num_levels == 1) { | ||
| 171 | return Extent3D{ | ||
| 172 | .width = info.block.width, | ||
| 173 | .height = info.block.height, | ||
| 174 | .depth = info.block.depth, | ||
| 175 | }; | ||
| 176 | } | ||
| 170 | const Extent3D blocks = NumLevelBlocks(info, level); | 177 | const Extent3D blocks = NumLevelBlocks(info, level); |
| 171 | return Extent3D{ | 178 | return Extent3D{ |
| 172 | .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width), | 179 | .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width), |
| @@ -1293,9 +1300,9 @@ u32 MapSizeBytes(const ImageBase& image) { | |||
| 1293 | 1300 | ||
| 1294 | static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) == | 1301 | static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) == |
| 1295 | 0x7f8000); | 1302 | 0x7f8000); |
| 1296 | static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x4000); | 1303 | static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x40000); |
| 1297 | 1304 | ||
| 1298 | static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x4000); | 1305 | static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x40000); |
| 1299 | 1306 | ||
| 1300 | static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == | 1307 | static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == |
| 1301 | 0x2afc00); | 1308 | 0x2afc00); |
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 82767fdf0..8dd1667f3 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp | |||
| @@ -66,9 +66,10 @@ struct Range { | |||
| 66 | switch (usage) { | 66 | switch (usage) { |
| 67 | case MemoryUsage::Upload: | 67 | case MemoryUsage::Upload: |
| 68 | case MemoryUsage::Stream: | 68 | case MemoryUsage::Stream: |
| 69 | return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; | 69 | return VMA_ALLOCATION_CREATE_MAPPED_BIT | |
| 70 | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; | ||
| 70 | case MemoryUsage::Download: | 71 | case MemoryUsage::Download: |
| 71 | return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; | 72 | return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; |
| 72 | case MemoryUsage::DeviceLocal: | 73 | case MemoryUsage::DeviceLocal: |
| 73 | return {}; | 74 | return {}; |
| 74 | } | 75 | } |
| @@ -252,8 +253,7 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const { | |||
| 252 | 253 | ||
| 253 | vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { | 254 | vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { |
| 254 | const VmaAllocationCreateInfo alloc_ci = { | 255 | const VmaAllocationCreateInfo alloc_ci = { |
| 255 | .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT | | 256 | .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), |
| 256 | MemoryUsageVmaFlags(usage), | ||
| 257 | .usage = MemoryUsageVma(usage), | 257 | .usage = MemoryUsageVma(usage), |
| 258 | .requiredFlags = 0, | 258 | .requiredFlags = 0, |
| 259 | .preferredFlags = MemoryUsagePreferedVmaFlags(usage), | 259 | .preferredFlags = MemoryUsagePreferedVmaFlags(usage), |
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 9ebece907..34208ed74 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -384,7 +384,7 @@ if (USE_DISCORD_PRESENCE) | |||
| 384 | discord_impl.cpp | 384 | discord_impl.cpp |
| 385 | discord_impl.h | 385 | discord_impl.h |
| 386 | ) | 386 | ) |
| 387 | target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib) | 387 | target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network) |
| 388 | target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) | 388 | target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) |
| 389 | endif() | 389 | endif() |
| 390 | 390 | ||
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index d15559518..ca0e14fad 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp | |||
| @@ -23,6 +23,7 @@ | |||
| 23 | #include "yuzu/configuration/configure_vibration.h" | 23 | #include "yuzu/configuration/configure_vibration.h" |
| 24 | #include "yuzu/configuration/input_profiles.h" | 24 | #include "yuzu/configuration/input_profiles.h" |
| 25 | #include "yuzu/main.h" | 25 | #include "yuzu/main.h" |
| 26 | #include "yuzu/util/controller_navigation.h" | ||
| 26 | 27 | ||
| 27 | namespace { | 28 | namespace { |
| 28 | 29 | ||
| @@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( | |||
| 132 | ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, | 133 | ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, |
| 133 | }; | 134 | }; |
| 134 | 135 | ||
| 136 | ui->labelError->setVisible(false); | ||
| 137 | |||
| 135 | // Setup/load everything prior to setting up connections. | 138 | // Setup/load everything prior to setting up connections. |
| 136 | // This avoids unintentionally changing the states of elements while loading them in. | 139 | // This avoids unintentionally changing the states of elements while loading them in. |
| 137 | SetSupportedControllers(); | 140 | SetSupportedControllers(); |
| @@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( | |||
| 143 | 146 | ||
| 144 | LoadConfiguration(); | 147 | LoadConfiguration(); |
| 145 | 148 | ||
| 149 | controller_navigation = new ControllerNavigation(system.HIDCore(), this); | ||
| 150 | |||
| 146 | for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { | 151 | for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { |
| 147 | SetExplainText(i); | 152 | SetExplainText(i); |
| 148 | UpdateControllerIcon(i); | 153 | UpdateControllerIcon(i); |
| @@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( | |||
| 151 | 156 | ||
| 152 | connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { | 157 | connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { |
| 153 | if (checked) { | 158 | if (checked) { |
| 159 | // Hide eventual error message about number of controllers | ||
| 160 | ui->labelError->setVisible(false); | ||
| 154 | for (std::size_t index = 0; index <= i; ++index) { | 161 | for (std::size_t index = 0; index <= i; ++index) { |
| 155 | connected_controller_checkboxes[index]->setChecked(checked); | 162 | connected_controller_checkboxes[index]->setChecked(checked); |
| 156 | } | 163 | } |
| @@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( | |||
| 199 | connect(ui->buttonBox, &QDialogButtonBox::accepted, this, | 206 | connect(ui->buttonBox, &QDialogButtonBox::accepted, this, |
| 200 | &QtControllerSelectorDialog::ApplyConfiguration); | 207 | &QtControllerSelectorDialog::ApplyConfiguration); |
| 201 | 208 | ||
| 209 | connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, | ||
| 210 | [this](Qt::Key key) { | ||
| 211 | QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); | ||
| 212 | QCoreApplication::postEvent(this, event); | ||
| 213 | }); | ||
| 214 | |||
| 202 | // Enhancement: Check if the parameters have already been met before disconnecting controllers. | 215 | // Enhancement: Check if the parameters have already been met before disconnecting controllers. |
| 203 | // If all the parameters are met AND only allows a single player, | 216 | // If all the parameters are met AND only allows a single player, |
| 204 | // stop the constructor here as we do not need to continue. | 217 | // stop the constructor here as we do not need to continue. |
| @@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( | |||
| 217 | } | 230 | } |
| 218 | 231 | ||
| 219 | QtControllerSelectorDialog::~QtControllerSelectorDialog() { | 232 | QtControllerSelectorDialog::~QtControllerSelectorDialog() { |
| 233 | controller_navigation->UnloadController(); | ||
| 220 | system.HIDCore().DisableAllControllerConfiguration(); | 234 | system.HIDCore().DisableAllControllerConfiguration(); |
| 221 | } | 235 | } |
| 222 | 236 | ||
| @@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() { | |||
| 291 | dialog.exec(); | 305 | dialog.exec(); |
| 292 | } | 306 | } |
| 293 | 307 | ||
| 308 | void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) { | ||
| 309 | const auto num_connected_players = static_cast<int>( | ||
| 310 | std::count_if(player_groupboxes.begin(), player_groupboxes.end(), | ||
| 311 | [](const QGroupBox* player) { return player->isChecked(); })); | ||
| 312 | |||
| 313 | const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; | ||
| 314 | const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; | ||
| 315 | |||
| 316 | if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) { | ||
| 317 | // Display error message when trying to validate using "Enter" and "OK" button is disabled | ||
| 318 | ui->labelError->setVisible(true); | ||
| 319 | return; | ||
| 320 | } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) { | ||
| 321 | // Remove a player if possible | ||
| 322 | connected_controller_checkboxes[num_connected_players - 1]->setChecked(false); | ||
| 323 | return; | ||
| 324 | } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) { | ||
| 325 | // Add a player, if possible | ||
| 326 | ui->labelError->setVisible(false); | ||
| 327 | connected_controller_checkboxes[num_connected_players]->setChecked(true); | ||
| 328 | return; | ||
| 329 | } | ||
| 330 | QDialog::keyPressEvent(evt); | ||
| 331 | } | ||
| 332 | |||
| 294 | bool QtControllerSelectorDialog::CheckIfParametersMet() { | 333 | bool QtControllerSelectorDialog::CheckIfParametersMet() { |
| 295 | // Here, we check and validate the current configuration against all applicable parameters. | 334 | // Here, we check and validate the current configuration against all applicable parameters. |
| 296 | const auto num_connected_players = static_cast<int>( | 335 | const auto num_connected_players = static_cast<int>( |
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index 2fdc35857..7f0673d06 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h | |||
| @@ -34,6 +34,8 @@ class HIDCore; | |||
| 34 | enum class NpadStyleIndex : u8; | 34 | enum class NpadStyleIndex : u8; |
| 35 | } // namespace Core::HID | 35 | } // namespace Core::HID |
| 36 | 36 | ||
| 37 | class ControllerNavigation; | ||
| 38 | |||
| 37 | class QtControllerSelectorDialog final : public QDialog { | 39 | class QtControllerSelectorDialog final : public QDialog { |
| 38 | Q_OBJECT | 40 | Q_OBJECT |
| 39 | 41 | ||
| @@ -46,6 +48,8 @@ public: | |||
| 46 | 48 | ||
| 47 | int exec() override; | 49 | int exec() override; |
| 48 | 50 | ||
| 51 | void keyPressEvent(QKeyEvent* evt) override; | ||
| 52 | |||
| 49 | private: | 53 | private: |
| 50 | // Applies the current configuration. | 54 | // Applies the current configuration. |
| 51 | void ApplyConfiguration(); | 55 | void ApplyConfiguration(); |
| @@ -110,6 +114,8 @@ private: | |||
| 110 | 114 | ||
| 111 | Core::System& system; | 115 | Core::System& system; |
| 112 | 116 | ||
| 117 | ControllerNavigation* controller_navigation = nullptr; | ||
| 118 | |||
| 113 | // This is true if and only if all parameters are met. Otherwise, this is false. | 119 | // This is true if and only if all parameters are met. Otherwise, this is false. |
| 114 | // This determines whether the "OK" button can be clicked to exit the applet. | 120 | // This determines whether the "OK" button can be clicked to exit the applet. |
| 115 | bool parameters_met{false}; | 121 | bool parameters_met{false}; |
diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui index 729e921ee..6f7cb3c13 100644 --- a/src/yuzu/applets/qt_controller.ui +++ b/src/yuzu/applets/qt_controller.ui | |||
| @@ -2624,13 +2624,53 @@ | |||
| 2624 | </spacer> | 2624 | </spacer> |
| 2625 | </item> | 2625 | </item> |
| 2626 | <item alignment="Qt::AlignBottom"> | 2626 | <item alignment="Qt::AlignBottom"> |
| 2627 | <widget class="QDialogButtonBox" name="buttonBox"> | 2627 | <widget class="QWidget" name="closeButtons" native="true"> |
| 2628 | <property name="enabled"> | 2628 | <layout class="QVBoxLayout" name="verticalLayout_46"> |
| 2629 | <bool>true</bool> | 2629 | <property name="spacing"> |
| 2630 | </property> | 2630 | <number>7</number> |
| 2631 | <property name="standardButtons"> | 2631 | </property> |
| 2632 | <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | 2632 | <property name="leftMargin"> |
| 2633 | </property> | 2633 | <number>0</number> |
| 2634 | </property> | ||
| 2635 | <property name="topMargin"> | ||
| 2636 | <number>0</number> | ||
| 2637 | </property> | ||
| 2638 | <property name="rightMargin"> | ||
| 2639 | <number>0</number> | ||
| 2640 | </property> | ||
| 2641 | <property name="bottomMargin"> | ||
| 2642 | <number>0</number> | ||
| 2643 | </property> | ||
| 2644 | <item> | ||
| 2645 | <widget class="QLabel" name="labelError"> | ||
| 2646 | <property name="enabled"> | ||
| 2647 | <bool>true</bool> | ||
| 2648 | </property> | ||
| 2649 | <property name="styleSheet"> | ||
| 2650 | <string notr="true">QLabel { color : red; }</string> | ||
| 2651 | </property> | ||
| 2652 | <property name="text"> | ||
| 2653 | <string>Not enough controllers</string> | ||
| 2654 | </property> | ||
| 2655 | <property name="alignment"> | ||
| 2656 | <set>Qt::AlignCenter</set> | ||
| 2657 | </property> | ||
| 2658 | <property name="margin"> | ||
| 2659 | <number>0</number> | ||
| 2660 | </property> | ||
| 2661 | </widget> | ||
| 2662 | </item> | ||
| 2663 | <item> | ||
| 2664 | <widget class="QDialogButtonBox" name="buttonBox"> | ||
| 2665 | <property name="enabled"> | ||
| 2666 | <bool>true</bool> | ||
| 2667 | </property> | ||
| 2668 | <property name="standardButtons"> | ||
| 2669 | <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||
| 2670 | </property> | ||
| 2671 | </widget> | ||
| 2672 | </item> | ||
| 2673 | </layout> | ||
| 2634 | </widget> | 2674 | </widget> |
| 2635 | </item> | 2675 | </item> |
| 2636 | </layout> | 2676 | </layout> |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 1de093447..d5157c502 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -128,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ | |||
| 128 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, | 128 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, |
| 129 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, | 129 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, |
| 130 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, | 130 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, |
| 131 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}}, | 131 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}}, |
| 132 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}}, | 132 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}}, |
| 133 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, | 133 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, |
| 134 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, | 134 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, |
| 135 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, | 135 | {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, |
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index e8f9ebfd8..5a48e388b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp | |||
| @@ -115,17 +115,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, | |||
| 115 | for (std::size_t i = 0; i < player_tabs.size(); ++i) { | 115 | for (std::size_t i = 0; i < player_tabs.size(); ++i) { |
| 116 | player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); | 116 | player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); |
| 117 | player_tabs[i]->layout()->addWidget(player_controllers[i]); | 117 | player_tabs[i]->layout()->addWidget(player_controllers[i]); |
| 118 | connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { | 118 | connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) { |
| 119 | // Ensures that the controllers are always connected in sequential order | 119 | // Ensures that the controllers are always connected in sequential order |
| 120 | if (is_connected) { | 120 | this->propagateMouseClickOnPlayers(i, checked, true); |
| 121 | for (std::size_t index = 0; index <= i; ++index) { | ||
| 122 | player_connected[index]->setChecked(is_connected); | ||
| 123 | } | ||
| 124 | } else { | ||
| 125 | for (std::size_t index = i; index < player_tabs.size(); ++index) { | ||
| 126 | player_connected[index]->setChecked(is_connected); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | }); | 121 | }); |
| 130 | connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, | 122 | connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, |
| 131 | &ConfigureInput::UpdateAllInputDevices); | 123 | &ConfigureInput::UpdateAllInputDevices); |
| @@ -183,6 +175,30 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, | |||
| 183 | LoadConfiguration(); | 175 | LoadConfiguration(); |
| 184 | } | 176 | } |
| 185 | 177 | ||
| 178 | void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) { | ||
| 179 | // Origin has already been toggled | ||
| 180 | if (!origin) { | ||
| 181 | player_connected[player_index]->setChecked(checked); | ||
| 182 | } | ||
| 183 | |||
| 184 | if (checked) { | ||
| 185 | // Check all previous buttons when checked | ||
| 186 | if (player_index > 0) { | ||
| 187 | propagateMouseClickOnPlayers(player_index - 1, checked, false); | ||
| 188 | } | ||
| 189 | } else { | ||
| 190 | // Unchecked all following buttons when unchecked | ||
| 191 | if (player_index < player_tabs.size() - 1) { | ||
| 192 | // Reconnect current player if it was the last one checked | ||
| 193 | // (player number was reduced by more than one) | ||
| 194 | if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) { | ||
| 195 | player_connected[player_index]->setCheckState(Qt::Checked); | ||
| 196 | } | ||
| 197 | propagateMouseClickOnPlayers(player_index + 1, checked, false); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 186 | QList<QWidget*> ConfigureInput::GetSubTabs() const { | 202 | QList<QWidget*> ConfigureInput::GetSubTabs() const { |
| 187 | return { | 203 | return { |
| 188 | ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, | 204 | ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, |
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index c89189c36..abb7f7089 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h | |||
| @@ -56,6 +56,7 @@ private: | |||
| 56 | void UpdateDockedState(bool is_handheld); | 56 | void UpdateDockedState(bool is_handheld); |
| 57 | void UpdateAllInputDevices(); | 57 | void UpdateAllInputDevices(); |
| 58 | void UpdateAllInputProfiles(std::size_t player_index); | 58 | void UpdateAllInputProfiles(std::size_t player_index); |
| 59 | void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked); | ||
| 59 | 60 | ||
| 60 | /// Load configuration settings. | 61 | /// Load configuration settings. |
| 61 | void LoadConfiguration(); | 62 | void LoadConfiguration(); |
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index a4e8af1b4..3fe448f27 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp | |||
| @@ -157,6 +157,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) { | |||
| 157 | INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); | 157 | INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); |
| 158 | INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); | 158 | INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); |
| 159 | INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); | 159 | INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); |
| 160 | INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", ""); | ||
| 160 | INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); | 161 | INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); |
| 161 | INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); | 162 | INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); |
| 162 | 163 | ||
| @@ -383,6 +384,13 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) { | |||
| 383 | translations->insert( | 384 | translations->insert( |
| 384 | {Settings::EnumMetadata<Settings::ConsoleMode>::Index(), | 385 | {Settings::EnumMetadata<Settings::ConsoleMode>::Index(), |
| 385 | {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); | 386 | {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); |
| 387 | translations->insert( | ||
| 388 | {Settings::EnumMetadata<Settings::ConfirmStop>::Index(), | ||
| 389 | { | ||
| 390 | PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"), | ||
| 391 | PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"), | ||
| 392 | PAIR(ConfirmStop, Ask_Never, "Never ask"), | ||
| 393 | }}); | ||
| 386 | 394 | ||
| 387 | #undef PAIR | 395 | #undef PAIR |
| 388 | #undef CTX_PAIR | 396 | #undef CTX_PAIR |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 74f48031a..2bb1a0239 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -826,12 +826,13 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 826 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); | 826 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); |
| 827 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | 827 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); |
| 828 | 828 | ||
| 829 | // Before deleting rows, cancel the worker so that it is not using them | ||
| 830 | emit ShouldCancelWorker(); | ||
| 831 | |||
| 829 | // Delete any rows that might already exist if we're repopulating | 832 | // Delete any rows that might already exist if we're repopulating |
| 830 | item_model->removeRows(0, item_model->rowCount()); | 833 | item_model->removeRows(0, item_model->rowCount()); |
| 831 | search_field->clear(); | 834 | search_field->clear(); |
| 832 | 835 | ||
| 833 | emit ShouldCancelWorker(); | ||
| 834 | |||
| 835 | GameListWorker* worker = | 836 | GameListWorker* worker = |
| 836 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); | 837 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); |
| 837 | 838 | ||
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 588f1dd6e..077ced12b 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -293,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | |||
| 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 5427758c1..1431cf2fe 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -67,6 +67,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 67 | #define QT_NO_OPENGL | 67 | #define QT_NO_OPENGL |
| 68 | #include <QClipboard> | 68 | #include <QClipboard> |
| 69 | #include <QDesktopServices> | 69 | #include <QDesktopServices> |
| 70 | #include <QDir> | ||
| 70 | #include <QFile> | 71 | #include <QFile> |
| 71 | #include <QFileDialog> | 72 | #include <QFileDialog> |
| 72 | #include <QInputDialog> | 73 | #include <QInputDialog> |
| @@ -76,6 +77,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 76 | #include <QPushButton> | 77 | #include <QPushButton> |
| 77 | #include <QScreen> | 78 | #include <QScreen> |
| 78 | #include <QShortcut> | 79 | #include <QShortcut> |
| 80 | #include <QStandardPaths> | ||
| 79 | #include <QStatusBar> | 81 | #include <QStatusBar> |
| 80 | #include <QString> | 82 | #include <QString> |
| 81 | #include <QSysInfo> | 83 | #include <QSysInfo> |
| @@ -209,7 +211,7 @@ void GMainWindow::ShowTelemetryCallout() { | |||
| 209 | tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " | 211 | tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " |
| 210 | "data is collected</a> to help improve yuzu. " | 212 | "data is collected</a> to help improve yuzu. " |
| 211 | "<br/><br/>Would you like to share your usage data with us?"); | 213 | "<br/><br/>Would you like to share your usage data with us?"); |
| 212 | if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { | 214 | if (!question(this, tr("Telemetry"), telemetry_message)) { |
| 213 | Settings::values.enable_telemetry = false; | 215 | Settings::values.enable_telemetry = false; |
| 214 | system->ApplySettings(); | 216 | system->ApplySettings(); |
| 215 | } | 217 | } |
| @@ -2418,9 +2420,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT | |||
| 2418 | } | 2420 | } |
| 2419 | }(); | 2421 | }(); |
| 2420 | 2422 | ||
| 2421 | if (QMessageBox::question(this, tr("Remove Entry"), entry_question, | 2423 | if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No, |
| 2422 | QMessageBox::Yes | QMessageBox::No, | 2424 | QMessageBox::No)) { |
| 2423 | QMessageBox::No) != QMessageBox::Yes) { | ||
| 2424 | return; | 2425 | return; |
| 2425 | } | 2426 | } |
| 2426 | 2427 | ||
| @@ -2519,8 +2520,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ | |||
| 2519 | } | 2520 | } |
| 2520 | }(); | 2521 | }(); |
| 2521 | 2522 | ||
| 2522 | if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, | 2523 | if (!GMainWindow::question(this, tr("Remove File"), question, |
| 2523 | QMessageBox::No) != QMessageBox::Yes) { | 2524 | QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { |
| 2524 | return; | 2525 | return; |
| 2525 | } | 2526 | } |
| 2526 | 2527 | ||
| @@ -2869,44 +2870,50 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga | |||
| 2869 | #endif // __linux__ | 2870 | #endif // __linux__ |
| 2870 | 2871 | ||
| 2871 | std::filesystem::path target_directory{}; | 2872 | std::filesystem::path target_directory{}; |
| 2872 | // Determine target directory for shortcut | ||
| 2873 | #if defined(WIN32) | ||
| 2874 | const char* home = std::getenv("USERPROFILE"); | ||
| 2875 | #else | ||
| 2876 | const char* home = std::getenv("HOME"); | ||
| 2877 | #endif | ||
| 2878 | const std::filesystem::path home_path = (home == nullptr ? "~" : home); | ||
| 2879 | const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); | ||
| 2880 | 2873 | ||
| 2881 | if (target == GameListShortcutTarget::Desktop) { | 2874 | switch (target) { |
| 2882 | target_directory = home_path / "Desktop"; | 2875 | case GameListShortcutTarget::Desktop: { |
| 2883 | if (!Common::FS::IsDir(target_directory)) { | 2876 | const QString desktop_path = |
| 2884 | QMessageBox::critical( | 2877 | QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); |
| 2885 | this, tr("Create Shortcut"), | 2878 | target_directory = desktop_path.toUtf8().toStdString(); |
| 2886 | tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") | 2879 | break; |
| 2887 | .arg(QString::fromStdString(target_directory.generic_string())), | 2880 | } |
| 2888 | QMessageBox::StandardButton::Ok); | 2881 | case GameListShortcutTarget::Applications: { |
| 2889 | return; | 2882 | const QString applications_path = |
| 2890 | } | 2883 | QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); |
| 2891 | } else if (target == GameListShortcutTarget::Applications) { | 2884 | if (applications_path.isEmpty()) { |
| 2892 | target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / | 2885 | const char* home = std::getenv("HOME"); |
| 2893 | "applications"; | 2886 | if (home != nullptr) { |
| 2894 | if (!Common::FS::CreateDirs(target_directory)) { | 2887 | target_directory = std::filesystem::path(home) / ".local/share/applications"; |
| 2895 | QMessageBox::critical( | 2888 | } |
| 2896 | this, tr("Create Shortcut"), | 2889 | } else { |
| 2897 | tr("Cannot create shortcut in applications menu. Path \"%1\" " | 2890 | target_directory = applications_path.toUtf8().toStdString(); |
| 2898 | "does not exist and cannot be created.") | ||
| 2899 | .arg(QString::fromStdString(target_directory.generic_string())), | ||
| 2900 | QMessageBox::StandardButton::Ok); | ||
| 2901 | return; | ||
| 2902 | } | 2891 | } |
| 2892 | break; | ||
| 2893 | } | ||
| 2894 | default: | ||
| 2895 | return; | ||
| 2896 | } | ||
| 2897 | |||
| 2898 | const QDir dir(QString::fromStdString(target_directory.generic_string())); | ||
| 2899 | if (!dir.exists()) { | ||
| 2900 | QMessageBox::critical(this, tr("Create Shortcut"), | ||
| 2901 | tr("Cannot create shortcut. Path \"%1\" does not exist.") | ||
| 2902 | .arg(QString::fromStdString(target_directory.generic_string())), | ||
| 2903 | QMessageBox::StandardButton::Ok); | ||
| 2904 | return; | ||
| 2903 | } | 2905 | } |
| 2904 | 2906 | ||
| 2905 | const std::string game_file_name = std::filesystem::path(game_path).filename().string(); | 2907 | const std::string game_file_name = std::filesystem::path(game_path).filename().string(); |
| 2906 | // Determine full paths for icon and shortcut | 2908 | // Determine full paths for icon and shortcut |
| 2907 | #if defined(__linux__) || defined(__FreeBSD__) | 2909 | #if defined(__linux__) || defined(__FreeBSD__) |
| 2910 | const char* home = std::getenv("HOME"); | ||
| 2911 | const std::filesystem::path home_path = (home == nullptr ? "~" : home); | ||
| 2912 | const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); | ||
| 2913 | |||
| 2908 | std::filesystem::path system_icons_path = | 2914 | std::filesystem::path system_icons_path = |
| 2909 | (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) / | 2915 | (xdg_data_home == nullptr ? home_path / ".local/share/" |
| 2916 | : std::filesystem::path(xdg_data_home)) / | ||
| 2910 | "icons/hicolor/256x256"; | 2917 | "icons/hicolor/256x256"; |
| 2911 | if (!Common::FS::CreateDirs(system_icons_path)) { | 2918 | if (!Common::FS::CreateDirs(system_icons_path)) { |
| 2912 | QMessageBox::critical( | 2919 | QMessageBox::critical( |
| @@ -3401,10 +3408,13 @@ void GMainWindow::OnRestartGame() { | |||
| 3401 | if (!system->IsPoweredOn()) { | 3408 | if (!system->IsPoweredOn()) { |
| 3402 | return; | 3409 | return; |
| 3403 | } | 3410 | } |
| 3404 | // Make a copy since ShutdownGame edits game_path | 3411 | |
| 3405 | const auto current_game = QString(current_game_path); | 3412 | if (ConfirmShutdownGame()) { |
| 3406 | ShutdownGame(); | 3413 | // Make a copy since ShutdownGame edits game_path |
| 3407 | BootGame(current_game); | 3414 | const auto current_game = QString(current_game_path); |
| 3415 | ShutdownGame(); | ||
| 3416 | BootGame(current_game); | ||
| 3417 | } | ||
| 3408 | } | 3418 | } |
| 3409 | 3419 | ||
| 3410 | void GMainWindow::OnPauseGame() { | 3420 | void GMainWindow::OnPauseGame() { |
| @@ -3426,18 +3436,39 @@ void GMainWindow::OnPauseContinueGame() { | |||
| 3426 | } | 3436 | } |
| 3427 | 3437 | ||
| 3428 | void GMainWindow::OnStopGame() { | 3438 | void GMainWindow::OnStopGame() { |
| 3429 | if (system->GetExitLocked() && !ConfirmForceLockedExit()) { | 3439 | if (ConfirmShutdownGame()) { |
| 3430 | return; | 3440 | play_time_manager->Stop(); |
| 3441 | // Update game list to show new play time | ||
| 3442 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 3443 | if (OnShutdownBegin()) { | ||
| 3444 | OnShutdownBeginDialog(); | ||
| 3445 | } else { | ||
| 3446 | OnEmulationStopped(); | ||
| 3447 | } | ||
| 3431 | } | 3448 | } |
| 3449 | } | ||
| 3432 | 3450 | ||
| 3433 | play_time_manager->Stop(); | 3451 | bool GMainWindow::ConfirmShutdownGame() { |
| 3434 | // Update game list to show new play time | 3452 | if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) { |
| 3435 | game_list->PopulateAsync(UISettings::values.game_dirs); | 3453 | if (system->GetExitLocked()) { |
| 3436 | if (OnShutdownBegin()) { | 3454 | if (!ConfirmForceLockedExit()) { |
| 3437 | OnShutdownBeginDialog(); | 3455 | return false; |
| 3456 | } | ||
| 3457 | } else { | ||
| 3458 | if (!ConfirmChangeGame()) { | ||
| 3459 | return false; | ||
| 3460 | } | ||
| 3461 | } | ||
| 3438 | } else { | 3462 | } else { |
| 3439 | OnEmulationStopped(); | 3463 | if (UISettings::values.confirm_before_stopping.GetValue() == |
| 3464 | ConfirmStop::Ask_Based_On_Game && | ||
| 3465 | system->GetExitLocked()) { | ||
| 3466 | if (!ConfirmForceLockedExit()) { | ||
| 3467 | return false; | ||
| 3468 | } | ||
| 3469 | } | ||
| 3440 | } | 3470 | } |
| 3471 | return true; | ||
| 3441 | } | 3472 | } |
| 3442 | 3473 | ||
| 3443 | void GMainWindow::OnLoadComplete() { | 3474 | void GMainWindow::OnLoadComplete() { |
| @@ -3817,22 +3848,11 @@ void GMainWindow::OnTasRecord() { | |||
| 3817 | const bool is_recording = input_subsystem->GetTas()->Record(); | 3848 | const bool is_recording = input_subsystem->GetTas()->Record(); |
| 3818 | if (!is_recording) { | 3849 | if (!is_recording) { |
| 3819 | is_tas_recording_dialog_active = true; | 3850 | is_tas_recording_dialog_active = true; |
| 3820 | ControllerNavigation* controller_navigation = | 3851 | |
| 3821 | new ControllerNavigation(system->HIDCore(), this); | 3852 | bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"), |
| 3822 | // Use QMessageBox instead of question so we can link controller navigation | 3853 | QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); |
| 3823 | QMessageBox* box_dialog = new QMessageBox(); | 3854 | |
| 3824 | box_dialog->setWindowTitle(tr("TAS Recording")); | 3855 | input_subsystem->GetTas()->SaveRecording(answer); |
| 3825 | box_dialog->setText(tr("Overwrite file of player 1?")); | ||
| 3826 | box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); | ||
| 3827 | box_dialog->setDefaultButton(QMessageBox::Yes); | ||
| 3828 | connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, | ||
| 3829 | [box_dialog](Qt::Key key) { | ||
| 3830 | QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); | ||
| 3831 | QCoreApplication::postEvent(box_dialog, event); | ||
| 3832 | }); | ||
| 3833 | int res = box_dialog->exec(); | ||
| 3834 | controller_navigation->UnloadController(); | ||
| 3835 | input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); | ||
| 3836 | is_tas_recording_dialog_active = false; | 3856 | is_tas_recording_dialog_active = false; |
| 3837 | } | 3857 | } |
| 3838 | OnTasStateChanged(); | 3858 | OnTasStateChanged(); |
| @@ -4073,6 +4093,29 @@ void GMainWindow::OnLoadAmiibo() { | |||
| 4073 | LoadAmiibo(filename); | 4093 | LoadAmiibo(filename); |
| 4074 | } | 4094 | } |
| 4075 | 4095 | ||
| 4096 | bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text, | ||
| 4097 | QMessageBox::StandardButtons buttons, | ||
| 4098 | QMessageBox::StandardButton defaultButton) { | ||
| 4099 | |||
| 4100 | QMessageBox* box_dialog = new QMessageBox(parent); | ||
| 4101 | box_dialog->setWindowTitle(title); | ||
| 4102 | box_dialog->setText(text); | ||
| 4103 | box_dialog->setStandardButtons(buttons); | ||
| 4104 | box_dialog->setDefaultButton(defaultButton); | ||
| 4105 | |||
| 4106 | ControllerNavigation* controller_navigation = | ||
| 4107 | new ControllerNavigation(system->HIDCore(), box_dialog); | ||
| 4108 | connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, | ||
| 4109 | [box_dialog](Qt::Key key) { | ||
| 4110 | QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); | ||
| 4111 | QCoreApplication::postEvent(box_dialog, event); | ||
| 4112 | }); | ||
| 4113 | int res = box_dialog->exec(); | ||
| 4114 | |||
| 4115 | controller_navigation->UnloadController(); | ||
| 4116 | return res == QMessageBox::Yes; | ||
| 4117 | } | ||
| 4118 | |||
| 4076 | void GMainWindow::LoadAmiibo(const QString& filename) { | 4119 | void GMainWindow::LoadAmiibo(const QString& filename) { |
| 4077 | auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); | 4120 | auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); |
| 4078 | const QString title = tr("Error loading Amiibo data"); | 4121 | const QString title = tr("Error loading Amiibo data"); |
| @@ -4806,8 +4849,7 @@ bool GMainWindow::ConfirmClose() { | |||
| 4806 | return true; | 4849 | return true; |
| 4807 | } | 4850 | } |
| 4808 | const auto text = tr("Are you sure you want to close yuzu?"); | 4851 | const auto text = tr("Are you sure you want to close yuzu?"); |
| 4809 | const auto answer = QMessageBox::question(this, tr("yuzu"), text); | 4852 | return question(this, tr("yuzu"), text); |
| 4810 | return answer != QMessageBox::No; | ||
| 4811 | } | 4853 | } |
| 4812 | 4854 | ||
| 4813 | void GMainWindow::closeEvent(QCloseEvent* event) { | 4855 | void GMainWindow::closeEvent(QCloseEvent* event) { |
| @@ -4900,11 +4942,11 @@ bool GMainWindow::ConfirmChangeGame() { | |||
| 4900 | if (emu_thread == nullptr) | 4942 | if (emu_thread == nullptr) |
| 4901 | return true; | 4943 | return true; |
| 4902 | 4944 | ||
| 4903 | const auto answer = QMessageBox::question( | 4945 | // Use custom question to link controller navigation |
| 4946 | return question( | ||
| 4904 | this, tr("yuzu"), | 4947 | this, tr("yuzu"), |
| 4905 | tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), | 4948 | tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), |
| 4906 | QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | 4949 | QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); |
| 4907 | return answer != QMessageBox::No; | ||
| 4908 | } | 4950 | } |
| 4909 | 4951 | ||
| 4910 | bool GMainWindow::ConfirmForceLockedExit() { | 4952 | bool GMainWindow::ConfirmForceLockedExit() { |
| @@ -4914,8 +4956,7 @@ bool GMainWindow::ConfirmForceLockedExit() { | |||
| 4914 | const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" | 4956 | const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" |
| 4915 | "Would you like to bypass this and exit anyway?"); | 4957 | "Would you like to bypass this and exit anyway?"); |
| 4916 | 4958 | ||
| 4917 | const auto answer = QMessageBox::question(this, tr("yuzu"), text); | 4959 | return question(this, tr("yuzu"), text); |
| 4918 | return answer != QMessageBox::No; | ||
| 4919 | } | 4960 | } |
| 4920 | 4961 | ||
| 4921 | void GMainWindow::RequestGameExit() { | 4962 | void GMainWindow::RequestGameExit() { |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 2346eb3bd..270a40c5f 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include <optional> | 7 | #include <optional> |
| 8 | 8 | ||
| 9 | #include <QMainWindow> | 9 | #include <QMainWindow> |
| 10 | #include <QMessageBox> | ||
| 10 | #include <QTimer> | 11 | #include <QTimer> |
| 11 | #include <QTranslator> | 12 | #include <QTranslator> |
| 12 | 13 | ||
| @@ -15,6 +16,7 @@ | |||
| 15 | #include "input_common/drivers/tas_input.h" | 16 | #include "input_common/drivers/tas_input.h" |
| 16 | #include "yuzu/compatibility_list.h" | 17 | #include "yuzu/compatibility_list.h" |
| 17 | #include "yuzu/hotkeys.h" | 18 | #include "yuzu/hotkeys.h" |
| 19 | #include "yuzu/util/controller_navigation.h" | ||
| 18 | 20 | ||
| 19 | #ifdef __unix__ | 21 | #ifdef __unix__ |
| 20 | #include <QVariant> | 22 | #include <QVariant> |
| @@ -424,6 +426,11 @@ private: | |||
| 424 | bool CheckSystemArchiveDecryption(); | 426 | bool CheckSystemArchiveDecryption(); |
| 425 | bool CheckFirmwarePresence(); | 427 | bool CheckFirmwarePresence(); |
| 426 | void ConfigureFilesystemProvider(const std::string& filepath); | 428 | void ConfigureFilesystemProvider(const std::string& filepath); |
| 429 | /** | ||
| 430 | * Open (or not) the right confirm dialog based on current setting and game exit lock | ||
| 431 | * @returns true if the player confirmed or the settings do no require it | ||
| 432 | */ | ||
| 433 | bool ConfirmShutdownGame(); | ||
| 427 | 434 | ||
| 428 | QString GetTasStateDescription() const; | 435 | QString GetTasStateDescription() const; |
| 429 | bool CreateShortcut(const std::string& shortcut_path, const std::string& title, | 436 | bool CreateShortcut(const std::string& shortcut_path, const std::string& title, |
| @@ -431,6 +438,17 @@ private: | |||
| 431 | const std::string& command, const std::string& arguments, | 438 | const std::string& command, const std::string& arguments, |
| 432 | const std::string& categories, const std::string& keywords); | 439 | const std::string& categories, const std::string& keywords); |
| 433 | 440 | ||
| 441 | /** | ||
| 442 | * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog | ||
| 443 | * The only difference is that it returns a boolean. | ||
| 444 | * | ||
| 445 | * @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button. | ||
| 446 | */ | ||
| 447 | bool question(QWidget* parent, const QString& title, const QString& text, | ||
| 448 | QMessageBox::StandardButtons buttons = | ||
| 449 | QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), | ||
| 450 | QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); | ||
| 451 | |||
| 434 | std::unique_ptr<Ui::MainWindow> ui; | 452 | std::unique_ptr<Ui::MainWindow> ui; |
| 435 | 453 | ||
| 436 | std::unique_ptr<Core::System> system; | 454 | std::unique_ptr<Core::System> system; |
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 975008159..b62ff620c 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -16,7 +16,9 @@ | |||
| 16 | #include "common/settings_enums.h" | 16 | #include "common/settings_enums.h" |
| 17 | 17 | ||
| 18 | using Settings::Category; | 18 | using Settings::Category; |
| 19 | using Settings::ConfirmStop; | ||
| 19 | using Settings::Setting; | 20 | using Settings::Setting; |
| 21 | using Settings::SwitchableSetting; | ||
| 20 | 22 | ||
| 21 | #ifndef CANNOT_EXPLICITLY_INSTANTIATE | 23 | #ifndef CANNOT_EXPLICITLY_INSTANTIATE |
| 22 | namespace Settings { | 24 | namespace Settings { |
| @@ -94,6 +96,15 @@ struct Values { | |||
| 94 | Setting<bool> confirm_before_closing{ | 96 | Setting<bool> confirm_before_closing{ |
| 95 | linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, | 97 | linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, |
| 96 | true, true}; | 98 | true, true}; |
| 99 | |||
| 100 | SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage, | ||
| 101 | ConfirmStop::Ask_Always, | ||
| 102 | "confirmStop", | ||
| 103 | Category::UiGeneral, | ||
| 104 | Settings::Specialization::Default, | ||
| 105 | true, | ||
| 106 | true}; | ||
| 107 | |||
| 97 | Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; | 108 | Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; |
| 98 | Setting<bool> pause_when_in_background{linkage, | 109 | Setting<bool> pause_when_in_background{linkage, |
| 99 | false, | 110 | false, |
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index 61cf00176..f2854c8ec 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp | |||
| @@ -63,25 +63,15 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) { | |||
| 63 | }; | 63 | }; |
| 64 | #pragma pack(pop) | 64 | #pragma pack(pop) |
| 65 | 65 | ||
| 66 | QImage source_image = image.convertToFormat(QImage::Format_RGB32); | 66 | const QImage source_image = image.convertToFormat(QImage::Format_RGB32); |
| 67 | constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16}; | ||
| 67 | constexpr int bytes_per_pixel = 4; | 68 | constexpr int bytes_per_pixel = 4; |
| 68 | const int image_size = source_image.width() * source_image.height() * bytes_per_pixel; | 69 | |
| 69 | 70 | const IconDir icon_dir{ | |
| 70 | BITMAPINFOHEADER info_header{}; | 71 | .id_reserved = 0, |
| 71 | info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(), | 72 | .id_type = 1, |
| 72 | info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1, | 73 | .id_count = static_cast<WORD>(scale_sizes.size()), |
| 73 | info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB; | 74 | }; |
| 74 | |||
| 75 | const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1}; | ||
| 76 | const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()), | ||
| 77 | .height = static_cast<BYTE>(source_image.height() * 2), | ||
| 78 | .color_count = 0, | ||
| 79 | .reserved = 0, | ||
| 80 | .planes = 1, | ||
| 81 | .bit_count = bytes_per_pixel * 8, | ||
| 82 | .bytes_in_res = | ||
| 83 | static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size), | ||
| 84 | .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)}; | ||
| 85 | 75 | ||
| 86 | Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write, | 76 | Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write, |
| 87 | Common::FS::FileType::BinaryFile); | 77 | Common::FS::FileType::BinaryFile); |
| @@ -92,20 +82,55 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) { | |||
| 92 | if (!icon_file.Write(icon_dir)) { | 82 | if (!icon_file.Write(icon_dir)) { |
| 93 | return false; | 83 | return false; |
| 94 | } | 84 | } |
| 95 | if (!icon_file.Write(icon_entry)) { | 85 | |
| 96 | return false; | 86 | std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size()); |
| 97 | } | 87 | for (std::size_t i = 0; i < scale_sizes.size(); i++) { |
| 98 | if (!icon_file.Write(info_header)) { | 88 | const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel; |
| 99 | return false; | 89 | const IconDirEntry icon_entry{ |
| 90 | .width = static_cast<BYTE>(scale_sizes[i]), | ||
| 91 | .height = static_cast<BYTE>(scale_sizes[i]), | ||
| 92 | .color_count = 0, | ||
| 93 | .reserved = 0, | ||
| 94 | .planes = 1, | ||
| 95 | .bit_count = bytes_per_pixel * 8, | ||
| 96 | .bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size), | ||
| 97 | .image_offset = static_cast<DWORD>(image_offset), | ||
| 98 | }; | ||
| 99 | image_offset += icon_entry.bytes_in_res; | ||
| 100 | if (!icon_file.Write(icon_entry)) { | ||
| 101 | return false; | ||
| 102 | } | ||
| 100 | } | 103 | } |
| 101 | 104 | ||
| 102 | for (int y = 0; y < image.height(); y++) { | 105 | for (std::size_t i = 0; i < scale_sizes.size(); i++) { |
| 103 | const auto* line = source_image.scanLine(source_image.height() - 1 - y); | 106 | const QImage scaled_image = source_image.scaled( |
| 104 | std::vector<u8> line_data(source_image.width() * bytes_per_pixel); | 107 | scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation); |
| 105 | std::memcpy(line_data.data(), line, line_data.size()); | 108 | const BITMAPINFOHEADER info_header{ |
| 106 | if (!icon_file.Write(line_data)) { | 109 | .biSize = sizeof(BITMAPINFOHEADER), |
| 110 | .biWidth = scaled_image.width(), | ||
| 111 | .biHeight = scaled_image.height() * 2, | ||
| 112 | .biPlanes = 1, | ||
| 113 | .biBitCount = bytes_per_pixel * 8, | ||
| 114 | .biCompression = BI_RGB, | ||
| 115 | .biSizeImage{}, | ||
| 116 | .biXPelsPerMeter{}, | ||
| 117 | .biYPelsPerMeter{}, | ||
| 118 | .biClrUsed{}, | ||
| 119 | .biClrImportant{}, | ||
| 120 | }; | ||
| 121 | |||
| 122 | if (!icon_file.Write(info_header)) { | ||
| 107 | return false; | 123 | return false; |
| 108 | } | 124 | } |
| 125 | |||
| 126 | for (int y = 0; y < scaled_image.height(); y++) { | ||
| 127 | const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y); | ||
| 128 | std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel); | ||
| 129 | std::memcpy(line_data.data(), line, line_data.size()); | ||
| 130 | if (!icon_file.Write(line_data)) { | ||
| 131 | return false; | ||
| 132 | } | ||
| 133 | } | ||
| 109 | } | 134 | } |
| 110 | icon_file.Close(); | 135 | icon_file.Close(); |
| 111 | 136 | ||