diff options
Diffstat (limited to 'src')
71 files changed, 1861 insertions, 889 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index b7556e353..1c9fb0675 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 | |||
| @@ -21,6 +21,8 @@ import org.yuzu.yuzu_emu.utils.DocumentsTree | |||
| 21 | import org.yuzu.yuzu_emu.utils.FileUtil | 21 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 22 | import org.yuzu.yuzu_emu.utils.Log | 22 | import org.yuzu.yuzu_emu.utils.Log |
| 23 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | 23 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable |
| 24 | import org.yuzu.yuzu_emu.model.InstallResult | ||
| 25 | import org.yuzu.yuzu_emu.model.Patch | ||
| 24 | 26 | ||
| 25 | /** | 27 | /** |
| 26 | * Class which contains methods that interact | 28 | * Class which contains methods that interact |
| @@ -235,9 +237,12 @@ object NativeLibrary { | |||
| 235 | /** | 237 | /** |
| 236 | * Installs a nsp or xci file to nand | 238 | * Installs a nsp or xci file to nand |
| 237 | * @param filename String representation of file uri | 239 | * @param filename String representation of file uri |
| 238 | * @param extension Lowercase string representation of file extension without "." | 240 | * @return int representation of [InstallResult] |
| 239 | */ | 241 | */ |
| 240 | external fun installFileToNand(filename: String, extension: String): Int | 242 | external fun installFileToNand( |
| 243 | filename: String, | ||
| 244 | callback: (max: Long, progress: Long) -> Boolean | ||
| 245 | ): Int | ||
| 241 | 246 | ||
| 242 | external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean | 247 | external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean |
| 243 | 248 | ||
| @@ -535,9 +540,29 @@ object NativeLibrary { | |||
| 535 | * | 540 | * |
| 536 | * @param path Path to game file. Can be a [Uri]. | 541 | * @param path Path to game file. Can be a [Uri]. |
| 537 | * @param programId String representation of a game's program ID | 542 | * @param programId String representation of a game's program ID |
| 538 | * @return Array of pairs where the first value is the name of an addon and the second is the version | 543 | * @return Array of available patches |
| 539 | */ | 544 | */ |
| 540 | external fun getAddonsForFile(path: String, programId: String): Array<Pair<String, String>>? | 545 | external fun getPatchesForFile(path: String, programId: String): Array<Patch>? |
| 546 | |||
| 547 | /** | ||
| 548 | * Removes an update for a given [programId] | ||
| 549 | * @param programId String representation of a game's program ID | ||
| 550 | */ | ||
| 551 | external fun removeUpdate(programId: String) | ||
| 552 | |||
| 553 | /** | ||
| 554 | * Removes all DLC for a [programId] | ||
| 555 | * @param programId String representation of a game's program ID | ||
| 556 | */ | ||
| 557 | external fun removeDLC(programId: String) | ||
| 558 | |||
| 559 | /** | ||
| 560 | * Removes a mod installed for a given [programId] | ||
| 561 | * @param programId String representation of a game's program ID | ||
| 562 | * @param name The name of a mod as given by [getPatchesForFile]. This corresponds with the name | ||
| 563 | * of the mod's directory in a game's load folder. | ||
| 564 | */ | ||
| 565 | external fun removeMod(programId: String, name: String) | ||
| 541 | 566 | ||
| 542 | /** | 567 | /** |
| 543 | * Gets the save location for a specific game | 568 | * Gets the save location for a specific game |
| @@ -609,15 +634,4 @@ object NativeLibrary { | |||
| 609 | const val RELEASED = 0 | 634 | const val RELEASED = 0 |
| 610 | const val PRESSED = 1 | 635 | const val PRESSED = 1 |
| 611 | } | 636 | } |
| 612 | |||
| 613 | /** | ||
| 614 | * Result from installFileToNand | ||
| 615 | */ | ||
| 616 | object InstallFileToNandResult { | ||
| 617 | const val Success = 0 | ||
| 618 | const val SuccessFileOverwritten = 1 | ||
| 619 | const val Error = 2 | ||
| 620 | const val ErrorBaseGame = 3 | ||
| 621 | const val ErrorFilenameExtension = 4 | ||
| 622 | } | ||
| 623 | } | 637 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt index 94c151325..ff254d9b7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt | |||
| @@ -6,27 +6,32 @@ package org.yuzu.yuzu_emu.adapters | |||
| 6 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
| 7 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
| 8 | import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding | 8 | import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding |
| 9 | import org.yuzu.yuzu_emu.model.Addon | 9 | import org.yuzu.yuzu_emu.model.Patch |
| 10 | import org.yuzu.yuzu_emu.model.AddonViewModel | ||
| 10 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 11 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
| 11 | 12 | ||
| 12 | class AddonAdapter : AbstractDiffAdapter<Addon, AddonAdapter.AddonViewHolder>() { | 13 | class AddonAdapter(val addonViewModel: AddonViewModel) : |
| 14 | AbstractDiffAdapter<Patch, AddonAdapter.AddonViewHolder>() { | ||
| 13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder { | 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder { |
| 14 | ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 16 | ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 15 | .also { return AddonViewHolder(it) } | 17 | .also { return AddonViewHolder(it) } |
| 16 | } | 18 | } |
| 17 | 19 | ||
| 18 | inner class AddonViewHolder(val binding: ListItemAddonBinding) : | 20 | inner class AddonViewHolder(val binding: ListItemAddonBinding) : |
| 19 | AbstractViewHolder<Addon>(binding) { | 21 | AbstractViewHolder<Patch>(binding) { |
| 20 | override fun bind(model: Addon) { | 22 | override fun bind(model: Patch) { |
| 21 | binding.root.setOnClickListener { | 23 | binding.root.setOnClickListener { |
| 22 | binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked | 24 | binding.addonCheckbox.isChecked = !binding.addonCheckbox.isChecked |
| 23 | } | 25 | } |
| 24 | binding.title.text = model.title | 26 | binding.title.text = model.name |
| 25 | binding.version.text = model.version | 27 | binding.version.text = model.version |
| 26 | binding.addonSwitch.setOnCheckedChangeListener { _, checked -> | 28 | binding.addonCheckbox.setOnCheckedChangeListener { _, checked -> |
| 27 | model.enabled = checked | 29 | model.enabled = checked |
| 28 | } | 30 | } |
| 29 | binding.addonSwitch.isChecked = model.enabled | 31 | binding.addonCheckbox.isChecked = model.enabled |
| 32 | binding.buttonDelete.setOnClickListener { | ||
| 33 | addonViewModel.setAddonToDelete(model) | ||
| 34 | } | ||
| 30 | } | 35 | } |
| 31 | } | 36 | } |
| 32 | } | 37 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt index 816336820..adb65812c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt | |||
| @@ -74,7 +74,7 @@ class AddonsFragment : Fragment() { | |||
| 74 | 74 | ||
| 75 | binding.listAddons.apply { | 75 | binding.listAddons.apply { |
| 76 | layoutManager = LinearLayoutManager(requireContext()) | 76 | layoutManager = LinearLayoutManager(requireContext()) |
| 77 | adapter = AddonAdapter() | 77 | adapter = AddonAdapter(addonViewModel) |
| 78 | } | 78 | } |
| 79 | 79 | ||
| 80 | viewLifecycleOwner.lifecycleScope.apply { | 80 | viewLifecycleOwner.lifecycleScope.apply { |
| @@ -110,6 +110,21 @@ class AddonsFragment : Fragment() { | |||
| 110 | } | 110 | } |
| 111 | } | 111 | } |
| 112 | } | 112 | } |
| 113 | launch { | ||
| 114 | repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 115 | addonViewModel.addonToDelete.collect { | ||
| 116 | if (it != null) { | ||
| 117 | MessageDialogFragment.newInstance( | ||
| 118 | requireActivity(), | ||
| 119 | titleId = R.string.confirm_uninstall, | ||
| 120 | descriptionId = R.string.confirm_uninstall_description, | ||
| 121 | positiveAction = { addonViewModel.onDeleteAddon(it) } | ||
| 122 | ).show(parentFragmentManager, MessageDialogFragment.TAG) | ||
| 123 | addonViewModel.setAddonToDelete(null) | ||
| 124 | } | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } | ||
| 113 | } | 128 | } |
| 114 | 129 | ||
| 115 | binding.buttonInstall.setOnClickListener { | 130 | binding.buttonInstall.setOnClickListener { |
| @@ -156,22 +171,22 @@ class AddonsFragment : Fragment() { | |||
| 156 | descriptionId = R.string.invalid_directory_description | 171 | descriptionId = R.string.invalid_directory_description |
| 157 | ) | 172 | ) |
| 158 | if (isValid) { | 173 | if (isValid) { |
| 159 | IndeterminateProgressDialogFragment.newInstance( | 174 | ProgressDialogFragment.newInstance( |
| 160 | requireActivity(), | 175 | requireActivity(), |
| 161 | R.string.installing_game_content, | 176 | R.string.installing_game_content, |
| 162 | false | 177 | false |
| 163 | ) { | 178 | ) { progressCallback, _ -> |
| 164 | val parentDirectoryName = externalAddonDirectory.name | 179 | val parentDirectoryName = externalAddonDirectory.name |
| 165 | val internalAddonDirectory = | 180 | val internalAddonDirectory = |
| 166 | File(args.game.addonDir + parentDirectoryName) | 181 | File(args.game.addonDir + parentDirectoryName) |
| 167 | try { | 182 | try { |
| 168 | externalAddonDirectory.copyFilesTo(internalAddonDirectory) | 183 | externalAddonDirectory.copyFilesTo(internalAddonDirectory, progressCallback) |
| 169 | } catch (_: Exception) { | 184 | } catch (_: Exception) { |
| 170 | return@newInstance errorMessage | 185 | return@newInstance errorMessage |
| 171 | } | 186 | } |
| 172 | addonViewModel.refreshAddons() | 187 | addonViewModel.refreshAddons() |
| 173 | return@newInstance getString(R.string.addon_installed_successfully) | 188 | return@newInstance getString(R.string.addon_installed_successfully) |
| 174 | }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) | 189 | }.show(parentFragmentManager, ProgressDialogFragment.TAG) |
| 175 | } else { | 190 | } else { |
| 176 | errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG) | 191 | errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG) |
| 177 | } | 192 | } |
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 index 9dabb9c41..6c758d80b 100644 --- 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 | |||
| @@ -173,11 +173,11 @@ class DriverManagerFragment : Fragment() { | |||
| 173 | return@registerForActivityResult | 173 | return@registerForActivityResult |
| 174 | } | 174 | } |
| 175 | 175 | ||
| 176 | IndeterminateProgressDialogFragment.newInstance( | 176 | ProgressDialogFragment.newInstance( |
| 177 | requireActivity(), | 177 | requireActivity(), |
| 178 | R.string.installing_driver, | 178 | R.string.installing_driver, |
| 179 | false | 179 | false |
| 180 | ) { | 180 | ) { _, _ -> |
| 181 | val driverPath = | 181 | val driverPath = |
| 182 | "${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}" | 182 | "${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}" |
| 183 | val driverFile = File(driverPath) | 183 | val driverFile = File(driverPath) |
| @@ -213,6 +213,6 @@ class DriverManagerFragment : Fragment() { | |||
| 213 | } | 213 | } |
| 214 | } | 214 | } |
| 215 | return@newInstance Any() | 215 | return@newInstance Any() |
| 216 | }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG) | 216 | }.show(childFragmentManager, ProgressDialogFragment.TAG) |
| 217 | } | 217 | } |
| 218 | } | 218 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index b04d1208f..83a845434 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt | |||
| @@ -44,7 +44,6 @@ import org.yuzu.yuzu_emu.utils.FileUtil | |||
| 44 | import org.yuzu.yuzu_emu.utils.GameIconUtils | 44 | import org.yuzu.yuzu_emu.utils.GameIconUtils |
| 45 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 45 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
| 46 | import org.yuzu.yuzu_emu.utils.MemoryUtil | 46 | import org.yuzu.yuzu_emu.utils.MemoryUtil |
| 47 | import java.io.BufferedInputStream | ||
| 48 | import java.io.BufferedOutputStream | 47 | import java.io.BufferedOutputStream |
| 49 | import java.io.File | 48 | import java.io.File |
| 50 | 49 | ||
| @@ -357,27 +356,17 @@ class GamePropertiesFragment : Fragment() { | |||
| 357 | return@registerForActivityResult | 356 | return@registerForActivityResult |
| 358 | } | 357 | } |
| 359 | 358 | ||
| 360 | val inputZip = requireContext().contentResolver.openInputStream(result) | ||
| 361 | val savesFolder = File(args.game.saveDir) | 359 | val savesFolder = File(args.game.saveDir) |
| 362 | val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") | 360 | val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") |
| 363 | cacheSaveDir.mkdir() | 361 | cacheSaveDir.mkdir() |
| 364 | 362 | ||
| 365 | if (inputZip == null) { | 363 | ProgressDialogFragment.newInstance( |
| 366 | Toast.makeText( | ||
| 367 | YuzuApplication.appContext, | ||
| 368 | getString(R.string.fatal_error), | ||
| 369 | Toast.LENGTH_LONG | ||
| 370 | ).show() | ||
| 371 | return@registerForActivityResult | ||
| 372 | } | ||
| 373 | |||
| 374 | IndeterminateProgressDialogFragment.newInstance( | ||
| 375 | requireActivity(), | 364 | requireActivity(), |
| 376 | R.string.save_files_importing, | 365 | R.string.save_files_importing, |
| 377 | false | 366 | false |
| 378 | ) { | 367 | ) { _, _ -> |
| 379 | try { | 368 | try { |
| 380 | FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir) | 369 | FileUtil.unzipToInternalStorage(result.toString(), cacheSaveDir) |
| 381 | val files = cacheSaveDir.listFiles() | 370 | val files = cacheSaveDir.listFiles() |
| 382 | var savesFolderFile: File? = null | 371 | var savesFolderFile: File? = null |
| 383 | if (files != null) { | 372 | if (files != null) { |
| @@ -422,7 +411,7 @@ class GamePropertiesFragment : Fragment() { | |||
| 422 | Toast.LENGTH_LONG | 411 | Toast.LENGTH_LONG |
| 423 | ).show() | 412 | ).show() |
| 424 | } | 413 | } |
| 425 | }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) | 414 | }.show(parentFragmentManager, ProgressDialogFragment.TAG) |
| 426 | } | 415 | } |
| 427 | 416 | ||
| 428 | /** | 417 | /** |
| @@ -436,11 +425,11 @@ class GamePropertiesFragment : Fragment() { | |||
| 436 | return@registerForActivityResult | 425 | return@registerForActivityResult |
| 437 | } | 426 | } |
| 438 | 427 | ||
| 439 | IndeterminateProgressDialogFragment.newInstance( | 428 | ProgressDialogFragment.newInstance( |
| 440 | requireActivity(), | 429 | requireActivity(), |
| 441 | R.string.save_files_exporting, | 430 | R.string.save_files_exporting, |
| 442 | false | 431 | false |
| 443 | ) { | 432 | ) { _, _ -> |
| 444 | val saveLocation = args.game.saveDir | 433 | val saveLocation = args.game.saveDir |
| 445 | val zipResult = FileUtil.zipFromInternalStorage( | 434 | val zipResult = FileUtil.zipFromInternalStorage( |
| 446 | File(saveLocation), | 435 | File(saveLocation), |
| @@ -452,6 +441,6 @@ class GamePropertiesFragment : Fragment() { | |||
| 452 | TaskState.Completed -> getString(R.string.export_success) | 441 | TaskState.Completed -> getString(R.string.export_success) |
| 453 | TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) | 442 | TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) |
| 454 | } | 443 | } |
| 455 | }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) | 444 | }.show(parentFragmentManager, ProgressDialogFragment.TAG) |
| 456 | } | 445 | } |
| 457 | } | 446 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt index 5b4bf2c9f..7df8e6bf4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt | |||
| @@ -34,7 +34,6 @@ import org.yuzu.yuzu_emu.model.TaskState | |||
| 34 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 34 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| 35 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 35 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 36 | import org.yuzu.yuzu_emu.utils.FileUtil | 36 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 37 | import java.io.BufferedInputStream | ||
| 38 | import java.io.BufferedOutputStream | 37 | import java.io.BufferedOutputStream |
| 39 | import java.io.File | 38 | import java.io.File |
| 40 | import java.math.BigInteger | 39 | import java.math.BigInteger |
| @@ -195,26 +194,20 @@ class InstallableFragment : Fragment() { | |||
| 195 | return@registerForActivityResult | 194 | return@registerForActivityResult |
| 196 | } | 195 | } |
| 197 | 196 | ||
| 198 | val inputZip = requireContext().contentResolver.openInputStream(result) | ||
| 199 | val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") | 197 | val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") |
| 200 | cacheSaveDir.mkdir() | 198 | cacheSaveDir.mkdir() |
| 201 | 199 | ||
| 202 | if (inputZip == null) { | 200 | ProgressDialogFragment.newInstance( |
| 203 | Toast.makeText( | ||
| 204 | YuzuApplication.appContext, | ||
| 205 | getString(R.string.fatal_error), | ||
| 206 | Toast.LENGTH_LONG | ||
| 207 | ).show() | ||
| 208 | return@registerForActivityResult | ||
| 209 | } | ||
| 210 | |||
| 211 | IndeterminateProgressDialogFragment.newInstance( | ||
| 212 | requireActivity(), | 201 | requireActivity(), |
| 213 | R.string.save_files_importing, | 202 | R.string.save_files_importing, |
| 214 | false | 203 | false |
| 215 | ) { | 204 | ) { progressCallback, _ -> |
| 216 | try { | 205 | try { |
| 217 | FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir) | 206 | FileUtil.unzipToInternalStorage( |
| 207 | result.toString(), | ||
| 208 | cacheSaveDir, | ||
| 209 | progressCallback | ||
| 210 | ) | ||
| 218 | val files = cacheSaveDir.listFiles() | 211 | val files = cacheSaveDir.listFiles() |
| 219 | var successfulImports = 0 | 212 | var successfulImports = 0 |
| 220 | var failedImports = 0 | 213 | var failedImports = 0 |
| @@ -287,7 +280,7 @@ class InstallableFragment : Fragment() { | |||
| 287 | Toast.LENGTH_LONG | 280 | Toast.LENGTH_LONG |
| 288 | ).show() | 281 | ).show() |
| 289 | } | 282 | } |
| 290 | }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) | 283 | }.show(parentFragmentManager, ProgressDialogFragment.TAG) |
| 291 | } | 284 | } |
| 292 | 285 | ||
| 293 | private val exportSaves = registerForActivityResult( | 286 | private val exportSaves = registerForActivityResult( |
| @@ -297,11 +290,11 @@ class InstallableFragment : Fragment() { | |||
| 297 | return@registerForActivityResult | 290 | return@registerForActivityResult |
| 298 | } | 291 | } |
| 299 | 292 | ||
| 300 | IndeterminateProgressDialogFragment.newInstance( | 293 | ProgressDialogFragment.newInstance( |
| 301 | requireActivity(), | 294 | requireActivity(), |
| 302 | R.string.save_files_exporting, | 295 | R.string.save_files_exporting, |
| 303 | false | 296 | false |
| 304 | ) { | 297 | ) { _, _ -> |
| 305 | val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") | 298 | val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") |
| 306 | cacheSaveDir.mkdir() | 299 | cacheSaveDir.mkdir() |
| 307 | 300 | ||
| @@ -338,6 +331,6 @@ class InstallableFragment : Fragment() { | |||
| 338 | TaskState.Completed -> getString(R.string.export_success) | 331 | TaskState.Completed -> getString(R.string.export_success) |
| 339 | TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) | 332 | TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) |
| 340 | } | 333 | } |
| 341 | }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) | 334 | }.show(parentFragmentManager, ProgressDialogFragment.TAG) |
| 342 | } | 335 | } |
| 343 | } | 336 | } |
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/ProgressDialogFragment.kt index 8847e5531..d201cb80c 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/ProgressDialogFragment.kt | |||
| @@ -23,11 +23,13 @@ import org.yuzu.yuzu_emu.R | |||
| 23 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 23 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 24 | import org.yuzu.yuzu_emu.model.TaskViewModel | 24 | import org.yuzu.yuzu_emu.model.TaskViewModel |
| 25 | 25 | ||
| 26 | class IndeterminateProgressDialogFragment : DialogFragment() { | 26 | class ProgressDialogFragment : DialogFragment() { |
| 27 | private val taskViewModel: TaskViewModel by activityViewModels() | 27 | private val taskViewModel: TaskViewModel by activityViewModels() |
| 28 | 28 | ||
| 29 | private lateinit var binding: DialogProgressBarBinding | 29 | private lateinit var binding: DialogProgressBarBinding |
| 30 | 30 | ||
| 31 | private val PROGRESS_BAR_RESOLUTION = 1000 | ||
| 32 | |||
| 31 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | 33 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
| 32 | val titleId = requireArguments().getInt(TITLE) | 34 | val titleId = requireArguments().getInt(TITLE) |
| 33 | val cancellable = requireArguments().getBoolean(CANCELLABLE) | 35 | val cancellable = requireArguments().getBoolean(CANCELLABLE) |
| @@ -61,6 +63,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||
| 61 | 63 | ||
| 62 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 64 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 63 | super.onViewCreated(view, savedInstanceState) | 65 | super.onViewCreated(view, savedInstanceState) |
| 66 | binding.message.isSelected = true | ||
| 64 | viewLifecycleOwner.lifecycleScope.apply { | 67 | viewLifecycleOwner.lifecycleScope.apply { |
| 65 | launch { | 68 | launch { |
| 66 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 69 | repeatOnLifecycle(Lifecycle.State.CREATED) { |
| @@ -97,6 +100,35 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||
| 97 | } | 100 | } |
| 98 | } | 101 | } |
| 99 | } | 102 | } |
| 103 | launch { | ||
| 104 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 105 | taskViewModel.progress.collect { | ||
| 106 | if (it != 0.0) { | ||
| 107 | binding.progressBar.apply { | ||
| 108 | isIndeterminate = false | ||
| 109 | progress = ( | ||
| 110 | (it / taskViewModel.maxProgress.value) * | ||
| 111 | PROGRESS_BAR_RESOLUTION | ||
| 112 | ).toInt() | ||
| 113 | min = 0 | ||
| 114 | max = PROGRESS_BAR_RESOLUTION | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
| 120 | launch { | ||
| 121 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 122 | taskViewModel.message.collect { | ||
| 123 | if (it.isEmpty()) { | ||
| 124 | binding.message.visibility = View.GONE | ||
| 125 | } else { | ||
| 126 | binding.message.visibility = View.VISIBLE | ||
| 127 | binding.message.text = it | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 100 | } | 132 | } |
| 101 | } | 133 | } |
| 102 | 134 | ||
| @@ -108,6 +140,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||
| 108 | val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE) | 140 | val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE) |
| 109 | negativeButton.setOnClickListener { | 141 | negativeButton.setOnClickListener { |
| 110 | alertDialog.setTitle(getString(R.string.cancelling)) | 142 | alertDialog.setTitle(getString(R.string.cancelling)) |
| 143 | binding.progressBar.isIndeterminate = true | ||
| 111 | taskViewModel.setCancelled(true) | 144 | taskViewModel.setCancelled(true) |
| 112 | } | 145 | } |
| 113 | } | 146 | } |
| @@ -122,9 +155,12 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||
| 122 | activity: FragmentActivity, | 155 | activity: FragmentActivity, |
| 123 | titleId: Int, | 156 | titleId: Int, |
| 124 | cancellable: Boolean = false, | 157 | cancellable: Boolean = false, |
| 125 | task: suspend () -> Any | 158 | task: suspend ( |
| 126 | ): IndeterminateProgressDialogFragment { | 159 | progressCallback: (max: Long, progress: Long) -> Boolean, |
| 127 | val dialog = IndeterminateProgressDialogFragment() | 160 | messageCallback: (message: String) -> Unit |
| 161 | ) -> Any | ||
| 162 | ): ProgressDialogFragment { | ||
| 163 | val dialog = ProgressDialogFragment() | ||
| 128 | val args = Bundle() | 164 | val args = Bundle() |
| 129 | ViewModelProvider(activity)[TaskViewModel::class.java].task = task | 165 | ViewModelProvider(activity)[TaskViewModel::class.java].task = task |
| 130 | args.putInt(TITLE, titleId) | 166 | args.putInt(TITLE, titleId) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt index 64b295fbd..20b10b1a0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt | |||
| @@ -136,14 +136,14 @@ class SearchFragment : Fragment() { | |||
| 136 | baseList.filter { | 136 | baseList.filter { |
| 137 | val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L) | 137 | val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L) |
| 138 | lastPlayedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000) | 138 | lastPlayedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000) |
| 139 | } | 139 | }.sortedByDescending { preferences.getLong(it.keyLastPlayedTime, 0L) } |
| 140 | } | 140 | } |
| 141 | 141 | ||
| 142 | R.id.chip_recently_added -> { | 142 | R.id.chip_recently_added -> { |
| 143 | baseList.filter { | 143 | baseList.filter { |
| 144 | val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L) | 144 | val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L) |
| 145 | addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000) | 145 | addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000) |
| 146 | } | 146 | }.sortedByDescending { preferences.getLong(it.keyAddedToLibraryTime, 0L) } |
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | R.id.chip_homebrew -> baseList.filter { it.isHomebrew } | 149 | R.id.chip_homebrew -> baseList.filter { it.isHomebrew } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Addon.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Addon.kt deleted file mode 100644 index ed79a8b02..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Addon.kt +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 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 | data class Addon( | ||
| 7 | var enabled: Boolean, | ||
| 8 | val title: String, | ||
| 9 | val version: String | ||
| 10 | ) | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt index 075252f5b..b9c8e49ca 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt | |||
| @@ -15,8 +15,8 @@ import org.yuzu.yuzu_emu.utils.NativeConfig | |||
| 15 | import java.util.concurrent.atomic.AtomicBoolean | 15 | import java.util.concurrent.atomic.AtomicBoolean |
| 16 | 16 | ||
| 17 | class AddonViewModel : ViewModel() { | 17 | class AddonViewModel : ViewModel() { |
| 18 | private val _addonList = MutableStateFlow(mutableListOf<Addon>()) | 18 | private val _patchList = MutableStateFlow(mutableListOf<Patch>()) |
| 19 | val addonList get() = _addonList.asStateFlow() | 19 | val addonList get() = _patchList.asStateFlow() |
| 20 | 20 | ||
| 21 | private val _showModInstallPicker = MutableStateFlow(false) | 21 | private val _showModInstallPicker = MutableStateFlow(false) |
| 22 | val showModInstallPicker get() = _showModInstallPicker.asStateFlow() | 22 | val showModInstallPicker get() = _showModInstallPicker.asStateFlow() |
| @@ -24,6 +24,9 @@ class AddonViewModel : ViewModel() { | |||
| 24 | private val _showModNoticeDialog = MutableStateFlow(false) | 24 | private val _showModNoticeDialog = MutableStateFlow(false) |
| 25 | val showModNoticeDialog get() = _showModNoticeDialog.asStateFlow() | 25 | val showModNoticeDialog get() = _showModNoticeDialog.asStateFlow() |
| 26 | 26 | ||
| 27 | private val _addonToDelete = MutableStateFlow<Patch?>(null) | ||
| 28 | val addonToDelete = _addonToDelete.asStateFlow() | ||
| 29 | |||
| 27 | var game: Game? = null | 30 | var game: Game? = null |
| 28 | 31 | ||
| 29 | private val isRefreshing = AtomicBoolean(false) | 32 | private val isRefreshing = AtomicBoolean(false) |
| @@ -40,36 +43,47 @@ class AddonViewModel : ViewModel() { | |||
| 40 | isRefreshing.set(true) | 43 | isRefreshing.set(true) |
| 41 | viewModelScope.launch { | 44 | viewModelScope.launch { |
| 42 | withContext(Dispatchers.IO) { | 45 | withContext(Dispatchers.IO) { |
| 43 | val addonList = mutableListOf<Addon>() | 46 | val patchList = ( |
| 44 | val disabledAddons = NativeConfig.getDisabledAddons(game!!.programId) | 47 | NativeLibrary.getPatchesForFile(game!!.path, game!!.programId) |
| 45 | NativeLibrary.getAddonsForFile(game!!.path, game!!.programId)?.forEach { | 48 | ?: emptyArray() |
| 46 | val name = it.first.replace("[D] ", "") | 49 | ).toMutableList() |
| 47 | addonList.add(Addon(!disabledAddons.contains(name), name, it.second)) | 50 | patchList.sortBy { it.name } |
| 48 | } | 51 | _patchList.value = patchList |
| 49 | addonList.sortBy { it.title } | ||
| 50 | _addonList.value = addonList | ||
| 51 | isRefreshing.set(false) | 52 | isRefreshing.set(false) |
| 52 | } | 53 | } |
| 53 | } | 54 | } |
| 54 | } | 55 | } |
| 55 | 56 | ||
| 57 | fun setAddonToDelete(patch: Patch?) { | ||
| 58 | _addonToDelete.value = patch | ||
| 59 | } | ||
| 60 | |||
| 61 | fun onDeleteAddon(patch: Patch) { | ||
| 62 | when (PatchType.from(patch.type)) { | ||
| 63 | PatchType.Update -> NativeLibrary.removeUpdate(patch.programId) | ||
| 64 | PatchType.DLC -> NativeLibrary.removeDLC(patch.programId) | ||
| 65 | PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name) | ||
| 66 | } | ||
| 67 | refreshAddons() | ||
| 68 | } | ||
| 69 | |||
| 56 | fun onCloseAddons() { | 70 | fun onCloseAddons() { |
| 57 | if (_addonList.value.isEmpty()) { | 71 | if (_patchList.value.isEmpty()) { |
| 58 | return | 72 | return |
| 59 | } | 73 | } |
| 60 | 74 | ||
| 61 | NativeConfig.setDisabledAddons( | 75 | NativeConfig.setDisabledAddons( |
| 62 | game!!.programId, | 76 | game!!.programId, |
| 63 | _addonList.value.mapNotNull { | 77 | _patchList.value.mapNotNull { |
| 64 | if (it.enabled) { | 78 | if (it.enabled) { |
| 65 | null | 79 | null |
| 66 | } else { | 80 | } else { |
| 67 | it.title | 81 | it.name |
| 68 | } | 82 | } |
| 69 | }.toTypedArray() | 83 | }.toTypedArray() |
| 70 | ) | 84 | ) |
| 71 | NativeConfig.saveGlobalConfig() | 85 | NativeConfig.saveGlobalConfig() |
| 72 | _addonList.value.clear() | 86 | _patchList.value.clear() |
| 73 | game = null | 87 | game = null |
| 74 | } | 88 | } |
| 75 | 89 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/InstallResult.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/InstallResult.kt new file mode 100644 index 000000000..0c3cd0521 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/InstallResult.kt | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | enum class InstallResult(val int: Int) { | ||
| 7 | Success(0), | ||
| 8 | Overwrite(1), | ||
| 9 | Failure(2), | ||
| 10 | BaseInstallAttempted(3); | ||
| 11 | |||
| 12 | companion object { | ||
| 13 | fun from(int: Int): InstallResult = entries.firstOrNull { it.int == int } ?: Success | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt new file mode 100644 index 000000000..25cb9e365 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt | |||
| @@ -0,0 +1,16 @@ | |||
| 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.annotation.Keep | ||
| 7 | |||
| 8 | @Keep | ||
| 9 | data class Patch( | ||
| 10 | var enabled: Boolean, | ||
| 11 | val name: String, | ||
| 12 | val version: String, | ||
| 13 | val type: Int, | ||
| 14 | val programId: String, | ||
| 15 | val titleId: String | ||
| 16 | ) | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt new file mode 100644 index 000000000..e9a54162b --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | enum class PatchType(val int: Int) { | ||
| 7 | Update(0), | ||
| 8 | DLC(1), | ||
| 9 | Mod(2); | ||
| 10 | |||
| 11 | companion object { | ||
| 12 | fun from(int: Int): PatchType = entries.firstOrNull { it.int == int } ?: Update | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt index e59c95733..4361eb972 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt | |||
| @@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope | |||
| 8 | import kotlinx.coroutines.Dispatchers | 8 | import kotlinx.coroutines.Dispatchers |
| 9 | import kotlinx.coroutines.flow.MutableStateFlow | 9 | import kotlinx.coroutines.flow.MutableStateFlow |
| 10 | import kotlinx.coroutines.flow.StateFlow | 10 | import kotlinx.coroutines.flow.StateFlow |
| 11 | import kotlinx.coroutines.flow.asStateFlow | ||
| 11 | import kotlinx.coroutines.launch | 12 | import kotlinx.coroutines.launch |
| 12 | 13 | ||
| 13 | class TaskViewModel : ViewModel() { | 14 | class TaskViewModel : ViewModel() { |
| @@ -23,13 +24,28 @@ class TaskViewModel : ViewModel() { | |||
| 23 | val cancelled: StateFlow<Boolean> get() = _cancelled | 24 | val cancelled: StateFlow<Boolean> get() = _cancelled |
| 24 | private val _cancelled = MutableStateFlow(false) | 25 | private val _cancelled = MutableStateFlow(false) |
| 25 | 26 | ||
| 26 | lateinit var task: suspend () -> Any | 27 | private val _progress = MutableStateFlow(0.0) |
| 28 | val progress = _progress.asStateFlow() | ||
| 29 | |||
| 30 | private val _maxProgress = MutableStateFlow(0.0) | ||
| 31 | val maxProgress = _maxProgress.asStateFlow() | ||
| 32 | |||
| 33 | private val _message = MutableStateFlow("") | ||
| 34 | val message = _message.asStateFlow() | ||
| 35 | |||
| 36 | lateinit var task: suspend ( | ||
| 37 | progressCallback: (max: Long, progress: Long) -> Boolean, | ||
| 38 | messageCallback: (message: String) -> Unit | ||
| 39 | ) -> Any | ||
| 27 | 40 | ||
| 28 | fun clear() { | 41 | fun clear() { |
| 29 | _result.value = Any() | 42 | _result.value = Any() |
| 30 | _isComplete.value = false | 43 | _isComplete.value = false |
| 31 | _isRunning.value = false | 44 | _isRunning.value = false |
| 32 | _cancelled.value = false | 45 | _cancelled.value = false |
| 46 | _progress.value = 0.0 | ||
| 47 | _maxProgress.value = 0.0 | ||
| 48 | _message.value = "" | ||
| 33 | } | 49 | } |
| 34 | 50 | ||
| 35 | fun setCancelled(value: Boolean) { | 51 | fun setCancelled(value: Boolean) { |
| @@ -43,7 +59,16 @@ class TaskViewModel : ViewModel() { | |||
| 43 | _isRunning.value = true | 59 | _isRunning.value = true |
| 44 | 60 | ||
| 45 | viewModelScope.launch(Dispatchers.IO) { | 61 | viewModelScope.launch(Dispatchers.IO) { |
| 46 | val res = task() | 62 | val res = task( |
| 63 | { max, progress -> | ||
| 64 | _maxProgress.value = max.toDouble() | ||
| 65 | _progress.value = progress.toDouble() | ||
| 66 | return@task cancelled.value | ||
| 67 | }, | ||
| 68 | { message -> | ||
| 69 | _message.value = message | ||
| 70 | } | ||
| 71 | ) | ||
| 47 | _result.value = res | 72 | _result.value = res |
| 48 | _isComplete.value = true | 73 | _isComplete.value = true |
| 49 | _isRunning.value = false | 74 | _isRunning.value = false |
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 644289e25..c2cc29961 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 | |||
| @@ -38,12 +38,13 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity | |||
| 38 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding | 38 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding |
| 39 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 39 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 40 | import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment | 40 | import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment |
| 41 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 41 | import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment |
| 42 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 42 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 43 | import org.yuzu.yuzu_emu.model.AddonViewModel | 43 | import org.yuzu.yuzu_emu.model.AddonViewModel |
| 44 | import org.yuzu.yuzu_emu.model.DriverViewModel | 44 | import org.yuzu.yuzu_emu.model.DriverViewModel |
| 45 | import org.yuzu.yuzu_emu.model.GamesViewModel | 45 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 46 | import org.yuzu.yuzu_emu.model.HomeViewModel | 46 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 47 | import org.yuzu.yuzu_emu.model.InstallResult | ||
| 47 | import org.yuzu.yuzu_emu.model.TaskState | 48 | import org.yuzu.yuzu_emu.model.TaskState |
| 48 | import org.yuzu.yuzu_emu.model.TaskViewModel | 49 | import org.yuzu.yuzu_emu.model.TaskViewModel |
| 49 | import org.yuzu.yuzu_emu.utils.* | 50 | import org.yuzu.yuzu_emu.utils.* |
| @@ -369,26 +370,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 369 | return@registerForActivityResult | 370 | return@registerForActivityResult |
| 370 | } | 371 | } |
| 371 | 372 | ||
| 372 | val inputZip = contentResolver.openInputStream(result) | ||
| 373 | if (inputZip == null) { | ||
| 374 | Toast.makeText( | ||
| 375 | applicationContext, | ||
| 376 | getString(R.string.fatal_error), | ||
| 377 | Toast.LENGTH_LONG | ||
| 378 | ).show() | ||
| 379 | return@registerForActivityResult | ||
| 380 | } | ||
| 381 | |||
| 382 | val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") } | 373 | val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") } |
| 383 | 374 | ||
| 384 | val firmwarePath = | 375 | val firmwarePath = |
| 385 | File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") | 376 | File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") |
| 386 | val cacheFirmwareDir = File("${cacheDir.path}/registered/") | 377 | val cacheFirmwareDir = File("${cacheDir.path}/registered/") |
| 387 | 378 | ||
| 388 | val task: () -> Any = { | 379 | ProgressDialogFragment.newInstance( |
| 380 | this, | ||
| 381 | R.string.firmware_installing | ||
| 382 | ) { progressCallback, _ -> | ||
| 389 | var messageToShow: Any | 383 | var messageToShow: Any |
| 390 | try { | 384 | try { |
| 391 | FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir) | 385 | FileUtil.unzipToInternalStorage( |
| 386 | result.toString(), | ||
| 387 | cacheFirmwareDir, | ||
| 388 | progressCallback | ||
| 389 | ) | ||
| 392 | val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 | 390 | val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 |
| 393 | val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 | 391 | val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 |
| 394 | messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { | 392 | messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { |
| @@ -404,18 +402,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 404 | getString(R.string.save_file_imported_success) | 402 | getString(R.string.save_file_imported_success) |
| 405 | } | 403 | } |
| 406 | } catch (e: Exception) { | 404 | } catch (e: Exception) { |
| 405 | Log.error("[MainActivity] Firmware install failed - ${e.message}") | ||
| 407 | messageToShow = getString(R.string.fatal_error) | 406 | messageToShow = getString(R.string.fatal_error) |
| 408 | } finally { | 407 | } finally { |
| 409 | cacheFirmwareDir.deleteRecursively() | 408 | cacheFirmwareDir.deleteRecursively() |
| 410 | } | 409 | } |
| 411 | messageToShow | 410 | messageToShow |
| 412 | } | 411 | }.show(supportFragmentManager, ProgressDialogFragment.TAG) |
| 413 | |||
| 414 | IndeterminateProgressDialogFragment.newInstance( | ||
| 415 | this, | ||
| 416 | R.string.firmware_installing, | ||
| 417 | task = task | ||
| 418 | ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | ||
| 419 | } | 412 | } |
| 420 | 413 | ||
| 421 | val getAmiiboKey = | 414 | val getAmiiboKey = |
| @@ -474,11 +467,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 474 | return@registerForActivityResult | 467 | return@registerForActivityResult |
| 475 | } | 468 | } |
| 476 | 469 | ||
| 477 | IndeterminateProgressDialogFragment.newInstance( | 470 | ProgressDialogFragment.newInstance( |
| 478 | this@MainActivity, | 471 | this@MainActivity, |
| 479 | R.string.verifying_content, | 472 | R.string.verifying_content, |
| 480 | false | 473 | false |
| 481 | ) { | 474 | ) { _, _ -> |
| 482 | var updatesMatchProgram = true | 475 | var updatesMatchProgram = true |
| 483 | for (document in documents) { | 476 | for (document in documents) { |
| 484 | val valid = NativeLibrary.doesUpdateMatchProgram( | 477 | val valid = NativeLibrary.doesUpdateMatchProgram( |
| @@ -501,44 +494,42 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 501 | positiveAction = { homeViewModel.setContentToInstall(documents) } | 494 | positiveAction = { homeViewModel.setContentToInstall(documents) } |
| 502 | ) | 495 | ) |
| 503 | } | 496 | } |
| 504 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 497 | }.show(supportFragmentManager, ProgressDialogFragment.TAG) |
| 505 | } | 498 | } |
| 506 | 499 | ||
| 507 | private fun installContent(documents: List<Uri>) { | 500 | private fun installContent(documents: List<Uri>) { |
| 508 | IndeterminateProgressDialogFragment.newInstance( | 501 | ProgressDialogFragment.newInstance( |
| 509 | this@MainActivity, | 502 | this@MainActivity, |
| 510 | R.string.installing_game_content | 503 | R.string.installing_game_content |
| 511 | ) { | 504 | ) { progressCallback, messageCallback -> |
| 512 | var installSuccess = 0 | 505 | var installSuccess = 0 |
| 513 | var installOverwrite = 0 | 506 | var installOverwrite = 0 |
| 514 | var errorBaseGame = 0 | 507 | var errorBaseGame = 0 |
| 515 | var errorExtension = 0 | 508 | var error = 0 |
| 516 | var errorOther = 0 | ||
| 517 | documents.forEach { | 509 | documents.forEach { |
| 510 | messageCallback.invoke(FileUtil.getFilename(it)) | ||
| 518 | when ( | 511 | when ( |
| 519 | NativeLibrary.installFileToNand( | 512 | InstallResult.from( |
| 520 | it.toString(), | 513 | NativeLibrary.installFileToNand( |
| 521 | FileUtil.getExtension(it) | 514 | it.toString(), |
| 515 | progressCallback | ||
| 516 | ) | ||
| 522 | ) | 517 | ) |
| 523 | ) { | 518 | ) { |
| 524 | NativeLibrary.InstallFileToNandResult.Success -> { | 519 | InstallResult.Success -> { |
| 525 | installSuccess += 1 | 520 | installSuccess += 1 |
| 526 | } | 521 | } |
| 527 | 522 | ||
| 528 | NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { | 523 | InstallResult.Overwrite -> { |
| 529 | installOverwrite += 1 | 524 | installOverwrite += 1 |
| 530 | } | 525 | } |
| 531 | 526 | ||
| 532 | NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { | 527 | InstallResult.BaseInstallAttempted -> { |
| 533 | errorBaseGame += 1 | 528 | errorBaseGame += 1 |
| 534 | } | 529 | } |
| 535 | 530 | ||
| 536 | NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { | 531 | InstallResult.Failure -> { |
| 537 | errorExtension += 1 | 532 | error += 1 |
| 538 | } | ||
| 539 | |||
| 540 | else -> { | ||
| 541 | errorOther += 1 | ||
| 542 | } | 533 | } |
| 543 | } | 534 | } |
| 544 | } | 535 | } |
| @@ -565,7 +556,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 565 | ) | 556 | ) |
| 566 | installResult.append(separator) | 557 | installResult.append(separator) |
| 567 | } | 558 | } |
| 568 | val errorTotal: Int = errorBaseGame + errorExtension + errorOther | 559 | val errorTotal: Int = errorBaseGame + error |
| 569 | if (errorTotal > 0) { | 560 | if (errorTotal > 0) { |
| 570 | installResult.append(separator) | 561 | installResult.append(separator) |
| 571 | installResult.append( | 562 | installResult.append( |
| @@ -582,14 +573,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 582 | ) | 573 | ) |
| 583 | installResult.append(separator) | 574 | installResult.append(separator) |
| 584 | } | 575 | } |
| 585 | if (errorExtension > 0) { | 576 | if (error > 0) { |
| 586 | installResult.append(separator) | ||
| 587 | installResult.append( | ||
| 588 | getString(R.string.install_game_content_failure_file_extension) | ||
| 589 | ) | ||
| 590 | installResult.append(separator) | ||
| 591 | } | ||
| 592 | if (errorOther > 0) { | ||
| 593 | installResult.append( | 577 | installResult.append( |
| 594 | getString(R.string.install_game_content_failure_description) | 578 | getString(R.string.install_game_content_failure_description) |
| 595 | ) | 579 | ) |
| @@ -608,7 +592,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 608 | descriptionString = installResult.toString().trim() | 592 | descriptionString = installResult.toString().trim() |
| 609 | ) | 593 | ) |
| 610 | } | 594 | } |
| 611 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 595 | }.show(supportFragmentManager, ProgressDialogFragment.TAG) |
| 612 | } | 596 | } |
| 613 | 597 | ||
| 614 | val exportUserData = registerForActivityResult( | 598 | val exportUserData = registerForActivityResult( |
| @@ -618,16 +602,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 618 | return@registerForActivityResult | 602 | return@registerForActivityResult |
| 619 | } | 603 | } |
| 620 | 604 | ||
| 621 | IndeterminateProgressDialogFragment.newInstance( | 605 | ProgressDialogFragment.newInstance( |
| 622 | this, | 606 | this, |
| 623 | R.string.exporting_user_data, | 607 | R.string.exporting_user_data, |
| 624 | true | 608 | true |
| 625 | ) { | 609 | ) { progressCallback, _ -> |
| 626 | val zipResult = FileUtil.zipFromInternalStorage( | 610 | val zipResult = FileUtil.zipFromInternalStorage( |
| 627 | File(DirectoryInitialization.userDirectory!!), | 611 | File(DirectoryInitialization.userDirectory!!), |
| 628 | DirectoryInitialization.userDirectory!!, | 612 | DirectoryInitialization.userDirectory!!, |
| 629 | BufferedOutputStream(contentResolver.openOutputStream(result)), | 613 | BufferedOutputStream(contentResolver.openOutputStream(result)), |
| 630 | taskViewModel.cancelled, | 614 | progressCallback, |
| 631 | compression = false | 615 | compression = false |
| 632 | ) | 616 | ) |
| 633 | return@newInstance when (zipResult) { | 617 | return@newInstance when (zipResult) { |
| @@ -635,7 +619,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 635 | TaskState.Failed -> R.string.export_failed | 619 | TaskState.Failed -> R.string.export_failed |
| 636 | TaskState.Cancelled -> R.string.user_data_export_cancelled | 620 | TaskState.Cancelled -> R.string.user_data_export_cancelled |
| 637 | } | 621 | } |
| 638 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 622 | }.show(supportFragmentManager, ProgressDialogFragment.TAG) |
| 639 | } | 623 | } |
| 640 | 624 | ||
| 641 | val importUserData = | 625 | val importUserData = |
| @@ -644,10 +628,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 644 | return@registerForActivityResult | 628 | return@registerForActivityResult |
| 645 | } | 629 | } |
| 646 | 630 | ||
| 647 | IndeterminateProgressDialogFragment.newInstance( | 631 | ProgressDialogFragment.newInstance( |
| 648 | this, | 632 | this, |
| 649 | R.string.importing_user_data | 633 | R.string.importing_user_data |
| 650 | ) { | 634 | ) { progressCallback, _ -> |
| 651 | val checkStream = | 635 | val checkStream = |
| 652 | ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result))) | 636 | ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result))) |
| 653 | var isYuzuBackup = false | 637 | var isYuzuBackup = false |
| @@ -676,8 +660,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 676 | // Copy archive to internal storage | 660 | // Copy archive to internal storage |
| 677 | try { | 661 | try { |
| 678 | FileUtil.unzipToInternalStorage( | 662 | FileUtil.unzipToInternalStorage( |
| 679 | BufferedInputStream(contentResolver.openInputStream(result)), | 663 | result.toString(), |
| 680 | File(DirectoryInitialization.userDirectory!!) | 664 | File(DirectoryInitialization.userDirectory!!), |
| 665 | progressCallback | ||
| 681 | ) | 666 | ) |
| 682 | } catch (e: Exception) { | 667 | } catch (e: Exception) { |
| 683 | return@newInstance MessageDialogFragment.newInstance( | 668 | return@newInstance MessageDialogFragment.newInstance( |
| @@ -694,6 +679,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 694 | driverViewModel.reloadDriverData() | 679 | driverViewModel.reloadDriverData() |
| 695 | 680 | ||
| 696 | return@newInstance getString(R.string.user_data_import_success) | 681 | return@newInstance getString(R.string.user_data_import_success) |
| 697 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 682 | }.show(supportFragmentManager, ProgressDialogFragment.TAG) |
| 698 | } | 683 | } |
| 699 | } | 684 | } |
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 b54a19c65..fc2339f5a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt | |||
| @@ -7,7 +7,6 @@ import android.database.Cursor | |||
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import android.provider.DocumentsContract | 8 | import android.provider.DocumentsContract |
| 9 | import androidx.documentfile.provider.DocumentFile | 9 | import androidx.documentfile.provider.DocumentFile |
| 10 | import kotlinx.coroutines.flow.StateFlow | ||
| 11 | import java.io.BufferedInputStream | 10 | import java.io.BufferedInputStream |
| 12 | import java.io.File | 11 | import java.io.File |
| 13 | import java.io.IOException | 12 | import java.io.IOException |
| @@ -19,6 +18,7 @@ import org.yuzu.yuzu_emu.YuzuApplication | |||
| 19 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | 18 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile |
| 20 | import org.yuzu.yuzu_emu.model.TaskState | 19 | import org.yuzu.yuzu_emu.model.TaskState |
| 21 | import java.io.BufferedOutputStream | 20 | import java.io.BufferedOutputStream |
| 21 | import java.io.OutputStream | ||
| 22 | import java.lang.NullPointerException | 22 | import java.lang.NullPointerException |
| 23 | import java.nio.charset.StandardCharsets | 23 | import java.nio.charset.StandardCharsets |
| 24 | import java.util.zip.Deflater | 24 | import java.util.zip.Deflater |
| @@ -283,12 +283,34 @@ object FileUtil { | |||
| 283 | 283 | ||
| 284 | /** | 284 | /** |
| 285 | * Extracts the given zip file into the given directory. | 285 | * Extracts the given zip file into the given directory. |
| 286 | * @param path String representation of a [Uri] or a typical path delimited by '/' | ||
| 287 | * @param destDir Location to unzip the contents of [path] into | ||
| 288 | * @param progressCallback Lambda that is called with the total number of files and the current | ||
| 289 | * progress through the process. Stops execution as soon as possible if this returns true. | ||
| 286 | */ | 290 | */ |
| 287 | @Throws(SecurityException::class) | 291 | @Throws(SecurityException::class) |
| 288 | fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) { | 292 | fun unzipToInternalStorage( |
| 289 | ZipInputStream(zipStream).use { zis -> | 293 | path: String, |
| 294 | destDir: File, | ||
| 295 | progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false } | ||
| 296 | ) { | ||
| 297 | var totalEntries = 0L | ||
| 298 | ZipInputStream(getInputStream(path)).use { zis -> | ||
| 299 | var tempEntry = zis.nextEntry | ||
| 300 | while (tempEntry != null) { | ||
| 301 | tempEntry = zis.nextEntry | ||
| 302 | totalEntries++ | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | var progress = 0L | ||
| 307 | ZipInputStream(getInputStream(path)).use { zis -> | ||
| 290 | var entry: ZipEntry? = zis.nextEntry | 308 | var entry: ZipEntry? = zis.nextEntry |
| 291 | while (entry != null) { | 309 | while (entry != null) { |
| 310 | if (progressCallback.invoke(totalEntries, progress)) { | ||
| 311 | return@use | ||
| 312 | } | ||
| 313 | |||
| 292 | val newFile = File(destDir, entry.name) | 314 | val newFile = File(destDir, entry.name) |
| 293 | val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile | 315 | val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile |
| 294 | 316 | ||
| @@ -304,6 +326,7 @@ object FileUtil { | |||
| 304 | newFile.outputStream().use { fos -> zis.copyTo(fos) } | 326 | newFile.outputStream().use { fos -> zis.copyTo(fos) } |
| 305 | } | 327 | } |
| 306 | entry = zis.nextEntry | 328 | entry = zis.nextEntry |
| 329 | progress++ | ||
| 307 | } | 330 | } |
| 308 | } | 331 | } |
| 309 | } | 332 | } |
| @@ -313,14 +336,15 @@ object FileUtil { | |||
| 313 | * @param inputFile File representation of the item that will be zipped | 336 | * @param inputFile File representation of the item that will be zipped |
| 314 | * @param rootDir Directory containing the inputFile | 337 | * @param rootDir Directory containing the inputFile |
| 315 | * @param outputStream Stream where the zip file will be output | 338 | * @param outputStream Stream where the zip file will be output |
| 316 | * @param cancelled [StateFlow] that reports whether this process has been cancelled | 339 | * @param progressCallback Lambda that is called with the total number of files and the current |
| 340 | * progress through the process. Stops execution as soon as possible if this returns true. | ||
| 317 | * @param compression Disables compression if true | 341 | * @param compression Disables compression if true |
| 318 | */ | 342 | */ |
| 319 | fun zipFromInternalStorage( | 343 | fun zipFromInternalStorage( |
| 320 | inputFile: File, | 344 | inputFile: File, |
| 321 | rootDir: String, | 345 | rootDir: String, |
| 322 | outputStream: BufferedOutputStream, | 346 | outputStream: BufferedOutputStream, |
| 323 | cancelled: StateFlow<Boolean>? = null, | 347 | progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false }, |
| 324 | compression: Boolean = true | 348 | compression: Boolean = true |
| 325 | ): TaskState { | 349 | ): TaskState { |
| 326 | try { | 350 | try { |
| @@ -330,8 +354,10 @@ object FileUtil { | |||
| 330 | zos.setLevel(Deflater.NO_COMPRESSION) | 354 | zos.setLevel(Deflater.NO_COMPRESSION) |
| 331 | } | 355 | } |
| 332 | 356 | ||
| 357 | var count = 0L | ||
| 358 | val totalFiles = inputFile.walkTopDown().count().toLong() | ||
| 333 | inputFile.walkTopDown().forEach { file -> | 359 | inputFile.walkTopDown().forEach { file -> |
| 334 | if (cancelled?.value == true) { | 360 | if (progressCallback.invoke(totalFiles, count)) { |
| 335 | return TaskState.Cancelled | 361 | return TaskState.Cancelled |
| 336 | } | 362 | } |
| 337 | 363 | ||
| @@ -343,6 +369,7 @@ object FileUtil { | |||
| 343 | if (file.isFile) { | 369 | if (file.isFile) { |
| 344 | file.inputStream().use { fis -> fis.copyTo(zos) } | 370 | file.inputStream().use { fis -> fis.copyTo(zos) } |
| 345 | } | 371 | } |
| 372 | count++ | ||
| 346 | } | 373 | } |
| 347 | } | 374 | } |
| 348 | } | 375 | } |
| @@ -356,9 +383,14 @@ object FileUtil { | |||
| 356 | /** | 383 | /** |
| 357 | * Helper function that copies the contents of a DocumentFile folder into a [File] | 384 | * Helper function that copies the contents of a DocumentFile folder into a [File] |
| 358 | * @param file [File] representation of the folder to copy into | 385 | * @param file [File] representation of the folder to copy into |
| 386 | * @param progressCallback Lambda that is called with the total number of files and the current | ||
| 387 | * progress through the process. Stops execution as soon as possible if this returns true. | ||
| 359 | * @throws IllegalStateException Fails when trying to copy a folder into a file and vice versa | 388 | * @throws IllegalStateException Fails when trying to copy a folder into a file and vice versa |
| 360 | */ | 389 | */ |
| 361 | fun DocumentFile.copyFilesTo(file: File) { | 390 | fun DocumentFile.copyFilesTo( |
| 391 | file: File, | ||
| 392 | progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false } | ||
| 393 | ) { | ||
| 362 | file.mkdirs() | 394 | file.mkdirs() |
| 363 | if (!this.isDirectory || !file.isDirectory) { | 395 | if (!this.isDirectory || !file.isDirectory) { |
| 364 | throw IllegalStateException( | 396 | throw IllegalStateException( |
| @@ -366,7 +398,13 @@ object FileUtil { | |||
| 366 | ) | 398 | ) |
| 367 | } | 399 | } |
| 368 | 400 | ||
| 401 | var count = 0L | ||
| 402 | val totalFiles = this.listFiles().size.toLong() | ||
| 369 | this.listFiles().forEach { | 403 | this.listFiles().forEach { |
| 404 | if (progressCallback.invoke(totalFiles, count)) { | ||
| 405 | return | ||
| 406 | } | ||
| 407 | |||
| 370 | val newFile = File(file, it.name!!) | 408 | val newFile = File(file, it.name!!) |
| 371 | if (it.isDirectory) { | 409 | if (it.isDirectory) { |
| 372 | newFile.mkdirs() | 410 | newFile.mkdirs() |
| @@ -381,6 +419,7 @@ object FileUtil { | |||
| 381 | newFile.outputStream().use { os -> bos.copyTo(os) } | 419 | newFile.outputStream().use { os -> bos.copyTo(os) } |
| 382 | } | 420 | } |
| 383 | } | 421 | } |
| 422 | count++ | ||
| 384 | } | 423 | } |
| 385 | } | 424 | } |
| 386 | 425 | ||
| @@ -427,6 +466,18 @@ object FileUtil { | |||
| 427 | } | 466 | } |
| 428 | } | 467 | } |
| 429 | 468 | ||
| 469 | fun getInputStream(path: String) = if (path.contains("content://")) { | ||
| 470 | Uri.parse(path).inputStream() | ||
| 471 | } else { | ||
| 472 | File(path).inputStream() | ||
| 473 | } | ||
| 474 | |||
| 475 | fun getOutputStream(path: String) = if (path.contains("content://")) { | ||
| 476 | Uri.parse(path).outputStream() | ||
| 477 | } else { | ||
| 478 | File(path).outputStream() | ||
| 479 | } | ||
| 480 | |||
| 430 | @Throws(IOException::class) | 481 | @Throws(IOException::class) |
| 431 | fun getStringFromFile(file: File): String = | 482 | fun getStringFromFile(file: File): String = |
| 432 | String(file.readBytes(), StandardCharsets.UTF_8) | 483 | String(file.readBytes(), StandardCharsets.UTF_8) |
| @@ -434,4 +485,19 @@ object FileUtil { | |||
| 434 | @Throws(IOException::class) | 485 | @Throws(IOException::class) |
| 435 | fun getStringFromInputStream(stream: InputStream): String = | 486 | fun getStringFromInputStream(stream: InputStream): String = |
| 436 | String(stream.readBytes(), StandardCharsets.UTF_8) | 487 | String(stream.readBytes(), StandardCharsets.UTF_8) |
| 488 | |||
| 489 | fun DocumentFile.inputStream(): InputStream = | ||
| 490 | YuzuApplication.appContext.contentResolver.openInputStream(uri)!! | ||
| 491 | |||
| 492 | fun DocumentFile.outputStream(): OutputStream = | ||
| 493 | YuzuApplication.appContext.contentResolver.openOutputStream(uri)!! | ||
| 494 | |||
| 495 | fun Uri.inputStream(): InputStream = | ||
| 496 | YuzuApplication.appContext.contentResolver.openInputStream(this)!! | ||
| 497 | |||
| 498 | fun Uri.outputStream(): OutputStream = | ||
| 499 | YuzuApplication.appContext.contentResolver.openOutputStream(this)!! | ||
| 500 | |||
| 501 | fun Uri.asDocumentFile(): DocumentFile? = | ||
| 502 | DocumentFile.fromSingleUri(YuzuApplication.appContext, this) | ||
| 437 | } | 503 | } |
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 a8f9dcc34..81212cbee 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 | |||
| @@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.net.Uri | 6 | import android.net.Uri |
| 7 | import android.os.Build | 7 | import android.os.Build |
| 8 | import java.io.BufferedInputStream | ||
| 9 | import java.io.File | 8 | import java.io.File |
| 10 | import java.io.IOException | 9 | import java.io.IOException |
| 11 | import org.yuzu.yuzu_emu.NativeLibrary | 10 | import org.yuzu.yuzu_emu.NativeLibrary |
| @@ -123,7 +122,7 @@ object GpuDriverHelper { | |||
| 123 | // Unzip the driver. | 122 | // Unzip the driver. |
| 124 | try { | 123 | try { |
| 125 | FileUtil.unzipToInternalStorage( | 124 | FileUtil.unzipToInternalStorage( |
| 126 | BufferedInputStream(copiedFile.inputStream()), | 125 | copiedFile.path, |
| 127 | File(driverInstallationPath!!) | 126 | File(driverInstallationPath!!) |
| 128 | ) | 127 | ) |
| 129 | } catch (e: SecurityException) { | 128 | } catch (e: SecurityException) { |
| @@ -156,7 +155,7 @@ object GpuDriverHelper { | |||
| 156 | // Unzip the driver to the private installation directory | 155 | // Unzip the driver to the private installation directory |
| 157 | try { | 156 | try { |
| 158 | FileUtil.unzipToInternalStorage( | 157 | FileUtil.unzipToInternalStorage( |
| 159 | BufferedInputStream(driver.inputStream()), | 158 | driver.path, |
| 160 | File(driverInstallationPath!!) | 159 | File(driverInstallationPath!!) |
| 161 | ) | 160 | ) |
| 162 | } catch (e: SecurityException) { | 161 | } catch (e: SecurityException) { |
diff --git a/src/android/app/src/main/jni/android_common/android_common.cpp b/src/android/app/src/main/jni/android_common/android_common.cpp index 1e884ffdd..7018a52af 100644 --- a/src/android/app/src/main/jni/android_common/android_common.cpp +++ b/src/android/app/src/main/jni/android_common/android_common.cpp | |||
| @@ -42,3 +42,19 @@ double GetJDouble(JNIEnv* env, jobject jdouble) { | |||
| 42 | jobject ToJDouble(JNIEnv* env, double value) { | 42 | jobject ToJDouble(JNIEnv* env, double value) { |
| 43 | return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value); | 43 | return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value); |
| 44 | } | 44 | } |
| 45 | |||
| 46 | s32 GetJInteger(JNIEnv* env, jobject jinteger) { | ||
| 47 | return env->GetIntField(jinteger, IDCache::GetIntegerValueField()); | ||
| 48 | } | ||
| 49 | |||
| 50 | jobject ToJInteger(JNIEnv* env, s32 value) { | ||
| 51 | return env->NewObject(IDCache::GetIntegerClass(), IDCache::GetIntegerConstructor(), value); | ||
| 52 | } | ||
| 53 | |||
| 54 | bool GetJBoolean(JNIEnv* env, jobject jboolean) { | ||
| 55 | return env->GetBooleanField(jboolean, IDCache::GetBooleanValueField()); | ||
| 56 | } | ||
| 57 | |||
| 58 | jobject ToJBoolean(JNIEnv* env, bool value) { | ||
| 59 | return env->NewObject(IDCache::GetBooleanClass(), IDCache::GetBooleanConstructor(), value); | ||
| 60 | } | ||
diff --git a/src/android/app/src/main/jni/android_common/android_common.h b/src/android/app/src/main/jni/android_common/android_common.h index 8eb803e1b..29a338c0a 100644 --- a/src/android/app/src/main/jni/android_common/android_common.h +++ b/src/android/app/src/main/jni/android_common/android_common.h | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include <string> | 6 | #include <string> |
| 7 | 7 | ||
| 8 | #include <jni.h> | 8 | #include <jni.h> |
| 9 | #include "common/common_types.h" | ||
| 9 | 10 | ||
| 10 | std::string GetJString(JNIEnv* env, jstring jstr); | 11 | std::string GetJString(JNIEnv* env, jstring jstr); |
| 11 | jstring ToJString(JNIEnv* env, std::string_view str); | 12 | jstring ToJString(JNIEnv* env, std::string_view str); |
| @@ -13,3 +14,9 @@ jstring ToJString(JNIEnv* env, std::u16string_view str); | |||
| 13 | 14 | ||
| 14 | double GetJDouble(JNIEnv* env, jobject jdouble); | 15 | double GetJDouble(JNIEnv* env, jobject jdouble); |
| 15 | jobject ToJDouble(JNIEnv* env, double value); | 16 | jobject ToJDouble(JNIEnv* env, double value); |
| 17 | |||
| 18 | s32 GetJInteger(JNIEnv* env, jobject jinteger); | ||
| 19 | jobject ToJInteger(JNIEnv* env, s32 value); | ||
| 20 | |||
| 21 | bool GetJBoolean(JNIEnv* env, jobject jboolean); | ||
| 22 | jobject ToJBoolean(JNIEnv* env, bool value); | ||
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index c79ad7d76..96f2ad3d4 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp | |||
| @@ -43,10 +43,27 @@ static jfieldID s_overlay_control_data_landscape_position_field; | |||
| 43 | static jfieldID s_overlay_control_data_portrait_position_field; | 43 | static jfieldID s_overlay_control_data_portrait_position_field; |
| 44 | static jfieldID s_overlay_control_data_foldable_position_field; | 44 | static jfieldID s_overlay_control_data_foldable_position_field; |
| 45 | 45 | ||
| 46 | static jclass s_patch_class; | ||
| 47 | static jmethodID s_patch_constructor; | ||
| 48 | static jfieldID s_patch_enabled_field; | ||
| 49 | static jfieldID s_patch_name_field; | ||
| 50 | static jfieldID s_patch_version_field; | ||
| 51 | static jfieldID s_patch_type_field; | ||
| 52 | static jfieldID s_patch_program_id_field; | ||
| 53 | static jfieldID s_patch_title_id_field; | ||
| 54 | |||
| 46 | static jclass s_double_class; | 55 | static jclass s_double_class; |
| 47 | static jmethodID s_double_constructor; | 56 | static jmethodID s_double_constructor; |
| 48 | static jfieldID s_double_value_field; | 57 | static jfieldID s_double_value_field; |
| 49 | 58 | ||
| 59 | static jclass s_integer_class; | ||
| 60 | static jmethodID s_integer_constructor; | ||
| 61 | static jfieldID s_integer_value_field; | ||
| 62 | |||
| 63 | static jclass s_boolean_class; | ||
| 64 | static jmethodID s_boolean_constructor; | ||
| 65 | static jfieldID s_boolean_value_field; | ||
| 66 | |||
| 50 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | 67 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; |
| 51 | 68 | ||
| 52 | namespace IDCache { | 69 | namespace IDCache { |
| @@ -186,6 +203,38 @@ jfieldID GetOverlayControlDataFoldablePositionField() { | |||
| 186 | return s_overlay_control_data_foldable_position_field; | 203 | return s_overlay_control_data_foldable_position_field; |
| 187 | } | 204 | } |
| 188 | 205 | ||
| 206 | jclass GetPatchClass() { | ||
| 207 | return s_patch_class; | ||
| 208 | } | ||
| 209 | |||
| 210 | jmethodID GetPatchConstructor() { | ||
| 211 | return s_patch_constructor; | ||
| 212 | } | ||
| 213 | |||
| 214 | jfieldID GetPatchEnabledField() { | ||
| 215 | return s_patch_enabled_field; | ||
| 216 | } | ||
| 217 | |||
| 218 | jfieldID GetPatchNameField() { | ||
| 219 | return s_patch_name_field; | ||
| 220 | } | ||
| 221 | |||
| 222 | jfieldID GetPatchVersionField() { | ||
| 223 | return s_patch_version_field; | ||
| 224 | } | ||
| 225 | |||
| 226 | jfieldID GetPatchTypeField() { | ||
| 227 | return s_patch_type_field; | ||
| 228 | } | ||
| 229 | |||
| 230 | jfieldID GetPatchProgramIdField() { | ||
| 231 | return s_patch_program_id_field; | ||
| 232 | } | ||
| 233 | |||
| 234 | jfieldID GetPatchTitleIdField() { | ||
| 235 | return s_patch_title_id_field; | ||
| 236 | } | ||
| 237 | |||
| 189 | jclass GetDoubleClass() { | 238 | jclass GetDoubleClass() { |
| 190 | return s_double_class; | 239 | return s_double_class; |
| 191 | } | 240 | } |
| @@ -198,6 +247,30 @@ jfieldID GetDoubleValueField() { | |||
| 198 | return s_double_value_field; | 247 | return s_double_value_field; |
| 199 | } | 248 | } |
| 200 | 249 | ||
| 250 | jclass GetIntegerClass() { | ||
| 251 | return s_integer_class; | ||
| 252 | } | ||
| 253 | |||
| 254 | jmethodID GetIntegerConstructor() { | ||
| 255 | return s_integer_constructor; | ||
| 256 | } | ||
| 257 | |||
| 258 | jfieldID GetIntegerValueField() { | ||
| 259 | return s_integer_value_field; | ||
| 260 | } | ||
| 261 | |||
| 262 | jclass GetBooleanClass() { | ||
| 263 | return s_boolean_class; | ||
| 264 | } | ||
| 265 | |||
| 266 | jmethodID GetBooleanConstructor() { | ||
| 267 | return s_boolean_constructor; | ||
| 268 | } | ||
| 269 | |||
| 270 | jfieldID GetBooleanValueField() { | ||
| 271 | return s_boolean_value_field; | ||
| 272 | } | ||
| 273 | |||
| 201 | } // namespace IDCache | 274 | } // namespace IDCache |
| 202 | 275 | ||
| 203 | #ifdef __cplusplus | 276 | #ifdef __cplusplus |
| @@ -278,12 +351,37 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { | |||
| 278 | env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); | 351 | env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); |
| 279 | env->DeleteLocalRef(overlay_control_data_class); | 352 | env->DeleteLocalRef(overlay_control_data_class); |
| 280 | 353 | ||
| 354 | const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); | ||
| 355 | s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class)); | ||
| 356 | s_patch_constructor = env->GetMethodID( | ||
| 357 | patch_class, "<init>", | ||
| 358 | "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); | ||
| 359 | s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); | ||
| 360 | s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); | ||
| 361 | s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); | ||
| 362 | s_patch_type_field = env->GetFieldID(patch_class, "type", "I"); | ||
| 363 | s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); | ||
| 364 | s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); | ||
| 365 | env->DeleteLocalRef(patch_class); | ||
| 366 | |||
| 281 | const jclass double_class = env->FindClass("java/lang/Double"); | 367 | const jclass double_class = env->FindClass("java/lang/Double"); |
| 282 | s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class)); | 368 | s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class)); |
| 283 | s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V"); | 369 | s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V"); |
| 284 | s_double_value_field = env->GetFieldID(double_class, "value", "D"); | 370 | s_double_value_field = env->GetFieldID(double_class, "value", "D"); |
| 285 | env->DeleteLocalRef(double_class); | 371 | env->DeleteLocalRef(double_class); |
| 286 | 372 | ||
| 373 | const jclass int_class = env->FindClass("java/lang/Integer"); | ||
| 374 | s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class)); | ||
| 375 | s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V"); | ||
| 376 | s_integer_value_field = env->GetFieldID(int_class, "value", "I"); | ||
| 377 | env->DeleteLocalRef(int_class); | ||
| 378 | |||
| 379 | const jclass boolean_class = env->FindClass("java/lang/Boolean"); | ||
| 380 | s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class)); | ||
| 381 | s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V"); | ||
| 382 | s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); | ||
| 383 | env->DeleteLocalRef(boolean_class); | ||
| 384 | |||
| 287 | // Initialize Android Storage | 385 | // Initialize Android Storage |
| 288 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); | 386 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); |
| 289 | 387 | ||
| @@ -309,7 +407,10 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { | |||
| 309 | env->DeleteGlobalRef(s_string_class); | 407 | env->DeleteGlobalRef(s_string_class); |
| 310 | env->DeleteGlobalRef(s_pair_class); | 408 | env->DeleteGlobalRef(s_pair_class); |
| 311 | env->DeleteGlobalRef(s_overlay_control_data_class); | 409 | env->DeleteGlobalRef(s_overlay_control_data_class); |
| 410 | env->DeleteGlobalRef(s_patch_class); | ||
| 312 | env->DeleteGlobalRef(s_double_class); | 411 | env->DeleteGlobalRef(s_double_class); |
| 412 | env->DeleteGlobalRef(s_integer_class); | ||
| 413 | env->DeleteGlobalRef(s_boolean_class); | ||
| 313 | 414 | ||
| 314 | // UnInitialize applets | 415 | // UnInitialize applets |
| 315 | SoftwareKeyboard::CleanupJNI(env); | 416 | SoftwareKeyboard::CleanupJNI(env); |
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index 784d1412f..a002e705d 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h | |||
| @@ -43,8 +43,25 @@ jfieldID GetOverlayControlDataLandscapePositionField(); | |||
| 43 | jfieldID GetOverlayControlDataPortraitPositionField(); | 43 | jfieldID GetOverlayControlDataPortraitPositionField(); |
| 44 | jfieldID GetOverlayControlDataFoldablePositionField(); | 44 | jfieldID GetOverlayControlDataFoldablePositionField(); |
| 45 | 45 | ||
| 46 | jclass GetPatchClass(); | ||
| 47 | jmethodID GetPatchConstructor(); | ||
| 48 | jfieldID GetPatchEnabledField(); | ||
| 49 | jfieldID GetPatchNameField(); | ||
| 50 | jfieldID GetPatchVersionField(); | ||
| 51 | jfieldID GetPatchTypeField(); | ||
| 52 | jfieldID GetPatchProgramIdField(); | ||
| 53 | jfieldID GetPatchTitleIdField(); | ||
| 54 | |||
| 46 | jclass GetDoubleClass(); | 55 | jclass GetDoubleClass(); |
| 47 | jmethodID GetDoubleConstructor(); | 56 | jmethodID GetDoubleConstructor(); |
| 48 | jfieldID GetDoubleValueField(); | 57 | jfieldID GetDoubleValueField(); |
| 49 | 58 | ||
| 59 | jclass GetIntegerClass(); | ||
| 60 | jmethodID GetIntegerConstructor(); | ||
| 61 | jfieldID GetIntegerValueField(); | ||
| 62 | |||
| 63 | jclass GetBooleanClass(); | ||
| 64 | jmethodID GetBooleanConstructor(); | ||
| 65 | jfieldID GetBooleanValueField(); | ||
| 66 | |||
| 50 | } // namespace IDCache | 67 | } // namespace IDCache |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index ed3b1353a..be0a723b1 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -17,6 +17,7 @@ | |||
| 17 | #include <core/file_sys/patch_manager.h> | 17 | #include <core/file_sys/patch_manager.h> |
| 18 | #include <core/file_sys/savedata_factory.h> | 18 | #include <core/file_sys/savedata_factory.h> |
| 19 | #include <core/loader/nro.h> | 19 | #include <core/loader/nro.h> |
| 20 | #include <frontend_common/content_manager.h> | ||
| 20 | #include <jni.h> | 21 | #include <jni.h> |
| 21 | 22 | ||
| 22 | #include "common/detached_tasks.h" | 23 | #include "common/detached_tasks.h" |
| @@ -100,67 +101,6 @@ void EmulationSession::SetNativeWindow(ANativeWindow* native_window) { | |||
| 100 | m_native_window = native_window; | 101 | m_native_window = native_window; |
| 101 | } | 102 | } |
| 102 | 103 | ||
| 103 | int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) { | ||
| 104 | jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, | ||
| 105 | std::size_t block_size) { | ||
| 106 | if (src == nullptr || dest == nullptr) { | ||
| 107 | return false; | ||
| 108 | } | ||
| 109 | if (!dest->Resize(src->GetSize())) { | ||
| 110 | return false; | ||
| 111 | } | ||
| 112 | |||
| 113 | using namespace Common::Literals; | ||
| 114 | [[maybe_unused]] std::vector<u8> buffer(1_MiB); | ||
| 115 | |||
| 116 | for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { | ||
| 117 | jconst read = src->Read(buffer.data(), buffer.size(), i); | ||
| 118 | dest->Write(buffer.data(), read, i); | ||
| 119 | } | ||
| 120 | return true; | ||
| 121 | }; | ||
| 122 | |||
| 123 | enum InstallResult { | ||
| 124 | Success = 0, | ||
| 125 | SuccessFileOverwritten = 1, | ||
| 126 | InstallError = 2, | ||
| 127 | ErrorBaseGame = 3, | ||
| 128 | ErrorFilenameExtension = 4, | ||
| 129 | }; | ||
| 130 | |||
| 131 | [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; | ||
| 132 | if (file_extension == "nsp") { | ||
| 133 | nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); | ||
| 134 | if (nsp->IsExtractedType()) { | ||
| 135 | return InstallError; | ||
| 136 | } | ||
| 137 | } else { | ||
| 138 | return ErrorFilenameExtension; | ||
| 139 | } | ||
| 140 | |||
| 141 | if (!nsp) { | ||
| 142 | return InstallError; | ||
| 143 | } | ||
| 144 | |||
| 145 | if (nsp->GetStatus() != Loader::ResultStatus::Success) { | ||
| 146 | return InstallError; | ||
| 147 | } | ||
| 148 | |||
| 149 | jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, | ||
| 150 | copy_func); | ||
| 151 | |||
| 152 | switch (res) { | ||
| 153 | case FileSys::InstallResult::Success: | ||
| 154 | return Success; | ||
| 155 | case FileSys::InstallResult::OverwriteExisting: | ||
| 156 | return SuccessFileOverwritten; | ||
| 157 | case FileSys::InstallResult::ErrorBaseInstall: | ||
| 158 | return ErrorBaseGame; | ||
| 159 | default: | ||
| 160 | return InstallError; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir, | 104 | void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir, |
| 165 | const std::string& custom_driver_dir, | 105 | const std::string& custom_driver_dir, |
| 166 | const std::string& custom_driver_name, | 106 | const std::string& custom_driver_name, |
| @@ -512,10 +452,20 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject | |||
| 512 | } | 452 | } |
| 513 | 453 | ||
| 514 | int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, | 454 | int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, |
| 515 | jstring j_file, | 455 | jstring j_file, jobject jcallback) { |
| 516 | jstring j_file_extension) { | 456 | auto jlambdaClass = env->GetObjectClass(jcallback); |
| 517 | return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file), | 457 | auto jlambdaInvokeMethod = env->GetMethodID( |
| 518 | GetJString(env, j_file_extension)); | 458 | jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); |
| 459 | const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) { | ||
| 460 | auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod, | ||
| 461 | ToJDouble(env, max), ToJDouble(env, progress)); | ||
| 462 | return GetJBoolean(env, jwasCancelled); | ||
| 463 | }; | ||
| 464 | |||
| 465 | return static_cast<int>( | ||
| 466 | ContentManager::InstallNSP(&EmulationSession::GetInstance().System(), | ||
| 467 | EmulationSession::GetInstance().System().GetFilesystem().get(), | ||
| 468 | GetJString(env, j_file), callback)); | ||
| 519 | } | 469 | } |
| 520 | 470 | ||
| 521 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, | 471 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, |
| @@ -824,9 +774,9 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, | |||
| 824 | return true; | 774 | return true; |
| 825 | } | 775 | } |
| 826 | 776 | ||
| 827 | jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env, jobject jobj, | 777 | jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj, |
| 828 | jstring jpath, | 778 | jstring jpath, |
| 829 | jstring jprogramId) { | 779 | jstring jprogramId) { |
| 830 | const auto path = GetJString(env, jpath); | 780 | const auto path = GetJString(env, jpath); |
| 831 | const auto vFile = | 781 | const auto vFile = |
| 832 | Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); | 782 | Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); |
| @@ -843,20 +793,40 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env, | |||
| 843 | FileSys::VirtualFile update_raw; | 793 | FileSys::VirtualFile update_raw; |
| 844 | loader->ReadUpdateRaw(update_raw); | 794 | loader->ReadUpdateRaw(update_raw); |
| 845 | 795 | ||
| 846 | auto addons = pm.GetPatchVersionNames(update_raw); | 796 | auto patches = pm.GetPatches(update_raw); |
| 847 | auto jemptyString = ToJString(env, ""); | 797 | jobjectArray jpatchArray = |
| 848 | auto jemptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(), | 798 | env->NewObjectArray(patches.size(), IDCache::GetPatchClass(), nullptr); |
| 849 | jemptyString, jemptyString); | ||
| 850 | jobjectArray jaddonsArray = | ||
| 851 | env->NewObjectArray(addons.size(), IDCache::GetPairClass(), jemptyStringPair); | ||
| 852 | int i = 0; | 799 | int i = 0; |
| 853 | for (const auto& addon : addons) { | 800 | for (const auto& patch : patches) { |
| 854 | jobject jaddon = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(), | 801 | jobject jpatch = env->NewObject( |
| 855 | ToJString(env, addon.first), ToJString(env, addon.second)); | 802 | IDCache::GetPatchClass(), IDCache::GetPatchConstructor(), patch.enabled, |
| 856 | env->SetObjectArrayElement(jaddonsArray, i, jaddon); | 803 | ToJString(env, patch.name), ToJString(env, patch.version), |
| 804 | static_cast<jint>(patch.type), ToJString(env, std::to_string(patch.program_id)), | ||
| 805 | ToJString(env, std::to_string(patch.title_id))); | ||
| 806 | env->SetObjectArrayElement(jpatchArray, i, jpatch); | ||
| 857 | ++i; | 807 | ++i; |
| 858 | } | 808 | } |
| 859 | return jaddonsArray; | 809 | return jpatchArray; |
| 810 | } | ||
| 811 | |||
| 812 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj, | ||
| 813 | jstring jprogramId) { | ||
| 814 | auto program_id = EmulationSession::GetProgramId(env, jprogramId); | ||
| 815 | ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(), | ||
| 816 | program_id); | ||
| 817 | } | ||
| 818 | |||
| 819 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj, | ||
| 820 | jstring jprogramId) { | ||
| 821 | auto program_id = EmulationSession::GetProgramId(env, jprogramId); | ||
| 822 | ContentManager::RemoveAllDLC(&EmulationSession::GetInstance().System(), program_id); | ||
| 823 | } | ||
| 824 | |||
| 825 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId, | ||
| 826 | jstring jname) { | ||
| 827 | auto program_id = EmulationSession::GetProgramId(env, jprogramId); | ||
| 828 | ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(), | ||
| 829 | program_id, GetJString(env, jname)); | ||
| 860 | } | 830 | } |
| 861 | 831 | ||
| 862 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, | 832 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, |
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 4a8049578..dadb138ad 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include "core/file_sys/registered_cache.h" | 7 | #include "core/file_sys/registered_cache.h" |
| 8 | #include "core/hle/service/acc/profile_manager.h" | 8 | #include "core/hle/service/acc/profile_manager.h" |
| 9 | #include "core/perf_stats.h" | 9 | #include "core/perf_stats.h" |
| 10 | #include "frontend_common/content_manager.h" | ||
| 10 | #include "jni/applets/software_keyboard.h" | 11 | #include "jni/applets/software_keyboard.h" |
| 11 | #include "jni/emu_window/emu_window.h" | 12 | #include "jni/emu_window/emu_window.h" |
| 12 | #include "video_core/rasterizer_interface.h" | 13 | #include "video_core/rasterizer_interface.h" |
| @@ -29,7 +30,6 @@ public: | |||
| 29 | void SetNativeWindow(ANativeWindow* native_window); | 30 | void SetNativeWindow(ANativeWindow* native_window); |
| 30 | void SurfaceChanged(); | 31 | void SurfaceChanged(); |
| 31 | 32 | ||
| 32 | int InstallFileToNand(std::string filename, std::string file_extension); | ||
| 33 | void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, | 33 | void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, |
| 34 | const std::string& custom_driver_name, | 34 | const std::string& custom_driver_name, |
| 35 | const std::string& file_redirect_dir); | 35 | const std::string& file_redirect_dir); |
diff --git a/src/android/app/src/main/res/layout/dialog_progress_bar.xml b/src/android/app/src/main/res/layout/dialog_progress_bar.xml index 0209ea082..e61aa5294 100644 --- a/src/android/app/src/main/res/layout/dialog_progress_bar.xml +++ b/src/android/app/src/main/res/layout/dialog_progress_bar.xml | |||
| @@ -1,8 +1,30 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | 3 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 4 | android:id="@+id/progress_bar" | ||
| 5 | android:layout_width="match_parent" | 4 | android:layout_width="match_parent" |
| 6 | android:layout_height="wrap_content" | 5 | android:layout_height="wrap_content" |
| 7 | android:padding="24dp" | 6 | android:orientation="vertical"> |
| 8 | app:trackCornerRadius="4dp" /> | 7 | |
| 8 | <com.google.android.material.textview.MaterialTextView | ||
| 9 | android:id="@+id/message" | ||
| 10 | style="@style/TextAppearance.Material3.BodyMedium" | ||
| 11 | android:layout_width="match_parent" | ||
| 12 | android:layout_height="wrap_content" | ||
| 13 | android:layout_marginHorizontal="24dp" | ||
| 14 | android:layout_marginTop="12dp" | ||
| 15 | android:layout_marginBottom="6dp" | ||
| 16 | android:ellipsize="marquee" | ||
| 17 | android:marqueeRepeatLimit="marquee_forever" | ||
| 18 | android:requiresFadingEdge="horizontal" | ||
| 19 | android:singleLine="true" | ||
| 20 | android:textAlignment="viewStart" | ||
| 21 | android:visibility="gone" /> | ||
| 22 | |||
| 23 | <com.google.android.material.progressindicator.LinearProgressIndicator | ||
| 24 | android:id="@+id/progress_bar" | ||
| 25 | android:layout_width="match_parent" | ||
| 26 | android:layout_height="wrap_content" | ||
| 27 | android:padding="24dp" | ||
| 28 | app:trackCornerRadius="4dp" /> | ||
| 29 | |||
| 30 | </LinearLayout> | ||
diff --git a/src/android/app/src/main/res/layout/list_item_addon.xml b/src/android/app/src/main/res/layout/list_item_addon.xml index 74ca04ef1..3a1382fe2 100644 --- a/src/android/app/src/main/res/layout/list_item_addon.xml +++ b/src/android/app/src/main/res/layout/list_item_addon.xml | |||
| @@ -14,12 +14,11 @@ | |||
| 14 | android:id="@+id/text_container" | 14 | android:id="@+id/text_container" |
| 15 | android:layout_width="0dp" | 15 | android:layout_width="0dp" |
| 16 | android:layout_height="wrap_content" | 16 | android:layout_height="wrap_content" |
| 17 | android:layout_marginEnd="16dp" | ||
| 18 | android:orientation="vertical" | 17 | android:orientation="vertical" |
| 19 | app:layout_constraintBottom_toBottomOf="@+id/addon_switch" | 18 | android:layout_marginEnd="16dp" |
| 20 | app:layout_constraintEnd_toStartOf="@+id/addon_switch" | 19 | app:layout_constraintEnd_toStartOf="@+id/addon_checkbox" |
| 21 | app:layout_constraintStart_toStartOf="parent" | 20 | app:layout_constraintStart_toStartOf="parent" |
| 22 | app:layout_constraintTop_toTopOf="@+id/addon_switch"> | 21 | app:layout_constraintTop_toTopOf="parent"> |
| 23 | 22 | ||
| 24 | <com.google.android.material.textview.MaterialTextView | 23 | <com.google.android.material.textview.MaterialTextView |
| 25 | android:id="@+id/title" | 24 | android:id="@+id/title" |
| @@ -42,16 +41,29 @@ | |||
| 42 | 41 | ||
| 43 | </LinearLayout> | 42 | </LinearLayout> |
| 44 | 43 | ||
| 45 | <com.google.android.material.materialswitch.MaterialSwitch | 44 | <com.google.android.material.checkbox.MaterialCheckBox |
| 46 | android:id="@+id/addon_switch" | 45 | android:id="@+id/addon_checkbox" |
| 47 | android:layout_width="wrap_content" | 46 | android:layout_width="wrap_content" |
| 48 | android:layout_height="wrap_content" | 47 | android:layout_height="wrap_content" |
| 49 | android:focusable="true" | 48 | android:focusable="true" |
| 50 | android:gravity="center" | 49 | android:gravity="center" |
| 51 | android:nextFocusLeft="@id/addon_container" | 50 | android:layout_marginEnd="8dp" |
| 52 | app:layout_constraintBottom_toBottomOf="parent" | 51 | app:layout_constraintTop_toTopOf="@+id/text_container" |
| 52 | app:layout_constraintBottom_toBottomOf="@+id/text_container" | ||
| 53 | app:layout_constraintEnd_toStartOf="@+id/button_delete" /> | ||
| 54 | |||
| 55 | <Button | ||
| 56 | android:id="@+id/button_delete" | ||
| 57 | style="@style/Widget.Material3.Button.IconButton" | ||
| 58 | android:layout_width="wrap_content" | ||
| 59 | android:layout_height="wrap_content" | ||
| 60 | android:layout_gravity="center_vertical" | ||
| 61 | android:contentDescription="@string/delete" | ||
| 62 | android:tooltipText="@string/delete" | ||
| 63 | app:icon="@drawable/ic_delete" | ||
| 64 | app:iconTint="?attr/colorControlNormal" | ||
| 53 | app:layout_constraintEnd_toEndOf="parent" | 65 | app:layout_constraintEnd_toEndOf="parent" |
| 54 | app:layout_constraintStart_toEndOf="@id/text_container" | 66 | app:layout_constraintTop_toTopOf="@+id/addon_checkbox" |
| 55 | app:layout_constraintTop_toTopOf="parent" /> | 67 | app:layout_constraintBottom_toBottomOf="@+id/addon_checkbox" /> |
| 56 | 68 | ||
| 57 | </androidx.constraintlayout.widget.ConstraintLayout> | 69 | </androidx.constraintlayout.widget.ConstraintLayout> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 547752bda..db5b27d38 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -286,6 +286,7 @@ | |||
| 286 | <string name="custom">Custom</string> | 286 | <string name="custom">Custom</string> |
| 287 | <string name="notice">Notice</string> | 287 | <string name="notice">Notice</string> |
| 288 | <string name="import_complete">Import complete</string> | 288 | <string name="import_complete">Import complete</string> |
| 289 | <string name="more_options">More options</string> | ||
| 289 | 290 | ||
| 290 | <!-- GPU driver installation --> | 291 | <!-- GPU driver installation --> |
| 291 | <string name="select_gpu_driver">Select GPU driver</string> | 292 | <string name="select_gpu_driver">Select GPU driver</string> |
| @@ -348,6 +349,8 @@ | |||
| 348 | <string name="verifying_content">Verifying content…</string> | 349 | <string name="verifying_content">Verifying content…</string> |
| 349 | <string name="content_install_notice">Content install notice</string> | 350 | <string name="content_install_notice">Content install notice</string> |
| 350 | <string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string> | 351 | <string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string> |
| 352 | <string name="confirm_uninstall">Confirm uninstall</string> | ||
| 353 | <string name="confirm_uninstall_description">Are you sure you want to uninstall this addon?</string> | ||
| 351 | 354 | ||
| 352 | <!-- ROM loading errors --> | 355 | <!-- ROM loading errors --> |
| 353 | <string name="loader_error_encrypted">Your ROM is encrypted</string> | 356 | <string name="loader_error_encrypted">Your ROM is encrypted</string> |
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 4a3dbc6a3..612122224 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -466,12 +466,12 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs | |||
| 466 | return romfs; | 466 | return romfs; |
| 467 | } | 467 | } |
| 468 | 468 | ||
| 469 | PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const { | 469 | std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const { |
| 470 | if (title_id == 0) { | 470 | if (title_id == 0) { |
| 471 | return {}; | 471 | return {}; |
| 472 | } | 472 | } |
| 473 | 473 | ||
| 474 | std::map<std::string, std::string, std::less<>> out; | 474 | std::vector<Patch> out; |
| 475 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 475 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 476 | 476 | ||
| 477 | // Game Updates | 477 | // Game Updates |
| @@ -482,20 +482,28 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u | |||
| 482 | 482 | ||
| 483 | const auto update_disabled = | 483 | const auto update_disabled = |
| 484 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); | 484 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); |
| 485 | const auto update_label = update_disabled ? "[D] Update" : "Update"; | 485 | Patch update_patch = {.enabled = !update_disabled, |
| 486 | .name = "Update", | ||
| 487 | .version = "", | ||
| 488 | .type = PatchType::Update, | ||
| 489 | .program_id = title_id, | ||
| 490 | .title_id = title_id}; | ||
| 486 | 491 | ||
| 487 | if (nacp != nullptr) { | 492 | if (nacp != nullptr) { |
| 488 | out.insert_or_assign(update_label, nacp->GetVersionString()); | 493 | update_patch.version = nacp->GetVersionString(); |
| 494 | out.push_back(update_patch); | ||
| 489 | } else { | 495 | } else { |
| 490 | if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { | 496 | if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { |
| 491 | const auto meta_ver = content_provider.GetEntryVersion(update_tid); | 497 | const auto meta_ver = content_provider.GetEntryVersion(update_tid); |
| 492 | if (meta_ver.value_or(0) == 0) { | 498 | if (meta_ver.value_or(0) == 0) { |
| 493 | out.insert_or_assign(update_label, ""); | 499 | out.push_back(update_patch); |
| 494 | } else { | 500 | } else { |
| 495 | out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver)); | 501 | update_patch.version = FormatTitleVersion(*meta_ver); |
| 502 | out.push_back(update_patch); | ||
| 496 | } | 503 | } |
| 497 | } else if (update_raw != nullptr) { | 504 | } else if (update_raw != nullptr) { |
| 498 | out.insert_or_assign(update_label, "PACKED"); | 505 | update_patch.version = "PACKED"; |
| 506 | out.push_back(update_patch); | ||
| 499 | } | 507 | } |
| 500 | } | 508 | } |
| 501 | 509 | ||
| @@ -539,7 +547,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u | |||
| 539 | 547 | ||
| 540 | const auto mod_disabled = | 548 | const auto mod_disabled = |
| 541 | std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end(); | 549 | std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end(); |
| 542 | out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types); | 550 | out.push_back({.enabled = !mod_disabled, |
| 551 | .name = mod->GetName(), | ||
| 552 | .version = types, | ||
| 553 | .type = PatchType::Mod, | ||
| 554 | .program_id = title_id, | ||
| 555 | .title_id = title_id}); | ||
| 543 | } | 556 | } |
| 544 | } | 557 | } |
| 545 | 558 | ||
| @@ -557,7 +570,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u | |||
| 557 | if (!types.empty()) { | 570 | if (!types.empty()) { |
| 558 | const auto mod_disabled = | 571 | const auto mod_disabled = |
| 559 | std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end(); | 572 | std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end(); |
| 560 | out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", types); | 573 | out.push_back({.enabled = !mod_disabled, |
| 574 | .name = "SDMC", | ||
| 575 | .version = types, | ||
| 576 | .type = PatchType::Mod, | ||
| 577 | .program_id = title_id, | ||
| 578 | .title_id = title_id}); | ||
| 561 | } | 579 | } |
| 562 | } | 580 | } |
| 563 | 581 | ||
| @@ -584,7 +602,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u | |||
| 584 | 602 | ||
| 585 | const auto dlc_disabled = | 603 | const auto dlc_disabled = |
| 586 | std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end(); | 604 | std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end(); |
| 587 | out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list)); | 605 | out.push_back({.enabled = !dlc_disabled, |
| 606 | .name = "DLC", | ||
| 607 | .version = std::move(list), | ||
| 608 | .type = PatchType::DLC, | ||
| 609 | .program_id = title_id, | ||
| 610 | .title_id = dlc_match.back().title_id}); | ||
| 588 | } | 611 | } |
| 589 | 612 | ||
| 590 | return out; | 613 | return out; |
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 03e9c7301..2601b8217 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h | |||
| @@ -26,12 +26,22 @@ class ContentProvider; | |||
| 26 | class NCA; | 26 | class NCA; |
| 27 | class NACP; | 27 | class NACP; |
| 28 | 28 | ||
| 29 | enum class PatchType { Update, DLC, Mod }; | ||
| 30 | |||
| 31 | struct Patch { | ||
| 32 | bool enabled; | ||
| 33 | std::string name; | ||
| 34 | std::string version; | ||
| 35 | PatchType type; | ||
| 36 | u64 program_id; | ||
| 37 | u64 title_id; | ||
| 38 | }; | ||
| 39 | |||
| 29 | // A centralized class to manage patches to games. | 40 | // A centralized class to manage patches to games. |
| 30 | class PatchManager { | 41 | class PatchManager { |
| 31 | public: | 42 | public: |
| 32 | using BuildID = std::array<u8, 0x20>; | 43 | using BuildID = std::array<u8, 0x20>; |
| 33 | using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>; | 44 | using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>; |
| 34 | using PatchVersionNames = std::map<std::string, std::string, std::less<>>; | ||
| 35 | 45 | ||
| 36 | explicit PatchManager(u64 title_id_, | 46 | explicit PatchManager(u64 title_id_, |
| 37 | const Service::FileSystem::FileSystemController& fs_controller_, | 47 | const Service::FileSystem::FileSystemController& fs_controller_, |
| @@ -66,9 +76,8 @@ public: | |||
| 66 | VirtualFile packed_update_raw = nullptr, | 76 | VirtualFile packed_update_raw = nullptr, |
| 67 | bool apply_layeredfs = true) const; | 77 | bool apply_layeredfs = true) const; |
| 68 | 78 | ||
| 69 | // Returns a vector of pairs between patch names and patch versions. | 79 | // Returns a vector of patches |
| 70 | // i.e. Update 3.2.2 will return {"Update", "3.2.2"} | 80 | [[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const; |
| 71 | [[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const; | ||
| 72 | 81 | ||
| 73 | // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails, | 82 | // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails, |
| 74 | // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be | 83 | // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be |
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 03ebdc137..595a3372e 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp | |||
| @@ -26,6 +26,7 @@ void LoopProcess(Core::System& system) { | |||
| 26 | resource_manager->Initialize(); | 26 | resource_manager->Initialize(); |
| 27 | resource_manager->RegisterAppletResourceUserId(system.ApplicationProcess()->GetProcessId(), | 27 | resource_manager->RegisterAppletResourceUserId(system.ApplicationProcess()->GetProcessId(), |
| 28 | true); | 28 | true); |
| 29 | resource_manager->SetAruidValidForVibration(system.ApplicationProcess()->GetProcessId(), true); | ||
| 29 | 30 | ||
| 30 | server_manager->RegisterNamedService( | 31 | server_manager->RegisterNamedService( |
| 31 | "hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings)); | 32 | "hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings)); |
diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index 1951da33b..30afed812 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp | |||
| @@ -22,12 +22,16 @@ | |||
| 22 | #include "hid_core/resources/mouse/mouse.h" | 22 | #include "hid_core/resources/mouse/mouse.h" |
| 23 | #include "hid_core/resources/npad/npad.h" | 23 | #include "hid_core/resources/npad/npad.h" |
| 24 | #include "hid_core/resources/npad/npad_types.h" | 24 | #include "hid_core/resources/npad/npad_types.h" |
| 25 | #include "hid_core/resources/npad/npad_vibration.h" | ||
| 25 | #include "hid_core/resources/palma/palma.h" | 26 | #include "hid_core/resources/palma/palma.h" |
| 26 | #include "hid_core/resources/six_axis/console_six_axis.h" | 27 | #include "hid_core/resources/six_axis/console_six_axis.h" |
| 27 | #include "hid_core/resources/six_axis/seven_six_axis.h" | 28 | #include "hid_core/resources/six_axis/seven_six_axis.h" |
| 28 | #include "hid_core/resources/six_axis/six_axis.h" | 29 | #include "hid_core/resources/six_axis/six_axis.h" |
| 29 | #include "hid_core/resources/touch_screen/gesture.h" | 30 | #include "hid_core/resources/touch_screen/gesture.h" |
| 30 | #include "hid_core/resources/touch_screen/touch_screen.h" | 31 | #include "hid_core/resources/touch_screen/touch_screen.h" |
| 32 | #include "hid_core/resources/vibration/gc_vibration_device.h" | ||
| 33 | #include "hid_core/resources/vibration/n64_vibration_device.h" | ||
| 34 | #include "hid_core/resources/vibration/vibration_device.h" | ||
| 31 | 35 | ||
| 32 | namespace Service::HID { | 36 | namespace Service::HID { |
| 33 | 37 | ||
| @@ -38,7 +42,7 @@ public: | |||
| 38 | : ServiceFramework{system_, "IActiveVibrationDeviceList"}, resource_manager(resource) { | 42 | : ServiceFramework{system_, "IActiveVibrationDeviceList"}, resource_manager(resource) { |
| 39 | // clang-format off | 43 | // clang-format off |
| 40 | static const FunctionInfo functions[] = { | 44 | static const FunctionInfo functions[] = { |
| 41 | {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"}, | 45 | {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"}, |
| 42 | }; | 46 | }; |
| 43 | // clang-format on | 47 | // clang-format on |
| 44 | 48 | ||
| @@ -46,22 +50,49 @@ public: | |||
| 46 | } | 50 | } |
| 47 | 51 | ||
| 48 | private: | 52 | private: |
| 49 | void InitializeVibrationDevice(HLERequestContext& ctx) { | 53 | void ActivateVibrationDevice(HLERequestContext& ctx) { |
| 50 | IPC::RequestParser rp{ctx}; | 54 | IPC::RequestParser rp{ctx}; |
| 51 | const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()}; | 55 | const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()}; |
| 52 | 56 | ||
| 53 | if (resource_manager != nullptr && resource_manager->GetNpad()) { | ||
| 54 | resource_manager->GetNpad()->InitializeVibrationDevice(vibration_device_handle); | ||
| 55 | } | ||
| 56 | |||
| 57 | LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}", | 57 | LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}", |
| 58 | vibration_device_handle.npad_type, vibration_device_handle.npad_id, | 58 | vibration_device_handle.npad_type, vibration_device_handle.npad_id, |
| 59 | vibration_device_handle.device_index); | 59 | vibration_device_handle.device_index); |
| 60 | 60 | ||
| 61 | const auto result = ActivateVibrationDeviceImpl(vibration_device_handle); | ||
| 62 | |||
| 61 | IPC::ResponseBuilder rb{ctx, 2}; | 63 | IPC::ResponseBuilder rb{ctx, 2}; |
| 62 | rb.Push(ResultSuccess); | 64 | rb.Push(result); |
| 63 | } | 65 | } |
| 64 | 66 | ||
| 67 | Result ActivateVibrationDeviceImpl(const Core::HID::VibrationDeviceHandle& handle) { | ||
| 68 | std::scoped_lock lock{mutex}; | ||
| 69 | |||
| 70 | const Result is_valid = IsVibrationHandleValid(handle); | ||
| 71 | if (is_valid.IsError()) { | ||
| 72 | return is_valid; | ||
| 73 | } | ||
| 74 | |||
| 75 | for (std::size_t i = 0; i < list_size; i++) { | ||
| 76 | if (handle.device_index == vibration_device_list[i].device_index && | ||
| 77 | handle.npad_id == vibration_device_list[i].npad_id && | ||
| 78 | handle.npad_type == vibration_device_list[i].npad_type) { | ||
| 79 | return ResultSuccess; | ||
| 80 | } | ||
| 81 | } | ||
| 82 | if (list_size == vibration_device_list.size()) { | ||
| 83 | return ResultVibrationDeviceIndexOutOfRange; | ||
| 84 | } | ||
| 85 | const Result result = resource_manager->GetVibrationDevice(handle)->Activate(); | ||
| 86 | if (result.IsError()) { | ||
| 87 | return result; | ||
| 88 | } | ||
| 89 | vibration_device_list[list_size++] = handle; | ||
| 90 | return ResultSuccess; | ||
| 91 | } | ||
| 92 | |||
| 93 | mutable std::mutex mutex; | ||
| 94 | std::size_t list_size{}; | ||
| 95 | std::array<Core::HID::VibrationDeviceHandle, 0x100> vibration_device_list{}; | ||
| 65 | std::shared_ptr<ResourceManager> resource_manager; | 96 | std::shared_ptr<ResourceManager> resource_manager; |
| 66 | }; | 97 | }; |
| 67 | 98 | ||
| @@ -153,7 +184,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r | |||
| 153 | {209, &IHidServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, | 184 | {209, &IHidServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, |
| 154 | {210, &IHidServer::EndPermitVibrationSession, "EndPermitVibrationSession"}, | 185 | {210, &IHidServer::EndPermitVibrationSession, "EndPermitVibrationSession"}, |
| 155 | {211, &IHidServer::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"}, | 186 | {211, &IHidServer::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"}, |
| 156 | {212, nullptr, "SendVibrationValueInBool"}, | 187 | {212, &IHidServer::SendVibrationValueInBool, "SendVibrationValueInBool"}, |
| 157 | {300, &IHidServer::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, | 188 | {300, &IHidServer::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, |
| 158 | {301, &IHidServer::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, | 189 | {301, &IHidServer::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, |
| 159 | {302, &IHidServer::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"}, | 190 | {302, &IHidServer::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"}, |
| @@ -1492,59 +1523,13 @@ void IHidServer::ClearNpadCaptureButtonAssignment(HLERequestContext& ctx) { | |||
| 1492 | void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) { | 1523 | void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) { |
| 1493 | IPC::RequestParser rp{ctx}; | 1524 | IPC::RequestParser rp{ctx}; |
| 1494 | const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()}; | 1525 | const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()}; |
| 1495 | const auto controller = GetResourceManager()->GetNpad(); | ||
| 1496 | |||
| 1497 | Core::HID::VibrationDeviceInfo vibration_device_info; | ||
| 1498 | bool check_device_index = false; | ||
| 1499 | |||
| 1500 | switch (vibration_device_handle.npad_type) { | ||
| 1501 | case Core::HID::NpadStyleIndex::Fullkey: | ||
| 1502 | case Core::HID::NpadStyleIndex::Handheld: | ||
| 1503 | case Core::HID::NpadStyleIndex::JoyconDual: | ||
| 1504 | case Core::HID::NpadStyleIndex::JoyconLeft: | ||
| 1505 | case Core::HID::NpadStyleIndex::JoyconRight: | ||
| 1506 | vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator; | ||
| 1507 | check_device_index = true; | ||
| 1508 | break; | ||
| 1509 | case Core::HID::NpadStyleIndex::GameCube: | ||
| 1510 | vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm; | ||
| 1511 | break; | ||
| 1512 | case Core::HID::NpadStyleIndex::N64: | ||
| 1513 | vibration_device_info.type = Core::HID::VibrationDeviceType::N64; | ||
| 1514 | break; | ||
| 1515 | default: | ||
| 1516 | vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown; | ||
| 1517 | break; | ||
| 1518 | } | ||
| 1519 | |||
| 1520 | vibration_device_info.position = Core::HID::VibrationDevicePosition::None; | ||
| 1521 | if (check_device_index) { | ||
| 1522 | switch (vibration_device_handle.device_index) { | ||
| 1523 | case Core::HID::DeviceIndex::Left: | ||
| 1524 | vibration_device_info.position = Core::HID::VibrationDevicePosition::Left; | ||
| 1525 | break; | ||
| 1526 | case Core::HID::DeviceIndex::Right: | ||
| 1527 | vibration_device_info.position = Core::HID::VibrationDevicePosition::Right; | ||
| 1528 | break; | ||
| 1529 | case Core::HID::DeviceIndex::None: | ||
| 1530 | default: | ||
| 1531 | ASSERT_MSG(false, "DeviceIndex should never be None!"); | ||
| 1532 | break; | ||
| 1533 | } | ||
| 1534 | } | ||
| 1535 | 1526 | ||
| 1536 | LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}", | 1527 | Core::HID::VibrationDeviceInfo vibration_device_info{}; |
| 1537 | vibration_device_info.type, vibration_device_info.position); | 1528 | const auto result = GetResourceManager()->GetVibrationDeviceInfo(vibration_device_info, |
| 1538 | 1529 | vibration_device_handle); | |
| 1539 | const auto result = IsVibrationHandleValid(vibration_device_handle); | ||
| 1540 | if (result.IsError()) { | ||
| 1541 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1542 | rb.Push(result); | ||
| 1543 | return; | ||
| 1544 | } | ||
| 1545 | 1530 | ||
| 1546 | IPC::ResponseBuilder rb{ctx, 4}; | 1531 | IPC::ResponseBuilder rb{ctx, 4}; |
| 1547 | rb.Push(ResultSuccess); | 1532 | rb.Push(result); |
| 1548 | rb.PushRaw(vibration_device_info); | 1533 | rb.PushRaw(vibration_device_info); |
| 1549 | } | 1534 | } |
| 1550 | 1535 | ||
| @@ -1560,16 +1545,16 @@ void IHidServer::SendVibrationValue(HLERequestContext& ctx) { | |||
| 1560 | 1545 | ||
| 1561 | const auto parameters{rp.PopRaw<Parameters>()}; | 1546 | const auto parameters{rp.PopRaw<Parameters>()}; |
| 1562 | 1547 | ||
| 1563 | GetResourceManager()->GetNpad()->VibrateController(parameters.applet_resource_user_id, | ||
| 1564 | parameters.vibration_device_handle, | ||
| 1565 | parameters.vibration_value); | ||
| 1566 | |||
| 1567 | LOG_DEBUG(Service_HID, | 1548 | LOG_DEBUG(Service_HID, |
| 1568 | "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", | 1549 | "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", |
| 1569 | parameters.vibration_device_handle.npad_type, | 1550 | parameters.vibration_device_handle.npad_type, |
| 1570 | parameters.vibration_device_handle.npad_id, | 1551 | parameters.vibration_device_handle.npad_id, |
| 1571 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); | 1552 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); |
| 1572 | 1553 | ||
| 1554 | GetResourceManager()->SendVibrationValue(parameters.applet_resource_user_id, | ||
| 1555 | parameters.vibration_device_handle, | ||
| 1556 | parameters.vibration_value); | ||
| 1557 | |||
| 1573 | IPC::ResponseBuilder rb{ctx, 2}; | 1558 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1574 | rb.Push(ResultSuccess); | 1559 | rb.Push(ResultSuccess); |
| 1575 | } | 1560 | } |
| @@ -1591,10 +1576,28 @@ void IHidServer::GetActualVibrationValue(HLERequestContext& ctx) { | |||
| 1591 | parameters.vibration_device_handle.npad_id, | 1576 | parameters.vibration_device_handle.npad_id, |
| 1592 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); | 1577 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); |
| 1593 | 1578 | ||
| 1579 | bool has_active_aruid{}; | ||
| 1580 | NpadVibrationDevice* device{nullptr}; | ||
| 1581 | Core::HID::VibrationValue vibration_value{}; | ||
| 1582 | Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id, | ||
| 1583 | has_active_aruid); | ||
| 1584 | |||
| 1585 | if (result.IsSuccess() && has_active_aruid) { | ||
| 1586 | result = IsVibrationHandleValid(parameters.vibration_device_handle); | ||
| 1587 | } | ||
| 1588 | if (result.IsSuccess() && has_active_aruid) { | ||
| 1589 | device = GetResourceManager()->GetNSVibrationDevice(parameters.vibration_device_handle); | ||
| 1590 | } | ||
| 1591 | if (device != nullptr) { | ||
| 1592 | result = device->GetActualVibrationValue(vibration_value); | ||
| 1593 | } | ||
| 1594 | if (result.IsError()) { | ||
| 1595 | vibration_value = Core::HID::DEFAULT_VIBRATION_VALUE; | ||
| 1596 | } | ||
| 1597 | |||
| 1594 | IPC::ResponseBuilder rb{ctx, 6}; | 1598 | IPC::ResponseBuilder rb{ctx, 6}; |
| 1595 | rb.Push(ResultSuccess); | 1599 | rb.Push(ResultSuccess); |
| 1596 | rb.PushRaw(GetResourceManager()->GetNpad()->GetLastVibration( | 1600 | rb.PushRaw(vibration_value); |
| 1597 | parameters.applet_resource_user_id, parameters.vibration_device_handle)); | ||
| 1598 | } | 1601 | } |
| 1599 | 1602 | ||
| 1600 | void IHidServer::CreateActiveVibrationDeviceList(HLERequestContext& ctx) { | 1603 | void IHidServer::CreateActiveVibrationDeviceList(HLERequestContext& ctx) { |
| @@ -1609,25 +1612,27 @@ void IHidServer::PermitVibration(HLERequestContext& ctx) { | |||
| 1609 | IPC::RequestParser rp{ctx}; | 1612 | IPC::RequestParser rp{ctx}; |
| 1610 | const auto can_vibrate{rp.Pop<bool>()}; | 1613 | const auto can_vibrate{rp.Pop<bool>()}; |
| 1611 | 1614 | ||
| 1612 | // nnSDK saves this value as a float. Since it can only be 1.0f or 0.0f we simplify this value | ||
| 1613 | // by converting it to a bool | ||
| 1614 | Settings::values.vibration_enabled.SetValue(can_vibrate); | ||
| 1615 | |||
| 1616 | LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); | 1615 | LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); |
| 1617 | 1616 | ||
| 1617 | const auto result = | ||
| 1618 | GetResourceManager()->GetNpad()->GetVibrationHandler()->SetVibrationMasterVolume( | ||
| 1619 | can_vibrate ? 1.0f : 0.0f); | ||
| 1620 | |||
| 1618 | IPC::ResponseBuilder rb{ctx, 2}; | 1621 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1619 | rb.Push(ResultSuccess); | 1622 | rb.Push(result); |
| 1620 | } | 1623 | } |
| 1621 | 1624 | ||
| 1622 | void IHidServer::IsVibrationPermitted(HLERequestContext& ctx) { | 1625 | void IHidServer::IsVibrationPermitted(HLERequestContext& ctx) { |
| 1623 | LOG_DEBUG(Service_HID, "called"); | 1626 | LOG_DEBUG(Service_HID, "called"); |
| 1624 | 1627 | ||
| 1625 | // nnSDK checks if a float is greater than zero. We return the bool we stored earlier | 1628 | f32 master_volume{}; |
| 1626 | const auto is_enabled = Settings::values.vibration_enabled.GetValue(); | 1629 | const auto result = |
| 1630 | GetResourceManager()->GetNpad()->GetVibrationHandler()->GetVibrationMasterVolume( | ||
| 1631 | master_volume); | ||
| 1627 | 1632 | ||
| 1628 | IPC::ResponseBuilder rb{ctx, 3}; | 1633 | IPC::ResponseBuilder rb{ctx, 3}; |
| 1629 | rb.Push(ResultSuccess); | 1634 | rb.Push(result); |
| 1630 | rb.Push(is_enabled); | 1635 | rb.Push(master_volume > 0.0f); |
| 1631 | } | 1636 | } |
| 1632 | 1637 | ||
| 1633 | void IHidServer::SendVibrationValues(HLERequestContext& ctx) { | 1638 | void IHidServer::SendVibrationValues(HLERequestContext& ctx) { |
| @@ -1645,13 +1650,22 @@ void IHidServer::SendVibrationValues(HLERequestContext& ctx) { | |||
| 1645 | auto vibration_values = std::span( | 1650 | auto vibration_values = std::span( |
| 1646 | reinterpret_cast<const Core::HID::VibrationValue*>(vibration_data.data()), vibration_count); | 1651 | reinterpret_cast<const Core::HID::VibrationValue*>(vibration_data.data()), vibration_count); |
| 1647 | 1652 | ||
| 1648 | GetResourceManager()->GetNpad()->VibrateControllers(applet_resource_user_id, | ||
| 1649 | vibration_device_handles, vibration_values); | ||
| 1650 | |||
| 1651 | LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); | 1653 | LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); |
| 1652 | 1654 | ||
| 1655 | Result result = ResultSuccess; | ||
| 1656 | if (handle_count != vibration_count) { | ||
| 1657 | result = ResultVibrationArraySizeMismatch; | ||
| 1658 | } | ||
| 1659 | |||
| 1660 | for (std::size_t i = 0; i < handle_count; i++) { | ||
| 1661 | if (result.IsSuccess()) { | ||
| 1662 | result = GetResourceManager()->SendVibrationValue( | ||
| 1663 | applet_resource_user_id, vibration_device_handles[i], vibration_values[i]); | ||
| 1664 | } | ||
| 1665 | } | ||
| 1666 | |||
| 1653 | IPC::ResponseBuilder rb{ctx, 2}; | 1667 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1654 | rb.Push(ResultSuccess); | 1668 | rb.Push(result); |
| 1655 | } | 1669 | } |
| 1656 | 1670 | ||
| 1657 | void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) { | 1671 | void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) { |
| @@ -1666,43 +1680,6 @@ void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) { | |||
| 1666 | 1680 | ||
| 1667 | const auto parameters{rp.PopRaw<Parameters>()}; | 1681 | const auto parameters{rp.PopRaw<Parameters>()}; |
| 1668 | 1682 | ||
| 1669 | /** | ||
| 1670 | * Note: This uses yuzu-specific behavior such that the StopHard command produces | ||
| 1671 | * vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined below, | ||
| 1672 | * in order to differentiate between Stop and StopHard commands. | ||
| 1673 | * This is done to reuse the controller vibration functions made for regular controllers. | ||
| 1674 | */ | ||
| 1675 | const auto vibration_value = [parameters] { | ||
| 1676 | switch (parameters.gc_erm_command) { | ||
| 1677 | case Core::HID::VibrationGcErmCommand::Stop: | ||
| 1678 | return Core::HID::VibrationValue{ | ||
| 1679 | .low_amplitude = 0.0f, | ||
| 1680 | .low_frequency = 160.0f, | ||
| 1681 | .high_amplitude = 0.0f, | ||
| 1682 | .high_frequency = 320.0f, | ||
| 1683 | }; | ||
| 1684 | case Core::HID::VibrationGcErmCommand::Start: | ||
| 1685 | return Core::HID::VibrationValue{ | ||
| 1686 | .low_amplitude = 1.0f, | ||
| 1687 | .low_frequency = 160.0f, | ||
| 1688 | .high_amplitude = 1.0f, | ||
| 1689 | .high_frequency = 320.0f, | ||
| 1690 | }; | ||
| 1691 | case Core::HID::VibrationGcErmCommand::StopHard: | ||
| 1692 | return Core::HID::VibrationValue{ | ||
| 1693 | .low_amplitude = 0.0f, | ||
| 1694 | .low_frequency = 0.0f, | ||
| 1695 | .high_amplitude = 0.0f, | ||
| 1696 | .high_frequency = 0.0f, | ||
| 1697 | }; | ||
| 1698 | default: | ||
| 1699 | return Core::HID::DEFAULT_VIBRATION_VALUE; | ||
| 1700 | } | ||
| 1701 | }(); | ||
| 1702 | |||
| 1703 | GetResourceManager()->GetNpad()->VibrateController( | ||
| 1704 | parameters.applet_resource_user_id, parameters.vibration_device_handle, vibration_value); | ||
| 1705 | |||
| 1706 | LOG_DEBUG(Service_HID, | 1683 | LOG_DEBUG(Service_HID, |
| 1707 | "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, " | 1684 | "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, " |
| 1708 | "gc_erm_command={}", | 1685 | "gc_erm_command={}", |
| @@ -1711,8 +1688,23 @@ void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) { | |||
| 1711 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id, | 1688 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id, |
| 1712 | parameters.gc_erm_command); | 1689 | parameters.gc_erm_command); |
| 1713 | 1690 | ||
| 1691 | bool has_active_aruid{}; | ||
| 1692 | NpadGcVibrationDevice* gc_device{nullptr}; | ||
| 1693 | Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id, | ||
| 1694 | has_active_aruid); | ||
| 1695 | |||
| 1696 | if (result.IsSuccess() && has_active_aruid) { | ||
| 1697 | result = IsVibrationHandleValid(parameters.vibration_device_handle); | ||
| 1698 | } | ||
| 1699 | if (result.IsSuccess() && has_active_aruid) { | ||
| 1700 | gc_device = GetResourceManager()->GetGcVibrationDevice(parameters.vibration_device_handle); | ||
| 1701 | } | ||
| 1702 | if (gc_device != nullptr) { | ||
| 1703 | result = gc_device->SendVibrationGcErmCommand(parameters.gc_erm_command); | ||
| 1704 | } | ||
| 1705 | |||
| 1714 | IPC::ResponseBuilder rb{ctx, 2}; | 1706 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1715 | rb.Push(ResultSuccess); | 1707 | rb.Push(result); |
| 1716 | } | 1708 | } |
| 1717 | 1709 | ||
| 1718 | void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) { | 1710 | void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) { |
| @@ -1725,33 +1717,31 @@ void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) { | |||
| 1725 | 1717 | ||
| 1726 | const auto parameters{rp.PopRaw<Parameters>()}; | 1718 | const auto parameters{rp.PopRaw<Parameters>()}; |
| 1727 | 1719 | ||
| 1728 | const auto last_vibration = GetResourceManager()->GetNpad()->GetLastVibration( | ||
| 1729 | parameters.applet_resource_user_id, parameters.vibration_device_handle); | ||
| 1730 | |||
| 1731 | const auto gc_erm_command = [last_vibration] { | ||
| 1732 | if (last_vibration.low_amplitude != 0.0f || last_vibration.high_amplitude != 0.0f) { | ||
| 1733 | return Core::HID::VibrationGcErmCommand::Start; | ||
| 1734 | } | ||
| 1735 | |||
| 1736 | /** | ||
| 1737 | * Note: This uses yuzu-specific behavior such that the StopHard command produces | ||
| 1738 | * vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined in the HID function | ||
| 1739 | * SendVibrationGcErmCommand, in order to differentiate between Stop and StopHard commands. | ||
| 1740 | * This is done to reuse the controller vibration functions made for regular controllers. | ||
| 1741 | */ | ||
| 1742 | if (last_vibration.low_frequency == 0.0f && last_vibration.high_frequency == 0.0f) { | ||
| 1743 | return Core::HID::VibrationGcErmCommand::StopHard; | ||
| 1744 | } | ||
| 1745 | |||
| 1746 | return Core::HID::VibrationGcErmCommand::Stop; | ||
| 1747 | }(); | ||
| 1748 | |||
| 1749 | LOG_DEBUG(Service_HID, | 1720 | LOG_DEBUG(Service_HID, |
| 1750 | "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", | 1721 | "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", |
| 1751 | parameters.vibration_device_handle.npad_type, | 1722 | parameters.vibration_device_handle.npad_type, |
| 1752 | parameters.vibration_device_handle.npad_id, | 1723 | parameters.vibration_device_handle.npad_id, |
| 1753 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); | 1724 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); |
| 1754 | 1725 | ||
| 1726 | bool has_active_aruid{}; | ||
| 1727 | NpadGcVibrationDevice* gc_device{nullptr}; | ||
| 1728 | Core::HID::VibrationGcErmCommand gc_erm_command{}; | ||
| 1729 | Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id, | ||
| 1730 | has_active_aruid); | ||
| 1731 | |||
| 1732 | if (result.IsSuccess() && has_active_aruid) { | ||
| 1733 | result = IsVibrationHandleValid(parameters.vibration_device_handle); | ||
| 1734 | } | ||
| 1735 | if (result.IsSuccess() && has_active_aruid) { | ||
| 1736 | gc_device = GetResourceManager()->GetGcVibrationDevice(parameters.vibration_device_handle); | ||
| 1737 | } | ||
| 1738 | if (gc_device != nullptr) { | ||
| 1739 | result = gc_device->GetActualVibrationGcErmCommand(gc_erm_command); | ||
| 1740 | } | ||
| 1741 | if (result.IsError()) { | ||
| 1742 | gc_erm_command = Core::HID::VibrationGcErmCommand::Stop; | ||
| 1743 | } | ||
| 1744 | |||
| 1755 | IPC::ResponseBuilder rb{ctx, 4}; | 1745 | IPC::ResponseBuilder rb{ctx, 4}; |
| 1756 | rb.Push(ResultSuccess); | 1746 | rb.Push(ResultSuccess); |
| 1757 | rb.PushEnum(gc_erm_command); | 1747 | rb.PushEnum(gc_erm_command); |
| @@ -1761,21 +1751,24 @@ void IHidServer::BeginPermitVibrationSession(HLERequestContext& ctx) { | |||
| 1761 | IPC::RequestParser rp{ctx}; | 1751 | IPC::RequestParser rp{ctx}; |
| 1762 | const auto applet_resource_user_id{rp.Pop<u64>()}; | 1752 | const auto applet_resource_user_id{rp.Pop<u64>()}; |
| 1763 | 1753 | ||
| 1764 | GetResourceManager()->GetNpad()->SetPermitVibrationSession(true); | ||
| 1765 | |||
| 1766 | LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); | 1754 | LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); |
| 1767 | 1755 | ||
| 1756 | const auto result = | ||
| 1757 | GetResourceManager()->GetNpad()->GetVibrationHandler()->BeginPermitVibrationSession( | ||
| 1758 | applet_resource_user_id); | ||
| 1759 | |||
| 1768 | IPC::ResponseBuilder rb{ctx, 2}; | 1760 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1769 | rb.Push(ResultSuccess); | 1761 | rb.Push(result); |
| 1770 | } | 1762 | } |
| 1771 | 1763 | ||
| 1772 | void IHidServer::EndPermitVibrationSession(HLERequestContext& ctx) { | 1764 | void IHidServer::EndPermitVibrationSession(HLERequestContext& ctx) { |
| 1773 | GetResourceManager()->GetNpad()->SetPermitVibrationSession(false); | ||
| 1774 | |||
| 1775 | LOG_DEBUG(Service_HID, "called"); | 1765 | LOG_DEBUG(Service_HID, "called"); |
| 1776 | 1766 | ||
| 1767 | const auto result = | ||
| 1768 | GetResourceManager()->GetNpad()->GetVibrationHandler()->EndPermitVibrationSession(); | ||
| 1769 | |||
| 1777 | IPC::ResponseBuilder rb{ctx, 2}; | 1770 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1778 | rb.Push(ResultSuccess); | 1771 | rb.Push(result); |
| 1779 | } | 1772 | } |
| 1780 | 1773 | ||
| 1781 | void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) { | 1774 | void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) { |
| @@ -1795,10 +1788,61 @@ void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) { | |||
| 1795 | parameters.vibration_device_handle.npad_id, | 1788 | parameters.vibration_device_handle.npad_id, |
| 1796 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); | 1789 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); |
| 1797 | 1790 | ||
| 1791 | bool is_mounted{}; | ||
| 1792 | NpadVibrationBase* device{nullptr}; | ||
| 1793 | Result result = IsVibrationHandleValid(parameters.vibration_device_handle); | ||
| 1794 | |||
| 1795 | if (result.IsSuccess()) { | ||
| 1796 | device = GetResourceManager()->GetVibrationDevice(parameters.vibration_device_handle); | ||
| 1797 | } | ||
| 1798 | |||
| 1799 | if (device != nullptr) { | ||
| 1800 | is_mounted = device->IsVibrationMounted(); | ||
| 1801 | } | ||
| 1802 | |||
| 1798 | IPC::ResponseBuilder rb{ctx, 3}; | 1803 | IPC::ResponseBuilder rb{ctx, 3}; |
| 1799 | rb.Push(ResultSuccess); | 1804 | rb.Push(result); |
| 1800 | rb.Push(GetResourceManager()->GetNpad()->IsVibrationDeviceMounted( | 1805 | rb.Push(is_mounted); |
| 1801 | parameters.applet_resource_user_id, parameters.vibration_device_handle)); | 1806 | } |
| 1807 | |||
| 1808 | void IHidServer::SendVibrationValueInBool(HLERequestContext& ctx) { | ||
| 1809 | IPC::RequestParser rp{ctx}; | ||
| 1810 | struct Parameters { | ||
| 1811 | Core::HID::VibrationDeviceHandle vibration_device_handle; | ||
| 1812 | INSERT_PADDING_WORDS_NOINIT(1); | ||
| 1813 | u64 applet_resource_user_id; | ||
| 1814 | bool is_vibrating; | ||
| 1815 | }; | ||
| 1816 | static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); | ||
| 1817 | |||
| 1818 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 1819 | |||
| 1820 | LOG_DEBUG(Service_HID, | ||
| 1821 | "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, " | ||
| 1822 | "is_vibrating={}", | ||
| 1823 | parameters.vibration_device_handle.npad_type, | ||
| 1824 | parameters.vibration_device_handle.npad_id, | ||
| 1825 | parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id, | ||
| 1826 | parameters.is_vibrating); | ||
| 1827 | |||
| 1828 | bool has_active_aruid{}; | ||
| 1829 | NpadN64VibrationDevice* n64_device{nullptr}; | ||
| 1830 | Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id, | ||
| 1831 | has_active_aruid); | ||
| 1832 | |||
| 1833 | if (result.IsSuccess() && has_active_aruid) { | ||
| 1834 | result = IsVibrationHandleValid(parameters.vibration_device_handle); | ||
| 1835 | } | ||
| 1836 | if (result.IsSuccess() && has_active_aruid) { | ||
| 1837 | n64_device = | ||
| 1838 | GetResourceManager()->GetN64VibrationDevice(parameters.vibration_device_handle); | ||
| 1839 | } | ||
| 1840 | if (n64_device != nullptr) { | ||
| 1841 | result = n64_device->SendValueInBool(parameters.is_vibrating); | ||
| 1842 | } | ||
| 1843 | |||
| 1844 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1845 | rb.Push(result); | ||
| 1802 | } | 1846 | } |
| 1803 | 1847 | ||
| 1804 | void IHidServer::ActivateConsoleSixAxisSensor(HLERequestContext& ctx) { | 1848 | void IHidServer::ActivateConsoleSixAxisSensor(HLERequestContext& ctx) { |
diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index cc7c4ebdd..3a2e0a230 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h | |||
| @@ -97,6 +97,7 @@ private: | |||
| 97 | void BeginPermitVibrationSession(HLERequestContext& ctx); | 97 | void BeginPermitVibrationSession(HLERequestContext& ctx); |
| 98 | void EndPermitVibrationSession(HLERequestContext& ctx); | 98 | void EndPermitVibrationSession(HLERequestContext& ctx); |
| 99 | void IsVibrationDeviceMounted(HLERequestContext& ctx); | 99 | void IsVibrationDeviceMounted(HLERequestContext& ctx); |
| 100 | void SendVibrationValueInBool(HLERequestContext& ctx); | ||
| 100 | void ActivateConsoleSixAxisSensor(HLERequestContext& ctx); | 101 | void ActivateConsoleSixAxisSensor(HLERequestContext& ctx); |
| 101 | void StartConsoleSixAxisSensor(HLERequestContext& ctx); | 102 | void StartConsoleSixAxisSensor(HLERequestContext& ctx); |
| 102 | void StopConsoleSixAxisSensor(HLERequestContext& ctx); | 103 | void StopConsoleSixAxisSensor(HLERequestContext& ctx); |
diff --git a/src/core/hle/service/hid/hid_system_server.cpp b/src/core/hle/service/hid/hid_system_server.cpp index 3a0cb3cb1..4466a189b 100644 --- a/src/core/hle/service/hid/hid_system_server.cpp +++ b/src/core/hle/service/hid/hid_system_server.cpp | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include "hid_core/resource_manager.h" | 7 | #include "hid_core/resource_manager.h" |
| 8 | #include "hid_core/resources/npad/npad.h" | 8 | #include "hid_core/resources/npad/npad.h" |
| 9 | #include "hid_core/resources/npad/npad_types.h" | 9 | #include "hid_core/resources/npad/npad_types.h" |
| 10 | #include "hid_core/resources/npad/npad_vibration.h" | ||
| 10 | #include "hid_core/resources/palma/palma.h" | 11 | #include "hid_core/resources/palma/palma.h" |
| 11 | #include "hid_core/resources/touch_screen/touch_screen.h" | 12 | #include "hid_core/resources/touch_screen/touch_screen.h" |
| 12 | 13 | ||
| @@ -67,14 +68,14 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour | |||
| 67 | {501, &IHidSystemServer::RegisterAppletResourceUserId, "RegisterAppletResourceUserId"}, | 68 | {501, &IHidSystemServer::RegisterAppletResourceUserId, "RegisterAppletResourceUserId"}, |
| 68 | {502, &IHidSystemServer::UnregisterAppletResourceUserId, "UnregisterAppletResourceUserId"}, | 69 | {502, &IHidSystemServer::UnregisterAppletResourceUserId, "UnregisterAppletResourceUserId"}, |
| 69 | {503, &IHidSystemServer::EnableAppletToGetInput, "EnableAppletToGetInput"}, | 70 | {503, &IHidSystemServer::EnableAppletToGetInput, "EnableAppletToGetInput"}, |
| 70 | {504, nullptr, "SetAruidValidForVibration"}, | 71 | {504, &IHidSystemServer::SetAruidValidForVibration, "SetAruidValidForVibration"}, |
| 71 | {505, &IHidSystemServer::EnableAppletToGetSixAxisSensor, "EnableAppletToGetSixAxisSensor"}, | 72 | {505, &IHidSystemServer::EnableAppletToGetSixAxisSensor, "EnableAppletToGetSixAxisSensor"}, |
| 72 | {506, &IHidSystemServer::EnableAppletToGetPadInput, "EnableAppletToGetPadInput"}, | 73 | {506, &IHidSystemServer::EnableAppletToGetPadInput, "EnableAppletToGetPadInput"}, |
| 73 | {507, &IHidSystemServer::EnableAppletToGetTouchScreen, "EnableAppletToGetTouchScreen"}, | 74 | {507, &IHidSystemServer::EnableAppletToGetTouchScreen, "EnableAppletToGetTouchScreen"}, |
| 74 | {510, nullptr, "SetVibrationMasterVolume"}, | 75 | {510, &IHidSystemServer::SetVibrationMasterVolume, "SetVibrationMasterVolume"}, |
| 75 | {511, nullptr, "GetVibrationMasterVolume"}, | 76 | {511, &IHidSystemServer::GetVibrationMasterVolume, "GetVibrationMasterVolume"}, |
| 76 | {512, nullptr, "BeginPermitVibrationSession"}, | 77 | {512, &IHidSystemServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, |
| 77 | {513, nullptr, "EndPermitVibrationSession"}, | 78 | {513, &IHidSystemServer::EndPermitVibrationSession, "EndPermitVibrationSession"}, |
| 78 | {514, nullptr, "Unknown514"}, | 79 | {514, nullptr, "Unknown514"}, |
| 79 | {520, nullptr, "EnableHandheldHids"}, | 80 | {520, nullptr, "EnableHandheldHids"}, |
| 80 | {521, nullptr, "DisableHandheldHids"}, | 81 | {521, nullptr, "DisableHandheldHids"}, |
| @@ -156,7 +157,7 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour | |||
| 156 | {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, | 157 | {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, |
| 157 | {1153, &IHidSystemServer::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"}, | 158 | {1153, &IHidSystemServer::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"}, |
| 158 | {1154, nullptr, "IsFirmwareAvailableForNotification"}, | 159 | {1154, nullptr, "IsFirmwareAvailableForNotification"}, |
| 159 | {1155, nullptr, "SetForceHandheldStyleVibration"}, | 160 | {1155, &IHidSystemServer::SetForceHandheldStyleVibration, "SetForceHandheldStyleVibration"}, |
| 160 | {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, | 161 | {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, |
| 161 | {1157, nullptr, "CancelConnectionTrigger"}, | 162 | {1157, nullptr, "CancelConnectionTrigger"}, |
| 162 | {1200, nullptr, "IsButtonConfigSupported"}, | 163 | {1200, nullptr, "IsButtonConfigSupported"}, |
| @@ -538,6 +539,27 @@ void IHidSystemServer::EnableAppletToGetInput(HLERequestContext& ctx) { | |||
| 538 | rb.Push(ResultSuccess); | 539 | rb.Push(ResultSuccess); |
| 539 | } | 540 | } |
| 540 | 541 | ||
| 542 | void IHidSystemServer::SetAruidValidForVibration(HLERequestContext& ctx) { | ||
| 543 | IPC::RequestParser rp{ctx}; | ||
| 544 | struct Parameters { | ||
| 545 | bool is_enabled; | ||
| 546 | INSERT_PADDING_WORDS_NOINIT(1); | ||
| 547 | u64 applet_resource_user_id; | ||
| 548 | }; | ||
| 549 | static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); | ||
| 550 | |||
| 551 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 552 | |||
| 553 | LOG_INFO(Service_HID, "called, is_enabled={}, applet_resource_user_id={}", | ||
| 554 | parameters.is_enabled, parameters.applet_resource_user_id); | ||
| 555 | |||
| 556 | GetResourceManager()->SetAruidValidForVibration(parameters.applet_resource_user_id, | ||
| 557 | parameters.is_enabled); | ||
| 558 | |||
| 559 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 560 | rb.Push(ResultSuccess); | ||
| 561 | } | ||
| 562 | |||
| 541 | void IHidSystemServer::EnableAppletToGetSixAxisSensor(HLERequestContext& ctx) { | 563 | void IHidSystemServer::EnableAppletToGetSixAxisSensor(HLERequestContext& ctx) { |
| 542 | IPC::RequestParser rp{ctx}; | 564 | IPC::RequestParser rp{ctx}; |
| 543 | struct Parameters { | 565 | struct Parameters { |
| @@ -601,6 +623,57 @@ void IHidSystemServer::EnableAppletToGetTouchScreen(HLERequestContext& ctx) { | |||
| 601 | rb.Push(ResultSuccess); | 623 | rb.Push(ResultSuccess); |
| 602 | } | 624 | } |
| 603 | 625 | ||
| 626 | void IHidSystemServer::SetVibrationMasterVolume(HLERequestContext& ctx) { | ||
| 627 | IPC::RequestParser rp{ctx}; | ||
| 628 | const auto master_volume{rp.Pop<f32>()}; | ||
| 629 | |||
| 630 | LOG_INFO(Service_HID, "called, volume={}", master_volume); | ||
| 631 | |||
| 632 | const auto result = | ||
| 633 | GetResourceManager()->GetNpad()->GetVibrationHandler()->SetVibrationMasterVolume( | ||
| 634 | master_volume); | ||
| 635 | |||
| 636 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 637 | rb.Push(result); | ||
| 638 | } | ||
| 639 | |||
| 640 | void IHidSystemServer::GetVibrationMasterVolume(HLERequestContext& ctx) { | ||
| 641 | f32 master_volume{}; | ||
| 642 | const auto result = | ||
| 643 | GetResourceManager()->GetNpad()->GetVibrationHandler()->GetVibrationMasterVolume( | ||
| 644 | master_volume); | ||
| 645 | |||
| 646 | LOG_INFO(Service_HID, "called, volume={}", master_volume); | ||
| 647 | |||
| 648 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 649 | rb.Push(result); | ||
| 650 | rb.Push(master_volume); | ||
| 651 | } | ||
| 652 | |||
| 653 | void IHidSystemServer::BeginPermitVibrationSession(HLERequestContext& ctx) { | ||
| 654 | IPC::RequestParser rp{ctx}; | ||
| 655 | const auto applet_resource_user_id{rp.Pop<u64>()}; | ||
| 656 | |||
| 657 | LOG_INFO(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); | ||
| 658 | |||
| 659 | const auto result = | ||
| 660 | GetResourceManager()->GetNpad()->GetVibrationHandler()->BeginPermitVibrationSession( | ||
| 661 | applet_resource_user_id); | ||
| 662 | |||
| 663 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 664 | rb.Push(result); | ||
| 665 | } | ||
| 666 | |||
| 667 | void IHidSystemServer::EndPermitVibrationSession(HLERequestContext& ctx) { | ||
| 668 | LOG_INFO(Service_HID, "called"); | ||
| 669 | |||
| 670 | const auto result = | ||
| 671 | GetResourceManager()->GetNpad()->GetVibrationHandler()->EndPermitVibrationSession(); | ||
| 672 | |||
| 673 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 674 | rb.Push(result); | ||
| 675 | } | ||
| 676 | |||
| 604 | void IHidSystemServer::IsJoyConAttachedOnAllRail(HLERequestContext& ctx) { | 677 | void IHidSystemServer::IsJoyConAttachedOnAllRail(HLERequestContext& ctx) { |
| 605 | const bool is_attached = true; | 678 | const bool is_attached = true; |
| 606 | 679 | ||
| @@ -749,6 +822,19 @@ void IHidSystemServer::GetTouchScreenDefaultConfiguration(HLERequestContext& ctx | |||
| 749 | rb.PushRaw(touchscreen_config); | 822 | rb.PushRaw(touchscreen_config); |
| 750 | } | 823 | } |
| 751 | 824 | ||
| 825 | void IHidSystemServer::SetForceHandheldStyleVibration(HLERequestContext& ctx) { | ||
| 826 | IPC::RequestParser rp{ctx}; | ||
| 827 | const auto is_forced{rp.Pop<bool>()}; | ||
| 828 | |||
| 829 | LOG_INFO(Service_HID, "called, is_forced={}", is_forced); | ||
| 830 | |||
| 831 | GetResourceManager()->SetForceHandheldStyleVibration(is_forced); | ||
| 832 | GetResourceManager()->GetNpad()->UpdateHandheldAbstractState(); | ||
| 833 | |||
| 834 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 835 | rb.Push(ResultSuccess); | ||
| 836 | } | ||
| 837 | |||
| 752 | void IHidSystemServer::IsUsingCustomButtonConfig(HLERequestContext& ctx) { | 838 | void IHidSystemServer::IsUsingCustomButtonConfig(HLERequestContext& ctx) { |
| 753 | const bool is_enabled = false; | 839 | const bool is_enabled = false; |
| 754 | 840 | ||
diff --git a/src/core/hle/service/hid/hid_system_server.h b/src/core/hle/service/hid/hid_system_server.h index 0c2634e3f..90a719f02 100644 --- a/src/core/hle/service/hid/hid_system_server.h +++ b/src/core/hle/service/hid/hid_system_server.h | |||
| @@ -42,9 +42,14 @@ private: | |||
| 42 | void RegisterAppletResourceUserId(HLERequestContext& ctx); | 42 | void RegisterAppletResourceUserId(HLERequestContext& ctx); |
| 43 | void UnregisterAppletResourceUserId(HLERequestContext& ctx); | 43 | void UnregisterAppletResourceUserId(HLERequestContext& ctx); |
| 44 | void EnableAppletToGetInput(HLERequestContext& ctx); | 44 | void EnableAppletToGetInput(HLERequestContext& ctx); |
| 45 | void SetAruidValidForVibration(HLERequestContext& ctx); | ||
| 45 | void EnableAppletToGetSixAxisSensor(HLERequestContext& ctx); | 46 | void EnableAppletToGetSixAxisSensor(HLERequestContext& ctx); |
| 46 | void EnableAppletToGetPadInput(HLERequestContext& ctx); | 47 | void EnableAppletToGetPadInput(HLERequestContext& ctx); |
| 47 | void EnableAppletToGetTouchScreen(HLERequestContext& ctx); | 48 | void EnableAppletToGetTouchScreen(HLERequestContext& ctx); |
| 49 | void SetVibrationMasterVolume(HLERequestContext& ctx); | ||
| 50 | void GetVibrationMasterVolume(HLERequestContext& ctx); | ||
| 51 | void BeginPermitVibrationSession(HLERequestContext& ctx); | ||
| 52 | void EndPermitVibrationSession(HLERequestContext& ctx); | ||
| 48 | void IsJoyConAttachedOnAllRail(HLERequestContext& ctx); | 53 | void IsJoyConAttachedOnAllRail(HLERequestContext& ctx); |
| 49 | void AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx); | 54 | void AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx); |
| 50 | void AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx); | 55 | void AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx); |
| @@ -61,6 +66,7 @@ private: | |||
| 61 | void FinalizeUsbFirmwareUpdate(HLERequestContext& ctx); | 66 | void FinalizeUsbFirmwareUpdate(HLERequestContext& ctx); |
| 62 | void InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx); | 67 | void InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx); |
| 63 | void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx); | 68 | void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx); |
| 69 | void SetForceHandheldStyleVibration(HLERequestContext& ctx); | ||
| 64 | void IsUsingCustomButtonConfig(HLERequestContext& ctx); | 70 | void IsUsingCustomButtonConfig(HLERequestContext& ctx); |
| 65 | 71 | ||
| 66 | std::shared_ptr<ResourceManager> GetResourceManager(); | 72 | std::shared_ptr<ResourceManager> GetResourceManager(); |
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp index 0469110e8..af6591370 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.cpp +++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp | |||
| @@ -112,9 +112,7 @@ void Nvnflinger::ShutdownLayers() { | |||
| 112 | { | 112 | { |
| 113 | const auto lock_guard = Lock(); | 113 | const auto lock_guard = Lock(); |
| 114 | for (auto& display : displays) { | 114 | for (auto& display : displays) { |
| 115 | for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) { | 115 | display.Abandon(); |
| 116 | display.GetLayer(layer).GetConsumer().Abandon(); | ||
| 117 | } | ||
| 118 | } | 116 | } |
| 119 | 117 | ||
| 120 | is_abandoned = true; | 118 | is_abandoned = true; |
| @@ -176,24 +174,28 @@ void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id) { | |||
| 176 | display.CreateLayer(layer_id, buffer_id, nvdrv->container); | 174 | display.CreateLayer(layer_id, buffer_id, nvdrv->container); |
| 177 | } | 175 | } |
| 178 | 176 | ||
| 179 | void Nvnflinger::OpenLayer(u64 layer_id) { | 177 | bool Nvnflinger::OpenLayer(u64 layer_id) { |
| 180 | const auto lock_guard = Lock(); | 178 | const auto lock_guard = Lock(); |
| 181 | 179 | ||
| 182 | for (auto& display : displays) { | 180 | for (auto& display : displays) { |
| 183 | if (auto* layer = display.FindLayer(layer_id); layer) { | 181 | if (auto* layer = display.FindLayer(layer_id); layer) { |
| 184 | layer->Open(); | 182 | return layer->Open(); |
| 185 | } | 183 | } |
| 186 | } | 184 | } |
| 185 | |||
| 186 | return false; | ||
| 187 | } | 187 | } |
| 188 | 188 | ||
| 189 | void Nvnflinger::CloseLayer(u64 layer_id) { | 189 | bool Nvnflinger::CloseLayer(u64 layer_id) { |
| 190 | const auto lock_guard = Lock(); | 190 | const auto lock_guard = Lock(); |
| 191 | 191 | ||
| 192 | for (auto& display : displays) { | 192 | for (auto& display : displays) { |
| 193 | if (auto* layer = display.FindLayer(layer_id); layer) { | 193 | if (auto* layer = display.FindLayer(layer_id); layer) { |
| 194 | layer->Close(); | 194 | return layer->Close(); |
| 195 | } | 195 | } |
| 196 | } | 196 | } |
| 197 | |||
| 198 | return false; | ||
| 197 | } | 199 | } |
| 198 | 200 | ||
| 199 | void Nvnflinger::DestroyLayer(u64 layer_id) { | 201 | void Nvnflinger::DestroyLayer(u64 layer_id) { |
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h index 871285764..a60e0ae6b 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.h +++ b/src/core/hle/service/nvnflinger/nvnflinger.h | |||
| @@ -74,10 +74,10 @@ public: | |||
| 74 | [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id); | 74 | [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id); |
| 75 | 75 | ||
| 76 | /// Opens a layer on all displays for the given layer ID. | 76 | /// Opens a layer on all displays for the given layer ID. |
| 77 | void OpenLayer(u64 layer_id); | 77 | bool OpenLayer(u64 layer_id); |
| 78 | 78 | ||
| 79 | /// Closes a layer on all displays for the given layer ID. | 79 | /// Closes a layer on all displays for the given layer ID. |
| 80 | void CloseLayer(u64 layer_id); | 80 | bool CloseLayer(u64 layer_id); |
| 81 | 81 | ||
| 82 | /// Destroys the given layer ID. | 82 | /// Destroys the given layer ID. |
| 83 | void DestroyLayer(u64 layer_id); | 83 | void DestroyLayer(u64 layer_id); |
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index e2d9cd98a..725311c53 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp | |||
| @@ -91,6 +91,10 @@ void Display::CreateLayer(u64 layer_id, u32 binder_id, | |||
| 91 | layers.emplace_back(std::make_unique<Layer>(layer_id, binder_id, *core, *producer, | 91 | layers.emplace_back(std::make_unique<Layer>(layer_id, binder_id, *core, *producer, |
| 92 | std::move(buffer_item_consumer))); | 92 | std::move(buffer_item_consumer))); |
| 93 | 93 | ||
| 94 | if (is_abandoned) { | ||
| 95 | this->FindLayer(layer_id)->GetConsumer().Abandon(); | ||
| 96 | } | ||
| 97 | |||
| 94 | hos_binder_driver_server.RegisterProducer(std::move(producer)); | 98 | hos_binder_driver_server.RegisterProducer(std::move(producer)); |
| 95 | } | 99 | } |
| 96 | 100 | ||
| @@ -103,6 +107,13 @@ void Display::DestroyLayer(u64 layer_id) { | |||
| 103 | [layer_id](const auto& layer) { return layer->GetLayerId() == layer_id; }); | 107 | [layer_id](const auto& layer) { return layer->GetLayerId() == layer_id; }); |
| 104 | } | 108 | } |
| 105 | 109 | ||
| 110 | void Display::Abandon() { | ||
| 111 | for (auto& layer : layers) { | ||
| 112 | layer->GetConsumer().Abandon(); | ||
| 113 | } | ||
| 114 | is_abandoned = true; | ||
| 115 | } | ||
| 116 | |||
| 106 | Layer* Display::FindLayer(u64 layer_id) { | 117 | Layer* Display::FindLayer(u64 layer_id) { |
| 107 | const auto itr = | 118 | const auto itr = |
| 108 | std::find_if(layers.begin(), layers.end(), [layer_id](const std::unique_ptr<Layer>& layer) { | 119 | std::find_if(layers.begin(), layers.end(), [layer_id](const std::unique_ptr<Layer>& layer) { |
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 7e68ee79b..8eb8a5155 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h | |||
| @@ -98,6 +98,8 @@ public: | |||
| 98 | layers.clear(); | 98 | layers.clear(); |
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | void Abandon(); | ||
| 102 | |||
| 101 | /// Attempts to find a layer with the given ID. | 103 | /// Attempts to find a layer with the given ID. |
| 102 | /// | 104 | /// |
| 103 | /// @param layer_id The layer ID. | 105 | /// @param layer_id The layer ID. |
| @@ -124,6 +126,7 @@ private: | |||
| 124 | 126 | ||
| 125 | std::vector<std::unique_ptr<Layer>> layers; | 127 | std::vector<std::unique_ptr<Layer>> layers; |
| 126 | Kernel::KEvent* vsync_event{}; | 128 | Kernel::KEvent* vsync_event{}; |
| 129 | bool is_abandoned{}; | ||
| 127 | }; | 130 | }; |
| 128 | 131 | ||
| 129 | } // namespace Service::VI | 132 | } // namespace Service::VI |
diff --git a/src/core/hle/service/vi/layer/vi_layer.h b/src/core/hle/service/vi/layer/vi_layer.h index 295005e23..f95e2dc71 100644 --- a/src/core/hle/service/vi/layer/vi_layer.h +++ b/src/core/hle/service/vi/layer/vi_layer.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <memory> | 6 | #include <memory> |
| 7 | #include <utility> | ||
| 7 | 8 | ||
| 8 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 9 | 10 | ||
| @@ -75,12 +76,12 @@ public: | |||
| 75 | return open; | 76 | return open; |
| 76 | } | 77 | } |
| 77 | 78 | ||
| 78 | void Close() { | 79 | bool Close() { |
| 79 | open = false; | 80 | return std::exchange(open, false); |
| 80 | } | 81 | } |
| 81 | 82 | ||
| 82 | void Open() { | 83 | bool Open() { |
| 83 | open = true; | 84 | return !std::exchange(open, true); |
| 84 | } | 85 | } |
| 85 | 86 | ||
| 86 | private: | 87 | private: |
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 39d5be90d..bfcc27ddc 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp | |||
| @@ -719,7 +719,12 @@ private: | |||
| 719 | return; | 719 | return; |
| 720 | } | 720 | } |
| 721 | 721 | ||
| 722 | nvnflinger.OpenLayer(layer_id); | 722 | if (!nvnflinger.OpenLayer(layer_id)) { |
| 723 | LOG_WARNING(Service_VI, "Tried to open layer which was already open"); | ||
| 724 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 725 | rb.Push(ResultOperationFailed); | ||
| 726 | return; | ||
| 727 | } | ||
| 723 | 728 | ||
| 724 | android::OutputParcel parcel; | 729 | android::OutputParcel parcel; |
| 725 | parcel.WriteInterface(NativeWindow{*buffer_queue_id}); | 730 | parcel.WriteInterface(NativeWindow{*buffer_queue_id}); |
| @@ -737,7 +742,12 @@ private: | |||
| 737 | 742 | ||
| 738 | LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}", layer_id); | 743 | LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}", layer_id); |
| 739 | 744 | ||
| 740 | nvnflinger.CloseLayer(layer_id); | 745 | if (!nvnflinger.CloseLayer(layer_id)) { |
| 746 | LOG_WARNING(Service_VI, "Tried to close layer which was not open"); | ||
| 747 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 748 | rb.Push(ResultOperationFailed); | ||
| 749 | return; | ||
| 750 | } | ||
| 741 | 751 | ||
| 742 | IPC::ResponseBuilder rb{ctx, 2}; | 752 | IPC::ResponseBuilder rb{ctx, 2}; |
| 743 | rb.Push(ResultSuccess); | 753 | rb.Push(ResultSuccess); |
diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt index 22e9337c4..94d8cc4c3 100644 --- a/src/frontend_common/CMakeLists.txt +++ b/src/frontend_common/CMakeLists.txt | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | add_library(frontend_common STATIC | 4 | add_library(frontend_common STATIC |
| 5 | config.cpp | 5 | config.cpp |
| 6 | config.h | 6 | config.h |
| 7 | content_manager.h | ||
| 7 | ) | 8 | ) |
| 8 | 9 | ||
| 9 | create_target_directory_groups(frontend_common) | 10 | create_target_directory_groups(frontend_common) |
diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h new file mode 100644 index 000000000..23f2979db --- /dev/null +++ b/src/frontend_common/content_manager.h | |||
| @@ -0,0 +1,238 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <boost/algorithm/string.hpp> | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/core.h" | ||
| 10 | #include "core/file_sys/common_funcs.h" | ||
| 11 | #include "core/file_sys/content_archive.h" | ||
| 12 | #include "core/file_sys/mode.h" | ||
| 13 | #include "core/file_sys/nca_metadata.h" | ||
| 14 | #include "core/file_sys/registered_cache.h" | ||
| 15 | #include "core/file_sys/submission_package.h" | ||
| 16 | #include "core/hle/service/filesystem/filesystem.h" | ||
| 17 | #include "core/loader/loader.h" | ||
| 18 | |||
| 19 | namespace ContentManager { | ||
| 20 | |||
| 21 | enum class InstallResult { | ||
| 22 | Success, | ||
| 23 | Overwrite, | ||
| 24 | Failure, | ||
| 25 | BaseInstallAttempted, | ||
| 26 | }; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * \brief Removes a single installed DLC | ||
| 30 | * \param fs_controller [FileSystemController] reference from the Core::System instance | ||
| 31 | * \param title_id Unique title ID representing the DLC which will be removed | ||
| 32 | * \return 'true' if successful | ||
| 33 | */ | ||
| 34 | inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_controller, | ||
| 35 | const u64 title_id) { | ||
| 36 | return fs_controller.GetUserNANDContents()->RemoveExistingEntry(title_id) || | ||
| 37 | fs_controller.GetSDMCContents()->RemoveExistingEntry(title_id); | ||
| 38 | } | ||
| 39 | |||
| 40 | /** | ||
| 41 | * \brief Removes all DLC for a game | ||
| 42 | * \param system Raw pointer to the system instance | ||
| 43 | * \param program_id Program ID for the game that will have all of its DLC removed | ||
| 44 | * \return Number of DLC removed | ||
| 45 | */ | ||
| 46 | inline size_t RemoveAllDLC(Core::System* system, const u64 program_id) { | ||
| 47 | size_t count{}; | ||
| 48 | const auto& fs_controller = system->GetFileSystemController(); | ||
| 49 | const auto dlc_entries = system->GetContentProvider().ListEntriesFilter( | ||
| 50 | FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); | ||
| 51 | std::vector<u64> program_dlc_entries; | ||
| 52 | |||
| 53 | for (const auto& entry : dlc_entries) { | ||
| 54 | if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { | ||
| 55 | program_dlc_entries.push_back(entry.title_id); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | for (const auto& entry : program_dlc_entries) { | ||
| 60 | if (RemoveDLC(fs_controller, entry)) { | ||
| 61 | ++count; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | return count; | ||
| 65 | } | ||
| 66 | |||
| 67 | /** | ||
| 68 | * \brief Removes the installed update for a game | ||
| 69 | * \param fs_controller [FileSystemController] reference from the Core::System instance | ||
| 70 | * \param program_id Program ID for the game that will have its installed update removed | ||
| 71 | * \return 'true' if successful | ||
| 72 | */ | ||
| 73 | inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller, | ||
| 74 | const u64 program_id) { | ||
| 75 | const auto update_id = program_id | 0x800; | ||
| 76 | return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || | ||
| 77 | fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); | ||
| 78 | } | ||
| 79 | |||
| 80 | /** | ||
| 81 | * \brief Removes the base content for a game | ||
| 82 | * \param fs_controller [FileSystemController] reference from the Core::System instance | ||
| 83 | * \param program_id Program ID for the game that will have its base content removed | ||
| 84 | * \return 'true' if successful | ||
| 85 | */ | ||
| 86 | inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& fs_controller, | ||
| 87 | const u64 program_id) { | ||
| 88 | return fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) || | ||
| 89 | fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id); | ||
| 90 | } | ||
| 91 | |||
| 92 | /** | ||
| 93 | * \brief Removes a mod for a game | ||
| 94 | * \param fs_controller [FileSystemController] reference from the Core::System instance | ||
| 95 | * \param program_id Program ID for the game where [mod_name] will be removed | ||
| 96 | * \param mod_name The name of a mod as given by FileSys::PatchManager::GetPatches. This corresponds | ||
| 97 | * with the name of the mod's directory in a game's load folder. | ||
| 98 | * \return 'true' if successful | ||
| 99 | */ | ||
| 100 | inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller, | ||
| 101 | const u64 program_id, const std::string& mod_name) { | ||
| 102 | // Check general Mods (LayeredFS and IPS) | ||
| 103 | const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id); | ||
| 104 | if (mod_dir != nullptr) { | ||
| 105 | return mod_dir->DeleteSubdirectoryRecursive(mod_name); | ||
| 106 | } | ||
| 107 | |||
| 108 | // Check SDMC mod directory (RomFS LayeredFS) | ||
| 109 | const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id); | ||
| 110 | if (sdmc_mod_dir != nullptr) { | ||
| 111 | return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name); | ||
| 112 | } | ||
| 113 | |||
| 114 | return false; | ||
| 115 | } | ||
| 116 | |||
| 117 | /** | ||
| 118 | * \brief Installs an NSP | ||
| 119 | * \param system Raw pointer to the system instance | ||
| 120 | * \param vfs Raw pointer to the VfsFilesystem instance in Core::System | ||
| 121 | * \param filename Path to the NSP file | ||
| 122 | * \param callback Optional callback to report the progress of the installation. The first size_t | ||
| 123 | * parameter is the total size of the virtual file and the second is the current progress. If you | ||
| 124 | * return false to the callback, it will cancel the installation as soon as possible. | ||
| 125 | * \return [InstallResult] representing how the installation finished | ||
| 126 | */ | ||
| 127 | inline InstallResult InstallNSP( | ||
| 128 | Core::System* system, FileSys::VfsFilesystem* vfs, const std::string& filename, | ||
| 129 | const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | ||
| 130 | const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, | ||
| 131 | std::size_t block_size) { | ||
| 132 | if (src == nullptr || dest == nullptr) { | ||
| 133 | return false; | ||
| 134 | } | ||
| 135 | if (!dest->Resize(src->GetSize())) { | ||
| 136 | return false; | ||
| 137 | } | ||
| 138 | |||
| 139 | using namespace Common::Literals; | ||
| 140 | std::vector<u8> buffer(1_MiB); | ||
| 141 | |||
| 142 | for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { | ||
| 143 | if (callback(src->GetSize(), i)) { | ||
| 144 | dest->Resize(0); | ||
| 145 | return false; | ||
| 146 | } | ||
| 147 | const auto read = src->Read(buffer.data(), buffer.size(), i); | ||
| 148 | dest->Write(buffer.data(), read, i); | ||
| 149 | } | ||
| 150 | return true; | ||
| 151 | }; | ||
| 152 | |||
| 153 | std::shared_ptr<FileSys::NSP> nsp; | ||
| 154 | FileSys::VirtualFile file = vfs->OpenFile(filename, FileSys::Mode::Read); | ||
| 155 | if (boost::to_lower_copy(file->GetName()).ends_with(std::string("nsp"))) { | ||
| 156 | nsp = std::make_shared<FileSys::NSP>(file); | ||
| 157 | if (nsp->IsExtractedType()) { | ||
| 158 | return InstallResult::Failure; | ||
| 159 | } | ||
| 160 | } else { | ||
| 161 | return InstallResult::Failure; | ||
| 162 | } | ||
| 163 | |||
| 164 | if (nsp->GetStatus() != Loader::ResultStatus::Success) { | ||
| 165 | return InstallResult::Failure; | ||
| 166 | } | ||
| 167 | const auto res = | ||
| 168 | system->GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy); | ||
| 169 | switch (res) { | ||
| 170 | case FileSys::InstallResult::Success: | ||
| 171 | return InstallResult::Success; | ||
| 172 | case FileSys::InstallResult::OverwriteExisting: | ||
| 173 | return InstallResult::Overwrite; | ||
| 174 | case FileSys::InstallResult::ErrorBaseInstall: | ||
| 175 | return InstallResult::BaseInstallAttempted; | ||
| 176 | default: | ||
| 177 | return InstallResult::Failure; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | /** | ||
| 182 | * \brief Installs an NCA | ||
| 183 | * \param vfs Raw pointer to the VfsFilesystem instance in Core::System | ||
| 184 | * \param filename Path to the NCA file | ||
| 185 | * \param registered_cache Raw pointer to the registered cache that the NCA will be installed to | ||
| 186 | * \param title_type Type of NCA package to install | ||
| 187 | * \param callback Optional callback to report the progress of the installation. The first size_t | ||
| 188 | * parameter is the total size of the virtual file and the second is the current progress. If you | ||
| 189 | * return false to the callback, it will cancel the installation as soon as possible. | ||
| 190 | * \return [InstallResult] representing how the installation finished | ||
| 191 | */ | ||
| 192 | inline InstallResult InstallNCA( | ||
| 193 | FileSys::VfsFilesystem* vfs, const std::string& filename, | ||
| 194 | FileSys::RegisteredCache* registered_cache, const FileSys::TitleType title_type, | ||
| 195 | const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | ||
| 196 | const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, | ||
| 197 | std::size_t block_size) { | ||
| 198 | if (src == nullptr || dest == nullptr) { | ||
| 199 | return false; | ||
| 200 | } | ||
| 201 | if (!dest->Resize(src->GetSize())) { | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | using namespace Common::Literals; | ||
| 206 | std::vector<u8> buffer(1_MiB); | ||
| 207 | |||
| 208 | for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { | ||
| 209 | if (callback(src->GetSize(), i)) { | ||
| 210 | dest->Resize(0); | ||
| 211 | return false; | ||
| 212 | } | ||
| 213 | const auto read = src->Read(buffer.data(), buffer.size(), i); | ||
| 214 | dest->Write(buffer.data(), read, i); | ||
| 215 | } | ||
| 216 | return true; | ||
| 217 | }; | ||
| 218 | |||
| 219 | const auto nca = std::make_shared<FileSys::NCA>(vfs->OpenFile(filename, FileSys::Mode::Read)); | ||
| 220 | const auto id = nca->GetStatus(); | ||
| 221 | |||
| 222 | // Game updates necessary are missing base RomFS | ||
| 223 | if (id != Loader::ResultStatus::Success && | ||
| 224 | id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { | ||
| 225 | return InstallResult::Failure; | ||
| 226 | } | ||
| 227 | |||
| 228 | const auto res = registered_cache->InstallEntry(*nca, title_type, true, copy); | ||
| 229 | if (res == FileSys::InstallResult::Success) { | ||
| 230 | return InstallResult::Success; | ||
| 231 | } else if (res == FileSys::InstallResult::OverwriteExisting) { | ||
| 232 | return InstallResult::Overwrite; | ||
| 233 | } else { | ||
| 234 | return InstallResult::Failure; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | } // namespace ContentManager | ||
diff --git a/src/hid_core/frontend/emulated_controller.cpp b/src/hid_core/frontend/emulated_controller.cpp index b001cf2fd..f2499502d 100644 --- a/src/hid_core/frontend/emulated_controller.cpp +++ b/src/hid_core/frontend/emulated_controller.cpp | |||
| @@ -145,8 +145,8 @@ void EmulatedController::ReloadColorsFromSettings() { | |||
| 145 | 145 | ||
| 146 | void EmulatedController::LoadDevices() { | 146 | void EmulatedController::LoadDevices() { |
| 147 | // TODO(german77): Use more buttons to detect the correct device | 147 | // TODO(german77): Use more buttons to detect the correct device |
| 148 | const auto left_joycon = button_params[Settings::NativeButton::DRight]; | 148 | const auto& left_joycon = button_params[Settings::NativeButton::DRight]; |
| 149 | const auto right_joycon = button_params[Settings::NativeButton::A]; | 149 | const auto& right_joycon = button_params[Settings::NativeButton::A]; |
| 150 | 150 | ||
| 151 | // Triggers for GC controllers | 151 | // Triggers for GC controllers |
| 152 | trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL]; | 152 | trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL]; |
| @@ -1209,20 +1209,43 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { | |||
| 1209 | controller.nfc_state = controller.nfc_values; | 1209 | controller.nfc_state = controller.nfc_values; |
| 1210 | } | 1210 | } |
| 1211 | 1211 | ||
| 1212 | bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { | 1212 | bool EmulatedController::SetVibration(bool should_vibrate) { |
| 1213 | VibrationValue vibration_value = DEFAULT_VIBRATION_VALUE; | ||
| 1214 | if (should_vibrate) { | ||
| 1215 | vibration_value.high_amplitude = 1.0f; | ||
| 1216 | vibration_value.low_amplitude = 1.0f; | ||
| 1217 | } | ||
| 1218 | |||
| 1219 | return SetVibration(DeviceIndex::Left, vibration_value); | ||
| 1220 | } | ||
| 1221 | |||
| 1222 | bool EmulatedController::SetVibration(u32 slot, Core::HID::VibrationGcErmCommand erm_command) { | ||
| 1223 | VibrationValue vibration_value = DEFAULT_VIBRATION_VALUE; | ||
| 1224 | if (erm_command == Core::HID::VibrationGcErmCommand::Start) { | ||
| 1225 | vibration_value.high_amplitude = 1.0f; | ||
| 1226 | vibration_value.low_amplitude = 1.0f; | ||
| 1227 | } | ||
| 1228 | |||
| 1229 | return SetVibration(DeviceIndex::Left, vibration_value); | ||
| 1230 | } | ||
| 1231 | |||
| 1232 | bool EmulatedController::SetVibration(DeviceIndex device_index, const VibrationValue& vibration) { | ||
| 1213 | if (!is_initialized) { | 1233 | if (!is_initialized) { |
| 1214 | return false; | 1234 | return false; |
| 1215 | } | 1235 | } |
| 1216 | if (device_index >= output_devices.size()) { | 1236 | if (device_index >= DeviceIndex::MaxDeviceIndex) { |
| 1217 | return false; | 1237 | return false; |
| 1218 | } | 1238 | } |
| 1219 | if (!output_devices[device_index]) { | 1239 | const std::size_t index = static_cast<std::size_t>(device_index); |
| 1240 | if (!output_devices[index]) { | ||
| 1220 | return false; | 1241 | return false; |
| 1221 | } | 1242 | } |
| 1222 | const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); | 1243 | const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); |
| 1223 | const auto& player = Settings::values.players.GetValue()[player_index]; | 1244 | const auto& player = Settings::values.players.GetValue()[player_index]; |
| 1224 | const f32 strength = static_cast<f32>(player.vibration_strength) / 100.0f; | 1245 | const f32 strength = static_cast<f32>(player.vibration_strength) / 100.0f; |
| 1225 | 1246 | ||
| 1247 | last_vibration_value = vibration; | ||
| 1248 | |||
| 1226 | if (!player.vibration_enabled) { | 1249 | if (!player.vibration_enabled) { |
| 1227 | return false; | 1250 | return false; |
| 1228 | } | 1251 | } |
| @@ -1240,8 +1263,11 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v | |||
| 1240 | .high_frequency = vibration.high_frequency, | 1263 | .high_frequency = vibration.high_frequency, |
| 1241 | .type = type, | 1264 | .type = type, |
| 1242 | }; | 1265 | }; |
| 1243 | return output_devices[device_index]->SetVibration(status) == | 1266 | return output_devices[index]->SetVibration(status) == Common::Input::DriverResult::Success; |
| 1244 | Common::Input::DriverResult::Success; | 1267 | } |
| 1268 | |||
| 1269 | VibrationValue EmulatedController::GetActualVibrationValue(DeviceIndex device_index) const { | ||
| 1270 | return last_vibration_value; | ||
| 1245 | } | 1271 | } |
| 1246 | 1272 | ||
| 1247 | bool EmulatedController::IsVibrationEnabled(std::size_t device_index) { | 1273 | bool EmulatedController::IsVibrationEnabled(std::size_t device_index) { |
diff --git a/src/hid_core/frontend/emulated_controller.h b/src/hid_core/frontend/emulated_controller.h index 90e536e07..168abe089 100644 --- a/src/hid_core/frontend/emulated_controller.h +++ b/src/hid_core/frontend/emulated_controller.h | |||
| @@ -356,10 +356,27 @@ public: | |||
| 356 | const NfcState& GetNfc() const; | 356 | const NfcState& GetNfc() const; |
| 357 | 357 | ||
| 358 | /** | 358 | /** |
| 359 | * Sends an on/off vibration to the left device | ||
| 360 | * @return true if vibration had no errors | ||
| 361 | */ | ||
| 362 | bool SetVibration(bool should_vibrate); | ||
| 363 | |||
| 364 | /** | ||
| 365 | * Sends an GC vibration to the left device | ||
| 366 | * @return true if vibration had no errors | ||
| 367 | */ | ||
| 368 | bool SetVibration(u32 slot, Core::HID::VibrationGcErmCommand erm_command); | ||
| 369 | |||
| 370 | /** | ||
| 359 | * Sends a specific vibration to the output device | 371 | * Sends a specific vibration to the output device |
| 360 | * @return true if vibration had no errors | 372 | * @return true if vibration had no errors |
| 361 | */ | 373 | */ |
| 362 | bool SetVibration(std::size_t device_index, VibrationValue vibration); | 374 | bool SetVibration(DeviceIndex device_index, const VibrationValue& vibration); |
| 375 | |||
| 376 | /** | ||
| 377 | * @return The last sent vibration | ||
| 378 | */ | ||
| 379 | VibrationValue GetActualVibrationValue(DeviceIndex device_index) const; | ||
| 363 | 380 | ||
| 364 | /** | 381 | /** |
| 365 | * Sends a small vibration to the output device | 382 | * Sends a small vibration to the output device |
| @@ -564,6 +581,7 @@ private: | |||
| 564 | f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; | 581 | f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; |
| 565 | u32 turbo_button_state{0}; | 582 | u32 turbo_button_state{0}; |
| 566 | std::size_t nfc_handles{0}; | 583 | std::size_t nfc_handles{0}; |
| 584 | VibrationValue last_vibration_value{DEFAULT_VIBRATION_VALUE}; | ||
| 567 | 585 | ||
| 568 | // Temporary values to avoid doing changes while the controller is in configuring mode | 586 | // Temporary values to avoid doing changes while the controller is in configuring mode |
| 569 | NpadStyleIndex tmp_npad_type{NpadStyleIndex::None}; | 587 | NpadStyleIndex tmp_npad_type{NpadStyleIndex::None}; |
diff --git a/src/hid_core/resource_manager.cpp b/src/hid_core/resource_manager.cpp index 2c5fe6d51..ca824b4a3 100644 --- a/src/hid_core/resource_manager.cpp +++ b/src/hid_core/resource_manager.cpp | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include "core/hle/kernel/k_shared_memory.h" | 7 | #include "core/hle/kernel/k_shared_memory.h" |
| 8 | #include "core/hle/service/ipc_helpers.h" | 8 | #include "core/hle/service/ipc_helpers.h" |
| 9 | #include "hid_core/hid_core.h" | 9 | #include "hid_core/hid_core.h" |
| 10 | #include "hid_core/hid_util.h" | ||
| 10 | #include "hid_core/resource_manager.h" | 11 | #include "hid_core/resource_manager.h" |
| 11 | 12 | ||
| 12 | #include "hid_core/resources/applet_resource.h" | 13 | #include "hid_core/resources/applet_resource.h" |
| @@ -27,6 +28,10 @@ | |||
| 27 | #include "hid_core/resources/touch_screen/gesture.h" | 28 | #include "hid_core/resources/touch_screen/gesture.h" |
| 28 | #include "hid_core/resources/touch_screen/touch_screen.h" | 29 | #include "hid_core/resources/touch_screen/touch_screen.h" |
| 29 | #include "hid_core/resources/unique_pad/unique_pad.h" | 30 | #include "hid_core/resources/unique_pad/unique_pad.h" |
| 31 | #include "hid_core/resources/vibration/gc_vibration_device.h" | ||
| 32 | #include "hid_core/resources/vibration/n64_vibration_device.h" | ||
| 33 | #include "hid_core/resources/vibration/vibration_base.h" | ||
| 34 | #include "hid_core/resources/vibration/vibration_device.h" | ||
| 30 | 35 | ||
| 31 | namespace Service::HID { | 36 | namespace Service::HID { |
| 32 | 37 | ||
| @@ -52,6 +57,7 @@ void ResourceManager::Initialize() { | |||
| 52 | 57 | ||
| 53 | system.HIDCore().ReloadInputDevices(); | 58 | system.HIDCore().ReloadInputDevices(); |
| 54 | 59 | ||
| 60 | handheld_config = std::make_shared<HandheldConfig>(); | ||
| 55 | InitializeHidCommonSampler(); | 61 | InitializeHidCommonSampler(); |
| 56 | InitializeTouchScreenSampler(); | 62 | InitializeTouchScreenSampler(); |
| 57 | InitializeConsoleSixAxisSampler(); | 63 | InitializeConsoleSixAxisSampler(); |
| @@ -174,7 +180,7 @@ void ResourceManager::InitializeHidCommonSampler() { | |||
| 174 | debug_pad->SetAppletResource(applet_resource, &shared_mutex); | 180 | debug_pad->SetAppletResource(applet_resource, &shared_mutex); |
| 175 | digitizer->SetAppletResource(applet_resource, &shared_mutex); | 181 | digitizer->SetAppletResource(applet_resource, &shared_mutex); |
| 176 | keyboard->SetAppletResource(applet_resource, &shared_mutex); | 182 | keyboard->SetAppletResource(applet_resource, &shared_mutex); |
| 177 | npad->SetNpadExternals(applet_resource, &shared_mutex); | 183 | npad->SetNpadExternals(applet_resource, &shared_mutex, handheld_config); |
| 178 | six_axis->SetAppletResource(applet_resource, &shared_mutex); | 184 | six_axis->SetAppletResource(applet_resource, &shared_mutex); |
| 179 | mouse->SetAppletResource(applet_resource, &shared_mutex); | 185 | mouse->SetAppletResource(applet_resource, &shared_mutex); |
| 180 | debug_mouse->SetAppletResource(applet_resource, &shared_mutex); | 186 | debug_mouse->SetAppletResource(applet_resource, &shared_mutex); |
| @@ -257,6 +263,121 @@ void ResourceManager::EnableTouchScreen(u64 aruid, bool is_enabled) { | |||
| 257 | applet_resource->EnableTouchScreen(aruid, is_enabled); | 263 | applet_resource->EnableTouchScreen(aruid, is_enabled); |
| 258 | } | 264 | } |
| 259 | 265 | ||
| 266 | NpadVibrationBase* ResourceManager::GetVibrationDevice( | ||
| 267 | const Core::HID::VibrationDeviceHandle& handle) { | ||
| 268 | return npad->GetVibrationDevice(handle); | ||
| 269 | } | ||
| 270 | |||
| 271 | NpadN64VibrationDevice* ResourceManager::GetN64VibrationDevice( | ||
| 272 | const Core::HID::VibrationDeviceHandle& handle) { | ||
| 273 | return npad->GetN64VibrationDevice(handle); | ||
| 274 | } | ||
| 275 | |||
| 276 | NpadVibrationDevice* ResourceManager::GetNSVibrationDevice( | ||
| 277 | const Core::HID::VibrationDeviceHandle& handle) { | ||
| 278 | return npad->GetNSVibrationDevice(handle); | ||
| 279 | } | ||
| 280 | |||
| 281 | NpadGcVibrationDevice* ResourceManager::GetGcVibrationDevice( | ||
| 282 | const Core::HID::VibrationDeviceHandle& handle) { | ||
| 283 | return npad->GetGcVibrationDevice(handle); | ||
| 284 | } | ||
| 285 | |||
| 286 | Result ResourceManager::SetAruidValidForVibration(u64 aruid, bool is_enabled) { | ||
| 287 | std::scoped_lock lock{shared_mutex}; | ||
| 288 | const bool has_changed = applet_resource->SetAruidValidForVibration(aruid, is_enabled); | ||
| 289 | |||
| 290 | if (has_changed) { | ||
| 291 | auto devices = npad->GetAllVibrationDevices(); | ||
| 292 | for ([[maybe_unused]] auto* device : devices) { | ||
| 293 | // TODO | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | auto* vibration_handler = npad->GetVibrationHandler(); | ||
| 298 | if (aruid != vibration_handler->GetSessionAruid()) { | ||
| 299 | vibration_handler->EndPermitVibrationSession(); | ||
| 300 | } | ||
| 301 | |||
| 302 | return ResultSuccess; | ||
| 303 | } | ||
| 304 | |||
| 305 | void ResourceManager::SetForceHandheldStyleVibration(bool is_forced) { | ||
| 306 | handheld_config->is_force_handheld_style_vibration = is_forced; | ||
| 307 | } | ||
| 308 | |||
| 309 | Result ResourceManager::IsVibrationAruidActive(u64 aruid, bool& is_active) const { | ||
| 310 | std::scoped_lock lock{shared_mutex}; | ||
| 311 | is_active = applet_resource->IsVibrationAruidActive(aruid); | ||
| 312 | return ResultSuccess; | ||
| 313 | } | ||
| 314 | |||
| 315 | Result ResourceManager::GetVibrationDeviceInfo(Core::HID::VibrationDeviceInfo& device_info, | ||
| 316 | const Core::HID::VibrationDeviceHandle& handle) { | ||
| 317 | bool check_device_index = false; | ||
| 318 | |||
| 319 | const Result is_valid = IsVibrationHandleValid(handle); | ||
| 320 | if (is_valid.IsError()) { | ||
| 321 | return is_valid; | ||
| 322 | } | ||
| 323 | |||
| 324 | switch (handle.npad_type) { | ||
| 325 | case Core::HID::NpadStyleIndex::Fullkey: | ||
| 326 | case Core::HID::NpadStyleIndex::Handheld: | ||
| 327 | case Core::HID::NpadStyleIndex::JoyconDual: | ||
| 328 | case Core::HID::NpadStyleIndex::JoyconLeft: | ||
| 329 | case Core::HID::NpadStyleIndex::JoyconRight: | ||
| 330 | device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator; | ||
| 331 | check_device_index = true; | ||
| 332 | break; | ||
| 333 | case Core::HID::NpadStyleIndex::GameCube: | ||
| 334 | device_info.type = Core::HID::VibrationDeviceType::GcErm; | ||
| 335 | break; | ||
| 336 | case Core::HID::NpadStyleIndex::N64: | ||
| 337 | device_info.type = Core::HID::VibrationDeviceType::N64; | ||
| 338 | break; | ||
| 339 | default: | ||
| 340 | device_info.type = Core::HID::VibrationDeviceType::Unknown; | ||
| 341 | break; | ||
| 342 | } | ||
| 343 | |||
| 344 | device_info.position = Core::HID::VibrationDevicePosition::None; | ||
| 345 | if (check_device_index) { | ||
| 346 | switch (handle.device_index) { | ||
| 347 | case Core::HID::DeviceIndex::Left: | ||
| 348 | device_info.position = Core::HID::VibrationDevicePosition::Left; | ||
| 349 | break; | ||
| 350 | case Core::HID::DeviceIndex::Right: | ||
| 351 | device_info.position = Core::HID::VibrationDevicePosition::Right; | ||
| 352 | break; | ||
| 353 | case Core::HID::DeviceIndex::None: | ||
| 354 | default: | ||
| 355 | ASSERT_MSG(false, "DeviceIndex should never be None!"); | ||
| 356 | break; | ||
| 357 | } | ||
| 358 | } | ||
| 359 | return ResultSuccess; | ||
| 360 | } | ||
| 361 | |||
| 362 | Result ResourceManager::SendVibrationValue(u64 aruid, | ||
| 363 | const Core::HID::VibrationDeviceHandle& handle, | ||
| 364 | const Core::HID::VibrationValue& value) { | ||
| 365 | bool has_active_aruid{}; | ||
| 366 | NpadVibrationDevice* device{nullptr}; | ||
| 367 | Result result = IsVibrationAruidActive(aruid, has_active_aruid); | ||
| 368 | |||
| 369 | if (result.IsSuccess() && has_active_aruid) { | ||
| 370 | result = IsVibrationHandleValid(handle); | ||
| 371 | } | ||
| 372 | if (result.IsSuccess() && has_active_aruid) { | ||
| 373 | device = GetNSVibrationDevice(handle); | ||
| 374 | } | ||
| 375 | if (device != nullptr) { | ||
| 376 | result = device->SendVibrationValue(value); | ||
| 377 | } | ||
| 378 | return result; | ||
| 379 | } | ||
| 380 | |||
| 260 | void ResourceManager::UpdateControllers(std::chrono::nanoseconds ns_late) { | 381 | void ResourceManager::UpdateControllers(std::chrono::nanoseconds ns_late) { |
| 261 | auto& core_timing = system.CoreTiming(); | 382 | auto& core_timing = system.CoreTiming(); |
| 262 | debug_pad->OnUpdate(core_timing); | 383 | debug_pad->OnUpdate(core_timing); |
diff --git a/src/hid_core/resource_manager.h b/src/hid_core/resource_manager.h index 7a21d8eb8..128e00125 100644 --- a/src/hid_core/resource_manager.h +++ b/src/hid_core/resource_manager.h | |||
| @@ -10,6 +10,12 @@ namespace Core { | |||
| 10 | class System; | 10 | class System; |
| 11 | } | 11 | } |
| 12 | 12 | ||
| 13 | namespace Core::HID { | ||
| 14 | struct VibrationDeviceHandle; | ||
| 15 | struct VibrationValue; | ||
| 16 | struct VibrationDeviceInfo; | ||
| 17 | } // namespace Core::HID | ||
| 18 | |||
| 13 | namespace Core::Timing { | 19 | namespace Core::Timing { |
| 14 | struct EventType; | 20 | struct EventType; |
| 15 | } | 21 | } |
| @@ -37,6 +43,11 @@ class SixAxis; | |||
| 37 | class SleepButton; | 43 | class SleepButton; |
| 38 | class TouchScreen; | 44 | class TouchScreen; |
| 39 | class UniquePad; | 45 | class UniquePad; |
| 46 | class NpadVibrationBase; | ||
| 47 | class NpadN64VibrationDevice; | ||
| 48 | class NpadGcVibrationDevice; | ||
| 49 | class NpadVibrationDevice; | ||
| 50 | struct HandheldConfig; | ||
| 40 | 51 | ||
| 41 | class ResourceManager { | 52 | class ResourceManager { |
| 42 | 53 | ||
| @@ -79,6 +90,18 @@ public: | |||
| 79 | void EnablePadInput(u64 aruid, bool is_enabled); | 90 | void EnablePadInput(u64 aruid, bool is_enabled); |
| 80 | void EnableTouchScreen(u64 aruid, bool is_enabled); | 91 | void EnableTouchScreen(u64 aruid, bool is_enabled); |
| 81 | 92 | ||
| 93 | NpadVibrationBase* GetVibrationDevice(const Core::HID::VibrationDeviceHandle& handle); | ||
| 94 | NpadN64VibrationDevice* GetN64VibrationDevice(const Core::HID::VibrationDeviceHandle& handle); | ||
| 95 | NpadVibrationDevice* GetNSVibrationDevice(const Core::HID::VibrationDeviceHandle& handle); | ||
| 96 | NpadGcVibrationDevice* GetGcVibrationDevice(const Core::HID::VibrationDeviceHandle& handle); | ||
| 97 | Result SetAruidValidForVibration(u64 aruid, bool is_enabled); | ||
| 98 | void SetForceHandheldStyleVibration(bool is_forced); | ||
| 99 | Result IsVibrationAruidActive(u64 aruid, bool& is_active) const; | ||
| 100 | Result GetVibrationDeviceInfo(Core::HID::VibrationDeviceInfo& device_info, | ||
| 101 | const Core::HID::VibrationDeviceHandle& handle); | ||
| 102 | Result SendVibrationValue(u64 aruid, const Core::HID::VibrationDeviceHandle& handle, | ||
| 103 | const Core::HID::VibrationValue& value); | ||
| 104 | |||
| 82 | void UpdateControllers(std::chrono::nanoseconds ns_late); | 105 | void UpdateControllers(std::chrono::nanoseconds ns_late); |
| 83 | void UpdateNpad(std::chrono::nanoseconds ns_late); | 106 | void UpdateNpad(std::chrono::nanoseconds ns_late); |
| 84 | void UpdateMouseKeyboard(std::chrono::nanoseconds ns_late); | 107 | void UpdateMouseKeyboard(std::chrono::nanoseconds ns_late); |
| @@ -113,6 +136,8 @@ private: | |||
| 113 | std::shared_ptr<TouchScreen> touch_screen = nullptr; | 136 | std::shared_ptr<TouchScreen> touch_screen = nullptr; |
| 114 | std::shared_ptr<UniquePad> unique_pad = nullptr; | 137 | std::shared_ptr<UniquePad> unique_pad = nullptr; |
| 115 | 138 | ||
| 139 | std::shared_ptr<HandheldConfig> handheld_config = nullptr; | ||
| 140 | |||
| 116 | // TODO: Create these resources | 141 | // TODO: Create these resources |
| 117 | // std::shared_ptr<AudioControl> audio_control = nullptr; | 142 | // std::shared_ptr<AudioControl> audio_control = nullptr; |
| 118 | // std::shared_ptr<ButtonConfig> button_config = nullptr; | 143 | // std::shared_ptr<ButtonConfig> button_config = nullptr; |
diff --git a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp index d4e4181bf..e399edfd7 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp | |||
| @@ -115,7 +115,7 @@ Result NpadAbstractIrSensorHandler::GetXcdHandleForNpadWithIrSensor(u64& handle) | |||
| 115 | if (sensor_state < NpadIrSensorState::Available) { | 115 | if (sensor_state < NpadIrSensorState::Available) { |
| 116 | return ResultIrSensorIsNotReady; | 116 | return ResultIrSensorIsNotReady; |
| 117 | } | 117 | } |
| 118 | handle = xcd_handle; | 118 | // handle = xcd_handle; |
| 119 | return ResultSuccess; | 119 | return ResultSuccess; |
| 120 | } | 120 | } |
| 121 | 121 | ||
diff --git a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h index fe8e005af..997811511 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h | |||
| @@ -7,6 +7,10 @@ | |||
| 7 | #include "core/hle/result.h" | 7 | #include "core/hle/result.h" |
| 8 | #include "hid_core/hid_types.h" | 8 | #include "hid_core/hid_types.h" |
| 9 | 9 | ||
| 10 | namespace Core::HID { | ||
| 11 | class EmulatedController; | ||
| 12 | } | ||
| 13 | |||
| 10 | namespace Kernel { | 14 | namespace Kernel { |
| 11 | class KEvent; | 15 | class KEvent; |
| 12 | class KReadableEvent; | 16 | class KReadableEvent; |
| @@ -50,7 +54,7 @@ private: | |||
| 50 | 54 | ||
| 51 | s32 ref_counter{}; | 55 | s32 ref_counter{}; |
| 52 | Kernel::KEvent* ir_sensor_event{nullptr}; | 56 | Kernel::KEvent* ir_sensor_event{nullptr}; |
| 53 | u64 xcd_handle{}; | 57 | Core::HID::EmulatedController* xcd_handle{}; |
| 54 | NpadIrSensorState sensor_state{}; | 58 | NpadIrSensorState sensor_state{}; |
| 55 | }; | 59 | }; |
| 56 | } // namespace Service::HID | 60 | } // namespace Service::HID |
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad.cpp b/src/hid_core/resources/abstracted_pad/abstract_pad.cpp index 2c7691d7c..435b095f0 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_pad.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_pad.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | #include "hid_core/hid_core.h" | ||
| 4 | #include "hid_core/hid_result.h" | 5 | #include "hid_core/hid_result.h" |
| 5 | #include "hid_core/resources/abstracted_pad/abstract_pad.h" | 6 | #include "hid_core/resources/abstracted_pad/abstract_pad.h" |
| 6 | #include "hid_core/resources/applet_resource.h" | 7 | #include "hid_core/resources/applet_resource.h" |
| @@ -16,7 +17,7 @@ void AbstractPad::SetExternals(AppletResourceHolder* applet_resource, | |||
| 16 | CaptureButtonResource* capture_button_resource, | 17 | CaptureButtonResource* capture_button_resource, |
| 17 | HomeButtonResource* home_button_resource, | 18 | HomeButtonResource* home_button_resource, |
| 18 | SixAxisResource* sixaxis_resource, PalmaResource* palma_resource, | 19 | SixAxisResource* sixaxis_resource, PalmaResource* palma_resource, |
| 19 | VibrationHandler* vibration) { | 20 | NpadVibration* vibration, Core::HID::HIDCore* core) { |
| 20 | applet_resource_holder = applet_resource; | 21 | applet_resource_holder = applet_resource; |
| 21 | 22 | ||
| 22 | properties_handler.SetAppletResource(applet_resource_holder); | 23 | properties_handler.SetAppletResource(applet_resource_holder); |
| @@ -35,13 +36,14 @@ void AbstractPad::SetExternals(AppletResourceHolder* applet_resource, | |||
| 35 | mcu_handler.SetAbstractPadHolder(&abstract_pad_holder); | 36 | mcu_handler.SetAbstractPadHolder(&abstract_pad_holder); |
| 36 | mcu_handler.SetPropertiesHandler(&properties_handler); | 37 | mcu_handler.SetPropertiesHandler(&properties_handler); |
| 37 | 38 | ||
| 38 | std::array<NpadVibrationDevice*, 2> vibration_devices{&vibration_left, &vibration_right}; | ||
| 39 | vibration_handler.SetAppletResource(applet_resource_holder); | 39 | vibration_handler.SetAppletResource(applet_resource_holder); |
| 40 | vibration_handler.SetAbstractPadHolder(&abstract_pad_holder); | 40 | vibration_handler.SetAbstractPadHolder(&abstract_pad_holder); |
| 41 | vibration_handler.SetPropertiesHandler(&properties_handler); | 41 | vibration_handler.SetPropertiesHandler(&properties_handler); |
| 42 | vibration_handler.SetN64Vibration(&vibration_n64); | 42 | vibration_handler.SetN64Vibration(&vibration_n64); |
| 43 | vibration_handler.SetVibration(vibration_devices); | 43 | vibration_handler.SetVibration(&vibration_left, &vibration_right); |
| 44 | vibration_handler.SetGcVibration(&vibration_gc); | 44 | vibration_handler.SetGcVibration(&vibration_gc); |
| 45 | vibration_handler.SetVibrationHandler(vibration); | ||
| 46 | vibration_handler.SetHidCore(core); | ||
| 45 | 47 | ||
| 46 | sixaxis_handler.SetAppletResource(applet_resource_holder); | 48 | sixaxis_handler.SetAppletResource(applet_resource_holder); |
| 47 | sixaxis_handler.SetAbstractPadHolder(&abstract_pad_holder); | 49 | sixaxis_handler.SetAbstractPadHolder(&abstract_pad_holder); |
| @@ -239,11 +241,6 @@ NpadVibrationDevice* AbstractPad::GetVibrationDevice(Core::HID::DeviceIndex devi | |||
| 239 | return &vibration_left; | 241 | return &vibration_left; |
| 240 | } | 242 | } |
| 241 | 243 | ||
| 242 | void AbstractPad::GetLeftRightVibrationDevice(std::vector<NpadVibrationDevice*> list) { | ||
| 243 | list.emplace_back(&vibration_left); | ||
| 244 | list.emplace_back(&vibration_right); | ||
| 245 | } | ||
| 246 | |||
| 247 | NpadGcVibrationDevice* AbstractPad::GetGCVibrationDevice() { | 244 | NpadGcVibrationDevice* AbstractPad::GetGCVibrationDevice() { |
| 248 | return &vibration_gc; | 245 | return &vibration_gc; |
| 249 | } | 246 | } |
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad.h b/src/hid_core/resources/abstracted_pad/abstract_pad.h index cbdf84af7..329792457 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_pad.h +++ b/src/hid_core/resources/abstracted_pad/abstract_pad.h | |||
| @@ -32,7 +32,6 @@ class AppletResource; | |||
| 32 | class SixAxisResource; | 32 | class SixAxisResource; |
| 33 | class PalmaResource; | 33 | class PalmaResource; |
| 34 | class NPadResource; | 34 | class NPadResource; |
| 35 | class AbstractPad; | ||
| 36 | class NpadLastActiveHandler; | 35 | class NpadLastActiveHandler; |
| 37 | class NpadIrNfcHandler; | 36 | class NpadIrNfcHandler; |
| 38 | class UniquePads; | 37 | class UniquePads; |
| @@ -44,7 +43,6 @@ class NpadGcVibration; | |||
| 44 | 43 | ||
| 45 | class CaptureButtonResource; | 44 | class CaptureButtonResource; |
| 46 | class HomeButtonResource; | 45 | class HomeButtonResource; |
| 47 | class VibrationHandler; | ||
| 48 | 46 | ||
| 49 | struct HandheldConfig; | 47 | struct HandheldConfig; |
| 50 | 48 | ||
| @@ -57,7 +55,8 @@ public: | |||
| 57 | void SetExternals(AppletResourceHolder* applet_resource, | 55 | void SetExternals(AppletResourceHolder* applet_resource, |
| 58 | CaptureButtonResource* capture_button_resource, | 56 | CaptureButtonResource* capture_button_resource, |
| 59 | HomeButtonResource* home_button_resource, SixAxisResource* sixaxis_resource, | 57 | HomeButtonResource* home_button_resource, SixAxisResource* sixaxis_resource, |
| 60 | PalmaResource* palma_resource, VibrationHandler* vibration); | 58 | PalmaResource* palma_resource, NpadVibration* vibration, |
| 59 | Core::HID::HIDCore* core); | ||
| 61 | void SetNpadId(Core::HID::NpadIdType npad_id); | 60 | void SetNpadId(Core::HID::NpadIdType npad_id); |
| 62 | 61 | ||
| 63 | Result Activate(); | 62 | Result Activate(); |
| @@ -78,7 +77,6 @@ public: | |||
| 78 | 77 | ||
| 79 | NpadN64VibrationDevice* GetN64VibrationDevice(); | 78 | NpadN64VibrationDevice* GetN64VibrationDevice(); |
| 80 | NpadVibrationDevice* GetVibrationDevice(Core::HID::DeviceIndex device_index); | 79 | NpadVibrationDevice* GetVibrationDevice(Core::HID::DeviceIndex device_index); |
| 81 | void GetLeftRightVibrationDevice(std::vector<NpadVibrationDevice*> list); | ||
| 82 | NpadGcVibrationDevice* GetGCVibrationDevice(); | 80 | NpadGcVibrationDevice* GetGCVibrationDevice(); |
| 83 | 81 | ||
| 84 | Core::HID::NpadIdType GetLastActiveNpad(); | 82 | Core::HID::NpadIdType GetLastActiveNpad(); |
diff --git a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp index a00d6c9de..ca64b0a43 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp | |||
| @@ -1,6 +1,8 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | #include "hid_core/frontend/emulated_controller.h" | ||
| 5 | #include "hid_core/hid_core.h" | ||
| 4 | #include "hid_core/hid_result.h" | 6 | #include "hid_core/hid_result.h" |
| 5 | #include "hid_core/hid_util.h" | 7 | #include "hid_core/hid_util.h" |
| 6 | #include "hid_core/resources/abstracted_pad/abstract_pad_holder.h" | 8 | #include "hid_core/resources/abstracted_pad/abstract_pad_holder.h" |
| @@ -30,14 +32,22 @@ void NpadAbstractVibrationHandler::SetPropertiesHandler(NpadAbstractPropertiesHa | |||
| 30 | properties_handler = handler; | 32 | properties_handler = handler; |
| 31 | } | 33 | } |
| 32 | 34 | ||
| 35 | void NpadAbstractVibrationHandler::SetVibrationHandler(NpadVibration* handler) { | ||
| 36 | vibration_handler = handler; | ||
| 37 | } | ||
| 38 | |||
| 39 | void NpadAbstractVibrationHandler::SetHidCore(Core::HID::HIDCore* core) { | ||
| 40 | hid_core = core; | ||
| 41 | } | ||
| 42 | |||
| 33 | void NpadAbstractVibrationHandler::SetN64Vibration(NpadN64VibrationDevice* n64_device) { | 43 | void NpadAbstractVibrationHandler::SetN64Vibration(NpadN64VibrationDevice* n64_device) { |
| 34 | n64_vibration_device = n64_device; | 44 | n64_vibration_device = n64_device; |
| 35 | } | 45 | } |
| 36 | 46 | ||
| 37 | void NpadAbstractVibrationHandler::SetVibration(std::span<NpadVibrationDevice*> device) { | 47 | void NpadAbstractVibrationHandler::SetVibration(NpadVibrationDevice* left_device, |
| 38 | for (std::size_t i = 0; i < device.size() && i < vibration_device.size(); i++) { | 48 | NpadVibrationDevice* right_device) { |
| 39 | vibration_device[i] = device[i]; | 49 | left_vibration_device = left_device; |
| 40 | } | 50 | right_vibration_device = right_device; |
| 41 | } | 51 | } |
| 42 | 52 | ||
| 43 | void NpadAbstractVibrationHandler::SetGcVibration(NpadGcVibrationDevice* gc_device) { | 53 | void NpadAbstractVibrationHandler::SetGcVibration(NpadGcVibrationDevice* gc_device) { |
| @@ -69,5 +79,29 @@ void NpadAbstractVibrationHandler::UpdateVibrationState() { | |||
| 69 | if (!is_handheld_hid_enabled && is_force_handheld_style_vibration) { | 79 | if (!is_handheld_hid_enabled && is_force_handheld_style_vibration) { |
| 70 | // TODO | 80 | // TODO |
| 71 | } | 81 | } |
| 82 | |||
| 83 | // TODO: This function isn't accurate. It's supposed to get 5 abstracted pads from the | ||
| 84 | // NpadAbstractPropertiesHandler but this handler isn't fully implemented yet | ||
| 85 | IAbstractedPad abstracted_pad{}; | ||
| 86 | const auto npad_id = properties_handler->GetNpadId(); | ||
| 87 | abstracted_pad.xcd_handle = hid_core->GetEmulatedController(npad_id); | ||
| 88 | abstracted_pad.internal_flags.is_connected.Assign(abstracted_pad.xcd_handle->IsConnected()); | ||
| 89 | |||
| 90 | if (abstracted_pad.internal_flags.is_connected) { | ||
| 91 | left_vibration_device->Mount(abstracted_pad, Core::HID::DeviceIndex::Left, | ||
| 92 | vibration_handler); | ||
| 93 | right_vibration_device->Mount(abstracted_pad, Core::HID::DeviceIndex::Right, | ||
| 94 | vibration_handler); | ||
| 95 | gc_vibration_device->Mount(abstracted_pad, 0, vibration_handler); | ||
| 96 | gc_vibration_device->Mount(abstracted_pad, 0, vibration_handler); | ||
| 97 | n64_vibration_device->Mount(abstracted_pad, vibration_handler); | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | left_vibration_device->Unmount(); | ||
| 102 | right_vibration_device->Unmount(); | ||
| 103 | gc_vibration_device->Unmount(); | ||
| 104 | gc_vibration_device->Unmount(); | ||
| 105 | n64_vibration_device->Unmount(); | ||
| 72 | } | 106 | } |
| 73 | } // namespace Service::HID | 107 | } // namespace Service::HID |
diff --git a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h index aeb07ce86..8bc8129c2 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h | |||
| @@ -9,6 +9,10 @@ | |||
| 9 | #include "core/hle/result.h" | 9 | #include "core/hle/result.h" |
| 10 | #include "hid_core/hid_types.h" | 10 | #include "hid_core/hid_types.h" |
| 11 | 11 | ||
| 12 | namespace Core::HID { | ||
| 13 | class HIDCore; | ||
| 14 | } | ||
| 15 | |||
| 12 | namespace Service::HID { | 16 | namespace Service::HID { |
| 13 | struct AppletResourceHolder; | 17 | struct AppletResourceHolder; |
| 14 | class NpadAbstractedPadHolder; | 18 | class NpadAbstractedPadHolder; |
| @@ -27,9 +31,11 @@ public: | |||
| 27 | void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); | 31 | void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); |
| 28 | void SetAppletResource(AppletResourceHolder* applet_resource); | 32 | void SetAppletResource(AppletResourceHolder* applet_resource); |
| 29 | void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler); | 33 | void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler); |
| 34 | void SetVibrationHandler(NpadVibration* handler); | ||
| 35 | void SetHidCore(Core::HID::HIDCore* core); | ||
| 30 | 36 | ||
| 31 | void SetN64Vibration(NpadN64VibrationDevice* n64_device); | 37 | void SetN64Vibration(NpadN64VibrationDevice* n64_device); |
| 32 | void SetVibration(std::span<NpadVibrationDevice*> device); | 38 | void SetVibration(NpadVibrationDevice* left_device, NpadVibrationDevice* right_device); |
| 33 | void SetGcVibration(NpadGcVibrationDevice* gc_device); | 39 | void SetGcVibration(NpadGcVibrationDevice* gc_device); |
| 34 | 40 | ||
| 35 | Result IncrementRefCounter(); | 41 | Result IncrementRefCounter(); |
| @@ -41,9 +47,11 @@ private: | |||
| 41 | AppletResourceHolder* applet_resource_holder{nullptr}; | 47 | AppletResourceHolder* applet_resource_holder{nullptr}; |
| 42 | NpadAbstractedPadHolder* abstract_pad_holder{nullptr}; | 48 | NpadAbstractedPadHolder* abstract_pad_holder{nullptr}; |
| 43 | NpadAbstractPropertiesHandler* properties_handler{nullptr}; | 49 | NpadAbstractPropertiesHandler* properties_handler{nullptr}; |
| 50 | Core::HID::HIDCore* hid_core{nullptr}; | ||
| 44 | 51 | ||
| 45 | NpadN64VibrationDevice* n64_vibration_device{nullptr}; | 52 | NpadN64VibrationDevice* n64_vibration_device{nullptr}; |
| 46 | std::array<NpadVibrationDevice*, 2> vibration_device{}; | 53 | NpadVibrationDevice* left_vibration_device{}; |
| 54 | NpadVibrationDevice* right_vibration_device{}; | ||
| 47 | NpadGcVibrationDevice* gc_vibration_device{nullptr}; | 55 | NpadGcVibrationDevice* gc_vibration_device{nullptr}; |
| 48 | NpadVibration* vibration_handler{nullptr}; | 56 | NpadVibration* vibration_handler{nullptr}; |
| 49 | s32 ref_counter{}; | 57 | s32 ref_counter{}; |
diff --git a/src/hid_core/resources/applet_resource.cpp b/src/hid_core/resources/applet_resource.cpp index a84826050..db4134037 100644 --- a/src/hid_core/resources/applet_resource.cpp +++ b/src/hid_core/resources/applet_resource.cpp | |||
| @@ -200,6 +200,25 @@ void AppletResource::EnableInput(u64 aruid, bool is_enabled) { | |||
| 200 | data[index].flag.enable_touchscreen.Assign(is_enabled); | 200 | data[index].flag.enable_touchscreen.Assign(is_enabled); |
| 201 | } | 201 | } |
| 202 | 202 | ||
| 203 | bool AppletResource::SetAruidValidForVibration(u64 aruid, bool is_enabled) { | ||
| 204 | const u64 index = GetIndexFromAruid(aruid); | ||
| 205 | if (index >= AruidIndexMax) { | ||
| 206 | return false; | ||
| 207 | } | ||
| 208 | |||
| 209 | if (!is_enabled && aruid == active_vibration_aruid) { | ||
| 210 | active_vibration_aruid = SystemAruid; | ||
| 211 | return true; | ||
| 212 | } | ||
| 213 | |||
| 214 | if (is_enabled && aruid != active_vibration_aruid) { | ||
| 215 | active_vibration_aruid = aruid; | ||
| 216 | return true; | ||
| 217 | } | ||
| 218 | |||
| 219 | return false; | ||
| 220 | } | ||
| 221 | |||
| 203 | void AppletResource::EnableSixAxisSensor(u64 aruid, bool is_enabled) { | 222 | void AppletResource::EnableSixAxisSensor(u64 aruid, bool is_enabled) { |
| 204 | const u64 index = GetIndexFromAruid(aruid); | 223 | const u64 index = GetIndexFromAruid(aruid); |
| 205 | if (index >= AruidIndexMax) { | 224 | if (index >= AruidIndexMax) { |
diff --git a/src/hid_core/resources/applet_resource.h b/src/hid_core/resources/applet_resource.h index f3f32bac1..e9710d306 100644 --- a/src/hid_core/resources/applet_resource.h +++ b/src/hid_core/resources/applet_resource.h | |||
| @@ -101,6 +101,7 @@ public: | |||
| 101 | Result DestroySevenSixAxisTransferMemory(); | 101 | Result DestroySevenSixAxisTransferMemory(); |
| 102 | 102 | ||
| 103 | void EnableInput(u64 aruid, bool is_enabled); | 103 | void EnableInput(u64 aruid, bool is_enabled); |
| 104 | bool SetAruidValidForVibration(u64 aruid, bool is_enabled); | ||
| 104 | void EnableSixAxisSensor(u64 aruid, bool is_enabled); | 105 | void EnableSixAxisSensor(u64 aruid, bool is_enabled); |
| 105 | void EnablePadInput(u64 aruid, bool is_enabled); | 106 | void EnablePadInput(u64 aruid, bool is_enabled); |
| 106 | void EnableTouchScreen(u64 aruid, bool is_enabled); | 107 | void EnableTouchScreen(u64 aruid, bool is_enabled); |
diff --git a/src/hid_core/resources/npad/npad.cpp b/src/hid_core/resources/npad/npad.cpp index de0f9cbb9..212f01429 100644 --- a/src/hid_core/resources/npad/npad.cpp +++ b/src/hid_core/resources/npad/npad.cpp | |||
| @@ -21,6 +21,7 @@ | |||
| 21 | #include "hid_core/hid_util.h" | 21 | #include "hid_core/hid_util.h" |
| 22 | #include "hid_core/resources/applet_resource.h" | 22 | #include "hid_core/resources/applet_resource.h" |
| 23 | #include "hid_core/resources/npad/npad.h" | 23 | #include "hid_core/resources/npad/npad.h" |
| 24 | #include "hid_core/resources/npad/npad_vibration.h" | ||
| 24 | #include "hid_core/resources/shared_memory_format.h" | 25 | #include "hid_core/resources/shared_memory_format.h" |
| 25 | 26 | ||
| 26 | namespace Service::HID { | 27 | namespace Service::HID { |
| @@ -31,10 +32,6 @@ NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service | |||
| 31 | for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { | 32 | for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { |
| 32 | auto& controller = controller_data[aruid_index][i]; | 33 | auto& controller = controller_data[aruid_index][i]; |
| 33 | controller.device = hid_core.GetEmulatedControllerByIndex(i); | 34 | controller.device = hid_core.GetEmulatedControllerByIndex(i); |
| 34 | controller.vibration[Core::HID::EmulatedDeviceIndex::LeftIndex].latest_vibration_value = | ||
| 35 | Core::HID::DEFAULT_VIBRATION_VALUE; | ||
| 36 | controller.vibration[Core::HID::EmulatedDeviceIndex::RightIndex] | ||
| 37 | .latest_vibration_value = Core::HID::DEFAULT_VIBRATION_VALUE; | ||
| 38 | Core::HID::ControllerUpdateCallback engine_callback{ | 35 | Core::HID::ControllerUpdateCallback engine_callback{ |
| 39 | .on_change = | 36 | .on_change = |
| 40 | [this, i](Core::HID::ControllerTriggerType type) { ControllerUpdate(type, i); }, | 37 | [this, i](Core::HID::ControllerTriggerType type) { ControllerUpdate(type, i); }, |
| @@ -43,6 +40,10 @@ NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service | |||
| 43 | controller.callback_key = controller.device->SetCallback(engine_callback); | 40 | controller.callback_key = controller.device->SetCallback(engine_callback); |
| 44 | } | 41 | } |
| 45 | } | 42 | } |
| 43 | for (std::size_t i = 0; i < abstracted_pads.size(); ++i) { | ||
| 44 | abstracted_pads[i] = AbstractPad{}; | ||
| 45 | abstracted_pads[i].SetNpadId(IndexToNpadIdType(i)); | ||
| 46 | } | ||
| 46 | } | 47 | } |
| 47 | 48 | ||
| 48 | NPad::~NPad() { | 49 | NPad::~NPad() { |
| @@ -359,6 +360,7 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) { | |||
| 359 | npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); | 360 | npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); |
| 360 | WriteEmptyEntry(controller.shared_memory); | 361 | WriteEmptyEntry(controller.shared_memory); |
| 361 | hid_core.SetLastActiveController(npad_id); | 362 | hid_core.SetLastActiveController(npad_id); |
| 363 | abstracted_pads[NpadIdTypeToIndex(npad_id)].Update(); | ||
| 362 | } | 364 | } |
| 363 | 365 | ||
| 364 | void NPad::WriteEmptyEntry(NpadInternalState* npad) { | 366 | void NPad::WriteEmptyEntry(NpadInternalState* npad) { |
| @@ -740,171 +742,6 @@ bool NPad::SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID: | |||
| 740 | return true; | 742 | return true; |
| 741 | } | 743 | } |
| 742 | 744 | ||
| 743 | bool NPad::VibrateControllerAtIndex(u64 aruid, Core::HID::NpadIdType npad_id, | ||
| 744 | std::size_t device_index, | ||
| 745 | const Core::HID::VibrationValue& vibration_value) { | ||
| 746 | auto& controller = GetControllerFromNpadIdType(aruid, npad_id); | ||
| 747 | if (!controller.device->IsConnected()) { | ||
| 748 | return false; | ||
| 749 | } | ||
| 750 | |||
| 751 | if (!controller.device->IsVibrationEnabled(device_index)) { | ||
| 752 | if (controller.vibration[device_index].latest_vibration_value.low_amplitude != 0.0f || | ||
| 753 | controller.vibration[device_index].latest_vibration_value.high_amplitude != 0.0f) { | ||
| 754 | // Send an empty vibration to stop any vibrations. | ||
| 755 | Core::HID::VibrationValue vibration{0.0f, 160.0f, 0.0f, 320.0f}; | ||
| 756 | controller.device->SetVibration(device_index, vibration); | ||
| 757 | // Then reset the vibration value to its default value. | ||
| 758 | controller.vibration[device_index].latest_vibration_value = | ||
| 759 | Core::HID::DEFAULT_VIBRATION_VALUE; | ||
| 760 | } | ||
| 761 | |||
| 762 | return false; | ||
| 763 | } | ||
| 764 | |||
| 765 | if (!Settings::values.enable_accurate_vibrations.GetValue()) { | ||
| 766 | using std::chrono::duration_cast; | ||
| 767 | using std::chrono::milliseconds; | ||
| 768 | using std::chrono::steady_clock; | ||
| 769 | |||
| 770 | const auto now = steady_clock::now(); | ||
| 771 | |||
| 772 | // Filter out non-zero vibrations that are within 15ms of each other. | ||
| 773 | if ((vibration_value.low_amplitude != 0.0f || vibration_value.high_amplitude != 0.0f) && | ||
| 774 | duration_cast<milliseconds>( | ||
| 775 | now - controller.vibration[device_index].last_vibration_timepoint) < | ||
| 776 | milliseconds(15)) { | ||
| 777 | return false; | ||
| 778 | } | ||
| 779 | |||
| 780 | controller.vibration[device_index].last_vibration_timepoint = now; | ||
| 781 | } | ||
| 782 | |||
| 783 | Core::HID::VibrationValue vibration{ | ||
| 784 | vibration_value.low_amplitude, vibration_value.low_frequency, | ||
| 785 | vibration_value.high_amplitude, vibration_value.high_frequency}; | ||
| 786 | return controller.device->SetVibration(device_index, vibration); | ||
| 787 | } | ||
| 788 | |||
| 789 | void NPad::VibrateController(u64 aruid, | ||
| 790 | const Core::HID::VibrationDeviceHandle& vibration_device_handle, | ||
| 791 | const Core::HID::VibrationValue& vibration_value) { | ||
| 792 | if (IsVibrationHandleValid(vibration_device_handle).IsError()) { | ||
| 793 | return; | ||
| 794 | } | ||
| 795 | |||
| 796 | if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { | ||
| 797 | return; | ||
| 798 | } | ||
| 799 | |||
| 800 | auto& controller = GetControllerFromHandle(aruid, vibration_device_handle); | ||
| 801 | const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); | ||
| 802 | |||
| 803 | if (!controller.vibration[device_index].device_mounted || !controller.device->IsConnected()) { | ||
| 804 | return; | ||
| 805 | } | ||
| 806 | |||
| 807 | if (vibration_device_handle.device_index == Core::HID::DeviceIndex::None) { | ||
| 808 | ASSERT_MSG(false, "DeviceIndex should never be None!"); | ||
| 809 | return; | ||
| 810 | } | ||
| 811 | |||
| 812 | // Some games try to send mismatched parameters in the device handle, block these. | ||
| 813 | if ((controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconLeft && | ||
| 814 | (vibration_device_handle.npad_type == Core::HID::NpadStyleIndex::JoyconRight || | ||
| 815 | vibration_device_handle.device_index == Core::HID::DeviceIndex::Right)) || | ||
| 816 | (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight && | ||
| 817 | (vibration_device_handle.npad_type == Core::HID::NpadStyleIndex::JoyconLeft || | ||
| 818 | vibration_device_handle.device_index == Core::HID::DeviceIndex::Left))) { | ||
| 819 | return; | ||
| 820 | } | ||
| 821 | |||
| 822 | // Filter out vibrations with equivalent values to reduce unnecessary state changes. | ||
| 823 | if (vibration_value.low_amplitude == | ||
| 824 | controller.vibration[device_index].latest_vibration_value.low_amplitude && | ||
| 825 | vibration_value.high_amplitude == | ||
| 826 | controller.vibration[device_index].latest_vibration_value.high_amplitude) { | ||
| 827 | return; | ||
| 828 | } | ||
| 829 | |||
| 830 | if (VibrateControllerAtIndex(aruid, controller.device->GetNpadIdType(), device_index, | ||
| 831 | vibration_value)) { | ||
| 832 | controller.vibration[device_index].latest_vibration_value = vibration_value; | ||
| 833 | } | ||
| 834 | } | ||
| 835 | |||
| 836 | void NPad::VibrateControllers( | ||
| 837 | u64 aruid, std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles, | ||
| 838 | std::span<const Core::HID::VibrationValue> vibration_values) { | ||
| 839 | if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { | ||
| 840 | return; | ||
| 841 | } | ||
| 842 | |||
| 843 | ASSERT_OR_EXECUTE_MSG( | ||
| 844 | vibration_device_handles.size() == vibration_values.size(), { return; }, | ||
| 845 | "The amount of device handles does not match with the amount of vibration values," | ||
| 846 | "this is undefined behavior!"); | ||
| 847 | |||
| 848 | for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) { | ||
| 849 | VibrateController(aruid, vibration_device_handles[i], vibration_values[i]); | ||
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | Core::HID::VibrationValue NPad::GetLastVibration( | ||
| 854 | u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const { | ||
| 855 | if (IsVibrationHandleValid(vibration_device_handle).IsError()) { | ||
| 856 | return {}; | ||
| 857 | } | ||
| 858 | |||
| 859 | const auto& controller = GetControllerFromHandle(aruid, vibration_device_handle); | ||
| 860 | const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); | ||
| 861 | return controller.vibration[device_index].latest_vibration_value; | ||
| 862 | } | ||
| 863 | |||
| 864 | void NPad::InitializeVibrationDevice( | ||
| 865 | const Core::HID::VibrationDeviceHandle& vibration_device_handle) { | ||
| 866 | if (IsVibrationHandleValid(vibration_device_handle).IsError()) { | ||
| 867 | return; | ||
| 868 | } | ||
| 869 | |||
| 870 | const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid(); | ||
| 871 | const auto npad_index = static_cast<Core::HID::NpadIdType>(vibration_device_handle.npad_id); | ||
| 872 | const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); | ||
| 873 | |||
| 874 | if (aruid == 0) { | ||
| 875 | return; | ||
| 876 | } | ||
| 877 | |||
| 878 | InitializeVibrationDeviceAtIndex(aruid, npad_index, device_index); | ||
| 879 | } | ||
| 880 | |||
| 881 | void NPad::InitializeVibrationDeviceAtIndex(u64 aruid, Core::HID::NpadIdType npad_id, | ||
| 882 | std::size_t device_index) { | ||
| 883 | auto& controller = GetControllerFromNpadIdType(aruid, npad_id); | ||
| 884 | if (!Settings::values.vibration_enabled.GetValue()) { | ||
| 885 | controller.vibration[device_index].device_mounted = false; | ||
| 886 | return; | ||
| 887 | } | ||
| 888 | |||
| 889 | controller.vibration[device_index].device_mounted = | ||
| 890 | controller.device->IsVibrationEnabled(device_index); | ||
| 891 | } | ||
| 892 | |||
| 893 | void NPad::SetPermitVibrationSession(bool permit_vibration_session) { | ||
| 894 | permit_vibration_session_enabled = permit_vibration_session; | ||
| 895 | } | ||
| 896 | |||
| 897 | bool NPad::IsVibrationDeviceMounted( | ||
| 898 | u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const { | ||
| 899 | if (IsVibrationHandleValid(vibration_device_handle).IsError()) { | ||
| 900 | return false; | ||
| 901 | } | ||
| 902 | |||
| 903 | const auto& controller = GetControllerFromHandle(aruid, vibration_device_handle); | ||
| 904 | const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); | ||
| 905 | return controller.vibration[device_index].device_mounted; | ||
| 906 | } | ||
| 907 | |||
| 908 | Result NPad::AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event, | 745 | Result NPad::AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event, |
| 909 | Core::HID::NpadIdType npad_id) { | 746 | Core::HID::NpadIdType npad_id) { |
| 910 | std::scoped_lock lock{mutex}; | 747 | std::scoped_lock lock{mutex}; |
| @@ -936,11 +773,6 @@ Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) { | |||
| 936 | 773 | ||
| 937 | LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id); | 774 | LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id); |
| 938 | auto& controller = GetControllerFromNpadIdType(aruid, npad_id); | 775 | auto& controller = GetControllerFromNpadIdType(aruid, npad_id); |
| 939 | for (std::size_t device_idx = 0; device_idx < controller.vibration.size(); ++device_idx) { | ||
| 940 | // Send an empty vibration to stop any vibrations. | ||
| 941 | VibrateControllerAtIndex(aruid, npad_id, device_idx, {}); | ||
| 942 | controller.vibration[device_idx].device_mounted = false; | ||
| 943 | } | ||
| 944 | 776 | ||
| 945 | auto* shared_memory = controller.shared_memory; | 777 | auto* shared_memory = controller.shared_memory; |
| 946 | // Don't reset shared_memory->assignment_mode this value is persistent | 778 | // Don't reset shared_memory->assignment_mode this value is persistent |
| @@ -1243,22 +1075,17 @@ void NPad::UnregisterAppletResourceUserId(u64 aruid) { | |||
| 1243 | } | 1075 | } |
| 1244 | 1076 | ||
| 1245 | void NPad::SetNpadExternals(std::shared_ptr<AppletResource> resource, | 1077 | void NPad::SetNpadExternals(std::shared_ptr<AppletResource> resource, |
| 1246 | std::recursive_mutex* shared_mutex) { | 1078 | std::recursive_mutex* shared_mutex, |
| 1079 | std::shared_ptr<HandheldConfig> handheld_config) { | ||
| 1247 | applet_resource_holder.applet_resource = resource; | 1080 | applet_resource_holder.applet_resource = resource; |
| 1248 | applet_resource_holder.shared_mutex = shared_mutex; | 1081 | applet_resource_holder.shared_mutex = shared_mutex; |
| 1249 | applet_resource_holder.shared_npad_resource = &npad_resource; | 1082 | applet_resource_holder.shared_npad_resource = &npad_resource; |
| 1250 | } | 1083 | applet_resource_holder.handheld_config = handheld_config; |
| 1251 | 1084 | ||
| 1252 | NPad::NpadControllerData& NPad::GetControllerFromHandle( | 1085 | for (auto& abstract_pad : abstracted_pads) { |
| 1253 | u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) { | 1086 | abstract_pad.SetExternals(&applet_resource_holder, nullptr, nullptr, nullptr, nullptr, |
| 1254 | const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id); | 1087 | &vibration_handler, &hid_core); |
| 1255 | return GetControllerFromNpadIdType(aruid, npad_id); | 1088 | } |
| 1256 | } | ||
| 1257 | |||
| 1258 | const NPad::NpadControllerData& NPad::GetControllerFromHandle( | ||
| 1259 | u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) const { | ||
| 1260 | const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id); | ||
| 1261 | return GetControllerFromNpadIdType(aruid, npad_id); | ||
| 1262 | } | 1089 | } |
| 1263 | 1090 | ||
| 1264 | NPad::NpadControllerData& NPad::GetControllerFromHandle( | 1091 | NPad::NpadControllerData& NPad::GetControllerFromHandle( |
| @@ -1396,4 +1223,97 @@ Result NPad::GetLastActiveNpad(Core::HID::NpadIdType& out_npad_id) const { | |||
| 1396 | return ResultSuccess; | 1223 | return ResultSuccess; |
| 1397 | } | 1224 | } |
| 1398 | 1225 | ||
| 1226 | NpadVibration* NPad::GetVibrationHandler() { | ||
| 1227 | return &vibration_handler; | ||
| 1228 | } | ||
| 1229 | |||
| 1230 | std::vector<NpadVibrationBase*> NPad::GetAllVibrationDevices() { | ||
| 1231 | std::vector<NpadVibrationBase*> vibration_devices; | ||
| 1232 | |||
| 1233 | for (auto& abstract_pad : abstracted_pads) { | ||
| 1234 | auto* left_device = abstract_pad.GetVibrationDevice(Core::HID::DeviceIndex::Left); | ||
| 1235 | auto* right_device = abstract_pad.GetVibrationDevice(Core::HID::DeviceIndex::Right); | ||
| 1236 | auto* n64_device = abstract_pad.GetGCVibrationDevice(); | ||
| 1237 | auto* gc_device = abstract_pad.GetGCVibrationDevice(); | ||
| 1238 | |||
| 1239 | if (left_device != nullptr) { | ||
| 1240 | vibration_devices.emplace_back(left_device); | ||
| 1241 | } | ||
| 1242 | if (right_device != nullptr) { | ||
| 1243 | vibration_devices.emplace_back(right_device); | ||
| 1244 | } | ||
| 1245 | if (n64_device != nullptr) { | ||
| 1246 | vibration_devices.emplace_back(n64_device); | ||
| 1247 | } | ||
| 1248 | if (gc_device != nullptr) { | ||
| 1249 | vibration_devices.emplace_back(gc_device); | ||
| 1250 | } | ||
| 1251 | } | ||
| 1252 | |||
| 1253 | return vibration_devices; | ||
| 1254 | } | ||
| 1255 | |||
| 1256 | NpadVibrationBase* NPad::GetVibrationDevice(const Core::HID::VibrationDeviceHandle& handle) { | ||
| 1257 | if (IsVibrationHandleValid(handle).IsError()) { | ||
| 1258 | return nullptr; | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | const auto npad_index = NpadIdTypeToIndex(static_cast<Core::HID::NpadIdType>(handle.npad_id)); | ||
| 1262 | const auto style_inde = static_cast<Core::HID::NpadStyleIndex>(handle.npad_type); | ||
| 1263 | if (style_inde == Core::HID::NpadStyleIndex::GameCube) { | ||
| 1264 | return abstracted_pads[npad_index].GetGCVibrationDevice(); | ||
| 1265 | } | ||
| 1266 | if (style_inde == Core::HID::NpadStyleIndex::N64) { | ||
| 1267 | return abstracted_pads[npad_index].GetN64VibrationDevice(); | ||
| 1268 | } | ||
| 1269 | return abstracted_pads[npad_index].GetVibrationDevice(handle.device_index); | ||
| 1270 | } | ||
| 1271 | |||
| 1272 | NpadN64VibrationDevice* NPad::GetN64VibrationDevice( | ||
| 1273 | const Core::HID::VibrationDeviceHandle& handle) { | ||
| 1274 | if (IsVibrationHandleValid(handle).IsError()) { | ||
| 1275 | return nullptr; | ||
| 1276 | } | ||
| 1277 | |||
| 1278 | const auto npad_index = NpadIdTypeToIndex(static_cast<Core::HID::NpadIdType>(handle.npad_id)); | ||
| 1279 | const auto style_inde = static_cast<Core::HID::NpadStyleIndex>(handle.npad_type); | ||
| 1280 | if (style_inde != Core::HID::NpadStyleIndex::N64) { | ||
| 1281 | return nullptr; | ||
| 1282 | } | ||
| 1283 | return abstracted_pads[npad_index].GetN64VibrationDevice(); | ||
| 1284 | } | ||
| 1285 | |||
| 1286 | NpadVibrationDevice* NPad::GetNSVibrationDevice(const Core::HID::VibrationDeviceHandle& handle) { | ||
| 1287 | if (IsVibrationHandleValid(handle).IsError()) { | ||
| 1288 | return nullptr; | ||
| 1289 | } | ||
| 1290 | |||
| 1291 | const auto npad_index = NpadIdTypeToIndex(static_cast<Core::HID::NpadIdType>(handle.npad_id)); | ||
| 1292 | const auto style_inde = static_cast<Core::HID::NpadStyleIndex>(handle.npad_type); | ||
| 1293 | if (style_inde == Core::HID::NpadStyleIndex::GameCube || | ||
| 1294 | style_inde == Core::HID::NpadStyleIndex::N64) { | ||
| 1295 | return nullptr; | ||
| 1296 | } | ||
| 1297 | |||
| 1298 | return abstracted_pads[npad_index].GetVibrationDevice(handle.device_index); | ||
| 1299 | } | ||
| 1300 | |||
| 1301 | NpadGcVibrationDevice* NPad::GetGcVibrationDevice(const Core::HID::VibrationDeviceHandle& handle) { | ||
| 1302 | if (IsVibrationHandleValid(handle).IsError()) { | ||
| 1303 | return nullptr; | ||
| 1304 | } | ||
| 1305 | |||
| 1306 | const auto npad_index = NpadIdTypeToIndex(static_cast<Core::HID::NpadIdType>(handle.npad_id)); | ||
| 1307 | const auto style_inde = static_cast<Core::HID::NpadStyleIndex>(handle.npad_type); | ||
| 1308 | if (style_inde != Core::HID::NpadStyleIndex::GameCube) { | ||
| 1309 | return nullptr; | ||
| 1310 | } | ||
| 1311 | return abstracted_pads[npad_index].GetGCVibrationDevice(); | ||
| 1312 | } | ||
| 1313 | |||
| 1314 | void NPad::UpdateHandheldAbstractState() { | ||
| 1315 | std::scoped_lock lock{mutex}; | ||
| 1316 | abstracted_pads[NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld)].Update(); | ||
| 1317 | } | ||
| 1318 | |||
| 1399 | } // namespace Service::HID | 1319 | } // namespace Service::HID |
diff --git a/src/hid_core/resources/npad/npad.h b/src/hid_core/resources/npad/npad.h index 01f3dabb1..18b25c688 100644 --- a/src/hid_core/resources/npad/npad.h +++ b/src/hid_core/resources/npad/npad.h | |||
| @@ -10,9 +10,15 @@ | |||
| 10 | 10 | ||
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | #include "hid_core/hid_types.h" | 12 | #include "hid_core/hid_types.h" |
| 13 | #include "hid_core/resources/abstracted_pad/abstract_pad.h" | ||
| 13 | #include "hid_core/resources/controller_base.h" | 14 | #include "hid_core/resources/controller_base.h" |
| 14 | #include "hid_core/resources/npad/npad_resource.h" | 15 | #include "hid_core/resources/npad/npad_resource.h" |
| 15 | #include "hid_core/resources/npad/npad_types.h" | 16 | #include "hid_core/resources/npad/npad_types.h" |
| 17 | #include "hid_core/resources/npad/npad_vibration.h" | ||
| 18 | #include "hid_core/resources/vibration/gc_vibration_device.h" | ||
| 19 | #include "hid_core/resources/vibration/n64_vibration_device.h" | ||
| 20 | #include "hid_core/resources/vibration/vibration_base.h" | ||
| 21 | #include "hid_core/resources/vibration/vibration_device.h" | ||
| 16 | 22 | ||
| 17 | namespace Core::HID { | 23 | namespace Core::HID { |
| 18 | class EmulatedController; | 24 | class EmulatedController; |
| @@ -32,6 +38,7 @@ union Result; | |||
| 32 | 38 | ||
| 33 | namespace Service::HID { | 39 | namespace Service::HID { |
| 34 | class AppletResource; | 40 | class AppletResource; |
| 41 | struct HandheldConfig; | ||
| 35 | struct NpadInternalState; | 42 | struct NpadInternalState; |
| 36 | struct NpadSixAxisSensorLifo; | 43 | struct NpadSixAxisSensorLifo; |
| 37 | struct NpadSharedMemoryFormat; | 44 | struct NpadSharedMemoryFormat; |
| @@ -68,31 +75,6 @@ public: | |||
| 68 | bool SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id, | 75 | bool SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id, |
| 69 | NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode); | 76 | NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode); |
| 70 | 77 | ||
| 71 | bool VibrateControllerAtIndex(u64 aruid, Core::HID::NpadIdType npad_id, | ||
| 72 | std::size_t device_index, | ||
| 73 | const Core::HID::VibrationValue& vibration_value); | ||
| 74 | |||
| 75 | void VibrateController(u64 aruid, | ||
| 76 | const Core::HID::VibrationDeviceHandle& vibration_device_handle, | ||
| 77 | const Core::HID::VibrationValue& vibration_value); | ||
| 78 | |||
| 79 | void VibrateControllers( | ||
| 80 | u64 aruid, std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles, | ||
| 81 | std::span<const Core::HID::VibrationValue> vibration_values); | ||
| 82 | |||
| 83 | Core::HID::VibrationValue GetLastVibration( | ||
| 84 | u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const; | ||
| 85 | |||
| 86 | void InitializeVibrationDevice(const Core::HID::VibrationDeviceHandle& vibration_device_handle); | ||
| 87 | |||
| 88 | void InitializeVibrationDeviceAtIndex(u64 aruid, Core::HID::NpadIdType npad_id, | ||
| 89 | std::size_t device_index); | ||
| 90 | |||
| 91 | void SetPermitVibrationSession(bool permit_vibration_session); | ||
| 92 | |||
| 93 | bool IsVibrationDeviceMounted( | ||
| 94 | u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const; | ||
| 95 | |||
| 96 | Result AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event, | 78 | Result AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event, |
| 97 | Core::HID::NpadIdType npad_id); | 79 | Core::HID::NpadIdType npad_id); |
| 98 | 80 | ||
| @@ -145,7 +127,8 @@ public: | |||
| 145 | Result RegisterAppletResourceUserId(u64 aruid); | 127 | Result RegisterAppletResourceUserId(u64 aruid); |
| 146 | void UnregisterAppletResourceUserId(u64 aruid); | 128 | void UnregisterAppletResourceUserId(u64 aruid); |
| 147 | void SetNpadExternals(std::shared_ptr<AppletResource> resource, | 129 | void SetNpadExternals(std::shared_ptr<AppletResource> resource, |
| 148 | std::recursive_mutex* shared_mutex); | 130 | std::recursive_mutex* shared_mutex, |
| 131 | std::shared_ptr<HandheldConfig> handheld_config); | ||
| 149 | 132 | ||
| 150 | AppletDetailedUiType GetAppletDetailedUiType(Core::HID::NpadIdType npad_id); | 133 | AppletDetailedUiType GetAppletDetailedUiType(Core::HID::NpadIdType npad_id); |
| 151 | 134 | ||
| @@ -161,18 +144,20 @@ public: | |||
| 161 | 144 | ||
| 162 | Result GetLastActiveNpad(Core::HID::NpadIdType& out_npad_id) const; | 145 | Result GetLastActiveNpad(Core::HID::NpadIdType& out_npad_id) const; |
| 163 | 146 | ||
| 164 | private: | 147 | NpadVibration* GetVibrationHandler(); |
| 165 | struct VibrationData { | 148 | std::vector<NpadVibrationBase*> GetAllVibrationDevices(); |
| 166 | bool device_mounted{}; | 149 | NpadVibrationBase* GetVibrationDevice(const Core::HID::VibrationDeviceHandle& handle); |
| 167 | Core::HID::VibrationValue latest_vibration_value{}; | 150 | NpadN64VibrationDevice* GetN64VibrationDevice(const Core::HID::VibrationDeviceHandle& handle); |
| 168 | std::chrono::steady_clock::time_point last_vibration_timepoint{}; | 151 | NpadVibrationDevice* GetNSVibrationDevice(const Core::HID::VibrationDeviceHandle& handle); |
| 169 | }; | 152 | NpadGcVibrationDevice* GetGcVibrationDevice(const Core::HID::VibrationDeviceHandle& handle); |
| 170 | 153 | ||
| 154 | void UpdateHandheldAbstractState(); | ||
| 155 | |||
| 156 | private: | ||
| 171 | struct NpadControllerData { | 157 | struct NpadControllerData { |
| 172 | NpadInternalState* shared_memory = nullptr; | 158 | NpadInternalState* shared_memory = nullptr; |
| 173 | Core::HID::EmulatedController* device = nullptr; | 159 | Core::HID::EmulatedController* device = nullptr; |
| 174 | 160 | ||
| 175 | std::array<VibrationData, 2> vibration{}; | ||
| 176 | bool is_connected{}; | 161 | bool is_connected{}; |
| 177 | 162 | ||
| 178 | // Dual joycons can have only one side connected | 163 | // Dual joycons can have only one side connected |
| @@ -192,10 +177,6 @@ private: | |||
| 192 | void WriteEmptyEntry(NpadInternalState* npad); | 177 | void WriteEmptyEntry(NpadInternalState* npad); |
| 193 | 178 | ||
| 194 | NpadControllerData& GetControllerFromHandle( | 179 | NpadControllerData& GetControllerFromHandle( |
| 195 | u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle); | ||
| 196 | const NpadControllerData& GetControllerFromHandle( | ||
| 197 | u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) const; | ||
| 198 | NpadControllerData& GetControllerFromHandle( | ||
| 199 | u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle); | 180 | u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle); |
| 200 | const NpadControllerData& GetControllerFromHandle( | 181 | const NpadControllerData& GetControllerFromHandle( |
| 201 | u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle) const; | 182 | u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle) const; |
| @@ -215,11 +196,13 @@ private: | |||
| 215 | mutable std::mutex mutex; | 196 | mutable std::mutex mutex; |
| 216 | NPadResource npad_resource; | 197 | NPadResource npad_resource; |
| 217 | AppletResourceHolder applet_resource_holder{}; | 198 | AppletResourceHolder applet_resource_holder{}; |
| 199 | std::array<AbstractPad, MaxSupportedNpadIdTypes> abstracted_pads; | ||
| 200 | NpadVibration vibration_handler{}; | ||
| 201 | |||
| 218 | Kernel::KEvent* input_event{nullptr}; | 202 | Kernel::KEvent* input_event{nullptr}; |
| 219 | std::mutex* input_mutex{nullptr}; | 203 | std::mutex* input_mutex{nullptr}; |
| 220 | 204 | ||
| 221 | std::atomic<u64> press_state{}; | 205 | std::atomic<u64> press_state{}; |
| 222 | bool permit_vibration_session_enabled; | ||
| 223 | std::array<std::array<NpadControllerData, MaxSupportedNpadIdTypes>, AruidIndexMax> | 206 | std::array<std::array<NpadControllerData, MaxSupportedNpadIdTypes>, AruidIndexMax> |
| 224 | controller_data{}; | 207 | controller_data{}; |
| 225 | }; | 208 | }; |
diff --git a/src/hid_core/resources/npad/npad_types.h b/src/hid_core/resources/npad/npad_types.h index fd86c8e40..92700d69a 100644 --- a/src/hid_core/resources/npad/npad_types.h +++ b/src/hid_core/resources/npad/npad_types.h | |||
| @@ -8,6 +8,10 @@ | |||
| 8 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 9 | #include "hid_core/hid_types.h" | 9 | #include "hid_core/hid_types.h" |
| 10 | 10 | ||
| 11 | namespace Core::HID { | ||
| 12 | class EmulatedController; | ||
| 13 | } | ||
| 14 | |||
| 11 | namespace Service::HID { | 15 | namespace Service::HID { |
| 12 | static constexpr std::size_t MaxSupportedNpadIdTypes = 10; | 16 | static constexpr std::size_t MaxSupportedNpadIdTypes = 10; |
| 13 | static constexpr std::size_t StyleIndexCount = 7; | 17 | static constexpr std::size_t StyleIndexCount = 7; |
| @@ -348,7 +352,7 @@ struct IAbstractedPad { | |||
| 348 | u8 indicator; | 352 | u8 indicator; |
| 349 | std::vector<f32> virtual_six_axis_sensor_acceleration; | 353 | std::vector<f32> virtual_six_axis_sensor_acceleration; |
| 350 | std::vector<f32> virtual_six_axis_sensor_angle; | 354 | std::vector<f32> virtual_six_axis_sensor_angle; |
| 351 | u64 xcd_handle; | 355 | Core::HID::EmulatedController* xcd_handle; |
| 352 | u64 color; | 356 | u64 color; |
| 353 | }; | 357 | }; |
| 354 | } // namespace Service::HID | 358 | } // namespace Service::HID |
diff --git a/src/hid_core/resources/npad/npad_vibration.cpp b/src/hid_core/resources/npad/npad_vibration.cpp index 7056e8eab..05aad4c54 100644 --- a/src/hid_core/resources/npad/npad_vibration.cpp +++ b/src/hid_core/resources/npad/npad_vibration.cpp | |||
| @@ -77,4 +77,8 @@ Result NpadVibration::EndPermitVibrationSession() { | |||
| 77 | return ResultSuccess; | 77 | return ResultSuccess; |
| 78 | } | 78 | } |
| 79 | 79 | ||
| 80 | u64 NpadVibration::GetSessionAruid() const { | ||
| 81 | return session_aruid; | ||
| 82 | } | ||
| 83 | |||
| 80 | } // namespace Service::HID | 84 | } // namespace Service::HID |
diff --git a/src/hid_core/resources/npad/npad_vibration.h b/src/hid_core/resources/npad/npad_vibration.h index 0748aeffc..d5a95f2a0 100644 --- a/src/hid_core/resources/npad/npad_vibration.h +++ b/src/hid_core/resources/npad/npad_vibration.h | |||
| @@ -25,6 +25,8 @@ public: | |||
| 25 | Result BeginPermitVibrationSession(u64 aruid); | 25 | Result BeginPermitVibrationSession(u64 aruid); |
| 26 | Result EndPermitVibrationSession(); | 26 | Result EndPermitVibrationSession(); |
| 27 | 27 | ||
| 28 | u64 GetSessionAruid() const; | ||
| 29 | |||
| 28 | private: | 30 | private: |
| 29 | f32 volume{}; | 31 | f32 volume{}; |
| 30 | u64 session_aruid{}; | 32 | u64 session_aruid{}; |
diff --git a/src/hid_core/resources/vibration/gc_vibration_device.cpp b/src/hid_core/resources/vibration/gc_vibration_device.cpp index f01f81b9a..ad42b9d66 100644 --- a/src/hid_core/resources/vibration/gc_vibration_device.cpp +++ b/src/hid_core/resources/vibration/gc_vibration_device.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | #include "hid_core/frontend/emulated_controller.h" | ||
| 4 | #include "hid_core/hid_result.h" | 5 | #include "hid_core/hid_result.h" |
| 5 | #include "hid_core/resources/npad/npad_types.h" | 6 | #include "hid_core/resources/npad/npad_types.h" |
| 6 | #include "hid_core/resources/npad/npad_vibration.h" | 7 | #include "hid_core/resources/npad/npad_vibration.h" |
| @@ -10,24 +11,25 @@ namespace Service::HID { | |||
| 10 | 11 | ||
| 11 | NpadGcVibrationDevice::NpadGcVibrationDevice() {} | 12 | NpadGcVibrationDevice::NpadGcVibrationDevice() {} |
| 12 | 13 | ||
| 13 | Result NpadGcVibrationDevice::IncrementRefCounter() { | 14 | Result NpadGcVibrationDevice::Activate() { |
| 14 | if (ref_counter == 0 && is_mounted) { | 15 | if (ref_counter == 0 && is_mounted) { |
| 15 | f32 volume = 1.0f; | 16 | f32 volume = 1.0f; |
| 16 | const auto result = vibration_handler->GetVibrationVolume(volume); | 17 | const auto result = vibration_handler->GetVibrationVolume(volume); |
| 17 | if (result.IsSuccess()) { | 18 | if (result.IsSuccess()) { |
| 18 | // TODO: SendVibrationGcErmCommand | 19 | xcd_handle->SetVibration(adapter_slot, Core::HID::VibrationGcErmCommand::Stop); |
| 19 | } | 20 | } |
| 20 | } | 21 | } |
| 22 | |||
| 21 | ref_counter++; | 23 | ref_counter++; |
| 22 | return ResultSuccess; | 24 | return ResultSuccess; |
| 23 | } | 25 | } |
| 24 | 26 | ||
| 25 | Result NpadGcVibrationDevice::DecrementRefCounter() { | 27 | Result NpadGcVibrationDevice::Deactivate() { |
| 26 | if (ref_counter == 1 && !is_mounted) { | 28 | if (ref_counter == 1 && is_mounted) { |
| 27 | f32 volume = 1.0f; | 29 | f32 volume = 1.0f; |
| 28 | const auto result = vibration_handler->GetVibrationVolume(volume); | 30 | const auto result = vibration_handler->GetVibrationVolume(volume); |
| 29 | if (result.IsSuccess()) { | 31 | if (result.IsSuccess()) { |
| 30 | // TODO: SendVibrationGcErmCommand | 32 | xcd_handle->SetVibration(adapter_slot, Core::HID::VibrationGcErmCommand::Stop); |
| 31 | } | 33 | } |
| 32 | } | 34 | } |
| 33 | 35 | ||
| @@ -38,6 +40,48 @@ Result NpadGcVibrationDevice::DecrementRefCounter() { | |||
| 38 | return ResultSuccess; | 40 | return ResultSuccess; |
| 39 | } | 41 | } |
| 40 | 42 | ||
| 43 | Result NpadGcVibrationDevice::Mount(IAbstractedPad& abstracted_pad, u32 slot, | ||
| 44 | NpadVibration* handler) { | ||
| 45 | if (!abstracted_pad.internal_flags.is_connected) { | ||
| 46 | return ResultSuccess; | ||
| 47 | } | ||
| 48 | |||
| 49 | // TODO: This device doesn't use a xcd handle instead has an GC adapter handle. This is just to | ||
| 50 | // keep compatibility with the front end. | ||
| 51 | xcd_handle = abstracted_pad.xcd_handle; | ||
| 52 | adapter_slot = slot; | ||
| 53 | vibration_handler = handler; | ||
| 54 | is_mounted = true; | ||
| 55 | |||
| 56 | if (ref_counter == 0) { | ||
| 57 | return ResultSuccess; | ||
| 58 | } | ||
| 59 | |||
| 60 | f32 volume{1.0f}; | ||
| 61 | const auto result = vibration_handler->GetVibrationVolume(volume); | ||
| 62 | if (result.IsSuccess()) { | ||
| 63 | xcd_handle->SetVibration(adapter_slot, Core::HID::VibrationGcErmCommand::Stop); | ||
| 64 | } | ||
| 65 | |||
| 66 | return ResultSuccess; | ||
| 67 | } | ||
| 68 | |||
| 69 | Result NpadGcVibrationDevice::Unmount() { | ||
| 70 | if (ref_counter == 0 || !is_mounted) { | ||
| 71 | is_mounted = false; | ||
| 72 | return ResultSuccess; | ||
| 73 | } | ||
| 74 | |||
| 75 | f32 volume{1.0f}; | ||
| 76 | const auto result = vibration_handler->GetVibrationVolume(volume); | ||
| 77 | if (result.IsSuccess()) { | ||
| 78 | xcd_handle->SetVibration(adapter_slot, Core::HID::VibrationGcErmCommand::Stop); | ||
| 79 | } | ||
| 80 | |||
| 81 | is_mounted = false; | ||
| 82 | return ResultSuccess; | ||
| 83 | } | ||
| 84 | |||
| 41 | Result NpadGcVibrationDevice::SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command) { | 85 | Result NpadGcVibrationDevice::SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command) { |
| 42 | if (!is_mounted) { | 86 | if (!is_mounted) { |
| 43 | return ResultSuccess; | 87 | return ResultSuccess; |
| @@ -55,7 +99,7 @@ Result NpadGcVibrationDevice::SendVibrationGcErmCommand(Core::HID::VibrationGcEr | |||
| 55 | return ResultSuccess; | 99 | return ResultSuccess; |
| 56 | } | 100 | } |
| 57 | } | 101 | } |
| 58 | // TODO: SendVibrationGcErmCommand | 102 | xcd_handle->SetVibration(adapter_slot, command); |
| 59 | return ResultSuccess; | 103 | return ResultSuccess; |
| 60 | } | 104 | } |
| 61 | 105 | ||
diff --git a/src/hid_core/resources/vibration/gc_vibration_device.h b/src/hid_core/resources/vibration/gc_vibration_device.h index 87abca57d..c624cbb28 100644 --- a/src/hid_core/resources/vibration/gc_vibration_device.h +++ b/src/hid_core/resources/vibration/gc_vibration_device.h | |||
| @@ -20,12 +20,18 @@ class NpadGcVibrationDevice final : public NpadVibrationBase { | |||
| 20 | public: | 20 | public: |
| 21 | explicit NpadGcVibrationDevice(); | 21 | explicit NpadGcVibrationDevice(); |
| 22 | 22 | ||
| 23 | Result IncrementRefCounter() override; | 23 | Result Activate() override; |
| 24 | Result DecrementRefCounter() override; | 24 | Result Deactivate() override; |
| 25 | |||
| 26 | Result Mount(IAbstractedPad& abstracted_pad, u32 slot, NpadVibration* handler); | ||
| 27 | Result Unmount(); | ||
| 25 | 28 | ||
| 26 | Result SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command); | 29 | Result SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command); |
| 27 | 30 | ||
| 28 | Result GetActualVibrationGcErmCommand(Core::HID::VibrationGcErmCommand& out_command); | 31 | Result GetActualVibrationGcErmCommand(Core::HID::VibrationGcErmCommand& out_command); |
| 29 | Result SendVibrationNotificationPattern(Core::HID::VibrationGcErmCommand command); | 32 | Result SendVibrationNotificationPattern(Core::HID::VibrationGcErmCommand command); |
| 33 | |||
| 34 | private: | ||
| 35 | u32 adapter_slot; | ||
| 30 | }; | 36 | }; |
| 31 | } // namespace Service::HID | 37 | } // namespace Service::HID |
diff --git a/src/hid_core/resources/vibration/n64_vibration_device.cpp b/src/hid_core/resources/vibration/n64_vibration_device.cpp index 639f87abf..94ad37c8f 100644 --- a/src/hid_core/resources/vibration/n64_vibration_device.cpp +++ b/src/hid_core/resources/vibration/n64_vibration_device.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | #include "hid_core/frontend/emulated_controller.h" | ||
| 4 | #include "hid_core/hid_result.h" | 5 | #include "hid_core/hid_result.h" |
| 5 | #include "hid_core/resources/npad/npad_types.h" | 6 | #include "hid_core/resources/npad/npad_types.h" |
| 6 | #include "hid_core/resources/npad/npad_vibration.h" | 7 | #include "hid_core/resources/npad/npad_vibration.h" |
| @@ -10,12 +11,12 @@ namespace Service::HID { | |||
| 10 | 11 | ||
| 11 | NpadN64VibrationDevice::NpadN64VibrationDevice() {} | 12 | NpadN64VibrationDevice::NpadN64VibrationDevice() {} |
| 12 | 13 | ||
| 13 | Result NpadN64VibrationDevice::IncrementRefCounter() { | 14 | Result NpadN64VibrationDevice::Activate() { |
| 14 | if (ref_counter == 0 && is_mounted) { | 15 | if (ref_counter == 0 && is_mounted) { |
| 15 | f32 volume = 1.0f; | 16 | f32 volume = 1.0f; |
| 16 | const auto result = vibration_handler->GetVibrationVolume(volume); | 17 | const auto result = vibration_handler->GetVibrationVolume(volume); |
| 17 | if (result.IsSuccess()) { | 18 | if (result.IsSuccess()) { |
| 18 | // TODO: SendVibrationInBool | 19 | xcd_handle->SetVibration(false); |
| 19 | } | 20 | } |
| 20 | } | 21 | } |
| 21 | 22 | ||
| @@ -23,19 +24,12 @@ Result NpadN64VibrationDevice::IncrementRefCounter() { | |||
| 23 | return ResultSuccess; | 24 | return ResultSuccess; |
| 24 | } | 25 | } |
| 25 | 26 | ||
| 26 | Result NpadN64VibrationDevice::DecrementRefCounter() { | 27 | Result NpadN64VibrationDevice::Deactivate() { |
| 27 | if (ref_counter == 1) { | 28 | if (ref_counter == 1 && is_mounted) { |
| 28 | if (!is_mounted) { | ||
| 29 | ref_counter = 0; | ||
| 30 | if (is_mounted != false) { | ||
| 31 | // TODO: SendVibrationInBool | ||
| 32 | } | ||
| 33 | return ResultSuccess; | ||
| 34 | } | ||
| 35 | f32 volume = 1.0f; | 29 | f32 volume = 1.0f; |
| 36 | const auto result = vibration_handler->GetVibrationVolume(volume); | 30 | const auto result = vibration_handler->GetVibrationVolume(volume); |
| 37 | if (result.IsSuccess()) { | 31 | if (result.IsSuccess()) { |
| 38 | // TODO | 32 | xcd_handle->SetVibration(false); |
| 39 | } | 33 | } |
| 40 | } | 34 | } |
| 41 | 35 | ||
| @@ -46,6 +40,43 @@ Result NpadN64VibrationDevice::DecrementRefCounter() { | |||
| 46 | return ResultSuccess; | 40 | return ResultSuccess; |
| 47 | } | 41 | } |
| 48 | 42 | ||
| 43 | Result NpadN64VibrationDevice::Mount(IAbstractedPad& abstracted_pad, NpadVibration* handler) { | ||
| 44 | if (!abstracted_pad.internal_flags.is_connected) { | ||
| 45 | return ResultSuccess; | ||
| 46 | } | ||
| 47 | xcd_handle = abstracted_pad.xcd_handle; | ||
| 48 | vibration_handler = handler; | ||
| 49 | is_mounted = true; | ||
| 50 | |||
| 51 | if (ref_counter == 0) { | ||
| 52 | return ResultSuccess; | ||
| 53 | } | ||
| 54 | |||
| 55 | f32 volume{1.0f}; | ||
| 56 | const auto result = vibration_handler->GetVibrationVolume(volume); | ||
| 57 | if (result.IsSuccess()) { | ||
| 58 | xcd_handle->SetVibration(false); | ||
| 59 | } | ||
| 60 | |||
| 61 | return ResultSuccess; | ||
| 62 | } | ||
| 63 | |||
| 64 | Result NpadN64VibrationDevice::Unmount() { | ||
| 65 | if (ref_counter == 0 || !is_mounted) { | ||
| 66 | is_mounted = false; | ||
| 67 | return ResultSuccess; | ||
| 68 | } | ||
| 69 | |||
| 70 | f32 volume{1.0f}; | ||
| 71 | const auto result = vibration_handler->GetVibrationVolume(volume); | ||
| 72 | if (result.IsSuccess()) { | ||
| 73 | xcd_handle->SetVibration(false); | ||
| 74 | } | ||
| 75 | |||
| 76 | is_mounted = false; | ||
| 77 | return ResultSuccess; | ||
| 78 | } | ||
| 79 | |||
| 49 | Result NpadN64VibrationDevice::SendValueInBool(bool is_vibrating) { | 80 | Result NpadN64VibrationDevice::SendValueInBool(bool is_vibrating) { |
| 50 | if (ref_counter < 1) { | 81 | if (ref_counter < 1) { |
| 51 | return ResultVibrationNotInitialized; | 82 | return ResultVibrationNotInitialized; |
| @@ -56,7 +87,7 @@ Result NpadN64VibrationDevice::SendValueInBool(bool is_vibrating) { | |||
| 56 | if (result.IsError()) { | 87 | if (result.IsError()) { |
| 57 | return result; | 88 | return result; |
| 58 | } | 89 | } |
| 59 | // TODO: SendVibrationInBool | 90 | xcd_handle->SetVibration(false); |
| 60 | } | 91 | } |
| 61 | return ResultSuccess; | 92 | return ResultSuccess; |
| 62 | } | 93 | } |
diff --git a/src/hid_core/resources/vibration/n64_vibration_device.h b/src/hid_core/resources/vibration/n64_vibration_device.h index 54e6efc1a..09de7701c 100644 --- a/src/hid_core/resources/vibration/n64_vibration_device.h +++ b/src/hid_core/resources/vibration/n64_vibration_device.h | |||
| @@ -14,14 +14,18 @@ | |||
| 14 | 14 | ||
| 15 | namespace Service::HID { | 15 | namespace Service::HID { |
| 16 | class NpadVibration; | 16 | class NpadVibration; |
| 17 | struct IAbstractedPad; | ||
| 17 | 18 | ||
| 18 | /// Handles Npad request from HID interfaces | 19 | /// Handles Npad request from HID interfaces |
| 19 | class NpadN64VibrationDevice final : public NpadVibrationBase { | 20 | class NpadN64VibrationDevice final : public NpadVibrationBase { |
| 20 | public: | 21 | public: |
| 21 | explicit NpadN64VibrationDevice(); | 22 | explicit NpadN64VibrationDevice(); |
| 22 | 23 | ||
| 23 | Result IncrementRefCounter() override; | 24 | Result Activate() override; |
| 24 | Result DecrementRefCounter() override; | 25 | Result Deactivate() override; |
| 26 | |||
| 27 | Result Mount(IAbstractedPad& abstracted_pad, NpadVibration* handler); | ||
| 28 | Result Unmount(); | ||
| 25 | 29 | ||
| 26 | Result SendValueInBool(bool is_vibrating); | 30 | Result SendValueInBool(bool is_vibrating); |
| 27 | Result SendVibrationNotificationPattern(u32 pattern); | 31 | Result SendVibrationNotificationPattern(u32 pattern); |
diff --git a/src/hid_core/resources/vibration/vibration_base.cpp b/src/hid_core/resources/vibration/vibration_base.cpp index 350f349c2..f28d30406 100644 --- a/src/hid_core/resources/vibration/vibration_base.cpp +++ b/src/hid_core/resources/vibration/vibration_base.cpp | |||
| @@ -10,12 +10,12 @@ namespace Service::HID { | |||
| 10 | 10 | ||
| 11 | NpadVibrationBase::NpadVibrationBase() {} | 11 | NpadVibrationBase::NpadVibrationBase() {} |
| 12 | 12 | ||
| 13 | Result NpadVibrationBase::IncrementRefCounter() { | 13 | Result NpadVibrationBase::Activate() { |
| 14 | ref_counter++; | 14 | ref_counter++; |
| 15 | return ResultSuccess; | 15 | return ResultSuccess; |
| 16 | } | 16 | } |
| 17 | 17 | ||
| 18 | Result NpadVibrationBase::DecrementRefCounter() { | 18 | Result NpadVibrationBase::Deactivate() { |
| 19 | if (ref_counter > 0) { | 19 | if (ref_counter > 0) { |
| 20 | ref_counter--; | 20 | ref_counter--; |
| 21 | } | 21 | } |
diff --git a/src/hid_core/resources/vibration/vibration_base.h b/src/hid_core/resources/vibration/vibration_base.h index c6c5fc4d9..69c26e669 100644 --- a/src/hid_core/resources/vibration/vibration_base.h +++ b/src/hid_core/resources/vibration/vibration_base.h | |||
| @@ -6,6 +6,10 @@ | |||
| 6 | #include "common/common_types.h" | 6 | #include "common/common_types.h" |
| 7 | #include "core/hle/result.h" | 7 | #include "core/hle/result.h" |
| 8 | 8 | ||
| 9 | namespace Core::HID { | ||
| 10 | class EmulatedController; | ||
| 11 | } | ||
| 12 | |||
| 9 | namespace Service::HID { | 13 | namespace Service::HID { |
| 10 | class NpadVibration; | 14 | class NpadVibration; |
| 11 | 15 | ||
| @@ -14,13 +18,13 @@ class NpadVibrationBase { | |||
| 14 | public: | 18 | public: |
| 15 | explicit NpadVibrationBase(); | 19 | explicit NpadVibrationBase(); |
| 16 | 20 | ||
| 17 | virtual Result IncrementRefCounter(); | 21 | virtual Result Activate(); |
| 18 | virtual Result DecrementRefCounter(); | 22 | virtual Result Deactivate(); |
| 19 | 23 | ||
| 20 | bool IsVibrationMounted() const; | 24 | bool IsVibrationMounted() const; |
| 21 | 25 | ||
| 22 | protected: | 26 | protected: |
| 23 | u64 xcd_handle{}; | 27 | Core::HID::EmulatedController* xcd_handle{nullptr}; |
| 24 | s32 ref_counter{}; | 28 | s32 ref_counter{}; |
| 25 | bool is_mounted{}; | 29 | bool is_mounted{}; |
| 26 | NpadVibration* vibration_handler{nullptr}; | 30 | NpadVibration* vibration_handler{nullptr}; |
diff --git a/src/hid_core/resources/vibration/vibration_device.cpp b/src/hid_core/resources/vibration/vibration_device.cpp index 888c3a7ed..08b14591f 100644 --- a/src/hid_core/resources/vibration/vibration_device.cpp +++ b/src/hid_core/resources/vibration/vibration_device.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | #include "hid_core/frontend/emulated_controller.h" | ||
| 4 | #include "hid_core/hid_result.h" | 5 | #include "hid_core/hid_result.h" |
| 5 | #include "hid_core/resources/npad/npad_types.h" | 6 | #include "hid_core/resources/npad/npad_types.h" |
| 6 | #include "hid_core/resources/npad/npad_vibration.h" | 7 | #include "hid_core/resources/npad/npad_vibration.h" |
| @@ -10,12 +11,30 @@ namespace Service::HID { | |||
| 10 | 11 | ||
| 11 | NpadVibrationDevice::NpadVibrationDevice() {} | 12 | NpadVibrationDevice::NpadVibrationDevice() {} |
| 12 | 13 | ||
| 13 | Result NpadVibrationDevice::IncrementRefCounter() { | 14 | Result NpadVibrationDevice::Activate() { |
| 15 | if (ref_counter == 0 && is_mounted) { | ||
| 16 | f32 volume = 1.0f; | ||
| 17 | const auto result = vibration_handler->GetVibrationVolume(volume); | ||
| 18 | if (result.IsSuccess()) { | ||
| 19 | xcd_handle->SetVibration(device_index, Core::HID::DEFAULT_VIBRATION_VALUE); | ||
| 20 | // TODO: SendNotificationPattern; | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 14 | ref_counter++; | 24 | ref_counter++; |
| 15 | return ResultSuccess; | 25 | return ResultSuccess; |
| 16 | } | 26 | } |
| 17 | 27 | ||
| 18 | Result NpadVibrationDevice::DecrementRefCounter() { | 28 | Result NpadVibrationDevice::Deactivate() { |
| 29 | if (ref_counter == 1 && is_mounted) { | ||
| 30 | f32 volume = 1.0f; | ||
| 31 | const auto result = vibration_handler->GetVibrationVolume(volume); | ||
| 32 | if (result.IsSuccess()) { | ||
| 33 | xcd_handle->SetVibration(device_index, Core::HID::DEFAULT_VIBRATION_VALUE); | ||
| 34 | // TODO: SendNotificationPattern; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 19 | if (ref_counter > 0) { | 38 | if (ref_counter > 0) { |
| 20 | ref_counter--; | 39 | ref_counter--; |
| 21 | } | 40 | } |
| @@ -23,6 +42,45 @@ Result NpadVibrationDevice::DecrementRefCounter() { | |||
| 23 | return ResultSuccess; | 42 | return ResultSuccess; |
| 24 | } | 43 | } |
| 25 | 44 | ||
| 45 | Result NpadVibrationDevice::Mount(IAbstractedPad& abstracted_pad, Core::HID::DeviceIndex index, | ||
| 46 | NpadVibration* handler) { | ||
| 47 | if (!abstracted_pad.internal_flags.is_connected) { | ||
| 48 | return ResultSuccess; | ||
| 49 | } | ||
| 50 | xcd_handle = abstracted_pad.xcd_handle; | ||
| 51 | device_index = index; | ||
| 52 | vibration_handler = handler; | ||
| 53 | is_mounted = true; | ||
| 54 | |||
| 55 | if (ref_counter == 0) { | ||
| 56 | return ResultSuccess; | ||
| 57 | } | ||
| 58 | |||
| 59 | f32 volume{1.0f}; | ||
| 60 | const auto result = vibration_handler->GetVibrationVolume(volume); | ||
| 61 | if (result.IsSuccess()) { | ||
| 62 | xcd_handle->SetVibration(false); | ||
| 63 | } | ||
| 64 | |||
| 65 | return ResultSuccess; | ||
| 66 | } | ||
| 67 | |||
| 68 | Result NpadVibrationDevice::Unmount() { | ||
| 69 | if (ref_counter == 0 || !is_mounted) { | ||
| 70 | is_mounted = false; | ||
| 71 | return ResultSuccess; | ||
| 72 | } | ||
| 73 | |||
| 74 | f32 volume{1.0f}; | ||
| 75 | const auto result = vibration_handler->GetVibrationVolume(volume); | ||
| 76 | if (result.IsSuccess()) { | ||
| 77 | xcd_handle->SetVibration(device_index, Core::HID::DEFAULT_VIBRATION_VALUE); | ||
| 78 | } | ||
| 79 | |||
| 80 | is_mounted = false; | ||
| 81 | return ResultSuccess; | ||
| 82 | } | ||
| 83 | |||
| 26 | Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue& value) { | 84 | Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue& value) { |
| 27 | if (ref_counter == 0) { | 85 | if (ref_counter == 0) { |
| 28 | return ResultVibrationNotInitialized; | 86 | return ResultVibrationNotInitialized; |
| @@ -37,7 +95,7 @@ Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue& | |||
| 37 | return result; | 95 | return result; |
| 38 | } | 96 | } |
| 39 | if (volume <= 0.0f) { | 97 | if (volume <= 0.0f) { |
| 40 | // TODO: SendVibrationValue | 98 | xcd_handle->SetVibration(device_index, Core::HID::DEFAULT_VIBRATION_VALUE); |
| 41 | return ResultSuccess; | 99 | return ResultSuccess; |
| 42 | } | 100 | } |
| 43 | 101 | ||
| @@ -45,7 +103,7 @@ Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue& | |||
| 45 | vibration_value.high_amplitude *= volume; | 103 | vibration_value.high_amplitude *= volume; |
| 46 | vibration_value.low_amplitude *= volume; | 104 | vibration_value.low_amplitude *= volume; |
| 47 | 105 | ||
| 48 | // TODO: SendVibrationValue | 106 | xcd_handle->SetVibration(device_index, vibration_value); |
| 49 | return ResultSuccess; | 107 | return ResultSuccess; |
| 50 | } | 108 | } |
| 51 | 109 | ||
| @@ -63,11 +121,11 @@ Result NpadVibrationDevice::SendVibrationNotificationPattern([[maybe_unused]] u3 | |||
| 63 | pattern = 0; | 121 | pattern = 0; |
| 64 | } | 122 | } |
| 65 | 123 | ||
| 66 | // return xcd_handle->SendVibrationNotificationPattern(pattern); | 124 | // TODO: SendVibrationNotificationPattern; |
| 67 | return ResultSuccess; | 125 | return ResultSuccess; |
| 68 | } | 126 | } |
| 69 | 127 | ||
| 70 | Result NpadVibrationDevice::GetActualVibrationValue(Core::HID::VibrationValue& out_value) { | 128 | Result NpadVibrationDevice::GetActualVibrationValue(Core::HID::VibrationValue& out_value) const { |
| 71 | if (ref_counter < 1) { | 129 | if (ref_counter < 1) { |
| 72 | return ResultVibrationNotInitialized; | 130 | return ResultVibrationNotInitialized; |
| 73 | } | 131 | } |
| @@ -77,7 +135,7 @@ Result NpadVibrationDevice::GetActualVibrationValue(Core::HID::VibrationValue& o | |||
| 77 | return ResultSuccess; | 135 | return ResultSuccess; |
| 78 | } | 136 | } |
| 79 | 137 | ||
| 80 | // TODO: SendVibrationValue | 138 | out_value = xcd_handle->GetActualVibrationValue(device_index); |
| 81 | return ResultSuccess; | 139 | return ResultSuccess; |
| 82 | } | 140 | } |
| 83 | 141 | ||
diff --git a/src/hid_core/resources/vibration/vibration_device.h b/src/hid_core/resources/vibration/vibration_device.h index 3574ad60b..c2f9891d3 100644 --- a/src/hid_core/resources/vibration/vibration_device.h +++ b/src/hid_core/resources/vibration/vibration_device.h | |||
| @@ -12,6 +12,10 @@ | |||
| 12 | #include "hid_core/resources/npad/npad_types.h" | 12 | #include "hid_core/resources/npad/npad_types.h" |
| 13 | #include "hid_core/resources/vibration/vibration_base.h" | 13 | #include "hid_core/resources/vibration/vibration_base.h" |
| 14 | 14 | ||
| 15 | namespace Core::HID { | ||
| 16 | enum class DeviceIndex : u8; | ||
| 17 | } | ||
| 18 | |||
| 15 | namespace Service::HID { | 19 | namespace Service::HID { |
| 16 | class NpadVibration; | 20 | class NpadVibration; |
| 17 | 21 | ||
| @@ -20,16 +24,20 @@ class NpadVibrationDevice final : public NpadVibrationBase { | |||
| 20 | public: | 24 | public: |
| 21 | explicit NpadVibrationDevice(); | 25 | explicit NpadVibrationDevice(); |
| 22 | 26 | ||
| 23 | Result IncrementRefCounter(); | 27 | Result Activate(); |
| 24 | Result DecrementRefCounter(); | 28 | Result Deactivate(); |
| 29 | |||
| 30 | Result Mount(IAbstractedPad& abstracted_pad, Core::HID::DeviceIndex index, | ||
| 31 | NpadVibration* handler); | ||
| 32 | Result Unmount(); | ||
| 25 | 33 | ||
| 26 | Result SendVibrationValue(const Core::HID::VibrationValue& value); | 34 | Result SendVibrationValue(const Core::HID::VibrationValue& value); |
| 27 | Result SendVibrationNotificationPattern(u32 pattern); | 35 | Result SendVibrationNotificationPattern(u32 pattern); |
| 28 | 36 | ||
| 29 | Result GetActualVibrationValue(Core::HID::VibrationValue& out_value); | 37 | Result GetActualVibrationValue(Core::HID::VibrationValue& out_value) const; |
| 30 | 38 | ||
| 31 | private: | 39 | private: |
| 32 | u32 device_index{}; | 40 | Core::HID::DeviceIndex device_index{}; |
| 33 | }; | 41 | }; |
| 34 | 42 | ||
| 35 | } // namespace Service::HID | 43 | } // namespace Service::HID |
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 140a7fe5d..568775027 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp | |||
| @@ -122,9 +122,8 @@ void ConfigurePerGameAddons::LoadConfiguration() { | |||
| 122 | 122 | ||
| 123 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 123 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 124 | 124 | ||
| 125 | for (const auto& patch : pm.GetPatchVersionNames(update_raw)) { | 125 | for (const auto& patch : pm.GetPatches(update_raw)) { |
| 126 | const auto name = | 126 | const auto name = QString::fromStdString(patch.name); |
| 127 | QString::fromStdString(patch.first).replace(QStringLiteral("[D] "), QString{}); | ||
| 128 | 127 | ||
| 129 | auto* const first_item = new QStandardItem; | 128 | auto* const first_item = new QStandardItem; |
| 130 | first_item->setText(name); | 129 | first_item->setText(name); |
| @@ -136,7 +135,7 @@ void ConfigurePerGameAddons::LoadConfiguration() { | |||
| 136 | first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked); | 135 | first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked); |
| 137 | 136 | ||
| 138 | list_items.push_back(QList<QStandardItem*>{ | 137 | list_items.push_back(QList<QStandardItem*>{ |
| 139 | first_item, new QStandardItem{QString::fromStdString(patch.second)}}); | 138 | first_item, new QStandardItem{QString::fromStdString(patch.version)}}); |
| 140 | item_model->appendRow(list_items.back()); | 139 | item_model->appendRow(list_items.back()); |
| 141 | } | 140 | } |
| 142 | 141 | ||
diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp index d898d8acc..6b1f4527b 100644 --- a/src/yuzu/configuration/configure_vibration.cpp +++ b/src/yuzu/configuration/configure_vibration.cpp | |||
| @@ -116,8 +116,8 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type | |||
| 116 | .high_amplitude = 1.0f, | 116 | .high_amplitude = 1.0f, |
| 117 | .high_frequency = 320.0f, | 117 | .high_frequency = 320.0f, |
| 118 | }; | 118 | }; |
| 119 | controller->SetVibration(0, vibration); | 119 | controller->SetVibration(Core::HID::DeviceIndex::Left, vibration); |
| 120 | controller->SetVibration(1, vibration); | 120 | controller->SetVibration(Core::HID::DeviceIndex::Right, vibration); |
| 121 | 121 | ||
| 122 | // Restore previous values | 122 | // Restore previous values |
| 123 | player.vibration_enabled = old_vibration_enabled; | 123 | player.vibration_enabled = old_vibration_enabled; |
| @@ -127,7 +127,7 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type | |||
| 127 | void ConfigureVibration::StopVibrations() { | 127 | void ConfigureVibration::StopVibrations() { |
| 128 | for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { | 128 | for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { |
| 129 | auto controller = hid_core.GetEmulatedControllerByIndex(i); | 129 | auto controller = hid_core.GetEmulatedControllerByIndex(i); |
| 130 | controller->SetVibration(0, Core::HID::DEFAULT_VIBRATION_VALUE); | 130 | controller->SetVibration(Core::HID::DeviceIndex::Left, Core::HID::DEFAULT_VIBRATION_VALUE); |
| 131 | controller->SetVibration(1, Core::HID::DEFAULT_VIBRATION_VALUE); | 131 | controller->SetVibration(Core::HID::DeviceIndex::Right, Core::HID::DEFAULT_VIBRATION_VALUE); |
| 132 | } | 132 | } |
| 133 | } | 133 | } |
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index dc006832e..9747e3fb3 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -164,18 +164,19 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, | |||
| 164 | QString out; | 164 | QString out; |
| 165 | FileSys::VirtualFile update_raw; | 165 | FileSys::VirtualFile update_raw; |
| 166 | loader.ReadUpdateRaw(update_raw); | 166 | loader.ReadUpdateRaw(update_raw); |
| 167 | for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) { | 167 | for (const auto& patch : patch_manager.GetPatches(update_raw)) { |
| 168 | const bool is_update = kv.first == "Update" || kv.first == "[D] Update"; | 168 | const bool is_update = patch.name == "Update"; |
| 169 | if (!updatable && is_update) { | 169 | if (!updatable && is_update) { |
| 170 | continue; | 170 | continue; |
| 171 | } | 171 | } |
| 172 | 172 | ||
| 173 | const QString type = QString::fromStdString(kv.first); | 173 | const QString type = |
| 174 | QString::fromStdString(patch.enabled ? patch.name : "[D] " + patch.name); | ||
| 174 | 175 | ||
| 175 | if (kv.second.empty()) { | 176 | if (patch.version.empty()) { |
| 176 | out.append(QStringLiteral("%1\n").arg(type)); | 177 | out.append(QStringLiteral("%1\n").arg(type)); |
| 177 | } else { | 178 | } else { |
| 178 | auto ver = kv.second; | 179 | auto ver = patch.version; |
| 179 | 180 | ||
| 180 | // Display container name for packed updates | 181 | // Display container name for packed updates |
| 181 | if (is_update && ver == "PACKED") { | 182 | if (is_update && ver == "PACKED") { |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 3c562e3b2..05bd4174c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -47,6 +47,7 @@ | |||
| 47 | #include "core/hle/service/am/applet_oe.h" | 47 | #include "core/hle/service/am/applet_oe.h" |
| 48 | #include "core/hle/service/am/applets/applets.h" | 48 | #include "core/hle/service/am/applets/applets.h" |
| 49 | #include "core/hle/service/set/system_settings_server.h" | 49 | #include "core/hle/service/set/system_settings_server.h" |
| 50 | #include "frontend_common/content_manager.h" | ||
| 50 | #include "hid_core/frontend/emulated_controller.h" | 51 | #include "hid_core/frontend/emulated_controller.h" |
| 51 | #include "hid_core/hid_core.h" | 52 | #include "hid_core/hid_core.h" |
| 52 | #include "yuzu/multiplayer/state.h" | 53 | #include "yuzu/multiplayer/state.h" |
| @@ -2476,10 +2477,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT | |||
| 2476 | } | 2477 | } |
| 2477 | 2478 | ||
| 2478 | void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) { | 2479 | void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) { |
| 2479 | const auto& fs_controller = system->GetFileSystemController(); | 2480 | const auto res = |
| 2480 | const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) || | 2481 | ContentManager::RemoveBaseContent(system->GetFileSystemController(), program_id); |
| 2481 | fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id); | ||
| 2482 | |||
| 2483 | if (res) { | 2482 | if (res) { |
| 2484 | QMessageBox::information(this, tr("Successfully Removed"), | 2483 | QMessageBox::information(this, tr("Successfully Removed"), |
| 2485 | tr("Successfully removed the installed base game.")); | 2484 | tr("Successfully removed the installed base game.")); |
| @@ -2491,11 +2490,7 @@ void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) { | |||
| 2491 | } | 2490 | } |
| 2492 | 2491 | ||
| 2493 | void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { | 2492 | void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { |
| 2494 | const auto update_id = program_id | 0x800; | 2493 | const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id); |
| 2495 | const auto& fs_controller = system->GetFileSystemController(); | ||
| 2496 | const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || | ||
| 2497 | fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); | ||
| 2498 | |||
| 2499 | if (res) { | 2494 | if (res) { |
| 2500 | QMessageBox::information(this, tr("Successfully Removed"), | 2495 | QMessageBox::information(this, tr("Successfully Removed"), |
| 2501 | tr("Successfully removed the installed update.")); | 2496 | tr("Successfully removed the installed update.")); |
| @@ -2506,22 +2501,7 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { | |||
| 2506 | } | 2501 | } |
| 2507 | 2502 | ||
| 2508 | void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) { | 2503 | void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) { |
| 2509 | u32 count{}; | 2504 | const size_t count = ContentManager::RemoveAllDLC(system.get(), program_id); |
| 2510 | const auto& fs_controller = system->GetFileSystemController(); | ||
| 2511 | const auto dlc_entries = system->GetContentProvider().ListEntriesFilter( | ||
| 2512 | FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); | ||
| 2513 | |||
| 2514 | for (const auto& entry : dlc_entries) { | ||
| 2515 | if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { | ||
| 2516 | const auto res = | ||
| 2517 | fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) || | ||
| 2518 | fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id); | ||
| 2519 | if (res) { | ||
| 2520 | ++count; | ||
| 2521 | } | ||
| 2522 | } | ||
| 2523 | } | ||
| 2524 | |||
| 2525 | if (count == 0) { | 2505 | if (count == 0) { |
| 2526 | QMessageBox::warning(this, GetGameListErrorRemoving(type), | 2506 | QMessageBox::warning(this, GetGameListErrorRemoving(type), |
| 2527 | tr("There are no DLC installed for this title.")); | 2507 | tr("There are no DLC installed for this title.")); |
| @@ -3290,12 +3270,21 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 3290 | install_progress->setLabelText( | 3270 | install_progress->setLabelText( |
| 3291 | tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName())); | 3271 | tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName())); |
| 3292 | 3272 | ||
| 3293 | QFuture<InstallResult> future; | 3273 | QFuture<ContentManager::InstallResult> future; |
| 3294 | InstallResult result; | 3274 | ContentManager::InstallResult result; |
| 3295 | 3275 | ||
| 3296 | if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { | 3276 | if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { |
| 3297 | 3277 | const auto progress_callback = [this](size_t size, size_t progress) { | |
| 3298 | future = QtConcurrent::run([this, &file] { return InstallNSP(file); }); | 3278 | emit UpdateInstallProgress(); |
| 3279 | if (install_progress->wasCanceled()) { | ||
| 3280 | return true; | ||
| 3281 | } | ||
| 3282 | return false; | ||
| 3283 | }; | ||
| 3284 | future = QtConcurrent::run([this, &file, progress_callback] { | ||
| 3285 | return ContentManager::InstallNSP(system.get(), vfs.get(), file.toStdString(), | ||
| 3286 | progress_callback); | ||
| 3287 | }); | ||
| 3299 | 3288 | ||
| 3300 | while (!future.isFinished()) { | 3289 | while (!future.isFinished()) { |
| 3301 | QCoreApplication::processEvents(); | 3290 | QCoreApplication::processEvents(); |
| @@ -3311,16 +3300,16 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 3311 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); | 3300 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); |
| 3312 | 3301 | ||
| 3313 | switch (result) { | 3302 | switch (result) { |
| 3314 | case InstallResult::Success: | 3303 | case ContentManager::InstallResult::Success: |
| 3315 | new_files.append(QFileInfo(file).fileName()); | 3304 | new_files.append(QFileInfo(file).fileName()); |
| 3316 | break; | 3305 | break; |
| 3317 | case InstallResult::Overwrite: | 3306 | case ContentManager::InstallResult::Overwrite: |
| 3318 | overwritten_files.append(QFileInfo(file).fileName()); | 3307 | overwritten_files.append(QFileInfo(file).fileName()); |
| 3319 | break; | 3308 | break; |
| 3320 | case InstallResult::Failure: | 3309 | case ContentManager::InstallResult::Failure: |
| 3321 | failed_files.append(QFileInfo(file).fileName()); | 3310 | failed_files.append(QFileInfo(file).fileName()); |
| 3322 | break; | 3311 | break; |
| 3323 | case InstallResult::BaseInstallAttempted: | 3312 | case ContentManager::InstallResult::BaseInstallAttempted: |
| 3324 | failed_files.append(QFileInfo(file).fileName()); | 3313 | failed_files.append(QFileInfo(file).fileName()); |
| 3325 | detected_base_install = true; | 3314 | detected_base_install = true; |
| 3326 | break; | 3315 | break; |
| @@ -3354,96 +3343,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 3354 | ui->action_Install_File_NAND->setEnabled(true); | 3343 | ui->action_Install_File_NAND->setEnabled(true); |
| 3355 | } | 3344 | } |
| 3356 | 3345 | ||
| 3357 | InstallResult GMainWindow::InstallNSP(const QString& filename) { | 3346 | ContentManager::InstallResult GMainWindow::InstallNCA(const QString& filename) { |
| 3358 | const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, | ||
| 3359 | const FileSys::VirtualFile& dest, std::size_t block_size) { | ||
| 3360 | if (src == nullptr || dest == nullptr) { | ||
| 3361 | return false; | ||
| 3362 | } | ||
| 3363 | if (!dest->Resize(src->GetSize())) { | ||
| 3364 | return false; | ||
| 3365 | } | ||
| 3366 | |||
| 3367 | std::vector<u8> buffer(CopyBufferSize); | ||
| 3368 | |||
| 3369 | for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { | ||
| 3370 | if (install_progress->wasCanceled()) { | ||
| 3371 | dest->Resize(0); | ||
| 3372 | return false; | ||
| 3373 | } | ||
| 3374 | |||
| 3375 | emit UpdateInstallProgress(); | ||
| 3376 | |||
| 3377 | const auto read = src->Read(buffer.data(), buffer.size(), i); | ||
| 3378 | dest->Write(buffer.data(), read, i); | ||
| 3379 | } | ||
| 3380 | return true; | ||
| 3381 | }; | ||
| 3382 | |||
| 3383 | std::shared_ptr<FileSys::NSP> nsp; | ||
| 3384 | if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { | ||
| 3385 | nsp = std::make_shared<FileSys::NSP>( | ||
| 3386 | vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | ||
| 3387 | if (nsp->IsExtractedType()) { | ||
| 3388 | return InstallResult::Failure; | ||
| 3389 | } | ||
| 3390 | } else { | ||
| 3391 | return InstallResult::Failure; | ||
| 3392 | } | ||
| 3393 | |||
| 3394 | if (nsp->GetStatus() != Loader::ResultStatus::Success) { | ||
| 3395 | return InstallResult::Failure; | ||
| 3396 | } | ||
| 3397 | const auto res = system->GetFileSystemController().GetUserNANDContents()->InstallEntry( | ||
| 3398 | *nsp, true, qt_raw_copy); | ||
| 3399 | switch (res) { | ||
| 3400 | case FileSys::InstallResult::Success: | ||
| 3401 | return InstallResult::Success; | ||
| 3402 | case FileSys::InstallResult::OverwriteExisting: | ||
| 3403 | return InstallResult::Overwrite; | ||
| 3404 | case FileSys::InstallResult::ErrorBaseInstall: | ||
| 3405 | return InstallResult::BaseInstallAttempted; | ||
| 3406 | default: | ||
| 3407 | return InstallResult::Failure; | ||
| 3408 | } | ||
| 3409 | } | ||
| 3410 | |||
| 3411 | InstallResult GMainWindow::InstallNCA(const QString& filename) { | ||
| 3412 | const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, | ||
| 3413 | const FileSys::VirtualFile& dest, std::size_t block_size) { | ||
| 3414 | if (src == nullptr || dest == nullptr) { | ||
| 3415 | return false; | ||
| 3416 | } | ||
| 3417 | if (!dest->Resize(src->GetSize())) { | ||
| 3418 | return false; | ||
| 3419 | } | ||
| 3420 | |||
| 3421 | std::vector<u8> buffer(CopyBufferSize); | ||
| 3422 | |||
| 3423 | for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { | ||
| 3424 | if (install_progress->wasCanceled()) { | ||
| 3425 | dest->Resize(0); | ||
| 3426 | return false; | ||
| 3427 | } | ||
| 3428 | |||
| 3429 | emit UpdateInstallProgress(); | ||
| 3430 | |||
| 3431 | const auto read = src->Read(buffer.data(), buffer.size(), i); | ||
| 3432 | dest->Write(buffer.data(), read, i); | ||
| 3433 | } | ||
| 3434 | return true; | ||
| 3435 | }; | ||
| 3436 | |||
| 3437 | const auto nca = | ||
| 3438 | std::make_shared<FileSys::NCA>(vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | ||
| 3439 | const auto id = nca->GetStatus(); | ||
| 3440 | |||
| 3441 | // Game updates necessary are missing base RomFS | ||
| 3442 | if (id != Loader::ResultStatus::Success && | ||
| 3443 | id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { | ||
| 3444 | return InstallResult::Failure; | ||
| 3445 | } | ||
| 3446 | |||
| 3447 | const QStringList tt_options{tr("System Application"), | 3347 | const QStringList tt_options{tr("System Application"), |
| 3448 | tr("System Archive"), | 3348 | tr("System Archive"), |
| 3449 | tr("System Application Update"), | 3349 | tr("System Application Update"), |
| @@ -3464,7 +3364,7 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) { | |||
| 3464 | if (!ok || index == -1) { | 3364 | if (!ok || index == -1) { |
| 3465 | QMessageBox::warning(this, tr("Failed to Install"), | 3365 | QMessageBox::warning(this, tr("Failed to Install"), |
| 3466 | tr("The title type you selected for the NCA is invalid.")); | 3366 | tr("The title type you selected for the NCA is invalid.")); |
| 3467 | return InstallResult::Failure; | 3367 | return ContentManager::InstallResult::Failure; |
| 3468 | } | 3368 | } |
| 3469 | 3369 | ||
| 3470 | // If index is equal to or past Game, add the jump in TitleType. | 3370 | // If index is equal to or past Game, add the jump in TitleType. |
| @@ -3478,15 +3378,15 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) { | |||
| 3478 | auto* registered_cache = is_application ? fs_controller.GetUserNANDContents() | 3378 | auto* registered_cache = is_application ? fs_controller.GetUserNANDContents() |
| 3479 | : fs_controller.GetSystemNANDContents(); | 3379 | : fs_controller.GetSystemNANDContents(); |
| 3480 | 3380 | ||
| 3481 | const auto res = registered_cache->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), | 3381 | const auto progress_callback = [this](size_t size, size_t progress) { |
| 3482 | true, qt_raw_copy); | 3382 | emit UpdateInstallProgress(); |
| 3483 | if (res == FileSys::InstallResult::Success) { | 3383 | if (install_progress->wasCanceled()) { |
| 3484 | return InstallResult::Success; | 3384 | return true; |
| 3485 | } else if (res == FileSys::InstallResult::OverwriteExisting) { | 3385 | } |
| 3486 | return InstallResult::Overwrite; | 3386 | return false; |
| 3487 | } else { | 3387 | }; |
| 3488 | return InstallResult::Failure; | 3388 | return ContentManager::InstallNCA(vfs.get(), filename.toStdString(), registered_cache, |
| 3489 | } | 3389 | static_cast<FileSys::TitleType>(index), progress_callback); |
| 3490 | } | 3390 | } |
| 3491 | 3391 | ||
| 3492 | void GMainWindow::OnMenuRecentFile() { | 3392 | void GMainWindow::OnMenuRecentFile() { |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index f3276da64..280fae5c3 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -16,6 +16,7 @@ | |||
| 16 | #include "common/announce_multiplayer_room.h" | 16 | #include "common/announce_multiplayer_room.h" |
| 17 | #include "common/common_types.h" | 17 | #include "common/common_types.h" |
| 18 | #include "configuration/qt_config.h" | 18 | #include "configuration/qt_config.h" |
| 19 | #include "frontend_common/content_manager.h" | ||
| 19 | #include "input_common/drivers/tas_input.h" | 20 | #include "input_common/drivers/tas_input.h" |
| 20 | #include "yuzu/compatibility_list.h" | 21 | #include "yuzu/compatibility_list.h" |
| 21 | #include "yuzu/hotkeys.h" | 22 | #include "yuzu/hotkeys.h" |
| @@ -124,13 +125,6 @@ enum class EmulatedDirectoryTarget { | |||
| 124 | SDMC, | 125 | SDMC, |
| 125 | }; | 126 | }; |
| 126 | 127 | ||
| 127 | enum class InstallResult { | ||
| 128 | Success, | ||
| 129 | Overwrite, | ||
| 130 | Failure, | ||
| 131 | BaseInstallAttempted, | ||
| 132 | }; | ||
| 133 | |||
| 134 | enum class ReinitializeKeyBehavior { | 128 | enum class ReinitializeKeyBehavior { |
| 135 | NoWarning, | 129 | NoWarning, |
| 136 | Warning, | 130 | Warning, |
| @@ -427,8 +421,7 @@ private: | |||
| 427 | void RemoveCacheStorage(u64 program_id); | 421 | void RemoveCacheStorage(u64 program_id); |
| 428 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, | 422 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, |
| 429 | u64* selected_title_id, u8* selected_content_record_type); | 423 | u64* selected_title_id, u8* selected_content_record_type); |
| 430 | InstallResult InstallNSP(const QString& filename); | 424 | ContentManager::InstallResult InstallNCA(const QString& filename); |
| 431 | InstallResult InstallNCA(const QString& filename); | ||
| 432 | void MigrateConfigFiles(); | 425 | void MigrateConfigFiles(); |
| 433 | void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, | 426 | void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, |
| 434 | std::string_view gpu_vendor = {}); | 427 | std::string_view gpu_vendor = {}); |